Skip to content

Commit

Permalink
Api docs generate as md (#619)
Browse files Browse the repository at this point in the history
* Initial incomplete and most likely not working impl

* Lines

* EOL

* Trying to plumb in md

* More extension hacking

* Multiple tweaks
* fixed extensions
* supporting unordered list in md formatter

* Multiple tweaks
* basic table and image support for md formatting
* rendering entities in GenerateMarkdown

* Comment on copy/paste

* Fixed rendering of parent module

* Made table generation working

* Multiple tweaks
* added basic members rendering
* added secaping pipeline character in table rendering

* Multiple tweaks:
* better member rendering
* support for multiple paragraphs in table cells

* Multiple tweaks:
* only write paragraphs if there is content
* render empty cells in table as nbsp to keep table structure

* Member rendering improvements

* Put remarks in the right place

* Fixed choosing API docs format
* also added missing Md cases for Literate

* Extracting common logic #1

* Removed colon

* Fixed the first interface not rendering bug
Cleaned up redundant comments

* Multiple fixes
* finished md implementation
* more api model categorisation refactorisation

* Renamed files

* Multiple fixes
* made the user responsible for escaping pipeline chars
* replaced nbsp by  normal spaces

* Using empty map for links

* Clean up

* Encode pipeline in member's name

* Tests WIP

* Fixed test

* Fixed most of the tests

* Fixed another test and removed excessive spaces

* Removed redundant case

* Converted interpolation to sprintf

* Removed nameof usage

* Multiple tweaks
* disabled a test for what I think might not be an issue
* fixed a failing test

Co-authored-by: queil <[email protected]>
Co-authored-by: Phillip Carter <[email protected]>
Co-authored-by: Don Syme <[email protected]>
  • Loading branch information
4 people authored Feb 11, 2021
1 parent 9148ad7 commit 53f0772
Show file tree
Hide file tree
Showing 19 changed files with 1,043 additions and 388 deletions.
Empty file added docs/_template.md
Empty file.
62 changes: 56 additions & 6 deletions src/FSharp.Formatting.ApiDocs/ApiDocs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,19 @@ type ApiDocs =
/// <param name="urlRangeHighlight">A function that can be used to override the default way of generating GitHub links</param>
/// <param name="strict">Fail if any assembly is missing XML docs or can't be resolved</param>
///
static member GenerateModel(inputs: ApiDocInput list, collectionName, substitutions, ?qualify, ?libDirs, ?otherFlags, ?root, ?urlRangeHighlight, ?strict) =
static member GenerateModel(inputs: ApiDocInput list, collectionName, substitutions, ?qualify, ?libDirs, ?otherFlags, ?root, ?urlRangeHighlight, ?strict, ?extension) =
let root = defaultArg root "/"
let qualify = defaultArg qualify false
let strict = defaultArg strict false
let extensions = defaultArg extension { InFile = ".html"; InUrl = ".html"}
ApiDocModel.Generate(inputs, collectionName=collectionName,
libDirs=libDirs, qualify=qualify,
otherFlags=otherFlags,
urlRangeHighlight=urlRangeHighlight, root=root,
substitutions=substitutions,
strict=strict)
strict=strict,
extensions=extensions
)

/// <summary>
/// Generates the search index from the given documentation model
Expand All @@ -51,12 +54,13 @@ type ApiDocs =
let root = defaultArg root "/"
let qualify = defaultArg qualify false
let strict = defaultArg strict false
let extensions = { InFile = ".html"; InUrl = ".html"}
let model =
ApiDocModel.Generate(inputs, collectionName=collectionName,
libDirs=libDirs, qualify=qualify, otherFlags=otherFlags,
urlRangeHighlight=urlRangeHighlight, root=root, substitutions=substitutions, strict=strict)
urlRangeHighlight=urlRangeHighlight, root=root, substitutions=substitutions, strict=strict, extensions=extensions)
let renderer = GenerateHtml.HtmlRender(model)
let index = GenerateSearchIndex.searchIndexEntriesForModel(model)
let index = GenerateSearchIndex.searchIndexEntriesForModel model
renderer.GlobalSubstitutions, index, (fun globalParameters ->
renderer.Generate(output, template, collectionName, globalParameters))

