Skip to content

Commit 107a414

Browse files
committed
detailed declaration pages
1 parent 9594cb5 commit 107a414

3 files changed

Lines changed: 130 additions & 43 deletions

File tree

‎LeanExposition/Collect.lean‎

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ structure DeclInfo where
123123
deps : Array Name
124124
typeDeps : Array Name := #[]
125125
usedBy : Array Name := #[]
126+
transDeps : Array Name := #[]
126127
docstringBlock? : Option (Block Manual) := none
127128
deriving Repr
128129

@@ -338,9 +339,10 @@ def codeListParagraph (label : String) (items : Array String) : Option (Block Ma
338339
joinInlines entries #[.text " · "]
339340

340341
/-- Helper for mkLinkParagraph. -/
341-
def mkLinkParagraph (sourceUrl? issueUrl? : Option String) : Option (Block Manual) :=
342+
def mkLinkParagraph (sourceUrl? issueUrl? detailsUrl? : Option String) : Option (Block Manual) :=
342343
let items :=
343-
([sourceUrl?.map fun url => .link #[.text "Source"] url,
344+
([detailsUrl?.map fun url => .link #[.text "Details"] url,
345+
sourceUrl?.map fun url => .link #[.text "Source"] url,
344346
issueUrl?.map fun url => .link #[.text "Open Issue"] url].filterMap id)
345347
if items.isEmpty then
346348
none
@@ -921,6 +923,16 @@ def declHrefMap (decls : Array DeclInfo) : Std.HashMap Name String :=
921923
(fun acc decl => acc.insert decl.name (pathForPart decl.groupKey decl.modulePath decl.name))
922924
{}
923925

926+
/-- Computes path ForDeclPage. -/
927+
def pathForDeclPage (groupKey modulePath : String) (declName : Name) : String :=
928+
s!"{groupHrefOf groupKey}{moduleHrefOf modulePath}decl-{anchorIdOf declName}/"
929+
930+
/-- Maps each declaration name to its dedicated detail page. -/
931+
def declPageHrefMap (decls : Array DeclInfo) : Std.HashMap Name String :=
932+
decls.foldl
933+
(fun acc decl => acc.insert decl.name (pathForDeclPage decl.groupKey decl.modulePath decl.name))
934+
{}
935+
924936
/-- Helper for runCoreIO. -/
925937
def runCoreIO {α : Type} (env : Environment) (x : CoreM α) : IO α := do
926938
x.toIO'
@@ -1050,6 +1062,28 @@ def attachReverseDeps (decls : Array DeclInfo) : Array DeclInfo :=
10501062
{}
10511063
decls.map fun decl => { decl with usedBy := (rev.getD decl.name #[]).qsort Name.lt }
10521064

1065+
/-- Computes the set of declarations reachable from `start` via `depsMap`. -/
1066+
partial def transitiveClosure (depsMap : Std.HashMap Name (Array Name)) (start : Array Name) :
1067+
Std.HashSet Name :=
1068+
go {} start.toList
1069+
where
1070+
go (visited : Std.HashSet Name) : List Name → Std.HashSet Name
1071+
| [] => visited
1072+
| n :: rest =>
1073+
if visited.contains n then
1074+
go visited rest
1075+
else
1076+
go (visited.insert n) ((depsMap.getD n #[]).toList ++ rest)
1077+
1078+
/-- Adds the transitive closure of `deps` (all declarations reachable, recursively) to each
1079+
declaration as `transDeps`. -/
1080+
def attachTransitiveDeps (decls : Array DeclInfo) : Array DeclInfo :=
1081+
let depsMap : Std.HashMap Name (Array Name) :=
1082+
decls.foldl (fun acc decl => acc.insert decl.name decl.deps) {}
1083+
decls.map fun decl =>
1084+
let closure := transitiveClosure depsMap decl.deps
1085+
{ decl with transDeps := (closure.toArray.filter (· != decl.name)).qsort Name.lt }
1086+
10531087
/-- Marks declarations that transitively depend on any `sorry`. -/
10541088
def attachDependsOnSorry (decls : Array DeclInfo) : Array DeclInfo :=
10551089
Id.run do

‎LeanExposition/Site.lean‎

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ private def renderConfig (hasTfb : Bool) : RenderConfig :=
200200
emitTeX := false
201201
emitHtmlSingle := .no
202202
emitHtmlMulti := .immediately
203-
htmlDepth := 2
203+
htmlDepth := 3
204204
rootTocDepth := some 1
205205
sectionTocDepth := some 1
206206
extraCss := [customCss]
@@ -318,10 +318,11 @@ private def mkTrustedBaseIndexBlocks (groups : Array GroupInfo) : Array (Block M
318318

319319
/-- Renders one declaration card with docs, statement, links, and dependencies. -/
320320
private def mkDeclBlock (decl : DeclInfo) (repoUrl? : Option String)
321-
(declHrefs : Std.HashMap Name String) : Block Manual :=
321+
(declHrefs declPageHrefs : Std.HashMap Name String) : Block Manual :=
322322
Id.run do
323323
let issueUrl := issueUrlOf repoUrl? decl.name decl.moduleName decl.source? decl.hasSorry
324324
let sourceUrl := sourceUrlOf repoUrl? decl.source?
325+
let detailsUrl := declPageHrefs.get? decl.name
325326
let mkLinks (deps : Array Name) := deps.filterMap fun dep =>
326327
declHrefs.get? dep |>.map fun href => { label := dep.getString!, href? := some href }
327328
let typeDepLinks := mkLinks decl.typeDeps
@@ -342,7 +343,7 @@ private def mkDeclBlock (decl : DeclInfo) (repoUrl? : Option String)
342343
blocks := blocks.push <| .other (Block.details { summary := s!"Body uses ({proofDepLinks.size})" }) #[block]
343344
if let some block := depListBlock usedByLinks then
344345
blocks := blocks.push <| .other (Block.details { summary := s!"Used by ({usedByLinks.size})" }) #[block]
345-
if let some block := mkLinkParagraph sourceUrl issueUrl then
346+
if let some block := mkLinkParagraph sourceUrl issueUrl detailsUrl then
346347
blocks := blocks.push block
347348
if let some proof := decl.proofText? then
348349
blocks := blocks.push <| .other (Block.details { summary := "Proof" }) #[.code proof]
@@ -358,9 +359,44 @@ private def mkDeclBlock (decl : DeclInfo) (repoUrl? : Option String)
358359
}
359360
.other (Block.declCard cardData) blocks
360361

362+
/-- Builds a dedicated detail page for one declaration, listing its direct and transitive
363+
dependencies. -/
364+
private def mkDeclPart (decl : DeclInfo) (declPageHrefs : Std.HashMap Name String) : Part Manual :=
365+
Id.run do
366+
let mkLinks (deps : Array Name) := deps.filterMap fun dep =>
367+
declPageHrefs.get? dep |>.map fun href => { label := dep.getString!, href? := some href }
368+
let typeDepLinks := mkLinks decl.typeDeps
369+
let proofDepLinks := mkLinks <| decl.deps.filter (!decl.typeDeps.contains ·)
370+
let usedByLinks := mkLinks decl.usedBy
371+
let transDepLinks := mkLinks decl.transDeps
372+
let mut blocks : Array (Block Manual) := #[]
373+
if let some docstringBlock := decl.docstringBlock? then
374+
blocks := blocks.push docstringBlock
375+
blocks := blocks.push (.para #[.bold #[.text "Code"]])
376+
blocks := blocks.push (.code decl.displaySignature)
377+
if let some block := depListBlock typeDepLinks then
378+
blocks := blocks.push <| .other (Block.details { summary := s!"Type uses ({typeDepLinks.size})" }) #[block]
379+
if let some block := depListBlock proofDepLinks then
380+
blocks := blocks.push <| .other (Block.details { summary := s!"Body uses ({proofDepLinks.size})" }) #[block]
381+
if let some block := depListBlock usedByLinks then
382+
blocks := blocks.push <| .other (Block.details { summary := s!"Used by ({usedByLinks.size})" }) #[block]
383+
if let some block := depListBlock transDepLinks then
384+
blocks := blocks.push <| .other (Block.details { summary := s!"All dependencies, transitively ({transDepLinks.size})" }) #[block]
385+
return {
386+
title := #[.code decl.name.toString]
387+
titleString := decl.name.toString
388+
metadata := some {
389+
file := some s!"decl-{anchorIdOf decl.name}"
390+
shortTitle := some decl.name.getString!
391+
number := false
392+
}
393+
content := blocks
394+
subParts := #[]
395+
}
396+
361397
/-- Builds a module page from its declarations. -/
362398
private def mkModulePart (moduleInfo : ModuleInfo) (repoUrl? : Option String)
363-
(declHrefs : Std.HashMap Name String) : Part Manual :=
399+
(declHrefs declPageHrefs : Std.HashMap Name String) : Part Manual :=
364400
{
365401
title := #[.text moduleInfo.path]
366402
titleString := moduleInfo.path
@@ -375,13 +411,13 @@ private def mkModulePart (moduleInfo : ModuleInfo) (repoUrl? : Option String)
375411
.code moduleInfo.name.toString,
376412
.text s!" contains {moduleInfo.decls.size} exposed declarations."
377413
]
378-
] ++ moduleInfo.decls.map (fun decl => mkDeclBlock decl repoUrl? declHrefs)
379-
subParts := #[]
414+
] ++ moduleInfo.decls.map (fun decl => mkDeclBlock decl repoUrl? declHrefs declPageHrefs)
415+
subParts := moduleInfo.decls.map (fun decl => mkDeclPart decl declPageHrefs)
380416
}
381417

382418
/-- Builds a trusted-base module page from declarations in that module. -/
383419
private def mkTrustedBaseModulePart (moduleInfo : ModuleInfo) (repoUrl? : Option String)
384-
(declHrefs : Std.HashMap Name String) : Part Manual :=
420+
(declHrefs declPageHrefs : Std.HashMap Name String) : Part Manual :=
385421
{
386422
title := #[.text moduleInfo.path]
387423
titleString := moduleInfo.path
@@ -396,13 +432,13 @@ private def mkTrustedBaseModulePart (moduleInfo : ModuleInfo) (repoUrl? : Option
396432
.code moduleInfo.name.toString,
397433
.text s!" contributes {moduleInfo.decls.size} declarations to the trusted formalization base."
398434
]
399-
] ++ moduleInfo.decls.map (fun decl => mkDeclBlock decl repoUrl? declHrefs)
435+
] ++ moduleInfo.decls.map (fun decl => mkDeclBlock decl repoUrl? declHrefs declPageHrefs)
400436
subParts := #[]
401437
}
402438

403439
/-- Builds a chapter page that contains regular module pages. -/
404440
private def mkGroupPart (group : GroupInfo) (repoUrl? : Option String)
405-
(declHrefs : Std.HashMap Name String) : Part Manual :=
441+
(declHrefs declPageHrefs : Std.HashMap Name String) : Part Manual :=
406442
let title := humanizeWord group.key
407443
{
408444
title := #[.text title]
@@ -415,12 +451,12 @@ private def mkGroupPart (group : GroupInfo) (repoUrl? : Option String)
415451
content := #[
416452
.para #[.text s!"Modules in the {title} slice are grouped from the first path component after the project root."]
417453
]
418-
subParts := group.modules.map fun moduleInfo => mkModulePart moduleInfo repoUrl? declHrefs
454+
subParts := group.modules.map fun moduleInfo => mkModulePart moduleInfo repoUrl? declHrefs declPageHrefs
419455
}
420456

421457
/-- Builds a chapter page for the trusted-base view. -/
422458
private def mkTrustedBaseGroupPart (group : GroupInfo) (repoUrl? : Option String)
423-
(declHrefs : Std.HashMap Name String) : Part Manual :=
459+
(declHrefs declPageHrefs : Std.HashMap Name String) : Part Manual :=
424460
let title := humanizeWord group.key
425461
{
426462
title := #[.text title]
@@ -433,12 +469,12 @@ private def mkTrustedBaseGroupPart (group : GroupInfo) (repoUrl? : Option String
433469
content := #[
434470
.para #[.text s!"Modules in the {title} slice that contribute declarations to the trusted formalization base."]
435471
]
436-
subParts := group.modules.map fun moduleInfo => mkTrustedBaseModulePart moduleInfo repoUrl? declHrefs
472+
subParts := group.modules.map fun moduleInfo => mkTrustedBaseModulePart moduleInfo repoUrl? declHrefs declPageHrefs
437473
}
438474

439475
/-- Builds the top-level trusted-base section and its chapter subpages. -/
440476
private def mkTrustedBasePart (groups : Array GroupInfo) (repoUrl? : Option String)
441-
(declHrefs : Std.HashMap Name String) (targetBlocks : Array (Block Manual)) : Part Manual :=
477+
(declHrefs declPageHrefs : Std.HashMap Name String) (targetBlocks : Array (Block Manual)) : Part Manual :=
442478
let declCount := countDecls groups
443479
let moduleCount := groups.foldl (fun n group => n + group.modules.size) 0
444480
let intro :=
@@ -462,7 +498,7 @@ private def mkTrustedBasePart (groups : Array GroupInfo) (repoUrl? : Option Stri
462498
++ intro
463499
++ mkTrustedBaseIndexBlocks groups
464500
++ mkDashboardBlocks groups
465-
subParts := groups.map fun group => mkTrustedBaseGroupPart group repoUrl? declHrefs
501+
subParts := groups.map fun group => mkTrustedBaseGroupPart group repoUrl? declHrefs declPageHrefs
466502
}
467503

468504
/-- Builds the interactive dependency graph page and graph payload. -/
@@ -500,7 +536,7 @@ private def mkGraphPart (decls : Array DeclInfo) (declHrefs : Std.HashMap Name S
500536

501537
/-- Builds the root site part with chapter pages and utility sections. -/
502538
private def mkRootPart (cfg : Cli) (rootPrefix : Name) (groups : Array GroupInfo)
503-
(decls : Array DeclInfo) (declHrefs : Std.HashMap Name String)
539+
(decls : Array DeclInfo) (declHrefs declPageHrefs : Std.HashMap Name String)
504540
(introBlocks : Array (Block Manual)) (readerGuideBlocks : Array (Block Manual))
505541
(extraParts : Array (Part Manual)) : Part Manual :=
506542
let title := cfg.siteTitle.getD s!"{rootPrefix} exposition"
@@ -516,7 +552,7 @@ private def mkRootPart (cfg : Cli) (rootPrefix : Name) (groups : Array GroupInfo
516552
++ introBlocks
517553
++ readerGuideBlocks
518554
++ mkDashboardBlocks groups
519-
subParts := (groups.map fun group => mkGroupPart group cfg.repoUrl declHrefs)
555+
subParts := (groups.map fun group => mkGroupPart group cfg.repoUrl declHrefs declPageHrefs)
520556
++ extraParts
521557
++ #[mkGraphPart decls declHrefs]
522558
}
@@ -585,22 +621,23 @@ unsafe def mainImpl (args : List String) : IO UInt32 := do
585621
else
586622
IO.println s!"Collected {decls.size} declarations under {rootPrefix}"
587623
let tfbInfo ← loadTrustedBaseInfo cfg rootPrefix
588-
let decls := decls |> attachReverseDeps |> attachDependsOnSorry |> attachTrustedBaseFlags tfbInfo.names
624+
let decls := decls |> attachReverseDeps |> attachTransitiveDeps |> attachDependsOnSorry |> attachTrustedBaseFlags tfbInfo.names
589625
let order ← moduleOrderMap cfg.projectDir rootPrefix
590626
let modules := buildModules rootPrefix order decls
591627
let groups := buildGroups order modules
592628
let declHrefs := declHrefMap decls
629+
let declPageHrefs := declPageHrefMap decls
593630
let (introBlocks, extraParts) ← loadProjectContextParts cfg.projectDir cfg.repoUrl
594631
let tfbGroups := buildGroups order <| buildModules rootPrefix order <| decls.filter (·.inTfb)
595632
let targetBlocks ← loadTrustedBaseTargetBlocks cfg.projectDir cfg.repoUrl tfbInfo
596633
let hasContext := extraParts.any fun part => part.metadata.bind PartMetadata.file == some "context"
597634
let extraParts :=
598635
if tfbInfo.comparator?.isSome then
599-
extraParts.push <| mkTrustedBasePart tfbGroups cfg.repoUrl declHrefs targetBlocks
636+
extraParts.push <| mkTrustedBasePart tfbGroups cfg.repoUrl declHrefs declPageHrefs targetBlocks
600637
else
601638
extraParts
602639
let readerGuideBlocks := mkReaderGuideBlocks hasContext tfbInfo.comparator?.isSome
603-
let root := mkRootPart cfg rootPrefix groups decls declHrefs introBlocks readerGuideBlocks extraParts
640+
let root := mkRootPart cfg rootPrefix groups decls declHrefs declPageHrefs introBlocks readerGuideBlocks extraParts
604641
let versoArgs :=
605642
match cfg.outputDir with
606643
| some out => ["--output", out]

‎SITE_STRUCTURE.md‎

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@ The exposition site logic is split across five files:
44

55
| File | Lines | Purpose |
66
|---|---|---|
7-
| `LeanExposition/Theme.lean` | 352 | Site CSS (`customCss`) |
7+
| `LeanExposition/Theme.lean` | 374 | Site CSS (`customCss`) |
88
| `LeanExposition/GraphJs.lean` | 326 | Graph page JavaScript (`graphJs`) |
9-
| `LeanExposition/TocJs.lean` | 111 | TOC/utility-nav JavaScript (`tocJs`) |
10-
| `LeanExposition/Collect.lean` | 929 | Declaration collection and analysis pipeline |
11-
| `LeanExposition/Site.lean` | 576 | Rendering, page assembly, and CLI entrypoint wiring |
9+
| `LeanExposition/TocJs.lean` | 153 | TOC/utility-nav JavaScript (`tocJs`) |
10+
| `LeanExposition/Collect.lean` | 1109 | Declaration collection and analysis pipeline |
11+
| `LeanExposition/Site.lean` | 647 | Rendering, page assembly, and CLI entrypoint wiring |
1212

1313
---
1414

1515
## `LeanExposition/Theme.lean`
1616

17-
### 1 · CSS (lines 1–352)
17+
### 1 · CSS (lines 1–374)
1818

1919
`customCss` — the full site stylesheet as a Lean string literal. Covers CSS
20-
custom properties, layout, `.decl-card` variants, `.graph-*` components, TOC
21-
collapse, and responsive breakpoints.
20+
custom properties, layout, `.decl-card` variants, utility nav/buttons
21+
(including the "Hide Theorems" toggle), `.graph-*` components, TOC collapse,
22+
and responsive breakpoints.
2223

2324
---
2425

@@ -38,44 +39,55 @@ collapse, and responsive breakpoints.
3839

3940
## `LeanExposition/TocJs.lean`
4041

41-
### 1 · TOC behavior script (lines 1–111)
42+
### 1 · TOC behavior script (lines 1–153)
4243

43-
`tocJs` — table-of-contents and utility-nav behavior:
44+
`tocJs (hasTfb : Bool)` — table-of-contents and utility-nav behavior:
4445

45-
- injects the Overview / TFB / Graph utility links
46+
- injects the Overview / TFB (when present) / Graph utility links
4647
- removes duplicate utility entries from the generated TOC
4748
- adds a persistent TOC collapse/expand button via `localStorage`
49+
- adds a persistent "Hide Theorems" / "Show Theorems" toggle via `localStorage`
4850

4951
---
5052

5153
## `LeanExposition/Collect.lean`
5254

53-
### 1 · Data model and CLI config (lines 18–153)
55+
### 1 · Data model and CLI config (lines 18–~155)
5456

5557
Defines core structures and enums used by the exposition pipeline:
5658
`Cli`, `DeclKind`, `SourceInfo`, `LinkInfo`, `DeclCardData`, `DetailsData`,
5759
`GraphNode`, `GraphEdge`, `GraphData`, `DeclInfo`, `ModuleInfo`, `GroupInfo`,
5860
`MarkdownSection`, `ComparatorConfigInfo`, `TrustedBaseInfo`,
5961
`TargetStatementInfo`.
6062

61-
### 2 · CLI parsing and naming helpers (lines 154–364)
63+
`DeclInfo` carries, among other fields, `deps` (all type+body dependencies),
64+
`typeDeps` (type-only dependencies), `usedBy` (reverse dependencies),
65+
`transDeps` (full transitive closure of `deps`), and `docstringBlock?` (a
66+
Verso `Block.docstring` rendered for the declaration).
67+
68+
### 2 · CLI parsing and naming helpers (lines ~155–~365)
6269

6370
`usage`, `parseArgs`, name/path helpers, slugging, inline/doc helpers, and
6471
README markdown section parsing.
6572

66-
### 3 · Trusted-base and config loading (lines 365–523)
73+
### 3 · Trusted-base and config loading (lines ~365–~525)
6774

6875
Filesystem/config readers, comparator config parsing, trusted-base target block
6976
loading, and trusted closure computation via `lake exe extractDeps`.
7077

71-
### 4 · Declaration introspection pipeline (lines 524–894)
78+
### 4 · Declaration introspection pipeline (lines ~525–975)
7279

7380
Signature extraction, source-range resolution, exposure filtering, module import
74-
traversal, and declaration collection into `DeclInfo` records.
81+
traversal, `mkDocstringBlock?` (builds the `{docstring ...}`-equivalent block
82+
via `MetaM`), href-map builders (`declHrefMap`, `declPageHrefMap`), and
83+
declaration collection into `DeclInfo` records (including type/body dependency
84+
splitting for defs, theorems, structures, and classes).
7585

76-
### 5 · Post-processing passes (lines 895–925)
86+
### 5 · Post-processing passes (lines ~1050–1109)
7787

78-
`attachReverseDeps`, `attachDependsOnSorry`, and `attachTrustedBaseFlags`.
88+
`attachReverseDeps` (computes `usedBy`), `attachTransitiveDeps` (computes
89+
`transDeps` via `transitiveClosure`), `attachDependsOnSorry`, and
90+
`attachTrustedBaseFlags`.
7991

8092
---
8193

@@ -89,16 +101,20 @@ traversal, and declaration collection into `DeclInfo` records.
89101

90102
`mkSourceParagraph`, `mkMarkdownPart`, `loadProjectContextParts`.
91103

92-
### 3 · Verso block extensions and render config (lines 121–201)
104+
### 3 · Verso block extensions and render config (lines 121–212)
93105

94-
`Block.declCard`, `Block.details`, `Block.graph`, and `renderConfig`.
106+
`Block.declCard`, `Block.details`, `Block.graph`, and `renderConfig`
107+
(`htmlDepth := 3`, so each declaration gets its own split page).
95108

96-
### 4 · Page/document assembly (lines 203–494)
109+
### 4 · Page/document assembly (lines 213–559)
97110

98-
Dashboard blocks, reader guides, declaration cards, chapter/module pages,
111+
Dashboard blocks, reader guides, declaration cards (`mkDeclBlock`, including a
112+
"Details" link to the declaration's dedicated page), dedicated per-declaration
113+
pages (`mkDeclPart`, listing direct "Type uses" / "Body uses" / "Used by" and
114+
the full "All dependencies, transitively" list), chapter/module pages,
99115
trusted-base pages, graph page, and root part construction.
100116

101-
### 5 · Workspace loading and entrypoint (lines 496–576)
117+
### 5 · Workspace loading and entrypoint (lines 560–647)
102118

103119
`withCurrentDir`, `loadWorkspaceAt`, `importRoots`, `firstRootPrefix`,
104120
`loadEnv`, and `mainImpl`.

0 commit comments

Comments
 (0)