Author: Honza Pokorny <honza@redhat.com>
fixup
cmd/cache.go | 35 ++++++++ cmd/root.go | 2 cmd/yaml2org.go | 19 ++++ go.mod | 4 go.sum | 8 + pkg/bookends/bookends.go | 170 +++++++++++++++++++++++++++++++++++++++++ template.html | 68 ++++++++++++++++
diff --git a/cmd/cache.go b/cmd/cache.go new file mode 100644 index 0000000000000000000000000000000000000000..437fed7bbfd135f256dd10ebf7112ee2477e1bc5 --- /dev/null +++ b/cmd/cache.go @@ -0,0 +1,35 @@ +// bookends +// Copyright (C) 2021 Honza Pokorny <me@honza.ca> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +package cmd + +import ( + "fmt" + + "git.pokorny.ca/bookends/pkg/bookends" + "github.com/spf13/cobra" +) + +var cacheCovers = &cobra.Command{ + Use: "cache", + Short: "", + Run: func(cmd *cobra.Command, args []string) { + err := bookends.CacheCovers() + if err != nil { + fmt.Println("ERROR", err) + } + }, +} diff --git a/cmd/root.go b/cmd/root.go index 681008fc36febe63f6047ab7795ce3527ac523fb..0c75a15deaea89e83a2d0b36fbb1b68a7aabad70 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -44,5 +44,7 @@ // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file path (default is config.yaml)") // rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "") // rootCmd.AddCommand(generateDefaultConfigurationCmd) rootCmd.AddCommand(buildCmd) + rootCmd.AddCommand(yaml2OrgCmd) + rootCmd.AddCommand(cacheCovers) // rootCmd.AddCommand(versionCmd) } diff --git a/cmd/yaml2org.go b/cmd/yaml2org.go new file mode 100644 index 0000000000000000000000000000000000000000..396bbda60ef64c142a3eca3b963d8d399930f0ca --- /dev/null +++ b/cmd/yaml2org.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "fmt" + + "git.pokorny.ca/bookends/pkg/bookends" + "github.com/spf13/cobra" +) + +var yaml2OrgCmd = &cobra.Command{ + Use: "yaml2org", + Short: "", + Run: func(cmd *cobra.Command, args []string) { + err := bookends.Yaml2Org() + if err != nil { + fmt.Println(err) + } + }, +} diff --git a/go.mod b/go.mod index 04c6725ccdf946ea63a979f7578b42b9bf887157..f9195ccb5e52a0729c67e2c9ba64957c02d901eb 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ go 1.16 require ( - github.com/niklasfasching/go-org v1.5.0 // indirect + github.com/niklasfasching/go-org v1.5.0 github.com/spf13/cobra v1.1.3 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/go.sum b/go.sum index 9577210ba58e16ffffda440c67d53d39f58c73ce..a799ca1dc78caf93fc0d337b7e3690ea9a4d00b9 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -105,8 +106,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= @@ -134,6 +137,7 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -291,6 +295,7 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -298,8 +303,9 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/bookends/bookends.go b/pkg/bookends/bookends.go index 357a2eb437560c33317e8b2971677c1bd9a4146f..51c798a904c905f103a132707bcdb5a8b38cdc01 100644 --- a/pkg/bookends/bookends.go +++ b/pkg/bookends/bookends.go @@ -2,9 +2,12 @@ package bookends import ( "bytes" + "errors" "fmt" "html/template" "io" + "io/ioutil" + "net/http" "net/url" "os" "path" @@ -14,10 +17,27 @@ "strings" "time" "github.com/niklasfasching/go-org/org" + "gopkg.in/yaml.v3" ) type DateRead struct { time.Time +} + +func (t *DateRead) UnmarshalYAML(unmarshal func(interface{}) error) error { + var tm string + if err := unmarshal(&tm); err != nil { + return err + } + + dr, err := time.Parse("2006-01-02", tm) + if err != nil { + return err + } + + *t = DateRead{dr} + + return nil } type Book struct { @@ -63,6 +83,11 @@ return url.Parse(s) } +func (b Book) CacheCoverURL() (*url.URL, error) { + localPath := path.Join("cache", fmt.Sprintf("%s.jpg", b.ISBN)) + return url.Parse(localPath) +} + func (b Book) DownloadCover() error { return nil } @@ -110,7 +135,7 @@ if err != nil { return err } - books, err := OrgTest(f) + books, err := ParseOrgFile(f) if err != nil { return err @@ -135,16 +160,20 @@ return nil } -func OrgTest(input io.Reader) ([]Book, error) { +func ParseOrgFile(input io.Reader) ([]Book, error) { books := []Book{} conf := org.New() d := conf.Parse(input, "") for _, child := range d.Outline.Children { for _, sub := range child.Children { + tags := []string{} + for _, t := range sub.Headline.Tags { + tags = append(tags, strings.ReplaceAll(t, "_", "-")) + } book := Book{ Title: sub.Headline.Title[0].String(), - Tags: sub.Headline.Tags, + Tags: tags, } rest := bytes.NewBufferString("") @@ -248,3 +277,138 @@ return books, nil } + +func Yaml2Org() error { + contents, err := ioutil.ReadFile("books.yaml") + if err != nil { + return err + } + + var books []Book + yaml.Unmarshal(contents, &books) + if err != nil { + return err + } + + fmt.Println(books) + sort.Sort(BooksByDateRead(books)) + + year := 2021 + fmt.Println("* 2021") + + for _, book := range books { + if book.DateRead.Year() != year { + fmt.Println("*", book.DateRead.Year()) + year = book.DateRead.Year() + } + fmt.Println("** DONE", book.Title) + fmt.Printf("CLOSED: [%s]\n", book.DateRead.Format("2006-01-02 Mon 15:04")) + fmt.Println(":PROPERTIES:") + fmt.Printf(":author: %s\n", book.Author) + fmt.Printf(":rating: %d\n", book.Rating) + fmt.Printf(":isbn: %s\n", book.ISBN) + fmt.Println(":END:") + fmt.Println("") + } + + return nil +} + +func copy(src, dst string) error { + sourceFileStat, err := os.Stat(src) + if err != nil { + return err + } + + if !sourceFileStat.Mode().IsRegular() { + return fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return err + } + defer destination.Close() + _, err = io.Copy(destination, source) + return err +} + +func CacheCovers() error { + f, err := os.Open("book-log.org") + + if err != nil { + return err + } + + books, err := ParseOrgFile(f) + + if err != nil { + return err + } + + cacheDir := "cache" + coversDir := "covers" + + fmt.Println(len(books)) + + for _, book := range books { + fmt.Println("Processing", book.Title) + cachePath := path.Join(cacheDir, fmt.Sprintf("%s.jpg", book.ISBN)) + + if PathExists(cachePath) { + fmt.Println(" Cache exists", cachePath) + continue + } + + coversPath := path.Join(coversDir, fmt.Sprintf("%s.jpg", book.ISBN)) + + if PathExists(coversPath) { + fmt.Println(" Cover exists", coversPath) + // Copy the file over + err := copy(coversPath, cachePath) + + if err != nil { + return nil + } + + continue + + } + + coverUrl, err := book.CoverURL() + + if err != nil { + return err + } + + fmt.Println(" GET", coverUrl.String()) + resp, err := http.Get(coverUrl.String()) + + if err != nil { + return err + } + + if resp.StatusCode > 201 { + return errors.New(" non 200 response:" + book.Title) + } + + destination, err := os.Create(cachePath) + if err != nil { + return err + } + defer destination.Close() + _, err = io.Copy(destination, resp.Body) + + if err != nil { + return err + } + } + + return nil +} diff --git a/template.html b/template.html new file mode 100644 index 0000000000000000000000000000000000000000..a5c0d1a3e3fb1c16662097378e8305e75f2999a9 --- /dev/null +++ b/template.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<html> + <head> + <title>Reading List --- Honza Pokorny</title> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css"> + + <style> + .star { + width: 13px; + height: 12px; + display: inline-block; + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTMiIGhlaWdodD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTYuMzA5IDkuMjJMMi40MDkgMTJsMS40NC00LjU2N0wwIDQuNTgzbDQuNzg4LS4wNDJMNi4zMDggMCA3LjgzIDQuNTRsNC43ODkuMDQ0LTMuODUgMi44NDlMMTAuMjA5IDEyeiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+); + } + .star-1 { width: 13px; } + .star-2 { width: 26px; } + .star-3 { width: 39px; } + .star-4 { width: 52px; } + .star-5 { width: 65px; } + </style> + </head> + <body> + <section class="section"> + <div class="container"> + <h1 class="title">Reading List</h1> + <p class="subtitle">What is Honza reading these days?</p> + </div> + </section> + + <section class="section"> + <div class="container"> + {{ range . }} + <div class="columns"> + <div class="column is-2"> + <figure class="image"> + <img src="{{ .CacheCoverURL }}" alt="Placeholder image"> + </figure> + </div> + <div class="column is-10"> + <div class="media"> + <div class="media-content"> + <p class="title is-4">{{ .Title }}</p> + <p class="subtitle is-6">{{ .Author }}</p> + </div> + </div> + + <div class="content"> + <div class="star star-{{ .Rating }}"></div> + <div> + Finished on <time datetime="2016-1-1">{{ .DateRead.Format "January 2, 2006" }}</time> + </div> + <div> + {{ range .Tags }} + <span class="tag">{{ . }}</span> + {{ end }} + </div> + <div> + {{ .Review }} + </div> + </div> + </div> + </div> + {{ end }} + </div> + </section> + + </body> +</html>