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"?> |
|||
<repositories> |
|||
<repository path="..\src\FSharpExamples\packages.config" /> |
|||
<repository path="..\src\FSharpUnitTests\packages.config" /> |
|||
<repository path="..\src\Numerics\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