package cmd import ( "encoding/xml" "fmt" "log" "accorder/pkg/calibre" "github.com/beevik/etree" "github.com/spf13/cobra" "github.com/spf13/viper" ) // best zotero rdf documentation: // https://github.com/zotero/translators/blob/master/Zotero%20RDF.js // type ZoteroItem struct { XMLName xml.Name `xml:"zoteroItem"` FilePath string `xml:"filePath"` MimeType string `xml:"mimeType"` Publisher string `xml:"publisher,omitempty"` Authors []ZoteroAuthor `xml:"authors"` Title string `xml:"title"` Description string `xml:"description,omitempty"` } type ZoteroAuthor struct { XMLName xml.Name `xml:"authors"` Author string `xml:"author"` } var buildCmd = &cobra.Command{ Use: "build", Short: "Build standalone, portable webapp from Calibre library.", Long: `Build searchable, standalone, portable webapp against the local Calibre library including all the metadata needed. It creates BROWSE_LIBRARY.html in root directory of the local Calibre library. For search (authors, titles, tags...) it uses rendered metadata from static/data{1-8}.js files. Every time the directory path and/or librarian is provided it is saved in the configuration file for the future use (therefore: 'accorder build SESSION' should be enough for the next successful build).`, Args: cobra.ExactArgs(1), PreRun: func(cmd *cobra.Command, args []string) { session := args[0] for vipFlag, cliFlag := range map[string]string{ "librarian_name": "librarian", "local_upload": "directory", "library_uuid": "library-uuid", "library_secret": "library-secret", "server_upload": "server", "bucket_upload": "bucket", } { viper.BindPFlag(fmt.Sprintf("%s.%s", session, vipFlag), cmd.Flags().Lookup(cliFlag)) } }, Run: func(cmd *cobra.Command, args []string) { session := args[0] MissingRequiredFlags( map[string]string{ "librarian_name": "librarian", "local_upload": "directory", }, session, cmd, ) libraryUUID := ViperValue(session, "library_uuid") librarySecret := ViperValue(session, "library_secret") calibrePath := ViperValue(session, "local_upload") librarianName := ViperValue(session, "librarian_name") jsonPath := CliFlagValue(cmd, "jsonpath") bibtexPath := CliFlagValue(cmd, "import-bibtex") doc := etree.NewDocument() if err := doc.ReadFromFile(bibtexPath); err != nil { panic(err) } root := doc.SelectElement("rdf:RDF") for _, attachmentNode := range root.FindElements("[name()='link:type']") { var zoteroItem ZoteroItem if attachmentNode.Text() != "application/pdf" { continue } newDoc := etree.NewDocument() zoteroUnion := newDoc.CreateElement("zoteroItem") filePathElement := zoteroUnion.CreateElement("filePath") filePathQuery := attachmentNode.Parent().FindElement("[name()='rdf:resource']").SelectAttr("rdf:resource").Value filePathElement.CreateText(filePathQuery) mimeTypeElement := zoteroUnion.CreateElement("mimeType") mimeType := attachmentNode.Text() mimeTypeElement.CreateText(mimeType) bibliographyNode := root.FindElement(fmt.Sprintf("[@rdf:resource='%s']", attachmentNode.Parent().SelectAttr("rdf:about").Value)).Parent().Copy() publisherQuery := bibliographyNode.FindElement("[name()='foaf:name']") if publisherQuery != nil { publisherElement := zoteroUnion.CreateElement("publisher") publisher := publisherQuery.Text() publisherElement.CreateText(publisher) } authorsQuery := bibliographyNode.FindElements("[name()='foaf:Person']") authors := zoteroUnion.CreateElement("authors") for _, authorNode := range authorsQuery { var firstName, surName string author := authors.CreateElement("author") firstNameNode := authorNode.FindElement("[name()='foaf:givenName']") if firstNameNode != nil { firstName = firstNameNode.Text() } surNameNode := authorNode.FindElement("[name()='foaf:surname']") if surNameNode != nil { surName = surNameNode.Text() } author.CreateText(fmt.Sprintf("%s %s", firstName, surName)) } titleQuery := bibliographyNode.FindElement("[name()='dc:title']") if titleQuery != nil { titleNode := zoteroUnion.CreateElement("title") title := titleQuery.Text() titleNode.CreateText(title) } descriptionQuery := bibliographyNode.FindElement("[name()='dcterms:abstract']") if descriptionQuery != nil { descriptionNode := zoteroUnion.CreateElement("description") description := descriptionQuery.Text() descriptionNode.CreateText(description) } // newDoc.WriteTo(os.Stdout) b, err := newDoc.WriteToBytes() if err != nil { log.Fatal(err) } if err := xml.Unmarshal(b, &zoteroItem); err != nil { log.Fatalln(err) } fmt.Printf("\nZoteroItem: %#v\n", zoteroItem) } calibre.RenderStandaloneApp(calibrePath, librarianName, libraryUUID, librarySecret, jsonPath) }, } func init() { rootCmd.AddCommand(buildCmd) buildCmd.Flags().StringP("directory", "d", "", "A local Calibre directory.") buildCmd.Flags().StringP("librarian", "l", "", "Librarian's name.") buildCmd.Flags().StringP("export-json", "e", "", "Path where to render all metadata into JSON.") buildCmd.Flags().StringP("import-bibtex", "i", "", "Import books from BibTex file into Calibre.") buildCmd.Flags().StringP("library-uuid", "u", "", "A library's UUID used if part of MotW.") buildCmd.Flags().StringP("library-secret", "p", "", "A password used if part of MotW.") buildCmd.Flags().StringP("server", "s", "minio.memoryoftheworld.org", "Server.") buildCmd.Flags().StringP("bucket", "b", "", "Server.") CustomHelpOutput(buildCmd) buildCmd.Flags().MarkHidden("library-uuid") buildCmd.Flags().MarkHidden("library-secret") buildCmd.Flags().MarkHidden("server") buildCmd.Flags().MarkHidden("bucket") }