-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathMain.lean
More file actions
222 lines (188 loc) · 8.58 KB
/
Main.lean
File metadata and controls
222 lines (188 loc) · 8.58 KB
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import DocGen4
import Lean
import Cli
open DocGen4 DocGen4.DB DocGen4.Output Lean Cli
def runHeaderDataCmd (p : Parsed) : IO UInt32 := do
let buildDir := match p.flag? "build" with
| some dir => dir.as! String
| none => ".lake/build"
headerDataOutput buildDir
return 0
def runSingleCmd (p : Parsed) : IO UInt32 := do
let buildDir := match p.flag? "build" with
| some dir => dir.as! String
| none => ".lake/build"
let dbFile := p.positionalArg! "db" |>.as! String
let relevantModules := #[p.positionalArg! "module" |>.as! String |> String.toName]
let sourceUri := p.positionalArg! "sourceUri" |>.as! String
let doc ← load <| .analyzeConcreteModules relevantModules
updateModuleDb builtinDocstringValues doc buildDir dbFile (some sourceUri)
return 0
def runGenCoreCmd (p : Parsed) : IO UInt32 := do
let buildDir := match p.flag? "build" with
| some dir => dir.as! String
| none => ".lake/build"
let dbFile := p.positionalArg! "db" |>.as! String
let module := p.positionalArg! "module" |>.as! String |> String.toName
let doc ← load <| .analyzePrefixModules module
updateModuleDb builtinDocstringValues doc buildDir dbFile none
return 0
def runDocGenCmd (_p : Parsed) : IO UInt32 := do
IO.println "You most likely want to use me via Lake now, check my README on Github on how to:"
IO.println "https://github.com/leanprover/doc-gen4"
return 0
/-- A source linker that uses URLs from the database, falling back to core module URLs -/
def dbSourceLinker (sourceUrls : Std.HashMap Name String) (_gitUrl? : Option String) (module : Name) : Option DeclarationRange → String :=
let root := module.getRoot
let leanHash := Lean.githash
if root == `Lean ∨ root == `Init ∨ root == `Std then
let parts := module.components.map (Name.toString (escape := false))
let path := "/".intercalate parts
Output.SourceLinker.mkGithubSourceLinker s!"https://github.com/leanprover/lean4/blob/{leanHash}/src/{path}.lean"
else if root == `Lake then
let parts := module.components.map (Name.toString (escape := false))
let path := "/".intercalate parts
Output.SourceLinker.mkGithubSourceLinker s!"https://github.com/leanprover/lean4/blob/{leanHash}/src/lake/{path}.lean"
else
-- Look up source URL from database
match sourceUrls[module]? with
| some url =>
if url.startsWith "vscode://file/" then
Output.SourceLinker.mkVscodeSourceLinker url
else if url.startsWith "https://github.com" then
Output.SourceLinker.mkGithubSourceLinker url
else
fun _ => url
| none =>
-- Fallback for modules without source URL
fun _ => "#"
/-- Flush the WAL so the database file is self-contained. Connection is closed on return. -/
def walCheckpoint (dbPath : String) : IO Unit := do
-- The checkpoint requires a read-write connection, which can be blocked by concurrent
-- documentation info writes for other libraries that this library doesn't depend on. This uses a
-- very long timeout (24h) because a full Mathlib build on a slow machine could in principle keep
-- the DB locked for a long time.
let db ← SQLite.open dbPath (busyTimeoutMs := 86400000)
db.exec "PRAGMA wal_checkpoint(TRUNCATE)"
db.exec "PRAGMA optimize"
def runFromDbCmd (p : Parsed) : IO UInt32 := do
let buildDir := match p.flag? "build" with
| some dir => dir.as! String
| none => ".lake/build"
let dbPath := p.positionalArg! "db" |>.as! String
let manifestOutput? := (p.flag? "manifest").map (·.as! String)
let moduleRoots := (p.variableArgsAs! String).map String.toName
-- Flush WAL so the database file is self-contained for concurrent readers
walCheckpoint dbPath
-- Load linking context (module names, source URLs, declaration locations)
let db ← openForReading dbPath builtinDocstringValues
let linkCtx ← db.loadLinkingContext
-- Determine which modules to generate HTML for
let targetModules ←
if moduleRoots.isEmpty then
pure linkCtx.moduleNames
else
db.getTransitiveImports moduleRoots
let baseConfig ← getSimpleBaseContext buildDir (Hierarchy.fromArray targetModules)
-- Add `references` pseudo-module to hierarchy only when bibliography data exists
let hierarchy := Hierarchy.fromArray
(if baseConfig.refs.isEmpty then targetModules else targetModules.push `references)
let baseConfig := { baseConfig with hierarchy }
-- Parallel HTML generation
let (outputs, jsonModules) ← htmlOutputResultsParallel baseConfig dbPath linkCtx targetModules (sourceLinker? := some (dbSourceLinker linkCtx.sourceUrls))
-- Load all tactics from DB in sorted order and convert markdown docstrings to HTML
let allTacticsRaw ← db.loadAllTactics
let refsMap : Std.HashMap String BibItem :=
Std.HashMap.emptyWithCapacity baseConfig.refs.size |>.insertMany
(baseConfig.refs.iter.map fun x => (x.citekey, x))
let minimalSiteCtx : SiteContext := {
result := { name2ModIdx := linkCtx.name2ModIdx, moduleNames := linkCtx.moduleNames, moduleInfo := {} }
sourceLinker := fun _ _ => "#"
refsMap := refsMap
}
let (allTactics, _) := allTacticsRaw.mapM Process.TacticInfo.docStringToHtml |>.run {} minimalSiteCtx baseConfig
-- Generate the search index (declaration-data.bmp)
htmlOutputIndex baseConfig jsonModules allTactics
-- Update navbar to include all modules on disk
updateNavbarFromDisk buildDir
if let .some manifestOutput := manifestOutput? then
IO.FS.writeFile manifestOutput (Lean.toJson outputs).compress
return 0
def runBibPrepassCmd (p : Parsed) : IO UInt32 := do
let buildDir := match p.flag? "build" with
| some dir => dir.as! String
| none => ".lake/build"
if p.hasFlag "none" then
IO.println "INFO: reference page disabled"
disableBibFile buildDir
else
match p.variableArgsAs! String with
| #[source] =>
let contents ← IO.FS.readFile source
if p.hasFlag "json" then
IO.println "INFO: 'references.json' will be copied to the output path; there will be no 'references.bib'"
preprocessBibJson buildDir contents
else
preprocessBibFile buildDir contents Bibtex.process
| _ => throw <| IO.userError "there should be exactly one source file"
return 0
def singleCmd := `[Cli|
single VIA runSingleCmd;
"Populate the database with documentation for the specified module."
FLAGS:
b, build : String; "Build directory."
ARGS:
module : String; "The module to document."
db : String; "Path to the SQLite database (relative to build dir)"
sourceUri : String; "The sourceUri as computed by the Lake facet"
]
def genCoreCmd := `[Cli|
genCore VIA runGenCoreCmd;
"Populate the database with documentation for the specified Lean core module (Init, Std, Lake, Lean)."
FLAGS:
b, build : String; "Build directory."
ARGS:
module : String; "The core module prefix to document (e.g., Init, Lean)."
db : String; "Path to the SQLite database (relative to build dir)"
]
def bibPrepassCmd := `[Cli|
bibPrepass VIA runBibPrepassCmd;
"Run the bibliography prepass: copy the bibliography file to output directory. By default it assumes the input is '.bib'."
FLAGS:
n, none; "Disable bibliography in this project."
j, json; "The input file is '.json' which contains an array of objects with 4 fields: 'citekey', 'tag', 'html' and 'plaintext'."
b, build : String; "Build directory."
ARGS:
...source : String; "The bibliography file. We only support one file for input. Should be '.bib' or '.json' according to flags."
]
def headerDataCmd := `[Cli|
headerData VIA runHeaderDataCmd;
"Produce `header-data.bmp`, this allows embedding of doc-gen declarations into other pages and more."
FLAGS:
b, build : String; "Build directory."
]
-- Prior versions of doc-gen4 generated HTML for one module at a time, directly from the olean, and
-- then ran an index command at the end to create the search index. Now, `fromDb` generates all HTML
-- and the search index in a single pass from the DB.
def fromDbCmd := `[Cli|
fromDb VIA runFromDbCmd;
"Generate HTML documentation from a SQLite database."
FLAGS:
b, build : String; "Build directory (default: .lake/build)"
m, manifest : String; "Manifest output file, listing all generated HTML files."
ARGS:
db : String; "Path to the SQLite database"
...modules : String; "Optional: Module roots to generate docs for (computes transitive closure)"
]
def docGenCmd : Cmd := `[Cli|
"doc-gen4" VIA runDocGenCmd; ["0.1.0"]
"A documentation generator for Lean 4."
SUBCOMMANDS:
singleCmd;
genCoreCmd;
bibPrepassCmd;
headerDataCmd;
fromDbCmd
]
def main (args : List String) : IO UInt32 :=
docGenCmd.validate args