diff --git a/src/FibLib/FibLib.fsproj b/src/FibLib/FibLib.fsproj
index ee25264..d424556 100644
--- a/src/FibLib/FibLib.fsproj
+++ b/src/FibLib/FibLib.fsproj
@@ -6,13 +6,13 @@
+
-
diff --git a/src/FibLib/Library.fs b/src/FibLib/Library.fs
index a7af3c6..fd5a9ef 100644
--- a/src/FibLib/Library.fs
+++ b/src/FibLib/Library.fs
@@ -6,7 +6,43 @@ open YamlDotNet.Serialization
open System.Collections.Generic
+// ==========================================
+// 1. AST & Utils
+// ==========================================
+module Ast =
+ type InlineNode =
+ | Text of string
+ | RawHtml of string
+ | Expr of code: string * result: string option
+ | Element of tag: string * args: (string * string) list * children: InlineNode list
+
+ type BlockNode =
+ | Section of level: int * args: (string * string) list * children: InlineNode list
+ | Paragraph of children: InlineNode list
+
+ type Document = BlockNode list
+
+ type TagRenderer = Map -> (string * string) list -> InlineNode list -> InlineNode
+
+ let rec stringifyNodes (nodes: InlineNode list) =
+ let tupleToString (t: string * string) = sprintf "%s=\"%s\"" (fst t) (snd t)
+ nodes
+ |> List.map (function
+ | Text t -> t
+ | 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(code, None) -> sprintf "@(%s)" code // Fallback om den inte evaluerats
+ )
+ |> String.concat ""
+
+
+
module Utils =
+ open Ast
let smartDedent (input: string) =
let lines = input.Replace("\r\n", "\n").Split '\n' |> List.ofArray
@@ -30,26 +66,30 @@ module Utils =
// 3. Slå ihop och städa bort överflödiga radbrytningar i början/slutet
(String.concat "\n" dedented).Trim('\n', '\r')
-
-// ==========================================
-// 1. AST & Utils
-// ==========================================
-module Ast =
- type InlineNode =
- | Text of string
- | RawHtml of string
- | Expr of code: string * result: string option
- | MetaRef of string
- | Element of tag: string * args: string list * children: InlineNode list
+ let positional f: TagRenderer =
+ fun meta (args: (string*string) list) children -> f meta (List.map snd args) children
- type BlockNode =
- | Section of level: int * args: string list * children: InlineNode list
- | Paragraph of children: InlineNode list
+ let getArgIdx (args: (string*string) list) index defaultVal =
+ let unnamed = args |> List.filter (fun (k, _) -> k = "")
+ if index < unnamed.Length then (snd unnamed.[index]).Trim('"')
+ else defaultVal
- type Document = BlockNode list
+ // This gets the arg defined by "key" unless it is not set, it then tries to get it by index. If that fails, it gets the defaultVal
+ let getArg (args: (string * string) list) (key: string) (index: int) (defaultVal: string) =
+ match args |> List.tryFind (fun (k, _) -> k = key) with
+ | Some (_, v) -> v.Trim('"')
+ | None -> getArgIdx args index defaultVal
+
+ let withArg1 def (f: string -> InlineNode list -> InlineNode) =
+ fun _ args children -> f (getArgIdx args 0 def) children
- type TagRenderer = Map -> string list -> 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
+
+ let nameToElement (n:string) : TagRenderer =
+ fun meta args children -> Element(n, args, children)
+
@@ -72,7 +112,17 @@ module Parser =
let isSection (name: string) = name.EndsWith("section")
let pNewline = newline
- let pArg = spaces >>. manyChars (noneOf ",]") .>> spaces
+ let pArg =
+ spaces >>.
+ choice [
+ attempt (many1Chars (asciiLetter <|> digit <|> anyOf "-_")
+ .>> spaces .>> pchar '=' .>> spaces
+ .>>. manyChars (noneOf ",]"))
+ |>> fun (k, v) -> (k, v.Trim())
+
+ // Fallback: bara "värde" (ges en tom nyckel)
+ manyChars (noneOf ",]") |>> fun v -> ("", v.Trim())
+ ] .>> spaces
let pArgs = between (pstring "[") (pstring "]") (sepBy pArg (pstring ","))
// --- 1. Måsvinge-parser (för @kommandon) ---
@@ -83,9 +133,17 @@ module Parser =
pchar '{' >>. pRawBodyRef.Value .>> pchar '}' |>> sprintf "{%s}"
]) |>> String.concat ""
- let pBody: Parser =
+ let pBody =
between (pstring "{") (pstring "}") pRawBodyRef.Value >>= fun raw ->
- match run (many pInline .>> eof) (Utils.smartDedent raw) with
+ // 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
@@ -187,7 +245,6 @@ module Execution =
let rec transform (metadata: Map) (prelude: Map) (eval: IEvaluator) = function
| Element(n, a, c) when prelude.ContainsKey n ->
- // Skicka in metadata som första argument till din funktion
prelude.[n] metadata a (c |> List.map (transform metadata prelude eval))
| Element(n, a, c) ->
Element(n, a, c |> List.map (transform metadata prelude eval))
@@ -196,17 +253,35 @@ module Execution =
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) -> sprintf "<%s>%s%s>" t (c |> List.map renderInline |> String.concat "") t
+ | 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 "%s
" (c |> List.map renderInline |> String.concat "")
| Section(l, _, c) -> sprintf "%s" l (c |> List.map renderInline |> String.concat "") l)
|> String.concat "\n"
diff --git a/src/FibLib/Pandoc.fs b/src/FibLib/Pandoc.fs
new file mode 100644
index 0000000..6b510e4
--- /dev/null
+++ b/src/FibLib/Pandoc.fs
@@ -0,0 +1,38 @@
+namespace Fibble.FibLib
+
+module Pandoc =
+ open System.Diagnostics
+
+ let toHtml (from: string) (markdownText: string) =
+ let startInfo = ProcessStartInfo(
+ FileName = "pandoc",
+ Arguments = $"-f {from} -t html5 --lua-filter strip-p.lua",
+ RedirectStandardInput = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true)
+
+ try
+ use proc = Process.Start startInfo
+ // Skriv markdown till Pandoc
+ use stdin = proc.StandardInput
+ stdin.Write markdownText
+ stdin.Close() // Måste stängas så Pandoc vet att texten är slut
+ // Läs ut resultatet
+ let htmlOutput = proc.StandardOutput.ReadToEnd()
+ let errorOutput = proc.StandardError.ReadToEnd()
+
+ proc.WaitForExit()
+
+ if proc.ExitCode = 0 then
+ htmlOutput.Trim()
+ else
+ sprintf "\n%s
and %s
" errorOutput markdownText
+
+ with ex ->
+ // Fångar upp om Pandoc inte är installerat eller inte finns i PATH
+ sprintf "\n%s
and %s
" ex.Message markdownText
+
+ let mdToHtml markdownText =
+ toHtml "markdown" markdownText
diff --git a/src/FibLib/strip-p.lua b/src/FibLib/strip-p.lua
new file mode 100644
index 0000000..3d7d3d7
--- /dev/null
+++ b/src/FibLib/strip-p.lua
@@ -0,0 +1,5 @@
+function Pandoc(doc)
+ if #doc.blocks > 0 and doc.blocks[1].t == "Para" then
+ return pandoc.Pandoc(doc.blocks[1].content, doc.meta)
+ end
+end