From c63d2daccb30340c3df5e5e27e96c57268952660 Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Wed, 12 Nov 2025 16:19:21 +0100 Subject: [PATCH 01/45] wip: tutorials --- Main.lean | 6 +- Manual.lean | 3 - Manual/Grind/ExtendedExamples.lean | 7 +- Manual/Meta.lean | 1 - Manual/Meta/Example.lean | 1 - Manual/Meta/Imports.lean | 38 --------- Manual/Meta/ParserAlias.lean | 2 +- Manual/Meta/Syntax.lean | 8 +- Tutorial.lean | 2 + .../Grind}/IndexMap.lean | 79 +++++++++++++++++-- {Manual => Tutorial}/VCGen.lean | 35 +++++--- TutorialMain.lean | 52 ++++++++++++ extended-examples/IndexMapGrind.lean | 15 +++- lakefile.lean | 9 ++- 14 files changed, 186 insertions(+), 72 deletions(-) delete mode 100644 Manual/Meta/Imports.lean create mode 100644 Tutorial.lean rename {Manual/Grind/ExtendedExamples => Tutorial/Grind}/IndexMap.lean (94%) rename {Manual => Tutorial}/VCGen.lean (97%) create mode 100644 TutorialMain.lean diff --git a/Main.lean b/Main.lean index ffc473b9c..101e07047 100644 --- a/Main.lean +++ b/Main.lean @@ -1,5 +1,5 @@ /- -Copyright (c) 2024 Lean FRO LLC. All rights reserved. +Copyright (c) 2024-2025 Lean FRO LLC. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Author: David Thrane Christiansen -/ @@ -36,11 +36,11 @@ def staticCss := {{ def main := manualMain (%doc Manual) (config := config) (extraSteps := [extractExamples]) where - config := Config.addSearch <| Config.addKaTeX { + config := { extraFiles := [("static", "static")], extraHead := #[plausible, staticJs, staticCss], emitTeX := false, - emitHtmlSingle := true, -- for proofreading + emitHtmlSingle := .immediately, -- for proofreading logo := some "/static/lean_logo.svg", sourceLink := some "https://github.com/leanprover/reference-manual", issueLink := some "https://github.com/leanprover/reference-manual/issues", diff --git a/Manual.lean b/Manual.lean index 4e9144e6c..2952c6a7c 100644 --- a/Manual.lean +++ b/Manual.lean @@ -18,7 +18,6 @@ import Manual.ErrorExplanations import Manual.Tactics import Manual.Simp import Manual.Grind -import Manual.VCGen import Manual.BasicTypes import Manual.BasicProps import Manual.NotationsMacros @@ -100,8 +99,6 @@ Thus, this reference manual does not draw a barrier between the two aspects, but {include 0 Manual.Grind} -{include 0 Manual.VCGen} - {include 0 Manual.BasicProps} {include 0 Manual.BasicTypes} diff --git a/Manual/Grind/ExtendedExamples.lean b/Manual/Grind/ExtendedExamples.lean index 7ac200443..bc880375c 100644 --- a/Manual/Grind/ExtendedExamples.lean +++ b/Manual/Grind/ExtendedExamples.lean @@ -12,7 +12,6 @@ import Manual.Meta import Manual.Grind.ExtendedExamples.Integration import Manual.Grind.ExtendedExamples.IfElseNorm -import Manual.Grind.ExtendedExamples.IndexMap open Verso.Genre Manual open Verso.Genre.Manual.InlineLean @@ -27,8 +26,10 @@ open Lean.Grind tag := "grind-bigger-examples" %%% +:::TODO +Properly link to tutorial section +::: + {include 1 Manual.Grind.ExtendedExamples.Integration} {include 1 Manual.Grind.ExtendedExamples.IfElseNorm} - -{include 1 Manual.Grind.ExtendedExamples.IndexMap} diff --git a/Manual/Meta.lean b/Manual/Meta.lean index ec3b04891..c78713da8 100644 --- a/Manual/Meta.lean +++ b/Manual/Meta.lean @@ -33,7 +33,6 @@ import Manual.Meta.Syntax import Manual.Meta.Tactics import Manual.Meta.SpliceContents import Manual.Meta.Markdown -import Manual.Meta.Imports import Manual.Meta.Namespace diff --git a/Manual/Meta/Example.lean b/Manual/Meta/Example.lean index 882346526..e6d299242 100644 --- a/Manual/Meta/Example.lean +++ b/Manual/Meta/Example.lean @@ -6,7 +6,6 @@ Author: David Thrane Christiansen import VersoManual import Manual.Meta.Figure -import Manual.Meta.Imports import Manual.Meta.LzCompress import Lean.Elab.InfoTree.Types diff --git a/Manual/Meta/Imports.lean b/Manual/Meta/Imports.lean deleted file mode 100644 index 47df4bcaf..000000000 --- a/Manual/Meta/Imports.lean +++ /dev/null @@ -1,38 +0,0 @@ -/- -Copyright (c) 2025 Lean FRO LLC. All rights reserved. -Released under Apache 2.0 license as described in the file LICENSE. -Author: David Thrane Christiansen --/ - -import VersoManual -import Lean.Elab.InfoTree.Types -import SubVerso.Highlighting.Code - -open scoped Lean.Doc.Syntax - -open Verso Doc Elab -open Lean Elab -open Verso.Genre.Manual InlineLean Scopes -open Verso.SyntaxUtils -open SubVerso.Highlighting -open ArgParse - -namespace Manual - -structure ImportsParams where - «show» : Bool := true - -instance : FromArgs ImportsParams m where - fromArgs := ImportsParams.mk <$> .flag `show true (some "Whether to show the import header") - -@[code_block] -def imports : CodeBlockExpanderOf ImportsParams - | { «show» } , str => do - let altStr ← parserInputString str - let p := Parser.whitespace >> Parser.Module.header.fn - let headerStx ← p.parseString altStr - let hl ← highlight headerStx #[] {} - if «show» then - ``(Block.other (Block.lean $(quote hl) {}) #[Block.code $(quote str.getString)]) - else - ``(Block.empty) diff --git a/Manual/Meta/ParserAlias.lean b/Manual/Meta/ParserAlias.lean index 882558c69..bcc4ee9b2 100644 --- a/Manual/Meta/ParserAlias.lean +++ b/Manual/Meta/ParserAlias.lean @@ -69,7 +69,7 @@ def parserAlias : DirectiveExpander pure #[← ``(Verso.Doc.Block.other (Block.parserAlias $(quote opts.name) $(quote declName) $(quote opts.show) $(quote stackSz?) $(quote autoGroupArgs) $(quote docs?) $(quote argCount)) #[$(contents ++ userContents),*])] @[inline] -private def getFromJson {α} [Inhabited α] [FromJson α] (v : Json) : HtmlT Genre.Manual (ReaderT ExtensionImpls IO) α:= +private def getFromJson {α} [Monad m] [Inhabited α] [FromJson α] (v : Json) : HtmlT Genre.Manual m α:= match FromJson.fromJson? (α := α) v with | .error e => do Verso.Doc.Html.HtmlT.logError diff --git a/Manual/Meta/Syntax.lean b/Manual/Meta/Syntax.lean index b96b58215..810b6f6c1 100644 --- a/Manual/Meta/Syntax.lean +++ b/Manual/Meta/Syntax.lean @@ -830,7 +830,7 @@ where return bar ++ .nest 2 (← production which stx |>.run' {}) def testGetBnf (config : FreeSyntaxConfig) (isFirst : Bool) (stxs : List Syntax) : TermElabM String := do - let (tagged, _) ← getBnf config isFirst stxs |>.run (← moduleGenreElabContext) {} {partContext := ⟨⟨default, default, default, default, default⟩, default⟩} + let (tagged, _) ← getBnf config isFirst stxs |>.run ⟨← ``(Manual), mkConst ``Manual, .always, none⟩ {} {partContext := ⟨⟨default, default, default, default, default⟩, default⟩} pure tagged.stripTags namespace Tests @@ -1352,7 +1352,7 @@ window.addEventListener("load", () => { "# open Verso.Output Html HtmlT in -private def nonTermHtmlOf (kind : Name) (doc? : Option String) (rendered : Html) : HtmlT Manual (ReaderT ExtensionImpls IO) Html := do +private def nonTermHtmlOf (kind : Name) (doc? : Option String) (rendered : Html) : HtmlT Manual (ReaderT Multi.AllRemotes (ReaderT ExtensionImpls IO)) Html := do let xref ← match (← state).resolveDomainObject syntaxKindDomain kind.toString with | .error _ => pure none @@ -1398,7 +1398,7 @@ def noLook (ctx : GrammarHtmlContext) : GrammarHtmlContext := end GrammarHtmlContext open Verso.Output Html in -abbrev GrammarHtmlM := ReaderT GrammarHtmlContext (HtmlT Manual (ReaderT ExtensionImpls IO)) +abbrev GrammarHtmlM := ReaderT GrammarHtmlContext (HtmlT Manual (ReaderT Multi.AllRemotes (ReaderT ExtensionImpls IO))) private def lookingAt (k : Name) : GrammarHtmlM α → GrammarHtmlM α := withReader (·.look k) @@ -1492,7 +1492,7 @@ where let inner ← go if let some k := (← read).lookingAt then unless k == nullKind do - if let some tgt := ((← HtmlT.state (genre := Manual) (m := ReaderT ExtensionImpls IO)).localTargets.keyword k none)[0]? then + if let some tgt := ((← HtmlT.state (genre := Manual) (m := ReaderT Multi.AllRemotes (ReaderT ExtensionImpls IO))).localTargets.keyword k none)[0]? then return {{{{inner}}}} return {{{{inner}}}} | .nonterminal k doc? => do diff --git a/Tutorial.lean b/Tutorial.lean new file mode 100644 index 000000000..9d2f83520 --- /dev/null +++ b/Tutorial.lean @@ -0,0 +1,2 @@ +import Tutorial.VCGen +import Tutorial.Grind.IndexMap diff --git a/Manual/Grind/ExtendedExamples/IndexMap.lean b/Tutorial/Grind/IndexMap.lean similarity index 94% rename from Manual/Grind/ExtendedExamples/IndexMap.lean rename to Tutorial/Grind/IndexMap.lean index f03d31a47..b8eacaca2 100644 --- a/Manual/Grind/ExtendedExamples/IndexMap.lean +++ b/Tutorial/Grind/IndexMap.lean @@ -5,13 +5,14 @@ Author: Leo de Moura, Kim Morrison -/ import VersoManual +import VersoTutorial import Lean.Parser.Term import Manual.Meta -open Verso.Genre Manual +open Verso.Genre Manual Tutorial open Verso.Genre.Manual.InlineLean hiding module open Verso.Doc.Elab (CodeBlockExpander) open Verso.Code.External @@ -30,8 +31,12 @@ set_option verso.exampleProject "." set_option verso.exampleModule "IndexMapGrind" -#doc (Manual) "`IndexMap`" => - +#doc (Tutorial) "`IndexMap`" => +%%% +slug := "grind-index-map" +summary := "A walkthrough of ..." +exampleStyle := .inlineLean `IndexMap +%%% In this section we'll build an example of a new data structure and basic API for it, illustrating the use of {tactic}`grind`. The example will be derived from Rust's [`indexmap`](https://docs.rs/indexmap/latest/indexmap/) data structure. @@ -60,6 +65,18 @@ Our goals will be: Ideally, we don't even need to see the proofs; they should mostly be handled invisibly by {tactic}`grind`. + +:::paragraph +The first step is to import the necessary data structures: +```anchor imports +import Std.Data.HashMap +``` +::: + +# Skeleton + +:::displayOnly + To begin with, we'll write out a skeleton of what we want to achieve, liberally using {lean}`sorry` as a placeholder for all proofs. In particular, this version makes no use of {tactic}`grind`. @@ -212,6 +229,9 @@ theorem findIdx_insert_self end IndexMap ``` +::: + +# Header 2 Let's get started. We'll aspire to never writing a proof by hand, and the first step of that is to install auto-parameters for the `size_keys` and `WF` field, @@ -219,6 +239,8 @@ so we can omit these fields whenever `grind` can prove them. While we're modifying the definition of `IndexMap` itself, lets make all the fields private, since we're planning on having complete encapsulation. ```anchor IndexMap +open Std + structure IndexMap (α : Type u) (β : Type v) [BEq α] [Hashable α] where private indices : HashMap α Nat @@ -229,6 +251,14 @@ structure IndexMap keys[i]? = some a ↔ indices[a]? = some i := by grind ``` +For the rest of this tutorial, the following namespace and variable declarations are in effect: +```anchor variables +namespace IndexMap + +variable {α : Type u} {β : Type v} [BEq α] [Hashable α] +variable {m : IndexMap α β} {a : α} {b : β} {i : Nat} +``` + Let's give {tactic}`grind` access to the definition of `size`, and `size_keys` private field: ```anchor size @[inline] def size (m : IndexMap α β) : Nat := @@ -250,6 +280,21 @@ def emptyWithCapacity (capacity := 8) : IndexMap α β where ``` ::: +:::codeOnly +```anchor Membership +@[inline] def contains (m : IndexMap α β) + (a : α) : Bool := + m.indices.contains a + +instance : Membership α (IndexMap α β) where + mem m a := a ∈ m.indices + +instance {m : IndexMap α β} {a : α} : Decidable (a ∈ m) := + inferInstanceAs (Decidable (a ∈ m.indices)) +``` +::: + +:::displayOnly Our next task is to deal with the {lean}`sorry` in our construction of the original {anchorTerm GetElem?}`GetElem?` instance: ```anchor GetElem? (module := IndexMap) instance : @@ -261,6 +306,7 @@ instance : getElem! m a := m.indices[a]?.bind (m.values[·]?) |>.getD default ``` +::: The goal at this sorry is ``` @@ -331,7 +377,7 @@ However this proof is going to work, we know the following: * It must use the well-formedness condition of the map. * It can't do so without relating `m.indices[a]` and `m.indices[a]?` (because the later is what appears in the well-formedness condition). * The expected relationship there doesn't even hold unless the map `m.indices` satisfies {lean}`LawfulGetElem`, - for which we need {tech}[instances] of {lean}`LawfulBEq α` and {lean}`LawfulHashable α`. + for which we need {tech (remote:="reference")}[instances] of {lean}`LawfulBEq α` and {lean}`LawfulHashable α`. ::: :::TODO @@ -401,7 +447,13 @@ macro_rules | `(tactic| get_elem_tactic_extensible) => `(tactic| grind) ::: :::paragraph -We can now return to constructing our {anchorName GetElem?}`GetElem?` instance, and simply write: +We can now return to constructing our {anchorName GetElem?}`GetElem?` instance. +In order to use the well-formedness condition, {tactic}`grind` must be able to unfold {anchorName size}`size`: +```anchor local_grind_size +attribute [local grind] size +``` +The {anchorTerm local_grind_size}`local` modifier restricts this unfolding to the current file. +With this in place, we can simply write: ```anchor GetElem? instance : GetElem? (IndexMap α β) α β (fun m a => a ∈ m) where getElem m a h := @@ -626,6 +678,23 @@ Trying again with {anchorName eraseSwap}`eraseSwap`, everything goes through cle | none => m ``` +:::codeOnly +```anchor getFindIdx +@[inline] def findIdx? (m : IndexMap α β) (a : α) : Option Nat := + m.indices[a]? + +@[inline] def findIdx (m : IndexMap α β) (a : α) + (h : a ∈ m := by get_elem_tactic) : Nat := + m.indices[a] + +@[inline] def getIdx? (m : IndexMap α β) (i : Nat) : Option β := + m.values[i]? + +@[inline] def getIdx (m : IndexMap α β) (i : Nat) + (h : i < m.size := by get_elem_tactic) : β := + m.values[i] +``` +::: Finally we turn to the verification theorems about the basic operations, relating {anchorName Verification}`getIdx`, {anchorName Verification}`findIdx`, and {anchorName Verification}`insert`. By adding a {anchorTerm Verification}`local grind` annotation allowing {tactic}`grind` to unfold the definitions of these operations, diff --git a/Manual/VCGen.lean b/Tutorial/VCGen.lean similarity index 97% rename from Manual/VCGen.lean rename to Tutorial/VCGen.lean index fe7788140..941ed915a 100644 --- a/Manual/VCGen.lean +++ b/Tutorial/VCGen.lean @@ -5,6 +5,7 @@ Author: Sebastian Graf -/ import VersoManual +import VersoTutorial import Manual.Meta import Manual.Papers @@ -24,14 +25,14 @@ set_option linter.unusedVariables false set_option linter.typography.quotes true set_option linter.typography.dashes true -set_option mvcgen.warning false - open Manual (comment) -#doc (Manual) "The `mvcgen` tactic" => +#doc (Tutorial) "The `mvcgen` tactic" => %%% tag := "mvcgen-tactic" -htmlSplit := .never +slug := "mvcgen" +summary := "summary here" +exampleStyle := .inlineLean `MVCGenTutorial %%% The {tactic}`mvcgen` tactic implements a _monadic verification condition generator_: @@ -43,15 +44,27 @@ In order to use the {tactic}`mvcgen` tactic, {module}`Std.Tactic.Do` must be imp # Verifying Imperative Programs Using `mvcgen` +:::paragraph This section is a tutorial that introduces the most important concepts of {tactic}`mvcgen` top-down. -Recall that you need to import {module}`Std.Tactic.Do` and open {namespace}`Std.Do` to run these examples: - +A little preparation is required. +Recall that you need to import {module}`Std.Tactic.Do` to use the tactic: ```imports import Std.Tactic.Do ``` +The code also makes use of hash sets from the standard library: +```imports +import Std.Data.HashSet +``` +Because {tactic}`mvcgen` is presently an experimental feature, a warning is issued when it is used. +The warning can be disabled as follows: +```lean +set_option mvcgen.warning false +``` +Finally, the namespace {namespace}`Std.Do` must be opened in order to run these examples: ```lean open Std.Do ``` +::: ## Preconditions and Postconditions @@ -69,7 +82,7 @@ This means that the postcondition holds no matter what. ## Loops and Invariants :::leanFirst -As a first example of {tactic}`mvcgen`, the function {name}`mySum` computes the sum of an array using {ref "let-mut"}[local mutable state] and a {keywordOf Lean.Parser.Term.doFor}`for` loop: +As a first example of {tactic}`mvcgen`, the function {name}`mySum` computes the sum of an array using {ref "let-mut" (remote:="reference")}[local mutable state] and a {keywordOf Lean.Parser.Term.doFor}`for` loop: ```lean def mySum (l : Array Nat) : Nat := Id.run do @@ -241,7 +254,7 @@ axiom onExcept : ExceptConds PostShape.pure ``` The proof has the same succinct structure as for the initial {name}`mySum` example, because we again offload all proofs to {tactic}`grind` and its existing automation around {name}`List.Nodup`. Therefore, the only difference is in the {tech}[loop invariant]. -Since our loop has an {ref "early-return"}[early return], we construct the invariant using the helper function {lean}`Invariant.withEarlyReturn`. +Since our loop has an {ref "early-return" (remote:="reference")}[early return], we construct the invariant using the helper function {lean}`Invariant.withEarlyReturn`. This function allows us to specify the invariant in three parts: * {lean}`onReturn ret seen` holds after the loop was left through an early return with value {lean}`ret`. @@ -296,7 +309,7 @@ Some observations: * The proof is even shorter than the one with {tactic}`mvcgen`. * The use of {tactic}`generalize` to generalize the accumulator relies on there being exactly one occurrence of {lean (type := "Std.HashSet Int")}`∅` to generalize. If that were not the case, we would have to copy parts of the program into the proof. This is a no-go for larger functions. * {tactic}`grind` splits along the control flow of the function and reasons about {name}`Id`, given the right lemmas. - While this works for {name}`Id.run_pure` and {name}`Id.run_bind`, it would not work for {name}`Id.run_seq`, for example, because that lemma is not {tech (key := "E-matching")}[E-matchable]. + While this works for {name}`Id.run_pure` and {name}`Id.run_bind`, it would not work for {name}`Id.run_seq`, for example, because that lemma is not {tech (key := "E-matching") (remote := "reference")}[E-matchable]. If {tactic}`grind` would fail, we would be forced to do all the control flow splitting and monadic reasoning by hand until {tactic}`grind` could pick up again. ::: @@ -570,7 +583,7 @@ Hypotheses in the Lean context are part of the immutable {deftech}_frame_ of the ## Monad Transformers and Lifting -Real-world programs often use monads that are built from multiple {tech}[monad transformers], with operations being frequently {ref "lifting-monads"}[lifted] from one monad to another. +Real-world programs often use monads that are built from multiple {tech (remote := "reference")}[monad transformers], with operations being frequently {ref "lifting-monads" (remote:="reference")}[lifted] from one monad to another. Verification of these programs requires taking this into account. We can tweak the previous example to demonstrate this. @@ -1026,6 +1039,6 @@ A common case for when a VC of the form {lean}`H ⊢ₛ T` is left behind is whe In this case, the proof will depend on a {lean}`WP m ps` instance which governs the translation into the {name}`Assertion` language, but the exact correspondence to `σs : List (Type u)` is yet unknown. To successfully discharge such a VC, `mvcgen` comes with an entire proof mode that is inspired by that of the Iris concurrent separation logic. (In fact, the proof mode was adapted in large part from its Lean clone, [`iris-lean`](https://github.com/leanprover-community/iris-lean).) -The {ref "tactic-ref-spred"}[tactic reference] contains a list of all proof mode tactics. +The {ref "tactic-ref-spred" (remote:="reference")}[tactic reference] contains a list of all proof mode tactics. ::: diff --git a/TutorialMain.lean b/TutorialMain.lean new file mode 100644 index 000000000..e1eac3294 --- /dev/null +++ b/TutorialMain.lean @@ -0,0 +1,52 @@ +/- +Copyright (c) 2024 Lean FRO LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Author: David Thrane Christiansen +-/ + +import VersoTutorial +import Tutorial + +open Verso.Genre.Manual +open Verso.Genre.Tutorial + +open Verso.Output.Html in +def plausible := {{ + + }} + +open Verso.Output.Html in +def staticJs := {{ + + + }} + +open Verso.Output.Html in +def staticCss := {{ + + + + + + + + }} + +open Verso.Doc Concrete in +def tutorials : Tutorials where + content := + (verso (.none) "foo" + ::::::: + abc + :::::::).toPart + + topics := #[ + { title := "Tactics" + description := #[blocks!"..."] + tutorials := #[%doc Tutorial.VCGen, %doc Tutorial.Grind.IndexMap] + } + + ] + +def main := + tutorialsMain tutorials (config := {remoteConfigFile := "tutorial-remotes.json", destination := "_tutorial-out"}) diff --git a/extended-examples/IndexMapGrind.lean b/extended-examples/IndexMapGrind.lean index 5639e86f4..5e972c6a7 100644 --- a/extended-examples/IndexMapGrind.lean +++ b/extended-examples/IndexMapGrind.lean @@ -1,4 +1,6 @@ +--ANCHOR: imports import Std.Data.HashMap +--ANCHOR_END: imports import IndexMapGrind.CheckMsgs open Std in @@ -19,9 +21,11 @@ example (m : HashMap Nat Nat) : (m.insert 1 2).size ≤ m.size + 1 := by get_elem_tactic -open Std + -- ANCHOR: IndexMap +open Std + structure IndexMap (α : Type u) (β : Type v) [BEq α] [Hashable α] where private indices : HashMap α Nat @@ -32,10 +36,13 @@ structure IndexMap keys[i]? = some a ↔ indices[a]? = some i := by grind -- ANCHOR_END: IndexMap + +-- ANCHOR: variables namespace IndexMap variable {α : Type u} {β : Type v} [BEq α] [Hashable α] variable {m : IndexMap α β} {a : α} {b : β} {i : Nat} +-- ANCHOR_END: variables -- ANCHOR: size @[inline] def size (m : IndexMap α β) : Nat := @@ -58,6 +65,7 @@ instance : EmptyCollection (IndexMap α β) where instance : Inhabited (IndexMap α β) where default := ∅ +-- ANCHOR: Membership @[inline] def contains (m : IndexMap α β) (a : α) : Bool := m.indices.contains a @@ -67,6 +75,7 @@ instance : Membership α (IndexMap α β) where instance {m : IndexMap α β} {a : α} : Decidable (a ∈ m) := inferInstanceAs (Decidable (a ∈ m.indices)) +-- ANCHOR_END: Membership discarding /-- @@ -110,6 +119,7 @@ stop discarding a ∈ m ↔ a ∈ m.indices := Iff.rfl -- ANCHOR_END: mem_indices_of_mem +-- ANCHOR: getFindIdx @[inline] def findIdx? (m : IndexMap α β) (a : α) : Option Nat := m.indices[a]? @@ -123,6 +133,7 @@ stop discarding @[inline] def getIdx (m : IndexMap α β) (i : Nat) (h : i < m.size := by get_elem_tactic) : β := m.values[i] +-- ANCHOR_END: getFindIdx -- ANCHOR: Lawfuls variable [LawfulBEq α] [LawfulHashable α] @@ -153,7 +164,9 @@ end grind_pattern getElem_indices_lt => m.indices[a] -- ANCHOR_END: getElem_indices_lt_pattern +-- ANCHOR: local_grind_size attribute [local grind] size +-- ANCHOR_END: local_grind_size -- ANCHOR: GetElem? instance : GetElem? (IndexMap α β) α β (fun m a => a ∈ m) where diff --git a/lakefile.lean b/lakefile.lean index 5fc864600..0ee2dafd7 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -8,7 +8,7 @@ import Lake open Lake DSL open System (FilePath) -require verso from git "https://github.com/leanprover/verso.git"@"main" +require verso from "../leandoc" -- git "https://github.com/leanprover/verso.git"@"tutorials" package "verso-manual" where -- building the C code cost much more than the optimizations save @@ -283,3 +283,10 @@ end ExplanationPreprocessing lean_exe "generate-manual" where needs := #[`@/figures, `@/subversoExtractMod] root := `Main + +@[default_target] +lean_lib Tutorial where + +@[default_target] +lean_exe "generate-tutorials" where + root := `TutorialMain From 9610596149c1de156f7c273d5f38dcd25857a010 Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Mon, 17 Nov 2025 19:08:26 +0100 Subject: [PATCH 02/45] wip: tutorials --- Main.lean | 2 +- lakefile.lean | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Main.lean b/Main.lean index 101e07047..b26a41c45 100644 --- a/Main.lean +++ b/Main.lean @@ -40,7 +40,7 @@ where extraFiles := [("static", "static")], extraHead := #[plausible, staticJs, staticCss], emitTeX := false, - emitHtmlSingle := .immediately, -- for proofreading + emitHtmlSingle := .no, logo := some "/static/lean_logo.svg", sourceLink := some "https://github.com/leanprover/reference-manual", issueLink := some "https://github.com/leanprover/reference-manual/issues", diff --git a/lakefile.lean b/lakefile.lean index 0ee2dafd7..0649ff7fe 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -290,3 +290,27 @@ lean_lib Tutorial where @[default_target] lean_exe "generate-tutorials" where root := `TutorialMain + +def lakeExe (prog : String) (args : Array String) : IO Unit := do + IO.println s!"Running {prog} with args {args}" + -- Using spawn and wait here causes the process to inherit stdio streams from Lake, so output is immediately visible + let code ← IO.Process.Child.wait <| (← IO.Process.spawn { cmd := "lake", args := #["--quiet", "exe", prog] ++ args }) + if code ≠ 0 then + let code' := code.toUInt8 + let code := if code' ≠ 0 then code' else 1 + IO.eprintln s!"Failed to run {prog} with args {args}" + IO.Process.exit code + + +script generate args := do + if !args.isEmpty then + IO.eprintln "No args expected" + return 1 + + lakeExe "generate-manual" #["--depth", "2", "--verbose", "--delay-html-multi", "multi.json"] + lakeExe "generate-tutorials" #["--verbose", "--delay", "tutorials.json"] + lakeExe "generate-manual" #["--resume-html-multi", "multi.json"] + lakeExe "generate-tutorials" #["--resume", "tutorials.json"] + + + return 0 From a2f6505c9673e2c1dea9467b0a011b8379cd6b5b Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Wed, 10 Dec 2025 10:46:49 +0100 Subject: [PATCH 03/45] bump --- lake-manifest.json | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lake-manifest.json b/lake-manifest.json index 680981665..1849d4690 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -1,21 +1,18 @@ {"version": "1.1.0", "packagesDir": ".lake/packages", "packages": - [{"url": "https://github.com/leanprover/verso.git", - "type": "git", - "subDir": null, + [{"type": "path", "scope": "", - "rev": "795bc01672b6f616aeaa24a7c7c4b32b20fe9c04", "name": "verso", "manifestFile": "lake-manifest.json", - "inputRev": "main", "inherited": false, + "dir": "../leandoc", "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/plausible", "type": "git", "subDir": null, "scope": "", - "rev": "ec4a54b5308c1f46b4b52a9c62fb67d193aa0972", + "rev": "74835c84b38e4070b8240a063c6417c767e551ae", "name": "plausible", "manifestFile": "lake-manifest.json", "inputRev": "main", @@ -35,7 +32,7 @@ "type": "git", "subDir": null, "scope": "", - "rev": "519b262cfc93634f904c9fb0992a45377ee49a9d", + "rev": "eb77622e97e942ba2cfe02f60637705fc2d9481b", "name": "subverso", "manifestFile": "lake-manifest.json", "inputRev": "main", From 2304fe9afb3f00ba50616bd98cf81301422f7ed3 Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Wed, 10 Dec 2025 16:38:12 +0100 Subject: [PATCH 04/45] Fix ambiguous tech terms --- Manual.lean | 2 +- Manual/BuildTools/Lake/CLI.lean | 2 +- Manual/Defs.lean | 2 +- Manual/Elaboration.lean | 6 +++--- Manual/Grind/EMatching.lean | 2 +- Manual/IO/Threads.lean | 6 +++--- Manual/Interaction.lean | 4 ++-- Manual/Language/Namespaces.lean | 2 +- Manual/NotationsMacros.lean | 2 +- Manual/NotationsMacros/SyntaxDef.lean | 2 +- Manual/SourceFiles.lean | 6 +++--- Manual/Terms.lean | 4 ++-- Manual/Types.lean | 2 +- lake-manifest.json | 7 +++++-- lakefile.lean | 2 +- 15 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Manual.lean b/Manual.lean index 88a3fbde1..0b2e30ac3 100644 --- a/Manual.lean +++ b/Manual.lean @@ -57,7 +57,7 @@ Along with many other parts of Lean, the tactic language is user-extensible, so Tactics are written in Lean itself, and can be used immediately upon definition; rebuilding the prover or loading external modules is not required. Lean is also a pure *functional programming language*, with features such as a run-time system based on reference counting that can efficiently work with packed array structures, multi-threading, and monadic {name}`IO`. -As befits a programming language, Lean is primarily implemented in itself, including the language server, build tool, {tech}[elaborator], and tactic system. +As befits a programming language, Lean is primarily implemented in itself, including the language server, build tool, {tech (key := "Lean elaborator") -normalize}[elaborator], and tactic system. This very book is written in [Verso](https://github.com/leanprover/verso), a documentation authoring tool written in Lean. Familiarity with Lean's programming features is valuable even for users whose primary interest is in writing proofs, because Lean programs are used to implement new tactics and proof automation. diff --git a/Manual/BuildTools/Lake/CLI.lean b/Manual/BuildTools/Lake/CLI.lean index 3a0ca23a4..6ccad489b 100644 --- a/Manual/BuildTools/Lake/CLI.lean +++ b/Manual/BuildTools/Lake/CLI.lean @@ -308,7 +308,7 @@ Single-character flags cannot be combined; `-HR` is not equivalent to `-H -R`. : {lakeOptDef flag}`--reconfigure` or {lakeOptDef flag}`-R` - Normally, the {tech}[package configuration] file is {tech (key := "elaborator")}[elaborated] when a package is first configured, with the result cached to a {tech}[`.olean` file] that is used for future invocations until the package configuration + Normally, the {tech}[package configuration] file is {tech (key := "elaborator") -normalize}[elaborated] when a package is first configured, with the result cached to a {tech}[`.olean` file] that is used for future invocations until the package configuration Providing this flag causes the configuration file to be re-elaborated. : {lakeOptDef flag}`--keep-toolchain` diff --git a/Manual/Defs.lean b/Manual/Defs.lean index 61a7addbc..47534097e 100644 --- a/Manual/Defs.lean +++ b/Manual/Defs.lean @@ -30,7 +30,7 @@ The following commands in Lean are definition-like: {TODO}[Render commands as th * {keyword}`theorem` * {keyword}`opaque` -All of these commands cause Lean to {tech (key := "elaborator")}[elaborate] a term based on a {tech}[signature]. +All of these commands cause Lean to {tech (key := "elaborator") -normalize}[elaborate] a term based on a {tech}[signature]. With the exception of {keywordOf Lean.Parser.Command.example}`example`, which discards the result, the resulting expression in Lean's core language is saved for future use in the environment. The {keywordOf Lean.Parser.Command.declaration}`instance` command is described in the {ref "instance-declarations"}[section on instance declarations]. diff --git a/Manual/Elaboration.lean b/Manual/Elaboration.lean index b4bafb8d5..9f88f13bf 100644 --- a/Manual/Elaboration.lean +++ b/Manual/Elaboration.lean @@ -37,7 +37,7 @@ Roughly speaking, Lean's processing of a source file can be divided into the fol : Elaboration - {deftech (key := "elaborator")}[Elaboration] is the process of transforming Lean's user-facing syntax into its core type theory. + {deftech (key := "Lean elaborator") -normalize}[Elaboration] is the process of transforming Lean's user-facing syntax into its core type theory. This core theory is much simpler, enabling the trusted kernel to be very small. Elaboration additionally produces metadata, such as proof states or the types of expressions, used for Lean's interactive features, storing them in a side table. @@ -103,7 +103,7 @@ tag := "macro-and-elab" %%% Having parsed a command, the next step is to elaborate it. -The precise meaning of {deftech}_elaboration_ depends on what is being elaborated: elaborating a command effects a change in the state of Lean, while elaborating a term results in a term in Lean's core type theory. +The precise meaning of {deftech -normalize}_elaboration_ depends on what is being elaborated: elaborating a command effects a change in the state of Lean, while elaborating a term results in a term in Lean's core type theory. Elaboration of both commands and terms may be recursive, both because of command combinators such as {keywordOf Lean.Parser.Command.in}`in` and because terms may contain other terms. Command and term elaboration have different capabilities. @@ -290,7 +290,7 @@ The compiler stores an intermediate representation in an environment extension. For straightforwardly structurally recursive functions, the translation will use the type's recursor. These functions tend to be relatively efficient when run in the kernel, their defining equations hold definitionally, and they are easy to understand. -Functions that use other patterns of recursion that cannot be captured by the type's recursor are translated using {deftech}[well-founded recursion], which is structural recursion on a proof that some {deftech}_measure_ decreases at each recursive call, or using {ref "partial-fixpoint"}[partial fixpoints], which logically capture at least part of a function's specification by appealing to domain-theoretic constructions. +Functions that use other patterns of recursion that cannot be captured by the type's recursor are translated using {tech}[well-founded recursion], which is structural recursion on a proof that some {tech}[measure] decreases at each recursive call, or using {ref "partial-fixpoint"}[partial fixpoints], which logically capture at least part of a function's specification by appealing to domain-theoretic constructions. Lean can automatically derive many of these termination proofs, but some require manual proofs. Well-founded recursion is more flexible, but the resulting functions are often slower to execute in the kernel due to the proof terms that show that a measure decreases, and their defining equations may hold only propositionally. To provide a uniform interface to functions defined via structural and well-founded recursion and to check its own correctness, the elaborator proves {deftech}[equational lemmas] that relate the function to its original definition. diff --git a/Manual/Grind/EMatching.lean b/Manual/Grind/EMatching.lean index b1ae2e31a..849eeec2c 100644 --- a/Manual/Grind/EMatching.lean +++ b/Manual/Grind/EMatching.lean @@ -31,7 +31,7 @@ Each fact added to the whiteboard by E-matching is referred to as an {deftech (k Annotating theorems for E-matching, thus adding them to the index, is essential for enabling {tactic}`grind` to make effective use of a library. In addition to user-specified theorems, {tactic}`grind` uses automatically generated equations for {keywordOf Lean.Parser.Term.match}`match`-expressions as E-matching theorems. -Behind the scenes, the {tech}[elaborator] generates auxiliary functions that implement pattern matches, along with equational theorems that specify their behavior. +Behind the scenes, the {tech (key := "Lean elaborator")}[elaborator] generates auxiliary functions that implement pattern matches, along with equational theorems that specify their behavior. Using these equations with E-matching enables {tactic}`grind` to reduce these instances of pattern matching. diff --git a/Manual/IO/Threads.lean b/Manual/IO/Threads.lean index e0429d443..3e726f8ae 100644 --- a/Manual/IO/Threads.lean +++ b/Manual/IO/Threads.lean @@ -30,7 +30,7 @@ variable {α : Type u} ``` {deftech}_Tasks_ are the fundamental primitive for writing multi-threaded code. -A {lean}`Task α` represents a computation that, at some point, will {deftech}_resolve_ to a value of type `α`; it may be computed on a separate thread. +A {lean}`Task α` represents a computation that, at some point, will {tech (key := "resolve promise")}_resolve_ to a value of type `α`; it may be computed on a separate thread. When a task has resolved, its value can be read; attempting to get the value of a task before it resolves causes the current thread to block until the task has resolved. Tasks are similar to promises in JavaScript, `JoinHandle` in Rust, and `Future` in Scala. @@ -52,7 +52,7 @@ Tasks may be explicitly cancelled using {name}`IO.cancel`. The Lean runtime maintains a thread pool for running tasks. The size of the thread pool is determined by the environment variable {envVar +def}`LEAN_NUM_THREADS` if it is set, or by the number of logical processors on the current machine otherwise. The size of the thread pool is not a hard limit; in certain situations it may be exceeded to avoid deadlocks. -By default, these threads are used to run tasks; each task has a {deftech}_priority_ ({name}`Task.Priority`), and higher-priority tasks take precedence over lower-priority tasks. +By default, these threads are used to run tasks; each task has a {deftech (key := "task priority")}_priority_ ({name}`Task.Priority`), and higher-priority tasks take precedence over lower-priority tasks. Tasks may also be assigned to dedicated threads by spawning them with a sufficiently high priority. {docstring Task (label := "type") +hideStructureConstructor +hideFields} @@ -162,7 +162,7 @@ Pure tasks are terminated automatically upon cancellation. # Promises Promises represent a value that will be supplied in the future. -Supplying the value is called {deftech (key := "resolve")}_resolving_ the promise. +Supplying the value is called {deftech (key := "resolve promise")}_resolving_ the promise. Once created, a promise can be stored in a data structure or passed around like any other value, and attempts to read from it will block until it is resolved. diff --git a/Manual/Interaction.lean b/Manual/Interaction.lean index 5eacac46b..2ee3d92ac 100644 --- a/Manual/Interaction.lean +++ b/Manual/Interaction.lean @@ -28,7 +28,7 @@ Lean's interactive features are based on a different paradigm. Rather than a separate command prompt outside of the program, Lean provides {tech}[commands] for accomplishing the same tasks in the context of a source file. By convention, commands that are intended for interactive use rather than as part of a durable code artifact are prefixed with {keyword}`#`. -Information from Lean commands is available in the {deftech}_message log_, which accumulates output from the {tech}[elaborator]. +Information from Lean commands is available in the {deftech}_message log_, which accumulates output from the {tech (key := "Lean elaborator")}[elaborator]. Each entry in the message log is associated with a specific source range and has a {deftech}_severity_. There are three severities: {lean (type := "Lean.MessageSeverity")}`information` is used for messages that do not indicate a problem, {lean (type := "Lean.MessageSeverity")}`warning` indicates a potential problem, and {lean (type := "Lean.MessageSeverity")}`error` indicates a definite problem. For interactive commands, results are typically returned as informational messages that are associated with the command's leading keyword. @@ -56,7 +56,7 @@ Use {keywordOf Lean.reduceCmd}`#reduce` to instead reduce terms using the reduct ::: -{keywordOf Lean.Parser.Command.eval}`#eval` always {tech (key := "elaborator")}[elaborates] and compiles the provided term. +{keywordOf Lean.Parser.Command.eval}`#eval` always {tech (key := "Lean elaborator")}[elaborates] and compiles the provided term. It then checks whether the term transitively depends on any uses of {lean}`sorry`, in which case evaluation is terminated unless the command was invoked as {keywordOf Lean.Parser.Command.eval}`#eval!`. This is because compiled code may rely on compile-time invariants (such as array lookups being in-bounds) that are ensured by proofs of suitable statements, and running code that contains incomplete proofs (or uses of {lean}`sorry` that “prove” incorrect statements) can cause Lean itself to crash. diff --git a/Manual/Language/Namespaces.lean b/Manual/Language/Namespaces.lean index 1831c8e0f..ddc50f86e 100644 --- a/Manual/Language/Namespaces.lean +++ b/Manual/Language/Namespaces.lean @@ -65,7 +65,7 @@ end Forest # Namespaces and Section Scopes -Every {tech}[section scope] has a {deftech}_current namespace_, which is determined by the {keywordOf Lean.Parser.Command.namespace}`namespace` command.{margin}[The {keywordOf Lean.Parser.Command.namespace}`namespace` command is described in the {ref "scope-commands"}[section on commands that introduce section scopes].] +Every {tech}[section scope] has a {tech}[current namespace], which is determined by the {keywordOf Lean.Parser.Command.namespace}`namespace` command.{margin}[The {keywordOf Lean.Parser.Command.namespace}`namespace` command is described in the {ref "scope-commands"}[section on commands that introduce section scopes].] Names that are declared within the section scope are added to the current namespace. If the declared name has more than one component, then its namespace is nested within the current namespace; the body of the declaration's current namespace is the nested namespace. Section scopes also include a set of {deftech}_opened namespaces_, which are namespaces whose contents are in scope without additional qualification. diff --git a/Manual/NotationsMacros.lean b/Manual/NotationsMacros.lean index 1d9ef44f2..b382745a6 100644 --- a/Manual/NotationsMacros.lean +++ b/Manual/NotationsMacros.lean @@ -62,7 +62,7 @@ They can be combined flexibly to achieve the necessary results: tag := "macros" %%% -{deftech}_Macros_ are transformations from {name Lean.Syntax}`Syntax` to {name Lean.Syntax}`Syntax` that occur during {tech (key := "elaborator")}[elaboration] and during {ref "tactic-macros"}[tactic execution]. +{deftech}_Macros_ are transformations from {name Lean.Syntax}`Syntax` to {name Lean.Syntax}`Syntax` that occur during {tech (key := "elaborator") -normalize}[elaboration] and during {ref "tactic-macros"}[tactic execution]. Replacing syntax with the result of transforming it with a macro is called {deftech}_macro expansion_. Multiple macros may be associated with a single {tech}[syntax kind], and they are attempted in order of definition. Macros are run in a {tech}[monad] that has access to some compile-time metadata and has the ability to either emit an error message or to delegate to subsequent macros, but the macro monad is much less powerful than the elaboration monads. diff --git a/Manual/NotationsMacros/SyntaxDef.lean b/Manual/NotationsMacros/SyntaxDef.lean index ac0254495..a49070594 100644 --- a/Manual/NotationsMacros/SyntaxDef.lean +++ b/Manual/NotationsMacros/SyntaxDef.lean @@ -718,7 +718,7 @@ A variant of list literals that requires double square brackets and allows a tra syntax "[[" term,*,? "]]" : term ``` -Adding a {deftech}[macro] that describes how to translate it into an ordinary list literal allows it to be used in tests. +Adding a {tech}[macro] that describes how to translate it into an ordinary list literal allows it to be used in tests. ```lean macro_rules | `(term|[[$e:term,*]]) => `([$e,*]) diff --git a/Manual/SourceFiles.lean b/Manual/SourceFiles.lean index 42bb62c3f..6b9a5425c 100644 --- a/Manual/SourceFiles.lean +++ b/Manual/SourceFiles.lean @@ -17,7 +17,7 @@ tag := "files" htmlSplit := .never %%% -The smallest unit of compilation in Lean is a single {deftech}[source file]. +The smallest unit of compilation in Lean is a single {tech}[source file]. Source files may import other source files based on their file names. In other words, the names and folder structures of files are significant in Lean code. @@ -642,7 +642,7 @@ tag := "meta-phase" Definitions in Lean result in both a representation in the type theory that is designed for formal reasoning and a compiled representation that is designed for execution. This compiled representation is used to generate machine code, but it can also be executed directly using an interpreter. -The code runs during {tech}[elaboration], such as {ref "tactics"}[tactics] or {ref "macros"}[macros], is the compiled form of definitions. +The code runs during {tech -normalize}[elaboration], such as {ref "tactics"}[tactics] or {ref "macros"}[macros], is the compiled form of definitions. If this compiled representation changes, then any code created by it may no longer be up to date, and it must be re-run. Because the compiler performs non-trivial optimizations, changes to any definition in the transitive dependency chain of a function could in principle invalidate its compiled representation. This means that metaprograms exported by modules induce a much stronger coupling than ordinary definitions. @@ -863,7 +863,7 @@ tag := "code-distribution" %%% -Lean modules are organized into {deftech}_packages_, which are units of code distribution. +Lean modules are organized into {tech}_packages_, which are units of code distribution. A {tech}[package] may contain multiple libraries or executables. Code in a package that is intended for use by other Lean packages is organized into {deftech (key:="library")}[libraries]. diff --git a/Manual/Terms.lean b/Manual/Terms.lean index ba511755a..9958d3e94 100644 --- a/Manual/Terms.lean +++ b/Manual/Terms.lean @@ -28,7 +28,7 @@ tag := "terms" {deftech}_Terms_ are the principal means of writing mathematics and programs in Lean. -The {tech}[elaborator] translates them to Lean's minimal core language, which is then checked by the kernel and compiled for execution. +The {deftech (key := "Lean elaborator")}[elaborator] translates them to Lean's minimal core language, which is then checked by the kernel and compiled for execution. The syntax of terms is {ref "syntax-ext"}[arbitrarily extensible]; this chapter documents the term syntax that Lean provides out-of-the-box. # Identifiers @@ -410,7 +410,7 @@ Implicit parameters come in three varieties: : Instance implicit parameters - Arguments for {deftech}_instance implicit_ parameters are found via {ref "instance-synth"}[type class synthesis]. + Arguments for {tech}_instance implicit_ parameters are found via {ref "instance-synth"}[type class synthesis]. Instance implicit parameters are written in square brackets (`[` and `]`). Unlike the other kinds of implicit parameter, instance implicit parameters that are written without a `:` specify the parameter's type rather than providing a name. Furthermore, only a single name is allowed. diff --git a/Manual/Types.lean b/Manual/Types.lean index 1c1b49ccb..6939b5f99 100644 --- a/Manual/Types.lean +++ b/Manual/Types.lean @@ -24,7 +24,7 @@ shortContextTitle := "Type System" %%% {deftech}_Terms_, also known as {deftech}_expressions_, are the fundamental units of meaning in Lean's core language. -They are produced from user-written syntax by the {tech}[elaborator]. +They are produced from user-written syntax by the {tech (key := "Lean elaborator")}[elaborator]. Lean's type system relates terms to their _types_, which are also themselves terms. Types can be thought of as denoting sets, while terms denote individual elements of these sets. A term is {deftech}_well-typed_ if it has a type under the rules of Lean's type theory. diff --git a/lake-manifest.json b/lake-manifest.json index 1849d4690..c079a12ad 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -1,12 +1,15 @@ {"version": "1.1.0", "packagesDir": ".lake/packages", "packages": - [{"type": "path", + [{"url": "https://github.com/leanprover/verso.git", + "type": "git", + "subDir": null, "scope": "", + "rev": "bcc253c7f2bfa288acfcb1617ffb3b1511bb98e2", "name": "verso", "manifestFile": "lake-manifest.json", + "inputRev": "tutorials", "inherited": false, - "dir": "../leandoc", "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/plausible", "type": "git", diff --git a/lakefile.lean b/lakefile.lean index 0649ff7fe..1760605d3 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -8,7 +8,7 @@ import Lake open Lake DSL open System (FilePath) -require verso from "../leandoc" -- git "https://github.com/leanprover/verso.git"@"tutorials" +require verso from git "https://github.com/leanprover/verso.git"@"tutorials" package "verso-manual" where -- building the C code cost much more than the optimizations save From 95c690a90cc2cc14b1ab72db58e6a97f8f3b7f7c Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Fri, 12 Dec 2025 11:55:55 +0100 Subject: [PATCH 05/45] progress --- TutorialMain.lean | 2 +- lake-manifest.json | 2 +- lakefile.lean | 16 +++++++++++----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/TutorialMain.lean b/TutorialMain.lean index e1eac3294..ffd388e65 100644 --- a/TutorialMain.lean +++ b/TutorialMain.lean @@ -49,4 +49,4 @@ def tutorials : Tutorials where ] def main := - tutorialsMain tutorials (config := {remoteConfigFile := "tutorial-remotes.json", destination := "_tutorial-out"}) + tutorialsMain tutorials (config := {destination := "_tutorial-out"}) diff --git a/lake-manifest.json b/lake-manifest.json index c079a12ad..c8c897ab5 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -5,7 +5,7 @@ "type": "git", "subDir": null, "scope": "", - "rev": "bcc253c7f2bfa288acfcb1617ffb3b1511bb98e2", + "rev": "26224a9fda56c34bff3a63ecc82e8ee754c5ac86", "name": "verso", "manifestFile": "lake-manifest.json", "inputRev": "tutorials", diff --git a/lakefile.lean b/lakefile.lean index 1760605d3..1369e9c50 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -306,11 +306,17 @@ script generate args := do if !args.isEmpty then IO.eprintln "No args expected" return 1 - - lakeExe "generate-manual" #["--depth", "2", "--verbose", "--delay-html-multi", "multi.json"] - lakeExe "generate-tutorials" #["--verbose", "--delay", "tutorials.json"] - lakeExe "generate-manual" #["--resume-html-multi", "multi.json"] - lakeExe "generate-tutorials" #["--resume", "tutorials.json"] + let outDir : FilePath := "_out" + let siteDir := outDir / "site" + + lakeExe "generate-manual" #["--depth", "2", "--verbose", "--delay-html-multi", "multi.json", "--remote-config", "verso-sources.json"] + lakeExe "generate-tutorials" #["--verbose", "--delay", "tutorials.json", "--remote-config", "verso-sources.json"] + lakeExe "generate-manual" #["--verbose", "--resume-html-multi", "multi.json", "--remote-config", "verso-sources.json"] + lakeExe "generate-tutorials" #["--verbose", "--resume", "tutorials.json", "--remote-config", "verso-sources.json"] + IO.FS.createDirAll siteDir + IO.FS.writeFile (siteDir / "index.html") (← IO.FS.readFile (("test-data" : FilePath) / "index.html")) + discard <| IO.Process.run { cmd := "cp", args := #["-r", "_out/html-multi", (siteDir / "reference").toString] } + discard <| IO.Process.run { cmd := "cp", args := #["-r", "_tutorial-out", (siteDir / "tutorials").toString] } return 0 From f0536aefe36a620562d0dbba6f60eeb59f0ec4a4 Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Fri, 12 Dec 2025 15:04:57 +0100 Subject: [PATCH 06/45] Working cross-links --- lake-manifest.json | 2 +- lakefile.lean | 14 ++++++++------ reference-remotes.json | 12 ++++++++++++ tutorial-remotes.json | 12 ++++++++++++ 4 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 reference-remotes.json create mode 100644 tutorial-remotes.json diff --git a/lake-manifest.json b/lake-manifest.json index c8c897ab5..716e9e338 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -5,7 +5,7 @@ "type": "git", "subDir": null, "scope": "", - "rev": "26224a9fda56c34bff3a63ecc82e8ee754c5ac86", + "rev": "5834aca69fd494cdc0d0dee4b12649ad321ef537", "name": "verso", "manifestFile": "lake-manifest.json", "inputRev": "tutorials", diff --git a/lakefile.lean b/lakefile.lean index 1369e9c50..3128d6a04 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -309,14 +309,16 @@ script generate args := do let outDir : FilePath := "_out" let siteDir := outDir / "site" - lakeExe "generate-manual" #["--depth", "2", "--verbose", "--delay-html-multi", "multi.json", "--remote-config", "verso-sources.json"] - lakeExe "generate-tutorials" #["--verbose", "--delay", "tutorials.json", "--remote-config", "verso-sources.json"] - lakeExe "generate-manual" #["--verbose", "--resume-html-multi", "multi.json", "--remote-config", "verso-sources.json"] - lakeExe "generate-tutorials" #["--verbose", "--resume", "tutorials.json", "--remote-config", "verso-sources.json"] + lakeExe "generate-manual" #["--depth", "2", "--verbose", "--delay-html-multi", "multi.json", "--remote-config", "reference-remotes.json"] + lakeExe "generate-tutorials" #["--verbose", "--delay", "tutorials.json", "--remote-config", "tutorial-remotes.json"] + lakeExe "generate-manual" #["--verbose", "--resume-html-multi", "multi.json", "--remote-config", "reference-remotes.json"] + lakeExe "generate-tutorials" #["--verbose", "--resume", "tutorials.json", "--remote-config", "tutorial-remotes.json"] IO.FS.createDirAll siteDir + IO.FS.createDirAll <| siteDir / "reference" + IO.FS.createDirAll <| siteDir / "tutorials" IO.FS.writeFile (siteDir / "index.html") (← IO.FS.readFile (("test-data" : FilePath) / "index.html")) - discard <| IO.Process.run { cmd := "cp", args := #["-r", "_out/html-multi", (siteDir / "reference").toString] } - discard <| IO.Process.run { cmd := "cp", args := #["-r", "_tutorial-out", (siteDir / "tutorials").toString] } + discard <| IO.Process.run { cmd := "cp", args := #["-r", "_out/html-multi/", (siteDir / "reference/").toString] } + discard <| IO.Process.run { cmd := "cp", args := #["-r", "_tutorial-out/", (siteDir / "tutorials/").toString] } return 0 diff --git a/reference-remotes.json b/reference-remotes.json new file mode 100644 index 000000000..cf78b67f8 --- /dev/null +++ b/reference-remotes.json @@ -0,0 +1,12 @@ +{ + "version": 0, + "sources": { + "tutorials": { + "root": "/tutorials", + "shortName": "tutorials", + "longName": "Lean Tutorials", + "updateFrequency": "always", + "sources": [{"local": "_tutorial-out/xref.json"}] + } + } +} diff --git a/tutorial-remotes.json b/tutorial-remotes.json new file mode 100644 index 000000000..414b63692 --- /dev/null +++ b/tutorial-remotes.json @@ -0,0 +1,12 @@ +{ + "version": 0, + "sources": { + "reference": { + "root": "/reference", + "updateFrequency": "always", + "shortName": "ref", + "longName": "Lean Language Reference", + "sources": [{"local": "_out/html-multi/xref.json"}, "default"] + } + } +} From f3356610983b28e746a40a13993913ece1e33320 Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Fri, 12 Dec 2025 16:54:09 +0100 Subject: [PATCH 07/45] Better deploy for PRs --- lake-manifest.json | 2 +- lakefile.lean | 22 ---- test-data/index.html | 103 ++++++++++++++++++ .../reference-remotes.json | 0 .../tutorial-remotes.json | 0 5 files changed, 104 insertions(+), 23 deletions(-) create mode 100644 test-data/index.html rename reference-remotes.json => test-data/reference-remotes.json (100%) rename tutorial-remotes.json => test-data/tutorial-remotes.json (100%) diff --git a/lake-manifest.json b/lake-manifest.json index 716e9e338..504ce4f19 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -5,7 +5,7 @@ "type": "git", "subDir": null, "scope": "", - "rev": "5834aca69fd494cdc0d0dee4b12649ad321ef537", + "rev": "e195c57713a2736fcaf7aa7caeed8d7c1e5f3050", "name": "verso", "manifestFile": "lake-manifest.json", "inputRev": "tutorials", diff --git a/lakefile.lean b/lakefile.lean index 3128d6a04..09bc01a4c 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -300,25 +300,3 @@ def lakeExe (prog : String) (args : Array String) : IO Unit := do let code := if code' ≠ 0 then code' else 1 IO.eprintln s!"Failed to run {prog} with args {args}" IO.Process.exit code - - -script generate args := do - if !args.isEmpty then - IO.eprintln "No args expected" - return 1 - let outDir : FilePath := "_out" - let siteDir := outDir / "site" - - lakeExe "generate-manual" #["--depth", "2", "--verbose", "--delay-html-multi", "multi.json", "--remote-config", "reference-remotes.json"] - lakeExe "generate-tutorials" #["--verbose", "--delay", "tutorials.json", "--remote-config", "tutorial-remotes.json"] - lakeExe "generate-manual" #["--verbose", "--resume-html-multi", "multi.json", "--remote-config", "reference-remotes.json"] - lakeExe "generate-tutorials" #["--verbose", "--resume", "tutorials.json", "--remote-config", "tutorial-remotes.json"] - IO.FS.createDirAll siteDir - IO.FS.createDirAll <| siteDir / "reference" - IO.FS.createDirAll <| siteDir / "tutorials" - IO.FS.writeFile (siteDir / "index.html") (← IO.FS.readFile (("test-data" : FilePath) / "index.html")) - discard <| IO.Process.run { cmd := "cp", args := #["-r", "_out/html-multi/", (siteDir / "reference/").toString] } - discard <| IO.Process.run { cmd := "cp", args := #["-r", "_tutorial-out/", (siteDir / "tutorials/").toString] } - - - return 0 diff --git a/test-data/index.html b/test-data/index.html new file mode 100644 index 000000000..f303f728c --- /dev/null +++ b/test-data/index.html @@ -0,0 +1,103 @@ + + + + + + Lean Manual + + + + +
+

Lean Manual Testing

+

Preview Version

+ +
+ + diff --git a/reference-remotes.json b/test-data/reference-remotes.json similarity index 100% rename from reference-remotes.json rename to test-data/reference-remotes.json diff --git a/tutorial-remotes.json b/test-data/tutorial-remotes.json similarity index 100% rename from tutorial-remotes.json rename to test-data/tutorial-remotes.json From 10fe3ab76da995255ee263403a25f57ad8e27c9e Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Mon, 15 Dec 2025 12:34:54 +0100 Subject: [PATCH 08/45] Missing header --- Tutorial.lean | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tutorial.lean b/Tutorial.lean index 9d2f83520..6f892783b 100644 --- a/Tutorial.lean +++ b/Tutorial.lean @@ -1,2 +1,7 @@ +/- +Copyright (c) 2025 Lean FRO LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Author: David Thrane Christiansen +-/ import Tutorial.VCGen import Tutorial.Grind.IndexMap From a458c50fd43d94439b20372a01d5f6543e9a4e7b Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Mon, 15 Dec 2025 14:09:58 +0100 Subject: [PATCH 09/45] Add sources --- verso-sources.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 verso-sources.json diff --git a/verso-sources.json b/verso-sources.json new file mode 100644 index 000000000..226486252 --- /dev/null +++ b/verso-sources.json @@ -0,0 +1,19 @@ +{ + "version": 0, + "sources": { + "reference": { + "root": "/reference", + "shortName": "reference", + "longName": "The Lean Language Reference", + "updateFrequency": "always", + "sources": [{"local": "_out/html-multi/xref.json"}] + }, + "tutorials": { + "root": "/tutorials", + "shortName": "tutorials", + "longName": "Lean Tutorials", + "updateFrequency": "always", + "sources": [{"local": "_tutorial-out/xref.json"}] + } + } +} From 0ab8f066048c2759afe9e8b4d2eb3fb9ddc3e7b8 Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Mon, 15 Dec 2025 17:28:28 +0100 Subject: [PATCH 10/45] Update scripts --- .github/workflows/ci.yml | 11 ++- ...production-remotes-reference.json.template | 12 +++ ...production-remotes-tutorials.json.template | 12 +++ generate.sh | 94 +++++++++++++++++++ 4 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 config/production-remotes-reference.json.template create mode 100644 config/production-remotes-tutorials.json.template create mode 100755 generate.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62be29763..1fc7dc533 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,12 +155,13 @@ jobs: - name: Generate HTML (non-release) if: github.event_name != 'release' run: | - lake --quiet exe generate-manual --depth 2 --with-word-count "words.txt" --verbose --without-html-single + ./generate.sh --mode preview - name: Generate HTML (release) if: github.event_name == 'release' run: | - lake --quiet exe generate-manual --depth 2 --with-word-count "words.txt" --verbose --without-html-single + VERSION=${GITHUB_REF#refs/tags/v} + ./generate.sh --mode production --version "$VERSION" - name: Check Manual Examples are Isolated run: | @@ -170,7 +171,7 @@ jobs: if: github.event_name == 'pull_request' id: generate run: | - lake --quiet exe generate-manual --depth 2 --verbose --draft --without-html-single --output "_out/draft" + ./generate.sh --mode preview --draft --output "_out/draft-site" - name: Report status to Lean PR if: always() && github.repository == 'leanprover/reference-manual' @@ -222,7 +223,7 @@ jobs: - name: Run LinkChecker (local links only) if: github.event_name == 'push' || github.event_name == 'pull_request' run: | - linkchecker --config=.linkchecker/linkcheckerrc './_out/html-multi/' + linkchecker --config=.linkchecker/linkcheckerrc './_out/site/' # This saved number is used by a workflow_run trigger. It # manages labels that indicate the status of the built HTML. @@ -282,7 +283,7 @@ jobs: uses: nwtgck/actions-netlify@v2.0 if: github.event_name == 'release' with: - publish-dir: _out/html-multi + publish-dir: _out/site/reference production-branch: main production-deploy: true github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/config/production-remotes-reference.json.template b/config/production-remotes-reference.json.template new file mode 100644 index 000000000..8aef1ccdf --- /dev/null +++ b/config/production-remotes-reference.json.template @@ -0,0 +1,12 @@ +{ + "version": 0, + "sources": { + "tutorials": { + "root": "/doc/tutorials/__VERSION__", + "shortName": "tutorials", + "longName": "Lean Tutorials", + "updateFrequency": "always", + "sources": [{"local": "_tutorial-out/xref.json"}] + } + } +} diff --git a/config/production-remotes-tutorials.json.template b/config/production-remotes-tutorials.json.template new file mode 100644 index 000000000..1f03aa81e --- /dev/null +++ b/config/production-remotes-tutorials.json.template @@ -0,0 +1,12 @@ +{ + "version": 0, + "sources": { + "reference": { + "root": "/doc/reference/__VERSION__", + "updateFrequency": "always", + "shortName": "ref", + "longName": "Lean Language Reference", + "sources": [{"local": "_out/html-multi/xref.json"}, "default"] + } + } +} diff --git a/generate.sh b/generate.sh new file mode 100755 index 000000000..66455762b --- /dev/null +++ b/generate.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Generate the manual and tutorials with support for preview and production modes + +# Default values +MODE="preview" +VERSION="" +OUTPUT="_out/site" +DRAFT_FLAG="" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --mode) + MODE="$2" + shift 2 + ;; + --version) + VERSION="$2" + shift 2 + ;; + --output) + OUTPUT="$2" + shift 2 + ;; + --draft) + DRAFT_FLAG="--draft" + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--mode preview|production] [--version VERSION] [--output DIR] [--draft]" + exit 1 + ;; + esac +done + +# Validate production mode requirements +if [ "$MODE" = "production" ]; then + if [ -z "$VERSION" ]; then + echo "Error: --version required for production mode" + exit 1 + fi + if [ -n "$DRAFT_FLAG" ]; then + echo "Error: --draft not supported in production mode" + exit 1 + fi +fi + +# Set up remote configs based on mode +if [ "$MODE" = "preview" ]; then + REF_REMOTE_CONFIG="test-data/reference-remotes.json" + TUT_REMOTE_CONFIG="test-data/tutorial-remotes.json" +else + # Production mode: generate temporary configs from templates + REF_REMOTE_CONFIG="_build/production-remotes-reference.json" + TUT_REMOTE_CONFIG="_build/production-remotes-tutorials.json" + + # Replace __VERSION__ in templates and write to temporary files + mkdir -p _build + sed "s/__VERSION__/$VERSION/g" config/production-remotes-reference.json.template > "$REF_REMOTE_CONFIG" + sed "s/__VERSION__/$VERSION/g" config/production-remotes-tutorials.json.template > "$TUT_REMOTE_CONFIG" + + echo "Generated production configs with version $VERSION" +fi + +# Generate the manual and tutorials +echo "Running generate-manual with args --depth 2 --verbose --delay-html-multi multi.json --remote-config $REF_REMOTE_CONFIG --with-word-count words.txt $DRAFT_FLAG" +lake --quiet exe generate-manual --depth 2 --verbose --delay-html-multi multi.json --remote-config "$REF_REMOTE_CONFIG" --with-word-count "words.txt" $DRAFT_FLAG + +echo "Running generate-tutorials with args --verbose --delay tutorials.json --remote-config $TUT_REMOTE_CONFIG $DRAFT_FLAG" +lake --quiet exe generate-tutorials --verbose --delay tutorials.json --remote-config "$TUT_REMOTE_CONFIG" $DRAFT_FLAG + +echo "Running generate-manual with args --verbose --resume-html-multi multi.json --remote-config $REF_REMOTE_CONFIG $DRAFT_FLAG" +lake --quiet exe generate-manual --verbose --resume-html-multi multi.json --remote-config "$REF_REMOTE_CONFIG" $DRAFT_FLAG + +echo "Running generate-tutorials with args --verbose --resume tutorials.json --remote-config $TUT_REMOTE_CONFIG $DRAFT_FLAG" +lake --quiet exe generate-tutorials --verbose --resume tutorials.json --remote-config "$TUT_REMOTE_CONFIG" $DRAFT_FLAG + +# Set up output directories +echo "Setting up output directories" +mkdir -p "$OUTPUT/reference" +mkdir -p "$OUTPUT/tutorials" + +# Copy files +echo "Copying files to site directory" +if [ "$MODE" = "preview" ]; then + cp test-data/index.html "$OUTPUT/index.html" +fi +cp -r _out/html-multi/ "$OUTPUT/reference/" +cp -r _tutorial-out/ "$OUTPUT/tutorials/" + +echo "Done! Site generated at $OUTPUT/" From 0d8f23e7ae25beb6853f0b3c6d8171f0f0068410 Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Mon, 15 Dec 2025 17:36:06 +0100 Subject: [PATCH 11/45] No draft flag for tutorials --- generate.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/generate.sh b/generate.sh index 66455762b..f44d272fa 100755 --- a/generate.sh +++ b/generate.sh @@ -69,14 +69,14 @@ fi echo "Running generate-manual with args --depth 2 --verbose --delay-html-multi multi.json --remote-config $REF_REMOTE_CONFIG --with-word-count words.txt $DRAFT_FLAG" lake --quiet exe generate-manual --depth 2 --verbose --delay-html-multi multi.json --remote-config "$REF_REMOTE_CONFIG" --with-word-count "words.txt" $DRAFT_FLAG -echo "Running generate-tutorials with args --verbose --delay tutorials.json --remote-config $TUT_REMOTE_CONFIG $DRAFT_FLAG" -lake --quiet exe generate-tutorials --verbose --delay tutorials.json --remote-config "$TUT_REMOTE_CONFIG" $DRAFT_FLAG +echo "Running generate-tutorials with args --verbose --delay tutorials.json --remote-config $TUT_REMOTE_CONFIG" +lake --quiet exe generate-tutorials --verbose --delay tutorials.json --remote-config "$TUT_REMOTE_CONFIG" echo "Running generate-manual with args --verbose --resume-html-multi multi.json --remote-config $REF_REMOTE_CONFIG $DRAFT_FLAG" lake --quiet exe generate-manual --verbose --resume-html-multi multi.json --remote-config "$REF_REMOTE_CONFIG" $DRAFT_FLAG -echo "Running generate-tutorials with args --verbose --resume tutorials.json --remote-config $TUT_REMOTE_CONFIG $DRAFT_FLAG" -lake --quiet exe generate-tutorials --verbose --resume tutorials.json --remote-config "$TUT_REMOTE_CONFIG" $DRAFT_FLAG +echo "Running generate-tutorials with args --verbose --resume tutorials.json --remote-config $TUT_REMOTE_CONFIG" +lake --quiet exe generate-tutorials --verbose --resume tutorials.json --remote-config "$TUT_REMOTE_CONFIG" # Set up output directories echo "Setting up output directories" From 378192bad49b94316a2910464a3d9f74115db51a Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Mon, 15 Dec 2025 17:53:45 +0100 Subject: [PATCH 12/45] Try checking links with server --- .github/workflows/ci.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1fc7dc533..56f8c612e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -223,7 +223,20 @@ jobs: - name: Run LinkChecker (local links only) if: github.event_name == 'push' || github.event_name == 'pull_request' run: | - linkchecker --config=.linkchecker/linkcheckerrc './_out/site/' + # Start a local web server in the background for link checking + cd _out/site + python3 -m http.server 8877 & + SERVER_PID=$! + cd ../.. + + # Wait for server to start + sleep 2 + + # Run linkchecker against the local server + linkchecker --config=.linkchecker/linkcheckerrc 'http://localhost:8877/' + + # Stop the server + kill $SERVER_PID || true # This saved number is used by a workflow_run trigger. It # manages labels that indicate the status of the built HTML. From 388bb04a421591cb47ba2c8ad712fb058618cb60 Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Mon, 15 Dec 2025 18:03:50 +0100 Subject: [PATCH 13/45] fixes --- .github/workflows/ci.yml | 3 ++- generate.sh | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56f8c612e..9d102e7f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -224,8 +224,9 @@ jobs: if: github.event_name == 'push' || github.event_name == 'pull_request' run: | # Start a local web server in the background for link checking + # Suppress server errors (connection resets from parallel requests) cd _out/site - python3 -m http.server 8877 & + python3 -m http.server 8877 2>/dev/null & SERVER_PID=$! cd ../.. diff --git a/generate.sh b/generate.sh index f44d272fa..7fa824583 100755 --- a/generate.sh +++ b/generate.sh @@ -88,7 +88,7 @@ echo "Copying files to site directory" if [ "$MODE" = "preview" ]; then cp test-data/index.html "$OUTPUT/index.html" fi -cp -r _out/html-multi/ "$OUTPUT/reference/" -cp -r _tutorial-out/ "$OUTPUT/tutorials/" +cp -r _out/html-multi/* "$OUTPUT/reference/" +cp -r _tutorial-out/* "$OUTPUT/tutorials/" echo "Done! Site generated at $OUTPUT/" From 2ce693522eaae8ab7a25c905cb965335909da381 Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Mon, 15 Dec 2025 22:33:53 +0100 Subject: [PATCH 14/45] bump --- lake-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lake-manifest.json b/lake-manifest.json index 2d8e667cf..7dc538ccd 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -5,7 +5,7 @@ "type": "git", "subDir": null, "scope": "", - "rev": "84fecb1face7e417cd3da73aba6a3e36907c7fa2", + "rev": "42dcc7abf0af1572c951bdfc3c152d79ebcb0f71", "name": "verso", "manifestFile": "lake-manifest.json", "inputRev": "tutorials", From c80268351af40d9ab969a269fdf0822ad81e3e9b Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Mon, 15 Dec 2025 22:55:11 +0100 Subject: [PATCH 15/45] bump --- lake-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lake-manifest.json b/lake-manifest.json index 7dc538ccd..e754a1e7c 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -5,7 +5,7 @@ "type": "git", "subDir": null, "scope": "", - "rev": "42dcc7abf0af1572c951bdfc3c152d79ebcb0f71", + "rev": "2af0a3aa6f193b4baa8d4b91ec5a51e2bc1e8813", "name": "verso", "manifestFile": "lake-manifest.json", "inputRev": "tutorials", From c30023da5dd7acdf528a1f3cb4675eaab12de1e8 Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Tue, 16 Dec 2025 05:53:41 +0100 Subject: [PATCH 16/45] trailing slashes --- test-data/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/index.html b/test-data/index.html index f303f728c..dde409989 100644 --- a/test-data/index.html +++ b/test-data/index.html @@ -95,8 +95,8 @@

Lean Manual Testing

Preview Version

From 3189ef1832c5c2fefdf9a1c900f9e726ffac051e Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Tue, 16 Dec 2025 06:16:50 +0100 Subject: [PATCH 17/45] See if more threads makes linkchecker faster against local server --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d102e7f8..64678c973 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -233,8 +233,8 @@ jobs: # Wait for server to start sleep 2 - # Run linkchecker against the local server - linkchecker --config=.linkchecker/linkcheckerrc 'http://localhost:8877/' + # Run linkchecker + linkchecker --config=.linkchecker/linkcheckerrc --threads=30 'http://localhost:8877/' # Stop the server kill $SERVER_PID || true From d507a197fb1cfbb1ad3a711b25a45f9bb676c636 Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Tue, 16 Dec 2025 06:35:20 +0100 Subject: [PATCH 18/45] Output locations and proselint --- generate.sh | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/generate.sh b/generate.sh index 7fa824583..87e1f432a 100755 --- a/generate.sh +++ b/generate.sh @@ -65,15 +65,24 @@ else echo "Generated production configs with version $VERSION" fi +# Set up output locations based on draft mode +if [ -n "$DRAFT_FLAG" ]; then + MANUAL_OUTPUT_FLAG="--output _out/draft" + REF_SOURCE="_out/draft/html-multi" +else + MANUAL_OUTPUT_FLAG="" + REF_SOURCE="_out/html-multi" +fi + # Generate the manual and tutorials -echo "Running generate-manual with args --depth 2 --verbose --delay-html-multi multi.json --remote-config $REF_REMOTE_CONFIG --with-word-count words.txt $DRAFT_FLAG" -lake --quiet exe generate-manual --depth 2 --verbose --delay-html-multi multi.json --remote-config "$REF_REMOTE_CONFIG" --with-word-count "words.txt" $DRAFT_FLAG +echo "Running generate-manual with args --depth 2 --verbose --delay-html-multi multi.json --remote-config $REF_REMOTE_CONFIG --with-word-count words.txt $MANUAL_OUTPUT_FLAG $DRAFT_FLAG" +lake --quiet exe generate-manual --depth 2 --verbose --delay-html-multi multi.json --remote-config "$REF_REMOTE_CONFIG" --with-word-count "words.txt" $MANUAL_OUTPUT_FLAG $DRAFT_FLAG echo "Running generate-tutorials with args --verbose --delay tutorials.json --remote-config $TUT_REMOTE_CONFIG" lake --quiet exe generate-tutorials --verbose --delay tutorials.json --remote-config "$TUT_REMOTE_CONFIG" -echo "Running generate-manual with args --verbose --resume-html-multi multi.json --remote-config $REF_REMOTE_CONFIG $DRAFT_FLAG" -lake --quiet exe generate-manual --verbose --resume-html-multi multi.json --remote-config "$REF_REMOTE_CONFIG" $DRAFT_FLAG +echo "Running generate-manual with args --verbose --resume-html-multi multi.json --remote-config $REF_REMOTE_CONFIG $MANUAL_OUTPUT_FLAG $DRAFT_FLAG" +lake --quiet exe generate-manual --verbose --resume-html-multi multi.json --remote-config "$REF_REMOTE_CONFIG" $MANUAL_OUTPUT_FLAG $DRAFT_FLAG echo "Running generate-tutorials with args --verbose --resume tutorials.json --remote-config $TUT_REMOTE_CONFIG" lake --quiet exe generate-tutorials --verbose --resume tutorials.json --remote-config "$TUT_REMOTE_CONFIG" @@ -88,7 +97,7 @@ echo "Copying files to site directory" if [ "$MODE" = "preview" ]; then cp test-data/index.html "$OUTPUT/index.html" fi -cp -r _out/html-multi/* "$OUTPUT/reference/" +cp -r "$REF_SOURCE"/* "$OUTPUT/reference/" cp -r _tutorial-out/* "$OUTPUT/tutorials/" echo "Done! Site generated at $OUTPUT/" From 160762f491ad52d547f14f4d5cd56c3426f50bdc Mon Sep 17 00:00:00 2001 From: David Thrane Christiansen Date: Tue, 16 Dec 2025 08:56:37 +0100 Subject: [PATCH 19/45] Better text --- test-data/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/index.html b/test-data/index.html index dde409989..20f8ce60c 100644 --- a/test-data/index.html +++ b/test-data/index.html @@ -92,7 +92,7 @@
-

Lean Manual Testing

+

Lean Reference Manual and Tutorials

Preview Version