Update to doc-gen4 commit `e859e2f`.
parent
7907803093
commit
9dca45a997
|
@ -1,5 +1,6 @@
|
||||||
# Lean
|
# Lean
|
||||||
build
|
build
|
||||||
|
lakefile.olean
|
||||||
lake-packages
|
lake-packages
|
||||||
_target
|
_target
|
||||||
leanpkg.path
|
leanpkg.path
|
||||||
|
|
|
@ -5,35 +5,17 @@ Authors: Henrik Böving
|
||||||
-/
|
-/
|
||||||
|
|
||||||
import Lean
|
import Lean
|
||||||
import Lake
|
|
||||||
import Lake.CLI.Main
|
|
||||||
import DocGen4.Process
|
import DocGen4.Process
|
||||||
import Lean.Data.HashMap
|
import Lean.Data.HashMap
|
||||||
|
|
||||||
namespace DocGen4
|
namespace DocGen4
|
||||||
|
|
||||||
open Lean System IO
|
open Lean System IO
|
||||||
/--
|
|
||||||
Sets up a lake workspace for the current project. Furthermore initialize
|
|
||||||
the Lean search path with the path to the proper compiler from lean-toolchain
|
|
||||||
as well as all the dependencies.
|
|
||||||
-/
|
|
||||||
def lakeSetup : IO (Except UInt32 Lake.Workspace) := do
|
|
||||||
let (leanInstall?, lakeInstall?) ← Lake.findInstall?
|
|
||||||
let config := Lake.mkLoadConfig.{0} {leanInstall?, lakeInstall?}
|
|
||||||
match ←(EIO.toIO' config) with
|
|
||||||
| .ok config =>
|
|
||||||
let ws : Lake.Workspace ← Lake.loadWorkspace config
|
|
||||||
|>.run Lake.MonadLog.eio
|
|
||||||
|>.toIO (λ _ => IO.userError "Failed to load Lake workspace")
|
|
||||||
pure <| Except.ok ws
|
|
||||||
| .error err =>
|
|
||||||
throw <| IO.userError err.toString
|
|
||||||
|
|
||||||
def envOfImports (imports : List Name) : IO Environment := do
|
def envOfImports (imports : Array Name) : IO Environment := do
|
||||||
importModules (imports.map (Import.mk · false)) Options.empty
|
importModules (imports.map (Import.mk · false)) Options.empty
|
||||||
|
|
||||||
def loadInit (imports : List Name) : IO Hierarchy := do
|
def loadInit (imports : Array Name) : IO Hierarchy := do
|
||||||
let env ← envOfImports imports
|
let env ← envOfImports imports
|
||||||
pure <| Hierarchy.fromArray env.header.moduleNames
|
pure <| Hierarchy.fromArray env.header.moduleNames
|
||||||
|
|
||||||
|
@ -42,6 +24,7 @@ Load a list of modules from the current Lean search path into an `Environment`
|
||||||
to process for documentation.
|
to process for documentation.
|
||||||
-/
|
-/
|
||||||
def load (task : Process.AnalyzeTask) : IO (Process.AnalyzerResult × Hierarchy) := do
|
def load (task : Process.AnalyzeTask) : IO (Process.AnalyzerResult × Hierarchy) := do
|
||||||
|
initSearchPath (← findSysroot)
|
||||||
let env ← envOfImports task.getLoad
|
let env ← envOfImports task.getLoad
|
||||||
let config := {
|
let config := {
|
||||||
-- TODO: parameterize maxHeartbeats
|
-- TODO: parameterize maxHeartbeats
|
||||||
|
@ -55,6 +38,6 @@ def load (task : Process.AnalyzeTask) : IO (Process.AnalyzerResult × Hierarchy)
|
||||||
Prod.fst <$> Meta.MetaM.toIO (Process.process task) config { env := env } {} {}
|
Prod.fst <$> Meta.MetaM.toIO (Process.process task) config { env := env } {} {}
|
||||||
|
|
||||||
def loadCore : IO (Process.AnalyzerResult × Hierarchy) := do
|
def loadCore : IO (Process.AnalyzerResult × Hierarchy) := do
|
||||||
load <| .loadAll [`Init, `Lean, `Lake]
|
load <| .loadAll #[`Init, `Lean, `Lake]
|
||||||
|
|
||||||
end DocGen4
|
end DocGen4
|
||||||
|
|
|
@ -4,7 +4,6 @@ Released under Apache 2.0 license as described in the file LICENSE.
|
||||||
Authors: Henrik Böving
|
Authors: Henrik Böving
|
||||||
-/
|
-/
|
||||||
import Lean
|
import Lean
|
||||||
import Lake
|
|
||||||
import DocGen4.Process
|
import DocGen4.Process
|
||||||
import DocGen4.Output.Base
|
import DocGen4.Output.Base
|
||||||
import DocGen4.Output.Index
|
import DocGen4.Output.Index
|
||||||
|
@ -42,6 +41,8 @@ def htmlOutputSetup (config : SiteBaseContext) : IO Unit := do
|
||||||
("declaration-data.js", declarationDataCenterJs),
|
("declaration-data.js", declarationDataCenterJs),
|
||||||
("color-scheme.js", colorSchemeJs),
|
("color-scheme.js", colorSchemeJs),
|
||||||
("nav.js", navJs),
|
("nav.js", navJs),
|
||||||
|
("jump-src.js", jumpSrcJs),
|
||||||
|
("expand-nav.js", expandNavJs),
|
||||||
("how-about.js", howAboutJs),
|
("how-about.js", howAboutJs),
|
||||||
("search.html", searchHtml),
|
("search.html", searchHtml),
|
||||||
("search.js", searchJs),
|
("search.js", searchJs),
|
||||||
|
@ -79,19 +80,18 @@ def htmlOutputDeclarationDatas (result : AnalyzerResult) : HtmlT IO Unit := do
|
||||||
let jsonDecls ← Module.toJson mod
|
let jsonDecls ← Module.toJson mod
|
||||||
FS.writeFile (declarationsBasePath / s!"declaration-data-{mod.name}.bmp") (toJson jsonDecls).compress
|
FS.writeFile (declarationsBasePath / s!"declaration-data-{mod.name}.bmp") (toJson jsonDecls).compress
|
||||||
|
|
||||||
def htmlOutputResults (baseConfig : SiteBaseContext) (result : AnalyzerResult) (ws : Lake.Workspace) (ink : Bool) : IO Unit := do
|
def htmlOutputResults (baseConfig : SiteBaseContext) (result : AnalyzerResult) (gitUrl? : Option String) (ink : Bool) : IO Unit := do
|
||||||
let config : SiteContext := {
|
let config : SiteContext := {
|
||||||
result := result,
|
result := result,
|
||||||
sourceLinker := ← SourceLinker.sourceLinker ws
|
sourceLinker := ← SourceLinker.sourceLinker gitUrl?
|
||||||
leanInkEnabled := ink
|
leanInkEnabled := ink
|
||||||
}
|
}
|
||||||
|
|
||||||
FS.createDirAll basePath
|
FS.createDirAll basePath
|
||||||
FS.createDirAll declarationsBasePath
|
FS.createDirAll declarationsBasePath
|
||||||
|
|
||||||
-- Rendering the entire lean compiler takes time....
|
let some p := (← IO.getEnv "LEAN_SRC_PATH") | throw <| IO.userError "LEAN_SRC_PATH not found in env"
|
||||||
--let sourceSearchPath := ((←Lean.findSysroot) / "src" / "lean") :: ws.root.srcDir :: ws.leanSrcPath
|
let sourceSearchPath := System.SearchPath.parse p
|
||||||
let sourceSearchPath := ws.root.srcDir :: ws.leanSrcPath
|
|
||||||
|
|
||||||
discard <| htmlOutputDeclarationDatas result |>.run config baseConfig
|
discard <| htmlOutputDeclarationDatas result |>.run config baseConfig
|
||||||
|
|
||||||
|
@ -119,8 +119,6 @@ def getSimpleBaseContext (hierarchy : Hierarchy) : IO SiteBaseContext := do
|
||||||
depthToRoot := 0,
|
depthToRoot := 0,
|
||||||
currentName := none,
|
currentName := none,
|
||||||
hierarchy
|
hierarchy
|
||||||
projectGithubUrl := ← SourceLinker.getProjectGithubUrl
|
|
||||||
projectCommit := ← SourceLinker.getProjectCommit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def htmlOutputIndex (baseConfig : SiteBaseContext) : IO Unit := do
|
def htmlOutputIndex (baseConfig : SiteBaseContext) : IO Unit := do
|
||||||
|
@ -146,9 +144,9 @@ def htmlOutputIndex (baseConfig : SiteBaseContext) : IO Unit := do
|
||||||
The main entrypoint for outputting the documentation HTML based on an
|
The main entrypoint for outputting the documentation HTML based on an
|
||||||
`AnalyzerResult`.
|
`AnalyzerResult`.
|
||||||
-/
|
-/
|
||||||
def htmlOutput (result : AnalyzerResult) (hierarchy : Hierarchy) (ws : Lake.Workspace) (ink : Bool) : IO Unit := do
|
def htmlOutput (result : AnalyzerResult) (hierarchy : Hierarchy) (gitUrl? : Option String) (ink : Bool) : IO Unit := do
|
||||||
let baseConfig ← getSimpleBaseContext hierarchy
|
let baseConfig ← getSimpleBaseContext hierarchy
|
||||||
htmlOutputResults baseConfig result ws ink
|
htmlOutputResults baseConfig result gitUrl? ink
|
||||||
htmlOutputIndex baseConfig
|
htmlOutputIndex baseConfig
|
||||||
|
|
||||||
end DocGen4
|
end DocGen4
|
||||||
|
|
|
@ -33,14 +33,6 @@ structure SiteBaseContext where
|
||||||
pages that don't have a module name.
|
pages that don't have a module name.
|
||||||
-/
|
-/
|
||||||
currentName : Option Name
|
currentName : Option Name
|
||||||
/--
|
|
||||||
The Github URL of the project that we are building docs for.
|
|
||||||
-/
|
|
||||||
projectGithubUrl : String
|
|
||||||
/--
|
|
||||||
The commit of the project that we are building docs for.
|
|
||||||
-/
|
|
||||||
projectCommit : String
|
|
||||||
|
|
||||||
/--
|
/--
|
||||||
The context used in the `HtmlM` monad for HTML templating.
|
The context used in the `HtmlM` monad for HTML templating.
|
||||||
|
@ -94,8 +86,6 @@ def getCurrentName : BaseHtmlM (Option Name) := do return (← read).currentName
|
||||||
def getResult : HtmlM AnalyzerResult := do return (← read).result
|
def getResult : HtmlM AnalyzerResult := do return (← read).result
|
||||||
def getSourceUrl (module : Name) (range : Option DeclarationRange): HtmlM String := do return (← read).sourceLinker module range
|
def getSourceUrl (module : Name) (range : Option DeclarationRange): HtmlM String := do return (← read).sourceLinker module range
|
||||||
def leanInkEnabled? : HtmlM Bool := do return (← read).leanInkEnabled
|
def leanInkEnabled? : HtmlM Bool := do return (← read).leanInkEnabled
|
||||||
def getProjectGithubUrl : BaseHtmlM String := do return (← read).projectGithubUrl
|
|
||||||
def getProjectCommit : BaseHtmlM String := do return (← read).projectCommit
|
|
||||||
|
|
||||||
/--
|
/--
|
||||||
If a template is meant to be extended because it for example only provides the
|
If a template is meant to be extended because it for example only provides the
|
||||||
|
@ -156,7 +146,9 @@ are used in documentation generation, notably JS and CSS ones.
|
||||||
def styleCss : String := include_str "../../static/style.css"
|
def styleCss : String := include_str "../../static/style.css"
|
||||||
def declarationDataCenterJs : String := include_str "../../static/declaration-data.js"
|
def declarationDataCenterJs : String := include_str "../../static/declaration-data.js"
|
||||||
def colorSchemeJs : String := include_str "../../static/color-scheme.js"
|
def colorSchemeJs : String := include_str "../../static/color-scheme.js"
|
||||||
|
def jumpSrcJs : String := include_str "../../static/jump-src.js"
|
||||||
def navJs : String := include_str "../../static/nav.js"
|
def navJs : String := include_str "../../static/nav.js"
|
||||||
|
def expandNavJs : String := include_str "../../static/expand-nav.js"
|
||||||
def howAboutJs : String := include_str "../../static/how-about.js"
|
def howAboutJs : String := include_str "../../static/how-about.js"
|
||||||
def searchJs : String := include_str "../../static/search.js"
|
def searchJs : String := include_str "../../static/search.js"
|
||||||
def instancesJs : String := include_str "../../static/instances.js"
|
def instancesJs : String := include_str "../../static/instances.js"
|
||||||
|
@ -261,11 +253,12 @@ partial def infoFormatToHtml (i : CodeWithInfos) : HtmlM (Array Html) := do
|
||||||
| .sort _ =>
|
| .sort _ =>
|
||||||
match t with
|
match t with
|
||||||
| .text t =>
|
| .text t =>
|
||||||
let mut sortPrefix :: rest := t.splitOn " " | unreachable!
|
let sortPrefix :: rest := t.splitOn " " | unreachable!
|
||||||
let sortLink := <a href={s!"{← getRoot}foundational_types.html"}>{sortPrefix}</a>
|
let sortLink := <a href={s!"{← getRoot}foundational_types.html"}>{sortPrefix}</a>
|
||||||
if rest != [] then
|
let mut restStr := String.intercalate " " rest
|
||||||
rest := " " :: rest
|
if restStr.length != 0 then
|
||||||
return #[sortLink, Html.text <| String.join rest]
|
restStr := " " ++ restStr
|
||||||
|
return #[sortLink, Html.text restStr]
|
||||||
| _ =>
|
| _ =>
|
||||||
return #[<a href={s!"{← getRoot}foundational_types.html"}>[← infoFormatToHtml t]</a>]
|
return #[<a href={s!"{← getRoot}foundational_types.html"}>[← infoFormatToHtml t]</a>]
|
||||||
| _ =>
|
| _ =>
|
||||||
|
|
|
@ -70,7 +70,9 @@ partial def xmlGetHeadingId (el : Xml.Element) : String :=
|
||||||
-/
|
-/
|
||||||
def nameToLink? (s : String) : HtmlM (Option String) := do
|
def nameToLink? (s : String) : HtmlM (Option String) := do
|
||||||
let res ← getResult
|
let res ← getResult
|
||||||
if let some name := Lean.Syntax.decodeNameLit ("`" ++ s) then
|
if s.endsWith ".lean" && s.contains '/' then
|
||||||
|
return (← getRoot) ++ s.dropRight 5 ++ ".html"
|
||||||
|
else if let some name := Lean.Syntax.decodeNameLit ("`" ++ s) then
|
||||||
-- with exactly the same name
|
-- with exactly the same name
|
||||||
if res.name2ModIdx.contains name then
|
if res.name2ModIdx.contains name then
|
||||||
declNameToLink name
|
declNameToLink name
|
||||||
|
@ -208,7 +210,7 @@ partial def modifyElement (element : Element) : HtmlM Element :=
|
||||||
|
|
||||||
/-- Convert docstring to Html. -/
|
/-- Convert docstring to Html. -/
|
||||||
def docStringToHtml (s : String) : HtmlM (Array Html) := do
|
def docStringToHtml (s : String) : HtmlM (Array Html) := do
|
||||||
let rendered := CMark.renderHtml s
|
let rendered := CMark.renderHtml (Html.escape s)
|
||||||
match manyDocument rendered.mkIterator with
|
match manyDocument rendered.mkIterator with
|
||||||
| Parsec.ParseResult.success _ res =>
|
| Parsec.ParseResult.success _ res =>
|
||||||
res.mapM fun x => do return Html.text <| toString (← modifyElement x)
|
res.mapM fun x => do return Html.text <| toString (← modifyElement x)
|
||||||
|
|
|
@ -4,97 +4,31 @@ Released under Apache 2.0 license as described in the file LICENSE.
|
||||||
Authors: Henrik Böving
|
Authors: Henrik Böving
|
||||||
-/
|
-/
|
||||||
import Lean
|
import Lean
|
||||||
import Lake.Load
|
|
||||||
|
|
||||||
namespace DocGen4.Output.SourceLinker
|
namespace DocGen4.Output.SourceLinker
|
||||||
|
|
||||||
open Lean
|
open Lean
|
||||||
|
|
||||||
/--
|
|
||||||
Turns a Github git remote URL into an HTTPS Github URL.
|
|
||||||
Three link types from git supported:
|
|
||||||
- https://github.com/org/repo
|
|
||||||
- https://github.com/org/repo.git
|
|
||||||
- git@github.com:org/repo.git
|
|
||||||
|
|
||||||
TODO: This function is quite brittle and very Github specific, we can
|
|
||||||
probably do better.
|
|
||||||
-/
|
|
||||||
def getGithubBaseUrl (gitUrl : String) : String := Id.run do
|
|
||||||
let mut url := gitUrl
|
|
||||||
if url.startsWith "git@" then
|
|
||||||
url := url.drop 15
|
|
||||||
url := url.dropRight 4
|
|
||||||
return s!"https://github.com/{url}"
|
|
||||||
else if url.endsWith ".git" then
|
|
||||||
return url.dropRight 4
|
|
||||||
else
|
|
||||||
return url
|
|
||||||
|
|
||||||
/--
|
|
||||||
Obtain the Github URL of a project by parsing the origin remote.
|
|
||||||
-/
|
|
||||||
def getProjectGithubUrl (directory : System.FilePath := "." ) : IO String := do
|
|
||||||
let out ← IO.Process.output {
|
|
||||||
cmd := "git",
|
|
||||||
args := #["remote", "get-url", "origin"],
|
|
||||||
cwd := directory
|
|
||||||
}
|
|
||||||
if out.exitCode != 0 then
|
|
||||||
throw <| IO.userError <| "git exited with code " ++ toString out.exitCode
|
|
||||||
return out.stdout.trimRight
|
|
||||||
|
|
||||||
/--
|
|
||||||
Obtain the git commit hash of the project that is currently getting analyzed.
|
|
||||||
-/
|
|
||||||
def getProjectCommit (directory : System.FilePath := "." ) : IO String := do
|
|
||||||
let out ← IO.Process.output {
|
|
||||||
cmd := "git",
|
|
||||||
args := #["rev-parse", "HEAD"]
|
|
||||||
cwd := directory
|
|
||||||
}
|
|
||||||
if out.exitCode != 0 then
|
|
||||||
throw <| IO.userError <| "git exited with code " ++ toString out.exitCode
|
|
||||||
return out.stdout.trimRight
|
|
||||||
|
|
||||||
/--
|
/--
|
||||||
Given a lake workspace with all the dependencies as well as the hash of the
|
Given a lake workspace with all the dependencies as well as the hash of the
|
||||||
compiler release to work with this provides a function to turn names of
|
compiler release to work with this provides a function to turn names of
|
||||||
declarations into (optionally positional) Github URLs.
|
declarations into (optionally positional) Github URLs.
|
||||||
-/
|
-/
|
||||||
def sourceLinker (ws : Lake.Workspace) : IO (Name → Option DeclarationRange → String) := do
|
def sourceLinker (gitUrl? : Option String) : IO (Name → Option DeclarationRange → String) := do
|
||||||
let leanHash := ws.lakeEnv.lean.githash
|
-- TOOD: Refactor this, we don't need to pass in the module into the returned closure
|
||||||
-- Compute a map from package names to source URL
|
-- since we have one sourceLinker per module
|
||||||
let mut gitMap := Lean.mkHashMap
|
|
||||||
let projectBaseUrl := getGithubBaseUrl (← getProjectGithubUrl)
|
|
||||||
let projectCommit ← getProjectCommit
|
|
||||||
gitMap := gitMap.insert ws.root.name (projectBaseUrl, projectCommit)
|
|
||||||
let manifest ← Lake.Manifest.loadOrEmpty ws.root.manifestFile
|
|
||||||
|>.run (Lake.MonadLog.eio .normal)
|
|
||||||
|>.toIO (fun _ => IO.userError "Failed to load lake manifest")
|
|
||||||
for pkg in manifest.entryArray do
|
|
||||||
match pkg with
|
|
||||||
| .git _ _ _ url rev .. => gitMap := gitMap.insert pkg.name (getGithubBaseUrl url, rev)
|
|
||||||
| .path _ _ _ path =>
|
|
||||||
let pkgBaseUrl := getGithubBaseUrl (← getProjectGithubUrl path)
|
|
||||||
let pkgCommit ← getProjectCommit path
|
|
||||||
gitMap := gitMap.insert pkg.name (pkgBaseUrl, pkgCommit)
|
|
||||||
|
|
||||||
return fun module range =>
|
return fun module range =>
|
||||||
let parts := module.components.map Name.toString
|
let parts := module.components.map Name.toString
|
||||||
let path := (parts.intersperse "/").foldl (· ++ ·) ""
|
let path := String.intercalate "/" parts
|
||||||
let root := module.getRoot
|
let root := module.getRoot
|
||||||
|
let leanHash := Lean.githash
|
||||||
let basic := if root == `Lean ∨ root == `Init then
|
let basic := if root == `Lean ∨ root == `Init then
|
||||||
s!"https://github.com/leanprover/lean4/blob/{leanHash}/src/{path}.lean"
|
s!"https://github.com/leanprover/lean4/blob/{leanHash}/src/{path}.lean"
|
||||||
else if root == `Lake then
|
else if root == `Lake then
|
||||||
s!"https://github.com/leanprover/lean4/blob/{leanHash}/src/lake/{path}.lean"
|
s!"https://github.com/leanprover/lean4/blob/{leanHash}/src/lake/{path}.lean"
|
||||||
else
|
else
|
||||||
match ws.packageArray.find? (·.isLocalModule module) with
|
gitUrl?.get!
|
||||||
| some pkg =>
|
|
||||||
match gitMap.find? pkg.name with
|
|
||||||
| some (baseUrl, commit) => s!"{baseUrl}/blob/{commit}/{path}.lean"
|
|
||||||
| none => "https://example.com"
|
|
||||||
| none => "https://example.com"
|
|
||||||
|
|
||||||
match range with
|
match range with
|
||||||
| some range => s!"{basic}#L{range.pos.line}-L{range.endPos.line}"
|
| some range => s!"{basic}#L{range.pos.line}-L{range.endPos.line}"
|
||||||
|
|
|
@ -32,7 +32,9 @@ def baseHtmlGenerator (title : String) (site : Array Html) : BaseHtmlM Html := d
|
||||||
|
|
||||||
<script>{s!"const SITE_ROOT={String.quote (← getRoot)};"}</script>
|
<script>{s!"const SITE_ROOT={String.quote (← getRoot)};"}</script>
|
||||||
[moduleConstant]
|
[moduleConstant]
|
||||||
|
<script type="module" src={s!"{← getRoot}jump-src.js"}></script>
|
||||||
<script type="module" src={s!"{← getRoot}search.js"}></script>
|
<script type="module" src={s!"{← getRoot}search.js"}></script>
|
||||||
|
<script type="module" src={s!"{← getRoot}expand-nav.js"}></script>
|
||||||
<script type="module" src={s!"{← getRoot}how-about.js"}></script>
|
<script type="module" src={s!"{← getRoot}how-about.js"}></script>
|
||||||
<script type="module" src={s!"{← getRoot}instances.js"}></script>
|
<script type="module" src={s!"{← getRoot}instances.js"}></script>
|
||||||
<script type="module" src={s!"{← getRoot}importedBy.js"}></script>
|
<script type="module" src={s!"{← getRoot}importedBy.js"}></script>
|
||||||
|
|
|
@ -53,10 +53,10 @@ partial def textLength : Html → Nat
|
||||||
|
|
||||||
def escapePairs : Array (String × String) :=
|
def escapePairs : Array (String × String) :=
|
||||||
#[
|
#[
|
||||||
("&", "&"),
|
("&", "&"),
|
||||||
("<", "<"),
|
("<", "<"),
|
||||||
(">", ">"),
|
(">", ">"),
|
||||||
("\"", """)
|
("\"", """)
|
||||||
]
|
]
|
||||||
|
|
||||||
def escape (s : String) : String :=
|
def escape (s : String) : String :=
|
||||||
|
|
|
@ -36,17 +36,22 @@ structure JsonModule where
|
||||||
deriving FromJson, ToJson
|
deriving FromJson, ToJson
|
||||||
|
|
||||||
structure JsonHeaderIndex where
|
structure JsonHeaderIndex where
|
||||||
headers : List (String × String) := []
|
declarations : List (String × JsonDeclaration) := []
|
||||||
|
|
||||||
|
structure JsonIndexedDeclarationInfo where
|
||||||
|
kind : String
|
||||||
|
docLink : String
|
||||||
|
deriving FromJson, ToJson
|
||||||
|
|
||||||
structure JsonIndex where
|
structure JsonIndex where
|
||||||
declarations : List (String × JsonDeclarationInfo) := []
|
declarations : List (String × JsonIndexedDeclarationInfo) := []
|
||||||
instances : HashMap String (RBTree String Ord.compare) := .empty
|
instances : HashMap String (RBTree String Ord.compare) := .empty
|
||||||
importedBy : HashMap String (Array String) := .empty
|
importedBy : HashMap String (Array String) := .empty
|
||||||
modules : List (String × String) := []
|
modules : List (String × String) := []
|
||||||
instancesFor : HashMap String (RBTree String Ord.compare) := .empty
|
instancesFor : HashMap String (RBTree String Ord.compare) := .empty
|
||||||
|
|
||||||
instance : ToJson JsonHeaderIndex where
|
instance : ToJson JsonHeaderIndex where
|
||||||
toJson idx := Json.mkObj <| idx.headers.map (fun (k, v) => (k, toJson v))
|
toJson idx := Json.mkObj <| idx.declarations.map (fun (k, v) => (k, toJson v))
|
||||||
|
|
||||||
instance : ToJson JsonIndex where
|
instance : ToJson JsonIndex where
|
||||||
toJson idx := Id.run do
|
toJson idx := Id.run do
|
||||||
|
@ -65,13 +70,16 @@ instance : ToJson JsonIndex where
|
||||||
return finalJson
|
return finalJson
|
||||||
|
|
||||||
def JsonHeaderIndex.addModule (index : JsonHeaderIndex) (module : JsonModule) : JsonHeaderIndex :=
|
def JsonHeaderIndex.addModule (index : JsonHeaderIndex) (module : JsonModule) : JsonHeaderIndex :=
|
||||||
let merge idx decl := { idx with headers := (decl.info.name, decl.header) :: idx.headers }
|
let merge idx decl := { idx with declarations := (decl.info.name, decl) :: idx.declarations }
|
||||||
module.declarations.foldl merge index
|
module.declarations.foldl merge index
|
||||||
|
|
||||||
def JsonIndex.addModule (index : JsonIndex) (module : JsonModule) : BaseHtmlM JsonIndex := do
|
def JsonIndex.addModule (index : JsonIndex) (module : JsonModule) : BaseHtmlM JsonIndex := do
|
||||||
let mut index := index
|
let mut index := index
|
||||||
let newModule := (module.name, ← moduleNameToHtmlLink (String.toName module.name))
|
let newModule := (module.name, ← moduleNameToHtmlLink (String.toName module.name))
|
||||||
let newDecls := module.declarations.map (fun d => (d.info.name, d.info))
|
let newDecls := module.declarations.map (fun d => (d.info.name, {
|
||||||
|
kind := d.info.kind,
|
||||||
|
docLink := d.info.docLink,
|
||||||
|
}))
|
||||||
index := { index with
|
index := { index with
|
||||||
modules := newModule :: index.modules
|
modules := newModule :: index.modules
|
||||||
declarations := newDecls ++ index.declarations
|
declarations := newDecls ++ index.declarations
|
||||||
|
|
|
@ -110,7 +110,7 @@ def process (task : AnalyzeTask) : MetaM (AnalyzerResult × Hierarchy) := do
|
||||||
let env ← getEnv
|
let env ← getEnv
|
||||||
let relevantModules := match task with
|
let relevantModules := match task with
|
||||||
| .loadAll _ => HashSet.fromArray env.header.moduleNames
|
| .loadAll _ => HashSet.fromArray env.header.moduleNames
|
||||||
| .loadAllLimitAnalysis analysis => HashSet.fromArray analysis.toArray
|
| .loadAllLimitAnalysis analysis => HashSet.fromArray analysis
|
||||||
let allModules := env.header.moduleNames
|
let allModules := env.header.moduleNames
|
||||||
|
|
||||||
let mut res ← getAllModuleDocs relevantModules.toArray
|
let mut res ← getAllModuleDocs relevantModules.toArray
|
||||||
|
|
|
@ -12,20 +12,13 @@ namespace DocGen4.Process
|
||||||
|
|
||||||
open Lean Meta Widget
|
open Lean Meta Widget
|
||||||
|
|
||||||
partial def stripArgs (e : Expr) : Expr :=
|
partial def stripArgs (e : Expr) (k : Expr → MetaM α) : MetaM α :=
|
||||||
match e.consumeMData with
|
match e.consumeMData with
|
||||||
| Expr.lam name _ body _ =>
|
| Expr.forallE name type body bi =>
|
||||||
let name := name.eraseMacroScopes
|
let name := name.eraseMacroScopes
|
||||||
stripArgs (Expr.instantiate1 body (mkFVar ⟨name⟩))
|
Meta.withLocalDecl name bi type fun fvar => do
|
||||||
| Expr.forallE name _ body _ =>
|
stripArgs (Expr.instantiate1 body fvar) k
|
||||||
let name := name.eraseMacroScopes
|
| _ => k e
|
||||||
stripArgs (Expr.instantiate1 body (mkFVar ⟨name⟩))
|
|
||||||
| _ => e
|
|
||||||
|
|
||||||
def processEq (eq : Name) : MetaM CodeWithInfos := do
|
|
||||||
let type ← (mkConstWithFreshMVarLevels eq >>= inferType)
|
|
||||||
let final := stripArgs type
|
|
||||||
prettyPrintTerm final
|
|
||||||
|
|
||||||
def valueToEq (v : DefinitionVal) : MetaM Expr := withLCtx {} {} do
|
def valueToEq (v : DefinitionVal) : MetaM Expr := withLCtx {} {} do
|
||||||
withOptions (tactic.hygienic.set . false) do
|
withOptions (tactic.hygienic.set . false) do
|
||||||
|
@ -35,6 +28,10 @@ def valueToEq (v : DefinitionVal) : MetaM Expr := withLCtx {} {} do
|
||||||
let type ← mkForallFVars xs type
|
let type ← mkForallFVars xs type
|
||||||
return type
|
return type
|
||||||
|
|
||||||
|
def processEq (eq : Name) : MetaM CodeWithInfos := do
|
||||||
|
let type ← (mkConstWithFreshMVarLevels eq >>= inferType)
|
||||||
|
stripArgs type prettyPrintTerm
|
||||||
|
|
||||||
def DefinitionInfo.ofDefinitionVal (v : DefinitionVal) : MetaM DefinitionInfo := do
|
def DefinitionInfo.ofDefinitionVal (v : DefinitionVal) : MetaM DefinitionInfo := do
|
||||||
let info ← Info.ofConstantVal v.toConstantVal
|
let info ← Info.ofConstantVal v.toConstantVal
|
||||||
let isUnsafe := v.safety == DefinitionSafety.unsafe
|
let isUnsafe := v.safety == DefinitionSafety.unsafe
|
||||||
|
@ -52,7 +49,7 @@ def DefinitionInfo.ofDefinitionVal (v : DefinitionVal) : MetaM DefinitionInfo :=
|
||||||
isNonComputable
|
isNonComputable
|
||||||
}
|
}
|
||||||
| none =>
|
| none =>
|
||||||
let equations := #[← prettyPrintTerm <| stripArgs (← valueToEq v)]
|
let equations := #[← stripArgs (← valueToEq v) prettyPrintTerm]
|
||||||
return {
|
return {
|
||||||
toInfo := info,
|
toInfo := info,
|
||||||
isUnsafe,
|
isUnsafe,
|
||||||
|
|
|
@ -95,14 +95,16 @@ def baseDirBlackList : HashSet String :=
|
||||||
"color-scheme.js",
|
"color-scheme.js",
|
||||||
"declaration-data.js",
|
"declaration-data.js",
|
||||||
"declarations",
|
"declarations",
|
||||||
|
"expand-nav.js",
|
||||||
"find",
|
"find",
|
||||||
|
"foundational_types.html",
|
||||||
"how-about.js",
|
"how-about.js",
|
||||||
"index.html",
|
"index.html",
|
||||||
"search.html",
|
"jump-src.js",
|
||||||
"foundational_types.html",
|
|
||||||
"mathjax-config.js",
|
"mathjax-config.js",
|
||||||
"navbar.html",
|
"navbar.html",
|
||||||
"nav.js",
|
"nav.js",
|
||||||
|
"search.html",
|
||||||
"search.js",
|
"search.js",
|
||||||
"src",
|
"src",
|
||||||
"style.css"
|
"style.css"
|
||||||
|
|
|
@ -16,19 +16,23 @@ open Lean Meta
|
||||||
def getInstanceTypes (typ : Expr) : MetaM (Array Name) := do
|
def getInstanceTypes (typ : Expr) : MetaM (Array Name) := do
|
||||||
let (_, _, tail) ← forallMetaTelescopeReducing typ
|
let (_, _, tail) ← forallMetaTelescopeReducing typ
|
||||||
let args := tail.getAppArgs
|
let args := tail.getAppArgs
|
||||||
let (_, names) ← args.mapM (Expr.forEach · findName) |>.run .empty
|
let (_, bis, _) ← forallMetaTelescopeReducing (← inferType tail.getAppFn)
|
||||||
|
let (_, names) ← (bis.zip args).mapM findName |>.run .empty
|
||||||
return names
|
return names
|
||||||
where
|
where
|
||||||
findName : Expr → StateRefT (Array Name) MetaM Unit
|
findName : BinderInfo × Expr → StateRefT (Array Name) MetaM Unit
|
||||||
| .const name _ => modify (·.push name)
|
| (.default, .sort .zero) => modify (·.push "_builtin_prop")
|
||||||
| .sort .zero => modify (·.push "_builtin_prop")
|
| (.default, .sort (.succ _)) => modify (·.push "_builtin_typeu")
|
||||||
| .sort (.succ _) => modify (·.push "_builtin_typeu")
|
| (.default, .sort _) => modify (·.push "_builtin_sortu")
|
||||||
| .sort _ => modify (·.push "_builtin_sortu")
|
| (.default, e) =>
|
||||||
|
match e.getAppFn with
|
||||||
|
| .const name .. => modify (·.push name)
|
||||||
|
| _ => return ()
|
||||||
| _ => return ()
|
| _ => return ()
|
||||||
|
|
||||||
def InstanceInfo.ofDefinitionVal (v : DefinitionVal) : MetaM InstanceInfo := do
|
def InstanceInfo.ofDefinitionVal (v : DefinitionVal) : MetaM InstanceInfo := do
|
||||||
let mut info ← DefinitionInfo.ofDefinitionVal v
|
let mut info ← DefinitionInfo.ofDefinitionVal v
|
||||||
let some className ← isClass? v.type | unreachable!
|
let some className ← isClass? v.type | panic! s!"isClass? on {v.name} returned none"
|
||||||
if let some instAttr ← getDefaultInstance v.name className then
|
if let some instAttr ← getDefaultInstance v.name className then
|
||||||
info := { info with attrs := info.attrs.push instAttr }
|
info := { info with attrs := info.attrs.push instAttr }
|
||||||
let typeNames ← getInstanceTypes v.type
|
let typeNames ← getInstanceTypes v.type
|
||||||
|
|
|
@ -15,34 +15,37 @@ def NameInfo.ofTypedName (n : Name) (t : Expr) : MetaM NameInfo := do
|
||||||
let env ← getEnv
|
let env ← getEnv
|
||||||
return { name := n, type := ← prettyPrintTerm t, doc := ← findDocString? env n}
|
return { name := n, type := ← prettyPrintTerm t, doc := ← findDocString? env n}
|
||||||
|
|
||||||
partial def typeToArgsType (e : Expr) : (Array ((Option Name) × Expr × BinderInfo) × Expr) :=
|
partial def argTypeTelescope {α : Type} (e : Expr) (k : Array ((Option Name) × Expr × BinderInfo) → Expr → MetaM α) : MetaM α :=
|
||||||
let helper := fun name type body data =>
|
go e #[]
|
||||||
|
where
|
||||||
|
go (e : Expr) (args : Array ((Option Name) × Expr × BinderInfo)) : MetaM α := do
|
||||||
|
let helper := fun name type body bi =>
|
||||||
-- Once we hit a name with a macro scope we stop traversing the expression
|
-- Once we hit a name with a macro scope we stop traversing the expression
|
||||||
-- and print what is left after the : instead. The only exception
|
-- and print what is left after the : instead. The only exception
|
||||||
-- to this is instances since these almost never have a name
|
-- to this is instances since these almost never have a name
|
||||||
-- but should still be printed as arguments instead of after the :.
|
-- but should still be printed as arguments instead of after the :.
|
||||||
if data.isInstImplicit && name.hasMacroScopes then
|
if bi.isInstImplicit && name.hasMacroScopes then
|
||||||
let arg := (none, type, data)
|
let arg := (none, type, bi)
|
||||||
let (args, final) := typeToArgsType (Expr.instantiate1 body (mkFVar ⟨name⟩))
|
Meta.withLocalDecl name bi type fun fvar => do
|
||||||
(#[arg] ++ args, final)
|
go (Expr.instantiate1 body fvar) (args.push arg)
|
||||||
else if name.hasMacroScopes then
|
else if name.hasMacroScopes then
|
||||||
(#[], e)
|
k args e
|
||||||
else
|
else
|
||||||
let arg := (some name, type, data)
|
let arg := (some name, type, bi)
|
||||||
let (args, final) := typeToArgsType (Expr.instantiate1 body (mkFVar ⟨name⟩))
|
Meta.withLocalDecl name bi type fun fvar => do
|
||||||
(#[arg] ++ args, final)
|
go (Expr.instantiate1 body fvar) (args.push arg)
|
||||||
match e.consumeMData with
|
match e.consumeMData with
|
||||||
| Expr.lam name type body binderInfo => helper name type body binderInfo
|
|
||||||
| Expr.forallE name type body binderInfo => helper name type body binderInfo
|
| Expr.forallE name type body binderInfo => helper name type body binderInfo
|
||||||
| _ => (#[], e)
|
| _ => k args e
|
||||||
|
|
||||||
def Info.ofConstantVal (v : ConstantVal) : MetaM Info := do
|
def Info.ofConstantVal (v : ConstantVal) : MetaM Info := do
|
||||||
let (args, type) := typeToArgsType v.type
|
argTypeTelescope v.type fun args type => do
|
||||||
let args ← args.mapM (fun (n, e, b) => do return Arg.mk n (← prettyPrintTerm e) b)
|
let args ← args.mapM (fun (n, e, b) => do return Arg.mk n (← prettyPrintTerm e) b)
|
||||||
let nameInfo ← NameInfo.ofTypedName v.name type
|
let nameInfo ← NameInfo.ofTypedName v.name type
|
||||||
match ← findDeclarationRanges? v.name with
|
match ← findDeclarationRanges? v.name with
|
||||||
-- TODO: Maybe selection range is more relevant? Figure this out in the future
|
-- TODO: Maybe selection range is more relevant? Figure this out in the future
|
||||||
| some range => return {
|
| some range =>
|
||||||
|
return {
|
||||||
toNameInfo := nameInfo,
|
toNameInfo := nameInfo,
|
||||||
args,
|
args,
|
||||||
declarationRange := range.range,
|
declarationRange := range.range,
|
||||||
|
|
|
@ -12,24 +12,24 @@ namespace DocGen4.Process
|
||||||
|
|
||||||
open Lean Meta
|
open Lean Meta
|
||||||
|
|
||||||
-- TODO: replace with Leos variant from Zulip
|
/--
|
||||||
def dropArgs (type : Expr) (n : Nat) : (Expr × List (Name × Expr)) :=
|
Execute `k` with an array containing pairs `(fieldName, fieldType)`.
|
||||||
match type, n with
|
`k` is executed in an updated local context which contains local declarations for the `structName` parameters.
|
||||||
| e, 0 => (e, [])
|
-/
|
||||||
| Expr.forallE name type body _, x + 1 =>
|
def withFields (info : InductiveVal) (k : Array (Name × Expr) → MetaM α) (includeSubobjectFields : Bool := false) : MetaM α := do
|
||||||
let body := body.instantiate1 <| mkFVar ⟨name⟩
|
let structName := info.name
|
||||||
let next := dropArgs body x
|
let us := info.levelParams.map mkLevelParam
|
||||||
{ next with snd := (name, type) :: next.snd}
|
forallTelescopeReducing info.type fun params _ =>
|
||||||
| _, _ + 1 => panic! s!"No forallE left"
|
withLocalDeclD `s (mkAppN (mkConst structName us) params) fun s => do
|
||||||
|
let mut info := #[]
|
||||||
|
for fieldName in getStructureFieldsFlattened (← getEnv) structName includeSubobjectFields do
|
||||||
|
let proj ← mkProjection s fieldName
|
||||||
|
info := info.push (fieldName, (← inferType proj))
|
||||||
|
k info
|
||||||
|
|
||||||
def getFieldTypes (struct : Name) (ctor : ConstructorVal) (parents : Nat) : MetaM (Array NameInfo) := do
|
def getFieldTypes (v : InductiveVal) : MetaM (Array NameInfo) := do
|
||||||
let type := ctor.type
|
withFields v fun fields =>
|
||||||
let (fieldFunction, _) := dropArgs type (ctor.numParams + parents)
|
fields.foldlM (init := #[]) (fun acc (name, type) => do return acc.push (← NameInfo.ofTypedName (v.name.append name) type))
|
||||||
let (_, fields) := dropArgs fieldFunction (ctor.numFields - parents)
|
|
||||||
let mut fieldInfos := #[]
|
|
||||||
for (name, type) in fields do
|
|
||||||
fieldInfos := fieldInfos.push <| ← NameInfo.ofTypedName (struct.append name) type
|
|
||||||
return fieldInfos
|
|
||||||
|
|
||||||
def StructureInfo.ofInductiveVal (v : InductiveVal) : MetaM StructureInfo := do
|
def StructureInfo.ofInductiveVal (v : InductiveVal) : MetaM StructureInfo := do
|
||||||
let info ← Info.ofConstantVal v.toConstantVal
|
let info ← Info.ofConstantVal v.toConstantVal
|
||||||
|
@ -42,7 +42,7 @@ def StructureInfo.ofInductiveVal (v : InductiveVal) : MetaM StructureInfo := do
|
||||||
if i.fieldNames.size - parents.size > 0 then
|
if i.fieldNames.size - parents.size > 0 then
|
||||||
return {
|
return {
|
||||||
toInfo := info,
|
toInfo := info,
|
||||||
fieldInfo := ← getFieldTypes v.name ctorVal parents.size,
|
fieldInfo := ← getFieldTypes v,
|
||||||
parents,
|
parents,
|
||||||
ctor
|
ctor
|
||||||
}
|
}
|
||||||
|
|
16
Main.lean
16
Main.lean
|
@ -11,15 +11,12 @@ def getTopLevelModules (p : Parsed) : IO (List String) := do
|
||||||
return topLevelModules
|
return topLevelModules
|
||||||
|
|
||||||
def runSingleCmd (p : Parsed) : IO UInt32 := do
|
def runSingleCmd (p : Parsed) : IO UInt32 := do
|
||||||
let relevantModules := [p.positionalArg! "module" |>.as! String |> String.toName]
|
let relevantModules := #[p.positionalArg! "module" |>.as! String |> String.toName]
|
||||||
let res ← lakeSetup
|
let gitUrl := p.positionalArg! "gitUrl" |>.as! String
|
||||||
match res with
|
|
||||||
| Except.ok ws =>
|
|
||||||
let (doc, hierarchy) ← load <| .loadAllLimitAnalysis relevantModules
|
let (doc, hierarchy) ← load <| .loadAllLimitAnalysis relevantModules
|
||||||
let baseConfig ← getSimpleBaseContext hierarchy
|
let baseConfig ← getSimpleBaseContext hierarchy
|
||||||
htmlOutputResults baseConfig doc ws (p.hasFlag "ink")
|
htmlOutputResults baseConfig doc (some gitUrl) (p.hasFlag "ink")
|
||||||
return 0
|
return 0
|
||||||
| Except.error rc => pure rc
|
|
||||||
|
|
||||||
def runIndexCmd (_p : Parsed) : IO UInt32 := do
|
def runIndexCmd (_p : Parsed) : IO UInt32 := do
|
||||||
let hierarchy ← Hierarchy.fromDirectory Output.basePath
|
let hierarchy ← Hierarchy.fromDirectory Output.basePath
|
||||||
|
@ -28,14 +25,10 @@ def runIndexCmd (_p : Parsed) : IO UInt32 := do
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def runGenCoreCmd (_p : Parsed) : IO UInt32 := do
|
def runGenCoreCmd (_p : Parsed) : IO UInt32 := do
|
||||||
let res ← lakeSetup
|
|
||||||
match res with
|
|
||||||
| Except.ok ws =>
|
|
||||||
let (doc, hierarchy) ← loadCore
|
let (doc, hierarchy) ← loadCore
|
||||||
let baseConfig ← getSimpleBaseContext hierarchy
|
let baseConfig ← getSimpleBaseContext hierarchy
|
||||||
htmlOutputResults baseConfig doc ws (ink := False)
|
htmlOutputResults baseConfig doc none (ink := False)
|
||||||
return 0
|
return 0
|
||||||
| Except.error rc => pure rc
|
|
||||||
|
|
||||||
def runDocGenCmd (_p : Parsed) : IO UInt32 := do
|
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 "You most likely want to use me via Lake now, check my README on Github on how to:"
|
||||||
|
@ -51,6 +44,7 @@ def singleCmd := `[Cli|
|
||||||
|
|
||||||
ARGS:
|
ARGS:
|
||||||
module : String; "The module to generate the HTML for. Does not have to be part of topLevelModules."
|
module : String; "The module to generate the HTML for. Does not have to be part of topLevelModules."
|
||||||
|
gitUrl : String; "The gitUrl as computed by the Lake facet"
|
||||||
]
|
]
|
||||||
|
|
||||||
def indexCmd := `[Cli|
|
def indexCmd := `[Cli|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{"version": 5,
|
{"version": 6,
|
||||||
"packagesDir": "lake-packages",
|
"packagesDir": "lake-packages",
|
||||||
"packages":
|
"packages":
|
||||||
[{"git":
|
[{"git":
|
||||||
|
@ -20,15 +20,15 @@
|
||||||
{"git":
|
{"git":
|
||||||
{"url": "https://github.com/fgdorais/lean4-unicode-basic",
|
{"url": "https://github.com/fgdorais/lean4-unicode-basic",
|
||||||
"subDir?": null,
|
"subDir?": null,
|
||||||
"rev": "2491e781ae478b6e6f1d86a7157f1c58fc50f895",
|
"rev": "4ecf4f1f98d14d03a9e84fa1c082630fa69df88b",
|
||||||
"opts": {},
|
"opts": {},
|
||||||
"name": "«lean4-unicode-basic»",
|
"name": "UnicodeBasic",
|
||||||
"inputRev?": "main",
|
"inputRev?": "main",
|
||||||
"inherited": false}},
|
"inherited": false}},
|
||||||
{"git":
|
{"git":
|
||||||
{"url": "https://github.com/mhuisi/lean4-cli",
|
{"url": "https://github.com/mhuisi/lean4-cli",
|
||||||
"subDir?": null,
|
"subDir?": null,
|
||||||
"rev": "21dac2e9cc7e3cf7da5800814787b833e680b2fd",
|
"rev": "39229f3630d734af7d9cfb5937ddc6b41d3aa6aa",
|
||||||
"opts": {},
|
"opts": {},
|
||||||
"name": "Cli",
|
"name": "Cli",
|
||||||
"inputRev?": "nightly",
|
"inputRev?": "nightly",
|
||||||
|
@ -72,4 +72,5 @@
|
||||||
"opts": {},
|
"opts": {},
|
||||||
"name": "std",
|
"name": "std",
|
||||||
"inputRev?": "main",
|
"inputRev?": "main",
|
||||||
"inherited": false}}]}
|
"inherited": false}}],
|
||||||
|
"name": "«bookshelf»"}
|
||||||
|
|
|
@ -14,7 +14,7 @@ require Cli from git
|
||||||
require CMark from git
|
require CMark from git
|
||||||
"https://github.com/xubaiw/CMark.lean" @
|
"https://github.com/xubaiw/CMark.lean" @
|
||||||
"main"
|
"main"
|
||||||
require «lean4-unicode-basic» from git
|
require UnicodeBasic from git
|
||||||
"https://github.com/fgdorais/lean4-unicode-basic" @
|
"https://github.com/fgdorais/lean4-unicode-basic" @
|
||||||
"main"
|
"main"
|
||||||
require leanInk from git
|
require leanInk from git
|
||||||
|
@ -38,22 +38,87 @@ lean_exe «doc-gen4» {
|
||||||
supportInterpreter := true
|
supportInterpreter := true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/--
|
||||||
|
Turns a Github git remote URL into an HTTPS Github URL.
|
||||||
|
Three link types from git supported:
|
||||||
|
- https://github.com/org/repo
|
||||||
|
- https://github.com/org/repo.git
|
||||||
|
- git@github.com:org/repo.git
|
||||||
|
TODO: This function is quite brittle and very Github specific, we can
|
||||||
|
probably do better.
|
||||||
|
-/
|
||||||
|
def getGithubBaseUrl (gitUrl : String) : String := Id.run do
|
||||||
|
let mut url := gitUrl
|
||||||
|
if url.startsWith "git@" then
|
||||||
|
url := url.drop 15
|
||||||
|
url := url.dropRight 4
|
||||||
|
return s!"https://github.com/{url}"
|
||||||
|
else if url.endsWith ".git" then
|
||||||
|
return url.dropRight 4
|
||||||
|
else
|
||||||
|
return url
|
||||||
|
|
||||||
|
/--
|
||||||
|
Obtain the Github URL of a project by parsing the origin remote.
|
||||||
|
-/
|
||||||
|
def getProjectGithubUrl (directory : System.FilePath := "." ) : IO String := do
|
||||||
|
let out ← IO.Process.output {
|
||||||
|
cmd := "git",
|
||||||
|
args := #["remote", "get-url", "origin"],
|
||||||
|
cwd := directory
|
||||||
|
}
|
||||||
|
if out.exitCode != 0 then
|
||||||
|
throw <| IO.userError <| s!"git exited with code {out.exitCode} while looking for the git remote in {directory}"
|
||||||
|
return out.stdout.trimRight
|
||||||
|
|
||||||
|
/--
|
||||||
|
Obtain the git commit hash of the project that is currently getting analyzed.
|
||||||
|
-/
|
||||||
|
def getProjectCommit (directory : System.FilePath := "." ) : IO String := do
|
||||||
|
let out ← IO.Process.output {
|
||||||
|
cmd := "git",
|
||||||
|
args := #["rev-parse", "HEAD"]
|
||||||
|
cwd := directory
|
||||||
|
}
|
||||||
|
if out.exitCode != 0 then
|
||||||
|
throw <| IO.userError <| s!"git exited with code {out.exitCode} while looking for the current commit in {directory}"
|
||||||
|
return out.stdout.trimRight
|
||||||
|
|
||||||
|
def getGitUrl (pkg : Package) (lib : LeanLibConfig) (mod : Module) : IO String := do
|
||||||
|
let baseUrl := getGithubBaseUrl (← getProjectGithubUrl pkg.dir)
|
||||||
|
let commit ← getProjectCommit pkg.dir
|
||||||
|
|
||||||
|
let parts := mod.name.components.map toString
|
||||||
|
let path := String.intercalate "/" parts
|
||||||
|
let libPath := pkg.config.srcDir / lib.srcDir
|
||||||
|
let basePath := String.intercalate "/" (libPath.components.filter (· != "."))
|
||||||
|
let url := s!"{baseUrl}/blob/{commit}/{basePath}/{path}.lean"
|
||||||
|
return url
|
||||||
|
|
||||||
module_facet docs (mod) : FilePath := do
|
module_facet docs (mod) : FilePath := do
|
||||||
let some docGen4 ← findLeanExe? `«doc-gen4»
|
let some docGen4 ← findLeanExe? `«doc-gen4»
|
||||||
| error "no doc-gen4 executable configuration found in workspace"
|
| error "no doc-gen4 executable configuration found in workspace"
|
||||||
let exeJob ← docGen4.exe.fetch
|
let exeJob ← docGen4.exe.fetch
|
||||||
let modJob ← mod.leanBin.fetch
|
let modJob ← mod.leanArts.fetch
|
||||||
let buildDir := (← getWorkspace).root.buildDir
|
let ws ← getWorkspace
|
||||||
|
let pkg ← ws.packages.find? (·.isLocalModule mod.name)
|
||||||
|
let libConfig ← pkg.leanLibConfigs.toArray.find? (·.isLocalModule mod.name)
|
||||||
|
-- Build all documentation imported modules
|
||||||
|
let imports ← mod.imports.fetch
|
||||||
|
let depDocJobs ← BuildJob.mixArray <| ← imports.mapM fun mod => fetch <| mod.facet `docs
|
||||||
|
let gitUrl ← getGitUrl pkg libConfig mod
|
||||||
|
let buildDir := ws.root.buildDir
|
||||||
let docFile := mod.filePath (buildDir / "doc") "html"
|
let docFile := mod.filePath (buildDir / "doc") "html"
|
||||||
|
depDocJobs.bindAsync fun _ depDocTrace => do
|
||||||
exeJob.bindAsync fun exeFile exeTrace => do
|
exeJob.bindAsync fun exeFile exeTrace => do
|
||||||
modJob.bindSync fun _ modTrace => do
|
modJob.bindSync fun _ modTrace => do
|
||||||
let depTrace := exeTrace.mix modTrace
|
let depTrace := mixTraceArray #[exeTrace, modTrace, depDocTrace]
|
||||||
let trace ← buildFileUnlessUpToDate docFile depTrace do
|
let trace ← buildFileUnlessUpToDate docFile depTrace do
|
||||||
logInfo s!"Documenting module: {mod.name}"
|
logStep s!"Documenting module: {mod.name}"
|
||||||
proc {
|
proc {
|
||||||
cmd := exeFile.toString
|
cmd := exeFile.toString
|
||||||
args := #["single", mod.name.toString]
|
args := #["single", mod.name.toString, gitUrl]
|
||||||
env := #[("LEAN_PATH", (← getAugmentedLeanPath).toString)]
|
env := ← getAugmentedEnv
|
||||||
}
|
}
|
||||||
return (docFile, trace)
|
return (docFile, trace)
|
||||||
|
|
||||||
|
@ -66,11 +131,11 @@ target coreDocs : FilePath := do
|
||||||
let dataFile := basePath / "declarations" / "declaration-data-Lean.bmp"
|
let dataFile := basePath / "declarations" / "declaration-data-Lean.bmp"
|
||||||
exeJob.bindSync fun exeFile exeTrace => do
|
exeJob.bindSync fun exeFile exeTrace => do
|
||||||
let trace ← buildFileUnlessUpToDate dataFile exeTrace do
|
let trace ← buildFileUnlessUpToDate dataFile exeTrace do
|
||||||
logInfo "Documenting Lean core: Init and Lean"
|
logStep "Documenting Lean core: Init and Lean"
|
||||||
proc {
|
proc {
|
||||||
cmd := exeFile.toString
|
cmd := exeFile.toString
|
||||||
args := #["genCore"]
|
args := #["genCore"]
|
||||||
env := #[("LEAN_PATH", (← getAugmentedLeanPath).toString)]
|
env := ← getAugmentedEnv
|
||||||
}
|
}
|
||||||
return (dataFile, trace)
|
return (dataFile, trace)
|
||||||
|
|
||||||
|
@ -90,6 +155,8 @@ library_facet docs (lib) : FilePath := do
|
||||||
basePath / "declaration-data.js",
|
basePath / "declaration-data.js",
|
||||||
basePath / "color-scheme.js",
|
basePath / "color-scheme.js",
|
||||||
basePath / "nav.js",
|
basePath / "nav.js",
|
||||||
|
basePath / "jump-src.js",
|
||||||
|
basePath / "expand-nav.js",
|
||||||
basePath / "how-about.js",
|
basePath / "how-about.js",
|
||||||
basePath / "search.js",
|
basePath / "search.js",
|
||||||
basePath / "mathjax-config.js",
|
basePath / "mathjax-config.js",
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
leanprover/lean4:4.0.0
|
leanprover/lean4:v4.2.0-rc4
|
|
@ -54,7 +54,12 @@ export class DeclarationDataCenter {
|
||||||
);
|
);
|
||||||
|
|
||||||
// try to use cache first
|
// try to use cache first
|
||||||
const data = await fetchCachedDeclarationData().catch(_e => null);
|
// TODO: This API is not thought 100% through. If we have a DB cached
|
||||||
|
// already it will not even ask the remote for a new one so we end up
|
||||||
|
// with outdated declaration-data. This has to have some form of cache
|
||||||
|
// invalidation: https://github.com/leanprover/doc-gen4/issues/133
|
||||||
|
// const data = await fetchCachedDeclarationData().catch(_e => null);
|
||||||
|
const data = null;
|
||||||
if (data) {
|
if (data) {
|
||||||
// if data is defined, use the cached one.
|
// if data is defined, use the cached one.
|
||||||
return new DeclarationDataCenter(data);
|
return new DeclarationDataCenter(data);
|
||||||
|
@ -135,7 +140,7 @@ export class DeclarationDataCenter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSeparater(char) {
|
function isSeparator(char) {
|
||||||
return char === "." || char === "_";
|
return char === "." || char === "_";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,11 +153,11 @@ function matchCaseSensitive(declName, lowerDeclName, pattern) {
|
||||||
lastMatch = 0;
|
lastMatch = 0;
|
||||||
while (i < declName.length && j < pattern.length) {
|
while (i < declName.length && j < pattern.length) {
|
||||||
if (pattern[j] === declName[i] || pattern[j] === lowerDeclName[i]) {
|
if (pattern[j] === declName[i] || pattern[j] === lowerDeclName[i]) {
|
||||||
err += (isSeparater(pattern[j]) ? 0.125 : 1) * (i - lastMatch);
|
err += (isSeparator(pattern[j]) ? 0.125 : 1) * (i - lastMatch);
|
||||||
if (pattern[j] !== declName[i]) err += 0.5;
|
if (pattern[j] !== declName[i]) err += 0.5;
|
||||||
lastMatch = i + 1;
|
lastMatch = i + 1;
|
||||||
j++;
|
j++;
|
||||||
} else if (isSeparater(declName[i])) {
|
} else if (isSeparator(declName[i])) {
|
||||||
err += 0.125 * (i + 1 - lastMatch);
|
err += 0.125 * (i + 1 - lastMatch);
|
||||||
lastMatch = i + 1;
|
lastMatch = i + 1;
|
||||||
}
|
}
|
||||||
|
@ -168,12 +173,9 @@ function getMatches(declarations, pattern, allowedKinds = undefined, maxResults
|
||||||
const lowerPats = pattern.toLowerCase().split(/\s/g);
|
const lowerPats = pattern.toLowerCase().split(/\s/g);
|
||||||
const patNoSpaces = pattern.replace(/\s/g, "");
|
const patNoSpaces = pattern.replace(/\s/g, "");
|
||||||
const results = [];
|
const results = [];
|
||||||
for (const [_, {
|
for (const [name, {
|
||||||
name,
|
|
||||||
kind,
|
kind,
|
||||||
doc,
|
|
||||||
docLink,
|
docLink,
|
||||||
sourceLink,
|
|
||||||
}] of Object.entries(declarations)) {
|
}] of Object.entries(declarations)) {
|
||||||
// Apply "kind" filter
|
// Apply "kind" filter
|
||||||
if (allowedKinds !== undefined) {
|
if (allowedKinds !== undefined) {
|
||||||
|
@ -182,26 +184,14 @@ function getMatches(declarations, pattern, allowedKinds = undefined, maxResults
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const lowerName = name.toLowerCase();
|
const lowerName = name.toLowerCase();
|
||||||
const lowerDoc = doc.toLowerCase();
|
|
||||||
let err = matchCaseSensitive(name, lowerName, patNoSpaces);
|
let err = matchCaseSensitive(name, lowerName, patNoSpaces);
|
||||||
// match all words as substrings of docstring
|
|
||||||
if (
|
|
||||||
err >= 3 &&
|
|
||||||
pattern.length > 3 &&
|
|
||||||
lowerPats.every((l) => lowerDoc.indexOf(l) != -1)
|
|
||||||
) {
|
|
||||||
err = 3;
|
|
||||||
}
|
|
||||||
if (err !== undefined) {
|
if (err !== undefined) {
|
||||||
results.push({
|
results.push({
|
||||||
name,
|
name,
|
||||||
kind,
|
kind,
|
||||||
doc,
|
|
||||||
err,
|
err,
|
||||||
lowerName,
|
lowerName,
|
||||||
lowerDoc,
|
|
||||||
docLink,
|
docLink,
|
||||||
sourceLink,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,12 +263,7 @@ async function fetchCachedDeclarationData() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let transactionRequest = store.get(CACHE_DB_KEY);
|
let transactionRequest = store.get(CACHE_DB_KEY);
|
||||||
transactionRequest.onsuccess = function (event) {
|
transactionRequest.onsuccess = function (event) {
|
||||||
// TODO: This API is not thought 100% through. If we have a DB cached
|
resolve(event.target.result);
|
||||||
// already it will not even ask the remote for a new one so we end up
|
|
||||||
// with outdated declaration-data. This has to have some form of cache
|
|
||||||
// invalidation: https://github.com/leanprover/doc-gen4/issues/133
|
|
||||||
// resolve(event.target.result);
|
|
||||||
resolve(undefined);
|
|
||||||
};
|
};
|
||||||
transactionRequest.onerror = function (event) {
|
transactionRequest.onerror = function (event) {
|
||||||
reject(new Error(`fail to store declaration data`));
|
reject(new Error(`fail to store declaration data`));
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
document.querySelector('.navframe').addEventListener('load', function() {
|
||||||
|
// Get the current page URL without the suffix after #
|
||||||
|
var currentPageURL = window.location.href.split('#')[0];
|
||||||
|
|
||||||
|
// Get all anchor tags
|
||||||
|
var as = document.querySelector('.navframe').contentWindow.document.body.querySelectorAll('a');
|
||||||
|
for (const a of as) {
|
||||||
|
if (a.href) {
|
||||||
|
var href = a.href.split('#')[0];
|
||||||
|
// find the one with the current url
|
||||||
|
if (href === currentPageURL) {
|
||||||
|
a.style.fontStyle = 'italic';
|
||||||
|
// open all detail tags above the current
|
||||||
|
var el = a.parentNode.closest('details');
|
||||||
|
while (el) {
|
||||||
|
el.open = true;
|
||||||
|
el = el.parentNode.closest('details');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// seeing as we found the link we were looking for, stop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -49,7 +49,7 @@ const queryParams = new Map(
|
||||||
const fragmentPaths = fragment?.split(LEAN_FRIENDLY_SLASH_SEPARATOR) ?? [];
|
const fragmentPaths = fragment?.split(LEAN_FRIENDLY_SLASH_SEPARATOR) ?? [];
|
||||||
|
|
||||||
const encodedPattern = queryParams.get("pattern") ?? fragmentPaths[1]; // if first fail then second, may be undefined
|
const encodedPattern = queryParams.get("pattern") ?? fragmentPaths[1]; // if first fail then second, may be undefined
|
||||||
const pattern = decodeURIComponent(encodedPattern);
|
const pattern = encodedPattern && decodeURIComponent(encodedPattern);
|
||||||
const strict = (queryParams.get("strict") ?? "true") === "true"; // default to true
|
const strict = (queryParams.get("strict") ?? "true") === "true"; // default to true
|
||||||
const view = fragmentPaths[0];
|
const view = fragmentPaths[0];
|
||||||
|
|
||||||
|
@ -81,7 +81,8 @@ async function findAndRedirect(pattern, strict, view) {
|
||||||
} else if (view == "doc") {
|
} else if (view == "doc") {
|
||||||
window.location.replace(result.docLink);
|
window.location.replace(result.docLink);
|
||||||
} else if (view == "src") {
|
} else if (view == "src") {
|
||||||
window.location.replace(result.sourceLink);
|
const [module, decl] = result.docLink.split("#", 2);
|
||||||
|
window.location.replace(`${module}?jump=src#${decl}`);
|
||||||
} else {
|
} else {
|
||||||
// fallback to doc page
|
// fallback to doc page
|
||||||
window.location.replace(result.docLink);
|
window.location.replace(result.docLink);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { DeclarationDataCenter } from "./declaration-data.js";
|
||||||
fillImportedBy();
|
fillImportedBy();
|
||||||
|
|
||||||
async function fillImportedBy() {
|
async function fillImportedBy() {
|
||||||
if (!MODULE_NAME) {
|
if (typeof(MODULE_NAME) == "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dataCenter = await DeclarationDataCenter.init();
|
const dataCenter = await DeclarationDataCenter.init();
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const hash = document.location.hash.substring(1);
|
||||||
|
const tgt = new URLSearchParams(document.location.search).get("jump");
|
||||||
|
if (tgt === "src" && hash) {
|
||||||
|
const target = document.getElementById(hash)
|
||||||
|
?.querySelector(".gh_link a")
|
||||||
|
?.getAttribute("href");
|
||||||
|
if (target) window.location.replace(target);
|
||||||
|
}
|
||||||
|
})
|
|
@ -112,7 +112,7 @@ function handleSearch(dataCenter, err, ev, sr, maxResults, autocomplete) {
|
||||||
|
|
||||||
// update autocomplete results
|
// update autocomplete results
|
||||||
removeAllChildren(sr);
|
removeAllChildren(sr);
|
||||||
for (const { name, kind, doc, docLink } of result) {
|
for (const { name, kind, docLink } of result) {
|
||||||
const row = sr.appendChild(document.createElement("div"));
|
const row = sr.appendChild(document.createElement("div"));
|
||||||
row.classList.add("search_result")
|
row.classList.add("search_result")
|
||||||
const linkdiv = row.appendChild(document.createElement("div"))
|
const linkdiv = row.appendChild(document.createElement("div"))
|
||||||
|
@ -121,11 +121,6 @@ function handleSearch(dataCenter, err, ev, sr, maxResults, autocomplete) {
|
||||||
link.innerText = name;
|
link.innerText = name;
|
||||||
link.title = name;
|
link.title = name;
|
||||||
link.href = SITE_ROOT + docLink;
|
link.href = SITE_ROOT + docLink;
|
||||||
if (!autocomplete) {
|
|
||||||
const doctext = row.appendChild(document.createElement("div"));
|
|
||||||
doctext.innerText = doc
|
|
||||||
doctext.classList.add("result_doc")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// handle error
|
// handle error
|
||||||
|
@ -151,7 +146,7 @@ const debounce = (callback, wait) => {
|
||||||
DeclarationDataCenter.init()
|
DeclarationDataCenter.init()
|
||||||
.then((dataCenter) => {
|
.then((dataCenter) => {
|
||||||
// Search autocompletion.
|
// Search autocompletion.
|
||||||
SEARCH_INPUT.addEventListener("input", debounce(ev => handleSearch(dataCenter, null, ev, ac_results, AC_MAX_RESULTS, true), 500));
|
SEARCH_INPUT.addEventListener("input", debounce(ev => handleSearch(dataCenter, null, ev, ac_results, AC_MAX_RESULTS, true), 300));
|
||||||
if(SEARCH_PAGE_INPUT) {
|
if(SEARCH_PAGE_INPUT) {
|
||||||
SEARCH_PAGE_INPUT.addEventListener("input", ev => handleSearch(dataCenter, null, ev, SEARCH_RESULTS, SEARCH_PAGE_MAX_RESULTS, false))
|
SEARCH_PAGE_INPUT.addEventListener("input", ev => handleSearch(dataCenter, null, ev, SEARCH_RESULTS, SEARCH_PAGE_MAX_RESULTS, false))
|
||||||
document.querySelectorAll(".kind_checkbox").forEach((checkbox) =>
|
document.querySelectorAll(".kind_checkbox").forEach((checkbox) =>
|
||||||
|
@ -162,7 +157,7 @@ DeclarationDataCenter.init()
|
||||||
SEARCH_INPUT.dispatchEvent(new Event("input"))
|
SEARCH_INPUT.dispatchEvent(new Event("input"))
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
SEARCH_INPUT.addEventListener("input", debounce(ev => handleSearch(null, e, ev, ac_results, AC_MAX_RESULTS, true), 500));
|
SEARCH_INPUT.addEventListener("input", debounce(ev => handleSearch(null, e, ev, ac_results, AC_MAX_RESULTS, true), 300));
|
||||||
if(SEARCH_PAGE_INPUT) {
|
if(SEARCH_PAGE_INPUT) {
|
||||||
SEARCH_PAGE_INPUT.addEventListener("input", ev => handleSearch(null, e, ev, SEARCH_RESULTS, SEARCH_PAGE_MAX_RESULTS, false));
|
SEARCH_PAGE_INPUT.addEventListener("input", ev => handleSearch(null, e, ev, SEARCH_RESULTS, SEARCH_PAGE_MAX_RESULTS, false));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue