Skip to content

Commit

Permalink
instead of ipynb -> md do pynb -> fsx
Browse files Browse the repository at this point in the history
  • Loading branch information
nhirschey committed Nov 22, 2023
1 parent 5e688fa commit 3934dfa
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 3 deletions.
45 changes: 42 additions & 3 deletions src/FSharp.Formatting.Literate/Literate.fs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,45 @@ type Literate private () =
|> Transformations.formatCodeSnippets filePath ctx
|> Transformations.evaluateCodeSnippets ctx

/// <summary>
/// Parse pynb string as literate document
/// </summary>
/// <param name="content"></param>
/// <param name="path">optional file path for debugging purposes</param>
/// <param name="definedSymbols"></param>
/// <param name="references"></param>
/// <param name="parseOptions">Defaults to MarkdownParseOptions.AllowYamlFrontMatter</param>
/// <param name="rootInputFolder"></param>
/// <param name="onError"></param>
static member ParsePynbString
(
content,
?path,
?definedSymbols,
?references,
?parseOptions,
?rootInputFolder,
?onError
) =
let onError = defaultArg onError ignore
let ctx = parsingContext None None definedSymbols onError

let filePath =
match path with
| Some s -> s
| None ->
match rootInputFolder with
| None -> "C:\\script.fsx"
| Some r -> Path.Combine(r, "script.fsx")

let content = ParsePynb.pynbStringToFsx content

ParseScript(parseOptions, ctx)
.ParseAndCheckScriptFile(filePath, content, rootInputFolder, onError)
|> Transformations.generateReferences references
|> Transformations.formatCodeSnippets filePath ctx
|> Transformations.evaluateCodeSnippets ctx

// ------------------------------------------------------------------------------------
// Simple writing functions
// ------------------------------------------------------------------------------------
Expand Down Expand Up @@ -542,11 +581,11 @@ type Literate private () =
//||| MarkdownParseOptions.ParseNonCodeAsOther
| _ -> parseOptions

let md = ParsePynb.pynbToMarkdown input
let fsx = ParsePynb.pynbToFsx input

let doc =
Literate.ParseMarkdownString(
md,
Literate.ParseScriptString(
fsx,
?fscOptions = fscOptions,
?references = references,
parseOptions = parseOptions,
Expand Down
24 changes: 24 additions & 0 deletions src/FSharp.Formatting.Literate/ParsePynb.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ module internal ParsePynb =
| Some outputs ->
let outputsString = outputs |> String.concat "\n"
sprintf $"{codeBlock}\n{outputsString}"
member this.ToFsx() =
match this with
| Markdown source -> $"(**\n{source}\n*)"
| Code code when code.lang = "fsharp" ->
let codeBlock = addLineEnd code.source

match code.outputs with
| None -> codeBlock
| Some outputs ->
let outputsString = outputs |> String.concat "\n"
sprintf $"{codeBlock}\n(**\n{outputsString}\n*)"
| Code _ ->
$"(**\n{this.ToMarkdown()}\n*)"

module Output =
let (|TextHtml|_|) (x: JsonElement) =
Expand Down Expand Up @@ -147,6 +160,17 @@ module internal ParsePynb =
let pynbToMarkdown ipynbFile =
ipynbFile |> File.ReadAllText |> pynbStringToMarkdown


let pynbStringToFsx (ipynb: string) =
let json = JsonDocument.Parse(ipynb)

json.RootElement.GetProperty("cells").EnumerateArray()
|> Seq.map (parseCell >> (fun x -> x.ToFsx()))
|> String.concat "\n"

let pynbToFsx ipynbFile =
ipynbFile |> File.ReadAllText |> pynbStringToFsx

let parseFrontMatter ipynbFile =
let json = JsonDocument.Parse(ipynbFile |> File.ReadAllText)

Expand Down
74 changes: 74 additions & 0 deletions tests/FSharp.Literate.Tests/LiterateTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1467,6 +1467,80 @@ let add a b = a + b

(mdOut.Trim()) |> shouldEqual (mdIn.Trim())

[<Test>]
let ``Notebook is converted to script exactly right`` () =
let doc =
Literate.ParsePynbString(
"""
{
"cells": [
{
"cell_type": "code",
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
},
"polyglot_notebook": {
"kernelName": "fsharp"
}
},
"execution_count": null, "outputs": [],
"source": [
"let hello = 1\n",
"\n",
"let goodbye = 2\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".NET (F#)",
"language": "F#",
"name": ".net-fsharp"
},
"language_info": {
"file_extension": ".fs",
"mimetype": "text/x-fsharp",
"name": "polyglot-notebook",
"pygments_lexer": "fsharp"
},
"polyglot_notebook": {
"kernelInfo": {
"defaultKernelName": "fsharp",
"items": [
{
"aliases": [],
"languageName": "fsharp",
"name": "fsharp"
}
]
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}""",
parseOptions =
(MarkdownParseOptions.ParseCodeAsOther
||| MarkdownParseOptions.ParseNonCodeAsOther)
)

let fsx = Literate.ToFsx(doc)
printfn "----"
printfn "%s" fsx
printfn "----"

let fsx2 = fsx.Replace("\r\n", "\n").Replace("\n", "!")

let expected =
"""let hello = 1
let goodbye = 2"""

let expected2 = expected.Replace("\r\n", "\n").Replace("\n", "!")

fsx2 |> shouldEqual expected2

[<Test>]
let ``Script output is exactly right`` () =
let md =
Expand Down

0 comments on commit 3934dfa

Please sign in to comment.