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 =