hide drafts and future-dated bundles; reveal them with -dev on
A draft is now not served at all, and neither is a bundle whose date has not arrived. The filter sits in `Site.Lookup` and `Site.Run`, which is every path to a bundle — so the files inside an unpublished bundle inherit its status for free, which is what ADR-0024 asks for and what the asset route was written to allow. A test asserts the 404 for the bundle *and* its picture, and that nothing leaks into a listing, a feed or a sitemap. The clock is read per request rather than at startup, so a scheduled post appears exactly when its date arrives with nothing to restart and nothing to invalidate. That first clock read created internal/content/clock.go, which is the only place `verify.sh` allows `time.Now` — a render that depends on the time is worth being able to find. `-dev on` reveals both and reparses the theme before each render. Deliberately not a bare boolean flag: turning unpublished work into public work should not be one fumbled argument away. A reload that fails to parse leaves the working template set in place, so a typo shows an error rather than replacing a good set with a broken one.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
package content
|
||||
|
||||
import "time"
|
||||
|
||||
// now is the only place the engine reads the wall clock, which `verify.sh` enforces by filename: a render
|
||||
// that depends on the time is only true for a while, and that is worth being able to find.
|
||||
//
|
||||
// A variable so a test can hold time still. Nothing else assigns it.
|
||||
var now = time.Now
|
||||
|
||||
// Now is the current time as the engine sees it, for whoever needs to date something.
|
||||
func Now() time.Time { return now() }
|
||||
@@ -49,6 +49,9 @@ type Bundle struct {
|
||||
// Route is the path this bundle is served at: its Key, unless a slug renamed the last segment. Set by
|
||||
// NewSite, which is the only place that can see whether every variant agrees.
|
||||
Route string
|
||||
// Draft is true when frontmatter says so. A draft is not served at all until `-dev` reveals it, and
|
||||
// neither are the files inside its bundle (ADR-0024).
|
||||
Draft bool
|
||||
// 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).
|
||||
@@ -159,6 +162,8 @@ func Parse(name string, data []byte) (Bundle, error) {
|
||||
delete(b.Extra, "tags")
|
||||
b.Order = asInt(b.Extra["order"])
|
||||
delete(b.Extra, "order")
|
||||
b.Draft, _ = b.Extra["draft"].(bool)
|
||||
delete(b.Extra, "draft")
|
||||
if slug, isStr := b.Extra["slug"].(string); isStr {
|
||||
// One segment, normalised like every other identifier (ADR-0015). Slashes would let a slug move the
|
||||
// bundle to another section, which is a move, not a rename.
|
||||
@@ -169,6 +174,15 @@ func Parse(name string, data []byte) (Bundle, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Published reports whether a bundle is visible to a reader at the given moment.
|
||||
//
|
||||
// Two ways not to be: marked a draft, or dated in the future. The second is the same rule seen from the other
|
||||
// side — a bundle becomes public exactly when its own date arrives, with nothing to run and nothing to
|
||||
// invalidate (content-model.md).
|
||||
func (b Bundle) Published(at time.Time) bool {
|
||||
return !b.Draft && !b.Date.After(at)
|
||||
}
|
||||
|
||||
// Assets is the directory holding a bundle's own local files, and false for a bundle that has none.
|
||||
//
|
||||
// Only a directory bundle has one. A single-file bundle's neighbours belong to its section rather than to it,
|
||||
|
||||
@@ -16,6 +16,8 @@ type Site struct {
|
||||
keyByRoute map[string]string
|
||||
// problems are what indexing worked around, kept for `check` rather than only logged.
|
||||
problems []Problem
|
||||
// reveal serves drafts and future-dated bundles, for `-dev` only.
|
||||
reveal bool
|
||||
// renamed records keys that a slug moved away from, so the old path answers 404 instead of still working
|
||||
// — the engine serves the new path only (ADR-0035), and an author who wants both writes an alias.
|
||||
renamed map[string]bool
|
||||
@@ -38,6 +40,19 @@ func NewSite(bundles []Bundle) *Site {
|
||||
return s
|
||||
}
|
||||
|
||||
// Reveal makes drafts and future-dated bundles visible. Only `-dev` calls it: in a serving build an
|
||||
// unpublished bundle answers 404 for itself and for every file inside it (ADR-0024).
|
||||
func (s *Site) Reveal() { s.reveal = true }
|
||||
|
||||
// visible reports whether a bundle may be served now.
|
||||
//
|
||||
// Asked in Lookup and in Run, which is every path to a bundle — so hiding a draft is one rule in one place
|
||||
// rather than a filter each caller must remember. The clock is read here rather than at startup, so a
|
||||
// future-dated bundle appears the moment its date arrives without anything being restarted or invalidated.
|
||||
func (s *Site) visible(b Bundle) bool {
|
||||
return s.reveal || b.Published(now())
|
||||
}
|
||||
|
||||
// Problems lists what indexing worked around: a contested alias, a slug two variants disagree on, a slug
|
||||
// landing where something already answers. Each was also logged.
|
||||
func (s *Site) Problems() []Problem { return s.problems }
|
||||
@@ -171,13 +186,14 @@ func (s *Site) Lookup(key, lang string) (b Bundle, served string, ok bool) {
|
||||
if try == "" {
|
||||
continue
|
||||
}
|
||||
if b, ok = s.byKeyLang[key+"\x00"+try]; ok {
|
||||
if b, ok = s.byKeyLang[key+"\x00"+try]; ok && s.visible(b) {
|
||||
return b, try, true
|
||||
}
|
||||
}
|
||||
for _, l := range s.Variants(key) {
|
||||
b = s.byKeyLang[key+"\x00"+l]
|
||||
return b, l, true
|
||||
if b = s.byKeyLang[key+"\x00"+l]; s.visible(b) {
|
||||
return b, l, true
|
||||
}
|
||||
}
|
||||
return Bundle{}, "", false
|
||||
}
|
||||
@@ -379,7 +395,9 @@ func (s *Site) Everything() []Bundle {
|
||||
out := make([]Bundle, 0, len(s.byKeyLang))
|
||||
for _, key := range s.keys() {
|
||||
for _, lang := range s.Variants(key) {
|
||||
out = append(out, s.byKeyLang[key+"\x00"+lang])
|
||||
if b := s.byKeyLang[key+"\x00"+lang]; s.visible(b) {
|
||||
out = append(out, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
|
||||
Reference in New Issue
Block a user