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.
This commit is contained in:
Timur Demin 2021-08-22 17:52:19 +03:00
parent 7fba4182e1
commit b4ae1981d6
No known key found for this signature in database
GPG key ID: 9EDF3F9D9286FA20
4 changed files with 85 additions and 40 deletions

View file

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

View file

@ -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
}

26
internal/gmnhg/post.go Normal file
View file

@ -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
}

View file

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