From fe5302581712a714e2f6d40280685dcb1ed765da Mon Sep 17 00:00:00 2001
From: Joshua Potter
Date: Thu, 11 May 2023 07:55:25 -0600
Subject: [PATCH] Add PDF generation to absorbed doc-gen4 project.
---
DocGen4/Output/Base.lean | 17 ++++++---
DocGen4/Output/DocString.lean | 2 +-
DocGen4/Output/Index.lean | 45 ++++++++++++++++++++++--
DocGen4/Output/Navbar.lean | 12 +++----
DocGen4/Output/ToJson.lean | 2 +-
DocGen4/Process.lean | 1 +
DocGen4/Process/Hierarchy.lean | 64 ++++++++++++++++++++--------------
DocGen4/Process/NameExt.lean | 43 +++++++++++++++++++++++
README.md | 25 ++++---------
lakefile.lean | 21 +++++++++++
scripts/run_pdflatex.sh | 26 ++++++++++++++
11 files changed, 198 insertions(+), 60 deletions(-)
create mode 100644 DocGen4/Process/NameExt.lean
create mode 100755 scripts/run_pdflatex.sh
diff --git a/DocGen4/Output/Base.lean b/DocGen4/Output/Base.lean
index d02bf99..b45481f 100644
--- a/DocGen4/Output/Base.lean
+++ b/DocGen4/Output/Base.lean
@@ -107,18 +107,25 @@ def templateExtends {α β} {m} [Bind m] (base : α → m β) (new : m α) : m
def templateLiftExtends {α β} {m n} [Bind m] [MonadLift n m] (base : α → n β) (new : m α) : m β :=
new >>= (monadLift ∘ base)
+
+/-
+Returns the doc-gen4 link to a module `NameExt`.
+-/
+def moduleNameExtToLink (n : NameExt) : BaseHtmlM String := do
+ let parts := n.name.components.map Name.toString
+ return (← getRoot) ++ (parts.intersperse "/").foldl (· ++ ·) "" ++ "." ++ n.ext.toString
+
/--
Returns the doc-gen4 link to a module name.
-/
-def moduleNameToLink (n : Name) : BaseHtmlM String := do
- let parts := n.components.map Name.toString
- return (← getRoot) ++ (parts.intersperse "/").foldl (· ++ ·) "" ++ ".html"
+def moduleNameToHtmlLink (n : Name) : BaseHtmlM String :=
+ moduleNameExtToLink ⟨n, .html⟩
/--
Returns the HTML doc-gen4 link to a module name.
-/
def moduleToHtmlLink (module : Name) : BaseHtmlM Html := do
- return {module.toString}
+ return {module.toString}
/--
Returns the LeanInk link to a module name.
@@ -168,7 +175,7 @@ Returns the doc-gen4 link to a declaration name.
def declNameToLink (name : Name) : HtmlM String := do
let res ← getResult
let module := res.moduleNames[res.name2ModIdx.find! name |>.toNat]!
- return (← moduleNameToLink module) ++ "#" ++ name.toString
+ return (← moduleNameToHtmlLink module) ++ "#" ++ name.toString
/--
Returns the HTML doc-gen4 link to a declaration name.
diff --git a/DocGen4/Output/DocString.lean b/DocGen4/Output/DocString.lean
index ef75d76..33c8503 100644
--- a/DocGen4/Output/DocString.lean
+++ b/DocGen4/Output/DocString.lean
@@ -76,7 +76,7 @@ def nameToLink? (s : String) : HtmlM (Option String) := do
declNameToLink name
-- module name
else if res.moduleNames.contains name then
- moduleNameToLink name
+ moduleNameToHtmlLink name
-- find similar name in the same module
else
match (← getCurrentName) with
diff --git a/DocGen4/Output/Index.lean b/DocGen4/Output/Index.lean
index afa4eb5..c87a68c 100644
--- a/DocGen4/Output/Index.lean
+++ b/DocGen4/Output/Index.lean
@@ -15,9 +15,48 @@ def index : BaseHtmlM Html := do templateExtends (baseHtml "Index") <|
pure <|
- Welcome to the documentation page
- -- Temporary comment until the lake issue is resolved
- -- for commit {s!"{← getProjectCommit} "}
+ Bookshelf
+
+ A study of the books listed below. Most proofs are conducted in LaTeX.
+ Where feasible, theorems are also formally proven in
+ Lean.
+
+
+ - Apostol, Tom M. Calculus, Vol. 1: One-Variable Calculus, with an Introduction to Linear Algebra. 2nd ed. Vol. 1. 2 vols. Wiley, 1991.
+ - Avigad, Jeremy. ‘Theorem Proving in Lean’, n.d.
+ - Axler, Sheldon. Linear Algebra Done Right. Undergraduate Texts in Mathematics. Cham: Springer International Publishing, 2015.
+ - Cormen, Thomas H., Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein. Introduction to Algorithms. 3rd ed. Cambridge, Mass: MIT Press, 2009.
+ - Enderton, Herbert B. A Mathematical Introduction to Logic. 2nd ed. San Diego: Harcourt/Academic Press, 2001.
+ - Gries, David. The Science of Programming. Texts and Monographs in Computer Science. New York: Springer-Verlag, 1981.
+ - Gustedt, Jens. Modern C. Shelter Island, NY: Manning Publications Co, 2020.
+ - Ross, Sheldon. A First Course in Probability Theory. 8th ed. Pearson Prentice Hall, n.d.
+ - Smullyan, Raymond M. To Mock a Mockingbird: And Other Logic Puzzles Including an Amazing Adventure in Combinatory Logic. Oxford: Oxford university press, 2000.
+
+
+ A color/symbol code is used on generated PDF headers to indicate their
+ status:
+
+ -
+ Cyan statements indicate axioms and
+ definitions. There must exist a corresponding
axiom
or
+ def
in Lean.
+
+ -
+ Teal statements indicate those
+ with complete proofs in both LaTeX and Lean.
+
+ -
+ Magenta statements indicate
+ those that have not been completely proven in either LaTeX or Lean
+ (or both). Progress is currently being made to correct this though.
+
+ -
+ Red coloring is a catch-all for all
+ statements that don't fit the above categorizations. Incomplete
+ definitions, proofs only conducted in LaTeX, etc. belong here.
+
+
+
This was built using Lean 4 at commit {Lean.githash}
diff --git a/DocGen4/Output/Navbar.lean b/DocGen4/Output/Navbar.lean
index 4280864..75975b6 100644
--- a/DocGen4/Output/Navbar.lean
+++ b/DocGen4/Output/Navbar.lean
@@ -13,9 +13,9 @@ namespace Output
open Lean
open scoped DocGen4.Jsx
-def moduleListFile (file : Name) : BaseHtmlM Html := do
- return
-
{file.getString!}
+def moduleListFile (file : NameExt) : BaseHtmlM Html := do
+ return
/--
@@ -25,13 +25,13 @@ partial def moduleListDir (h : Hierarchy) : BaseHtmlM Html := do
let children := Array.mk (h.getChildren.toList.map Prod.snd)
let dirs := children.filter (fun c => c.getChildren.toList.length != 0)
let files := children.filter (fun c => Hierarchy.isFile c && c.getChildren.toList.length = 0)
- |>.map Hierarchy.getName
+ |>.map Hierarchy.getNameExt
let dirNodes ← dirs.mapM moduleListDir
let fileNodes ← files.mapM moduleListFile
- let moduleLink ← moduleNameToLink h.getName
+ let moduleLink ← moduleNameToHtmlLink h.getName
let summary :=
if h.isFile then
-
{s!"{h.getName.getString!} ({file})"}
+
{s!"{h.getName.getString!} ({file})"}
else
{h.getName.getString!}
diff --git a/DocGen4/Output/ToJson.lean b/DocGen4/Output/ToJson.lean
index a641f67..95e84a0 100644
--- a/DocGen4/Output/ToJson.lean
+++ b/DocGen4/Output/ToJson.lean
@@ -53,7 +53,7 @@ instance : ToJson JsonIndex where
def JsonIndex.addModule (index : JsonIndex) (module : JsonModule) : BaseHtmlM JsonIndex := do
let mut index := index
- let newModule := (module.name, ← moduleNameToLink (String.toName module.name))
+ let newModule := (module.name, ← moduleNameToHtmlLink (String.toName module.name))
let newDecls := module.declarations.map (fun d => (d.name, d))
index := { index with
modules := newModule :: index.modules
diff --git a/DocGen4/Process.lean b/DocGen4/Process.lean
index 0c1d9e6..dd6a317 100644
--- a/DocGen4/Process.lean
+++ b/DocGen4/Process.lean
@@ -14,6 +14,7 @@ import DocGen4.Process.DocInfo
import DocGen4.Process.Hierarchy
import DocGen4.Process.InductiveInfo
import DocGen4.Process.InstanceInfo
+import DocGen4.Process.NameExt
import DocGen4.Process.NameInfo
import DocGen4.Process.OpaqueInfo
import DocGen4.Process.StructureInfo
diff --git a/DocGen4/Process/Hierarchy.lean b/DocGen4/Process/Hierarchy.lean
index 86d2be0..2667f89 100644
--- a/DocGen4/Process/Hierarchy.lean
+++ b/DocGen4/Process/Hierarchy.lean
@@ -6,6 +6,8 @@ Authors: Henrik Böving
import Lean
import Lean.Data.HashMap
+import DocGen4.Process.NameExt
+
def Lean.HashSet.fromArray [BEq α] [Hashable α] (xs : Array α) : Lean.HashSet α :=
xs.foldr (flip .insert) .empty
@@ -18,35 +20,38 @@ def getNLevels (name : Name) (levels: Nat) : Name :=
(components.drop (components.length - levels)).reverse.foldl (· ++ ·) Name.anonymous
inductive Hierarchy where
-| node (name : Name) (isFile : Bool) (children : RBNode Name (fun _ => Hierarchy)) : Hierarchy
+| node (name : NameExt) (isFile : Bool) (children : RBNode NameExt (fun _ => Hierarchy)) : Hierarchy
-instance : Inhabited Hierarchy := ⟨Hierarchy.node Name.anonymous false RBNode.leaf⟩
+instance : Inhabited Hierarchy := ⟨Hierarchy.node ⟨.anonymous, .html⟩ false RBNode.leaf⟩
-abbrev HierarchyMap := RBNode Name (fun _ => Hierarchy)
+abbrev HierarchyMap := RBNode NameExt (fun _ => Hierarchy)
-- Everything in this namespace is adapted from stdlib's RBNode
namespace HierarchyMap
-def toList : HierarchyMap → List (Name × Hierarchy)
+def toList : HierarchyMap → List (NameExt × Hierarchy)
| t => t.revFold (fun ps k v => (k, v)::ps) []
-def toArray : HierarchyMap → Array (Name × Hierarchy)
+def toArray : HierarchyMap → Array (NameExt × Hierarchy)
| t => t.fold (fun ps k v => ps ++ #[(k, v)] ) #[]
-def hForIn [Monad m] (t : HierarchyMap) (init : σ) (f : (Name × Hierarchy) → σ → m (ForInStep σ)) : m σ :=
+def hForIn [Monad m] (t : HierarchyMap) (init : σ) (f : (NameExt × Hierarchy) → σ → m (ForInStep σ)) : m σ :=
t.forIn init (fun a b acc => f (a, b) acc)
-instance : ForIn m HierarchyMap (Name × Hierarchy) where
+instance : ForIn m HierarchyMap (NameExt × Hierarchy) where
forIn := HierarchyMap.hForIn
end HierarchyMap
namespace Hierarchy
-def empty (n : Name) (isFile : Bool) : Hierarchy :=
+def empty (n : NameExt) (isFile : Bool) : Hierarchy :=
node n isFile RBNode.leaf
def getName : Hierarchy → Name
+| node n _ _ => n.name
+
+def getNameExt : Hierarchy → NameExt
| node n _ _ => n
def getChildren : Hierarchy → HierarchyMap
@@ -55,31 +60,34 @@ def getChildren : Hierarchy → HierarchyMap
def isFile : Hierarchy → Bool
| node _ f _ => f
-partial def insert! (h : Hierarchy) (n : Name) : Hierarchy := Id.run do
- let hn := h.getName
+partial def insert! (h : Hierarchy) (n : NameExt) : Hierarchy := Id.run do
+ let hn := h.getNameExt
let mut cs := h.getChildren
- if getNumParts hn + 1 == getNumParts n then
- match cs.find Name.cmp n with
+ if getNumParts hn.name + 1 == getNumParts n.name then
+ match cs.find NameExt.cmp n with
| none =>
- node hn h.isFile (cs.insert Name.cmp n <| empty n true)
+ node hn h.isFile (cs.insert NameExt.cmp n <| empty n true)
| some (node _ true _) => h
| some (node _ false ccs) =>
- cs := cs.erase Name.cmp n
- node hn h.isFile (cs.insert Name.cmp n <| node n true ccs)
+ cs := cs.erase NameExt.cmp n
+ node hn h.isFile (cs.insert NameExt.cmp n <| node n true ccs)
else
- let leveledName := getNLevels n (getNumParts hn + 1)
- match cs.find Name.cmp leveledName with
+ let leveled := ⟨getNLevels n.name (getNumParts hn.name + 1), .html⟩
+ match cs.find NameExt.cmp leveled with
| some nextLevel =>
- cs := cs.erase Name.cmp leveledName
+ cs := cs.erase NameExt.cmp leveled
-- BUG?
- node hn h.isFile <| cs.insert Name.cmp leveledName (nextLevel.insert! n)
+ node hn h.isFile <| cs.insert NameExt.cmp leveled (nextLevel.insert! n)
| none =>
- let child := (insert! (empty leveledName false) n)
- node hn h.isFile <| cs.insert Name.cmp leveledName child
+ let child := (insert! (empty leveled false) n)
+ node hn h.isFile <| cs.insert NameExt.cmp leveled child
partial def fromArray (names : Array Name) : Hierarchy :=
- names.foldl insert! (empty anonymous false)
+ (names.map (fun n => NameExt.mk n .html)).foldl insert! (empty ⟨anonymous, .html⟩ false)
+
+partial def fromArrayExt (names : Array NameExt) : Hierarchy :=
+ names.foldl insert! (empty ⟨anonymous, .html⟩ false)
def baseDirBlackList : HashSet String :=
HashSet.fromArray #[
@@ -99,13 +107,15 @@ def baseDirBlackList : HashSet String :=
"style.css"
]
-partial def fromDirectoryAux (dir : System.FilePath) (previous : Name) : IO (Array Name) := do
+partial def fromDirectoryAux (dir : System.FilePath) (previous : Name) : IO (Array NameExt) := do
let mut children := #[]
for entry in ← System.FilePath.readDir dir do
if ← entry.path.isDir then
children := children ++ (← fromDirectoryAux entry.path (.str previous entry.fileName))
else if entry.path.extension = some "html" then
- children := children.push <| .str previous (entry.fileName.dropRight ".html".length)
+ children := children.push <| ⟨.str previous (entry.fileName.dropRight ".html".length), .html⟩
+ else if entry.path.extension = some "pdf" then
+ children := children.push <| ⟨.str previous (entry.fileName.dropRight ".pdf".length), .pdf⟩
return children
def fromDirectory (dir : System.FilePath) : IO Hierarchy := do
@@ -116,8 +126,10 @@ def fromDirectory (dir : System.FilePath) : IO Hierarchy := do
else if ← entry.path.isDir then
children := children ++ (← fromDirectoryAux entry.path (.mkSimple entry.fileName))
else if entry.path.extension = some "html" then
- children := children.push <| .mkSimple (entry.fileName.dropRight ".html".length)
- return Hierarchy.fromArray children
+ children := children.push <| ⟨.mkSimple (entry.fileName.dropRight ".html".length), .html⟩
+ else if entry.path.extension = some "pdf" then
+ children := children.push <| ⟨.mkSimple (entry.fileName.dropRight ".pdf".length), .pdf⟩
+ return Hierarchy.fromArrayExt children
end Hierarchy
end DocGen4
diff --git a/DocGen4/Process/NameExt.lean b/DocGen4/Process/NameExt.lean
new file mode 100644
index 0000000..d495cc5
--- /dev/null
+++ b/DocGen4/Process/NameExt.lean
@@ -0,0 +1,43 @@
+/-
+A generalization of `Lean.Name` that includes a file extension.
+-/
+import Lean
+
+open Lean Name
+
+inductive Extension where
+| html
+| pdf
+deriving Repr
+
+namespace Extension
+
+def cmp : Extension → Extension → Ordering
+ | html, html => Ordering.eq
+ | html, _ => Ordering.lt
+ | pdf, pdf => Ordering.eq
+ | pdf, _ => Ordering.gt
+
+def toString : Extension → String
+ | html => "html"
+ | pdf => "pdf"
+
+end Extension
+
+structure NameExt where
+ name : Name
+ ext : Extension
+
+namespace NameExt
+
+def cmp (n₁ n₂ : NameExt) : Ordering :=
+ match Name.cmp n₁.name n₂.name with
+ | Ordering.eq => Extension.cmp n₁.ext n₂.ext
+ | ord => ord
+
+def getString! : NameExt → String
+ | ⟨str _ s, .html⟩ => s
+ | ⟨str _ s, .pdf⟩ => s ++ "_pdf"
+ | _ => unreachable!
+
+end NameExt
diff --git a/README.md b/README.md
index 9a6beb4..1977250 100644
--- a/README.md
+++ b/README.md
@@ -15,27 +15,16 @@ feasible, theorems are also formally proven in [Lean](https://leanprover.github.
## Documentation
-To generate documentation, we use [bookshelf-docgen](https://github.com/jrpotter/bookshelf-docgen).
-Refer to this project on prerequisites and then run the following to build and
-serve files locally:
+This project has absorbed [doc-gen4](https://github.com/leanprover/doc-gen4) to
+ease customization. In particular, the `DocGen4` module found in this project
+allows generating PDFs and including them into the navbar. To generate
+documentation and serve files locally, run the following:
```bash
> lake build Bookshelf:docs
> lake run server
```
-This assumes you have `python3` available in your `$PATH`. To change how the
-server behaves, refer to the `.env` file located in the root directory of this
-project.
-
-A color code is used on generated PDF headers to indicate their status:
-
-* Cyan statements indicate axioms and definitions. There must exist a
- corresponding `axiom` or `def` in Lean.
-* Teal statements indicate those with complete proofs in both LaTeX *and* Lean.
-* Magenta statements indicate those that have not been completely proven in
- either LaTeX or Lean (or both). Progress is currently being made to correct
- this though.
-* Red coloring is a catch-all for all statements that don't fit the above
- categorizations. Incomplete definitions, proofs only conducted in LaTeX, etc.
- belong here.
+This assumes you have `pdflatex` and `python3` available in your `$PATH`. To
+change how the server behaves, refer to the `.env` file located in the root
+directory of this project.
diff --git a/lakefile.lean b/lakefile.lean
index 41676d8..fbee696 100644
--- a/lakefile.lean
+++ b/lakefile.lean
@@ -77,7 +77,28 @@ target coreDocs : FilePath := do
}
return (dataFile, trace)
+/--
+Wrapper for running scripts found in the `script` directory.
+-/
+def run_sh (name : String) : IndexBuildM Unit := do
+ let some bookshelfPkg ← findPackage? `«bookshelf»
+ | error "no bookshelf package found in workspace"
+ let proc <- IO.Process.output {
+ cmd := bookshelfPkg.dir.toString ++ s!"/scripts/{name}.sh",
+ args := #[(← getWorkspace).root.buildDir.toString]
+ }
+ if proc.exitCode == 0 then
+ let out := proc.stdout.trim
+ if out.length > 0 then IO.println out
+ else
+ let err := proc.stderr.trim
+ if err.length > 0 then IO.eprintln err
+
library_facet docs (lib) : FilePath := do
+ run_sh "run_pdflatex"
+ -- Ordering is important. The index file is generated by walking through the
+ -- filesystem directory. Files copied from the shell scripts need to exist
+ -- prior to this.
let some bookshelfPkg ← findPackage? `«bookshelf»
| error "no bookshelf package found in workspace"
let some docGen4 := bookshelfPkg.findLeanExe? `«doc-gen4»
diff --git a/scripts/run_pdflatex.sh b/scripts/run_pdflatex.sh
new file mode 100755
index 0000000..5911bf6
--- /dev/null
+++ b/scripts/run_pdflatex.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+if ! command -v pdflatex > /dev/null; then
+ >&2 echo 'pdflatex was not found in the current $PATH.'
+ exit 1
+fi
+
+BUILD_DIR="$1"
+
+function process_file () {
+ REL_DIR=$(dirname "$1")
+ REL_BASE=$(basename -s ".tex" "$1")
+ mkdir -p "$BUILD_DIR/doc/$REL_DIR"
+ (cd "$REL_DIR" && pdflatex "$REL_BASE.tex")
+ cp "$REL_DIR/$REL_BASE.pdf" "$BUILD_DIR/doc/$REL_DIR/"
+}
+
+export BUILD_DIR
+export -f process_file
+
+# We run this command twice to allow any cross-references to resolve correctly.
+# https://tex.stackexchange.com/questions/41539/does-hyperref-work-between-two-files
+for _ in {1..2}; do
+ find ./* \( -path build -o -path lake-packages \) -prune -o -name "*.tex" -print0 \
+ | xargs -0 -I{} bash -c "process_file {}"
+done