~bigbes/sr-ht-spec

ref: 8255ff90741113fd85e6f15706127bb5c30ca3f5 sr-ht-spec/doc/doc.go -rw-r--r-- 5.6 KiB
8255ff90 — Eugene Blikh ci: export the version instead of sed-ing a tracked APKBUILD 9 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// Package doc is spec.sr.ht's read model for a space's documents: it turns the
// markdown blobs of one git revision into an addressable Archive, resolves the
// [[wikilinks]] between them, and renders them to HTML.
//
// It is warren's vault/ + render/ packages absorbed, with exactly one
// structural change and one consolidation.
//
// # No filesystem
//
// warren scanned a directory: filepath.WalkDir plus os.ReadFile. There is no
// checkout here, so a caller walks a git tree instead — every read resolves a
// revision and reads blobs, which is what makes the approved head, a pinned
// ?rev=<sha> and a proposal branch the same code path with a different rev.
// This package does no I/O of its own: FromDocuments and the seam warren
// already had, FromPages, build an Archive out of a document set that is handed
// to them, and everything downstream of Archive — resolution, backlinks,
// hierarchy, rendering — is unaware of where the pages came from. Resolving a
// revision and reading it belongs to service/, which is the layer that owns the
// read contract.
//
// # One frontmatter parser
//
// warren's vault/frontmatter.go had its own YAML header parser. core/ already
// owns that contract (core.SplitFrontmatter, core.ParseFrontmatter,
// core.Frontmatter, core.Schema, core.DocID) and it is the one the write plane
// and the push hook validate against. Front therefore embeds core.Frontmatter
// and adds only what core deliberately does not model — `parent:`, `aliases:`,
// `planned:`, and the ordered key list used for display and search text. Two
// parsers that disagree about a document header is a bug that surfaces months
// later, in the registry, not at the door.
//
// # Tolerant on the read path, strict at the door
//
// core's parser is strict, because a malformed header must be rejected when it
// is proposed or pushed. This package is not the door: `--push-option=
// skip-validation` exists, so a document with a broken header can be on the
// approved branch, and refusing to render it would turn a cosmetic typo into an
// outage. A header this package cannot parse degrades to "no frontmatter" — the
// document still renders, still indexes, and still has a title from its first
// H1 or its file name.
package doc

import "sourcecraft.dev/bigbes/sr-ht-spec/core"

// PageKind distinguishes ordinary documents from the two structurally unusual
// kinds — hand-maintained catalogs and append-only logs — whose links are
// suppressed in backlink counts.
type PageKind string

const (
	KindMarkdown PageKind = "markdown"
	// KindCatalog is a hand-maintained listing page that links to nearly every
	// document in its section. Marked by frontmatter `type: catalog`, or by the
	// file name `index.md` as a fallback.
	KindCatalog PageKind = "catalog"
	// KindLog is an append-only activity log carrying dated entries. Marked by
	// frontmatter `type: log`, or by the file name `log.md` as a fallback.
	KindLog PageKind = "log"
)

// Page is one document of a space at one revision, as the read plane addresses
// it. It carries no body: bodies live in git and are read by blob sha, which is
// what keeps an Archive cheap to build and impossible to serve stale.
type Page struct {
	// ID is the archive's addressing key: the document's frontmatter id when it
	// has a well-formed one that no other document in the space claims, and
	// otherwise its path without the ".md" extension.
	//
	// Falling back to the path rather than to warren's bare filename stem is
	// deliberate. warren keyed on the stem because Obsidian resolves wikilinks
	// that way; here `id` is the load-bearing field and paths are unique by
	// construction, so the fallback is unique too and cannot be taken away from
	// a document by an unrelated file appearing elsewhere.
	ID string `json:"id"`
	// DocID is the frontmatter `id:` when it parses as a core.DocID, otherwise
	// empty. A document with a malformed or duplicated id keeps its path and
	// stays readable — it is excluded from id resolution, not from the archive.
	DocID string `json:"doc_id,omitempty"`

	Kind  PageKind `json:"kind"`
	Title string   `json:"title"`
	// Path is the document's path in the git tree, forward-slashed, with its
	// ".md" extension.
	Path string `json:"path"`
	// Blob is the hex sha of the document's blob at this revision: the render
	// cache key. Content-addressed, so an entry keyed by it can never go stale.
	Blob string `json:"blob,omitempty"`

	Status  core.Status `json:"status,omitempty"`
	Summary string      `json:"summary,omitempty"`
	Tags    []string    `json:"tags,omitempty"`

	// ParentID is the resolved ID of the document named by `parent:`, or empty.
	ParentID string `json:"parent_id,omitempty"`
	// Section is the top-level directory the document lives under ("specs",
	// "notes", "reports"), used to group results and to resolve a bare wikilink
	// in favour of the linking document's own section.
	Section string `json:"section"`
	// Crumbs is the ordered list of ancestor IDs from root to this document's
	// parent (not including the document itself).
	Crumbs []string `json:"crumbs,omitempty"`
	// Links holds the IDs of documents this one links to, deduplicated, in
	// order of first appearance. Filled in by [Archive.LinkPass], not when the
	// archive is built: links come out of a render, not a frontmatter parse.
	Links []string `json:"links,omitempty"`
	// WordCount is an approximate word count of the rendered text. Filled in by
	// [Archive.LinkPass], from the same render.
	WordCount int `json:"word_count,omitempty"`
}

// DocProperty is one frontmatter key flattened to text, in document order.
type DocProperty struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}