bookends

commit 1e56f2875d3d2d1166601e6b8b90a8420578278c

Author: Honza Pokorny <honza@pokorny.ca>

Add Atom feed

 cmd/build.go | 11 ++--
 cmd/cache.go | 11 ++--
 cmd/root.go | 2 
 go.mod | 1 
 go.sum | 4 +
 pkg/bookends/bookends.go | 97 +++++++++++++++++++++++++++++++++++++----


diff --git a/cmd/build.go b/cmd/build.go
index b58970656b732d438175c8d9918eb76003d5b64f..931d6c46199b95814c40a728c809990ba5ed64a3 100644
--- a/cmd/build.go
+++ b/cmd/build.go
@@ -29,11 +29,12 @@ 	Use:   "build",
 	Short: "",
 	Run: func(cmd *cobra.Command, args []string) {
 		config := bookends.Config{
-			BookLogFilename:  bookLogFilename,
-			CacheDir:         cacheDir,
-			CoversDir:        coversDir,
-			OutputFilename:   outputFilename,
-			TemplateFilename: templateFilename,
+			BookLogFilename:    bookLogFilename,
+			CacheDir:           cacheDir,
+			CoversDir:          coversDir,
+			OutputFilename:     outputFilename,
+			AtomOutputFilename: atomOutputFilename,
+			TemplateFilename:   templateFilename,
 		}
 		err := bookends.Build(config)
 		if err != nil {




diff --git a/cmd/cache.go b/cmd/cache.go
index 86e63b3628cd0b82e982aa6f4eb2fa464c49d3a2..c7bc4a26879749879bbf0d0177034376dd38af80 100644
--- a/cmd/cache.go
+++ b/cmd/cache.go
@@ -29,11 +29,12 @@ 	Use:   "cache",
 	Short: "",
 	Run: func(cmd *cobra.Command, args []string) {
 		config := bookends.Config{
-			BookLogFilename:  bookLogFilename,
-			CacheDir:         cacheDir,
-			CoversDir:        coversDir,
-			OutputFilename:   outputFilename,
-			TemplateFilename: templateFilename,
+			BookLogFilename:    bookLogFilename,
+			CacheDir:           cacheDir,
+			CoversDir:          coversDir,
+			OutputFilename:     outputFilename,
+			AtomOutputFilename: atomOutputFilename,
+			TemplateFilename:   templateFilename,
 		}
 		err := bookends.CacheCovers(config)
 		if err != nil {




diff --git a/cmd/root.go b/cmd/root.go
index 0934acc2151c550d43a2bf2434b19464983fb487..b95fd2b90191eca6a5a6afe09cb98c030951fae5 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -27,6 +27,7 @@ var bookLogFilename string
 var cacheDir string
 var coversDir string
 var outputFilename string
+var atomOutputFilename string
 var templateFilename string
 
 var rootCmd = &cobra.Command{
@@ -50,6 +51,7 @@ 	rootCmd.PersistentFlags().StringVar(&bookLogFilename, "book-log-filename", "book-log.org", "")
 	rootCmd.PersistentFlags().StringVar(&cacheDir, "cache-dir", "cache", "")
 	rootCmd.PersistentFlags().StringVar(&coversDir, "covers-dir", "covers", "")
 	rootCmd.PersistentFlags().StringVar(&outputFilename, "output-filename", "output.html", "")
+	rootCmd.PersistentFlags().StringVar(&atomOutputFilename, "atom-output-filename", "atom.xml", "")
 	rootCmd.PersistentFlags().StringVar(&templateFilename, "template-filename", "template.html", "")
 
 	rootCmd.AddCommand(buildCmd)




diff --git a/go.mod b/go.mod
index d7bc339e491f484f85a36695cef09e24e02479ce..6ad56605d6708de5cb461b6e4df71dbf75c8e29c 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@
 go 1.16
 
 require (
+	github.com/gorilla/feeds v1.1.1
 	github.com/niklasfasching/go-org v1.5.0
 	github.com/spf13/cobra v1.1.3
 )




diff --git a/go.sum b/go.sum
index 1b548be0cd60a2de297fb748166b6e2568d0c3f1..aca65712bcbfc7159dba5ae23f7b871d5446b234 100644
--- a/go.sum
+++ b/go.sum
@@ -71,6 +71,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
+github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@@ -106,8 +108,10 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=




diff --git a/pkg/bookends/bookends.go b/pkg/bookends/bookends.go
index bafec02a594a9aa639c07bce578074f1b184f497..cba1d3f05c7b4e2c3c3d8c68418a857d2b545933 100644
--- a/pkg/bookends/bookends.go
+++ b/pkg/bookends/bookends.go
@@ -31,15 +31,25 @@ 	"strconv"
 	"strings"
 	"time"
 
+	"github.com/gorilla/feeds"
 	"github.com/niklasfasching/go-org/org"
 )
 
 type Config struct {
-	BookLogFilename  string
-	CacheDir         string
-	CoversDir        string
-	OutputFilename   string
-	TemplateFilename string
+	BookLogFilename    string
+	CacheDir           string
+	CoversDir          string
+	OutputFilename     string
+	AtomOutputFilename string
+	TemplateFilename   string
+}
+
+type AtomConfig struct {
+	Name        string
+	Author      string
+	Email       string
+	Link        string
+	Description string
 }
 
 type Book struct {
@@ -140,6 +150,13 @@ 	}
 	return nil
 }
 
+func (b Book) AtomContent() string {
+	if b.Review == "" {
+		return fmt.Sprintf("%s by %s, rated with %d stars", b.Title, b.Author, b.Rating)
+	}
+	return fmt.Sprintf("%s by %s, rated with %d stars\n\n%s", b.Title, b.Author, b.Rating, string(b.Review))
+}
+
 func BuildHTML(config Config, books []Book) (string, error) {
 	t, err := template.ParseFiles(config.TemplateFilename)
 
@@ -176,6 +193,35 @@ 	return nil
 
 }
 
+func BuildAtom(books []Book, config AtomConfig) (string, error) {
+	now := time.Now()
+	author := &feeds.Author{Name: config.Author, Email: config.Email}
+	feed := &feeds.Feed{
+		Title:       config.Name,
+		Link:        &feeds.Link{Href: config.Link},
+		Description: config.Description,
+		Author:      author,
+		Created:     now,
+	}
+
+	var items []*feeds.Item
+
+	for _, book := range books {
+		item := &feeds.Item{
+			Title:   book.Title,
+			Link:    &feeds.Link{},
+			Content: book.AtomContent(),
+			Author:  author,
+			Created: book.DateRead,
+		}
+
+		items = append(items, item)
+	}
+
+	feed.Items = items
+	return feed.ToAtom()
+}
+
 func Build(config Config) error {
 	f, err := os.Open(config.BookLogFilename)
 
@@ -183,7 +229,7 @@ 	if err != nil {
 		return err
 	}
 
-	books, err := ParseOrgFile(f)
+	books, atomConfig, err := ParseOrgFile(f)
 
 	if err != nil {
 		return err
@@ -203,13 +249,42 @@ 	if err != nil {
 		return err
 	}
 
+	atom, err := BuildAtom(books, atomConfig)
+
+	if err != nil {
+		return err
+	}
+
+	err = WriteFile(config.AtomOutputFilename, atom)
+
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 
-func ParseOrgFile(input io.Reader) ([]Book, error) {
+func ParseAtomConfig(drawer org.PropertyDrawer) AtomConfig {
+	name, _ := drawer.Get("ATOM_NAME")
+	author, _ := drawer.Get("ATOM_AUTHOR")
+	description, _ := drawer.Get("ATOM_DESCRIPTION")
+	link, _ := drawer.Get("ATOM_LINK")
+
+	return AtomConfig{
+		Name:        name,
+		Author:      author,
+		Description: description,
+		Link:        link,
+	}
+}
+
+func ParseOrgFile(input io.Reader) ([]Book, AtomConfig, error) {
 	books := []Book{}
 	conf := org.New()
 	d := conf.Parse(input, "")
+
+	documentProperties := d.Nodes[0].(org.PropertyDrawer)
+	atomConfig := ParseAtomConfig(documentProperties)
 
 	for _, child := range d.Outline.Children {
 		for _, sub := range child.Children {
@@ -238,7 +313,7 @@ 					rating, ok := t.Get("RATING")
 					if ok {
 						ratingInt, err := strconv.ParseInt(rating, 10, 8)
 						if err != nil {
-							return books, err
+							return books, atomConfig, err
 						}
 						book.Rating = int(ratingInt)
 					}
@@ -255,7 +330,7 @@ 					if strings.HasPrefix(s, "CLOSED") {
 						s = strings.TrimSpace(s)
 						dt, err := time.Parse("CLOSED: [2006-01-02 Mon 15:04]", s)
 						if err != nil {
-							return books, err
+							return books, atomConfig, err
 						}
 						book.DateRead = dt
 						continue
@@ -332,7 +407,7 @@ 		}
 
 	}
 
-	return books, nil
+	return books, atomConfig, nil
 
 }
 
@@ -368,7 +443,7 @@ 	if err != nil {
 		return err
 	}
 
-	books, err := ParseOrgFile(f)
+	books, _, err := ParseOrgFile(f)
 
 	if err != nil {
 		return err