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:
parent
7fba4182e1
commit
b4ae1981d6
4 changed files with 85 additions and 40 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
26
internal/gmnhg/post.go
Normal 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
|
||||
}
|
36
internal/gmnhg/templates.go
Normal file
36
internal/gmnhg/templates.go
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue