forked from tsai/mathnet-numerics
39 changed files with 305 additions and 1252 deletions
@ -0,0 +1,6 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<configuration> |
||||
|
<solution> |
||||
|
<add key="disableSourceControlIntegration" value="true" /> |
||||
|
</solution> |
||||
|
</configuration> |
||||
@ -0,0 +1,136 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<PropertyGroup> |
||||
|
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir> |
||||
|
|
||||
|
<!-- Enable the restore command to run before builds --> |
||||
|
<RestorePackages Condition=" '$(RestorePackages)' == '' ">false</RestorePackages> |
||||
|
|
||||
|
<!-- Property that enables building a package from a project --> |
||||
|
<BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage> |
||||
|
|
||||
|
<!-- Determines if package restore consent is required to restore packages --> |
||||
|
<RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent> |
||||
|
|
||||
|
<!-- Download NuGet.exe if it does not already exist --> |
||||
|
<DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">false</DownloadNuGetExe> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup Condition=" '$(PackageSources)' == '' "> |
||||
|
<!-- Package sources used to restore packages. By default, registered sources under %APPDATA%\NuGet\NuGet.Config will be used --> |
||||
|
<!-- The official NuGet package source (https://www.nuget.org/api/v2/) will be excluded if package sources are specified and it does not appear in the list --> |
||||
|
<!-- |
||||
|
<PackageSource Include="https://www.nuget.org/api/v2/" /> |
||||
|
<PackageSource Include="https://my-nuget-source/nuget/" /> |
||||
|
--> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'"> |
||||
|
<!-- Windows specific commands --> |
||||
|
<NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath> |
||||
|
<PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'"> |
||||
|
<!-- We need to launch nuget.exe with the mono command if we're not on windows --> |
||||
|
<NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath> |
||||
|
<PackagesConfig>packages.config</PackagesConfig> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<!-- NuGet command --> |
||||
|
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\NuGet.exe</NuGetExePath> |
||||
|
<PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources> |
||||
|
|
||||
|
<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand> |
||||
|
<NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 $(NuGetExePath)</NuGetCommand> |
||||
|
|
||||
|
<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir> |
||||
|
|
||||
|
<RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch> |
||||
|
<NonInteractiveSwitch Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' ">-NonInteractive</NonInteractiveSwitch> |
||||
|
|
||||
|
<PaddedSolutionDir Condition=" '$(OS)' == 'Windows_NT'">"$(SolutionDir) "</PaddedSolutionDir> |
||||
|
<PaddedSolutionDir Condition=" '$(OS)' != 'Windows_NT' ">"$(SolutionDir)"</PaddedSolutionDir> |
||||
|
|
||||
|
<!-- Commands --> |
||||
|
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)</RestoreCommand> |
||||
|
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols</BuildCommand> |
||||
|
|
||||
|
<!-- We need to ensure packages are restored prior to assembly resolve --> |
||||
|
<BuildDependsOn Condition="$(RestorePackages) == 'true'"> |
||||
|
RestorePackages; |
||||
|
$(BuildDependsOn); |
||||
|
</BuildDependsOn> |
||||
|
|
||||
|
<!-- Make the build depend on restore packages --> |
||||
|
<BuildDependsOn Condition="$(BuildPackage) == 'true'"> |
||||
|
$(BuildDependsOn); |
||||
|
BuildPackage; |
||||
|
</BuildDependsOn> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<Target Name="CheckPrerequisites"> |
||||
|
<!-- Raise an error if we're unable to locate nuget.exe --> |
||||
|
<Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" /> |
||||
|
<!-- |
||||
|
Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once. |
||||
|
This effectively acts as a lock that makes sure that the download operation will only happen once and all |
||||
|
parallel builds will have to wait for it to complete. |
||||
|
--> |
||||
|
<MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT;DownloadNuGetExe=$(DownloadNuGetExe)" /> |
||||
|
</Target> |
||||
|
|
||||
|
<Target Name="_DownloadNuGet"> |
||||
|
<DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" /> |
||||
|
</Target> |
||||
|
|
||||
|
<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites"> |
||||
|
<Exec Command="$(RestoreCommand)" |
||||
|
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" /> |
||||
|
|
||||
|
<Exec Command="$(RestoreCommand)" |
||||
|
LogStandardErrorAsError="true" |
||||
|
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" /> |
||||
|
</Target> |
||||
|
|
||||
|
<Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites"> |
||||
|
<Exec Command="$(BuildCommand)" |
||||
|
Condition=" '$(OS)' != 'Windows_NT' " /> |
||||
|
|
||||
|
<Exec Command="$(BuildCommand)" |
||||
|
LogStandardErrorAsError="true" |
||||
|
Condition=" '$(OS)' == 'Windows_NT' " /> |
||||
|
</Target> |
||||
|
|
||||
|
<UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> |
||||
|
<ParameterGroup> |
||||
|
<OutputFilename ParameterType="System.String" Required="true" /> |
||||
|
</ParameterGroup> |
||||
|
<Task> |
||||
|
<Reference Include="System.Core" /> |
||||
|
<Using Namespace="System" /> |
||||
|
<Using Namespace="System.IO" /> |
||||
|
<Using Namespace="System.Net" /> |
||||
|
<Using Namespace="Microsoft.Build.Framework" /> |
||||
|
<Using Namespace="Microsoft.Build.Utilities" /> |
||||
|
<Code Type="Fragment" Language="cs"> |
||||
|
<![CDATA[ |
||||
|
try { |
||||
|
OutputFilename = Path.GetFullPath(OutputFilename); |
||||
|
|
||||
|
Log.LogMessage("Downloading latest version of NuGet.exe..."); |
||||
|
WebClient webClient = new WebClient(); |
||||
|
webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
catch (Exception ex) { |
||||
|
Log.LogErrorFromException(ex); |
||||
|
return false; |
||||
|
} |
||||
|
]]> |
||||
|
</Code> |
||||
|
</Task> |
||||
|
</UsingTask> |
||||
|
</Project> |
||||
Binary file not shown.
@ -0,0 +1,4 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<packages> |
||||
|
<package id="NUnit.Runners" version="2.6.3" /> |
||||
|
</packages> |
||||
@ -0,0 +1,7 @@ |
|||||
|
@echo off |
||||
|
cls |
||||
|
if not exist packages\FAKE\tools\Fake.exe ( |
||||
|
.nuget\nuget.exe install FAKE -OutputDirectory packages -ExcludeVersion |
||||
|
) |
||||
|
packages\FAKE\tools\FAKE.exe build.fsx %* |
||||
|
pause |
||||
@ -0,0 +1,67 @@ |
|||||
|
// -------------------------------------------------------------------------------------- |
||||
|
// FAKE build script, see http://fsharp.github.io/FAKE |
||||
|
// -------------------------------------------------------------------------------------- |
||||
|
|
||||
|
#r @"packages/FAKE/tools/FakeLib.dll" |
||||
|
open Fake |
||||
|
open Fake.Git |
||||
|
open Fake.AssemblyInfoFile |
||||
|
open Fake.ReleaseNotesHelper |
||||
|
open System |
||||
|
|
||||
|
Environment.CurrentDirectory <- __SOURCE_DIRECTORY__ |
||||
|
|
||||
|
|
||||
|
// PREPARE |
||||
|
|
||||
|
Target "Clean" (fun _ -> CleanDirs ["out"; "obj"; "temp"]) |
||||
|
Target "RestorePackages" RestorePackages |
||||
|
Target "AssemblyInfo" DoNothing |
||||
|
Target "Prepare" DoNothing |
||||
|
|
||||
|
"Clean" ==> "Restorepackages" ==> "AssemblyInfo" ==> "Prepare" |
||||
|
|
||||
|
|
||||
|
// BUILD |
||||
|
|
||||
|
Target "BuildMain" (fun _ -> !! "MathNet.Numerics.sln" |> MSBuildRelease "" "Rebuild" |> ignore) |
||||
|
Target "BuildNet35" (fun _ -> !! "MathNet.Numerics.Net35Only.sln" |> MSBuildRelease "" "Rebuild" |> ignore) |
||||
|
Target "BuildAll" (fun _ -> !! "MathNet.Numerics.Portable.sln" |> MSBuildRelease "" "Rebuild" |> ignore) |
||||
|
|
||||
|
Target "Main" DoNothing |
||||
|
Target "Net35" DoNothing |
||||
|
Target "All" DoNothing |
||||
|
|
||||
|
"Prepare" ==> "BuildMain" ==> "Main" |
||||
|
"Prepare" ==> "BuildNet35" ==> "Net35" |
||||
|
"Prepare" ==> "BuildAll" ==> "All" |
||||
|
|
||||
|
|
||||
|
// TEST |
||||
|
|
||||
|
Target "RunTests" (fun _ -> |
||||
|
!! "out/test/*/*UnitTests*.dll" |
||||
|
|> NUnit (fun p -> |
||||
|
{ p with |
||||
|
DisableShadowCopy = true |
||||
|
TimeOut = TimeSpan.FromMinutes 20. |
||||
|
OutputFile = "TestResults.xml" }) |
||||
|
) |
||||
|
|
||||
|
"RunTests" ==> "Main" |
||||
|
"RunTests" ==> "Net35" |
||||
|
"RunTests" ==> "All" |
||||
|
|
||||
|
|
||||
|
// DOCUMENTATION |
||||
|
|
||||
|
Target "BuildDocs" DoNothing |
||||
|
|
||||
|
|
||||
|
// RELEASE |
||||
|
|
||||
|
Target "Release" DoNothing |
||||
|
"All" ==> "BuildDocs" ==> "Release" |
||||
|
|
||||
|
|
||||
|
RunTargetOrDefault "Main" |
||||
@ -0,0 +1,5 @@ |
|||||
|
#!/bin/bash |
||||
|
if [ ! -f packages/FAKE/tools/Fake.exe ]; then |
||||
|
mono .NuGet/NuGet.exe install FAKE -OutputDirectory packages -ExcludeVersion |
||||
|
fi |
||||
|
mono packages/FAKE/tools/FAKE.exe build.fsx $@ |
||||
@ -0,0 +1,6 @@ |
|||||
|
NUnit.Runners* |
||||
|
FAKE* |
||||
|
FSharp.Compiler.Service.* |
||||
|
FSharp.Formatting.* |
||||
|
Microsoft.AspNet.Razor.* |
||||
|
RazorEngine.* |
||||
Binary file not shown.
@ -1,19 +0,0 @@ |
|||||
<?xml version="1.0"?> |
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> |
|
||||
<metadata> |
|
||||
<id>FSharp.Formatting</id> |
|
||||
<version>1.0.15</version> |
|
||||
<title>FSharp.Formatting</title> |
|
||||
<authors>Tomas Petricek, Oleg Pestov, Anh-Dung Phan</authors> |
|
||||
<owners>Tomas Petricek, Oleg Pestov, Anh-Dung Phan</owners> |
|
||||
<licenseUrl>http://github.com/tpetricek/FSharp.Formatting/blob/master/LICENSE.md</licenseUrl> |
|
||||
<projectUrl>http://github.com/tpetricek/FSharp.Formatting</projectUrl> |
|
||||
<iconUrl>https://raw.github.com/tpetricek/FSharp.Formatting/master/docs/misc/logo.png</iconUrl> |
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance> |
|
||||
<description>Provides an F# implementation of Markdown parser and F# code formatter that can used to tokenize F# code and obtain information about tokens including tool tips with type information. The package comes with a sample that implements literate programming for F#.</description> |
|
||||
<releaseNotes>Added latex support, tables and better formatting with line numbers</releaseNotes> |
|
||||
<copyright>Copyright 2013</copyright> |
|
||||
<language /> |
|
||||
<tags>F# fsharp formatting markdown code fssnip literate programming</tags> |
|
||||
</metadata> |
|
||||
</package> |
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,160 +0,0 @@ |
|||||
// -------------------------------------------------------------------------------------- |
|
||||
// F# Markdown (StringParsing.fs) |
|
||||
// (c) Tomas Petricek, 2012, Available under Apache 2.0 license. |
|
||||
// -------------------------------------------------------------------------------------- |
|
||||
|
|
||||
module FSharp.Patterns |
|
||||
|
|
||||
open System |
|
||||
open FSharp.Collections |
|
||||
|
|
||||
// -------------------------------------------------------------------------------------- |
|
||||
// Active patterns that simplify parsing of strings and lists of strings (lines) |
|
||||
// -------------------------------------------------------------------------------------- |
|
||||
|
|
||||
module String = |
|
||||
/// Matches when a string is a whitespace or null |
|
||||
let (|WhiteSpace|_|) s = |
|
||||
if String.IsNullOrWhiteSpace(s) then Some() else None |
|
||||
|
|
||||
/// Matches when a string does starts with non-whitespace |
|
||||
let (|Unindented|_|) (s:string) = |
|
||||
if not (String.IsNullOrWhiteSpace(s)) && s.TrimStart() = s then Some() else None |
|
||||
|
|
||||
/// Returns a string trimmed from both start and end |
|
||||
let (|TrimBoth|) (text:string) = text.Trim() |
|
||||
/// Returns a string trimmed from the end |
|
||||
let (|TrimEnd|) (text:string) = text.TrimEnd() |
|
||||
/// Returns a string trimmed from the start |
|
||||
let (|TrimStart|) (text:string) = text.TrimStart() |
|
||||
|
|
||||
/// Retrusn a string trimmed from the end using characters given as a parameter |
|
||||
let (|TrimEndUsing|) chars (text:string) = text.TrimEnd(Array.ofSeq chars) |
|
||||
|
|
||||
/// Returns a string trimmed from the start together with |
|
||||
/// the number of skipped whitespace characters |
|
||||
let (|TrimStartAndCount|) (text:string) = |
|
||||
let trimmed = text.TrimStart() |
|
||||
text.Length - trimmed.Length, trimmed |
|
||||
|
|
||||
/// Matches when a string starts with any of the specified sub-strings |
|
||||
let (|StartsWithAny|_|) (starts:seq<string>) (text:string) = |
|
||||
if starts |> Seq.exists (text.StartsWith) then Some() else None |
|
||||
/// Matches when a string starts with the specified sub-string |
|
||||
let (|StartsWith|_|) (start:string) (text:string) = |
|
||||
if text.StartsWith(start) then Some(text.Substring(start.Length)) else None |
|
||||
/// Matches when a string starts with the specified sub-string |
|
||||
/// The matched string is trimmed from all whitespace. |
|
||||
let (|StartsWithTrim|_|) (start:string) (text:string) = |
|
||||
if text.StartsWith(start) then Some(text.Substring(start.Length).Trim()) else None |
|
||||
|
|
||||
/// Matches when a string starts with the given value and ends |
|
||||
/// with a given value (and returns the rest of it) |
|
||||
let (|StartsAndEndsWith|_|) (starts, ends) (s:string) = |
|
||||
if s.StartsWith(starts) && s.EndsWith(ends) && |
|
||||
s.Length >= starts.Length + ends.Length then |
|
||||
Some(s.Substring(starts.Length, s.Length - starts.Length - ends.Length)) |
|
||||
else None |
|
||||
|
|
||||
/// Matches when a string starts with the given value and ends |
|
||||
/// with a given value (and returns trimmed body) |
|
||||
let (|StartsAndEndsWithTrim|_|) args = function |
|
||||
| StartsAndEndsWith args (TrimBoth res) -> Some res |
|
||||
| _ -> None |
|
||||
|
|
||||
/// Matches when a string starts with a non-zero number of complete |
|
||||
/// repetitions of the specified parameter (and returns the number |
|
||||
/// of repetitions, together with the rest of the string) |
|
||||
/// |
|
||||
/// let (StartsWithRepeated "/\" (2, " abc")) = "/\/\ abc" |
|
||||
/// |
|
||||
let (|StartsWithRepeated|_|) (repeated:string) (text:string) = |
|
||||
let rec loop i = |
|
||||
if i = text.Length then i |
|
||||
elif text.[i] <> repeated.[i % repeated.Length] then i |
|
||||
else loop (i + 1) |
|
||||
|
|
||||
let n = loop 0 |
|
||||
if n = 0 || n % repeated.Length <> 0 then None |
|
||||
else Some(n/repeated.Length, text.Substring(n, text.Length - n)) |
|
||||
|
|
||||
/// Matches when a string starts with a sub-string wrapped using the |
|
||||
/// opening and closing sub-string specified in the parameter. |
|
||||
/// For example "[aa]bc" is wrapped in [ and ] pair. Returns the wrapped |
|
||||
/// text together with the rest. |
|
||||
let (|StartsWithWrapped|_|) (starts:string, ends:string) (text:string) = |
|
||||
if text.StartsWith(starts) then |
|
||||
let id = text.IndexOf(ends, starts.Length) |
|
||||
if id >= 0 then |
|
||||
let wrapped = text.Substring(starts.Length, id - starts.Length) |
|
||||
let rest = text.Substring(id + ends.Length, text.Length - id - ends.Length) |
|
||||
Some(wrapped, rest) |
|
||||
else None |
|
||||
else None |
|
||||
|
|
||||
/// Matches when a string consists of some number of |
|
||||
/// complete repetitions of a specified sub-string. |
|
||||
let (|EqualsRepeated|_|) repeated = function |
|
||||
| StartsWithRepeated repeated (n, "") -> Some() |
|
||||
| _ -> None |
|
||||
|
|
||||
module List = |
|
||||
/// Matches a list if it starts with a sub-list that is delimited |
|
||||
/// using the specified delimiters. Returns a wrapped list and the rest. |
|
||||
let inline (|DelimitedWith|_|) startl endl input = |
|
||||
if List.startsWith startl input then |
|
||||
match List.partitionUntilEquals endl (List.skip startl.Length input) with |
|
||||
| Some(pre, post) -> Some(pre, List.skip endl.Length post) |
|
||||
| None -> None |
|
||||
else None |
|
||||
|
|
||||
/// Matches a list if it starts with a sub-list that is delimited |
|
||||
/// using the specified delimiter. Returns a wrapped list and the rest. |
|
||||
let inline (|Delimited|_|) str = (|DelimitedWith|_|) str str |
|
||||
|
|
||||
/// Matches a list if it starts with a bracketed list. Nested brackets |
|
||||
/// are skipped (by counting opening and closing brackets) and can be |
|
||||
/// escaped using the '\' symbol. |
|
||||
let (|BracketDelimited|_|) startc endc input = |
|
||||
let rec loop acc count = function |
|
||||
| '\\'::x::xs when x = endc -> loop (x::acc) count xs |
|
||||
| x::xs when x = endc && count = 0 -> Some(List.rev acc, xs) |
|
||||
| x::xs when x = endc -> loop (x::acc) (count - 1) xs |
|
||||
| x::xs when x = startc -> loop (x::acc) (count + 1) xs |
|
||||
| x::xs -> loop (x::acc) count xs |
|
||||
| [] -> None |
|
||||
match input with |
|
||||
| x::xs when x = startc -> loop [] 0 xs |
|
||||
| _ -> None |
|
||||
|
|
||||
/// Retruns a list of characters as a string. |
|
||||
let (|AsString|) chars = String(Array.ofList chars) |
|
||||
|
|
||||
module Lines = |
|
||||
/// Removes blank lines from the start and the end of a list |
|
||||
let (|TrimBlank|) lines = |
|
||||
lines |
|
||||
|> List.skipWhile String.IsNullOrWhiteSpace |> List.rev |
|
||||
|> List.skipWhile String.IsNullOrWhiteSpace |> List.rev |
|
||||
|
|
||||
/// Matches when there are some lines at the beginning that are |
|
||||
/// either empty (or whitespace) or start with the specified string. |
|
||||
/// Returns all such lines from the beginning until a different line. |
|
||||
let (|TakeStartingWithOrBlank|_|) start input = |
|
||||
match List.partitionWhile (fun s -> |
|
||||
String.IsNullOrWhiteSpace s || s.StartsWith(start)) input with |
|
||||
| matching, rest when matching <> [] -> Some(matching, rest) |
|
||||
| _ -> None |
|
||||
|
|
||||
/// Removes whitespace lines from the beginning of the list |
|
||||
let (|TrimBlankStart|) = List.skipWhile (String.IsNullOrWhiteSpace) |
|
||||
|
|
||||
|
|
||||
/// Parameterized pattern that assigns the specified value to the |
|
||||
/// first component of a tuple. Usage: |
|
||||
/// |
|
||||
/// match str with |
|
||||
/// | Let 1 (n, "one") | Let 2 (n, "two") -> n |
|
||||
/// |
|
||||
let (|Let|) a b = (a, b) |
|
||||
|
|
||||
@ -1,152 +0,0 @@ |
|||||
@import url(http://fonts.googleapis.com/css?family=Ovo); |
|
||||
@import url(http://fonts.googleapis.com/css?family=Droid+Sans+Mono); |
|
||||
|
|
||||
/*********************** TOOL TIP STYLES **********************/ |
|
||||
/* The following CSS classes are used by the F# formatter and */ |
|
||||
/* so you should include them whenever you want to display */ |
|
||||
/* HTML code generated by FSharp.CodeFormat.dll */ |
|
||||
/**************************************************************/ |
|
||||
|
|
||||
/* identifier */ |
|
||||
span.i { color:#000000; } |
|
||||
/* comment */ |
|
||||
span.c { color:#008000; } |
|
||||
/* inactive code */ |
|
||||
span.inactive { color:#808080; } |
|
||||
/* keywords */ |
|
||||
span.k { color:#000080; } |
|
||||
/* numbers */ |
|
||||
span.n { color:#008000; } |
|
||||
/* operators */ |
|
||||
span.o { color:#800080; } |
|
||||
/* preprocessor */ |
|
||||
span.prep { color:#800080; } |
|
||||
/* string */ |
|
||||
span.s { color:#808000; } |
|
||||
/* line number */ |
|
||||
span.l { color:#80b0b0; } |
|
||||
/* fsi output */ |
|
||||
span.fsi { font-style:italic; color:#606060; } |
|
||||
/* omitted */ |
|
||||
span.omitted { |
|
||||
border:solid 1px #d8d8d8; |
|
||||
color:#808080; |
|
||||
padding:0px 0px 1px 0px; |
|
||||
background:#fafafa; |
|
||||
} |
|
||||
/* tool tip */ |
|
||||
div.tip |
|
||||
{ |
|
||||
font: 10pt calibri, sans-serif; |
|
||||
padding:3px; |
|
||||
border:1px solid #606060; |
|
||||
background:#ffffd0; |
|
||||
display:none; |
|
||||
} |
|
||||
|
|
||||
/* Optionally, also configure how the PRE element and TABLE.PRE look */ |
|
||||
table.pre pre { |
|
||||
padding:0px; |
|
||||
margin:0px; |
|
||||
border-style:none; |
|
||||
} |
|
||||
|
|
||||
pre, table.pre td { |
|
||||
padding:9.5px; |
|
||||
background:#f5f5f5; |
|
||||
border:solid 1px black; |
|
||||
border-radius:4px; |
|
||||
border-color:rgba(0,0,0,0.15); |
|
||||
margin:0px 0px 10px 0px; |
|
||||
} |
|
||||
|
|
||||
table.pre td.lines { |
|
||||
border-top-right-radius:0px; |
|
||||
border-bottom-right-radius:0px; |
|
||||
border-right-style: none; |
|
||||
padding-right:0px; |
|
||||
} |
|
||||
table.pre td.snippet { |
|
||||
border-top-left-radius:0px; |
|
||||
border-bottom-left-radius:0px; |
|
||||
border-left-style: none; |
|
||||
padding-left:4px; |
|
||||
} |
|
||||
|
|
||||
code, pre, pre code { |
|
||||
font-family:9pt "Droid Sans Mono", sans-serif; |
|
||||
line-height:13pt; |
|
||||
} |
|
||||
|
|
||||
/********************* ADDITIONAL STYLES **********************/ |
|
||||
/* These styles are not necessary - they just provide a nice */ |
|
||||
/* Bootstrap template that can be freely used by F# libraries */ |
|
||||
/**************************************************************/ |
|
||||
|
|
||||
body { |
|
||||
font-family: Ovo, serif; |
|
||||
padding-top: 0px; |
|
||||
padding-bottom: 40px; |
|
||||
} |
|
||||
|
|
||||
/* Format the heading - nicer spacing etc. */ |
|
||||
.masthead { |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
.masthead ul, .masthead li { |
|
||||
margin-bottom:0px; |
|
||||
} |
|
||||
.masthead .nav li { |
|
||||
margin-top: 15px; |
|
||||
font-size:110%; |
|
||||
} |
|
||||
.masthead h3 { |
|
||||
margin-bottom:5px; |
|
||||
font-size:170%; |
|
||||
} |
|
||||
hr { |
|
||||
margin:0px 0px 20px 0px; |
|
||||
} |
|
||||
|
|
||||
/* Format the right-side menu */ |
|
||||
#menu { |
|
||||
margin-top:50px; |
|
||||
font-size:11pt; |
|
||||
padding-left:20px; |
|
||||
} |
|
||||
|
|
||||
#menu .nav-header { |
|
||||
font-size:12pt; |
|
||||
color:#606060; |
|
||||
margin-top:20px; |
|
||||
} |
|
||||
|
|
||||
#menu li { |
|
||||
line-height:25px; |
|
||||
} |
|
||||
|
|
||||
/* Change font sizes for headings etc. */ |
|
||||
#main h1 { font-size: 26pt; margin:10px 0px 15px 0px; } |
|
||||
#main h2 { font-size: 20pt; margin:20px 0px 0px 0px; } |
|
||||
#main h3 { font-size: 14pt; margin:15px 0px 0px 0px; } |
|
||||
#main p { font-size: 12pt; margin:5px 0px 15px 0px; } |
|
||||
#main ul { font-size: 12pt; margin-top:10px; } |
|
||||
#main li { font-size: 12pt; margin: 5px 0px 5px 0px; } |
|
||||
|
|
||||
/* Additional formatting for the homepage */ |
|
||||
#nuget { |
|
||||
margin-top:20px; |
|
||||
font-size: 11pt; |
|
||||
padding:20px; |
|
||||
} |
|
||||
|
|
||||
#nuget pre { |
|
||||
font-size:11pt; |
|
||||
-moz-border-radius: 0px; |
|
||||
-webkit-border-radius: 0px; |
|
||||
border-radius: 0px; |
|
||||
background: #404040; |
|
||||
border-style:none; |
|
||||
color: #e0e0e0; |
|
||||
margin-top:15px; |
|
||||
} |
|
||||
@ -1,47 +0,0 @@ |
|||||
var currentTip = null; |
|
||||
var currentTipElement = null; |
|
||||
|
|
||||
function hideTip(evt, name, unique) |
|
||||
{ |
|
||||
var el = document.getElementById(name); |
|
||||
el.style.display = "none"; |
|
||||
currentTip = null; |
|
||||
} |
|
||||
|
|
||||
function findPos(obj) |
|
||||
{ |
|
||||
var curleft = 0; |
|
||||
var curtop = obj.offsetHeight; |
|
||||
while (obj) |
|
||||
{ |
|
||||
curleft += obj.offsetLeft; |
|
||||
curtop += obj.offsetTop; |
|
||||
obj = obj.offsetParent; |
|
||||
}; |
|
||||
return [curleft, curtop]; |
|
||||
} |
|
||||
|
|
||||
function hideUsingEsc(e) |
|
||||
{ |
|
||||
if (!e) { e = event; } |
|
||||
hideTip(e, currentTipElement, currentTip); |
|
||||
} |
|
||||
|
|
||||
function showTip(evt, name, unique, owner) |
|
||||
{ |
|
||||
document.onkeydown = hideUsingEsc; |
|
||||
if (currentTip == unique) return; |
|
||||
currentTip = unique; |
|
||||
currentTipElement = name; |
|
||||
|
|
||||
var pos = findPos(owner ? owner : (evt.srcElement ? evt.srcElement : evt.target)); |
|
||||
var posx = pos[0]; |
|
||||
var posy = pos[1]; |
|
||||
|
|
||||
var el = document.getElementById(name); |
|
||||
var parent = (document.documentElement == null) ? document.body : document.documentElement; |
|
||||
el.style.position = "absolute"; |
|
||||
el.style.left = posx + "px"; |
|
||||
el.style.top = posy + "px"; |
|
||||
el.style.display = "block"; |
|
||||
} |
|
||||
@ -1,41 +0,0 @@ |
|||||
// Given a typical setup (with 'FSharp.Formatting' referenced using NuGet), |
|
||||
// the following will include binaries and load the literate script |
|
||||
#I "../bin" |
|
||||
#load "literate.fsx" |
|
||||
open FSharp.Literate |
|
||||
|
|
||||
/// This functions processes a single F# Script file |
|
||||
let processScript templateFile outputKind = |
|
||||
let file = __SOURCE_DIRECTORY__ + "\\test.fsx" |
|
||||
let output = __SOURCE_DIRECTORY__ + "\\outputs\\test." + (outputKind.ToString()) |
|
||||
let template = __SOURCE_DIRECTORY__ + templateFile |
|
||||
Literate.ProcessScriptFile(file, template, output, format = outputKind) |
|
||||
|
|
||||
/// This functions processes a single Markdown document |
|
||||
let processDocument templateFile outputKind = |
|
||||
let file = __SOURCE_DIRECTORY__ + "\\demo.md" |
|
||||
let output = __SOURCE_DIRECTORY__ + "\\outputs\\demo." + (outputKind.ToString()) |
|
||||
let template = __SOURCE_DIRECTORY__ + templateFile |
|
||||
Literate.ProcessMarkdown(file, template, output, format = outputKind) |
|
||||
|
|
||||
/// This functions processes an entire directory containing |
|
||||
/// multiple script files (*.fsx) and Markdown documents (*.md) |
|
||||
/// and it specifies additional replacements for the template file |
|
||||
let processDirectory() = |
|
||||
let dir = __SOURCE_DIRECTORY__ |
|
||||
let template = __SOURCE_DIRECTORY__ + "\\templates\\template-project.html" |
|
||||
let projInfo = |
|
||||
[ "page-description", "F# Literate Programming" |
|
||||
"page-author", "Tomas Petricek" |
|
||||
"github-link", "https://github.com/tpetricek/FSharp.Formatting" |
|
||||
"project-name", "F# Formatting" ] |
|
||||
|
|
||||
Literate.ProcessDirectory |
|
||||
( dir, template, dir + "\\output", OutputKind.Html, |
|
||||
replacements = projInfo) |
|
||||
|
|
||||
// Generate output for sample scripts & documents in both HTML & Latex |
|
||||
processScript "\\templates\\template-file.html" OutputKind.Html |
|
||||
processDocument "\\templates\\template-file.html" OutputKind.Html |
|
||||
processScript "\\templates\\template-color.tex" OutputKind.Latex |
|
||||
processDocument "\\templates\\template-color.tex" OutputKind.Latex |
|
||||
@ -1,717 +0,0 @@ |
|||||
(** |
|
||||
Literate programming for F# |
|
||||
=========================== |
|
||||
|
|
||||
Implementation |
|
||||
-------------- |
|
||||
|
|
||||
This document is written as a literate F# script file, so the remaining text |
|
||||
is an overview of the implementation. The implementation uses `FSharp.Markdown.dll` |
|
||||
and `FSharp.CodeFormat.dll` to colorize F# source & parse Markdown: |
|
||||
*) |
|
||||
|
|
||||
(*** hide ***) |
|
||||
namespace FSharp.Literate |
|
||||
#if INTERACTIVE |
|
||||
#I "../bin/" |
|
||||
#r "System.Web.dll" |
|
||||
#r "FSharp.Markdown.dll" |
|
||||
#r "FSharp.CodeFormat.dll" |
|
||||
#load "StringParsing.fs" |
|
||||
#endif |
|
||||
|
|
||||
open System |
|
||||
open System.IO |
|
||||
open System.Web |
|
||||
open System.Reflection |
|
||||
open System.Collections.Generic |
|
||||
|
|
||||
open FSharp.Patterns |
|
||||
open FSharp.CodeFormat |
|
||||
open FSharp.Markdown |
|
||||
|
|
||||
(** |
|
||||
### OutputKind type |
|
||||
|
|
||||
The following type defines the two possible output types from literate script: |
|
||||
HTML and LaTeX. |
|
||||
|
|
||||
*) |
|
||||
[<RequireQualifiedAccess>] |
|
||||
type OutputKind = |
|
||||
| Html |
|
||||
| Latex |
|
||||
(*[omit:(members omitted)]*) |
|
||||
|
|
||||
/// Name of the format (used as a file extension) |
|
||||
override x.ToString() = |
|
||||
match x with |
|
||||
| Html -> "html" |
|
||||
| Latex -> "tex" |
|
||||
|
|
||||
/// Format a given document as HTML or LaTeX depending on the current kind |
|
||||
member x.Format(doc) = |
|
||||
match x with |
|
||||
| OutputKind.Html -> Markdown.WriteHtml(doc) |
|
||||
| OutputKind.Latex -> Markdown.WriteLatex(doc) |
|
||||
|
|
||||
/// The name of the {tag} that is used for pasting content into a template file |
|
||||
/// (the default is {document}, but that collides in LaTeX) |
|
||||
member x.ContentTag = |
|
||||
match x with |
|
||||
| OutputKind.Html -> "document" |
|
||||
| OutputKind.Latex -> "contents" |
|
||||
(*[/omit]*) |
|
||||
|
|
||||
(** |
|
||||
### CommandUtils module |
|
||||
|
|
||||
Utilities for parsing commands. Commands can be used in different places. We |
|
||||
recognize `key1=value, key2=value` and also `key1:value, key2:value` |
|
||||
*) |
|
||||
module internal CommandUtils = |
|
||||
(*[omit:(Implementation omitted)]*) |
|
||||
|
|
||||
let (|ParseCommands|_|) (str:string) = |
|
||||
let kvs = |
|
||||
[ for cmd in str.Split(',') do |
|
||||
let kv = cmd.Split([| '='; ':' |]) |
|
||||
if kv.Length = 2 then yield kv.[0].Trim(), kv.[1].Trim() |
|
||||
elif kv.Length = 1 then yield kv.[0].Trim(), "" ] |
|
||||
if kvs <> [] then Some(dict kvs) else None |
|
||||
|
|
||||
let (|Command|_|) k (d:IDictionary<_, _>) = |
|
||||
match d.TryGetValue(k) with |
|
||||
| true, v -> Some v |
|
||||
| _ -> None |
|
||||
(*[/omit]*) |
|
||||
|
|
||||
(** |
|
||||
### LiterateUtils module |
|
||||
|
|
||||
Utilities for processing Markdown documents - extract links for references, |
|
||||
add links to references, extract code blocks for colorization and replace them |
|
||||
with formatted HTML (after running F# code formatter) |
|
||||
*) |
|
||||
module internal LiterateUtils = |
|
||||
(*[omit:(Implementation omitted)]*) |
|
||||
open CommandUtils |
|
||||
|
|
||||
/// Given Markdown document, get the keys of all IndirectLinks |
|
||||
/// (to be used when generating paragraph with all references) |
|
||||
let rec collectReferences = |
|
||||
|
|
||||
// Collect IndirectLinks in a span |
|
||||
let rec collectSpanReferences span = seq { |
|
||||
match span with |
|
||||
| IndirectLink(_, _, key) -> yield key |
|
||||
| Matching.SpanLeaf _ -> () |
|
||||
| Matching.SpanNode(_, spans) -> |
|
||||
for s in spans do yield! collectSpanReferences s } |
|
||||
|
|
||||
// Collect IndirectLinks in a paragraph |
|
||||
let rec loop par = seq { |
|
||||
match par with |
|
||||
| Matching.ParagraphLeaf _ -> () |
|
||||
| Matching.ParagraphNested(_, pars) -> |
|
||||
for ps in pars do |
|
||||
for p in ps do yield! loop p |
|
||||
| Matching.ParagraphSpans(_, spans) -> |
|
||||
for s in spans do yield! collectSpanReferences s } |
|
||||
loop |
|
||||
|
|
||||
/// Given Markdown document, add a number using the given index to all indirect |
|
||||
/// references. For example, [article][ref] becomes [article][ref] [1](#rfxyz) |
|
||||
let replaceReferences (refIndex:IDictionary<string, int>) = |
|
||||
|
|
||||
// Replace IndirectLinks with a nice link given a single span element |
|
||||
let rec replaceSpans = function |
|
||||
| IndirectLink(body, original, key) -> |
|
||||
[ yield IndirectLink(body, original, key) |
|
||||
match refIndex.TryGetValue(key) with |
|
||||
| true, i -> |
|
||||
yield Literal " [" |
|
||||
yield DirectLink([Literal (string i)], ("#rf" + DateTime.Now.ToString("yyMMddhh"), None)) |
|
||||
yield Literal "]" |
|
||||
| _ -> () ] |
|
||||
| Matching.SpanLeaf(sl) -> [Matching.SpanLeaf(sl)] |
|
||||
| Matching.SpanNode(nd, spans) -> |
|
||||
[ Matching.SpanNode(nd, List.collect replaceSpans spans) ] |
|
||||
|
|
||||
// Given a paragraph, process it recursively and transform all spans |
|
||||
let rec loop = function |
|
||||
| Matching.ParagraphNested(pn, nested) -> |
|
||||
Matching.ParagraphNested(pn, List.map (List.choose loop) nested) |> Some |
|
||||
| Matching.ParagraphSpans(ps, spans) -> |
|
||||
Matching.ParagraphSpans(ps, List.collect replaceSpans spans) |> Some |
|
||||
| Matching.ParagraphLeaf(pl) -> Matching.ParagraphLeaf(pl) |> Some |
|
||||
loop |
|
||||
|
|
||||
/// Iterate over Markdown document and extract all F# code snippets that we want |
|
||||
/// to colorize. We skip snippets that specify non-fsharp langauge e.g. [lang=csharp]. |
|
||||
let rec collectCodeSnippets par = seq { |
|
||||
match par with |
|
||||
| CodeBlock(String.StartsWithWrapped ("[", "]") (ParseCommands cmds, String.TrimStart code)) |
|
||||
when cmds.ContainsKey("lang") && cmds.["lang"] <> "fsharp" -> () |
|
||||
| CodeBlock(String.StartsWithWrapped ("[", "]") (ParseCommands cmds, String.TrimStart code)) |
|
||||
| CodeBlock(Let (dict []) (cmds, code)) -> |
|
||||
let modul = |
|
||||
match cmds.TryGetValue("module") with |
|
||||
| true, v -> Some v | _ -> None |
|
||||
yield modul, code |
|
||||
| Matching.ParagraphLeaf _ -> () |
|
||||
| Matching.ParagraphNested(_, pars) -> |
|
||||
for ps in pars do |
|
||||
for p in ps do yield! collectCodeSnippets p |
|
||||
| Matching.ParagraphSpans(_, spans) -> () } |
|
||||
|
|
||||
/// Replace CodeBlock elements with formatted HTML that was processed by the F# snippets tool |
|
||||
/// (The dictionary argument is a map from original code snippets to formatted HTML snippets.) |
|
||||
let rec replaceCodeSnippets outputKind (codeLookup:IDictionary<_, _>) = function |
|
||||
| CodeBlock(String.StartsWithWrapped ("[", "]") (ParseCommands cmds, String.TrimStart code)) |
|
||||
when cmds.ContainsKey("hide") -> None |
|
||||
| CodeBlock(String.StartsWithWrapped ("[", "]") (ParseCommands cmds, String.TrimStart code)) |
|
||||
| CodeBlock(Let (dict []) (cmds, code)) -> |
|
||||
if (cmds.ContainsKey("lang")) && cmds.["lang"] <> "fsharp" then |
|
||||
let content = |
|
||||
if outputKind = OutputKind.Html then |
|
||||
"<pre lang=\"" + cmds.["lang"] + "\">" + HttpUtility.HtmlEncode(code) + "</pre>" |
|
||||
else sprintf "\\begin{lstlisting}\n%s\n\\end{lstlisting}" <| HttpUtility.HtmlDecode(code) |
|
||||
HtmlBlock(content) |> Some |
|
||||
else |
|
||||
let content : string = codeLookup.[code] |
|
||||
HtmlBlock(content) |> Some |
|
||||
|
|
||||
// Recursively process nested paragraphs, other nodes return without change |
|
||||
| Matching.ParagraphNested(pn, nested) -> |
|
||||
let pars = List.map (List.choose (replaceCodeSnippets outputKind codeLookup)) nested |
|
||||
Matching.ParagraphNested(pn, pars) |> Some |
|
||||
| other -> Some other |
|
||||
|
|
||||
/// Try find first-level heading in the paragraph collection |
|
||||
let findHeadings paragraphs (outputKind:OutputKind) = |
|
||||
paragraphs |> Seq.tryPick (function |
|
||||
| (Heading(1, text)) -> |
|
||||
let doc = MarkdownDocument([Span(text)], dict []) |
|
||||
Some(outputKind.Format(doc)) |
|
||||
| _ -> None) |
|
||||
(*[/omit]*) |
|
||||
|
|
||||
(** |
|
||||
### CodeBlockUtils module |
|
||||
|
|
||||
Parsing of F# Script files with Markdown commands. Given a parsed script file, we |
|
||||
split it into a sequence of comments, snippets and commands (comment starts with |
|
||||
`(**` and ending with `*)` are translated to Markdown, snippet is all other F# code |
|
||||
and command looks like `(*** key1:value, key2:value ***)` (and should be single line). |
|
||||
*) |
|
||||
module internal CodeBlockUtils = |
|
||||
(*[omit:(Implementation omitted)]*) |
|
||||
open CommandUtils |
|
||||
|
|
||||
type Block = |
|
||||
| BlockComment of string |
|
||||
| BlockSnippet of Line list |
|
||||
| BlockCommand of IDictionary<string, string> |
|
||||
|
|
||||
/// Trim blank lines from both ends of a lines list & reverse it (we accumulate |
|
||||
/// lines & we want to remove all blanks before returning BlockSnippet) |
|
||||
let private trimBlanksAndReverse lines = |
|
||||
lines |
|
||||
|> Seq.skipWhile (function Line[] -> true | _ -> false) |
|
||||
|> List.ofSeq |> List.rev |
|
||||
|> Seq.skipWhile (function Line[] -> true | _ -> false) |
|
||||
|> List.ofSeq |
|
||||
|
|
||||
/// Succeeds when a line (list of tokens) contains only Comment |
|
||||
/// tokens and returns the text from the comment as a string |
|
||||
let private (|ConcatenatedComments|_|) (Line tokens) = |
|
||||
let comments = |
|
||||
tokens |> List.choose (function |
|
||||
| Token(TokenKind.Comment, text, _) -> Some text |
|
||||
| _ -> None) |
|
||||
if comments.Length <> tokens.Length then None |
|
||||
else Some (String.concat "" comments) |
|
||||
|
|
||||
// Process lines of an F# script file. Simple state machine with two states |
|
||||
// * collectComment - we're parsing a comment and waiting for the end |
|
||||
// * collectSnippet - we're in a normal F# code and we're waiting for a comment |
|
||||
// (in both states, we also need to recognize (*** commands ***) |
|
||||
|
|
||||
/// Waiting for the end of a comment |
|
||||
let rec private collectComment (comment:string) lines = seq { |
|
||||
match lines with |
|
||||
| (ConcatenatedComments(String.StartsAndEndsWith ("(***", "***)") (ParseCommands cmds)))::lines -> |
|
||||
// Ended with a command, yield comment, command & parse the next as a snippet |
|
||||
let cend = comment.LastIndexOf("*)") |
|
||||
yield BlockComment (comment.Substring(0, cend)) |
|
||||
yield BlockCommand cmds |
|
||||
yield! collectSnippet [] lines |
|
||||
|
|
||||
| (ConcatenatedComments text)::_ when |
|
||||
comment.LastIndexOf("*)") <> -1 && text.Trim().StartsWith("//") -> |
|
||||
// Comment ended, but we found a code snippet starting with // comment |
|
||||
let cend = comment.LastIndexOf("*)") |
|
||||
yield BlockComment (comment.Substring(0, cend)) |
|
||||
yield! collectSnippet [] lines |
|
||||
|
|
||||
| (Line[Token(TokenKind.Comment, String.StartsWith "(**" text, _)])::lines -> |
|
||||
// Another block of Markdown comment starting... |
|
||||
// Yield the previous snippet block and continue parsing more comments |
|
||||
let cend = comment.LastIndexOf("*)") |
|
||||
yield BlockComment (comment.Substring(0, cend)) |
|
||||
if lines <> [] then yield! collectComment text lines |
|
||||
|
|
||||
| (ConcatenatedComments text)::lines -> |
|
||||
// Continue parsing comment |
|
||||
yield! collectComment (comment + "\n" + text) lines |
|
||||
|
|
||||
| lines -> |
|
||||
// Ended - yield comment & continue parsing snippet |
|
||||
let cend = comment.LastIndexOf("*)") |
|
||||
yield BlockComment (comment.Substring(0, cend)) |
|
||||
if lines <> [] then yield! collectSnippet [] lines } |
|
||||
|
|
||||
/// Collecting a block of F# snippet |
|
||||
and private collectSnippet acc lines = seq { |
|
||||
match lines with |
|
||||
| (ConcatenatedComments(String.StartsAndEndsWith ("(***", "***)") (ParseCommands cmds)))::lines -> |
|
||||
// Found a special command, yield snippet, command and parse another snippet |
|
||||
if acc <> [] then yield BlockSnippet (trimBlanksAndReverse acc) |
|
||||
yield BlockCommand cmds |
|
||||
yield! collectSnippet [] lines |
|
||||
|
|
||||
| (Line[Token(TokenKind.Comment, String.StartsWith "(**" text, _)])::lines -> |
|
||||
// Found a comment - yield snippet & switch to parsing comment state |
|
||||
if acc <> [] then yield BlockSnippet (trimBlanksAndReverse acc) |
|
||||
yield! collectComment text lines |
|
||||
|
|
||||
| x::xs -> yield! collectSnippet (x::acc) xs |
|
||||
| [] -> yield BlockSnippet (trimBlanksAndReverse acc) } |
|
||||
|
|
||||
/// Parse F# script file into a sequence of snippets, comments and commands |
|
||||
let parseScriptFile = collectSnippet [] |
|
||||
|
|
||||
/// Given a parsed script file, extract "definitions". A definition is a part of |
|
||||
/// the file that we want to include elsewhere (and hide in the original location): |
|
||||
/// |
|
||||
/// (*** define:key ***) |
|
||||
/// let foo = 1 + 2 |
|
||||
/// |
|
||||
/// This function returns 'string * Block' list containing all definitions |
|
||||
/// together with all a list of all remaining blocks that were not extracted. |
|
||||
let extractDefinitions defns = |
|
||||
let rec loop defns normal = function |
|
||||
| [] -> defns, normal |> List.rev |
|
||||
| BlockCommand(Command "hide" _)::(BlockSnippet _)::rest -> |
|
||||
loop defns normal rest |
|
||||
| BlockCommand(Command "define" key)::(BlockSnippet lines)::rest -> |
|
||||
// If we have command with 'define' in it, then pick the following |
|
||||
// snippet (it should be a snippet) and return it as a definition |
|
||||
loop ((key, lines)::defns) normal rest |
|
||||
| current::rest -> |
|
||||
loop defns (current::normal) rest |
|
||||
defns |> List.ofSeq |> loop [] [] |
|
||||
(*[/omit]*) |
|
||||
|
|
||||
(** |
|
||||
### SourceProcessors module |
|
||||
|
|
||||
Functions that process `*.fsx` and `*.md` files. The function `processScriptFile` |
|
||||
assumes that the file is an F# script file (with text hidden in comments) while |
|
||||
`processMarkdown` assumes that all F# code is included as code snippets. |
|
||||
*) |
|
||||
module internal SourceProcessors = |
|
||||
|
|
||||
/// Specifies a context that is passed to the |
|
||||
/// code/document processing functions |
|
||||
type ProcessingContext = |
|
||||
{ // An instance of the F# code formatting agent |
|
||||
FormatAgent : CodeFormatAgent |
|
||||
// Source code of a HTML template file |
|
||||
Template : string option |
|
||||
// Short prefix code added to all HTML 'id' elements |
|
||||
Prefix : string |
|
||||
// Should the processing add 'References' section? |
|
||||
GenerateReferences : bool |
|
||||
// Additional replacements to be made in the template file |
|
||||
Replacements : list<string * string> |
|
||||
// Generate line numbers for F# snippets? |
|
||||
GenerateLineNumbers : bool |
|
||||
// Include the source file in the generated output as '{source}' |
|
||||
IncludeSource : bool |
|
||||
// Command line options for the F# compiler |
|
||||
Options : string |
|
||||
// The output format |
|
||||
OutputKind : OutputKind |
|
||||
// Custom function for reporting errors |
|
||||
ErrorHandler : option<string * SourceError -> unit> } |
|
||||
|
|
||||
(*[omit:(Implementation omitted)]*) |
|
||||
open CommandUtils |
|
||||
open CodeBlockUtils |
|
||||
open LiterateUtils |
|
||||
|
|
||||
/// Print information about all errors during the processing |
|
||||
let private reportErrors ctx file (errors:seq<SourceError>) = |
|
||||
match ctx.ErrorHandler with |
|
||||
| Some eh -> for e in errors do eh(file, e) |
|
||||
| _ -> |
|
||||
for (SourceError((sl, sc), (el, ec), kind, msg)) in errors do |
|
||||
printfn " * (%d:%d)-(%d:%d) (%A): %s" sl sc el ec kind msg |
|
||||
if Seq.length errors > 0 then printfn "" |
|
||||
|
|
||||
/// Given all links defined in the Markdown document and a list of all links |
|
||||
/// that are accessed somewhere from the document, generate References paragraph |
|
||||
let generateReferences (definedLinks:IDictionary<_, string * string option>) refs outputKind = |
|
||||
|
|
||||
// For all unique references in the document, |
|
||||
// get the link & title from definitions |
|
||||
let refs = |
|
||||
refs |> set |> Seq.choose (fun ref -> |
|
||||
match definedLinks.TryGetValue(ref) with |
|
||||
| true, (link, Some title) -> Some (ref, link, title) |
|
||||
| _ -> None) |
|
||||
|> Seq.sort |> Seq.mapi (fun i v -> i+1, v) |
|
||||
// Generate dictionary with a number for all references |
|
||||
let refLookup = dict [ for (i, (r, _, _)) in refs -> r, i ] |
|
||||
|
|
||||
// Generate Markdown blocks paragraphs representing Reference <li> items |
|
||||
let refList = |
|
||||
[ for i, (ref, link, title) in refs do |
|
||||
let colon = title.IndexOf(":") |
|
||||
if colon > 0 then |
|
||||
let auth = title.Substring(0, colon) |
|
||||
let name = title.Substring(colon + 1, title.Length - 1 - colon) |
|
||||
yield [Span [ Literal (sprintf "[%d] " i) |
|
||||
DirectLink([Literal name], (link, Some title)) |
|
||||
Literal (" - " + auth)] ] |
|
||||
else |
|
||||
yield [Span [ Literal (sprintf "[%d] " i) |
|
||||
DirectLink([Literal title], (link, Some title))]] ] |
|
||||
|
|
||||
// Return the document together with dictionary for looking up indices |
|
||||
let literal = |
|
||||
match outputKind with |
|
||||
| OutputKind.Html -> |
|
||||
// Return the document together with dictionary for looking up indices |
|
||||
let id = DateTime.Now.ToString("yyMMddhh") |
|
||||
Literal ("<a name=\"rf" + id + "\"> </a>References") |
|
||||
| OutputKind.Latex -> |
|
||||
// Add formatting later |
|
||||
Literal ("References") |
|
||||
[ Heading(3, [literal]) |
|
||||
ListBlock(MarkdownListKind.Unordered, refList) ], refLookup |
|
||||
|
|
||||
/// Replace {parameter} in the input string with |
|
||||
/// values defined in the specified list |
|
||||
let replaceParameters parameters input = |
|
||||
match input with |
|
||||
| None -> |
|
||||
// If there is no template, return just document + tooltips |
|
||||
let lookup = parameters |> dict |
|
||||
lookup.["document"] + "\n\n" + lookup.["tooltips"] |
|
||||
| Some input -> |
|
||||
// First replace keys with some uglier keys and then replace them with values |
|
||||
// (in case one of the keys appears in some other value) |
|
||||
let id = System.Guid.NewGuid().ToString("d") |
|
||||
let input = parameters |> Seq.fold (fun (html:string) (key, value) -> |
|
||||
html.Replace("{" + key + "}", "{" + key + id + "}")) input |
|
||||
let result = parameters |> Seq.fold (fun (html:string) (key, value) -> |
|
||||
html.Replace("{" + key + id + "}", value)) input |
|
||||
result |
|
||||
|
|
||||
/// Write formatted blocks to a specified string builder |
|
||||
/// and return first-level heading if there is some |
|
||||
let outputBlocks (sb:Text.StringBuilder) |
|
||||
// Original blocks of the input document |
|
||||
blocks |
|
||||
// Sequence with just formatted BlockSnippet elements |
|
||||
(snippets:seq<FormattedSnippet>) |
|
||||
// Sequence with just formatted BlockComment elements |
|
||||
(comments:seq<MarkdownDocument>) |
|
||||
(definitions:IDictionary<_, string>) refLookup outputKind = |
|
||||
|
|
||||
// We traverse sequences using enumerators as we need them |
|
||||
let heading = ref None |
|
||||
use snippetsEn = snippets.GetEnumerator() |
|
||||
use commentsEn = comments.GetEnumerator() |
|
||||
let nextSnippet () = snippetsEn.MoveNext() |> ignore; snippetsEn.Current |
|
||||
let nextComment () = commentsEn.MoveNext() |> ignore; commentsEn.Current |
|
||||
|
|
||||
for block in blocks do |
|
||||
match block with |
|
||||
// Skip known commands and comments ('hide' is removed in earlier step) |
|
||||
| BlockCommand (Command "include" key) -> sb.Append(definitions.[key]) |> ignore |
|
||||
| BlockCommand (Command "define" _) -> () |
|
||||
| BlockCommand cmds when cmds.Count = 1 && cmds.Keys |> Seq.head |> Seq.forall ((=) '*') -> () |
|
||||
| BlockCommand cmds -> |
|
||||
failwithf "Unsupported command: %s" (String.concat ", " [ for (KeyValue(k,v)) in cmds -> k + ":" + v ]) |
|
||||
|
|
||||
// Emit next comment, but search for headings |
|
||||
| BlockComment s -> |
|
||||
let mdoc = nextComment() |
|
||||
let paragraphs = mdoc.Paragraphs |> List.choose (replaceReferences refLookup) |
|
||||
findHeadings paragraphs outputKind |> Option.iter (fun v -> heading := Some v) |
|
||||
let doc = MarkdownDocument(paragraphs, mdoc.DefinedLinks) |
|
||||
sb.Append(outputKind.Format(doc)) |> ignore |
|
||||
|
|
||||
// Emit next snippet (if it is not just empty list) |
|
||||
| BlockSnippet lines -> |
|
||||
let snip = nextSnippet() |
|
||||
if lines <> [] then sb.Append(snip.Content) |> ignore |
|
||||
!heading |
|
||||
|
|
||||
// ------------------------------------------------------------------------------------ |
|
||||
|
|
||||
/// Process F# Script file |
|
||||
let processScriptFile ctx file output = |
|
||||
let name = Path.GetFileNameWithoutExtension(file) |
|
||||
|
|
||||
// Parse the entire file as an F# script file, |
|
||||
// get sequence of blocks & extract definitions |
|
||||
let sourceSnippets, errors = ctx.FormatAgent.ParseSource(file, File.ReadAllText(file), ctx.Options) |
|
||||
reportErrors ctx file errors |
|
||||
let (Snippet(_, lines)) = match sourceSnippets with [| it |] -> it | _ -> failwith "multiple snippets" |
|
||||
let definitions, blocks = parseScriptFile lines |> extractDefinitions |
|
||||
|
|
||||
// Process all definitions & build a dictionary with HTML for each definition |
|
||||
let snippets = [| for name, lines in definitions -> Snippet(name, lines) |] |
|
||||
let formattedDefns = |
|
||||
match ctx.OutputKind with |
|
||||
| OutputKind.Html -> CodeFormat.FormatHtml(snippets, ctx.Prefix + "d", ctx.GenerateLineNumbers, false) |
|
||||
| OutputKind.Latex -> CodeFormat.FormatLatex(snippets, ctx.GenerateLineNumbers) |
|
||||
let definitions = dict [ for snip in formattedDefns.Snippets -> snip.Title, snip.Content ] |
|
||||
|
|
||||
// Process all snippet blocks in the script file (using F# formatter) |
|
||||
let snippets = blocks |> List.choose (function |
|
||||
| BlockSnippet(lines) -> Some(Snippet("Untitled", lines)) |
|
||||
| _ -> None) |> Array.ofList |
|
||||
let formatted = |
|
||||
match ctx.OutputKind with |
|
||||
| OutputKind.Html -> CodeFormat.FormatHtml(snippets, ctx.Prefix, ctx.GenerateLineNumbers, false) |
|
||||
| OutputKind.Latex -> CodeFormat.FormatLatex(snippets, ctx.GenerateLineNumbers) |
|
||||
|
|
||||
// Parse all comment blocks in the script file (as Markdown) |
|
||||
let parsedBlocks = blocks |> Array.ofSeq |> Seq.choose (function |
|
||||
| BlockComment(text) -> Some(Markdown.Parse(text)) |
|
||||
| _ -> None) |
|
||||
|
|
||||
// Turn all indirect links into a references & add paragraph to the document |
|
||||
let refParagraph, refLookup = |
|
||||
if ctx.GenerateReferences then |
|
||||
// Union link definitions & collect all indirect links |
|
||||
let definedLinks = parsedBlocks |> Seq.collect (fun mdoc -> |
|
||||
[ for (KeyValue(k, v)) in mdoc.DefinedLinks -> k, v]) |> dict |
|
||||
let refs = parsedBlocks |> Seq.collect (fun mdoc -> |
|
||||
Seq.collect collectReferences mdoc.Paragraphs) |
|
||||
let pars, refLookup = generateReferences definedLinks refs ctx.OutputKind |
|
||||
Some pars, refLookup |
|
||||
else None, dict [] |
|
||||
|
|
||||
// Write all HTML content to a string builder & add References |
|
||||
let sb = Text.StringBuilder() |
|
||||
let heading = outputBlocks sb blocks formatted.Snippets parsedBlocks definitions refLookup ctx.OutputKind |
|
||||
refParagraph |> Option.iter (fun p -> |
|
||||
let output = ctx.OutputKind.Format(MarkdownDocument(p, dict [])) |
|
||||
sb.Append(output) |> ignore) |
|
||||
|
|
||||
// If we want to include the source code of the script, then process |
|
||||
// the entire source and generate replacement {source} => ...some html... |
|
||||
let sourceReplacement, sourceTips = |
|
||||
match ctx.OutputKind with |
|
||||
| OutputKind.Html -> |
|
||||
if ctx.IncludeSource then |
|
||||
let formatted = CodeFormat.FormatHtml(sourceSnippets, ctx.Prefix + "s") |
|
||||
let content = |
|
||||
match formatted.Snippets with |
|
||||
| [| snip |] -> snip.Content |
|
||||
| snips -> [ for s in snips -> sprintf "<h3>%s</h3>\n%s" s.Title s.Content ] |> String.concat "" |
|
||||
[ "source", content ], formatted.ToolTip |
|
||||
else [], "" |
|
||||
| OutputKind.Latex -> |
|
||||
if ctx.IncludeSource then |
|
||||
let formatted = CodeFormat.FormatLatex(sourceSnippets) |
|
||||
let content = |
|
||||
match formatted.Snippets with |
|
||||
| [| snip |] -> snip.Content |
|
||||
| snips -> [ for s in snips -> sprintf "\subsubsection{%s}\n%s" s.Title s.Content ] |> String.concat "" |
|
||||
[ "source", content ], formatted.ToolTip |
|
||||
else [], "" |
|
||||
|
|
||||
// Replace all parameters in the template & write to output |
|
||||
let parameters = |
|
||||
ctx.Replacements @ sourceReplacement @ |
|
||||
[ "page-title", defaultArg heading name |
|
||||
ctx.OutputKind.ContentTag, sb.ToString() |
|
||||
"tooltips", formatted.ToolTip + formattedDefns.ToolTip + sourceTips ] |
|
||||
File.WriteAllText(output, replaceParameters parameters ctx.Template) |
|
||||
|
|
||||
// ------------------------------------------------------------------------------------ |
|
||||
|
|
||||
/// Process Markdown document |
|
||||
let processMarkdown ctx file output = |
|
||||
// Read file & parse Markdown document |
|
||||
let name = Path.GetFileNameWithoutExtension(file) |
|
||||
let originalSource = File.ReadAllText(file) |
|
||||
let doc = Markdown.Parse(originalSource) |
|
||||
|
|
||||
// Turn all indirect links into a references & add paragraph to the document |
|
||||
let refParagraph, refLookup = |
|
||||
if ctx.GenerateReferences then |
|
||||
// Union link definitions & collect all indirect links |
|
||||
let refs = Seq.collect collectReferences doc.Paragraphs |
|
||||
let pars, refLookup = generateReferences doc.DefinedLinks refs ctx.OutputKind |
|
||||
Some pars, refLookup |
|
||||
else None, dict [] |
|
||||
|
|
||||
// Extract all CodeBlocks and pass them to F# snippets |
|
||||
let codes = doc.Paragraphs |> Seq.collect collectCodeSnippets |> Array.ofSeq |
|
||||
let codeLookup, tipsHtml = |
|
||||
if codes.Length = 0 then dict [], "" |
|
||||
else |
|
||||
// If there are some F# snippets, we build an F# source file |
|
||||
let blocks = codes |> Seq.mapi (fun index (modul, code) -> |
|
||||
match modul with |
|
||||
| Some modul -> |
|
||||
// generate module & add indentation |
|
||||
"module " + modul + " =\n" + |
|
||||
"// [snippet:" + (string index) + "]\n" + |
|
||||
" " + code.Replace("\n", "\n ") + "\n" + |
|
||||
"// [/snippet]" |
|
||||
| None -> |
|
||||
"// [snippet:" + (string index) + "]\n" + |
|
||||
code + "\n" + |
|
||||
"// [/snippet]" ) |
|
||||
|
|
||||
// Process F# script file, report errors & build lookup table for replacement |
|
||||
let modul = "module " + (new String(name |> Seq.filter Char.IsLetter |> Seq.toArray)) |
|
||||
let source = modul + "\r\n" + (String.concat "\n\n" blocks) |
|
||||
let snippets, errors = ctx.FormatAgent.ParseSource(output + ".fs", source, ctx.Options) |
|
||||
reportErrors ctx file errors |
|
||||
let formatted = |
|
||||
match ctx.OutputKind with |
|
||||
| OutputKind.Html -> CodeFormat.FormatHtml(snippets, ctx.Prefix, ctx.GenerateLineNumbers, false) |
|
||||
| OutputKind.Latex -> CodeFormat.FormatLatex(snippets, ctx.GenerateLineNumbers) |
|
||||
let snippetLookup = |
|
||||
[ for (_, code), fs in Array.zip codes formatted.Snippets -> code, fs.Content ] |
|
||||
dict snippetLookup, formatted.ToolTip |
|
||||
|
|
||||
// Process all paragraphs in two steps (replace F# snippets & references) |
|
||||
let paragraphs = |
|
||||
doc.Paragraphs |> List.choose (fun par -> |
|
||||
par |> replaceCodeSnippets ctx.OutputKind codeLookup |
|
||||
|> Option.bind (replaceReferences refLookup)) |
|
||||
|
|
||||
// If we want to include the source code of the script, then process |
|
||||
// the entire source and generate replacement {source} => ...some html... |
|
||||
let sourceReplacements = |
|
||||
if ctx.IncludeSource then |
|
||||
let doc = MarkdownDocument([CodeBlock originalSource], dict []) |
|
||||
let content = ctx.OutputKind.Format(doc) |
|
||||
[ "source", content ] |
|
||||
else [] |
|
||||
|
|
||||
// Construct new Markdown document and write it |
|
||||
let parameters = |
|
||||
ctx.Replacements @ sourceReplacements @ |
|
||||
[ "page-title", defaultArg (findHeadings paragraphs ctx.OutputKind) name |
|
||||
ctx.OutputKind.ContentTag, ctx.OutputKind.Format(MarkdownDocument(paragraphs, doc.DefinedLinks)) |
|
||||
"tooltips", tipsHtml ] |
|
||||
File.WriteAllText(output, replaceParameters parameters ctx.Template) |
|
||||
(*[/omit]*) |
|
||||
|
|
||||
|
|
||||
(** |
|
||||
|
|
||||
## Public API |
|
||||
|
|
||||
The following type provides three simple methods for calling the literate programming tool. |
|
||||
The `ProcessMarkdown` and `ProcessScriptFile` methods process a single Markdown document |
|
||||
and F# script, respectively. The `ProcessDirectory` method handles an entire directory tree |
|
||||
(looking for `*.fsx` and `*.md` files). |
|
||||
*) |
|
||||
open SourceProcessors |
|
||||
|
|
||||
type Literate = |
|
||||
(*[omit:(Helper methdods omitted)]*) |
|
||||
/// Provides default values for all optional parameters |
|
||||
static member private DefaultArguments |
|
||||
( input, templateFile, output,format, fsharpCompiler, prefix, compilerOptions, |
|
||||
lineNumbers, references, replacements, includeSource, errorHandler) = |
|
||||
let defaultArg v f = match v with Some v -> v | _ -> f() |
|
||||
|
|
||||
let outputKind = defaultArg format (fun _ -> OutputKind.Html) |
|
||||
|
|
||||
let output = defaultArg output (fun () -> |
|
||||
let dir = Path.GetDirectoryName(input) |
|
||||
let file = Path.GetFileNameWithoutExtension(input) |
|
||||
Path.Combine(dir, sprintf "%s.%O" file outputKind)) |
|
||||
let fsharpCompiler = defaultArg fsharpCompiler (fun () -> |
|
||||
Assembly.Load("FSharp.Compiler")) |
|
||||
|
|
||||
// Build & return processing context |
|
||||
let ctx = |
|
||||
{ FormatAgent = CodeFormat.CreateAgent(fsharpCompiler) |
|
||||
Template = templateFile |> Option.map (fun file -> File.ReadAllText(file)) |
|
||||
Prefix = defaultArg prefix (fun () -> "fs") |
|
||||
Options = defaultArg compilerOptions (fun () -> "") |
|
||||
GenerateLineNumbers = defaultArg lineNumbers (fun () -> true) |
|
||||
GenerateReferences = defaultArg references (fun () -> false) |
|
||||
Replacements = defaultArg replacements (fun () -> []) |
|
||||
IncludeSource = defaultArg includeSource (fun () -> false) |
|
||||
OutputKind = outputKind |
|
||||
ErrorHandler = errorHandler } |
|
||||
output, ctx(*[/omit]*) |
|
||||
|
|
||||
/// Process Markdown document |
|
||||
static member ProcessMarkdown |
|
||||
( input, ?templateFile, ?output, ?format, ?fsharpCompiler, ?prefix, ?compilerOptions, |
|
||||
?lineNumbers, ?references, ?replacements, ?includeSource, ?errorHandler ) = (*[omit:(...)]*) |
|
||||
let output, ctx = |
|
||||
Literate.DefaultArguments |
|
||||
( input, templateFile, output, format, fsharpCompiler, prefix, compilerOptions, |
|
||||
lineNumbers, references, replacements, includeSource, errorHandler ) |
|
||||
processMarkdown ctx input output (*[/omit]*) |
|
||||
|
|
||||
/// Process F# Script file |
|
||||
static member ProcessScriptFile |
|
||||
( input, ?templateFile, ?output, ?format, ?fsharpCompiler, ?prefix, ?compilerOptions, |
|
||||
?lineNumbers, ?references, ?replacements, ?includeSource, ?errorHandler ) = (*[omit:(...)]*) |
|
||||
let output, ctx = |
|
||||
Literate.DefaultArguments |
|
||||
( input, templateFile, output, format, fsharpCompiler, prefix, compilerOptions, |
|
||||
lineNumbers, references, replacements, includeSource, errorHandler ) |
|
||||
processScriptFile ctx input output (*[/omit]*) |
|
||||
|
|
||||
/// Process directory containing a mix of Markdown documents and F# Script files |
|
||||
static member ProcessDirectory |
|
||||
( inputDirectory, ?templateFile, ?outputDirectory, ?format, ?fsharpCompiler, ?prefix, ?compilerOptions, |
|
||||
?lineNumbers, ?references, ?replacements, ?includeSource, ?errorHandler ) = (*[omit:(...)]*) |
|
||||
let _, ctx = |
|
||||
Literate.DefaultArguments |
|
||||
( "", templateFile, Some "", format, fsharpCompiler, prefix, compilerOptions, |
|
||||
lineNumbers, references, replacements, includeSource, errorHandler ) |
|
||||
|
|
||||
/// Recursively process all files in the directory tree |
|
||||
let rec processDirectory indir outdir = |
|
||||
// Create output directory if it does not exist |
|
||||
if Directory.Exists(outdir) |> not then |
|
||||
try Directory.CreateDirectory(outdir) |> ignore |
|
||||
with _ -> failwithf "Cannot create directory '%s'" outdir |
|
||||
|
|
||||
let fsx = [ for f in Directory.GetFiles(indir, "*.fsx") -> processScriptFile, f ] |
|
||||
let mds = [ for f in Directory.GetFiles(indir, "*.md") -> processMarkdown, f ] |
|
||||
for func, file in fsx @ mds do |
|
||||
let name = Path.GetFileNameWithoutExtension(file) |
|
||||
let output = Path.Combine(outdir, sprintf "%s.%O" name ctx.OutputKind) |
|
||||
|
|
||||
// Update only when needed |
|
||||
let changeTime = File.GetLastWriteTime(file) |
|
||||
let generateTime = File.GetLastWriteTime(output) |
|
||||
if changeTime > generateTime then |
|
||||
printfn "Generating '%s.%O'" name ctx.OutputKind |
|
||||
func ctx file output |
|
||||
|
|
||||
let outputDirectory = defaultArg outputDirectory inputDirectory |
|
||||
processDirectory inputDirectory outputDirectory (*[/omit]*) |
|
||||
@ -1,35 +0,0 @@ |
|||||
<!DOCTYPE html> |
|
||||
<html lang="en"> |
|
||||
<head> |
|
||||
<meta charset="utf-8"> |
|
||||
<!-- |
|
||||
The {page-title} parameters will be replaced with the |
|
||||
document title extracted from the <h1> element or |
|
||||
file name, if there is no <h1> heading |
|
||||
--> |
|
||||
<title>{page-title}</title> |
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
||||
<script src="http://code.jquery.com/jquery-1.8.0.js"></script> |
|
||||
<script src="http://code.jquery.com/ui/1.8.23/jquery-ui.js"></script> |
|
||||
<script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/js/bootstrap.min.js"></script> |
|
||||
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap-combined.min.css" rel="stylesheet"> |
|
||||
<link type="text/css" rel="stylesheet" href="content/style.css" /> |
|
||||
<script src="content/tips.js" type="text/javascript"></script> |
|
||||
<!-- HTML5 shim, for IE6-8 support of HTML5 elements --> |
|
||||
<!--[if lt IE 9]> |
|
||||
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> |
|
||||
<![endif]--> |
|
||||
</head> |
|
||||
<body> |
|
||||
<div class="container"> |
|
||||
<div class="row" style="margin-top:30px"> |
|
||||
<div class="span1"></div> |
|
||||
<div class="span10" id="main"> |
|
||||
{document} |
|
||||
{tooltips} |
|
||||
</div> |
|
||||
<div class="span1"></div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</body> |
|
||||
</html> |
|
||||
@ -1,62 +0,0 @@ |
|||||
<!DOCTYPE html> |
|
||||
<html lang="en"> |
|
||||
<head> |
|
||||
<meta charset="utf-8"> |
|
||||
<!-- |
|
||||
The {page-title} parameters will be replaced with the |
|
||||
document title extracted from the <h1> element or |
|
||||
file name, if there is no <h1> heading |
|
||||
--> |
|
||||
<title>{page-title}</title> |
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
||||
<meta name="description" content="{page-description}"> |
|
||||
<meta name="author" content="{page-author}"> |
|
||||
<script src="http://code.jquery.com/jquery-1.8.0.js"></script> |
|
||||
<script src="http://code.jquery.com/ui/1.8.23/jquery-ui.js"></script> |
|
||||
<script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/js/bootstrap.min.js"></script> |
|
||||
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap-combined.min.css" rel="stylesheet"> |
|
||||
<link type="text/css" rel="stylesheet" href="content/style.css" /> |
|
||||
<script src="content/tips.js" type="text/javascript"></script> |
|
||||
<!-- HTML5 shim, for IE6-8 support of HTML5 elements --> |
|
||||
<!--[if lt IE 9]> |
|
||||
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> |
|
||||
<![endif]--> |
|
||||
</head> |
|
||||
<body> |
|
||||
<div class="container"> |
|
||||
<div class="masthead"> |
|
||||
<ul class="nav nav-pills pull-right"> |
|
||||
<li><a href="http://fsharp.org">fsharp.org</a></li> |
|
||||
<li><a href="{github-link}">github page</a></li> |
|
||||
</ul> |
|
||||
<h3 class="muted">{project-name}</h3> |
|
||||
</div> |
|
||||
<hr /> |
|
||||
<div class="row"> |
|
||||
<div class="span9" id="main"> |
|
||||
{document} |
|
||||
{tooltips} |
|
||||
</div> |
|
||||
<div class="span3"> |
|
||||
|
|
||||
<ul class="nav nav-list" id="menu"> |
|
||||
<li class="nav-header">{project-name}</li> |
|
||||
<li><a href="../index.html">Home page</a></li> |
|
||||
<!-- |
|
||||
|
|
||||
Here you can add links to other pages of the documentation |
|
||||
The 'divider' element creates a separator and additional |
|
||||
'nav-header' can be used to add sub-headings in the menu: |
|
||||
|
|
||||
* <li class="divider"></li> |
|
||||
* <li><a href="...">...</a></li> |
|
||||
* <li class="nav-header">Sub-heading</li> |
|
||||
|
|
||||
--> |
|
||||
</ul> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<a href="{github-link}"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png" alt="Fork me on GitHub"></a> |
|
||||
</body> |
|
||||
</html> |
|
||||
@ -1,6 +1,5 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<repositories> |
<repositories> |
||||
<repository path="..\src\FSharpExamples\packages.config" /> |
|
||||
<repository path="..\src\FSharpUnitTests\packages.config" /> |
<repository path="..\src\FSharpUnitTests\packages.config" /> |
||||
<repository path="..\src\Numerics\packages.config" /> |
<repository path="..\src\Numerics\packages.config" /> |
||||
<repository path="..\src\UnitTests\packages.config" /> |
<repository path="..\src\UnitTests\packages.config" /> |
||||
|
|||||
@ -1,4 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<packages> |
|
||||
<package id="FSharp.Formatting" version="1.0.15" targetFramework="net40" /> |
|
||||
</packages> |
|
||||
Loading…
Reference in new issue