Compare commits
No commits in common. "8402ff3a3cf035da660a6e53379f83ae6745cea5" and "27a169b30c6633b50f62d9e38677a4696da10c8c" have entirely different histories.
8402ff3a3c
...
27a169b30c
9 changed files with 143 additions and 310 deletions
|
|
@ -13,8 +13,4 @@
|
||||||
<ProjectReference Include="..\FibLib\FibLib.fsproj" />
|
<ProjectReference Include="..\FibLib\FibLib.fsproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Update="FSharp.Core" Version="10.1.201" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
open System.Net
|
open System.Net
|
||||||
open System.IO
|
open System.IO
|
||||||
open System.Runtime.CompilerServices
|
|
||||||
open FSharp.Compiler.Text
|
|
||||||
open Fibble.FibLib
|
open Fibble.FibLib
|
||||||
open Fibble.FibLib.Ast // Ger oss tillgång till Element, Text, RawHtml etc.
|
open Fibble.FibLib.Ast // Ger oss tillgång till Element, Text, RawHtml etc.
|
||||||
open Fibble.FibLib.Pandoc
|
open Fibble.FibLib.Pandoc
|
||||||
open Fibble.FibLib.HtmlPrinter
|
|
||||||
open Fibble.FibLib.Utils
|
open Fibble.FibLib.Utils
|
||||||
open Fibble.FibLib.ConstructionHelpers
|
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 1. Prelude (Dina egna taggar)
|
// 1. Prelude (Dina egna taggar)
|
||||||
|
|
@ -16,25 +12,27 @@ open Fibble.FibLib.ConstructionHelpers
|
||||||
|
|
||||||
let myPrelude : Map<string, TagRenderer> =
|
let myPrelude : Map<string, TagRenderer> =
|
||||||
Map [
|
Map [
|
||||||
"quotient", fun _ args _ _ ->
|
"quotient", fun _ args _ ->
|
||||||
match args with
|
match args with
|
||||||
| [one; two] ->
|
| [_, one; _, two] ->
|
||||||
Text (sprintf "%d" (int one / int two))
|
Text (sprintf "%d" (int one / int two))
|
||||||
| _ ->
|
| _ ->
|
||||||
Text "[Fel: quotient kräver två argument]"
|
Text "[Fel: quotient kräver två argument]"
|
||||||
"bold", fun _ _ _ children ->
|
"bold", fun _ args children ->
|
||||||
Strong(children)
|
Element("b", args, children)
|
||||||
"kursiv", fun _ _ _ children -> Emph(children)
|
"image", positional (fun _ args _ -> Element("img", [("src", args[0])], []))
|
||||||
"image", image
|
"value", positional (fun meta args _ ->
|
||||||
"value", value
|
match Map.tryFind args.Head meta with
|
||||||
"link", link
|
| Some(v) -> Text v
|
||||||
"list", fun _ _ _ c -> System.Console.WriteLine(c)
|
| None -> Text $"value {args.Head} not found in metadata")
|
||||||
RawHtml "hej"
|
"link", positional (fun _ args children ->
|
||||||
|
let url = if args.Length > 0 then args.[0].Trim '"' else "#"
|
||||||
|
Element("a", [("href", args.Head)], children))
|
||||||
|
|
||||||
// @br har varken argument eller barn, så vi returnerar bara rå HTML direkt
|
// @br har varken argument eller barn, så vi returnerar bara rå HTML direkt
|
||||||
"br", linebreak
|
"br", nameToElement "br"
|
||||||
"table", fun _ _ _ children -> Text "hej"
|
"table", fun _ _ children -> Text "hej"
|
||||||
"md", fun _ _ _ children -> RawHtml (mdToHtml (stringifyNodes children))
|
"md", fun meta args children -> RawHtml (mdToHtml (stringifyNodes children))
|
||||||
]
|
]
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
@ -45,7 +43,7 @@ module File =
|
||||||
let readFile path =
|
let readFile path =
|
||||||
match Path.Exists(path) with
|
match Path.Exists(path) with
|
||||||
| true -> File.ReadAllText(path)
|
| true -> File.ReadAllText(path)
|
||||||
| _ -> failwith $"{Path.GetFullPath path} does not exist"
|
| _ -> failwith $"{path} does not exist"
|
||||||
|
|
||||||
let pageTemplate = File.readFile "_page-template"
|
let pageTemplate = File.readFile "_page-template"
|
||||||
|
|
||||||
|
|
@ -69,12 +67,10 @@ let processDocument (source: string) =
|
||||||
| Section(l, a, children) ->
|
| Section(l, a, children) ->
|
||||||
Section(l, a, children
|
Section(l, a, children
|
||||||
|> List.map (Execution.transform metadata myPrelude evaluator))
|
|> List.map (Execution.transform metadata myPrelude evaluator))
|
||||||
| _ -> failwith "haha"
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Steg 3: Be printern skriva ut trädet till HTML
|
// Steg 3: Be printern skriva ut trädet till HTML
|
||||||
let bodyHtml = HtmlPrinter.render evaluatedBlocks
|
let bodyHtml = HtmlPrinter.render (metadata, evaluatedBlocks)
|
||||||
|
|
||||||
// Steg 4: Fyll i din HTML-mall
|
// Steg 4: Fyll i din HTML-mall
|
||||||
let mutable finalHtml = pageTemplate.Replace("{{body}}", bodyHtml)
|
let mutable finalHtml = pageTemplate.Replace("{{body}}", bodyHtml)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ let increment () =
|
||||||
Detta är en paragraf som har lite text i sig och lite @md{inbäddad *markdown* som kanske} funkar. Det är bra så.
|
Detta är en paragraf som har lite text i sig och lite @md{inbäddad *markdown* som kanske} funkar. Det är bra så.
|
||||||
|
|
||||||
@md{
|
@md{
|
||||||
babeuoastnuhaoesn
|
en tabell
|
||||||
|
|
||||||
hej hopp
|
hej hopp
|
||||||
--- ----
|
--- ----
|
||||||
|
|
@ -32,18 +32,8 @@ ueo aoeu
|
||||||
|
|
||||||
elleR?
|
elleR?
|
||||||
|
|
||||||
@list{
|
|
||||||
Första saken
|
|
||||||
Andra saken med @bold{text}
|
|
||||||
Tredje saken
|
|
||||||
}
|
|
||||||
|
|
||||||
@section{Användning}
|
@section{Användning}
|
||||||
Första anropet: @(increment())
|
Första anropet: @(increment())
|
||||||
Andra anropet: @(increment())
|
Andra anropet: @(increment())
|
||||||
|
|
||||||
@bold{detta är ibdenrerat.
|
|
||||||
detta också.
|
|
||||||
hej}
|
|
||||||
|
|
||||||
Test av utskrift: @value[date]
|
Test av utskrift: @value[date]
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,12 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Pandoc.fs" />
|
<Compile Include="Pandoc.fs" />
|
||||||
<Compile Include="Library.fs" />
|
<Compile Include="Library.fs" />
|
||||||
<Compile Include="constructorHelpers.fs" />
|
|
||||||
<Compile Include="HtmlPrinter.fs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FParsec" Version="1.1.1" />
|
<PackageReference Include="FParsec" Version="1.1.1" />
|
||||||
<PackageReference Include="FSharp.Compiler.Service" Version="43.12.201" />
|
<PackageReference Include="FSharp.Compiler.Service" Version="43.12.201" />
|
||||||
<PackageReference Include="YamlDotNet" Version="16.3.0" />
|
<PackageReference Include="YamlDotNet" Version="16.3.0" />
|
||||||
<PackageReference Update="FSharp.Core" Version="10.1.201" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
// ReSharper disable FSharpInterpolatedString
|
|
||||||
|
|
||||||
module Fibble.FibLib.HtmlPrinter
|
|
||||||
|
|
||||||
open System.Net
|
|
||||||
|
|
||||||
module HtmlPrinter =
|
|
||||||
open Ast
|
|
||||||
open System.Net
|
|
||||||
let renderAttributes (args: (string * string) list) =
|
|
||||||
args
|
|
||||||
|> List.map (fun k -> sprintf "%s=\"%s\"" (fst k) (snd k))
|
|
||||||
|> String.concat " "
|
|
||||||
let rec renderInline =
|
|
||||||
function
|
|
||||||
| Text t -> WebUtility.HtmlEncode t
|
|
||||||
| Strong(t) -> sprintf "<strong>%s</strong>" (renderInline t)
|
|
||||||
| RawHtml h ->
|
|
||||||
h
|
|
||||||
| _ -> ""
|
|
||||||
|
|
||||||
let renderFigure a c l =
|
|
||||||
failwith "haha"
|
|
||||||
|
|
||||||
let renderListItem item =
|
|
||||||
sprintf "<li>%s</li>" item
|
|
||||||
|
|
||||||
let renderList kind attributes nodesList =
|
|
||||||
let content = nodesList
|
|
||||||
|> List.map renderListItem
|
|
||||||
|> String.concat "\n"
|
|
||||||
$"<{kind} {(renderAttributes attributes)}>{content}</{kind}>"
|
|
||||||
|
|
||||||
|
|
||||||
let rec render blocks =
|
|
||||||
let doubleRender blocksblock =
|
|
||||||
List.map render blocksblock
|
|
||||||
|
|
||||||
let renderNode = function
|
|
||||||
| Paragraph [RawHtml html] ->
|
|
||||||
html
|
|
||||||
| Paragraph nodes when nodes |> List.forall (function
|
|
||||||
| RawHtml _ -> true
|
|
||||||
| Text t when System.String.IsNullOrWhiteSpace(t) -> true
|
|
||||||
| _ -> false) ->
|
|
||||||
nodes
|
|
||||||
|> List.choose (function RawHtml h -> Some h | _ -> None)
|
|
||||||
|> String.concat "\n"
|
|
||||||
| Paragraph c -> sprintf "<p>%s</p>" (c |> List.map renderInline |> String.concat "")
|
|
||||||
| Section(l, _, c) -> sprintf "<h%d>%s</h%d>" l (c |> List.map renderInline |> String.concat "") l
|
|
||||||
| CodeBlock(attributes, text) -> sprintf "<code>%s</code>" text
|
|
||||||
| Figure(attributes, caption, blocks) -> renderFigure attributes caption blocks
|
|
||||||
| ListBlock(l) -> match l with
|
|
||||||
| BulletList(attr, blocknodes) -> renderList "ul" attr.kvp (doubleRender blocknodes)
|
|
||||||
| Orderedlist(attr, start, blocknodes) -> renderList "ol" attr.kvp (doubleRender blocknodes)
|
|
||||||
| Plain nodes -> List.map renderInline nodes |> String.concat " "
|
|
||||||
blocks
|
|
||||||
|> List.map renderNode
|
|
||||||
|> String.concat "\n"
|
|
||||||
|
|
@ -10,55 +10,32 @@ open System.Collections.Generic
|
||||||
// 1. AST & Utils
|
// 1. AST & Utils
|
||||||
// ==========================================
|
// ==========================================
|
||||||
module Ast =
|
module Ast =
|
||||||
|
|
||||||
type Attr = { id: string; classes: string list; kvp: (string * string) list }
|
|
||||||
|
|
||||||
|
|
||||||
type InlineNode =
|
type InlineNode =
|
||||||
| Text of string
|
| Text of string
|
||||||
| RawHtml of string
|
| RawHtml of string
|
||||||
| Emph of InlineNode list
|
|
||||||
| Underline of InlineNode list
|
|
||||||
| Strong of InlineNode list
|
|
||||||
| Strikeout of InlineNode list
|
|
||||||
| Superscript of InlineNode list
|
|
||||||
| Subscript of InlineNode list
|
|
||||||
| Link of attributes: Attr * target: Target
|
|
||||||
| Code of attributes: Attr * text: string
|
|
||||||
| Image of attributes: Attr * altText: InlineNode list * target: Url
|
|
||||||
| Note of BlockNode list
|
|
||||||
| SoftBreak
|
|
||||||
| LineBreak
|
|
||||||
| Expr of code: string * result: string option
|
| Expr of code: string * result: string option
|
||||||
| Command of tag: string * args: string list * kwargs: Map<string, string> * children: InlineNode list
|
| Element of tag: string * args: (string * string) list * children: InlineNode list
|
||||||
and BlockNode =
|
|
||||||
| CodeBlock of attributes: Attr * text: string
|
type BlockNode =
|
||||||
| Figure of attributes: Attr * caption: InlineNode list * blocks: BlockNode list
|
|
||||||
| ListBlock of ListKind
|
|
||||||
| Plain of InlineNode list
|
|
||||||
| Paragraph of children: InlineNode list
|
|
||||||
| Section of level: int * args: (string * string) list * children: InlineNode list
|
| Section of level: int * args: (string * string) list * children: InlineNode list
|
||||||
and ListKind =
|
| Paragraph of children: InlineNode list
|
||||||
| Orderedlist of attributes: Attr * start: int * blocksList: (BlockNode list) list
|
|
||||||
| BulletList of attributes: Attr * blocksList: (BlockNode list) list
|
|
||||||
and Url = string
|
|
||||||
and Target = Url * InlineNode list
|
|
||||||
|
|
||||||
type Document = BlockNode list
|
type Document = BlockNode list
|
||||||
|
|
||||||
type TagRenderer = Map<string,string>
|
type TagRenderer = Map<string,string> -> (string * string) list -> InlineNode list -> InlineNode
|
||||||
-> string list
|
|
||||||
-> Map<string,string>
|
|
||||||
-> InlineNode list -> InlineNode
|
|
||||||
|
|
||||||
let rec stringifyNodes (nodes: InlineNode list) =
|
let rec stringifyNodes (nodes: InlineNode list) =
|
||||||
|
let tupleToString (t: string * string) = sprintf "%s=\"%s\"" (fst t) (snd t)
|
||||||
nodes
|
nodes
|
||||||
|> List.map (function
|
|> List.map (function
|
||||||
| Text t -> t
|
| Text t -> t
|
||||||
| RawHtml h -> h
|
| RawHtml h -> h
|
||||||
|
| Element(tag, args, children) ->
|
||||||
|
// Omvandla inre taggar till HTML
|
||||||
|
let attrs = if args.IsEmpty then "" else " " + String.concat " " (List.map tupleToString args)
|
||||||
|
sprintf "<%s%s>%s</%s>" tag attrs (stringifyNodes children) tag
|
||||||
| Expr(_, Some res) -> res
|
| Expr(_, Some res) -> res
|
||||||
| Expr(code, None) -> sprintf "@(%s)" code // Fallback om den inte evaluerats
|
| Expr(code, None) -> sprintf "@(%s)" code // Fallback om den inte evaluerats
|
||||||
| _ -> failwith "haha"
|
|
||||||
)
|
)
|
||||||
|> String.concat ""
|
|> String.concat ""
|
||||||
|
|
||||||
|
|
@ -66,63 +43,32 @@ module Ast =
|
||||||
|
|
||||||
module Utils =
|
module Utils =
|
||||||
open Ast
|
open Ast
|
||||||
let dedentNodes (nodes: InlineNode list) =
|
let smartDedent (input: string) =
|
||||||
let fullText =
|
let lines = input.Replace("\r\n", "\n").Split '\n' |> List.ofArray
|
||||||
nodes |> List.choose (function Text t -> Some t | _ -> None) |> String.concat ""
|
|
||||||
|
|
||||||
let lines = fullText.Replace("\r\n", "\n").Split('\n')
|
// 1. Hitta den minsta indenteringen bland alla rader som har text
|
||||||
|
|
||||||
// 1. Räkna BARA ut minIndent från rader som kommer efter en radbrytning (skippa rad 0)
|
|
||||||
let minIndent =
|
let minIndent =
|
||||||
if lines.Length <= 1 then 0
|
lines
|
||||||
else
|
|> List.filter (fun l -> not (System.String.IsNullOrWhiteSpace(l)))
|
||||||
lines |> Array.skip 1
|
|> List.map (fun l -> l.Length - l.TrimStart().Length)
|
||||||
|> Array.filter (fun l -> not (System.String.IsNullOrWhiteSpace(l)))
|
|> function
|
||||||
|> Array.map (fun l -> l.Length - l.TrimStart().Length)
|
| [] -> 0
|
||||||
|> function [||] -> 0 | arr -> Array.min arr
|
| indents -> List.min indents
|
||||||
|
|
||||||
let mutable isFirstText = true
|
// 2. Dra av exakt så många mellanslag från alla rader
|
||||||
let indentStr = "\n" + String.replicate minIndent " "
|
// för att dessa inte ska vara med i det slutgiltiga dokumentet.
|
||||||
|
|
||||||
// 2. Applicera formateringen
|
|
||||||
let dedented =
|
let dedented =
|
||||||
nodes |> List.map (function
|
lines
|
||||||
| Text t ->
|
|> List.map (fun l ->
|
||||||
let t1 = t.Replace("\r\n", "\n")
|
if System.String.IsNullOrWhiteSpace(l) then ""
|
||||||
|
else l.Substring(minIndent)
|
||||||
// Ta bort inledande mellanslag på den allra första texten direkt efter '{'
|
|
||||||
let t2 =
|
|
||||||
if isFirstText then
|
|
||||||
isFirstText <- false
|
|
||||||
t1.TrimStart(' ', '\t')
|
|
||||||
else t1
|
|
||||||
|
|
||||||
// Ta bort minIndent antal mellanslag efter varje radbrytning i noden
|
|
||||||
let t3 = if minIndent > 0 then t2.Replace(indentStr, "\n") else t2
|
|
||||||
Text t3
|
|
||||||
| otherNode -> otherNode
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 3. Städa bort överflödiga radbrytningar och blanksteg i ytterkanterna
|
// 3. Slå ihop och städa bort överflödiga radbrytningar i början/slutet
|
||||||
let rec trimStart = function
|
(String.concat "\n" dedented).Trim('\n', '\r')
|
||||||
| Text t :: rest ->
|
|
||||||
let trimmed = t.TrimStart('\n', '\r', ' ', '\t')
|
|
||||||
if trimmed = "" then trimStart rest else Text trimmed :: rest
|
|
||||||
| other -> other
|
|
||||||
|
|
||||||
let rec trimEnd = function
|
|
||||||
| Text t :: rest ->
|
|
||||||
let trimmed = t.TrimEnd('\n', '\r', ' ', '\t')
|
|
||||||
if trimmed = "" then trimEnd rest else Text trimmed :: rest
|
|
||||||
| other -> other
|
|
||||||
|
|
||||||
dedented |> trimStart |> List.rev |> trimEnd |> List.rev
|
|
||||||
|
|
||||||
let positional f: TagRenderer =
|
let positional f: TagRenderer =
|
||||||
fun _ (args: string list) _ children -> f args children
|
fun meta (args: (string*string) list) children -> f meta (List.map snd args) children
|
||||||
|
|
||||||
let onlyArgs f =
|
|
||||||
fun _ args kwargs children -> f args kwargs
|
|
||||||
|
|
||||||
let getArgIdx (args: (string*string) list) index defaultVal =
|
let getArgIdx (args: (string*string) list) index defaultVal =
|
||||||
let unnamed = args |> List.filter (fun (k, _) -> k = "")
|
let unnamed = args |> List.filter (fun (k, _) -> k = "")
|
||||||
|
|
@ -141,6 +87,10 @@ module Utils =
|
||||||
let withArg2 (k1: string) (d1: string) (k2: string) (d2: string) (f: string -> string -> InlineNode list -> InlineNode) =
|
let withArg2 (k1: string) (d1: string) (k2: string) (d2: string) (f: string -> string -> InlineNode list -> InlineNode) =
|
||||||
fun _ args children -> f (getArg args k1 0 d1) (getArg args k2 1 d2) children
|
fun _ args children -> f (getArg args k1 0 d1) (getArg args k2 1 d2) children
|
||||||
|
|
||||||
|
let nameToElement (n:string) : TagRenderer =
|
||||||
|
fun meta args children -> Element(n, args, children)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -162,49 +112,52 @@ module Parser =
|
||||||
let isSection (name: string) = name.EndsWith("section")
|
let isSection (name: string) = name.EndsWith("section")
|
||||||
|
|
||||||
let pNewline = newline
|
let pNewline = newline
|
||||||
|
let pArg =
|
||||||
|
spaces >>.
|
||||||
|
choice [
|
||||||
|
attempt (many1Chars (asciiLetter <|> digit <|> anyOf "-_")
|
||||||
|
.>> spaces .>> pchar '=' .>> spaces
|
||||||
|
.>>. manyChars (noneOf ",]"))
|
||||||
|
|>> fun (k, v) -> (k, v.Trim())
|
||||||
|
|
||||||
let pNamedArg =
|
// Fallback: bara "värde" (ges en tom nyckel)
|
||||||
// Leta efter "nyckel=värde"
|
manyChars (noneOf ",]") |>> fun v -> ("", v.Trim())
|
||||||
attempt (many1Chars (asciiLetter <|> digit <|> anyOf "-_")
|
] .>> spaces
|
||||||
.>> spaces .>> pchar '=' .>> spaces)
|
let pArgs = between (pstring "[") (pstring "]") (sepBy pArg (pstring ","))
|
||||||
.>>. manyChars (noneOf ",]")
|
|
||||||
|>> fun (k, v) -> (k, v.Trim())
|
|
||||||
|
|
||||||
let pPositionalArg =
|
|
||||||
// Bara "värde"
|
|
||||||
manyChars (noneOf ",]") |>> fun v -> ("", v.Trim())
|
|
||||||
|
|
||||||
let pSingleArg = spaces >>. (pNamedArg <|> pPositionalArg) .>> spaces
|
|
||||||
|
|
||||||
let pArgs =
|
|
||||||
between (pstring "[") (pstring "]") (sepBy pSingleArg (pchar ','))
|
|
||||||
>>= fun args ->
|
|
||||||
// Validera att positionella argument alltid kommer först
|
|
||||||
let rec validate canBePositional = function
|
|
||||||
| [] -> preturn args // Allt är okej, returnera listan
|
|
||||||
| ("", _) :: tail ->
|
|
||||||
if not canBePositional then
|
|
||||||
fail "Syntaxfel: Positionella argument får inte komma efter namngivna argument."
|
|
||||||
else validate true tail
|
|
||||||
| _ :: tail ->
|
|
||||||
validate false tail
|
|
||||||
|
|
||||||
validate true args
|
|
||||||
|
|
||||||
// --- 1. Måsvinge-parser (för @kommandon) ---
|
// --- 1. Måsvinge-parser (för @kommandon) ---
|
||||||
// Lägg till en referens för pBody högst upp bland dina referenser
|
let pRawBody, pRawBodyRef = createParserForwardedToRef<string, unit>()
|
||||||
// (Bör ligga precis under let pInline, pInlineRef = ...)
|
pRawBodyRef.Value <-
|
||||||
let pBody, pBodyRef = createParserForwardedToRef<InlineNode list, unit>()
|
many (choice [
|
||||||
|
many1Chars (noneOf "{}")
|
||||||
|
pchar '{' >>. pRawBodyRef.Value .>> pchar '}' |>> sprintf "{%s}"
|
||||||
|
]) |>> String.concat ""
|
||||||
|
|
||||||
|
let pBody =
|
||||||
|
between (pstring "{") (pstring "}") pRawBodyRef.Value >>= fun raw ->
|
||||||
|
// En mer tillåtande parser isolerad för innehållet inuti {...}
|
||||||
|
let pInnerInline =
|
||||||
|
choice [
|
||||||
|
attempt pInline
|
||||||
|
// Fångar upp dubbla radbrytningar och måsvingar som pInline normalt blockerar
|
||||||
|
many1Chars (anyOf "\r\n{}") |>> Text
|
||||||
|
]
|
||||||
|
|
||||||
|
match run (many pInnerInline .>> eof) (Utils.smartDedent raw) with
|
||||||
|
| Success(n, _, _) -> preturn n
|
||||||
|
| Failure(m, _, _) -> fail m
|
||||||
|
|
||||||
// --- 2. Parentes-parser (för @(...) med sträng-stöd) ---
|
// --- 2. Parentes-parser (för @(...) med sträng-stöd) ---
|
||||||
let pParenBody, pParenBodyRef = createParserForwardedToRef<string, unit>()
|
let pParenBody, pParenBodyRef = createParserForwardedToRef<string, unit>()
|
||||||
|
|
||||||
|
// En inre parser som känner igen F#-strängar och escape-tecken (\")
|
||||||
let pFSharpString =
|
let pFSharpString =
|
||||||
let normal = many1Chars (noneOf "\"\\")
|
let normal = many1Chars (noneOf "\"\\")
|
||||||
let escaped = pstring "\\" >>. anyChar |>> sprintf "\\%c"
|
let escaped = pstring "\\" >>. anyChar |>> sprintf "\\%c"
|
||||||
pstring "\"" .>>. manyStrings (normal <|> escaped) .>>. pstring "\""
|
pstring "\"" .>>. manyStrings (normal <|> escaped) .>>. pstring "\""
|
||||||
|>> fun ((start, inner), end_) -> start + inner + end_
|
|>> fun ((start, inner), end_) -> start + inner + end_
|
||||||
|
|
||||||
|
// Själva loopen letar nu efter strängar FÖRST, sen vanlig text, och sist inre parenteser
|
||||||
pParenBodyRef.Value <-
|
pParenBodyRef.Value <-
|
||||||
manyStrings (choice [
|
manyStrings (choice [
|
||||||
pFSharpString
|
pFSharpString
|
||||||
|
|
@ -219,38 +172,19 @@ module Parser =
|
||||||
// --- Övriga inline-parsers ---
|
// --- Övriga inline-parsers ---
|
||||||
let pMultilineCode =
|
let pMultilineCode =
|
||||||
pstring "@\"\"\"" >>. manyCharsTill anyChar (pstring "\"\"\"")
|
pstring "@\"\"\"" >>. manyCharsTill anyChar (pstring "\"\"\"")
|
||||||
|>> fun c -> Expr(c, None)
|
|>> fun c -> Expr(Utils.smartDedent c, None)
|
||||||
|
|
||||||
// pInlineCommand använder nu forward-referensen pBodyRef
|
|
||||||
let pInlineCommand =
|
let pInlineCommand =
|
||||||
attempt (pchar '@' >>. many1Chars asciiLetter)
|
attempt (
|
||||||
|
pchar '@' >>. many1Chars asciiLetter >>= fun name ->
|
||||||
|
if isSection name then fail "Sektioner är block-element."
|
||||||
|
else preturn name
|
||||||
|
)
|
||||||
.>>. opt pArgs
|
.>>. opt pArgs
|
||||||
.>>. opt pBody
|
.>>. opt pBody
|
||||||
|>> fun ((name, argsOpt), bodyOpt) ->
|
|>> fun ((n, a), b) -> Element(n, defaultArg a [], defaultArg b [])
|
||||||
let rawArgs = defaultArg argsOpt []
|
|
||||||
let posArgs = rawArgs |> List.choose (fun (k, v) -> if k = "" then Some v else None)
|
|
||||||
let kwargs = rawArgs |> List.filter (fun (k, _) -> k <> "") |> Map.ofList
|
|
||||||
|
|
||||||
// dedentNodes anropas här från Utils
|
// MÅSTE tilldelas efter att alla pExpr, pInlineCommand etc. är definierade
|
||||||
let children = defaultArg bodyOpt [] |> Utils.dedentNodes
|
|
||||||
|
|
||||||
Command(name, posArgs, kwargs, children)
|
|
||||||
|
|
||||||
// Nu när pInlineCommand, pExpr och pMultilineCode är definierade
|
|
||||||
// kan vi skapa pInnerInline
|
|
||||||
let pInnerInline =
|
|
||||||
choice [
|
|
||||||
attempt pInlineCommand
|
|
||||||
attempt pExpr
|
|
||||||
pMultilineCode
|
|
||||||
many1Chars (noneOf "@}") |>> Text
|
|
||||||
pchar '@' |>> fun _ -> Text "@"
|
|
||||||
]
|
|
||||||
|
|
||||||
// Tilldela värdet till pBodyRef
|
|
||||||
pBodyRef.Value <- between (pstring "{") (pstring "}") (many pInnerInline)
|
|
||||||
|
|
||||||
// Tilldela värdet till pInlineRef
|
|
||||||
pInlineRef.Value <- choice [
|
pInlineRef.Value <- choice [
|
||||||
pMultilineCode
|
pMultilineCode
|
||||||
pExpr
|
pExpr
|
||||||
|
|
@ -310,13 +244,47 @@ module Execution =
|
||||||
open Ast
|
open Ast
|
||||||
|
|
||||||
let rec transform (metadata: Map<string, string>) (prelude: Map<string, TagRenderer>) (eval: IEvaluator) = function
|
let rec transform (metadata: Map<string, string>) (prelude: Map<string, TagRenderer>) (eval: IEvaluator) = function
|
||||||
| Command(name, args, kwargs, children) when prelude.ContainsKey name ->
|
| Element(n, a, c) when prelude.ContainsKey n ->
|
||||||
prelude.[name] metadata args kwargs (children |> List.map (transform metadata prelude eval))
|
prelude.[n] metadata a (c |> List.map (transform metadata prelude eval))
|
||||||
| Command(n, _, _ ,_) -> failwithf "%s is not a defined command" n
|
| Element(n, a, c) ->
|
||||||
|
Element(n, a, c |> List.map (transform metadata prelude eval))
|
||||||
| Expr(c, _) -> RawHtml (eval.Evaluate c)
|
| Expr(c, _) -> RawHtml (eval.Evaluate c)
|
||||||
| n -> n
|
| n -> n
|
||||||
|
|
||||||
|
module HtmlPrinter =
|
||||||
|
open Ast
|
||||||
|
let voidElements = [ "area"; "base"; "br"; "col"; "command"; "embed"; "hr"; "img"; "input"; "keygen"; "link"; "meta"; "param"; "source"; "track"; "wbr"]
|
||||||
|
let renderAttributes args =
|
||||||
|
args
|
||||||
|
|> List.map (fun m -> sprintf "%s=\"%s\"" (fst m) (snd m))
|
||||||
|
|> String.concat " "
|
||||||
|
let rec renderInline =
|
||||||
|
function
|
||||||
|
| Text t -> WebUtility.HtmlEncode t
|
||||||
|
| RawHtml h -> h
|
||||||
|
| Element(t,a,c) when List.contains t voidElements -> sprintf "<%s %s />" t (renderAttributes a)
|
||||||
|
| Element(t, a, c) -> sprintf "<%s %s>%s</%s>" t (renderAttributes a) (c |> List.map renderInline |> String.concat "") t
|
||||||
|
| _ -> ""
|
||||||
|
|
||||||
|
let render (header, blocks) =
|
||||||
|
blocks
|
||||||
|
|> List.map (function
|
||||||
|
| Paragraph [RawHtml html] ->
|
||||||
|
html
|
||||||
|
|
||||||
|
// 2. (Frivillig) Mer robust guard om parsern råkar lämna kvar
|
||||||
|
// blanksteg (Text " ") runt ditt @md-block i samma paragraf
|
||||||
|
| Paragraph nodes when nodes |> List.forall (function
|
||||||
|
| RawHtml _ -> true
|
||||||
|
| Text t when System.String.IsNullOrWhiteSpace(t) -> true
|
||||||
|
| _ -> false) ->
|
||||||
|
|
||||||
|
nodes
|
||||||
|
|> List.choose (function RawHtml h -> Some h | _ -> None)
|
||||||
|
|> String.concat "\n"
|
||||||
|
| Paragraph c -> sprintf "<p>%s</p>" (c |> List.map renderInline |> String.concat "")
|
||||||
|
| Section(l, _, c) -> sprintf "<h%d>%s</h%d>" l (c |> List.map renderInline |> String.concat "") l)
|
||||||
|
|> String.concat "\n"
|
||||||
|
|
||||||
|
|
||||||
module Evaluators =
|
module Evaluators =
|
||||||
|
|
@ -325,8 +293,8 @@ module Evaluators =
|
||||||
open FSharp.Compiler.Interactive.Shell
|
open FSharp.Compiler.Interactive.Shell
|
||||||
|
|
||||||
type FsiEvaluator() =
|
type FsiEvaluator() =
|
||||||
let sbOut = StringBuilder()
|
let sbOut = new StringBuilder()
|
||||||
let sbErr = StringBuilder()
|
let sbErr = new StringBuilder()
|
||||||
let inStream = new StringReader("")
|
let inStream = new StringReader("")
|
||||||
let outStream = new StringWriter(sbOut)
|
let outStream = new StringWriter(sbOut)
|
||||||
let errStream = new StringWriter(sbErr)
|
let errStream = new StringWriter(sbErr)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
namespace Fibble.FibLib
|
namespace Fibble.FibLib
|
||||||
|
|
||||||
open System
|
|
||||||
open System.Xml.Schema
|
|
||||||
|
|
||||||
module Pandoc =
|
module Pandoc =
|
||||||
open System.Diagnostics
|
open System.Diagnostics
|
||||||
|
|
||||||
|
|
@ -20,16 +17,14 @@ module Pandoc =
|
||||||
use proc = Process.Start startInfo
|
use proc = Process.Start startInfo
|
||||||
// Skriv markdown till Pandoc
|
// Skriv markdown till Pandoc
|
||||||
use stdin = proc.StandardInput
|
use stdin = proc.StandardInput
|
||||||
stdin.WriteLine markdownText
|
stdin.Write markdownText
|
||||||
stdin.Close() // Måste stängas så Pandoc vet att texten är slut
|
stdin.Close() // Måste stängas så Pandoc vet att texten är slut
|
||||||
// Läs ut resultatet
|
// Läs ut resultatet
|
||||||
let htmlOutput = proc.StandardOutput.ReadToEnd()
|
let htmlOutput = proc.StandardOutput.ReadToEnd()
|
||||||
let errorOutput = proc.StandardError.ReadToEnd()
|
let errorOutput = proc.StandardError.ReadToEnd()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc.WaitForExit()
|
proc.WaitForExit()
|
||||||
Console.WriteLine(htmlOutput)
|
|
||||||
if proc.ExitCode = 0 then
|
if proc.ExitCode = 0 then
|
||||||
htmlOutput.Trim()
|
htmlOutput.Trim()
|
||||||
else
|
else
|
||||||
|
|
@ -40,5 +35,4 @@ module Pandoc =
|
||||||
sprintf "\n<pre>%s</pre> and <pre>%s</pre>" ex.Message markdownText
|
sprintf "\n<pre>%s</pre> and <pre>%s</pre>" ex.Message markdownText
|
||||||
|
|
||||||
let mdToHtml markdownText =
|
let mdToHtml markdownText =
|
||||||
let res= toHtml "markdown" markdownText
|
toHtml "markdown" markdownText
|
||||||
res
|
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
namespace Fibble.FibLib
|
|
||||||
|
|
||||||
open FSharp.Compiler.Text
|
|
||||||
open Fibble.FibLib.Ast
|
|
||||||
|
|
||||||
module Helpers =
|
|
||||||
let emptyAttr ={id=""; classes=[]; kvp=[]}
|
|
||||||
let ah cls kvp =
|
|
||||||
{id=""; classes = [cls]; kvp = Map.toList kvp }
|
|
||||||
let onlyChildren constructor : TagRenderer =
|
|
||||||
fun _ _ _ children -> constructor(children)
|
|
||||||
|
|
||||||
open Helpers
|
|
||||||
|
|
||||||
module ConstructionHelpers =
|
|
||||||
let linebreak _ _ _ _ = LineBreak
|
|
||||||
let softbreak _ _ _ _ = SoftBreak
|
|
||||||
|
|
||||||
let value : TagRenderer =
|
|
||||||
fun meta args _ _ ->
|
|
||||||
match Map.tryFind args.Head meta with
|
|
||||||
| Some(v) -> Text v
|
|
||||||
| None -> Text $"value {args.Head} not found in metadata"
|
|
||||||
|
|
||||||
|
|
||||||
let emph = onlyChildren Emph
|
|
||||||
let underline = onlyChildren Underline
|
|
||||||
let strong = onlyChildren Strong
|
|
||||||
let strikeout = onlyChildren Strikeout
|
|
||||||
let superscript = onlyChildren Superscript
|
|
||||||
let subscript = onlyChildren Subscript
|
|
||||||
|
|
||||||
let image : TagRenderer =
|
|
||||||
fun _ args kwargs children ->
|
|
||||||
let attributes = ah "inlineImage" kwargs
|
|
||||||
Image(attributes,children, args[0])
|
|
||||||
let code : TagRenderer =
|
|
||||||
fun _ _ kwargs children ->
|
|
||||||
let attributes = ah "inlineCode" kwargs
|
|
||||||
match children with
|
|
||||||
| [Text(c)] -> InlineNode.Code(attributes, c)
|
|
||||||
| _ -> failwith "Code tag was not Text,"
|
|
||||||
|
|
||||||
let link : TagRenderer =
|
|
||||||
fun _ args kwargs children ->
|
|
||||||
let attributes = ah "link" kwargs
|
|
||||||
Link(attributes,Target(args[0], children))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// blocks
|
|
||||||
let paragraph _ _ _ children = Paragraph(children)
|
|
||||||
let plain _ _ _ children = Plain(children)
|
|
||||||
|
|
@ -127,6 +127,10 @@ let processDocument (source: string) =
|
||||||
|
|
||||||
// Hjälpfunktion: Gå igenom trädet och byt ut @value{...} mot faktiskt data från YAML
|
// Hjälpfunktion: Gå igenom trädet och byt ut @value{...} mot faktiskt data från YAML
|
||||||
let rec resolveMeta = function
|
let rec resolveMeta = function
|
||||||
|
| MetaRef key ->
|
||||||
|
match metadata.TryFind key with
|
||||||
|
| Some v -> Text v
|
||||||
|
| None -> Text (sprintf "[Saknad meta: %s]" key)
|
||||||
| Element(tag, args, children) ->
|
| Element(tag, args, children) ->
|
||||||
Element(tag, args, children |> List.map resolveMeta)
|
Element(tag, args, children |> List.map resolveMeta)
|
||||||
| other -> other
|
| other -> other
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue