forked from tsai/mathnet-numerics
27 changed files with 1809 additions and 125 deletions
@ -0,0 +1,42 @@ |
|||||
|
#I "../../packages/FSharp.Formatting.1.0.15/lib/net40" |
||||
|
#load "../../packages/FSharp.Formatting.1.0.15/literate/literate.fsx" |
||||
|
|
||||
|
open System |
||||
|
open System.IO |
||||
|
open FSharp.Literate |
||||
|
|
||||
|
let (+/) l r = Path.GetFullPath(Path.Combine(l,r)) |
||||
|
|
||||
|
let source = __SOURCE_DIRECTORY__ |
||||
|
let toolDir = source +/ "../../packages/FSharp.Formatting.1.0.15/lib/net40" |
||||
|
let libDir = source +/ "../../out/lib/Net40" |
||||
|
let templateDir = source |
||||
|
let inputDir = source +/ "../../src/FSharpExamples" |
||||
|
let outputDir = source +/ "../../out/docs" |
||||
|
|
||||
|
let templateFile = "template.html" |
||||
|
let templateContent = ["content/style.css"; "content/tips.js"] |
||||
|
|
||||
|
// Additional strings to be replaced in the HTML template |
||||
|
let projInfo = |
||||
|
[ "page-description", "" |
||||
|
"page-author", "Math.NET Team & Contributors; Christoph Ruegg" |
||||
|
"project-name", "Math.NET Numerics" ] |
||||
|
|
||||
|
let compilerReferences = |
||||
|
[ toolDir +/ "FSharp.CompilerBinding.dll" |
||||
|
toolDir +/ "FSharp.CodeFormat.dll" |
||||
|
toolDir +/ "FSharp.Markdown.dll" |
||||
|
"System.Web.dll" |
||||
|
libDir +/ "MathNet.Numerics.dll" |
||||
|
libDir +/ "MathNet.Numerics.FSharp.dll" ] |
||||
|
|
||||
|
// Compiler options (reference the two dll files and System.Web.dll) |
||||
|
let compilerOptions = (List.fold (fun s p -> s + sprintf """--reference:"%s" """ p) "" compilerReferences).Trim() |
||||
|
|
||||
|
// Copy template content files (scripts, styles, images, ..) |
||||
|
List.iter (fun tc -> File.Copy(templateDir +/ tc, outputDir +/ tc, true)) templateContent |
||||
|
|
||||
|
// Now we can process the samples directory (with some additional references) |
||||
|
// and then we clean up the files & directories we had to create earlier |
||||
|
Literate.ProcessDirectory(inputDir, templateDir +/ templateFile, outputDir, OutputKind.Html, replacements=projInfo, compilerOptions=compilerOptions) |
||||
@ -0,0 +1,243 @@ |
|||||
|
@import url(http://fonts.googleapis.com/css?family=Ovo); |
||||
|
@import url(http://fonts.googleapis.com/css?family=Droid+Sans+Mono); |
||||
|
|
||||
|
/*-------------------------------------------------------------------------- |
||||
|
Formatting for F# code snippets |
||||
|
/*--------------------------------------------------------------------------*/ |
||||
|
|
||||
|
/* identifier */ |
||||
|
span.i { color:#000000; } |
||||
|
/* string */ |
||||
|
span.s { color:#ce4415; } |
||||
|
/* keywords */ |
||||
|
span.k { color:#0000fe; } |
||||
|
/* comment */ |
||||
|
span.c { color:#008000; } |
||||
|
/* operators */ |
||||
|
span.o { color:#800080; } |
||||
|
/* numbers */ |
||||
|
span.n { color:#cd8300; } |
||||
|
/* line number */ |
||||
|
span.l { color:#80b0b0; } |
||||
|
|
||||
|
/* inactive code */ |
||||
|
span.inactive { color:#808080; } |
||||
|
/* preprocessor */ |
||||
|
span.prep { color:#800080; } |
||||
|
/* fsi output */ |
||||
|
span.fsi { font-style:italic; color:#808080; } |
||||
|
|
||||
|
/* omitted */ |
||||
|
span.omitted { |
||||
|
color: #808080; |
||||
|
background: #fafafa; |
||||
|
border: solid 1px #d8d8d8; |
||||
|
border-radius: 5px; |
||||
|
padding: 0px 0px 1px 0px; |
||||
|
} |
||||
|
|
||||
|
/* tool tip */ |
||||
|
div.tip { |
||||
|
display: none; |
||||
|
font: 10pt Consolas, Calibri, sans-serif; |
||||
|
color: #d1d1d1; |
||||
|
background: #475b5f; |
||||
|
border: 1px solid #606060; |
||||
|
border-radius: 4px; |
||||
|
padding: 3px; |
||||
|
} |
||||
|
|
||||
|
/* Optionally, also configure how the PRE element and TABLE.PRE look */ |
||||
|
table.pre pre { |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
border-style: none; |
||||
|
border-radius:0; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
pre { |
||||
|
margin:10px 20px 20px 10px; |
||||
|
white-space:pre; |
||||
|
} |
||||
|
|
||||
|
code { |
||||
|
font: 9pt "Droid Sans Mono", Consolas, sans-serif; |
||||
|
color: #7d0a26; |
||||
|
background-color: inherit; |
||||
|
border: 1px solid #e8e8e8; |
||||
|
padding: 1px 3px; |
||||
|
} |
||||
|
|
||||
|
pre code { |
||||
|
width: 95%; |
||||
|
} |
||||
|
|
||||
|
table.pre { |
||||
|
width: 95%; |
||||
|
margin:10px 20px 20px 10px; |
||||
|
padding: 10px; |
||||
|
} |
||||
|
|
||||
|
table.pre, pre.fssnip, pre { |
||||
|
font: 9pt "Droid Sans Mono", Consolas, sans-serif; |
||||
|
color:#404040; |
||||
|
background: #f5f5f5; |
||||
|
border:1px solid #e0e0e0; |
||||
|
border-radius:5px; |
||||
|
border-collapse:separate; |
||||
|
line-height: 13pt; |
||||
|
margin:10px 20px 20px 10px; |
||||
|
padding:10px; |
||||
|
} |
||||
|
|
||||
|
/*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: 0 0 10px 0; |
||||
|
}*/ |
||||
|
|
||||
|
table.pre td { |
||||
|
padding: 0; |
||||
|
white-space: normal; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
table.pre td.lines { |
||||
|
width: 30px; |
||||
|
} |
||||
|
|
||||
|
/*table.pre td.lines { |
||||
|
border-top-right-radius: 0; |
||||
|
border-bottom-right-radius: 0; |
||||
|
border-right-style: none; |
||||
|
padding-right: 0; |
||||
|
} |
||||
|
table.pre td.snippet { |
||||
|
border-top-left-radius: 0; |
||||
|
border-bottom-left-radius: 0; |
||||
|
border-left-style: none; |
||||
|
padding-left: 4px; |
||||
|
}*/ |
||||
|
|
||||
|
|
||||
|
|
||||
|
/*-------------------------------------------------------------------------- |
||||
|
Formatting for page & standard document content |
||||
|
/*--------------------------------------------------------------------------*/ |
||||
|
|
||||
|
body { |
||||
|
font-family: Ovo, serif; |
||||
|
padding-top: 0; |
||||
|
padding-bottom: 40px; |
||||
|
} |
||||
|
|
||||
|
a, a:visited { |
||||
|
color: #1BA1E2; |
||||
|
text-decoration: none; |
||||
|
} |
||||
|
|
||||
|
a:hover { |
||||
|
color: white; |
||||
|
background: #1BA1E2; |
||||
|
} |
||||
|
|
||||
|
.nav { |
||||
|
font-family: "Segoe UI", "Segoe WP", "Helvetica Neue", 'RobotoRegular', "Trebuchet MS", sans-serif; |
||||
|
} |
||||
|
|
||||
|
.nav li a:hover { |
||||
|
color: white; |
||||
|
background: #1BA1E2; |
||||
|
} |
||||
|
|
||||
|
/* Format the heading - nicer spacing etc. */ |
||||
|
.masthead { |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
.masthead ul, .masthead li { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
.masthead .nav li { |
||||
|
margin-top: 15px; |
||||
|
font-size: 110%; |
||||
|
} |
||||
|
.masthead h3 { |
||||
|
font-family: "Segoe UI", "Segoe WP", "Helvetica Neue", 'RobotoRegular', "Trebuchet MS", sans-serif; |
||||
|
font-weight: normal; |
||||
|
color: #1BA1E2; |
||||
|
margin-bottom: 5px; |
||||
|
font-size: 170%; |
||||
|
} |
||||
|
hr { |
||||
|
margin: 0 0 20px 0; |
||||
|
} |
||||
|
|
||||
|
/* 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 0 15px 0; } |
||||
|
#main h2 { font-size: 20pt; margin:20px 0 0 0; } |
||||
|
#main h3 { font-size: 14pt; margin:15px 0 0 0; } |
||||
|
#main p { font-size: 12pt; margin:5px 0 15px 0; } |
||||
|
#main ul { font-size: 12pt; margin-top:10px; } |
||||
|
#main li { font-size: 12pt; margin: 5px 0 5px 0; } |
||||
|
|
||||
|
/*-------------------------------------------------------------------------- |
||||
|
Additional formatting for the homepage |
||||
|
/*--------------------------------------------------------------------------*/ |
||||
|
|
||||
|
#nuget { |
||||
|
margin-top: 20px; |
||||
|
font-size: 11pt; |
||||
|
padding: 20px; |
||||
|
} |
||||
|
|
||||
|
#nuget pre { |
||||
|
font-size: 11pt; |
||||
|
-moz-border-radius: 0; |
||||
|
-webkit-border-radius: 0; |
||||
|
border-radius: 0; |
||||
|
background: #404040; |
||||
|
border-style: none; |
||||
|
color: #e0e0e0; |
||||
|
margin-top: 15px; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#hp-snippet td.lines { |
||||
|
display: none; |
||||
|
} |
||||
|
#hp-snippet .table { |
||||
|
width: 80%; |
||||
|
margin-left: 30px; |
||||
|
} |
||||
|
#hp-snippet thead, #hp-snippet .title { |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#menu li.generatedBy { |
||||
|
margin-top: 40px; |
||||
|
font-size: 0.8em; |
||||
|
line-height: normal; |
||||
|
color: #808080; |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
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) { |
||||
|
// no idea why, but it behaves differently in webbrowser component
|
||||
|
if (window.location.search == "?inapp") |
||||
|
return [obj.offsetLeft + 10, obj.offsetTop + 30]; |
||||
|
|
||||
|
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"; |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="utf-8"> |
||||
|
<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> |
||||
|
<!--[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://www.mathdotnet.com">Math.NET Project</a></li> |
||||
|
<li><a href="http://numerics.mathdotnet.com">Math.NET Numerics</a></li> |
||||
|
<li><a href="https://github.com/mathnet/mathnet-numerics">GitHub</a></li> |
||||
|
<li><a href="https://mathnetnumerics.codeplex.com/">CodePlex</a></li> |
||||
|
</ul> |
||||
|
<h3>Math.NET Numerics</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">Math.NET Numerics</li> |
||||
|
<li><a href="http://numerics.mathdotnet.com">Home page</a></li> |
||||
|
<li><a href="http://numerics.mathdotnet.com/api/">API Reference</a></li> |
||||
|
|
||||
|
<li class="nav-header">User Guide</li> |
||||
|
<li><a href="index.html">Random & Distributions</a></li> |
||||
|
|
||||
|
<li class="generatedBy"><span> |
||||
|
<em>You</em> can edit this page <a href="https://github.com/mathnet/mathnet-numerics/tree/master/src/FSharpExamples">here</a>.<br /> |
||||
|
Generated using <a href="http://tpetricek.github.io/FSharp.Formatting/">FSharp.Literate</a>. |
||||
|
</span></li> |
||||
|
|
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
||||
Binary file not shown.
@ -0,0 +1,19 @@ |
|||||
|
<?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.
@ -0,0 +1,160 @@ |
|||||
|
// -------------------------------------------------------------------------------------- |
||||
|
// 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) |
||||
|
|
||||
@ -0,0 +1,152 @@ |
|||||
|
@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; |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
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"; |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
// 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 |
||||
@ -0,0 +1,717 @@ |
|||||
|
(** |
||||
|
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]*) |
||||
@ -0,0 +1,35 @@ |
|||||
|
<!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> |
||||
@ -0,0 +1,62 @@ |
|||||
|
<!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,5 +1,6 @@ |
|||||
<?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\UnitTests\packages.config" /> |
<repository path="..\src\UnitTests\packages.config" /> |
||||
</repositories> |
</repositories> |
||||
@ -0,0 +1,4 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<packages> |
||||
|
<package id="FSharp.Formatting" version="1.0.15" targetFramework="net40" /> |
||||
|
</packages> |
||||
Loading…
Reference in new issue