diff --git a/Directory.Packages.props b/Directory.Packages.props index c4b67d266..fdabf4ece 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,6 +16,7 @@ + diff --git a/FSharp.Formatting.sln b/FSharp.Formatting.sln index 14dbe4c4f..3a8feb2b9 100644 --- a/FSharp.Formatting.sln +++ b/FSharp.Formatting.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29724.152 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34309.116 MinimumVisualStudioVersion = 12.0.31101.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "project", "project", "{194BD478-0DB5-44F3-A6C2-1FC75D3F3294}" ProjectSection(SolutionItems) = preProject @@ -20,7 +20,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "project", "project", "{194B EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{312E452A-1068-4804-89E7-0AFBAD5F885F}" ProjectSection(SolutionItems) = preProject - docs\_template.html = docs\_template.html docs\apidocs.fsx = docs\apidocs.fsx docs\codeformat.fsx = docs\codeformat.fsx docs\commandline.md = docs\commandline.md @@ -33,6 +32,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{312E452A-1 docs\upgrade.md = docs\upgrade.md docs\users.md = docs\users.md docs\zero-to-hero.md = docs\zero-to-hero.md + docs\_template.html = docs\_template.html EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{D0550510-0DCE-4B40-B4FB-091668E1C5FD}" @@ -46,77 +46,77 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "reference", "reference", "{ misc\templates\reference\type.cshtml = misc\templates\reference\type.cshtml EndProjectSection EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Formatting.Markdown", "src\FSharp.Formatting.Markdown\FSharp.Formatting.Markdown.fsproj", "{C44C1C05-599A-40DD-9590-465EAB8960C5}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Formatting.Markdown", "src\FSharp.Formatting.Markdown\FSharp.Formatting.Markdown.fsproj", "{C44C1C05-599A-40DD-9590-465EAB8960C5}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Formatting.CodeFormat", "src\FSharp.Formatting.CodeFormat\FSharp.Formatting.CodeFormat.fsproj", "{341EBF32-D470-4C55-99E9-55F14F7FFBB1}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Formatting.CodeFormat", "src\FSharp.Formatting.CodeFormat\FSharp.Formatting.CodeFormat.fsproj", "{341EBF32-D470-4C55-99E9-55F14F7FFBB1}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Formatting.ApiDocs", "src\FSharp.Formatting.ApiDocs\FSharp.Formatting.ApiDocs.fsproj", "{BC4946BA-2724-4524-AC50-DFC49EE154A1}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Formatting.ApiDocs", "src\FSharp.Formatting.ApiDocs\FSharp.Formatting.ApiDocs.fsproj", "{BC4946BA-2724-4524-AC50-DFC49EE154A1}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Formatting.Literate", "src\FSharp.Formatting.Literate\FSharp.Formatting.Literate.fsproj", "{65E6D541-0486-4383-B619-5CFC5D2BA2F0}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Formatting.Literate", "src\FSharp.Formatting.Literate\FSharp.Formatting.Literate.fsproj", "{65E6D541-0486-4383-B619-5CFC5D2BA2F0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.Formatting.CSharpFormat", "src\FSharp.Formatting.CSharpFormat\FSharp.Formatting.CSharpFormat.csproj", "{9AB3650B-CC24-4404-A175-A573DA928475}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "fsdocs-tool", "src\fsdocs-tool\fsdocs-tool.fsproj", "{D30F7F2B-A4E3-4A07-A1BD-ED3EB21768F8}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "fsdocs-tool", "src\fsdocs-tool\fsdocs-tool.fsproj", "{D30F7F2B-A4E3-4A07-A1BD-ED3EB21768F8}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8D44B659-E9F7-4CE4-B5DA-D37CDDCD2525}" ProjectSection(SolutionItems) = preProject tests\commonmark_spec.json = tests\commonmark_spec.json EndProjectSection EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.CodeFormat.Tests", "tests\FSharp.CodeFormat.Tests\FSharp.CodeFormat.Tests.fsproj", "{5DEBD769-D86E-4E14-ABF1-373CA91BFAA2}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.CodeFormat.Tests", "tests\FSharp.CodeFormat.Tests\FSharp.CodeFormat.Tests.fsproj", "{5DEBD769-D86E-4E14-ABF1-373CA91BFAA2}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Literate.Tests", "tests\FSharp.Literate.Tests\FSharp.Literate.Tests.fsproj", "{C22A18AB-6C54-48B4-AAC5-892499E93D4D}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Literate.Tests", "tests\FSharp.Literate.Tests\FSharp.Literate.Tests.fsproj", "{C22A18AB-6C54-48B4-AAC5-892499E93D4D}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Markdown.Tests", "tests\FSharp.Markdown.Tests\FSharp.Markdown.Tests.fsproj", "{07DE4905-050C-4378-A039-F1EF7E1F309D}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Markdown.Tests", "tests\FSharp.Markdown.Tests\FSharp.Markdown.Tests.fsproj", "{07DE4905-050C-4378-A039-F1EF7E1F309D}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.ApiDocs.Tests", "tests\FSharp.ApiDocs.Tests\FSharp.ApiDocs.Tests.fsproj", "{D2EC3D6A-35C0-4445-A9CB-AA18B12B6350}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.ApiDocs.Tests", "tests\FSharp.ApiDocs.Tests\FSharp.ApiDocs.Tests.fsproj", "{D2EC3D6A-35C0-4445-A9CB-AA18B12B6350}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProjects", "{4AE0198D-EDE5-40B0-A5CD-FC7B6F891D94}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsLib1", "tests\FSharp.ApiDocs.Tests\files\FsLib1\FsLib1.fsproj", "{AD192375-D530-40FB-A4E9-380C74CBB771}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FsLib1", "tests\FSharp.ApiDocs.Tests\files\FsLib1\FsLib1.fsproj", "{AD192375-D530-40FB-A4E9-380C74CBB771}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsLib2", "tests\FSharp.ApiDocs.Tests\files\FsLib2\FsLib2.fsproj", "{768FD0E0-5CF7-4AF0-98C9-4B848F9AFB62}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FsLib2", "tests\FSharp.ApiDocs.Tests\files\FsLib2\FsLib2.fsproj", "{768FD0E0-5CF7-4AF0-98C9-4B848F9AFB62}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TestLib1", "tests\FSharp.ApiDocs.Tests\files\TestLib1\TestLib1.fsproj", "{86326769-3D0B-423F-AD28-A194B34318D6}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "TestLib1", "tests\FSharp.ApiDocs.Tests\files\TestLib1\TestLib1.fsproj", "{86326769-3D0B-423F-AD28-A194B34318D6}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TestLib2", "tests\FSharp.ApiDocs.Tests\files\TestLib2\TestLib2.fsproj", "{48EFFECF-ECB0-4DF3-A704-B56AB07557BF}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "TestLib2", "tests\FSharp.ApiDocs.Tests\files\TestLib2\TestLib2.fsproj", "{48EFFECF-ECB0-4DF3-A704-B56AB07557BF}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "TestLib3", "tests\FSharp.ApiDocs.Tests\files\TestLib3\TestLib3.fsproj", "{52B949AA-A3F7-4894-B713-804BAEB71118}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "TestLib3", "tests\FSharp.ApiDocs.Tests\files\TestLib3\TestLib3.fsproj", "{52B949AA-A3F7-4894-B713-804BAEB71118}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "csharpSupport", "tests\FSharp.ApiDocs.Tests\files\csharpSupport\csharpSupport.csproj", "{DA7BA2FA-447E-41F3-88D9-00CF3E052E2C}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "crefLib1", "tests\FSharp.ApiDocs.Tests\files\crefLib1\crefLib1.fsproj", "{A0C8DD00-BD08-48D6-B257-5A838E5DA819}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "crefLib1", "tests\FSharp.ApiDocs.Tests\files\crefLib1\crefLib1.fsproj", "{A0C8DD00-BD08-48D6-B257-5A838E5DA819}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "crefLib2", "tests\FSharp.ApiDocs.Tests\files\crefLib2\crefLib2.fsproj", "{55728B9D-1EDE-4A40-B439-1EB0B3F77B72}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "crefLib2", "tests\FSharp.ApiDocs.Tests\files\crefLib2\crefLib2.fsproj", "{55728B9D-1EDE-4A40-B439-1EB0B3F77B72}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "crefLib3", "tests\FSharp.ApiDocs.Tests\files\crefLib3\crefLib3.csproj", "{08029B28-A5EA-42DB-AB4E-9C6BA9EF9441}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "crefLib4", "tests\FSharp.ApiDocs.Tests\files\crefLib4\crefLib4.csproj", "{98624699-1B2F-4636-A3F7-EC72343CB2FD}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Formatting.Common", "src\FSharp.Formatting.Common\FSharp.Formatting.Common.fsproj", "{91BAD90E-BF3B-4646-A1A7-1568F8F25075}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Formatting.Common", "src\FSharp.Formatting.Common\FSharp.Formatting.Common.fsproj", "{91BAD90E-BF3B-4646-A1A7-1568F8F25075}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Formatting.TestHelpers", "tests\FSharp.Formatting.TestHelpers\FSharp.Formatting.TestHelpers.fsproj", "{0B552F94-33FE-4037-9C17-1EB2A885F263}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Formatting.TestHelpers", "tests\FSharp.Formatting.TestHelpers\FSharp.Formatting.TestHelpers.fsproj", "{0B552F94-33FE-4037-9C17-1EB2A885F263}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "AttributesTestLib", "tests\FSharp.ApiDocs.Tests\files\AttributesTestLib\AttributesTestLib.fsproj", "{B266D118-15AE-4BE6-809E-E9E30CE5C62F}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "AttributesTestLib", "tests\FSharp.ApiDocs.Tests\files\AttributesTestLib\AttributesTestLib.fsproj", "{B266D118-15AE-4BE6-809E-E9E30CE5C62F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4089E333-3857-4771-B209-5673EF4EEDA3}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Formatting", "src\FSharp.Formatting\FSharp.Formatting.fsproj", "{CB78F0EA-8005-4735-A02C-B86CEDC29D85}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Formatting", "src\FSharp.Formatting\FSharp.Formatting.fsproj", "{CB78F0EA-8005-4735-A02C-B86CEDC29D85}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sidebyside", "sidebyside", "{C3E359B5-DB2D-4AA4-A08C-77BAA0E94D21}" ProjectSection(SolutionItems) = preProject - docs\templates\leftside\_template.html = docs\templates\leftside\_template.html docs\sidebyside\sideextensions.md = docs\sidebyside\sideextensions.md docs\sidebyside\sidemarkdown.md = docs\sidebyside\sidemarkdown.md docs\sidebyside\sidescript.fsx = docs\sidebyside\sidescript.fsx + docs\templates\leftside\_template.html = docs\templates\leftside\_template.html EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{C7804F57-7FC6-4CF6-BDF6-127D6F9EBEA6}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "leftside", "leftside", "{188DC91F-2202-4495-ACD2-542D7C30364E}" ProjectSection(SolutionItems) = preProject - docs\templates\leftside\_template.html = docs\templates\leftside\_template.html docs\templates\leftside\styling.md = docs\templates\leftside\styling.md + docs\templates\leftside\_template.html = docs\templates\leftside\_template.html EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "content", "content", "{FAD5C374-4748-4A3D-A435-FFA425916F3A}" @@ -127,6 +127,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "content", "content", "{FAD5 docs\content\fsdocs-theme-toggle.js = docs\content\fsdocs-theme-toggle.js EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "init", "init", "{A70497A6-C9C3-46D8-A9FB-0E4BB6AD3309}" + ProjectSection(SolutionItems) = preProject + docs\templates\init\.index_md_template.md = docs\templates\init\.index_md_template.md + docs\templates\init\.literate_sample_template.fsx = docs\templates\init\.literate_sample_template.fsx + docs\templates\init\.logo.svg = docs\templates\init\.logo.svg + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -250,6 +257,7 @@ Global {768FD0E0-5CF7-4AF0-98C9-4B848F9AFB62} = {4AE0198D-EDE5-40B0-A5CD-FC7B6F891D94} {86326769-3D0B-423F-AD28-A194B34318D6} = {4AE0198D-EDE5-40B0-A5CD-FC7B6F891D94} {48EFFECF-ECB0-4DF3-A704-B56AB07557BF} = {4AE0198D-EDE5-40B0-A5CD-FC7B6F891D94} + {52B949AA-A3F7-4894-B713-804BAEB71118} = {4AE0198D-EDE5-40B0-A5CD-FC7B6F891D94} {DA7BA2FA-447E-41F3-88D9-00CF3E052E2C} = {4AE0198D-EDE5-40B0-A5CD-FC7B6F891D94} {A0C8DD00-BD08-48D6-B257-5A838E5DA819} = {4AE0198D-EDE5-40B0-A5CD-FC7B6F891D94} {55728B9D-1EDE-4A40-B439-1EB0B3F77B72} = {4AE0198D-EDE5-40B0-A5CD-FC7B6F891D94} @@ -263,7 +271,7 @@ Global {C7804F57-7FC6-4CF6-BDF6-127D6F9EBEA6} = {312E452A-1068-4804-89E7-0AFBAD5F885F} {188DC91F-2202-4495-ACD2-542D7C30364E} = {C7804F57-7FC6-4CF6-BDF6-127D6F9EBEA6} {FAD5C374-4748-4A3D-A435-FFA425916F3A} = {312E452A-1068-4804-89E7-0AFBAD5F885F} - {52B949AA-A3F7-4894-B713-804BAEB71118} = {4AE0198D-EDE5-40B0-A5CD-FC7B6F891D94} + {A70497A6-C9C3-46D8-A9FB-0E4BB6AD3309} = {C7804F57-7FC6-4CF6-BDF6-127D6F9EBEA6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {76F121F8-70E0-49FB-9ADF-C7B660C0EB67} diff --git a/build.fsx b/build.fsx index 6706d59d7..a9866d70a 100644 --- a/build.fsx +++ b/build.fsx @@ -50,9 +50,13 @@ let testStage = $"dotnet test {solutionFile} --configuration {configuration} --no-build --blame --logger trx --results-directory TestResults -tl" } -pipeline "CI" { - lintStage +let buildStage = + stage "Build" { + run $"dotnet restore {solutionFile} -tl" + run $"dotnet build {solutionFile} --configuration {configuration} -tl" + } +let cleanStage = stage "Clean" { run (fun _ -> !!artifactsDir ++ "temp" |> Shell.cleanDirs @@ -60,13 +64,14 @@ pipeline "CI" { [ "bin"; "temp"; "tests/bin" ] |> Seq.iter Directory.ensure) } - stage "Build" { - run $"dotnet restore {solutionFile} -tl" - run $"dotnet build {solutionFile} --configuration {configuration} -tl" - } - +let nugetStage = stage "NuGet" { run $"dotnet pack {solutionFile} --output \"{artifactsDir}\" --configuration {configuration} -tl" } +pipeline "CI" { + lintStage + cleanStage + buildStage + nugetStage testStage stage "GenerateDocs" { @@ -93,4 +98,11 @@ pipeline "Verify" { runIfOnlySpecified true } +pipeline "BuildAndPack" { + cleanStage + buildStage + nugetStage + runIfOnlySpecified true +} + tryPrintPipelineCommandHelp () diff --git a/docs/templates/init/.index_md_template.md b/docs/templates/init/.index_md_template.md new file mode 100644 index 000000000..0f369f310 --- /dev/null +++ b/docs/templates/init/.index_md_template.md @@ -0,0 +1,24 @@ +# Welcome to your documentation with fsdocs! + +For full guides please visit [the FSharp.Formatting](https://fsprojects.github.io/FSharp.Formatting/) documentation pages. + +As a quickstart you can start with changing this file with **any** _markdown_ `content` you like. + +Assuming you have initialized fsdocs with default settings, you can start the preview server with: + +``` +fsdocs watch +``` + +This will start a local server on port 8091, meaning you should be able to see content of this file rendered on [http://localhost:8901/](http://localhost:8901/) + +while you can write code blocks in markdown like this: + +```fsharp +let a = 42 +``` + +fsdocs supports a more powerful variant of fsharp scripts called [literate scripts](https://fsprojects.github.io/FSharp.Formatting/literate.html). + +in literate scripts, you can write markdown and fsharp code in the same file, and fsdocs will render the markdown and execute the fsharp code. +Check the `literate_sample.fsx` file in this folder for an example. \ No newline at end of file diff --git a/docs/templates/init/.literate_sample_template.fsx b/docs/templates/init/.literate_sample_template.fsx new file mode 100644 index 000000000..373e7ef2f --- /dev/null +++ b/docs/templates/init/.literate_sample_template.fsx @@ -0,0 +1,32 @@ +(** +# Literate script example + +- A multi-line comment starting with (`**` and ending with `*`) is turned into text and is processed using the F# Markdown processor (which supports standard Markdown commands). + +- A single-line comment starting with (`***` and ending with `***`) is treated as a special command. The command can consist of key, key: value or key=value pairs. + +The rest is just plain F# code goodness: +*) + +let helloWorld () = printfn "Hello world!" + +(** +## Using fsi evaluation + +If you run fsdocs with the `--eval` flag, literate scripts will be evaluated by fsi and the output will can be included in the documentation using special commands such as + +`(*** include-value ***)` or `(*** include-it ***)` : + +*) + +// include a value by name via (*** include-value: name ***) +let numbers = [ 0..99 ] +(*** include-value: numbers ***) + +// include the fsi output of an expression via (*** include-it ***) +List.sum numbers +(*** include-it ***) + +(** +Literate scripts can move your documentation to the next level, because you can make sure that the code in your documentation is always up-to-date and shows results from the latest version. +*) diff --git a/docs/templates/init/.logo.png b/docs/templates/init/.logo.png new file mode 100644 index 000000000..4dbbd9040 Binary files /dev/null and b/docs/templates/init/.logo.png differ diff --git a/src/fsdocs-tool/BuildCommand.fs b/src/fsdocs-tool/BuildCommand.fs index acca2b3ae..e94790aa6 100644 --- a/src/fsdocs-tool/BuildCommand.fs +++ b/src/fsdocs-tool/BuildCommand.fs @@ -2,6 +2,7 @@ namespace fsdocs open System.Collections.Concurrent open CommandLine +open Spectre.Console open System open System.Diagnostics @@ -1579,29 +1580,17 @@ type CoreBuildOptions(watch) = // to .nuget\packages\fsdocs-tool\7.1.7\templates let dir = Path.GetDirectoryName(typeof.Assembly.Location) - let defaultTemplateAttempt1 = - Path.GetFullPath(Path.Combine(dir, "..", "..", "..", "templates", "_template.html")) - // This is in-repo only - let defaultTemplateAttempt2 = - Path.GetFullPath(Path.Combine(dir, "..", "..", "..", "..", "..", "docs", "_template.html")) + // get template locations for in-package and in-repo and decide which to use later + let inNugetPackageLocations = Common.InNugetPackageLocations(Path.Combine(dir, "..", "..", "..")) + let inThisRepoLocations = Common.InDocsFolderLocations(Path.Combine(dir, "..", "..", "..", "..", "..", "docs")) let defaultTemplate = if this.nodefaultcontent then None - else if - (try - File.Exists(defaultTemplateAttempt1) - with _ -> - false) - then - Some defaultTemplateAttempt1 - elif - (try - File.Exists(defaultTemplateAttempt2) - with _ -> - false) - then - Some defaultTemplateAttempt2 + else if inNugetPackageLocations.AllLocationsExist() then + Some inNugetPackageLocations.``templates/template.html``.Path + elif inThisRepoLocations.AllLocationsExist() then + Some inThisRepoLocations.``template.html``.Path else None @@ -1610,33 +1599,22 @@ type CoreBuildOptions(watch) = // The "extras" content goes in "." // From .nuget\packages\fsdocs-tool\7.1.7\tools\net6.0\any // to .nuget\packages\fsdocs-tool\7.1.7\extras - let attempt1 = Path.GetFullPath(Path.Combine(dir, "..", "..", "..", "extras")) - - if - (try - Directory.Exists(attempt1) - with _ -> - false) - then - printfn "using extra content from %s" attempt1 - (attempt1, ".") - else + if inNugetPackageLocations.AllLocationsExist() then + printfn "using extra content from %s" inNugetPackageLocations.extras.Path + (inNugetPackageLocations.extras.Path, ".") + else if // This is for in-repo use only, assuming we are executing directly from // src\fsdocs-tool\bin\Debug\net6.0\fsdocs.exe // src\fsdocs-tool\bin\Release\net6.0\fsdocs.exe - let attempt2 = - Path.GetFullPath(Path.Combine(dir, "..", "..", "..", "..", "..", "docs", "content")) - - if - (try - Directory.Exists(attempt2) - with _ -> - false) - then - printfn "using extra content from %s" attempt2 - (attempt2, "content") - else - printfn "no extra content found at %s or %s" attempt1 attempt2 ] + inThisRepoLocations.AllLocationsExist() + then + printfn "using extra content from %s" inThisRepoLocations.content.Path + (inThisRepoLocations.content.Path, "content") + else + printfn + "no extra content found at %s or %s" + inNugetPackageLocations.extras.Path + inThisRepoLocations.content.Path ] // The incremental state (as well as the files written to disk) let mutable latestApiDocModel = None @@ -1699,7 +1677,7 @@ type CoreBuildOptions(watch) = printfn "note, no template file '%s' found, and no default template at '%s'" templateFiles - defaultTemplateAttempt1 + inThisRepoLocations.``template.html``.Path OutputKind.Html, None diff --git a/src/fsdocs-tool/InitCommand.fs b/src/fsdocs-tool/InitCommand.fs new file mode 100644 index 000000000..a4dd05e0d --- /dev/null +++ b/src/fsdocs-tool/InitCommand.fs @@ -0,0 +1,141 @@ +namespace fsdocs + +open System.IO +open CommandLine + +open Spectre.Console + +[] +type InitCommand() = + + let dir = Path.GetDirectoryName(typeof.Assembly.Location) + + // get template locations for in-package and in-repo files and decide which to use later + let inNugetPackageLocations = Common.InNugetPackageLocations(Path.Combine(dir, "..", "..", "..")) + let inThisRepoLocations = Common.InDocsFolderLocations(Path.Combine(dir, "..", "..", "..", "..", "..", "docs")) + + [] + member val output: string = "docs" with get, set + + [] + member val force: bool = false with get, set + + [] + member val ``non-interactive``: bool = false with get, set + + member this.Execute() = + + let docsOutputPath = Path.GetFullPath(this.output) + let initLocations = Common.InDocsFolderLocations(docsOutputPath) + + let ensureOutputDirs () = + [ docsOutputPath; initLocations.DocsFolder.Path; initLocations.img.Path ] + |> List.iter ensureDirectory + + if inNugetPackageLocations.AllLocationsExist() then + // if the in-package locations exist, this means fsdocs is run from the nuget package. + try + ensureOutputDirs () + + let fileMap = + [ inNugetPackageLocations.``templates/template.html``, initLocations.``template.html``.Path + inNugetPackageLocations.``templates/template.ipynb``, initLocations.``template.ipynb``.Path + inNugetPackageLocations.``templates/template.tex``, initLocations.``template.tex``.Path + inNugetPackageLocations.Dockerfile, initLocations.Dockerfile.Path + inNugetPackageLocations.``Nuget.config``, initLocations.``Nuget.config``.Path + inNugetPackageLocations.``extras/content/img/badge-binder.svg``, + initLocations.``img/badge-binder.svg``.Path + inNugetPackageLocations.``extras/content/img/badge-notebook.svg``, + initLocations.``img/badge-notebook.svg``.Path + inNugetPackageLocations.``extras/content/img/badge-script.svg``, + initLocations.``img/badge-script.svg``.Path + // these files must be renamed, because files prefixed with a dot are otherwise ignored by fsdocs. We want this in the source repo, but not in the output of this command. + inNugetPackageLocations.``templates/init/.logo.png``, + Path.GetFullPath(Path.Combine(initLocations.img.Path, "logo.png")) + inNugetPackageLocations.``templates/init/.index_md_template.md``, + Path.GetFullPath(Path.Combine(initLocations.DocsFolder.Path, "index.md")) + inNugetPackageLocations.``templates/init/.literate_sample_template.fsx``, + Path.GetFullPath(Path.Combine(initLocations.DocsFolder.Path, "literate_sample.fsx")) ] + + fileMap + |> List.map (fun (src, dst) -> + src, + dst, + if this.``non-interactive`` then + true + else + Common.CLI.confirmFileCreation dst src.Description) + |> List.iter (fun (src, dst, copy) -> + if copy then + File.Copy(src.Path, dst, this.force)) + + printfn "" + printfn "a basic fsdocs scaffold has been created in %s." this.output + printfn "" + printfn "check it out by running 'dotnet fsdocs watch' !" + + 0 + with _ as exn -> + printfn "Error: %s" exn.Message + 1 + + elif inThisRepoLocations.AllLocationsExist() then + // if the in-repo locations exist, this means fsdocs is run from inside the FSharp.Formatting repo itself. + + try + ensureOutputDirs () + + let fileMap = + [ (inThisRepoLocations.``template.html``, initLocations.``template.html``.Path) + (inThisRepoLocations.``template.ipynb``, initLocations.``template.ipynb``.Path) + (inThisRepoLocations.``template.tex``, initLocations.``template.tex``.Path) + (inThisRepoLocations.Dockerfile, initLocations.Dockerfile.Path) + (inThisRepoLocations.``Nuget.config``, initLocations.``Nuget.config``.Path) + (inThisRepoLocations.``img/badge-binder.svg``, initLocations.``img/badge-binder.svg``.Path) + (inThisRepoLocations.``img/badge-notebook.svg``, initLocations.``img/badge-notebook.svg``.Path) + (inThisRepoLocations.``img/badge-script.svg``, initLocations.``img/badge-script.svg``.Path) + // these files must be renamed, because files prefixed with a dot are otherwise ignored by fsdocs. We want this in the source repo, but not in the output of this command. + (inThisRepoLocations.``templates/init/.logo.png``, + Path.GetFullPath(Path.Combine(initLocations.img.Path, "logo.png"))) + (inThisRepoLocations.``templates/init/.index_md_template.md``, + Path.GetFullPath(Path.Combine(initLocations.DocsFolder.Path, "index.md"))) + (inThisRepoLocations.``templates/init/.literate_sample_template.fsx``, + Path.GetFullPath(Path.Combine(initLocations.DocsFolder.Path, "literate_sample.fsx"))) ] + + fileMap + |> List.map (fun (src, dst) -> + src, + dst, + if this.``non-interactive`` then + true + else + Common.CLI.confirmFileCreation dst src.Description) + |> List.iter (fun (src, dst, copy) -> + if copy then + File.Copy(src.Path, dst, this.force)) + + printfn "" + printfn "a basic fsdocs scaffold has been created in %s." this.output + printfn "" + printfn "check it out by running 'dotnet fsdocs watch' !" + + 0 + with _ as exn -> + printfn "Error: %s" exn.Message + 1 + else + printfn + "no sources for default files found from either %s or %s" + inNugetPackageLocations.NugetPackageRootPath.Path + inThisRepoLocations.DocsFolder.Path + + 1 diff --git a/src/fsdocs-tool/Options.fs b/src/fsdocs-tool/Options.fs index 97928d046..cba19c5ed 100644 --- a/src/fsdocs-tool/Options.fs +++ b/src/fsdocs-tool/Options.fs @@ -34,3 +34,302 @@ module Common = if b then printf "\nPress any key to continue ..." System.Console.ReadKey() |> ignore + + open System.IO + + [] + module DefaultLocationDescriptions = + + //folders + [] + let ``docs folder`` = "the path to the folder that contains the inputs (documentation) for fsdocs." + + let ``nuget package root path`` = + "the root path of the nuget package, e.g. when the tool is installed via `dotnet tool install`." + + [] + let ``templates`` = "contains additional default files (e.g., default files for the `init` command)" + + [] + let ``extras`` = "contains additional default files (e.g., default files for the `init` command)" + + [] + let ``templates/init`` = "contains the default files for the init command." + + [] + let ``img`` = "base folder to contain all images for your documentation" + + [] + let ``content`` = "contains additional content (e.g., custom css themes)" + + // files in the docs folder + [] + let ``_template.html`` = + "The root html template used for creating web pages. Documentation pages will all be based on this file with substitutions. If you do not want to customize this file, it is recommended to NOT include it in your docs folder and therefore use the default file that comes with the tool." + + [] + let ``_template.ipynb`` = + "The root ipynb template used for creating notebooks. Notebooks of your documentation pages will all be based on this file with substitutions. If you do not want to customize this file, it is recommended to NOT include it in your docs folder and therefore use the default file that comes with the tool." + + [] + let ``_template.tex`` = + "The root tex template used for creating LaTeX. LaTeX versions of your documentation pages will all be based on this file with substitutions. If you do not want to customize this file, it is recommended to NOT include it in your docs folder and therefore use the default file that comes with the tool." + + [] + let ``Dockerfile`` = + "Dockerfile used for setting up a binder instance that can host the generated notebooks. Include this file if you plan to provide binder links to generated notebooks." + + [] + let ``Nuget.config`` = + "Additional nuget sources used for setting up a binder instance that can host the generated notebooks. Include this file if you plan to provide binder links to generated notebooks." + + [] + let ``img/badge-binder.svg`` = "A badge that can be used for adding pretty link buttons to binder." + + [] + let ``img/badge-notebook.svg`` = + "A badge that can be used for adding pretty download link buttons for generated notebooks." + + [] + let ``img/badge-script.svg`` = "A badge that can be used for adding pretty download link buttons for scripts." + + [] + let ``img/logo.png`` = "The logo of the project." + + // specific files for the init command + [] + let ``templates/init/.logo.png`` = "A logo placeholder for better preview of how pages will look with a logo." + + [] + let ``templates/init/.index_md_template.md`` = + "A basic landing page markdown template that showcases how markdown files will be rendered." + + [] + let ``templates/init/.literate_sample_template.fsx`` = + "A basic literate script that showcases how literate scripts will be rendered." + + type AnnotatedPath = + { Path: string + Description: string } + + static member Combine(ap: AnnotatedPath, path, ?description) = + { Path = Path.Combine(ap.Path, path) |> Path.GetFullPath + Description = defaultArg description "" } + + /// + /// A set of default locations in a folder containing documentation inputs for fsdocs. + /// + /// When the fsdocs tool binary is called directly via + /// + /// `src\fsdocs-tool\bin\Debug\net6.0\fsdocs.exe` or `src\fsdocs-tool\bin\Release\net6.0\fsdocs.exe`, + /// + /// these locations can also be used + /// + /// - as default content for the `watch` and `build` commands when no user equivalents present and `nodefaultcontent` is not set to true. This can be achieved by using the relative assembly path (plus "/docs") of the command classes as `docsFolderPath`. + /// + /// - as output paths of the `init` command to initialize a default docs folder structure. + /// + /// because the paths will exist relative to the FSharp.Formatting repo root path. + /// + type InDocsFolderLocations(docsFolderPath) = + + // DocsFolderPath : the path to the docs folder which is used as the base path to construct the other paths. + // note that this folder is not necessarily named "docs", it can be any location that is used as the base folder containing inputs for fsdocs. + member _.DocsFolder = + { Path = docsFolderPath + Description = DefaultLocationDescriptions.``docs folder`` } + + // default folder locations based on the docs folder path + member this.templates = + AnnotatedPath.Combine(this.DocsFolder, "templates", DefaultLocationDescriptions.``templates``) + + member this.``templates/init`` = + AnnotatedPath.Combine(this.templates, "init", DefaultLocationDescriptions.``templates/init``) + + member this.content = AnnotatedPath.Combine(this.DocsFolder, "content", DefaultLocationDescriptions.content) + member this.img = AnnotatedPath.Combine(this.DocsFolder, "img", DefaultLocationDescriptions.``img``) + + // specific files in the docs folder. + member this.``template.html`` = + AnnotatedPath.Combine(this.DocsFolder, "_template.html", DefaultLocationDescriptions.``_template.html``) + + member this.``template.ipynb`` = + AnnotatedPath.Combine(this.DocsFolder, "_template.ipynb", DefaultLocationDescriptions.``_template.ipynb``) + + member this.``template.tex`` = + AnnotatedPath.Combine(this.DocsFolder, "_template.tex", DefaultLocationDescriptions.``_template.tex``) + + member this.Dockerfile = + AnnotatedPath.Combine(this.DocsFolder, "Dockerfile", DefaultLocationDescriptions.Dockerfile) + + member this.``Nuget.config`` = + AnnotatedPath.Combine(this.DocsFolder, "Nuget.config", DefaultLocationDescriptions.``Nuget.config``) + + member this.``img/badge-binder.svg`` = + AnnotatedPath.Combine(this.img, "badge-binder.svg", DefaultLocationDescriptions.``img/badge-binder.svg``) + + member this.``img/badge-notebook.svg`` = + AnnotatedPath.Combine( + this.img, + "badge-notebook.svg", + DefaultLocationDescriptions.``img/badge-notebook.svg`` + ) + + member this.``img/badge-script.svg`` = + AnnotatedPath.Combine(this.img, "badge-script.svg", DefaultLocationDescriptions.``img/badge-script.svg``) + + // specific files for the init command. Note that these typically only exist in the FSharp.Formatting repo because they are to be copied and renamed on running `fsdocs init``` + member this.``templates/init/.logo.png`` = + AnnotatedPath.Combine( + this.``templates/init``, + ".logo.png", + DefaultLocationDescriptions.``templates/init/.logo.png`` + ) + + member this.``templates/init/.index_md_template.md`` = + AnnotatedPath.Combine( + this.``templates/init``, + ".index_md_template.md", + DefaultLocationDescriptions.``templates/init/.index_md_template.md`` + ) + + member this.``templates/init/.literate_sample_template.fsx`` = + AnnotatedPath.Combine( + this.``templates/init``, + ".literate_sample_template.fsx", + DefaultLocationDescriptions.``templates/init/.literate_sample_template.fsx`` + ) + + /// + /// returns true if all files and folders of this location exist. + /// + member this.AllLocationsExist() = + try + Directory.Exists(this.DocsFolder.Path) + && Directory.Exists(this.templates.Path) + && Directory.Exists(this.``templates/init``.Path) + && Directory.Exists(this.content.Path) + && Directory.Exists(this.img.Path) + && File.Exists(this.``template.html``.Path) + && File.Exists(this.``template.ipynb``.Path) + && File.Exists(this.``template.tex``.Path) + && File.Exists(this.Dockerfile.Path) + && File.Exists(this.``img/badge-binder.svg``.Path) + && File.Exists(this.``img/badge-notebook.svg``.Path) + && File.Exists(this.``img/badge-script.svg``.Path) + && File.Exists(this.``templates/init/.logo.png``.Path) + && File.Exists(this.``templates/init/.index_md_template.md``.Path) + && File.Exists(this.``templates/init/.literate_sample_template.fsx``.Path) + with _ -> + false + + /// + /// a set of default locations in the nuget package created for fsdocs-tool. + /// these files are to be used when fsdocs is run as dotnet tool installed via `dotnet tool install` in the following scenarios: + /// + /// - as default files when running watch or build when there are no user equivalents present and `nodefaultcontent` is not set to true + /// + /// - as content of the output of the `init` command to initialize a default docs folder structure. + /// + /// Note that the path of these files will always be combined with the given `assemblyPath` because the cli tool will query it's own path on runtime via reflection. + /// + type InNugetPackageLocations(nugetPackageRootPath) = + + // PackageRootPath : the root path of the nuget package, e.g. when the tool is installed via `dotnet tool install`. + // for example, default on windows would be: ~\.nuget\packages\fsdocs-tool\20.0.0-alpha-010 + member _.NugetPackageRootPath = + { Path = nugetPackageRootPath + Description = + "the root path of the nuget package, e.g. when the tool is installed via `dotnet tool install`." } + + // default folder locations relative to the package root path + member this.templates = + AnnotatedPath.Combine(this.NugetPackageRootPath, "templates", DefaultLocationDescriptions.templates) + + member this.``templates/init`` = + AnnotatedPath.Combine(this.templates, "init", DefaultLocationDescriptions.templates) + + member this.extras = AnnotatedPath.Combine(this.NugetPackageRootPath, "extras") + member this.``extras/content`` = AnnotatedPath.Combine(this.extras, "content") + member this.``extras/content/img`` = AnnotatedPath.Combine(this.``extras/content``, "img") + + // specific files in this folder structure that might need special treatment instead of just copy pasting + member this.``templates/template.html`` = AnnotatedPath.Combine(this.templates, "_template.html") + member this.``templates/template.ipynb`` = AnnotatedPath.Combine(this.templates, "_template.ipynb") + member this.``templates/template.tex`` = AnnotatedPath.Combine(this.templates, "_template.tex") + member this.Dockerfile = AnnotatedPath.Combine(this.extras, "Dockerfile") + member this.``Nuget.config`` = AnnotatedPath.Combine(this.extras, "Nuget.config") + + member this.``extras/content/img/badge-binder.svg`` = + AnnotatedPath.Combine( + this.``extras/content/img``, + "badge-binder.svg", + DefaultLocationDescriptions.``img/badge-binder.svg`` + ) + + member this.``extras/content/img/badge-notebook.svg`` = + AnnotatedPath.Combine( + this.``extras/content/img``, + "badge-notebook.svg", + DefaultLocationDescriptions.``img/badge-notebook.svg`` + ) + + member this.``extras/content/img/badge-script.svg`` = + AnnotatedPath.Combine( + this.``extras/content/img``, + "badge-script.svg", + DefaultLocationDescriptions.``img/badge-script.svg`` + ) + // specific files for the init command + member this.``templates/init/.logo.png`` = AnnotatedPath.Combine(this.``templates/init``, ".logo.png") + + member this.``templates/init/.index_md_template.md`` = + AnnotatedPath.Combine(this.``templates/init``, ".index_md_template.md") + + member this.``templates/init/.literate_sample_template.fsx`` = + AnnotatedPath.Combine(this.``templates/init``, ".literate_sample_template.fsx") + + /// + /// returns true if all special files and folders of this location exist. + /// + member this.AllLocationsExist() = + try + Directory.Exists(this.templates.Path) + && Directory.Exists(this.extras.Path) + && Directory.Exists(this.``extras/content``.Path) + && Directory.Exists(this.``extras/content/img``.Path) + && File.Exists(this.``templates/template.html``.Path) + && File.Exists(this.``templates/template.ipynb``.Path) + && File.Exists(this.``templates/template.tex``.Path) + && File.Exists(this.Dockerfile.Path) + && File.Exists(this.``extras/content/img/badge-binder.svg``.Path) + && File.Exists(this.``extras/content/img/badge-notebook.svg``.Path) + && File.Exists(this.``extras/content/img/badge-script.svg``.Path) + && File.Exists(this.``templates/init/.logo.png``.Path) + && File.Exists(this.``templates/init/.index_md_template.md``.Path) + && File.Exists(this.``templates/init/.literate_sample_template.fsx``.Path) + with _ -> + false + + module CLI = + open Spectre.Console + + let confirmFileCreation (path: string) (description: string) = + let table = + Table() + .AddColumn(new TableColumn("""[bold]Default file[/]""")) + .AddColumn(new TableColumn($"""[green]{Path.GetFileName(path)}[/]""")) + .AddEmptyRow() + .AddRow("""[bold]Will be created at[/]""", $"""[green]{path}[/]""") + .AddEmptyRow() + .AddRow("""[bold]Description[/]""", $"""{description}""") + + AnsiConsole.WriteLine() + // AnsiConsole.MarkupLine $"""[bold]Default file:[/] [green]{Path.GetFileName(path)}[/]""" + // AnsiConsole.MarkupLine $"""[bold]Will be created at[/] [green]{path}[/]""" + // AnsiConsole.MarkupLine $"""[bold]Description:[/] {description}""" + // AnsiConsole.WriteLine() + AnsiConsole.Write(table) + let conf = AnsiConsole.Confirm $"""[bold]Do you want to create this file?[/]""" + AnsiConsole.WriteLine() + conf diff --git a/src/fsdocs-tool/Program.fs b/src/fsdocs-tool/Program.fs index be5df76fb..b023264c0 100644 --- a/src/fsdocs-tool/Program.fs +++ b/src/fsdocs-tool/Program.fs @@ -8,9 +8,10 @@ do () [] let main argv = CommandLine.Parser.Default - .ParseArguments(argv) + .ParseArguments(argv) .MapResult( (fun (opts: BuildCommand) -> opts.Execute()), (fun (opts: WatchCommand) -> opts.Execute()), + (fun (opts: InitCommand) -> opts.Execute()), (fun _ -> 1) ) diff --git a/src/fsdocs-tool/fsdocs-tool.fsproj b/src/fsdocs-tool/fsdocs-tool.fsproj index f30c81c08..6e2e044af 100644 --- a/src/fsdocs-tool/fsdocs-tool.fsproj +++ b/src/fsdocs-tool/fsdocs-tool.fsproj @@ -19,6 +19,7 @@ + @@ -27,10 +28,14 @@ + + + + @@ -46,6 +51,7 @@ + \ No newline at end of file