266 lines
8.5 KiB
Go
266 lines
8.5 KiB
Go
package cmd
|
|
|
|
import (
|
|
"accorder/pkg/calibre"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/araddon/dateparse"
|
|
"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"`
|
|
Attachments struct {
|
|
Attachment []struct {
|
|
Path string `xml:",chardata"`
|
|
MimeType string `xml:"mimeType,attr"`
|
|
} `xml:"attachment"`
|
|
} `xml:"attachments"`
|
|
Date string `xml:"date"`
|
|
Authors struct {
|
|
Author []string `xml:"author"`
|
|
} `xml:"authors"`
|
|
Title string `xml:"title"`
|
|
Description string `xml:"description"`
|
|
Publisher string `xml:"publisher"`
|
|
}
|
|
|
|
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")
|
|
attachmentsIDs := map[string]bool{}
|
|
for _, attachmentNode := range root.FindElements("[name()='link:type']") {
|
|
var bookOpf calibre.BookOpfW
|
|
var zoteroItem ZoteroItem
|
|
|
|
attachmentID := attachmentNode.Parent().SelectAttr("rdf:about").Value
|
|
if attachmentNode.Text() == "text/html" || attachmentsIDs[attachmentID] {
|
|
continue
|
|
}
|
|
|
|
bibliographyNode := root.FindElement(fmt.Sprintf("[@rdf:resource='%s']", attachmentID)).Parent().Copy()
|
|
|
|
newDoc := etree.NewDocument()
|
|
zoteroUnion := newDoc.CreateElement("zoteroItem")
|
|
|
|
attachmentsQuery := bibliographyNode.FindElements("[name()='link:link']")
|
|
itemAttachments := zoteroUnion.CreateElement("attachments")
|
|
mimeTypeMap := make(map[string]bool)
|
|
for _, attachment := range attachmentsQuery {
|
|
attachmentID := attachment.SelectAttr("rdf:resource").Value
|
|
zAttachment := root.FindElement(fmt.Sprintf("[@rdf:about='%s']", attachmentID))
|
|
mimeType := zAttachment.FindElement("[name()='link:type']").Text()
|
|
if mimeType != "text/html" {
|
|
if !mimeTypeMap[mimeType] {
|
|
attachmentsIDs[attachmentID] = true
|
|
filePath := zAttachment.FindElement("[name()='rdf:resource']").SelectAttr("rdf:resource").Value
|
|
attachment := itemAttachments.CreateElement("attachment")
|
|
attachment.SetText(filePath)
|
|
attachment.CreateAttr("mimeType", mimeType)
|
|
mimeTypeMap[mimeType] = true
|
|
} else {
|
|
fmt.Println("DUPLICATE mimeType:", mimeType, attachmentID)
|
|
}
|
|
}
|
|
}
|
|
|
|
dateQuery := bibliographyNode.FindElement("[name()='dc:date']")
|
|
if dateQuery != nil {
|
|
dateElement := zoteroUnion.CreateElement("date")
|
|
date, err := dateparse.ParseAny(dateQuery.Text())
|
|
if err == nil {
|
|
formattedDate := date.Format("2006-01-02")
|
|
dateElement.CreateText(formattedDate)
|
|
|
|
bookOpf.Metadata.Published = formattedDate
|
|
} else {
|
|
newDateQuery := fmt.Sprintf("1 %s", dateQuery.Text())
|
|
newDate, err := dateparse.ParseAny(newDateQuery)
|
|
if err == nil {
|
|
newFormattedDate := newDate.Format("2006-01-02")
|
|
dateElement.CreateText(newFormattedDate)
|
|
bookOpf.Metadata.Published = newFormattedDate
|
|
} else {
|
|
lastChanceDate := dateQuery.Text()[len(dateQuery.Text())-4:]
|
|
year, err := strconv.Atoi(lastChanceDate)
|
|
if err == nil {
|
|
justYear := fmt.Sprintf("%d-01-01", year)
|
|
dateElement.CreateText(justYear)
|
|
bookOpf.Metadata.Published = justYear
|
|
} else {
|
|
fmt.Println("ERROR parsing date...", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
publisherQuery := bibliographyNode.FindElement("[name()='foaf:name']")
|
|
if publisherQuery != nil {
|
|
publisherElement := zoteroUnion.CreateElement("publisher")
|
|
publisher := publisherQuery.Text()
|
|
publisherElement.CreateText(publisher)
|
|
|
|
bookOpf.Metadata.Publisher = publisher
|
|
}
|
|
|
|
authorsQuery := bibliographyNode.FindElements("[name()='foaf:Person']")
|
|
authors := zoteroUnion.CreateElement("authors")
|
|
for _, authorNode := range authorsQuery {
|
|
var firstName, surName string
|
|
|
|
firstNameNode := authorNode.FindElement("[name()='foaf:givenName']")
|
|
if firstNameNode != nil {
|
|
firstName = firstNameNode.Text() + " "
|
|
}
|
|
surNameNode := authorNode.FindElement("[name()='foaf:surname']")
|
|
if surNameNode != nil {
|
|
surName = surNameNode.Text() + " "
|
|
}
|
|
fullName := strings.TrimSuffix(fmt.Sprintf("%s%s", firstName, surName), " ")
|
|
authors.CreateElement("author").SetText(fullName)
|
|
|
|
bookOpf.Metadata.Creators = append(bookOpf.Metadata.Creators, calibre.Creator{
|
|
Role: "aut",
|
|
Name: fullName,
|
|
})
|
|
}
|
|
|
|
titleQuery := bibliographyNode.FindElement("[name()='dc:title']")
|
|
if titleQuery != nil {
|
|
titleNode := zoteroUnion.CreateElement("title")
|
|
title := titleQuery.Text()
|
|
titleNode.CreateText(title)
|
|
|
|
bookOpf.Metadata.Title = title
|
|
}
|
|
|
|
descriptionQuery := bibliographyNode.FindElement("[name()='dcterms:abstract']")
|
|
if descriptionQuery != nil {
|
|
descriptionNode := zoteroUnion.CreateElement("description")
|
|
description := descriptionQuery.Text()
|
|
descriptionNode.CreateText(description)
|
|
|
|
bookOpf.Metadata.Description = description
|
|
}
|
|
|
|
b, err := newDoc.WriteToBytes()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if err := xml.Unmarshal(b, &zoteroItem); err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
for _, a := range zoteroItem.Attachments.Attachment {
|
|
fmt.Println(a.Path, a.MimeType)
|
|
}
|
|
zi, _ := xml.MarshalIndent(zoteroItem, " ", " ")
|
|
_ = zi
|
|
// os.Stdout.Write(zi)
|
|
// fmt.Println("\n~+~ ~ ~ ~ ~")
|
|
// fmt.Printf("\nZoteroItem: %#v\n", zoteroItem)
|
|
|
|
bookOpf.Version = "2.0"
|
|
bookOpf.Xmlns = "http://www.idpf.org/2007/opf"
|
|
bookOpf.UniqueIdentifier = "uuid_id"
|
|
bookOpf.Metadata.DC = "http://purl.org/dc/elements/1.1/"
|
|
bookOpf.Metadata.OPF = "http://www.idpf.org/2007/opf"
|
|
|
|
bookOpf.Metadata.Identifiers = append(bookOpf.Metadata.Identifiers, calibre.Identifier{
|
|
Scheme: "calibre",
|
|
Id: "calibre_id",
|
|
Value: "-1",
|
|
})
|
|
|
|
bookOpfOutput, err := xml.MarshalIndent(bookOpf, " ", " ")
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
_ = bookOpfOutput
|
|
// os.Stdout.Write(bookOpfOutput)
|
|
// fmt.Println("\n ~ ~ ~ ~ ~")
|
|
}
|
|
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")
|
|
}
|