-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcodex.go
137 lines (117 loc) · 3.24 KB
/
codex.go
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package main
import (
"errors"
"fmt"
"github.com/PuerkitoBio/goquery"
"golang.org/x/sync/errgroup"
"log"
)
const (
PandocConcurrency = 3 // maximum number of pandoc subprocesses
)
// Codex holds the context for a single instance of the codex app.
// It's intended to be instantiated only once, using CLI arguments.
type Codex struct {
Inputs map[string]*Document
HtmlDoc *goquery.Document
HtmlStr string
pandocPool *PandocPool
}
func NewCodex(paths []string) (*Codex, error) {
if len(paths) == 0 {
return nil, errors.New("Need at least one input")
}
codocs := make(map[string]*Document)
for _, filePath := range paths {
codocs[filePath] = NewDocument(filePath)
}
cdx := Codex{
Inputs: codocs,
pandocPool: NewPandocPool(PandocConcurrency),
}
doc, err := cdx.DOMSkeleton()
if err != nil {
return nil, err
}
cdx.HtmlDoc = doc
log.Println("Starting with", len(cdx.Inputs), "input document(s)")
if err := cdx.BuildAll(); err != nil {
return nil, err
}
log.Println("Finished building from", len(cdx.Inputs), "docs")
return &cdx, nil
}
// DOMSkeleton loads the codex HTML template and creates stand-in
// <article> elements in <main> for each of the input Documents.
// <html> ... <body>
// <main>
// <article codex-source="example.md" ...> </article>
// <article codex-source="other.rst" ...> </article>
// ...
// </main>
// </body> </html>
func (cdx *Codex) DOMSkeleton() (*goquery.Document, error) {
doc, err := LoadHtml(CodexOutputTemplate)
if err != nil {
return nil, err
}
main := doc.Find("main")
for _, codoc := range cdx.Inputs {
main.AppendHtml(fmt.Sprintf(`<article codex-source="%s"/>`, codoc.Path))
}
return doc, nil
}
// CurrentDOMArticle returns a goquery Selection containing the current DOM
// <article> corresponding to the given input Document.
func (cdx *Codex) CurrentDOMArticle(codoc *Document) *goquery.Selection {
selector := fmt.Sprintf(`article[codex-source="%s"]`, codoc.Path)
article := cdx.HtmlDoc.Find(selector)
if article.Length() == 0 {
log.Fatal(errors.New(fmt.Sprintf("Unexpected input doc: %s", codoc.Path)))
}
return article
}
// Update rebuilds the specified document and updates its DOM <article>.
func (cdx *Codex) Update(codoc *Document) (string, error) {
article := cdx.CurrentDOMArticle(codoc)
innerHtml, err := cdx.Transform(codoc)
if err != nil {
return "", err
}
article.SetHtml(innerHtml)
article.SetAttr("codex-mtime", ToIso8601(codoc.Mtime))
cdx.HtmlStr = DocToHtml(cdx.HtmlDoc)
return OuterHtml(article), nil
}
// Transform takes an input Document and returns it as codex HTML.
func (cdx *Codex) Transform(codoc *Document) (string, error) {
codoc.CheckMtime()
codoc.SetBtime()
htmlDoc, err := cdx.pandocPool.Run(codoc.Path)
if err != nil {
return "", err
}
Treeify(htmlDoc)
return InnerHtml(htmlDoc.Find("body")), nil
}
func (cdx *Codex) BuildAll() error {
var errg errgroup.Group
for _, codoc := range cdx.Inputs {
codoc := codoc // because closure below
errg.Go(func() error {
_, err := cdx.Update(codoc)
if err != nil {
return err
}
return nil
})
}
if err := errg.Wait(); err != nil {
return err
}
cdx.HtmlStr = DocToHtml(cdx.HtmlDoc)
return nil
}
func (cdx *Codex) Html() string {
return cdx.HtmlStr
}