resolve sequences from the directory tree
A bundle nested under another bundle is a member of that series (ADR-0033), so
`Site.Sequence` walks up to the nearest bundle ancestor and back down to its
members: ordered by `order` where set, then by name. Members resolve through the
language fallback, so a chapter with no Bengali variant still holds its place in
Bengali reading order instead of breaking prev/next.
One `.Sequence` field carries both shapes a theme needs. A landing page renders
`.Members` as an archive; a chapter renders `.Prev`/`.Next`, which are pointers
into `.Members` so `{{with}}` yields nothing at the ends. `Index == 0` is what
tells the two apart.
`Query` was deliberately not extended. A series ascends where `Run` descends, and
an order knob on `Query` is the config knob rule 6 bans; instead `Site.keys()`
came out so both iterate the index one way, deleting `Run`'s own dedupe map.
`draft` is not honoured: no bundle carries the field and nothing else excludes
drafts, so entry 19 adds it in both places at once. Recorded in content-model.md
rather than left implied.
state.md also corrects six inventory rows that had drifted before this change —
three LOC figures, the test total, `go.mod`, and two lines that were flatly wrong
("Dependencies: none", "goldmark is not yet imported"). The coupling gate proves
state.md changed with the code; it cannot prove the numbers are right.
This commit is contained in:
@@ -43,6 +43,10 @@ type Bundle struct {
|
||||
// Aliases are paths that must keep resolving to this bundle, each redirecting to its canonical URL
|
||||
// (ADR-0008). Additive only: an alias is a promise never withdrawn.
|
||||
Aliases []string
|
||||
// Order is this bundle's position in the series it is nested under, zero when frontmatter omits it.
|
||||
// The convention is sparse (10, 20, 30), so zero is not a position: an unordered member sorts by name
|
||||
// after every ordered one (ADR-0033).
|
||||
Order int
|
||||
// Body is everything after the frontmatter, unrendered.
|
||||
Body []byte
|
||||
// Extra holds every frontmatter key other than title, exactly as YAML parsed it.
|
||||
@@ -123,6 +127,8 @@ func Parse(name string, data []byte) (Bundle, error) {
|
||||
b.Date = asTime(b.Extra["date"])
|
||||
b.Tags = terms(b.Extra["tags"])
|
||||
delete(b.Extra, "tags")
|
||||
b.Order = asInt(b.Extra["order"])
|
||||
delete(b.Extra, "order")
|
||||
return b, nil
|
||||
}
|
||||
|
||||
@@ -164,6 +170,20 @@ func asTime(v any) time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// asInt reads a frontmatter integer. yaml.v3 hands back an int for an unquoted number and a string for a
|
||||
// quoted one, so both spellings work and anything else is simply absent.
|
||||
func asInt(v any) int {
|
||||
switch t := v.(type) {
|
||||
case int:
|
||||
return t
|
||||
case string:
|
||||
if n, err := strconv.Atoi(t); err == nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// terms reads a scalar or sequence of tag names, preserving case and script.
|
||||
func terms(v any) []string {
|
||||
var out []string
|
||||
|
||||
Reference in New Issue
Block a user