Expand All @@ -79,11 +83,57 @@ type ApiDocs =
let root = defaultArg root "/"
let qualify = defaultArg qualify false
let strict = defaultArg strict false
let extensions = { InFile = ".html"; InUrl = ".html"}
let model =
ApiDocModel.Generate(inputs, collectionName=collectionName,
libDirs=libDirs, qualify=qualify, otherFlags=otherFlags,
urlRangeHighlight=urlRangeHighlight, root=root, substitutions=substitutions, strict=strict)
urlRangeHighlight=urlRangeHighlight, root=root, substitutions=substitutions, strict=strict, extensions=extensions)
let renderer = GenerateHtml.HtmlRender(model)
let index = GenerateSearchIndex.searchIndexEntriesForModel(model)
let index = GenerateSearchIndex.searchIndexEntriesForModel model
renderer.Generate(output, template, collectionName, renderer.GlobalSubstitutions)
model,index

/// Like GenerateMarkdown but allows for intermediate phase to insert other global substitutions
/// and combine search index
static member GenerateMarkdownPhased(inputs, output, collectionName, substitutions, ?template, ?root, ?qualify, ?libDirs, ?otherFlags, ?urlRangeHighlight, ?strict) =
let root = defaultArg root "/"
let qualify = defaultArg qualify false
let strict = defaultArg strict false
let extensions = { InFile = ".md"; InUrl = ""}
let model =
ApiDocModel.Generate(inputs, collectionName=collectionName,
libDirs=libDirs, qualify=qualify, otherFlags=otherFlags,
urlRangeHighlight=urlRangeHighlight, root=root, substitutions=substitutions, strict=strict, extensions=extensions)
let renderer = GenerateMarkdown.MarkdownRender(model)
let index = GenerateSearchIndex.searchIndexEntriesForModel model
renderer.GlobalSubstitutions, index, (fun globalParameters ->
renderer.Generate(output, template, collectionName, globalParameters))

/// <summary>
/// Generates default Markdown pages for the assemblies specified by the `inputs` parameter
/// </summary>
///
/// <param name="inputs">the components to generate documentation for</param>
/// <param name="output">the output directory</param>
/// <param name="collectionName">the overall collection name</param>
/// <param name="template">the template to use for each documentation page</param>
/// <param name="root">The root url of the generated documentation within the website</param>
/// <param name="qualify">qualify the output set by collection name, e.g. `reference/FSharp.Core/...`</param>
/// <param name="libDirs">Use this to specify additional paths where referenced DLL files can be found when formatting code snippets inside Markdown comments</param>
/// <param name="otherFlags">Additional flags that are passed to the F# compiler to specify references explicitly etc.</param>
/// <param name="urlRangeHighlight">A function that can be used to override the default way of generating GitHub links</param>
///
static member GenerateMarkdown(inputs, output, collectionName, substitutions, ?template, ?root, ?qualify, ?libDirs, ?otherFlags, ?urlRangeHighlight, ?strict) =
let root = defaultArg root "/"
let qualify = defaultArg qualify false
let strict = defaultArg strict false
let extensions = { InFile = ".md"; InUrl = ""}

let model =
ApiDocModel.Generate(inputs, collectionName=collectionName,
libDirs=libDirs, qualify=qualify, otherFlags=otherFlags,
urlRangeHighlight=urlRangeHighlight, root=root, substitutions=substitutions, strict=strict, extensions=extensions)
let renderer = GenerateMarkdown.MarkdownRender(model)
let index = GenerateSearchIndex.searchIndexEntriesForModel model
renderer.Generate(output, template, collectionName, renderer.GlobalSubstitutions)
model,index
77 changes: 77 additions & 0 deletions src/FSharp.Formatting.ApiDocs/Categorise.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
[<RequireQualifiedAccess>]
module internal FSharp.Formatting.ApiDocs.Categorise

open System

// Honour the CategoryIndex to put the categories in the right order
let private getSortedCategories xs exclude category categoryIndex =
xs
|> List.filter (exclude >> not)
|> List.groupBy (category)
|> List.map (fun (cat, xs) -> (cat, xs, xs |> List.minBy (categoryIndex)))
|> List.sortBy (fun (cat, _xs, x) -> categoryIndex x, cat)
|> List.map (fun (cat, xs, _x) -> cat, xs)

