diff options
| author | Mitchell Riedstra <mitch@riedstra.dev> | 2021-07-12 23:08:58 -0400 |
|---|---|---|
| committer | Mitchell Riedstra <mitch@riedstra.dev> | 2021-07-12 23:18:44 -0400 |
| commit | 904e37a88a6a2eab3919f7f2c40bbb2c07544a7c (patch) | |
| tree | 07ec38801bf572a2933d51d272fc4cd3ab74b61c | |
| parent | e6d53f71c9718ecdb9fde16a924d75a71aadd2d2 (diff) | |
| download | go-website-904e37a88a6a2eab3919f7f2c40bbb2c07544a7c.tar.gz go-website-904e37a88a6a2eab3919f7f2c40bbb2c07544a7c.tar.xz | |
Add atom feed to the go website
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | LICENSE | 2 | ||||
| -rw-r--r-- | cmd/server/feed.go | 216 | ||||
| -rw-r--r-- | cmd/server/handlers.go | 13 | ||||
| -rw-r--r-- | cmd/server/main.go | 42 | ||||
| -rw-r--r-- | go.mod | 10 | ||||
| -rw-r--r-- | go.sum | 40 | ||||
| -rw-r--r-- | page/index.go | 3 | ||||
| -rw-r--r-- | page/page.go | 18 | ||||
| -rw-r--r-- | page/pagelist.go | 16 | ||||
| -rw-r--r-- | page/render.go | 2 | ||||
| -rw-r--r-- | page/time.go | 5 |
12 files changed, 299 insertions, 70 deletions
diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d050e4c..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*site* -server @@ -1,4 +1,4 @@ -Copyright 2020 Mitchell Riedstra +Copyright 2021 Mitchell Riedstra Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/cmd/server/feed.go b/cmd/server/feed.go new file mode 100644 index 0000000..2d4a75b --- /dev/null +++ b/cmd/server/feed.go @@ -0,0 +1,216 @@ +package main + +import ( + "bytes" + "encoding/xml" + "errors" + "fmt" + "log" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gorilla/mux" + "riedstra.dev/mitch/go-website/page" +) + +type Author struct { + Name string `xml:"name"` // Required + Uri string `xml:"uri,omitempty"` + Email string `xml:"email,omitempty"` +} + +type Link struct { + Href string `xml:"href,attr,omitempty"` + Rel string `xml:"rel,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` + Title string `xml:"Title,attr,omitempty"` + Length string `xml:"Length,attr,omitempty"` +} + +type Content struct { + Type string `xml:"type,attr"` + Data string `xml:",chardata"` +} + +type Entry struct { + // Spec requires this, autogenerated from Title and updated if otherwise + // left empty + Id string `xml:"id"` + + Title string `xml:"title"` // Required + Updated *time.Time `xml:"updated"` // Required + Author *Author `xml:"author,omitempty"` + Published *time.Time `xml:"published,omitempty"` + Links []Link `xml:"link,omitempty"` + Content *Content `xml:"content,omitempty"` +} + +func (i Entry) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type Alias Entry + + errs := []string{} + if i.Title == "" { + errs = append(errs, "Title Cannot be empty") + } + if i.Updated == nil { + errs = append(errs, "Updated cannot be nil") + } + + if len(errs) > 0 { + return errors.New(strings.Join(errs, ",")) + } + + if i.Id == "" { + i.Id = fmt.Sprintf("%s::%d", i.Title, i.Updated.Unix()) + } + + i2 := (*Alias)(&i) + + return e.EncodeElement(i2, start) +} + +type Atom struct { + Ns string `xml:"xmlns,attr"` + Title string `xml:"title"` // Required + Id string `xml:"id"` // Required + Author Author `xml:"author,omitempty"` // Required + Updated *time.Time `xml:"updated"` // Required + Published *time.Time `xml:"published,omitempty"` + Subtitle string `xml:"subtitle,omitempty"` + Entries []Entry `xml:"entry"` +} + +func (a Atom) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + type Alias Atom + + a.Ns = "http://www.w3.org/2005/Atom" + errs := []string{} + if a.Id == "" { + errs = append(errs, "ID Cannot be empty") + } + if a.Author.Name == "" { + errs = append(errs, "Author Name cannot be empty") + } + if a.Updated == nil { + errs = append(errs, "Updated cannot be empty") + } + + if len(errs) > 0 { + return errors.New(strings.Join(errs, ",")) + } + + start.Name = xml.Name{Local: "feed"} + + a2 := (*Alias)(&a) + + return e.EncodeElement(a2, start) +} + +// FeedHandler takes care of pulling from the index all of the relevant posts +// and dumping them into an Atom feed. +// +// Relevant query parameters are: +// +// "content" if unset, or set to false content is omitted from the feed +// "limit=n" stop at "n" and return the feed +// +func (a *App) FeedHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + var addContent bool + var limit int + + if _, ok := r.URL.Query()["content"]; ok { + if r.URL.Query().Get("content") != "false" { + addContent = true + } + } + + if l := r.URL.Query().Get("limit"); l != "" { + i, err := strconv.Atoi(l) + if err == nil { + limit = i + } + } + + tag, ok := vars["tag"] + if !ok { + http.Error(w, "Tag not found or supplied", http.StatusNotFound) + return + } + + p := page.NewPage("index") + index, err := p.Index() + if err != nil { + log.Println(err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + pages, ok := index[tag] + if !ok { + http.Error(w, "Invalid tag", http.StatusNotFound) + return + } + + pages, dateless := pages.RemoveDateless() + for _, p := range dateless { + log.Printf("Warning, page %s has no Date field. Skipping inclusion on feed", p) + } + pages.SortDate() + + feed := &Atom{ + Author: a.Author, + Title: a.Title, + Id: a.FeedId, + Updated: &a.Updated.Time, + Subtitle: a.Description, + } + + entries := []Entry{} + + for n, p := range pages { + if limit != 0 && n >= limit { + break + } + + content := &bytes.Buffer{} + err := p.Render(content) + if err != nil { + log.Println(err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + entry := Entry{ + Title: p.Title, + Updated: &p.Date.Time, + Links: []Link{Link{Href: strings.Join([]string{a.SiteURL, p.Path()}, "/")}}, + } + + if addContent { + entry.Content = &Content{Type: "html", Data: content.String()} + } + + entries = append(entries, entry) + + } + + feed.Entries = entries + + w.Header().Add("Content-type", "application/xml") + w.Write([]byte(xml.Header)) + + enc := xml.NewEncoder(w) + enc.Indent("", " ") + + err = enc.Encode(feed) + if err != nil { + log.Println(err) + // Headers probably already sent, but we'll try anyway + http.Error(w, "Internal server error", http.StatusInternalServerError) + } + + return +} diff --git a/cmd/server/handlers.go b/cmd/server/handlers.go index 60e0a35..5ea89cd 100644 --- a/cmd/server/handlers.go +++ b/cmd/server/handlers.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "net/http" "path/filepath" @@ -8,9 +9,20 @@ import ( "riedstra.dev/mitch/go-website/page" ) +var FeedPrefixDefault = ".feeds" + type App struct { ReIndexPath string StaticDirectory string + + // Related to the Atom feed + Title string + Description string // aka, "subtitle" + Author Author + SiteURL string + FeedId string + Updated page.PageTime + FeedPrefix string } func (a *App) PageHandler(w http.ResponseWriter, r *http.Request) { @@ -48,6 +60,7 @@ func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) { rtr := mux.NewRouter() rtr.HandleFunc(a.ReIndexPath, a.RebuildIndexHandler) rtr.PathPrefix("/static/").Handler(a.StaticHandler()) + rtr.PathPrefix(fmt.Sprintf("/%s/{tag}", a.FeedPrefix)).HandlerFunc(a.FeedHandler) rtr.PathPrefix("/").HandlerFunc(a.PageHandler) rtr.ServeHTTP(w, r) } diff --git a/cmd/server/main.go b/cmd/server/main.go index 9ec96da..4b2c1ad 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "gopkg.in/yaml.v3" "log" "net/http" "os" @@ -18,13 +19,28 @@ func VersionPrint() { os.Exit(0) } +func loadConf(fn string) (*App, error) { + fh, err := os.Open(fn) + if err != nil { + return nil, err + } + dec := yaml.NewDecoder(fh) + + app := &App{} + err = dec.Decode(app) + return app, err +} + func main() { fl := flag.NewFlagSet("Website", flag.ExitOnError) listen := fl.String("l", "0.0.0.0:8001", "Listening address") directory := fl.String("d", ".", "Directory to serve.") version := fl.Bool("v", false, "Print the version then exit") - fl.StringVar(&page.TimeFormat, "T", page.TimeFormat, "Print the version then exit") - indexPath := fl.String("i", "/reIndex", + confFn := fl.String("c", "conf.yml", "Location for the config file") + verbose := fl.Bool("V", false, "Be more verbose ( dump config, etc ) ") + fl.StringVar(&page.TimeFormat, "T", page.TimeFormat, "Set the page time format, be careful with this") + defaultIndexPath := "/reIndex" + indexPath := fl.String("i", defaultIndexPath, "Path in which, when called will rebuild the index and clear the cache") _ = fl.Parse(os.Args[1:]) @@ -36,9 +52,25 @@ func main() { log.Fatal(err) } - app := &App{ - ReIndexPath: *indexPath, - StaticDirectory: "static", + app, err := loadConf(*confFn) + if err != nil { + log.Println(err) + app = &App{} + } + + if app.ReIndexPath == "" || *indexPath != defaultIndexPath { + app.ReIndexPath = *indexPath + } + if app.StaticDirectory == "" { + app.StaticDirectory = "static" + } + if app.FeedPrefix == "" { + app.FeedPrefix = FeedPrefixDefault + } + + if *verbose { + b, _ := yaml.Marshal(app) + os.Stderr.Write(b) } srv := &http.Server{ @@ -3,20 +3,10 @@ module riedstra.dev/mitch/go-website go 1.13 require ( - github.com/flimzy/diff v0.1.7 // indirect - github.com/flimzy/testy v0.1.17 // indirect - github.com/go-kivik/couchdb v2.0.0+incompatible - github.com/go-kivik/kivik v2.0.0+incompatible - github.com/go-kivik/kiviktest v2.0.0+incompatible // indirect - github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect github.com/gorilla/mux v1.8.0 github.com/kr/pretty v0.1.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/russross/blackfriday v2.0.0+incompatible github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - gitlab.com/flimzy/testy v0.3.0 // indirect - golang.org/x/net v0.0.0-20200925080053-05aa5d4ee321 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 ) @@ -1,17 +1,3 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/flimzy/diff v0.1.7 h1:DRbd+lN3lY1xVuQrfqvDNsqBwA6RMbClMs6tS5sqWWk= -github.com/flimzy/diff v0.1.7/go.mod h1:lFJtC7SPsK0EroDmGTSrdtWKAxOk3rO+q+e04LL05Hs= -github.com/flimzy/testy v0.1.17 h1:Y+TUugY6s4B/vrOEPo6SUKafc41W5aiX3qUWvhAPMdI= -github.com/flimzy/testy v0.1.17/go.mod h1:3szguN8NXqgq9bt9Gu8TQVj698PJWmyx/VY1frwwKrM= -github.com/go-kivik/couchdb v2.0.0+incompatible h1:DsXVuGJTng04Guz8tg7jGVQ53RlByEhk+gPB/1yo3Oo= -github.com/go-kivik/couchdb v2.0.0+incompatible/go.mod h1:5XJRkAMpBlEVA4q0ktIZjUPYBjoBmRoiWvwUBzP3BOQ= -github.com/go-kivik/kivik v2.0.0+incompatible h1:/7hgr29DKv/vlaJsUoyRlOFq0K+3ikz0wTbu+cIs7QY= -github.com/go-kivik/kivik v2.0.0+incompatible/go.mod h1:nIuJ8z4ikBrVUSk3Ua8NoDqYKULPNjuddjqRvlSUyyQ= -github.com/go-kivik/kiviktest v2.0.0+incompatible h1:y1RyPHqWQr+eFlevD30Tr3ipiPCxK78vRoD3o9YysjI= -github.com/go-kivik/kiviktest v2.0.0+incompatible/go.mod h1:JdhVyzixoYhoIDUt6hRf1yAfYyaDa5/u9SDOindDkfQ= -github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= -github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -19,36 +5,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN 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/otiai10/copy v1.0.2 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc= -github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 h1:+OLn68pqasWca0z5ryit9KGfp3sUsW4Lqg32iRMJyzs= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/mint v1.3.0 h1:Ady6MKVezQwHBkGzLFbrsywyp09Ah7rkmfjV3Bcr5uc= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -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/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk= github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -gitlab.com/flimzy/testy v0.3.0 h1:HbY+NAJjXWxRqX8X4yZ0Blr5t6Yxc2n5RYREGVkwFDw= -gitlab.com/flimzy/testy v0.3.0/go.mod h1:YObF4cq711ubd/3U0ydRQQVz7Cnq/ChgJpVwNr/AJac= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200925080053-05aa5d4ee321 h1:lleNcKRbcaC8MqgLwghIkzZ2JBQAb7QQ9MiwRt1BisA= -golang.org/x/net v0.0.0-20200925080053-05aa5d4ee321/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/page/index.go b/page/index.go index a453335..f9f2df5 100644 --- a/page/index.go +++ b/page/index.go @@ -1,7 +1,6 @@ package page import ( - "fmt" "os" "path/filepath" "strings" @@ -49,7 +48,7 @@ func (p *Page) Index() (map[string]PageList, error) { err = p2.Read() if err != nil { - fmt.Fprintln(os.Stderr, "Error encountered: ", err) + Logger.Println("Error encountered: ", err) return err } diff --git a/page/page.go b/page/page.go index 82f46f2..8b84099 100644 --- a/page/page.go +++ b/page/page.go @@ -25,6 +25,7 @@ package page import ( "bufio" "bytes" + "encoding/json" "fmt" "io" "log" @@ -45,6 +46,8 @@ type Page struct { Title string Head string Description string + AuthorName string + AuthorEmail string // Tags to apply to the page in question. Useful for Index() Tags map[string]interface{} Date *PageTime @@ -58,10 +61,6 @@ type Page struct { // .Global care must be taken when utilizing this functionality var Global interface{} -// CachePages determines whether or not the rendered page will be stored in -// memory -var CachePages = true - // CacheIndex determines whether or not the index will be cached in memory // or rebuilt on each call var CacheIndex = true @@ -102,11 +101,6 @@ func (p *Page) Global() interface{} { return Global } -// SetVars Will set to `nil` if provided -func (p *Page) SetVars(vars map[string]interface{}) { - p.Vars = vars -} - // Renders a page func (p *Page) Render(wr io.Writer) error { if err := p.Read(); err != nil { @@ -209,3 +203,9 @@ func (p *Page) RenderBody() (string, error) { func (p Page) String() string { return fmt.Sprintf("Page: %s", p.path) } + +// StringDetail prints a detailed string of the page +func (p Page) StringDetail() string { + b, _ := json.MarshalIndent(p, "", " ") + return fmt.Sprintf("Page: %s\n%s\n", p.path, b) +} diff --git a/page/pagelist.go b/page/pagelist.go index a5cf844..f2140f9 100644 --- a/page/pagelist.go +++ b/page/pagelist.go @@ -8,6 +8,22 @@ import ( // by the date, or date reversed type PageList []*Page +// RemoveDateless returns two PageLists, the first with valid dates, +// and the second without. This is useful if you need a PageList which +// will run SortDate and SortDateReverse without issue +func (p PageList) RemoveDateless() (PageList, PageList) { + with := PageList{} + without := PageList{} + for _, p := range p { + if p.Date != nil { + with = append(with, p) + } else { + without = append(without, p) + } + } + return with, without +} + func (p PageList) SortDate() PageList { sort.Slice(p, func(i, j int) bool { return p[i].Time().After(p[j].Time()) diff --git a/page/render.go b/page/render.go index 4378675..07b1b88 100644 --- a/page/render.go +++ b/page/render.go @@ -21,7 +21,7 @@ func Render(w http.ResponseWriter, r *http.Request, p := NewPage(path) if vars != nil { - p.SetVars(vars) + p.Vars = vars } err := p.Render(w) diff --git a/page/time.go b/page/time.go index 958dc38..0374490 100644 --- a/page/time.go +++ b/page/time.go @@ -21,3 +21,8 @@ func (pt *PageTime) UnmarshalYAML(n *yaml.Node) error { pt.Time = t return nil } + +func (pt PageTime) MarshalYAML() (interface{}, error) { + s := pt.Time.Format(TimeFormat) + return s, nil +} |
