diff --git a/src/FSharp.Formatting.Literate/Literate.fs b/src/FSharp.Formatting.Literate/Literate.fs index 88fe78194..d5a86de25 100644 --- a/src/FSharp.Formatting.Literate/Literate.fs +++ b/src/FSharp.Formatting.Literate/Literate.fs @@ -223,6 +223,45 @@ type Literate private () = |> Transformations.formatCodeSnippets filePath ctx |> Transformations.evaluateCodeSnippets ctx + /// + /// Parse pynb string as literate document + /// + /// + /// optional file path for debugging purposes + /// + /// + /// Defaults to MarkdownParseOptions.AllowYamlFrontMatter + /// + /// + 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 // ------------------------------------------------------------------------------------ @@ -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, diff --git a/src/FSharp.Formatting.Literate/ParsePynb.fs b/src/FSharp.Formatting.Literate/ParsePynb.fs index c2f14511f..a83c23f3b 100644 --- a/src/FSharp.Formatting.Literate/ParsePynb.fs +++ b/src/FSharp.Formatting.Literate/ParsePynb.fs @@ -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) = @@ -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) diff --git a/tests/FSharp.Literate.Tests/LiterateTests.fs b/tests/FSharp.Literate.Tests/LiterateTests.fs index 7dc34c16c..a8e3a8fa8 100644 --- a/tests/FSharp.Literate.Tests/LiterateTests.fs +++ b/tests/FSharp.Literate.Tests/LiterateTests.fs @@ -1467,6 +1467,80 @@ let add a b = a + b (mdOut.Trim()) |> shouldEqual (mdIn.Trim()) +[] +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 + [] let ``Script output is exactly right`` () = let md =