// Group all members by their category
let getMembersByCategory (members:ApiDocMember list) =
getSortedCategories members (fun m -> m.Exclude) (fun m -> m.Category) (fun m -> m.CategoryIndex)
|> List.mapi (fun i (key, elems) ->
let elems = elems |> List.sortBy (fun m -> m.Name)
let name = if String.IsNullOrEmpty(key) then "Other module members" else key
(i, elems, name))

let entities (nsIndex: int, ns: ApiDocNamespace, suppress) =
let entities = ns.Entities

let categories =
getSortedCategories entities (fun (m:ApiDocEntity) -> m.Exclude) (fun (m:ApiDocEntity) -> m.Category) (fun (m:ApiDocEntity) -> m.CategoryIndex)

let allByCategory =
[ for (catIndex, (categoryName, (categoryEntities: ApiDocEntity list))) in Seq.indexed categories do
let categoryName = (if String.IsNullOrEmpty(categoryName) then "Other namespace members" else categoryName)
let index = String.Format("{0}_{1}", nsIndex, catIndex)
let categoryEntities =
// When calculating list-of-namespaces suppress some entries
// Some bespoke hacks to make FSharp.Core docs look ok.
//
// TODO: use <exclude /> to do these, or work out if there's a better way
if suppress then
categoryEntities

// Remove FSharp.Data.UnitSystems.SI from the list-of-namespaces
// display - it's just so rarely used, has long names and dominates the docs.
//
// See https://github.com/fsharp/fsharp-core-docs/issues/57, we may rethink this
|> List.filter (fun e -> not (e.Symbol.Namespace = Some "Microsoft.FSharp.Data.UnitSystems.SI.UnitSymbols"))
|> List.filter (fun e -> not (e.Symbol.Namespace = Some "Microsoft.FSharp.Data.UnitSystems.SI.UnitNames"))
// Don't show 'AnonymousObject' in list-of-namespaces navigation
|> List.filter (fun e -> not (e.Symbol.Namespace = Some "Microsoft.FSharp.Linq.RuntimeHelpers" && e.Symbol.DisplayName = "AnonymousObject"))
// Don't show 'FSharp.Linq.QueryRunExtensions' in list-of-namespaces navigation
|> List.filter (fun e -> not (e.Symbol.Namespace = Some "Microsoft.FSharp.Linq.QueryRunExtensions" && e.Symbol.DisplayName = "LowPriority"))
|> List.filter (fun e -> not (e.Symbol.Namespace = Some "Microsoft.FSharp.Linq.QueryRunExtensions" && e.Symbol.DisplayName = "HighPriority"))
else
categoryEntities

// We currently suppress all obsolete entries all the time
let categoryEntities =
categoryEntities
|> List.filter (fun e -> not e.IsObsolete)

let categoryEntities =
categoryEntities
|> List.sortBy (fun e ->
(e.Symbol.DisplayName.ToLowerInvariant(), e.Symbol.GenericParameters.Count,
e.Name, (if e.IsTypeDefinition then e.UrlBaseName else "ZZZ")))

if categoryEntities.Length > 0 then
yield {| CategoryName = categoryName
CategoryIndex = index
CategoryEntites = categoryEntities |} ]

allByCategory

let model apiDocModel =
[ for (nsIndex, ns) in Seq.indexed apiDocModel.Collection.Namespaces do
let allByCategory = entities (nsIndex, ns, true)
if allByCategory.Length > 0 then
allByCategory, ns ]
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
<Link>Common\StringParsing.fs</Link>
</Compile>
<Compile Include="GenerateModel.fs" />
<Compile Include="Categorise.fs" />
<Compile Include="GenerateHtml.fs" />
<Compile Include="GenerateMarkdown.fs" />
<Compile Include="GenerateSearchIndex.fs" />
<Compile Include="ApiDocs.fs" />
</ItemGroup>
Expand Down
Loading

0 comments on commit 53f0772

Please sign in to comment.