Skip to content

Commit 59d83ac

Browse files
committed
docs: add history preprocessor directive
I was reading a post on [architecture decision records](https://andydote.co.uk/2019/06/29/architecture-decision-records/), and it pointed out that when processing documentation, one can leverage the vcs to add in historical information. This adds a tool that slip-streams the history of a file into the rendered output. Signed-off-by: Hank Donnay <[email protected]>
1 parent 3399a75 commit 59d83ac

File tree

2 files changed

+157
-0
lines changed

2 files changed

+157
-0
lines changed

Documentation/history.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
//go:build ignore
2+
// +build ignore
3+
4+
package main
5+
6+
import (
7+
"encoding/json"
8+
"html/template"
9+
"log"
10+
"os"
11+
"os/exec"
12+
"path/filepath"
13+
"regexp"
14+
"strings"
15+
)
16+
17+
func main() {
18+
log.SetFlags(log.LstdFlags | log.Lmsgprefix)
19+
log.SetPrefix("history: ")
20+
21+
// Handle when called with "supports $renderer".
22+
if len(os.Args) == 3 {
23+
switch os.Args[1] {
24+
case "supports":
25+
switch os.Args[2] {
26+
case "html":
27+
default:
28+
os.Exit(1)
29+
}
30+
default:
31+
os.Exit(1)
32+
}
33+
os.Exit(0)
34+
}
35+
36+
// Actual preprocessing mode.
37+
log.Println("running preprocessor")
38+
39+
in := make([]json.RawMessage, 2)
40+
dec := json.NewDecoder(os.Stdin)
41+
if err := dec.Decode(&in); err != nil {
42+
panic(err)
43+
}
44+
var cfg Config
45+
if err := json.Unmarshal(in[0], &cfg); err != nil {
46+
panic(err)
47+
}
48+
var book Book
49+
if err := json.Unmarshal(in[1], &book); err != nil {
50+
panic(err)
51+
}
52+
53+
var b strings.Builder
54+
for _, s := range book.Sections {
55+
if err := s.Process(&b, &cfg, tmpl); err != nil {
56+
panic(err)
57+
}
58+
}
59+
if err := json.NewEncoder(os.Stdout).Encode(&book); err != nil {
60+
panic(err)
61+
}
62+
}
63+
64+
// in: {"root":"/var/home/hank/work/clair/clair","config":{"book":{"authors":["Clair Authors"],"description":"Documentation for Clair.","language":"en","multilingual":false,"src":"Documentation","title":"Clair Documentation"},"output":{"html":{"git-repository-url":"https://github.com/quay/clair","preferred-dark-theme":"coal"}},"preprocessor":{"history":{"command":"go run Documentation/history.go"}}},"renderer":"html","mdbook_version":"0.4.13"}
65+
type Config struct {
66+
Root string `json:"root"`
67+
Renderer string `json:"renderer"`
68+
Version string `json:"mdbook_version"`
69+
Config struct {
70+
Book BookConfig `json:"book"`
71+
} `json:"config"`
72+
}
73+
74+
type BookConfig struct {
75+
Source string `json:"src"`
76+
}
77+
78+
type Book struct {
79+
Sections []Section `json:"sections"`
80+
X *struct{} `json:"__non_exhaustive"`
81+
}
82+
83+
type Section struct {
84+
Chapter *Chapter `json:",omitempty"`
85+
Separator interface{} `json:",omitempty"`
86+
PartTitle string `json:",omitempty"`
87+
}
88+
89+
func (s *Section) Process(b *strings.Builder, cfg *Config, t *template.Template) error {
90+
if s.Chapter != nil {
91+
return s.Chapter.Process(b, cfg, t)
92+
}
93+
return nil
94+
}
95+
96+
type Chapter struct {
97+
Name string `json:"name"`
98+
Content string `json:"content"`
99+
Number []int `json:"number"`
100+
SubItems []Section `json:"sub_items"`
101+
Path *string `json:"path"`
102+
SourcePath *string `json:"source_path"`
103+
ParentNames []string `json:"parent_names"`
104+
}
105+
106+
func (c *Chapter) Process(b *strings.Builder, cfg *Config, t *template.Template) error {
107+
if marker.MatchString(c.Content) && c.Path != nil {
108+
log.Println("inserting history into:", *c.Path)
109+
cmd := exec.Command(`git`, `log`, `--reverse`, "--format=%cs\t%s", `--`, filepath.Join(cfg.Root, cfg.Config.Book.Source, *c.Path))
110+
out, err := cmd.Output()
111+
if err != nil {
112+
return err
113+
}
114+
var ls []Line
115+
for _, l := range strings.FieldsFunc(string(out), func(r rune) bool { return r == '\n' }) {
116+
s := strings.SplitN(l, "\t", 2)
117+
ls = append(ls, Line{Date: s[0], Summary: s[1]})
118+
}
119+
b.Reset()
120+
if err := t.ExecuteTemplate(b, cfg.Renderer, ls); err != nil {
121+
return err
122+
}
123+
c.Content = marker.ReplaceAllString(c.Content, b.String())
124+
}
125+
for _, s := range c.SubItems {
126+
if err := s.Process(b, cfg, t); err != nil {
127+
return err
128+
}
129+
}
130+
return nil
131+
}
132+
133+
type Line struct {
134+
Date string
135+
Summary string
136+
}
137+
138+
var (
139+
tmpl *template.Template
140+
marker = regexp.MustCompile(`\{\{#\s*history\s*\}\}`)
141+
)
142+
143+
func init() {
144+
const (
145+
html = `<details>
146+
<summary>Document History</summary>
147+
<p><ul>
148+
{{ range . }}<li><strong>{{.Date}}</strong> {{.Summary}}</li>
149+
{{ end }}</ul></p>
150+
</details>`
151+
)
152+
tmpl = template.New("")
153+
template.Must(tmpl.New("html").Parse(html))
154+
}

book.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ create-missing = true
1212
[output.html]
1313
git-repository-url= "https://github.com/quay/clair"
1414
preferred-dark-theme = "coal"
15+
16+
[preprocessor.history]
17+
command = "go run Documentation/history.go"

0 commit comments

Comments
 (0)