diff --git a/docs/_template.md b/docs/_template.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/FSharp.Formatting.ApiDocs/ApiDocs.fs b/src/FSharp.Formatting.ApiDocs/ApiDocs.fs
index 31d38c9c2..1d3220815 100644
--- a/src/FSharp.Formatting.ApiDocs/ApiDocs.fs
+++ b/src/FSharp.Formatting.ApiDocs/ApiDocs.fs
@@ -26,16 +26,19 @@ type ApiDocs =
/// A function that can be used to override the default way of generating GitHub links
/// Fail if any assembly is missing XML docs or can't be resolved
///
- 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
+ )
///
/// Generates the search index from the given documentation model
@@ -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))
@@ -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))
+
+ ///
+ /// Generates default Markdown pages for the assemblies specified by the `inputs` parameter
+ ///
+ ///
+ /// the components to generate documentation for
+ /// the output directory
+ /// the overall collection name
+ /// the template to use for each documentation page
+ /// The root url of the generated documentation within the website
+ /// qualify the output set by collection name, e.g. `reference/FSharp.Core/...`
+ /// Use this to specify additional paths where referenced DLL files can be found when formatting code snippets inside Markdown comments
+ /// Additional flags that are passed to the F# compiler to specify references explicitly etc.
+ /// A function that can be used to override the default way of generating GitHub links
+ ///
+ 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
diff --git a/src/FSharp.Formatting.ApiDocs/Categorise.fs b/src/FSharp.Formatting.ApiDocs/Categorise.fs
new file mode 100644
index 000000000..c43a490e5
--- /dev/null
+++ b/src/FSharp.Formatting.ApiDocs/Categorise.fs
@@ -0,0 +1,77 @@
+[]
+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 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 ]
diff --git a/src/FSharp.Formatting.ApiDocs/FSharp.Formatting.ApiDocs.fsproj b/src/FSharp.Formatting.ApiDocs/FSharp.Formatting.ApiDocs.fsproj
index 0b0c71ba2..1a3fc323a 100644
--- a/src/FSharp.Formatting.ApiDocs/FSharp.Formatting.ApiDocs.fsproj
+++ b/src/FSharp.Formatting.ApiDocs/FSharp.Formatting.ApiDocs.fsproj
@@ -15,7 +15,9 @@
Common\StringParsing.fs
+
+
diff --git a/src/FSharp.Formatting.ApiDocs/GenerateHtml.fs b/src/FSharp.Formatting.ApiDocs/GenerateHtml.fs
index 950f8d4cc..355592709 100644
--- a/src/FSharp.Formatting.ApiDocs/GenerateHtml.fs
+++ b/src/FSharp.Formatting.ApiDocs/GenerateHtml.fs
@@ -16,6 +16,7 @@ type HtmlRender(model: ApiDocModel) =
let root = model.Root
let collectionName = model.Collection.CollectionName
let qualify = model.Qualify
+
//let obsoleteMessage msg =
// div [Class "alert alert-warning"] [
// strong [] [!!"NOTE:"]
@@ -200,7 +201,7 @@ type HtmlRender(model: ApiDocModel) =
let nmWithSiffix = if multi then (if e.IsTypeDefinition then nm + " (Type)" else nm + " (Module)") else nm
// This adds #EntityName anchor. These may currently be ambiguous
- p [] [a [Name nm] [a [Href (e.Url(root, collectionName, qualify))] [!!nmWithSiffix]]]
+ p [] [a [Name nm] [a [Href (e.Url(root, collectionName, qualify, model.FileExtensions.InUrl))] [!!nmWithSiffix]]]
]
td [Class "fsdocs-xmldoc" ] [
p [] [yield! sourceLink e.SourceLocation
@@ -212,27 +213,11 @@ type HtmlRender(model: ApiDocModel) =
]
]
- // Honour the CategoryIndex to put the categories in the right order
- let getSortedCategories xs exclude category categoryIndex =
- xs
- |> List.filter (fun x -> not (exclude x))
- |> List.groupBy (fun x -> category x)
- |> List.map (fun (cat, xs) -> (cat, xs, xs |> List.minBy (fun x -> categoryIndex x)))
- |> List.sortBy (fun (cat, _xs, x) -> categoryIndex x, cat)
- |> List.map (fun (cat, xs, _x) -> cat, xs)
-
let entityContent (info: ApiDocEntityInfo) =
// Get all the members & comment for the type
let entity = info.Entity
let members = entity.AllMembers |> List.filter (fun e -> not e.IsObsolete)
-
- // Group all members by their category
- let byCategory =
- 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 byCategory = members |> Categorise.getMembersByCategory
let usageName =
match info.ParentModule with
@@ -240,13 +225,13 @@ type HtmlRender(model: ApiDocModel) =
| _ -> entity.Name
[ h1 [] [!! (usageName + (if entity.IsTypeDefinition then " Type" else " Module")) ]
- p [] [!! "Namespace: "; a [Href (info.Namespace.Url(root, collectionName, qualify))] [!!info.Namespace .Name]]
+ p [] [!! "Namespace: "; a [Href (info.Namespace.Url(root, collectionName, qualify, model.FileExtensions.InUrl))] [!!info.Namespace .Name]]
p [] [!! ("Assembly: " + entity.Assembly.Name + ".dll")]
match info.ParentModule with
| None -> ()
| Some parentModule ->
- span [] [!! ("Parent Module: "); a [Href (parentModule.Url(root, collectionName, qualify))] [!! parentModule.Name ]]
+ span [] [!! ("Parent Module: "); a [Href (parentModule.Url(root, collectionName, qualify, model.FileExtensions.InUrl))] [!! parentModule.Name ]]
match entity.AbbreviatedType with
@@ -345,60 +330,9 @@ type HtmlRender(model: ApiDocModel) =
div [] (renderMembers "Instance members" "Instance member" (ms |> List.filter (fun m -> m.Kind = ApiDocMemberKind.InstanceMember)))
div [] (renderMembers "Static members" "Static member" (ms |> List.filter (fun m -> m.Kind = ApiDocMemberKind.StaticMember)))
]
-
-
- let categoriseEntities (nsIndex: int, ns: ApiDocNamespace, suppress) =
- let entities = ns.Entities
-
- let categories =
- getSortedCategories entities (fun m -> m.Exclude) (fun m -> m.Category) (fun m -> m.CategoryIndex)
-
- let allByCategory =
- [ for (catIndex, (categoryName, categoryEntities)) 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 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 namespaceContent (nsIndex, ns: ApiDocNamespace) =
- let allByCategory = categoriseEntities (nsIndex, ns, false)
+ let allByCategory = Categorise.entities (nsIndex, ns, false)
[ if allByCategory.Length > 0 then
h2 [Id ns.UrlHash] [!! (ns.Name + " Namespace") ]
@@ -432,15 +366,11 @@ type HtmlRender(model: ApiDocModel) =
// For non-FSharp.Core we only show one link "API Reference" in the nav menu
if otherDocs && nav && model.Collection.CollectionName <> "FSharp.Core" then
li [Class "nav-header"] [!! "API Reference"]
- li [ Class "nav-item" ] [a [Class "nav-link"; Href (model.IndexFileUrl(root, collectionName, qualify))] [!! "All Namespaces" ] ]
+ li [ Class "nav-item" ] [a [Class "nav-link"; Href (model.IndexFileUrl(root, collectionName, qualify, model.FileExtensions.InUrl))] [!! "All Namespaces" ] ]
else
- let categorise =
- [ for (nsIndex, ns) in Seq.indexed model.Collection.Namespaces do
- let allByCategory = categoriseEntities (nsIndex, ns, true)
- if allByCategory.Length > 0 then
- allByCategory, ns ]
-
+ let categorise = Categorise.model model
+
let someExist = categorise.Length > 0
if someExist && nav then
@@ -463,7 +393,7 @@ type HtmlRender(model: ApiDocModel) =
match nsOpt with
| Some ns2 when ns.Name = ns2.Name -> " active"
| _ -> "")
- Href (ns.Url(root, collectionName, qualify))] [!!ns.Name]
+ Href (ns.Url(root, collectionName, qualify, model.FileExtensions.InUrl))] [!!ns.Name]
// If not in the navigation list then generate the summary text as well
if not nav then
@@ -480,7 +410,7 @@ type HtmlRender(model: ApiDocModel) =
ul [ Custom ("list-style-type", "none") (* Class "navbar-nav " *) ] [
for category in allByCategory do
for e in category.CategoryEntites do
- li [ Class "nav-item" ] [a [Class "nav-link"; Href (e.Url(root, collectionName, qualify))] [!! e.Name] ]
+ li [ Class "nav-item" ] [a [Class "nav-link"; Href (e.Url(root, collectionName, qualify, model.FileExtensions.InUrl))] [!! e.Name] ]
]
| _ -> ()
]
@@ -515,7 +445,7 @@ type HtmlRender(model: ApiDocModel) =
let pageTitle = sprintf "%s (API Reference)" collectionName
let toc = listOfNamespaces false true None
let substitutions = getSubstitutons model.Substitutions toc content pageTitle
- let outFile = Path.Combine(outDir, model.IndexOutputFile(collectionName, model.Qualify) )
+ let outFile = Path.Combine(outDir, model.IndexOutputFile(collectionName, model.Qualify, model.FileExtensions.InFile) )
printfn " Generating %s" outFile
SimpleTemplating.UseFileAsSimpleTemplate (substitutions, templateOpt, outFile)
end
@@ -527,7 +457,7 @@ type HtmlRender(model: ApiDocModel) =
let pageTitle = ns.Name
let toc = listOfNamespaces false true (Some ns)
let substitutions = getSubstitutons model.Substitutions toc content pageTitle
- let outFile = Path.Combine(outDir, ns.OutputFile(collectionName, model.Qualify) )
+ let outFile = Path.Combine(outDir, ns.OutputFile(collectionName, model.Qualify, model.FileExtensions.InFile) )
printfn " Generating %s" outFile
SimpleTemplating.UseFileAsSimpleTemplate (substitutions, templateOpt, outFile)
@@ -536,7 +466,7 @@ type HtmlRender(model: ApiDocModel) =
let pageTitle = sprintf "%s (%s)" info.Entity.Name collectionName
let toc = listOfNamespaces false true (Some info.Namespace)
let substitutions = getSubstitutons info.Entity.Substitutions toc content pageTitle
- let outFile = Path.Combine(outDir, info.Entity.OutputFile(collectionName, model.Qualify))
+ let outFile = Path.Combine(outDir, info.Entity.OutputFile(collectionName, model.Qualify, model.FileExtensions.InFile))
printfn " Generating %s" outFile
SimpleTemplating.UseFileAsSimpleTemplate (substitutions, templateOpt, outFile)
diff --git a/src/FSharp.Formatting.ApiDocs/GenerateMarkdown.fs b/src/FSharp.Formatting.ApiDocs/GenerateMarkdown.fs
new file mode 100644
index 000000000..daa50a150
--- /dev/null
+++ b/src/FSharp.Formatting.ApiDocs/GenerateMarkdown.fs
@@ -0,0 +1,406 @@
+module internal FSharp.Formatting.ApiDocs.GenerateMarkdown
+
+open System
+open System.IO
+open System.Web
+open FSharp.Formatting.Markdown
+open FSharp.Formatting.Markdown.Dsl
+open FSharp.Formatting.Templating
+
+let encode (x:string) = HttpUtility.HtmlEncode(x).Replace("|", "|")
+let urlEncode (x: string) = HttpUtility.UrlEncode x
+let htmlString (x: ApiDocHtml) = (x.HtmlText.Trim())
+let htmlStringSafe (x: ApiDocHtml) = (x.HtmlText.Trim()).Replace("\n", " ").Replace("|", "|")
+let embed (x: ApiDocHtml) = !! (htmlString x)
+let embedSafe (x: ApiDocHtml) = !! (htmlStringSafe x)
+let br = !! " "
+
+type MarkdownRender(model: ApiDocModel) =
+ let root = model.Root
+ let collectionName = model.Collection.CollectionName
+ let qualify = model.Qualify
+
+ let sourceLink url =
+ [ match url with
+ | None -> ()
+ | Some href ->
+ link [
+ img "Link to source code" (sprintf "%scontent/img/github.png" root)
+ ] (href) ]
+
+ let renderMembers header tableHeader (members: ApiDocMember list) =
+ [ if members.Length > 0 then
+ ``###`` [!! header]
+ table [
+ [p [ !! tableHeader ]]
+ [p [ !! "Description"]]
+ [p [ !! "Source"]]
+ ]
+ [AlignLeft; AlignLeft; AlignCenter]
+ [
+ for m in members ->
+ [
+ [
+ p [link [embedSafe(m.UsageHtml)] ("#" + urlEncode(m.Name))]
+ ]
+ [
+ let summary = m.Comment.Summary
+ let emptySummary = summary.HtmlText |> String.IsNullOrWhiteSpace
+
+ if not emptySummary then
+ p [
+ embedSafe m.Comment.Summary
+ br
+ ]
+ match m.Comment.Remarks with
+ | None -> ()
+ | Some r -> p [
+ embedSafe r
+ br
+ ]
+ if not m.Parameters.IsEmpty then
+ p [ !! "Parameters"]
+ p []
+ yield! m.Parameters |> List.collect (fun parameter ->
+ [
+ p [
+ strong [!! parameter.ParameterNameText]
+ !! ": "
+ embedSafe parameter.ParameterType
+ ]
+ match parameter.ParameterDocs with
+ | None -> ()
+ | Some d -> p [!! (sprintf ": %s" (htmlStringSafe d))]
+
+ ])
+ p []
+ match m.ExtendedType with
+ | None -> ()
+ | Some s -> p [
+ !! "Extended Type: "
+ embedSafe s
+ br
+ ]
+ match m.ReturnInfo.ReturnType with
+ | None -> ()
+ | Some t -> p [
+ !! "Returns: "
+ embedSafe t
+ br
+ match m.ReturnInfo.ReturnDocs with
+ | None -> ()
+ | Some r -> embedSafe r
+ br
+ ]
+
+ if not m.Comment.Exceptions.IsEmpty then
+ for (nm, url, html) in m.Comment.Exceptions do
+ p [
+ match url with None -> () | Some href -> link [!! nm] href
+ embed html
+ br
+ ]
+
+ for e in m.Comment.Notes do
+ p [ !! "Note"]
+ p [
+ embed e
+ br
+ ]
+
+ for e in m.Comment.Examples do
+ p [!! "Example"]
+ p [
+ embed e
+ br
+ ]
+ ]
+ [
+ p [yield! sourceLink m.SourceLocation]
+ ]
+ ]
+ ]
+ ]
+
+ let renderEntities (entities: ApiDocEntity list) =
+ [ if entities.Length > 0 then
+ let hasTypes = entities |> List.exists (fun e -> e.IsTypeDefinition)
+ let hasModules = entities |> List.exists (fun e -> not e.IsTypeDefinition)
+ table [
+ [
+ p [!! (if hasTypes && hasModules then "Type/Module" elif hasTypes then "Type" else "Modules")]
+ p [!!"Description"]
+ p [!! "Source"]
+ ]
+ ]
+ [AlignLeft; AlignLeft; AlignCenter]
+ [
+ for e in entities do
+ [
+ [p [
+ let nm = e.Name
+ let multi = (entities |> List.filter (fun e -> e.Name = nm) |> List.length) > 1
+ let nmWithSiffix = if multi then (if e.IsTypeDefinition then nm + " (Type)" else nm + " (Module)") else nm
+ link [!!nmWithSiffix] (e.Url(root, collectionName, qualify, model.FileExtensions.InUrl))
+ ]]
+ [
+ p [embedSafe e.Comment.Summary]
+ ]
+ [
+ p [yield! (sourceLink e.SourceLocation)]
+ ]
+ ]
+ ]
+ ]
+
+ let entityContent (info: ApiDocEntityInfo) =
+ // Get all the members & comment for the type
+ let entity = info.Entity
+ let members = entity.AllMembers |> List.filter (fun e -> not e.IsObsolete)
+ let byCategory = Categorise.getMembersByCategory members
+ let usageName =
+ match info.ParentModule with
+ | Some m when m.RequiresQualifiedAccess -> m.Name + "." + entity.Name
+ | _ -> entity.Name
+
+ [
+ ``#`` [!! (usageName + (if entity.IsTypeDefinition then " Type" else " Module"))]
+ p [
+ !! "Namespace: "
+ link [!! info.Namespace.Name] (info.Namespace.Url(root, collectionName, qualify, model.FileExtensions.InUrl))
+ ]
+ p [!! ("Assembly: " + entity.Assembly.Name + ".dll")]
+
+ match info.ParentModule with
+ | None -> ()
+ | Some parentModule ->
+ p [
+ !! "Parent Module: "
+ link [!! parentModule.Name] (parentModule.Url(root, collectionName, qualify, model.FileExtensions.InUrl))
+ ]
+
+ match entity.AbbreviatedType with
+ | Some abbreviatedTyp ->
+ p [
+ !! "Abbreviation For: "
+ embed abbreviatedTyp
+ ]
+ | None -> ()
+
+ match entity.BaseType with
+ | Some baseType ->
+ p [
+ !! "Base Type: "
+ embed baseType
+ ]
+ | None -> ()
+
+ match entity.AllInterfaces with
+ | [] -> ()
+ | l ->
+ p [!! "All Interfaces: "
+ for (i, ity) in Seq.indexed l do
+ if i <> 0 then !! ", "
+ embed ity ]
+
+ if entity.Symbol.IsValueType then
+ p [!! ("Kind: Struct")]
+
+ match entity.DelegateSignature with
+ | Some d ->
+ p [!! ("Delegate Signature: "); embed d]
+ | None -> ()
+
+ if entity.Symbol.IsProvided then
+ p [!! ("This is a provided type definition")]
+
+ if entity.Symbol.IsAttributeType then
+ p [!! ("This is an attribute type definition")]
+
+ if entity.Symbol.IsEnum then
+ p [!! ("This is an enum type definition")]
+
+ //if info.Entity.IsObsolete then
+ // obsoleteMessage entity.ObsoleteMessage
+
+ // Show the summary (and sectioned docs without any members)
+ p [ embed entity.Comment.Summary ]
+
+ // Show the remarks etc.
+ match entity.Comment.Remarks with
+ | Some r ->
+ p [embed r]
+ | None -> ()
+
+ for note in entity.Comment.Notes do
+ ``#####`` [!! "Note"]
+ p [embed note]
+
+ for example in entity.Comment.Examples do
+ ``#####`` [!! "Example"]
+ p [embed example]
+
+ if (byCategory.Length > 1) then
+ // If there is more than 1 category in the type, generate TOC
+ ``###`` [!! "Table of contents"]
+ ul [
+ for (index, _, name) in byCategory do
+ [p [link [!! (sprintf "#section%d" index)] (name)]]
+ ]
+
+ //
+
+ let nestedEntities =
+ entity.NestedEntities
+ |> List.filter (fun e -> not e.IsObsolete)
+
+ if (nestedEntities.Length > 0) then
+
+ ``###`` [!! (if nestedEntities |> List.forall (fun e -> not e.IsTypeDefinition) then "Nested modules"
+ elif nestedEntities |> List.forall (fun e -> e.IsTypeDefinition) then "Types"
+ else "Types and nested modules")]
+ yield! renderEntities nestedEntities
+
+ for (_, ms, name) in byCategory do
+ // Iterate over all the categories and print members. If there are more than one
+ // categories, print the category heading (as
) and add XML comment from the type
+ // that is related to this specific category.
+ if (byCategory.Length > 1) then ``##`` [!! name]
+
+ yield! renderMembers "Functions and values" "Function or value" (ms |> List.filter (fun m -> m.Kind = ApiDocMemberKind.ValueOrFunction))
+ yield! renderMembers "Type extensions" "Type extension" (ms |> List.filter (fun m -> m.Kind = ApiDocMemberKind.TypeExtension))
+ yield! renderMembers "Active patterns" "Active pattern" (ms |> List.filter (fun m -> m.Kind = ApiDocMemberKind.ActivePattern))
+ yield! renderMembers "Union cases" "Union case" (ms |> List.filter (fun m -> m.Kind = ApiDocMemberKind.UnionCase))
+ yield! renderMembers "Record fields" "Record Field" (ms |> List.filter (fun m -> m.Kind = ApiDocMemberKind.RecordField))
+ yield! renderMembers "Static parameters" "Static parameters" (ms |> List.filter (fun m -> m.Kind = ApiDocMemberKind.StaticParameter))
+ yield! renderMembers "Constructors" "Constructor" (ms |> List.filter (fun m -> m.Kind = ApiDocMemberKind.Constructor))
+ yield! renderMembers "Instance members" "Instance member" (ms |> List.filter (fun m -> m.Kind = ApiDocMemberKind.InstanceMember))
+ yield! renderMembers "Static members" "Static member" (ms |> List.filter (fun m -> m.Kind = ApiDocMemberKind.StaticMember))
+ ]
+
+ let namespaceContent (nsIndex, ns: ApiDocNamespace) =
+ let allByCategory = Categorise.entities (nsIndex, ns, false)
+ [ if allByCategory.Length > 0 then
+ ``##`` [!! (ns.Name + " Namespace")]
+
+ match ns.NamespaceDocs with
+ | Some nsdocs ->
+ p [embed nsdocs.Summary ]
+ match nsdocs.Remarks with
+ | Some r -> p [embed r ]
+ | None -> ()
+ | None -> ()
+
+ if (allByCategory.Length > 1) then
+ p [!! "Categories:" ]
+
+ ul [
+ for category in allByCategory do
+ [p [link [!!category.CategoryName] ("#category-" + category.CategoryIndex)]]
+ ]
+
+ for category in allByCategory do
+ if (allByCategory.Length > 1) then
+ ``###`` [link [!! category.CategoryName] ("#category-" + category.CategoryIndex)]
+ yield! renderEntities category.CategoryEntites
+ ]
+
+ let listOfNamespacesAux otherDocs nav (nsOpt: ApiDocNamespace option) =
+ [
+ // For FSharp.Core we make all entries available to other docs else there's not a lot else to show.
+ //
+ // For non-FSharp.Core we only show one link "API Reference" in the nav menu
+ if otherDocs && nav && model.Collection.CollectionName <> "FSharp.Core" then
+ p [
+ !! "API Reference"
+ link [!! "All Namespaces"] (model.IndexFileUrl(root, collectionName, qualify, model.FileExtensions.InUrl))
+ ]
+ else
+
+ let categorise = Categorise.model model
+
+ let someExist = categorise.Length > 0
+
+ if someExist && nav then
+ p [!! "Namespaces"]
+
+ for allByCategory, ns in categorise do
+
+ // Generate the entry for the namespace
+ p [
+ link [!!ns.Name] (ns.Url(root, collectionName, qualify, model.FileExtensions.InUrl))
+
+ // If not in the navigation list then generate the summary text as well
+ if not nav then
+ !! " - "
+ match ns.NamespaceDocs with
+ | Some nsdocs -> embed nsdocs.Summary
+ | None -> () ]
+
+ // In the navigation bar generate the expanded list of entities
+ // for the active namespace
+ if nav then
+ match nsOpt with
+ | Some ns2 when ns.Name = ns2.Name ->
+ ul [
+ for category in allByCategory do
+ for e in category.CategoryEntites do
+ [p [link [!! e.Name] (e.Url(root, collectionName, qualify, model.FileExtensions.InUrl))] ]
+ ]
+ | _ -> ()
+ ]
+
+ let listOfNamespaces otherDocs nav (nsOpt: ApiDocNamespace option) =
+ listOfNamespacesAux otherDocs nav nsOpt
+ |> List.map (fun html -> html.ToString()) |> String.concat " \n"
+
+ /// Get the substitutions relevant to all
+ member _.GlobalSubstitutions : Substitutions =
+ let toc = listOfNamespaces true true None
+ [ yield (ParamKeys.``fsdocs-list-of-namespaces``, toc ) ]
+
+ member _.Generate(outDir: string, templateOpt, collectionName, globalParameters) =
+
+ let getSubstitutons parameters toc (content: MarkdownDocument) pageTitle =
+ [| yield! parameters
+ yield (ParamKeys.``fsdocs-list-of-namespaces``, toc )
+ yield (ParamKeys.``fsdocs-content``, Markdown.ToMd(content))
+ yield (ParamKeys.``fsdocs-source``,"" )
+ yield (ParamKeys.``fsdocs-tooltips``, "" )
+ yield (ParamKeys.``fsdocs-page-title``, pageTitle )
+ yield! globalParameters
+ |]
+
+ let collection = model.Collection
+ begin
+ let content = MarkdownDocument([
+ ``#`` [!! "API Reference"]
+ ``##`` [!! "Available Namespaces"]
+ ul [(listOfNamespacesAux false false None)]], Map.empty)
+ let pageTitle = sprintf "%s (API Reference)" collectionName
+ let toc = listOfNamespaces false true None
+ let substitutions = getSubstitutons model.Substitutions toc content pageTitle
+ let outFile = Path.Combine(outDir, model.IndexOutputFile(collectionName, model.Qualify, model.FileExtensions.InFile) )
+ printfn " Generating %s" outFile
+ SimpleTemplating.UseFileAsSimpleTemplate (substitutions, templateOpt, outFile)
+ end
+ ()
+
+ for (nsIndex, ns) in Seq.indexed collection.Namespaces do
+
+ let content = MarkdownDocument( namespaceContent (nsIndex, ns), Map.empty)
+ let pageTitle = ns.Name
+ let toc = listOfNamespaces false true (Some ns)
+ let substitutions = getSubstitutons model.Substitutions toc content pageTitle
+ let outFile = Path.Combine(outDir, ns.OutputFile(collectionName, model.Qualify, model.FileExtensions.InFile) )
+ printfn " Generating %s" outFile
+ SimpleTemplating.UseFileAsSimpleTemplate (substitutions, templateOpt, outFile)
+
+ for info in model.EntityInfos do
+ let content = MarkdownDocument(entityContent info, Map.empty)
+ let pageTitle = sprintf "%s (%s)" info.Entity.Name collectionName
+ let toc = listOfNamespaces false true (Some info.Namespace)
+ let substitutions = getSubstitutons info.Entity.Substitutions toc content pageTitle
+ let outFile = Path.Combine(outDir, info.Entity.OutputFile(collectionName, model.Qualify, model.FileExtensions.InFile))
+ printfn " Generating %s" outFile
+ SimpleTemplating.UseFileAsSimpleTemplate (substitutions, templateOpt, outFile)
diff --git a/src/FSharp.Formatting.ApiDocs/GenerateModel.fs b/src/FSharp.Formatting.ApiDocs/GenerateModel.fs
index 101bc150e..32b47208e 100644
--- a/src/FSharp.Formatting.ApiDocs/GenerateModel.fs
+++ b/src/FSharp.Formatting.ApiDocs/GenerateModel.fs
@@ -338,12 +338,12 @@ type ApiDocMember (displayName: string, attributes: ApiDocAttribute list, entity
member x.UrlBaseName = entityUrlBaseName
/// The URL of the best link documentation for the item relative to "reference" directory (without the http://site.io/reference)
- static member GetUrl(entityUrlBaseName, displayName, root, collectionName, qualify) =
- sprintf "%sreference/%s%s.html#%s" root (if qualify then collectionName + "/" else "") entityUrlBaseName displayName
+ static member GetUrl(entityUrlBaseName, displayName, root, collectionName, qualify, extension) =
+ sprintf "%sreference/%s%s%s#%s" root (if qualify then collectionName + "/" else "") entityUrlBaseName extension displayName
/// The URL of the best link documentation for the item relative to "reference" directory (without the http://site.io/reference)
- member x.Url(root, collectionName, qualify) =
- ApiDocMember.GetUrl(entityUrlBaseName, displayName, root, collectionName, qualify)
+ member x.Url(root, collectionName, qualify, extension) =
+ ApiDocMember.GetUrl(entityUrlBaseName, displayName, root, collectionName, qualify, extension)
/// The declared attributes of the member
member x.Attributes = attributes
@@ -414,16 +414,16 @@ type ApiDocEntity
member x.UrlBaseName : string = urlBaseName
/// Compute the URL of the best link for the entity relative to "reference" directory (without the http://site.io/reference)
- static member GetUrl(urlBaseName, root, collectionName, qualify) =
- sprintf "%sreference/%s%s.html" root (if qualify then collectionName + "/" else "") urlBaseName
+ static member GetUrl(urlBaseName, root, collectionName, qualify, extension) =
+ sprintf "%sreference/%s%s%s" root (if qualify then collectionName + "/" else "") urlBaseName extension
/// The URL of the best link for the entity relative to "reference" directory (without the http://site.io/reference)
- member x.Url(root, collectionName, qualify) =
- ApiDocEntity.GetUrl(urlBaseName, root, collectionName, qualify)
+ member x.Url(root, collectionName, qualify, extension) =
+ ApiDocEntity.GetUrl(urlBaseName, root, collectionName, qualify, extension)
/// The name of the file generated for this entity
- member x.OutputFile(collectionName, qualify) =
- sprintf "reference/%s%s.html" (if qualify then collectionName + "/" else "") urlBaseName
+ member x.OutputFile(collectionName, qualify, extension) =
+ sprintf "reference/%s%s%s" (if qualify then collectionName + "/" else "") urlBaseName extension
/// The attached comment
member x.Comment : ApiDocComment = comment
@@ -513,12 +513,12 @@ type ApiDocNamespace(name: string, modifiers, substitutions: Substitutions, nsdo
member x.UrlBaseName = urlBaseName
/// The URL of the best link documentation for the item (without the http://site.io/reference)
- member x.Url(root, collectionName, qualify) =
- sprintf "%sreference/%s%s.html" root (if qualify then collectionName + "/" else "") urlBaseName
+ member x.Url(root, collectionName, qualify, extension) =
+ sprintf "%sreference/%s%s%s" root (if qualify then collectionName + "/" else "") urlBaseName extension
/// The name of the file generated for this entity
- member x.OutputFile(collectionName, qualify) =
- sprintf "reference/%s%s.html" (if qualify then collectionName + "/" else "") urlBaseName
+ member x.OutputFile(collectionName, qualify, extension) =
+ sprintf "reference/%s%s%s" (if qualify then collectionName + "/" else "") urlBaseName extension
/// All modules in the namespace
member x.Entities : ApiDocEntity list = modifiers
@@ -620,7 +620,7 @@ module internal CrossReferences =
type internal CrefReference =
{ IsInternal : bool; ReferenceLink : string; NiceName : string; HasModuleSuffix: bool }
-type internal CrossReferenceResolver (root, collectionName, qualify) =
+type internal CrossReferenceResolver (root, collectionName, qualify, extensions) =
let toReplace =
([("Microsoft.", ""); (".", "-"); ("`", "-"); ("<", "_"); (">", "_"); (" ", "_"); ("#", "_")] @
(Path.GetInvalidPathChars()
@@ -632,6 +632,7 @@ type internal CrossReferenceResolver (root, collectionName, qualify) =
let registeredSymbolsToUrlBaseName = Dictionary()
let xmlDocNameToSymbol = Dictionary()
let niceNameEntityLookup = Dictionary<_, _>()
+ let extensions = extensions
let nameGen (name:string) =
let nice = (toReplace
@@ -712,10 +713,10 @@ type internal CrossReferenceResolver (root, collectionName, qualify) =
sprintf "https://docs.microsoft.com/dotnet/api/%s" docs
let internalCrossReference urlBaseName =
- ApiDocEntity.GetUrl(urlBaseName, root, collectionName, qualify)
+ ApiDocEntity.GetUrl(urlBaseName, root, collectionName, qualify, extensions.InUrl)
let internalCrossReferenceForMember entityUrlBaseName (memb: FSharpMemberOrFunctionOrValue) =
- ApiDocMember.GetUrl(entityUrlBaseName, memb.DisplayName, root, collectionName, qualify)
+ ApiDocMember.GetUrl(entityUrlBaseName, memb.DisplayName, root, collectionName, qualify, extensions.InUrl)
let tryResolveCrossReferenceForEntity (entity: FSharpEntity) =
match registeredSymbolsToUrlBaseName.TryGetValue (entity) with
@@ -2066,6 +2067,12 @@ type ApiDocInput =
PublicOnly=defaultArg publicOnly true;
MarkdownComments = defaultArg mdcomments false }
+
+type ApiDocFileExtensions = {
+ InFile: string
+ InUrl: string
+}
+
/// Represents a set of assemblies integrated with their associated documentation
type ApiDocModel =
{
@@ -2084,18 +2091,20 @@ type ApiDocModel =
/// Indicates if each collection is being qualified by its collection name, e.g. 'reference/FSharp.Core'
Qualify: bool
+ /// Specifies file extensions to use in files and URLs
+ FileExtensions: ApiDocFileExtensions
}
/// URL of the 'index.html' for the reference documentation for the model
- member x.IndexFileUrl(root, collectionName, qualify) =
- sprintf "%sreference/%sindex.html" root (if qualify then collectionName + "/" else "")
+ member x.IndexFileUrl(root, collectionName, qualify, extension) =
+ sprintf "%sreference/%sindex%s" root (if qualify then collectionName + "/" else "") extension
/// URL of the 'index.html' for the reference documentation for the model
- member x.IndexOutputFile(collectionName, qualify) =
- sprintf "reference/%sindex.html" (if qualify then collectionName + "/" else "")
+ member x.IndexOutputFile(collectionName, qualify, extension) =
+ sprintf "reference/%sindex%s" (if qualify then collectionName + "/" else "") extension
static member internal Generate(projects: ApiDocInput list, collectionName, libDirs, otherFlags,
- qualify, urlRangeHighlight, root, substitutions, strict) =
+ qualify, urlRangeHighlight, root, substitutions, strict, extensions) =
let (@@) a b = Path.Combine(a, b)
@@ -2104,7 +2113,7 @@ type ApiDocModel =
let otherFlags = defaultArg otherFlags []
let libDirs = defaultArg libDirs [] |> List.map Path.GetFullPath
let dllFiles = projects |> List.map (fun p -> Path.GetFullPath p.Path)
- let urlRangeHighlight =defaultArg urlRangeHighlight (fun url start stop -> String.Format("{0}#L{1}-{2}", url, start, stop))
+ let urlRangeHighlight = defaultArg urlRangeHighlight (fun url start stop -> String.Format("{0}#L{1}-{2}", url, start, stop))
// When resolving assemblies, look in folders where all DLLs live
AppDomain.CurrentDomain.add_AssemblyResolve(System.ResolveEventHandler(fun o e ->
@@ -2140,7 +2149,7 @@ type ApiDocModel =
|> List.zip projects
// generate the names for the html files beforehand so we can resolve links.
- let urlMap = CrossReferenceResolver(root, collectionName, qualify)
+ let urlMap = CrossReferenceResolver(root, collectionName, qualify, extensions)
for (_, asmOpt) in resolvedList do
match asmOpt with
@@ -2259,6 +2268,7 @@ type ApiDocModel =
EntityInfos = moduleInfos @ typesInfos
Root = root
Qualify = qualify
+ FileExtensions = extensions
}
/// Represents an entry suitable for constructing a Lunr index
diff --git a/src/FSharp.Formatting.ApiDocs/GenerateSearchIndex.fs b/src/FSharp.Formatting.ApiDocs/GenerateSearchIndex.fs
index 90f29bd1d..50446b1f3 100644
--- a/src/FSharp.Formatting.ApiDocs/GenerateSearchIndex.fs
+++ b/src/FSharp.Formatting.ApiDocs/GenerateSearchIndex.fs
@@ -36,7 +36,7 @@ let searchIndexEntriesForModel (model: ApiDocModel) =
| Some s -> yield s.HtmlText
] |> String.concat " \n"
- { uri = memb.Url(model.Root, model.Collection.CollectionName, model.Qualify)
+ { uri = memb.Url(model.Root, model.Collection.CollectionName, model.Qualify, model.FileExtensions.InUrl)
title = enclName + "." + memb.Name
content = cnt }
@@ -49,7 +49,7 @@ let searchIndexEntriesForModel (model: ApiDocModel) =
e.Name
] |> String.concat " \n"
- { uri = nsp.Url(model.Root, model.Collection.CollectionName, model.Qualify)
+ { uri = nsp.Url(model.Root, model.Collection.CollectionName, model.Qualify, model.FileExtensions.InUrl)
title = nsp.Name
content = ctn }
@@ -72,7 +72,7 @@ let searchIndexEntriesForModel (model: ApiDocModel) =
] |> String.concat " \n"
- let url = e.Url(model.Root, model.Collection.CollectionName, model.Qualify)
+ let url = e.Url(model.Root, model.Collection.CollectionName, model.Qualify, model.FileExtensions.InUrl)
{ uri = url
title = e.Name
content = cnt }
diff --git a/src/FSharp.Formatting.CommandTool/BuildCommand.fs b/src/FSharp.Formatting.CommandTool/BuildCommand.fs
index 598d37cf2..a200933ef 100644
--- a/src/FSharp.Formatting.CommandTool/BuildCommand.fs
+++ b/src/FSharp.Formatting.CommandTool/BuildCommand.fs
@@ -58,6 +58,7 @@ type internal DocContent(outputDirectory, previous: Map<_,_>, lineNumbers, fsiEv
| OutputKind.Pynb, None -> ()
| OutputKind.Latex, None -> ()
| OutputKind.Fsx, None -> ()
+ | OutputKind.Md, None -> ()
| _ ->
let imageSaverOpt =
@@ -66,6 +67,7 @@ type internal DocContent(outputDirectory, previous: Map<_,_>, lineNumbers, fsiEv
| OutputKind.Latex when saveImages <> Some false -> Some imageSaver
| OutputKind.Fsx when saveImages = Some true -> Some imageSaver
| OutputKind.Html when saveImages = Some true -> Some imageSaver
+ | OutputKind.Md when saveImages = Some true -> Some imageSaver
| _ -> None
let ext = outputKind.Extension
@@ -151,7 +153,7 @@ type internal DocContent(outputDirectory, previous: Map<_,_>, lineNumbers, fsiEv
//printfn "skipping unchanged file %s" inputFile
yield (Some (inputFile, haveModel.Value), (fun _ -> ()))
]
- let rec processDirectory (htmlTemplate, texTemplate, pynbTemplate, fsxTemplate) indir outputPrefix = [
+ let rec processDirectory (htmlTemplate, texTemplate, pynbTemplate, fsxTemplate, mdTemplate) indir outputPrefix = [
// Look for the presence of the _template.* files to activate the
// generation of the content.
let possibleNewHtmlTemplate = Path.Combine(indir, "_template.html")
@@ -160,6 +162,8 @@ type internal DocContent(outputDirectory, previous: Map<_,_>, lineNumbers, fsiEv
let pynbTemplate = if File.Exists(possibleNewPynbTemplate) then Some possibleNewPynbTemplate else pynbTemplate
let possibleNewFsxTemplate = Path.Combine(indir, "_template.fsx")
let fsxTemplate = if File.Exists(possibleNewFsxTemplate) then Some possibleNewFsxTemplate else fsxTemplate
+ let possibleNewMdTemplate = Path.Combine(indir, "_template.md")
+ let mdTemplate = if File.Exists(possibleNewMdTemplate) then Some possibleNewMdTemplate else mdTemplate
let possibleNewLatexTemplate = Path.Combine(indir, "_template.tex")
let texTemplate = if File.Exists(possibleNewLatexTemplate) then Some possibleNewLatexTemplate else texTemplate
@@ -174,13 +178,14 @@ type internal DocContent(outputDirectory, previous: Map<_,_>, lineNumbers, fsiEv
yield! processFile input OutputKind.Latex texTemplate outputPrefix imageSaver
yield! processFile input OutputKind.Pynb pynbTemplate outputPrefix imageSaver
yield! processFile input OutputKind.Fsx fsxTemplate outputPrefix imageSaver
+ yield! processFile input OutputKind.Md mdTemplate outputPrefix imageSaver
for subdir in Directory.EnumerateDirectories(indir) do
let name = Path.GetFileName(subdir)
if name.StartsWith "." then
printfn " skipping directory %s" subdir
else
- yield! processDirectory (htmlTemplate, texTemplate, pynbTemplate, fsxTemplate) (Path.Combine(indir, name)) (Path.Combine(outputPrefix, name))
+ yield! processDirectory (htmlTemplate, texTemplate, pynbTemplate, fsxTemplate, mdTemplate) (Path.Combine(indir, name)) (Path.Combine(outputPrefix, name))
]
member _.Convert(input, htmlTemplate, extraInputs) =
@@ -188,7 +193,7 @@ type internal DocContent(outputDirectory, previous: Map<_,_>, lineNumbers, fsiEv
let inputDirectories = extraInputs @ [(input, ".") ]
[
for (inputDirectory, outputPrefix) in inputDirectories do
- yield! processDirectory (htmlTemplate, None, None, None) inputDirectory outputPrefix
+ yield! processDirectory (htmlTemplate, None, None, None, None) inputDirectory outputPrefix
]
member _.GetSearchIndexEntries(docModels: (string * LiterateDocModel) list) =
@@ -579,38 +584,58 @@ type CoreBuildOptions(watch) =
if not this.noapidocs then
- let initialTemplate2 =
- let t1 = Path.Combine(this.input, "reference", "_template.html")
- let t2 = Path.Combine(this.input, "_template.html")
- if File.Exists(t1) then
- Some t1
- elif File.Exists(t2) then
- Some t2
- else
- match defaultTemplate with
+ let (outputKind, initialTemplate2) =
+ let templates = [
+ OutputKind.Html, Path.Combine(this.input, "reference", "_template.html")
+ OutputKind.Html, Path.Combine(this.input, "_template.html")
+ OutputKind.Md, Path.Combine(this.input, "reference", "_template.md")
+ OutputKind.Md, Path.Combine(this.input, "_template.md")
+ ]
+ match templates |> Seq.tryFind (fun (_,path) -> path |> File.Exists) with
+ | Some (kind, path) -> kind, Some path
+ | None ->
+ let templateFiles = templates |> Seq.map snd |> String.concat "', '"
+ match defaultTemplate with
| Some d ->
- printfn "note, no template file '%s' or '%s', using default template %s" t1 t2 d
- Some d
+ printfn "note, no template files: '%s' found, using default template %s" templateFiles d
+ OutputKind.Html, Some d
| None ->
- printfn "note, no template file '%s' or '%s', and no default template at '%s'" t1 t2 defaultTemplateAttempt1
- None
-
+ printfn "note, no template file '%s' found, and no default template at '%s'" templateFiles defaultTemplateAttempt1
+ OutputKind.Html, None
+
printfn ""
printfn "API docs:"
printfn " generating model for %d assemblies in API docs..." apiDocInputs.Length
+
let globals, index, phase2 =
- ApiDocs.GenerateHtmlPhased (
- inputs = apiDocInputs,
- output = output,
- collectionName = collectionName,
- substitutions = docsParameters,
- qualify = this.qualify,
- ?template = initialTemplate2,
- otherFlags = Seq.toList this.fscoptions,
- root = root,
- libDirs = paths,
- strict = this.strict
- )
+ match outputKind with
+ | OutputKind.Html ->
+ ApiDocs.GenerateHtmlPhased (
+ inputs = apiDocInputs,
+ output = output,
+ collectionName = collectionName,
+ substitutions = docsParameters,
+ qualify = this.qualify,
+ ?template = initialTemplate2,
+ otherFlags = Seq.toList this.fscoptions,
+ root = root,
+ libDirs = paths,
+ strict = this.strict
+ )
+ | OutputKind.Md ->
+ ApiDocs.GenerateMarkdownPhased (
+ inputs = apiDocInputs,
+ output = output,
+ collectionName = collectionName,
+ substitutions = docsParameters,
+ qualify = this.qualify,
+ ?template = initialTemplate2,
+ otherFlags = Seq.toList this.fscoptions,
+ root = root,
+ libDirs = paths,
+ strict = this.strict
+ )
+ | _ -> failwithf "API Docs format '%A' is not supported" outputKind
latestApiDocSearchIndexEntries <- index
latestApiDocGlobalParameters <- globals
diff --git a/src/FSharp.Formatting.Literate/Contexts.fs b/src/FSharp.Formatting.Literate/Contexts.fs
index 4d1a1d4ed..53d6d09ed 100644
--- a/src/FSharp.Formatting.Literate/Contexts.fs
+++ b/src/FSharp.Formatting.Literate/Contexts.fs
@@ -34,10 +34,14 @@ type OutputKind =
/// Requests F# Script output
| Fsx
+
+ /// Requests Markdown output
+ | Md
member x.Extension =
match x with
| Fsx -> "fsx"
| Latex -> "tex"
+ | Md -> "md"
| Html -> "html"
| Pynb -> "ipynb"
diff --git a/src/FSharp.Formatting.Literate/Formatting.fs b/src/FSharp.Formatting.Literate/Formatting.fs
index 06675296c..f9bd22462 100644
--- a/src/FSharp.Formatting.Literate/Formatting.fs
+++ b/src/FSharp.Formatting.Literate/Formatting.fs
@@ -12,6 +12,7 @@ module internal Formatting =
let format (doc: MarkdownDocument) generateAnchors outputKind substitutions =
match outputKind with
| OutputKind.Fsx -> Markdown.ToFsx(doc, substitutions=substitutions)
+ | OutputKind.Md -> Markdown.ToMd(doc, substitutions=substitutions)
| OutputKind.Pynb -> Markdown.ToPynb(doc, substitutions=substitutions)
| OutputKind.Latex -> Markdown.ToLatex(doc)
| OutputKind.Html ->
diff --git a/src/FSharp.Formatting.Literate/Transformations.fs b/src/FSharp.Formatting.Literate/Transformations.fs
index b2a43d9fe..9bcdc8af2 100644
--- a/src/FSharp.Formatting.Literate/Transformations.fs
+++ b/src/FSharp.Formatting.Literate/Transformations.fs
@@ -428,6 +428,8 @@ module internal Transformations =
code
| OutputKind.Fsx ->
code
+ | OutputKind.Md ->
+ code
Some(InlineBlock(inlined, None, None))
// Traverse all other structures recursively
| MarkdownPatterns.ParagraphNested(pn, nested) ->
@@ -461,6 +463,8 @@ module internal Transformations =
CodeFormat.FormatFsx(snippets)
| OutputKind.Fsx ->
CodeFormat.FormatFsx(snippets)
+ | OutputKind.Md ->
+ CodeFormat.FormatFsx(snippets)
let lookup =
[ for (key, (_, executionCount)), fmtd in Seq.zip codes formatted.Snippets ->
diff --git a/src/FSharp.Formatting.Markdown/FSharp.Formatting.Markdown.fsproj b/src/FSharp.Formatting.Markdown/FSharp.Formatting.Markdown.fsproj
index b74af1e63..9918c1055 100644
--- a/src/FSharp.Formatting.Markdown/FSharp.Formatting.Markdown.fsproj
+++ b/src/FSharp.Formatting.Markdown/FSharp.Formatting.Markdown.fsproj
@@ -21,6 +21,7 @@
+
diff --git a/src/FSharp.Formatting.Markdown/Formatting.fs b/src/FSharp.Formatting.Markdown/Formatting.fs
index 86ef0be6f..ab347823c 100644
--- a/src/FSharp.Formatting.Markdown/Formatting.fs
+++ b/src/FSharp.Formatting.Markdown/Formatting.fs
@@ -66,10 +66,10 @@ module internal MarkdownUtils =
"[" + formatSpans ctx body + "](" + link + ")"
| IndirectImage(_body, _, LookupKey ctx.Links (_link, _), _)
- | DirectImage(_body, _link, _, _)
| IndirectImage(_body, _link, _, _) ->
failwith "tbd - IndirectImage"
-
+ | DirectImage(_body, _link, _, _) ->
+ sprintf "![%s](%s)" _body _link
| Strong(body, _) ->
"**" + formatSpans ctx body + "**"
| InlineCode(body, _) ->
@@ -103,6 +103,37 @@ module internal MarkdownUtils =
| CodeBlock(code, _, _, _, _) ->
yield code
yield ""
+ | ListBlock (Unordered, paragraphs, _) ->
+ yield (String.concat "\n" (paragraphs |> List.collect(fun ps -> [ for p in ps -> String.concat "" (formatParagraph ctx p)])))
+ | TableBlock (headers, alignments, rows, _) ->
+
+ match headers with
+ | Some headers ->
+ yield (String.concat " | " (headers |> List.collect (fun hs -> [for h in hs -> String.concat "" (formatParagraph ctx h)])))
+ | None -> ()
+
+ yield (String.concat " | " [
+ for a in alignments ->
+ match a with
+ | AlignLeft -> ":---"
+ | AlignCenter -> ":---:"
+ | AlignRight -> "---:"
+ | AlignDefault -> "---"
+ ])
+ let replaceEmptyWith x s = match s with | "" | null -> x | s -> Some s
+ yield String.concat "\n" [
+ for r in rows do
+ [
+ for ps in r do
+ let x = [
+ for p in ps do
+ yield formatParagraph ctx p |> Seq.choose (replaceEmptyWith (Some "")) |> String.concat ""
+ ]
+ yield x |> Seq.choose (replaceEmptyWith (Some "")) |> String.concat " "
+ ] |> Seq.choose (replaceEmptyWith (Some " ")) |> String.concat " | "
+ ]
+ yield "\n"
+
| OutputBlock(output, "text/html", _executionCount) ->
yield (output.Trim())
yield ""
diff --git a/src/FSharp.Formatting.Markdown/Markdown.fs b/src/FSharp.Formatting.Markdown/Markdown.fs
index 81df8980f..464acc860 100644
--- a/src/FSharp.Formatting.Markdown/Markdown.fs
+++ b/src/FSharp.Formatting.Markdown/Markdown.fs
@@ -114,9 +114,14 @@ type Markdown =
let substitutions = defaultArg substitutions []
PynbFormatting.formatAsPynb doc.DefinedLinks substitutions newline doc.Paragraphs
- /// Transform the provided MarkdownDocument into Pynb and return the result as a string.
+ /// Transform the provided MarkdownDocument into Fsx and return the result as a string.
static member ToFsx(doc: MarkdownDocument, ?newline, ?substitutions) =
let newline = defaultArg newline Environment.NewLine
let substitutions = defaultArg substitutions []
FsxFormatting.formatAsFsx doc.DefinedLinks substitutions newline doc.Paragraphs
+ /// Transform the provided MarkdownDocument into Md and return the result as a string.
+ static member ToMd(doc: MarkdownDocument, ?newline, ?substitutions) =
+ let newline = defaultArg newline Environment.NewLine
+ let substitutions = defaultArg substitutions []
+ MarkdownFormatting.formatAsMd doc.DefinedLinks substitutions newline doc.Paragraphs
diff --git a/src/FSharp.Formatting.Markdown/MarkdownFormatting.fs b/src/FSharp.Formatting.Markdown/MarkdownFormatting.fs
new file mode 100644
index 000000000..3aa5d3201
--- /dev/null
+++ b/src/FSharp.Formatting.Markdown/MarkdownFormatting.fs
@@ -0,0 +1,14 @@
+// --------------------------------------------------------------------------------------
+// Format a document as a .md
+// --------------------------------------------------------------------------------------
+
+module internal FSharp.Formatting.Markdown.MarkdownFormatting
+
+open MarkdownUtils
+
+let rec formatParagraphs ctx (paragraphs:MarkdownParagraph list) =
+ paragraphs |> Seq.collect (formatParagraph ctx)
+
+let formatAsMd links replacements newline paragraphs =
+ formatParagraphs { Links = links; Substitutions=replacements; Newline=newline; DefineSymbol="MD" } paragraphs
+ |> String.concat newline
diff --git a/src/FSharp.Formatting.Markdown/MarkdownModel.fs b/src/FSharp.Formatting.Markdown/MarkdownModel.fs
index 1b018da66..7675c80ed 100644
--- a/src/FSharp.Formatting.Markdown/MarkdownModel.fs
+++ b/src/FSharp.Formatting.Markdown/MarkdownModel.fs
@@ -78,6 +78,23 @@ type MarkdownTableRow = list
type MarkdownEmbedParagraphs =
abstract Render : unit -> MarkdownParagraphs
+module Dsl =
+ let ``#`` value = Heading(1, value, None)
+ let ``##`` value = Heading(2, value, None)
+ let ``###`` value = Heading(3, value, None)
+ let ``####`` value = Heading(4, value, None)
+ let ``#####`` value = Heading(5, value, None)
+ let strong value = Strong(value, None)
+ let p value = Paragraph(value, None)
+ let span value = Span(value, None)
+ let (!!) value = Literal(value, None)
+ let link content url = DirectLink(content, url, None, None)
+ let ul value = ListBlock(Unordered, value, None)
+ let ol value = ListBlock(Ordered, value, None)
+ let table headers alignments rows =
+ let hs = match headers with | [] -> None | hs -> Some hs
+ TableBlock(hs, alignments, rows, None)
+ let img body link = DirectImage(body, link, None, None)
// --------------------------------------------------------------------------------------
// Patterns that make recursive Markdown processing easier
// --------------------------------------------------------------------------------------
diff --git a/tests/FSharp.ApiDocs.Tests/ApiDocsTests.fs b/tests/FSharp.ApiDocs.Tests/ApiDocsTests.fs
index 9544f8803..458761fd9 100644
--- a/tests/FSharp.ApiDocs.Tests/ApiDocsTests.fs
+++ b/tests/FSharp.ApiDocs.Tests/ApiDocsTests.fs
@@ -12,6 +12,30 @@ open FsUnitTyped
// Run the metadata formatter on sample project
// --------------------------------------------------------------------------------------
+type OutputFormat =
+ | Html
+ | Markdown
+with
+ member x.Extension =
+ match x with
+ | Html -> "html"
+ | Markdown -> "md"
+ member x.ExtensionInUrl =
+ match x with
+ | Html -> ".html"
+ | Markdown -> ""
+
+type DocsGenerator(format: OutputFormat) = class end
+with
+ member _.Run(input, output, collectionName, template, substitutions, ?libDirs, ?root) =
+ let root = defaultArg root "/"
+ let libDirs = defaultArg libDirs []
+ match format with
+ | Html -> ApiDocs.GenerateHtml(input, output, collectionName=collectionName, template=template, substitutions=substitutions, libDirs=libDirs, root=root)
+ | Markdown -> ApiDocs.GenerateMarkdown(input, output, collectionName=collectionName, template=template, substitutions=substitutions, libDirs=libDirs, root=root)
+
+let formats = [ Html; Markdown ]
+
let (>) a b = Path.Combine(a, b)
let fullpath = Path.GetFullPath
let fullpaths = List.map fullpath
@@ -23,8 +47,8 @@ let root = __SOURCE_DIRECTORY__ |> fullpath
// test project to be directed to the directory below
let testBin = AttributeTests.testBin
-let getOutputDir (uniq: string) =
- let outDir = __SOURCE_DIRECTORY__ + "/output/" + uniq
+let getOutputDir (format:OutputFormat) (uniq: string) =
+ let outDir = __SOURCE_DIRECTORY__ + "/output/" + format.Extension + "/" + uniq
while (try Directory.Exists outDir with _ -> false) do
Directory.Delete(outDir, true)
Directory.CreateDirectory(outDir).FullName
@@ -32,8 +56,8 @@ let getOutputDir (uniq: string) =
let removeWhiteSpace (str:string) =
str.Replace("\n", "").Replace("\r", "").Replace(" ", "")
-let docTemplate =
- root > "../../docs/_template.html"
+let docTemplate (format:OutputFormat) =
+ root > (sprintf "../../docs/_template.%s" format.Extension)
let substitutions =
[ ParamKeys.``fsdocs-collection-name``, "F# TestProject"
@@ -41,11 +65,11 @@ let substitutions =
ParamKeys.``fsdocs-repository-link``, "http://github.com/fsprojects/fsharp-test-project"
ParamKeys.``root``, "/root/" ]
-let generateApiDocs (libraries:string list) useMarkdown uniq =
+let generateApiDocs (libraries:string list) (format:OutputFormat) useMdComments uniq =
try
- let output = getOutputDir uniq
- let inputs = [ for x in libraries -> ApiDocInput.FromFile(x, mdcomments = useMarkdown) ]
- let _metadata = ApiDocs.GenerateHtml (inputs, libDirs = [root], output=output, collectionName="Collection", template=docTemplate, substitutions=substitutions)
+ let output = getOutputDir format uniq
+ let inputs = [ for x in libraries -> ApiDocInput.FromFile(x, mdcomments = useMdComments) ]
+ let _metadata = DocsGenerator(format).Run (inputs, output=output, collectionName="Collection", template=docTemplate format, substitutions=substitutions, libDirs = [root])
let fileNames = Directory.GetFiles(output > "reference")
let files = dict [ for f in fileNames -> Path.GetFileName(f), File.ReadAllText(f) ]
@@ -64,89 +88,99 @@ let generateApiDocs (libraries:string list) useMarkdown uniq =
do FSharp.Formatting.TestHelpers.enableLogging()
[]
-let ``ApiDocs works on sample Deedle assembly``() =
+[]
+let ``ApiDocs works on sample Deedle assembly`` (format:OutputFormat) =
let library = root > "files" > "Deedle.dll"
- let output = getOutputDir "Deedle"
+ let output = getOutputDir format "Deedle"
let input =
ApiDocInput.FromFile(library, mdcomments = true,
sourceRepo = "https://github.com/fslaborg/Deedle/",
sourceFolder = "c:/dev/FSharp.DataFrame")
let _model, _index =
- ApiDocs.GenerateHtml( [input], output, collectionName="Deedle", template=docTemplate, substitutions=substitutions, libDirs = [testBin])
+ DocsGenerator(format).Run([input], output, collectionName="Deedle", template=docTemplate format, substitutions=substitutions, libDirs = [testBin])
let files = Directory.GetFiles(output > "reference")
- let optIndex = files |> Seq.tryFind (fun s -> s.EndsWith "index.html")
+ let optIndex = files |> Seq.tryFind (fun s -> s.EndsWith (sprintf "index.%s" format.Extension))
optIndex.IsSome |> shouldEqual true
let optSeriesMod = files |> Seq.tryFind (fun s -> s.Contains "seriesmodule")
optSeriesMod.IsSome |> shouldEqual true
[]
-let ``ApiDocs works on sample FAKE assembly``() =
+[]
+let ``ApiDocs works on sample FAKE assembly`` (format:OutputFormat) =
let library = root > "files" > "FAKE" > "FakeLib.dll"
- let output = getOutputDir "FakeLib"
+ let output = getOutputDir format "FakeLib"
let input = ApiDocInput.FromFile(library, mdcomments = true)
- let _model, _index = ApiDocs.GenerateHtml( [input], output, collectionName="FAKE", template=docTemplate, substitutions=substitutions)
+ let _model, _index = DocsGenerator(format).Run([input], output, collectionName="FAKE", template=docTemplate format, substitutions=substitutions)
let files = Directory.GetFiles(output > "reference")
files |> Seq.length |> shouldEqual 166
-
[]
-let ``ApiDocs works on two sample F# assemblies``() =
+[]
+let ``ApiDocs works on two sample F# assemblies`` (format:OutputFormat) =
let libraries =
[ testBin > "FsLib1.dll"
testBin > "FsLib2.dll" ]
- let output = getOutputDir "FsLib12"
+ let output = getOutputDir format "FsLib12"
let inputs = [ for lib in libraries -> ApiDocInput.FromFile(lib, mdcomments = true, substitutions=substitutions) ]
let _model, searchIndex =
- ApiDocs.GenerateHtml(inputs, output, collectionName="FsLib", template=docTemplate,
+ DocsGenerator(format).Run(inputs, output, collectionName="FsLib", template=docTemplate format,
root="http://root.io/root/", substitutions=substitutions, libDirs = [testBin])
let fileNames = Directory.GetFiles(output > "reference")
let files = dict [ for f in fileNames -> Path.GetFileName(f), File.ReadAllText(f) ]
// Check that all comments appear in the output
- files.["fslib-class.html"] |> shouldContainText "Readonly int property"
- files.["fslib-record.html"] |> shouldContainText "This is name"
- files.["fslib-record.html"] |> shouldContainText "Additional member"
- files.["fslib-union.html"] |> shouldContainText "Hello of int"
- files.["fslib.html"] |> shouldContainText "Sample class"
- files.["fslib.html"] |> shouldContainText "Union sample"
- files.["fslib.html"] |> shouldContainText "Record sample"
- files.["fslib-nested.html"] |> shouldContainText "Somewhat nested type"
- files.["fslib-nested.html"] |> shouldContainText "Somewhat nested module"
- files.["fslib-nested-nestedtype.html"] |> shouldContainText "Very nested member"
- files.["fslib-nested-submodule.html"] |> shouldContainText "Very nested field"
+ files.[(sprintf "fslib-class.%s" format.Extension)] |> shouldContainText "Readonly int property"
+ files.[(sprintf "fslib-record.%s" format.Extension)] |> shouldContainText "This is name"
+ files.[(sprintf "fslib-record.%s" format.Extension)] |> shouldContainText "Additional member"
+ files.[(sprintf "fslib-union.%s" format.Extension)] |> shouldContainText "Hello of int"
+ files.[(sprintf "fslib.%s" format.Extension)] |> shouldContainText "Sample class"
+ files.[(sprintf "fslib.%s" format.Extension)] |> shouldContainText "Union sample"
+ files.[(sprintf "fslib.%s" format.Extension)] |> shouldContainText "Record sample"
+ files.[(sprintf "fslib-nested.%s" format.Extension)] |> shouldContainText "Somewhat nested type"
+ files.[(sprintf "fslib-nested.%s" format.Extension)] |> shouldContainText "Somewhat nested module"
+ files.[(sprintf "fslib-nested-nestedtype.%s" format.Extension)] |> shouldContainText "Very nested member"
+ files.[(sprintf "fslib-nested-submodule.%s" format.Extension)] |> shouldContainText "Very nested field"
// Check that union fields are correctly generated
- files.["fslib-union.html"] |> shouldContainText "World(string, int)"
- files.["fslib-union.html"] |> shouldContainText "Naming(rate, string)"
+ files.[(sprintf "fslib-union.%s" format.Extension)] |> shouldContainText "World(string, int)"
+ files.[(sprintf "fslib-union.%s" format.Extension)] |> shouldContainText "Naming(rate, string)"
+
+ (* This may be addressed in a separate issue or removed if not an issue.
+ // Check that implict cast operator is generated correctly
+ files.[(sprintf "fslib-space-missing-implicit-cast.%s" format.Extension)] |> shouldContainText "op_Implicit source"
+ files.[(sprintf "fslib-space-missing-implicit-cast.%s" format.Extension)] |> match format with
+ | Html -> shouldContainText "!|> value"
+ | Markdown -> shouldContainText "!|> value"
+ *)
(*
// Check that methods with no arguments are correctly generated (#113)
- files.["fslib-record.html"] |> shouldNotContainText "Foo2(arg1)"
- files.["fslib-record.html"] |> shouldContainText "Foo2()"
- files.["fslib-record.html"] |> shouldContainText "Signature"
- files.["fslib-record.html"] |> shouldContainText "unit -> int"
- files.["fslib-class.html"] |> shouldContainText "Class()"
- files.["fslib-class.html"] |> shouldContainText "unit -> Class"
+ files.[(sprintf "fslib-record.%s" format.Extension)] |> shouldNotContainText "Foo2(arg1)"
+ files.[(sprintf "fslib-record.%s" format.Extension)] |> shouldContainText "Foo2()"
+ files.[(sprintf "fslib-record.%s" format.Extension)] |> shouldContainText "Signature"
+ files.[(sprintf "fslib-record.%s" format.Extension)] |> shouldContainText "unit -> int"
+ files.[(sprintf "fslib-class.%s" format.Extension)] |> shouldContainText "Class()"
+ files.[(sprintf "fslib-class.%s" format.Extension)] |> shouldContainText "unit -> Class"
// Check that properties are correctly generated (#114)
- files.["fslib-class.html"] |> removeWhiteSpace |> shouldNotContainText ">this.Member(arg1)<"
- files.["fslib-class.html"] |> removeWhiteSpace |> shouldNotContainText ">this.Member()<"
- files.["fslib-class.html"] |> removeWhiteSpace |> shouldContainText ">this.Member<"
- files.["fslib-class.html"] |> shouldNotContainText "unit -> int"
- //files.["fslib-class.html"] |> shouldContainText "Signature:"
+ files.[(sprintf "fslib-class.%s" format.Extension)] |> removeWhiteSpace |> shouldNotContainText ">this.Member(arg1)<"
+ files.[(sprintf "fslib-class.%s" format.Extension)] |> removeWhiteSpace |> shouldNotContainText ">this.Member()<"
+ files.[(sprintf "fslib-class.%s" format.Extension)] |> removeWhiteSpace |> shouldContainText ">this.Member<"
+ files.[(sprintf "fslib-class.%s" format.Extension)] |> shouldNotContainText "unit -> int"
+ //files.[(sprintf "fslib-class.%s" format.Extension)] |> shouldContainText "Signature:"
// Check that formatting is correct
- files.["fslib-test_issue472_r.html"] |> shouldContainText "Test_Issue472_R.fmultipleargs x y"
- files.["fslib-test_issue472_r.html"] |> shouldContainText "Test_Issue472_R.ftupled(x, y)"
- files.["fslib-test_issue472.html"] |> shouldContainText "fmultipleargs x y"
- files.["fslib-test_issue472.html"] |> shouldContainText "ftupled(x, y)"
- files.["fslib-test_issue472_t.html"] |> shouldContainText "this.MultArg(arg1, arg2)"
- files.["fslib-test_issue472_t.html"] |> shouldContainText "this.MultArgTupled(arg)"
- files.["fslib-test_issue472_t.html"] |> shouldContainText "this.MultPartial arg1 arg2"
+ files.[(sprintf "fslib-test_issue472_r.%s" format.Extension)] |> shouldContainText "Test_Issue472_R.fmultipleargs x y"
+ files.[(sprintf "fslib-test_issue472_r.%s" format.Extension)] |> shouldContainText "Test_Issue472_R.ftupled(x, y)"
+ files.[(sprintf "fslib-test_issue472.%s" format.Extension)] |> shouldContainText "fmultipleargs x y"
+ files.[(sprintf "fslib-test_issue472.%s" format.Extension)] |> shouldContainText "ftupled(x, y)"
+ files.[(sprintf "fslib-test_issue472_t.%s" format.Extension)] |> shouldContainText "this.MultArg(arg1, arg2)"
+ files.[(sprintf "fslib-test_issue472_t.%s" format.Extension)] |> shouldContainText "this.MultArgTupled(arg)"
+ files.[(sprintf "fslib-test_issue472_t.%s" format.Extension)] |> shouldContainText "this.MultPartial arg1 arg2"
*)
let indxTxt = System.Text.Json.JsonSerializer.Serialize searchIndex
@@ -155,30 +189,32 @@ let ``ApiDocs works on two sample F# assemblies``() =
indxTxt |> shouldContainText "\"uri\""
indxTxt |> shouldContainText "\"content\""
indxTxt |> shouldContainText "\"title\""
- indxTxt |> shouldContainText "http://root.io/root/reference/fslib-nested-submodule-verynestedtype.html#Member"
- indxTxt |> shouldContainText "http://root.io/root/reference/fslib-test_issue472_t.html#MultArg"
+ indxTxt |> shouldContainText (sprintf "http://root.io/root/reference/fslib-nested-submodule-verynestedtype%s#Member" format.ExtensionInUrl)
+ indxTxt |> shouldContainText (sprintf "http://root.io/root/reference/fslib-test_issue472_t%s#MultArg" format.ExtensionInUrl)
indxTxt |> shouldContainText """ITest_Issue229.Name \nName \n"""
[]
-let ``Namespace summary generation works on two sample F# assemblies using XML docs``() =
+[]
+let ``Namespace summary generation works on two sample F# assemblies using XML docs`` (format:OutputFormat) =
let libraries =
[ testBin > "TestLib1.dll"
testBin > "TestLib2.dll" ]
- let output = getOutputDir "TestLib12_Namespaces"
+ let output = getOutputDir format "TestLib12_Namespaces"
let inputs = [ for lib in libraries -> ApiDocInput.FromFile(lib, mdcomments = false, substitutions=substitutions) ]
let _model, _searchIndex =
- ApiDocs.GenerateHtml(inputs, output, collectionName="TestLibs", template=docTemplate,
+ DocsGenerator(format).Run(inputs, output, collectionName="TestLibs", template=docTemplate format,
root="http://root.io/root/", substitutions=substitutions, libDirs = [testBin])
let fileNames = Directory.GetFiles(output > "reference")
let files = dict [ for f in fileNames -> Path.GetFileName(f), File.ReadAllText(f) ]
- files.["index.html"] |> shouldContainText "FsLib is a good namespace"
- files.["index.html"] |> shouldNotContainText "I tell you again FsLib is good"
- files.["fslib.html"] |> shouldContainText "FsLib is a good namespace"
- files.["fslib.html"] |> shouldContainText "I tell you again FsLib is good"
+ files.[(sprintf "index.%s" format.Extension)] |> shouldContainText "FsLib is a good namespace"
+ files.[(sprintf "index.%s" format.Extension)] |> shouldNotContainText "I tell you again FsLib is good"
+ files.[(sprintf "fslib.%s" format.Extension)] |> shouldContainText "FsLib is a good namespace"
+ files.[(sprintf "fslib.%s" format.Extension)] |> shouldContainText "I tell you again FsLib is good"
[]
-let ``ApiDocs model generation works on two sample F# assemblies``() =
+[]
+let ``ApiDocs model generation works on two sample F# assemblies`` (format:OutputFormat) =
let libraries =
[ testBin > "FsLib1.dll"
testBin > "FsLib2.dll" ]
@@ -194,7 +230,8 @@ let ``ApiDocs model generation works on two sample F# assemblies``() =
assemblies |> List.distinct |> List.sort |> shouldEqual ["FsLib1"; "FsLib2"]
[]
-let ``ApiDocs generates Go to GitHub source links``() =
+[]
+let ``ApiDocs generates Go to GitHub source links`` (format:OutputFormat) =
let libraries =
[ testBin > "FsLib1.dll"
testBin > "FsLib2.dll" ] |> fullpaths
@@ -203,29 +240,35 @@ let ``ApiDocs generates Go to GitHub source links``() =
ApiDocInput.FromFile(lib, mdcomments = true,
sourceRepo = "https://github.com/fsprojects/FSharp.Formatting/tree/master",
sourceFolder = (root > "../..")) ]
- let output = getOutputDir "FsLib12_SourceLinks"
+ let output = getOutputDir format "FsLib12_SourceLinks"
printfn "Output: %s" output
let _model, _searchIndex =
- ApiDocs.GenerateHtml
- ( inputs, output, collectionName="FsLib", template=docTemplate,
+ DocsGenerator(format).Run
+ ( inputs, output, collectionName="FsLib", template=docTemplate format,
substitutions=substitutions, libDirs = ([testBin] |> fullpaths))
let fileNames = Directory.GetFiles(output > "reference")
let files = dict [ for f in fileNames -> Path.GetFileName(f), File.ReadAllText(f) ]
- files.["fslib-class.html"] |> shouldContainText "fsdocs-source-link"
- files.["fslib-class.html"] |> shouldContainText "https://github.com/fsprojects/FSharp.Formatting/tree/master/tests/FSharp.ApiDocs.Tests/files/FsLib2/Library2.fs#L"
- files.["fslib-record.html"] |> shouldContainText "fsdocs-source-link"
- files.["fslib-record.html"] |> shouldContainText "https://github.com/fsprojects/FSharp.Formatting/tree/master/tests/FSharp.ApiDocs.Tests/files/FsLib1/Library1.fs#L"
- files.["fslib-union.html"] |> shouldContainText "fsdocs-source-link"
- files.["fslib-union.html"] |> shouldContainText "https://github.com/fsprojects/FSharp.Formatting/tree/master/tests/FSharp.ApiDocs.Tests/files/FsLib1/Library1.fs#L"
+ let onlyInHtml value =
+ match format with
+ | Html -> value
+ | Markdown -> ""
+
+ files.[(sprintf "fslib-class.%s" format.Extension)] |> shouldContainText ("fsdocs-source-link" |> onlyInHtml)
+ files.[(sprintf "fslib-class.%s" format.Extension)] |> shouldContainText "https://github.com/fsprojects/FSharp.Formatting/tree/master/tests/FSharp.ApiDocs.Tests/files/FsLib2/Library2.fs#L"
+ files.[(sprintf "fslib-record.%s" format.Extension)] |> shouldContainText ("fsdocs-source-link" |> onlyInHtml)
+ files.[(sprintf "fslib-record.%s" format.Extension)] |> shouldContainText "https://github.com/fsprojects/FSharp.Formatting/tree/master/tests/FSharp.ApiDocs.Tests/files/FsLib1/Library1.fs#L"
+ files.[(sprintf "fslib-union.%s" format.Extension)] |> shouldContainText ("fsdocs-source-link" |> onlyInHtml)
+ files.[(sprintf "fslib-union.%s" format.Extension)] |> shouldContainText "https://github.com/fsprojects/FSharp.Formatting/tree/master/tests/FSharp.ApiDocs.Tests/files/FsLib1/Library1.fs#L"
[]
-let ``ApiDocs test that cref generation works``() =
+[]
+let ``ApiDocs test that cref generation works`` (format:OutputFormat) =
let libraries =
[ testBin > "crefLib1.dll"
testBin > "crefLib2.dll"
testBin > "crefLib3.dll"
testBin > "crefLib4.dll" ] |> fullpaths
- let output = getOutputDir "crefLibs"
+ let output = getOutputDir format "crefLibs"
printfn "Output: %s" output
let inputs =
[ for lib in libraries ->
@@ -234,82 +277,83 @@ let ``ApiDocs test that cref generation works``() =
sourceFolder = (__SOURCE_DIRECTORY__ > "../.."),
mdcomments = false) ]
let _model, _searchIndex =
- ApiDocs.GenerateHtml
- ( inputs, output, collectionName="CrefLibs", template=docTemplate,
+ DocsGenerator(format).Run
+ ( inputs, output, collectionName="CrefLibs", template=docTemplate format,
substitutions=substitutions, libDirs = ([testBin] |> fullpaths))
let fileNames = Directory.GetFiles(output > "reference")
let files = dict [ for f in fileNames -> Path.GetFileName(f), File.ReadAllText(f) ]
// C# tests
// reference class in same assembly
- files.["creflib4-class1.html"] |> shouldContainText "Class2"
- files.["creflib4-class1.html"] |> shouldContainText "creflib4-class2.html"
+ files.[(sprintf "creflib4-class1.%s" format.Extension)] |> shouldContainText "Class2"
+ files.[(sprintf "creflib4-class1.%s" format.Extension)] |> shouldContainText (sprintf "creflib4-class2%s" format.ExtensionInUrl)
// reference to another assembly
- files.["creflib4-class2.html"] |> shouldContainText "Class1"
- files.["creflib4-class2.html"] |> shouldContainText "creflib1-class1.html"
+ files.[(sprintf "creflib4-class2.%s" format.Extension)] |> shouldContainText "Class1"
+ files.[(sprintf "creflib4-class2.%s" format.Extension)] |> shouldContainText (sprintf "creflib1-class1%s" format.ExtensionInUrl)
/// + no crash on unresolved reference.
- files.["creflib4-class2.html"] |> shouldContainText "Unknown__Reference"
+ files.[(sprintf "creflib4-class2.%s" format.Extension)] |> shouldContainText "Unknown__Reference"
/// reference to a member works.
- files.["creflib4-class3.html"] |> shouldContainText "Class2.Other"
- files.["creflib4-class3.html"] |> shouldContainText "creflib4-class2.html"
+ files.[(sprintf "creflib4-class3.%s" format.Extension)] |> shouldContainText "Class2.Other"
+ files.[(sprintf "creflib4-class3.%s" format.Extension)] |> shouldContainText (sprintf "creflib4-class2%s" format.ExtensionInUrl)
/// references to members work and give correct links
- files.["creflib2-class3.html"] |> shouldContainText """Class2.Other"""
- files.["creflib2-class3.html"] |> shouldContainText """and Class2.Method0"""
- files.["creflib2-class3.html"] |> shouldContainText """and Class2.Method1"""
- files.["creflib2-class3.html"] |> shouldContainText """and Class2.Method2"""
+ files.[(sprintf "creflib2-class3.%s" format.Extension)] |> shouldContainText (sprintf "Class2.Other" format.ExtensionInUrl)
+ files.[(sprintf "creflib2-class3.%s" format.Extension)] |> shouldContainText (sprintf "and Class2.Method0" format.ExtensionInUrl)
+ files.[(sprintf "creflib2-class3.%s" format.Extension)] |> shouldContainText (sprintf "and Class2.Method1" format.ExtensionInUrl)
+ files.[(sprintf "creflib2-class3.%s" format.Extension)] |> shouldContainText (sprintf "and Class2.Method2" format.ExtensionInUrl)
- files.["creflib2-class3.html"] |> shouldContainText """and GenericClass2"""
- files.["creflib2-class3.html"] |> shouldContainText """and GenericClass2.Property"""
- files.["creflib2-class3.html"] |> shouldContainText """and GenericClass2.NonGenericMethod"""
- files.["creflib2-class3.html"] |> shouldContainText """and GenericClass2.GenericMethod"""
+ files.[(sprintf "creflib2-class3.%s" format.Extension)] |> shouldContainText (sprintf "and GenericClass2" format.ExtensionInUrl)
+ files.[(sprintf "creflib2-class3.%s" format.Extension)] |> shouldContainText (sprintf "and GenericClass2.Property" format.ExtensionInUrl)
+ files.[(sprintf "creflib2-class3.%s" format.Extension)] |> shouldContainText (sprintf "and GenericClass2.NonGenericMethod" format.ExtensionInUrl)
+ files.[(sprintf "creflib2-class3.%s" format.Extension)] |> shouldContainText (sprintf "and GenericClass2.GenericMethod" format.ExtensionInUrl)
/// references to non-existent members where the type resolves give an approximation
- files.["creflib2-class3.html"] |> shouldContainText """and Class2.NotExistsProperty"""
- files.["creflib2-class3.html"] |> shouldContainText """and Class2.NotExistsMethod"""
+ files.[(sprintf "creflib2-class3.%s" format.Extension)] |> shouldContainText (sprintf "and Class2.NotExistsProperty" format.ExtensionInUrl)
+ files.[(sprintf "creflib2-class3.%s" format.Extension)] |> shouldContainText (sprintf "and Class2.NotExistsMethod" format.ExtensionInUrl)
/// reference to a corelib class works.
- files.["creflib4-class4.html"] |> shouldContainText "Assembly"
- files.["creflib4-class4.html"] |> shouldContainText "https://docs.microsoft.com/dotnet/api/system.reflection.assembly"
+ files.[(sprintf "creflib4-class4.%s" format.Extension)] |> shouldContainText "Assembly"
+ files.[(sprintf "creflib4-class4.%s" format.Extension)] |> shouldContainText "https://docs.microsoft.com/dotnet/api/system.reflection.assembly"
// F# tests (at least we not not crash for them, compiler doesn't resolve anything)
// reference class in same assembly
- files.["creflib2-class1.html"] |> shouldContainText "Class2"
- //files.["creflib2-class1.html"] |> shouldContainText "creflib2-class2.html"
+ files.[(sprintf "creflib2-class1.%s" format.Extension)] |> shouldContainText "Class2"
+ //files.[(sprintf "creflib2-class1.%s" format.Extension)] |> shouldContainText (sprintf "creflib2-class2%s" format.ExtensionInUrl)
// reference to another assembly
- files.["creflib2-class2.html"] |> shouldContainText "Class1"
- //files.["creflib2-class2.html"] |> shouldContainText "creflib1-class1.html"
+ files.[(sprintf "creflib2-class2.%s" format.Extension)] |> shouldContainText "Class1"
+ //files.[(sprintf "creflib2-class2.%s" format.Extension)] |> shouldContainText (sprintf "creflib1-class1%s" format.ExtensionInUrl)
/// + no crash on unresolved reference.
- files.["creflib2-class2.html"] |> shouldContainText "Unknown__Reference"
+ files.[(sprintf "creflib2-class2.%s" format.Extension)] |> shouldContainText "Unknown__Reference"
/// reference to a corelib class works.
- files.["creflib2-class4.html"] |> shouldContainText "Assembly"
- //files.["creflib2-class4.html"] |> shouldContainText "https://docs.microsoft.com/dotnet/api/system.reflection.assembly"
+ files.[(sprintf "creflib2-class4.%s" format.Extension)] |> shouldContainText "Assembly"
+ //files.[(sprintf "creflib2-class4.%s" format.Extension)] |> shouldContainText "https://docs.microsoft.com/dotnet/api/system.reflection.assembly"
// F# tests (fully quallified)
// reference class in same assembly
- files.["creflib2-class5.html"] |> shouldContainText "Class2"
- files.["creflib2-class5.html"] |> shouldContainText "creflib2-class2.html"
+ files.[(sprintf "creflib2-class5.%s" format.Extension)] |> shouldContainText "Class2"
+ files.[(sprintf "creflib2-class5.%s" format.Extension)] |> shouldContainText (sprintf "creflib2-class2%s" format.ExtensionInUrl)
// reference to another assembly
- files.["creflib2-class6.html"] |> shouldContainText "Class1"
- files.["creflib2-class6.html"] |> shouldContainText "creflib1-class1.html"
+ files.[(sprintf "creflib2-class6.%s" format.Extension)] |> shouldContainText "Class1"
+ files.[(sprintf "creflib2-class6.%s" format.Extension)] |> shouldContainText (sprintf "creflib1-class1%s" format.ExtensionInUrl)
/// + no crash on unresolved reference.
- files.["creflib2-class6.html"] |> shouldContainText "Unknown__Reference"
+ files.[(sprintf "creflib2-class6.%s" format.Extension)] |> shouldContainText "Unknown__Reference"
/// reference to a member works.
- files.["creflib2-class7.html"] |> shouldContainText "Class2.Other"
- files.["creflib2-class7.html"] |> shouldContainText "creflib2-class2.html"
+ files.[(sprintf "creflib2-class7.%s" format.Extension)] |> shouldContainText "Class2.Other"
+ files.[(sprintf "creflib2-class7.%s" format.Extension)] |> shouldContainText (sprintf "creflib2-class2%s" format.ExtensionInUrl)
/// reference to a corelib class works.
- files.["creflib2-class8.html"] |> shouldContainText "Assembly"
- files.["creflib2-class8.html"] |> shouldContainText "https://docs.microsoft.com/dotnet/api/system.reflection.assembly"
+ files.[(sprintf "creflib2-class8.%s" format.Extension)] |> shouldContainText "Assembly"
+ files.[(sprintf "creflib2-class8.%s" format.Extension)] |> shouldContainText "https://docs.microsoft.com/dotnet/api/system.reflection.assembly"
[]
-let ``Math in XML generated ok``() =
+[]
+let ``Math in XML generated ok`` (format:OutputFormat) =
let libraries =
[ testBin > "crefLib1.dll"
testBin > "crefLib2.dll" ] |> fullpaths
- let output = getOutputDir "crefLibs_math"
+ let output = getOutputDir format "crefLibs_math"
printfn "Output: %s" output
let inputs =
[ for lib in libraries ->
@@ -318,23 +362,24 @@ let ``Math in XML generated ok``() =
sourceFolder = (__SOURCE_DIRECTORY__ > "../.."),
mdcomments = false) ]
let _model, _searchIndex =
- ApiDocs.GenerateHtml
- ( inputs, output, collectionName="CrefLibs", template=docTemplate,
+ DocsGenerator(format).Run
+ ( inputs, output, collectionName="CrefLibs", template=docTemplate format,
substitutions=substitutions, libDirs = ([testBin] |> fullpaths))
let fileNames = Directory.GetFiles(output > "reference")
let files = dict [ for f in fileNames -> Path.GetFileName(f), File.ReadAllText(f) ]
/// math is emitted ok
- files.["creflib2-mathtest.html"] |> shouldContainText """This is XmlMath1 \(f(x)\)"""
- files.["creflib2-mathtest.html"] |> shouldContainText """This is XmlMath2 \(\left\lceil \frac{\text{end} - \text{start}}{\text{step}} \right\rceil\)"""
- files.["creflib2-mathtest.html"] |> shouldContainText """