[READ-ONLY] Mirror of https://github.com/darccio/zas. Most simple static website generator in Golang.
go static-site-generation static-site-generator
Something went wrong. Try again.
zas metadata.go
4.4 kB · 133 lines
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134package zas
import ( "fmt" "os/exec" "regexp" "strings" "time"
"go.yaml.in/yaml/v3")
// Date returns the explicit page date, or zero time when absent. Timestamps// must include a timezone in RFC3339 format; no publication date is inferred.func (zd *ZasData) Date() (time.Time, error) { return zd.pageDate("date")}
// Updated returns the explicit page updated timestamp, otherwise the most// recent git commit date for the currently tracked source. Missing history// returns zero time; file mtimes and the build time are never used.func (zd *ZasData) Updated() (time.Time, error) { if _, ok := zd.Page["updated"]; ok { return zd.pageDate("updated") } if zd.sourcePath == "" { return time.Time{}, nil } // Require a current index entry: git log alone can find history for a // source that has since been removed from the index. Literal pathspecs // also keep filenames containing glob characters from matching others. tracked := exec.Command("git", "--literal-pathspecs", "ls-files", "--error-unmatch", "--", zd.sourcePath).Run() == nil if !tracked { return time.Time{}, nil } date, _ := gitCommitDate(":(literal)" + zd.sourcePath) return date, nil}
func (zd *ZasData) pageDate(key string) (time.Time, error) { value, ok := zd.Page[key] if !ok { return time.Time{}, nil } switch value := value.(type) { case string: return parsePageDate(key, value) case time.Time: // Decode scalar text only when a date helper is actually called. // Decoding to Nodes also preserves YAML aliases and merged keys; // follow an alias to its scalar before validating the spelling. if zd.pageConfigSource != nil { var fields map[interface{}]yaml.Node if err := yaml.Unmarshal(zd.pageConfigSource, &fields); err != nil { return time.Time{}, fmt.Errorf("config value %q: %w", key, err) } node := fields[key] for node.Kind == yaml.AliasNode && node.Alias != nil { node = *node.Alias } if node.ShortTag() == "!!timestamp" { return parsePageDate(key, node.Value) } } return value, nil default: return time.Time{}, fmt.Errorf("config value %q must be an RFC3339 timestamp, got %T", key, value) }}
// Go's RFC3339 parser also accepts a one-digit hour, comma fractions and// out-of-range timezone offsets. Check the spelling and offset ranges first;// time.Parse then validates the calendar date and clock components.var pageDatePattern = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$`)
func parsePageDate(key, value string) (time.Time, error) { if !pageDatePattern.MatchString(value) { return time.Time{}, fmt.Errorf("config value %q must be an RFC3339 timestamp with a timezone", key) } date, err := time.Parse(time.RFC3339, value) if err != nil { return time.Time{}, fmt.Errorf("config value %q must be an RFC3339 timestamp with a timezone: %w", key, err) } return date, nil}
// Summary returns the explicit page summary, or an empty string when absent.func (zd *ZasData) Summary() (string, error) { value, ok := zd.Page["summary"] if !ok { return "", nil } summary, ok := value.(string) if !ok { return "", fmt.Errorf("config value %q must be a string, got %T", "summary", value) } return summary, nil}
// Tags returns the page's string list in its original order, or an empty// list when absent. A scalar or a list containing other types is an error.func (zd *ZasData) Tags() ([]string, error) { value, ok := zd.Page["tags"] if !ok { return nil, nil } switch tags := value.(type) { case []string: return append([]string(nil), tags...), nil case []interface{}: result := make([]string, len(tags)) for i, tag := range tags { text, ok := tag.(string) if !ok { return nil, fmt.Errorf("config value %q item %d must be a string, got %T", "tags", i, tag) } result[i] = text } return result, nil default: return nil, fmt.Errorf("config value %q must be a list of strings, got %T", "tags", value) }}
// Canonical returns the full URL, replacing a final /index.html with /.// Standalone .html URLs, Path, URL and sitemap generation are unchanged.func (zd *ZasData) Canonical() string { pagePath := zd.Path if strings.HasSuffix(pagePath, "/index.html") { pagePath = strings.TrimSuffix(pagePath, "index.html") } return strings.TrimRight(zd.Site.BaseURL, "/") + pagePath}