diff --git a/DocGen4/Output/Class.lean b/DocGen4/Output/Class.lean
index 7f64cc0..00fd252 100644
--- a/DocGen4/Output/Class.lean
+++ b/DocGen4/Output/Class.lean
@@ -21,7 +21,7 @@ def classInstancesToHtml (instances : Array Name) : HtmlM Html := do
def classToHtml (i : ClassInfo) : HtmlM (Array Html) := do
- pure $ (←structureToHtml i.toStructureInfo).push (←classInstancesToHtml i.instances)
+ pure $ (←structureToHtml i.toStructureInfo)
end Output
end DocGen4
diff --git a/DocGen4/Output/ClassInductive.lean b/DocGen4/Output/ClassInductive.lean
index 6113e73..140651e 100644
--- a/DocGen4/Output/ClassInductive.lean
+++ b/DocGen4/Output/ClassInductive.lean
@@ -7,7 +7,7 @@ namespace DocGen4
namespace Output
def classInductiveToHtml (i : ClassInductiveInfo) : HtmlM (Array Html) := do
- pure $ (←inductiveToHtml i.toInductiveInfo).push (←classInstancesToHtml i.instances)
+ pure $ (←inductiveToHtml i.toInductiveInfo)
end Output
end DocGen4
diff --git a/DocGen4/Output/Definition.lean b/DocGen4/Output/Definition.lean
index 7261c37..1152b53 100644
--- a/DocGen4/Output/Definition.lean
+++ b/DocGen4/Output/Definition.lean
@@ -10,25 +10,19 @@ open Lean Widget
def equationToHtml (c : CodeWithInfos) : HtmlM Html := do
pure
[←infoFormatToHtml c]
-def equationsToHtml (i : DefinitionInfo) : HtmlM (Option Html) := do
+def equationsToHtml (i : DefinitionInfo) : HtmlM (Array Html) := do
if let some eqs := i.equations then
let equationsHtml ← eqs.mapM equationToHtml
- pure
+ pure #[
Equations
+ ]
else
- pure none
-
-def definitionToHtml (i : DefinitionInfo) : HtmlM (Array Html) := do
- let equationsHtml? ← equationsToHtml i
- match equationsHtml? with
- | some e => pure #[e]
- | none => pure #[]
-
+ pure #[]
end Output
end DocGen4
diff --git a/DocGen4/Output/DocString.lean b/DocGen4/Output/DocString.lean
index d318752..7c933b0 100644
--- a/DocGen4/Output/DocString.lean
+++ b/DocGen4/Output/DocString.lean
@@ -1,8 +1,9 @@
import CMark
import DocGen4.Output.Template
import Lean.Data.Parsec
+import Unicode.General.GeneralCategory
-open Lean
+open Lean Unicode
namespace DocGen4
namespace Output
@@ -11,26 +12,44 @@ open Xml Parser Lean Parsec
instance : Inhabited Element := ⟨"", Std.RBMap.empty, #[]⟩
+/-- Parse an array of Xml/Html document from String. -/
def manyDocument : Parsec (Array Element) := many (prolog *> element <* many Misc) <* eof
-partial def elementToPlainText : Xml.Element → String
-| (Element.Element name attrs contents) =>
- "".intercalate (contents.toList.map contentToPlainText)
+/--
+ Generate id for heading elements, with the following rules:
+
+ 1. Characters in `letter`, `number` and `symbol` unicode categories are preserved.
+ 2. Any sequences of Characters in `mark`, `punctuation`, `separator` and `other` categories are replaced by a single dash.
+ 3. Cases (upper and lower) are preserved.
+ 4. Xml/Html tags are ignored.
+-/
+partial def xmlGetHeadingId (el : Xml.Element) : String :=
+ elementToPlainText el |> replaceCharSeq unicodeToDrop "-"
where
+ elementToPlainText el := match el with
+ | (Element.Element name attrs contents) =>
+ "".intercalate (contents.toList.map contentToPlainText)
contentToPlainText c := match c with
| Content.Element el => elementToPlainText el
| Content.Comment _ => ""
| Content.Character s => s
+ replaceCharSeq pattern replacement s :=
+ s.split pattern
+ |>.filter (!·.isEmpty)
+ |> replacement.intercalate
+ unicodeToDrop char :=
+ charInGeneralCategory char GeneralCategory.mark ||
+ charInGeneralCategory char GeneralCategory.punctuation ||
+ charInGeneralCategory char GeneralCategory.separator ||
+ charInGeneralCategory char GeneralCategory.other
-def dropAllCharWhile (s : String) (p : Char → Bool) : String :=
- ⟨ s.data.filter p ⟩
+/--
+ This function try to find the given name, both globally and in current module.
-def textToIdAttribute (s : String) : String :=
- dropAllCharWhile s (λ c => (c.isAlphanum ∨ c = '-' ∨ c = ' '))
- |>.toLower
- |>.replace " " "-"
-
-def possibleNameToLink (s : String) : HtmlM (Option String) := do
+ For global search, a precise name is need. If the global search fails, the function
+ tries to find a local one that ends with the given search name.
+-/
+def nameToLink? (s : String) : HtmlM (Option String) := do
let res ← getResult
let name := s.splitOn "." |>.foldl (λ acc part => Name.mkStr acc part) Name.anonymous
-- with exactly the same name
@@ -46,10 +65,18 @@ def possibleNameToLink (s : String) : HtmlM (Option String) := do
| _ => pure none
| _ => pure none
-def extendRelativeLink (s : String) : HtmlM String := do
+/--
+ Extend links with following rules:
+
+ 1. if the link starts with `##`, a name search is used and will panic if not found
+ 2. if the link starts with `#`, it's treated as id link, no modification
+ 3. if the link starts with `http`, it's an absolute one, no modification
+ 4. otherwise it's a relative link, extend it with base url
+-/
+def extendLink (s : String) : HtmlM String := do
-- for intra doc links
if s.startsWith "##" then
- if let some link ← possibleNameToLink (s.drop 2) then
+ if let some link ← nameToLink? (s.drop 2) then
pure link
else
panic! s!"Cannot find {s.drop 2}, only full name and abbrev in current module is supported"
@@ -61,51 +88,70 @@ def extendRelativeLink (s : String) : HtmlM String := do
pure s
else pure ((←getRoot) ++ s)
-def possibleNameToAnchor (s : String) : HtmlM Content := do
- let link? ← possibleNameToLink s
- match link? with
- | some link =>
- let res ← getResult
- let attributes := Std.RBMap.empty.insert "href" link
- pure $ Content.Element $ Element.Element "a" attributes #[Content.Character s]
- | none => pure $ Content.Character s
+/-- Add attributes for heading. -/
+def addHeadingAttributes (el : Element) (modifyElement : Element → HtmlM Element) : HtmlM Element := do
+ match el with
+ | Element.Element name attrs contents => do
+ let id := xmlGetHeadingId el
+ let anchorAttributes := Std.RBMap.empty
+ |>.insert "class" "hover-link"
+ |>.insert "href" s!"#{id}"
+ let anchor := Element.Element "a" anchorAttributes #[Content.Character "#"]
+ let newAttrs := attrs
+ |>.insert "id" id
+ |>.insert "class" "markdown-heading"
+ let newContents := (←
+ contents.mapM (λ c => match c with
+ | Content.Element e => (modifyElement e).map (Content.Element)
+ | _ => pure c))
+ |>.push (Content.Character " ")
+ |>.push (Content.Element anchor)
+ pure ⟨ name, newAttrs, newContents⟩
+/-- Extend anchor links. -/
+def extendAnchor (el : Element) : HtmlM Element := do
+ match el with
+ | Element.Element name attrs contents =>
+ let root ← getRoot
+ let newAttrs ← match (attrs.find? "href").map extendLink with
+ | some href => href.map (attrs.insert "href")
+ | none => pure attrs
+ pure ⟨ name, newAttrs, contents⟩
+
+/-- Automatically add intra documentation link for inline code span. -/
+def autoLink (el : Element) : HtmlM Element := do
+ match el with
+ | Element.Element name attrs contents =>
+ let mut newContents := #[]
+ for c in contents do
+ match c with
+ | Content.Character s =>
+ newContents := newContents ++ (← s.splitOn.mapM linkify).intersperse (Content.Character " ")
+ | _ => newContents := newContents.push c
+ pure ⟨ name, attrs, newContents ⟩
+ where
+ linkify s := do
+ let link? ← nameToLink? s
+ match link? with
+ | some link =>
+ let res ← getResult
+ let attributes := Std.RBMap.empty.insert "href" link
+ pure $ Content.Element $ Element.Element "a" attributes #[Content.Character s]
+ | none => pure $ Content.Character s
+
+/-- Core function of modifying the cmark rendered docstring html. -/
partial def modifyElement (element : Element) (linkCode : Bool := true) : HtmlM Element :=
match element with
| el@(Element.Element name attrs contents) => do
-- add id and class to
if name = "h1" ∨ name = "h2" ∨ name = "h3" ∨ name = "h4" ∨ name = "h5" ∨ name = "h6" then
- let id := textToIdAttribute (elementToPlainText el)
- let anchorAttributes := Std.RBMap.empty
- |>.insert "class" "hover-link"
- |>.insert "href" s!"#{id}"
- let anchor := Element.Element "a" anchorAttributes #[Content.Character "#"]
- let newAttrs := attrs
- |>.insert "id" id
- |>.insert "class" "markdown-heading"
- let newContents := (←
- contents.mapM (λ c => match c with
- | Content.Element e => (modifyElement e).map (Content.Element)
- | _ => pure c))
- |>.push (Content.Character " ")
- |>.push (Content.Element anchor)
- pure ⟨ name, newAttrs, newContents⟩
+ addHeadingAttributes el modifyElement
-- extend relative href for
else if name = "a" then
- let root ← getRoot
- let newAttrs ← match (attrs.find? "href").map (extendRelativeLink) with
- | some href => href.map (attrs.insert "href")
- | none => pure attrs
- pure ⟨ name, newAttrs, contents⟩
+ extendAnchor el
-- auto link for inline
- else if name = "code" ∧ linkCode then
- let mut newContents := #[]
- for c in contents do
- match c with
- | Content.Character s =>
- newContents := newContents ++ (← s.splitOn.mapM possibleNameToAnchor).intersperse (Content.Character " ")
- | _ => newContents := newContents.push c
- pure ⟨ name, attrs, newContents⟩
+ else if name = "code" ∧ linkCode = true then
+ autoLink el
-- recursively modify
else
let newContents ← contents.mapM λ c => match c with
@@ -114,6 +160,7 @@ partial def modifyElement (element : Element) (linkCode : Bool := true) : HtmlM
| _ => pure c
pure ⟨ name, attrs, newContents ⟩
+/-- Convert docstring to Html. -/
def docStringToHtml (s : String) : HtmlM (Array Html) := do
let rendered := CMark.renderHtml s
match manyDocument rendered.mkIterator with
diff --git a/DocGen4/Output/Instance.lean b/DocGen4/Output/Instance.lean
index bad10f1..78f098d 100644
--- a/DocGen4/Output/Instance.lean
+++ b/DocGen4/Output/Instance.lean
@@ -4,7 +4,5 @@ import DocGen4.Output.Definition
namespace DocGen4
namespace Output
-def instanceToHtml (i : InstanceInfo) : HtmlM (Array Html) := definitionToHtml i
-
end Output
end DocGen4
diff --git a/DocGen4/Output/Module.lean b/DocGen4/Output/Module.lean
index 4c76230..942051b 100644
--- a/DocGen4/Output/Module.lean
+++ b/DocGen4/Output/Module.lean
@@ -74,17 +74,23 @@ def docInfoHeader (doc : DocInfo) : HtmlM Html := do
pure [nodes]
def docInfoToHtml (module : Name) (doc : DocInfo) : HtmlM Html := do
+ -- basic info like headers, types, structure fields, etc.
let docInfoHtml ← match doc with
| DocInfo.inductiveInfo i => inductiveToHtml i
| DocInfo.structureInfo i => structureToHtml i
| DocInfo.classInfo i => classToHtml i
- | DocInfo.definitionInfo i => definitionToHtml i
- | DocInfo.instanceInfo i => instanceToHtml i
| DocInfo.classInductiveInfo i => classInductiveToHtml i
| i => pure #[]
+ -- rendered doc stirng
let docStringHtml ← match doc.getDocString with
| some s => docStringToHtml s
| none => pure #[]
+ -- extra information like equations and instances
+ let extraInfoHtml ← match doc with
+ | DocInfo.classInfo i => pure #[←classInstancesToHtml i.instances]
+ | DocInfo.definitionInfo i => equationsToHtml i
+ | DocInfo.classInductiveInfo i => pure #[←classInstancesToHtml i.instances]
+ | i => pure #[]
let attrs := doc.getAttrs
let attrsHtml :=
if attrs.size > 0 then
@@ -101,8 +107,9 @@ def docInfoToHtml (module : Name) (doc : DocInfo) : HtmlM Html := do
[attrsHtml]
{←docInfoHeader doc}
- [docStringHtml]
[docInfoHtml]
+ [docStringHtml]
+ [extraInfoHtml]
diff --git a/lakefile.lean b/lakefile.lean
index ba2f386..d0af360 100644
--- a/lakefile.lean
+++ b/lakefile.lean
@@ -7,7 +7,11 @@ package «doc-gen4» {
dependencies := #[
{
name := `CMark
- src := Source.git "https://github.com/xubaiw/CMark.lean" "0c59e4f"
+ src := Source.git "https://github.com/xubaiw/CMark.lean" "0c59e4fa0f8864502dc9e661d437be842d29d708"
+ },
+ {
+ name := `Unicode
+ src := Source.git "https://github.com/xubaiw/Unicode.lean" "88ad4aacfcc7ab941a22c54de3e4fef0809cda87"
}
]
}