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) +}