From b4ae1981d623db6da0a6b9bde6c0c4cb6a8b63aa Mon Sep 17 00:00:00 2001 From: Timur Demin Date: Sun, 22 Aug 2021 17:52:19 +0300 Subject: [PATCH] Implement sort / sortRev in templates This adds generic sort / sortRev functions for use in gmnhg templates which use sort.Sort to sort anything that implements sort.Interface (which includes lists of posts). The existing sortPosts function that used to sort posts in reverse order becomes an alias to sortRev for backwards compatibility. --- cmd/gmnhg/main.go | 33 +++++++++++++++++++-------------- cmd/gmnhg/templates.go | 30 ++++-------------------------- internal/gmnhg/post.go | 26 ++++++++++++++++++++++++++ internal/gmnhg/templates.go | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 internal/gmnhg/post.go create mode 100644 internal/gmnhg/templates.go diff --git a/cmd/gmnhg/main.go b/cmd/gmnhg/main.go index 8fdf8c8..bfb609c 100644 --- a/cmd/gmnhg/main.go +++ b/cmd/gmnhg/main.go @@ -63,8 +63,18 @@ // over the same post props as specified in 1, and .Content, which is // rendered from top-level _index.gmi.md. // -// This program provides some extra template functions, documented in -// templates.go. Template functions from sprig are also available +// This program provides some extra template functions on top of sort: +// +// * sort, which sorts slices of int, float64, strings, and anything +// implementing sort.Interface (which includes slices of posts), +// returning a new, sorted slice. +// +// * sortRev, which works like sort, but sorts in reverse order. +// +// * sortPosts, which is an alias to sortRev preserved for backwards +// compatilibity. +// +// Template functions from sprig are also available // (https://github.com/Masterminds/sprig); see the sprig documentation // for more details. // @@ -94,6 +104,7 @@ import ( "text/template" gemini "github.com/tdemin/gmnhg" + "github.com/tdemin/gmnhg/internal/gmnhg" ) const ( @@ -117,12 +128,6 @@ var ( var hugoConfigFiles = []string{"config.toml", "config.yaml", "config.json"} -type post struct { - Post []byte - Metadata gemini.HugoMetadata - Link string -} - func copyFile(dst, src string) error { input, err := os.Open(src) if err != nil { @@ -253,8 +258,8 @@ func main() { } // render posts to Gemtext and collect top level posts data - posts := make(map[string]*post) - topLevelPosts := make(map[string][]*post) + posts := make(map[string]gmnhg.Post) + topLevelPosts := make(map[string]gmnhg.Posts) if err := filepath.Walk(contentBase, func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -274,17 +279,17 @@ func main() { return err } key := strings.TrimPrefix(strings.TrimSuffix(path, ".md"), contentBase) + ".gmi" - p := post{ + p := gmnhg.Post{ Post: gemText, Link: key, Metadata: metadata, } - posts[key] = &p + posts[key] = p if matches := pagePathRegex.FindStringSubmatch(path); matches != nil { dirs := strings.Split(matches[1], "/") // only include leaf resources pages in leaf index if info.Name() != "index.md" && hasSubPath(leafIndexPaths, path) { - topLevelPosts[matches[1]] = append(topLevelPosts[matches[1]], &p) + topLevelPosts[matches[1]] = append(topLevelPosts[matches[1]], p) } else { // include normal pages in all subdirectory indices for i, dir := range dirs { @@ -293,7 +298,7 @@ func main() { } } for _, dir := range dirs { - topLevelPosts[dir] = append(topLevelPosts[dir], &p) + topLevelPosts[dir] = append(topLevelPosts[dir], p) } } } diff --git a/cmd/gmnhg/templates.go b/cmd/gmnhg/templates.go index 3dab24f..c8256a0 100644 --- a/cmd/gmnhg/templates.go +++ b/cmd/gmnhg/templates.go @@ -16,28 +16,12 @@ package main import ( - "sort" "text/template" "github.com/Masterminds/sprig/v3" + "github.com/tdemin/gmnhg/internal/gmnhg" ) -type postsSort []*post - -func (p postsSort) Len() int { - return len(p) -} - -func (p postsSort) Less(i, j int) bool { - return p[i].Metadata.PostDate.After(p[j].Metadata.PostDate) -} - -func (p postsSort) Swap(i, j int) { - t := p[i] - p[i] = p[j] - p[j] = t -} - func mustParseTmpl(name, value string) *template.Template { return template.Must(template.New(name).Funcs(defineFuncMap()).Parse(value)) } @@ -45,15 +29,9 @@ func mustParseTmpl(name, value string) *template.Template { func defineFuncMap() template.FuncMap { fm := sprig.TxtFuncMap() // sorts posts by date, newest posts go first - fm["sortPosts"] = func(posts []*post) []*post { - // sortPosts is most likely to be used in a pipeline, and the - // user has every right to expect it doesn't modify their - // existing posts slice - ps := make(postsSort, len(posts)) - copy(ps, posts) - sort.Sort(ps) - return ps - } + fm["sortPosts"] = gmnhg.SortRev + fm["sort"] = gmnhg.Sort + fm["sortRev"] = gmnhg.SortRev return fm } diff --git a/internal/gmnhg/post.go b/internal/gmnhg/post.go new file mode 100644 index 0000000..31db91a --- /dev/null +++ b/internal/gmnhg/post.go @@ -0,0 +1,26 @@ +package gmnhg + +import gemini "github.com/tdemin/gmnhg" + +type Post struct { + Post []byte + Metadata gemini.HugoMetadata + Link string +} + +// Posts implements sort.Interface. +type Posts []Post + +func (p Posts) Len() int { + return len(p) +} + +func (p Posts) Less(i, j int) bool { + return p[i].Metadata.PostDate.Before(p[j].Metadata.PostDate) +} + +func (p Posts) Swap(i, j int) { + t := p[i] + p[i] = p[j] + p[j] = t +} diff --git a/internal/gmnhg/templates.go b/internal/gmnhg/templates.go new file mode 100644 index 0000000..7743e54 --- /dev/null +++ b/internal/gmnhg/templates.go @@ -0,0 +1,36 @@ +package gmnhg + +import ( + "reflect" + "sort" +) + +func sortWhatever(sortable interface{}, reverse bool) interface{} { + // convert slices to their sort.Interface counterparts + switch s := sortable.(type) { + case []int: + sortable = sort.IntSlice(s) + case []float64: + sortable = sort.Float64Slice(s) + case []string: + sortable = sort.StringSlice(s) + } + v := reflect.ValueOf(sortable) + cpy := reflect.MakeSlice(v.Type(), v.Len(), v.Cap()) + reflect.Copy(cpy, v) + cpyAsInterface := v.Interface() + if !reverse { + sort.Sort(cpyAsInterface.(sort.Interface)) + } else { + sort.Sort(sort.Reverse(cpyAsInterface.(sort.Interface))) + } + return cpyAsInterface +} + +func Sort(sortable interface{}) interface{} { + return sortWhatever(sortable, false) +} + +func SortRev(sortable interface{}) interface{} { + return sortWhatever(sortable, true) +}