cripslock

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit a035deb09d7d8b50240183700a6d0709e6a11c7e
parent 4b189d1fa7bd2174592cf2174b9a810be932eccb
Author: Daniel Barlow <dan@telent.net>
Date:   Mon,  4 Aug 2025 23:20:11 +0100

textile: use lpeg for the code handling inline markup

this means we can handle (@code@) correctly, and also opens the way to
support __, ** etc if we find we've actually written any textile that
needs it

Diffstat:
Mdefault.nix | 1+
Mtextile.fnl | 167+++++++++++++++++++++++++++++++++++++++++--------------------------------------
2 files changed, 87 insertions(+), 81 deletions(-)

diff --git a/default.nix b/default.nix @@ -30,6 +30,7 @@ let readline http lcmark + lpeg ]); pname = "cripslock"; in stdenv.mkDerivation { diff --git a/textile.fnl b/textile.fnl @@ -1,5 +1,9 @@ (import-macros { : define-tests : expect : expect= } :assert) +(local lpeg (require :lpeg)) +(local loc (lpeg.locale)) +(local { : view } (require :fennel)) + (local { : to-html } (require :html)) (local { : prepend } (require :lib)) @@ -69,78 +73,83 @@ ;; @code@ ;; %(classname)span% +(fn merging-concat [tbl e] + (let [last (# tbl) + string? (fn [x] (= (type x) :string))] + (if (and (string? (. tbl last)) (string? e)) + (tset tbl last (.. (. tbl last) e)) + (table.insert tbl e)) + tbl)) + +(fn merge-strings [tbl] + (accumulate [o [] + _ v (ipairs tbl)] + (merging-concat o v))) + +(define-tests :merge-strings + (expect= (merge-strings []) []) + (expect= (merge-strings ["a"]) ["a"]) + (expect= (merge-strings ["a" "b" "j"]) ["abj"]) + (expect= (merge-strings ["a" 7 "j"]) ["a" 7 "j"]) + (expect= (merge-strings ["a" "b" 7 ",j"]) ["ab" 7 ",j"])) + + +(fn trace [x] + (print :TRACE (view x)) + x) + +(var inline-parser nil) +(set + inline-parser + (let [{ : P : V : C : B : Cp : Ct : S} lpeg + special (S "_*\"@-") + except (fn [x] (^ (- (P 1) x) 1)) + recurse #(merge-strings (inline-parser:match $1))] + (P { + 1 :elements + :elements (Ct (^ (V :element) 0)) + :element (+ + (/ (^ (except special) 1) #$1) + (V :strong) + (V :code) + (V :emph) + (V :link) + (V :del) + (C special)) + + :emph (* (P "_") + (/ (except (P "_")) #(prepend :em (recurse $1))) + (P "_")) + + :strong (* (P "*") + (/ (except (P "*")) #(prepend :strong (recurse $1))) + (P "*")) + + :del (* (B loc.space) + (P "-") + (/ (* (- (P 1) (S " -")) (except (P "-"))) + #(prepend :del (recurse $1))) + (P "-")) + + :code (* (B (S " (")) + (P "@") + (/ (except (P "@")) #[:code $1]) + (P "@")) + + :link (/ (* (P "\"") (C (except "\"")) (P "\":") + (C (except loc.space))) + #(prepend [:a {:href $2}] (recurse $1))) + }))) + +(fn inline-markup [x] (merge-strings (inline-parser:match x))) -;; walk the string from left to right. on encountering -;; any recognised opening token for inline markup, find the -;; matching closing token, and recursively walk the bit in the -;; middle. Note that markup may be nested - -(fn scan-tok [endtok tag] - (fn [f str] - (let [(s e) (string.find str endtok 3 true)] - (if s - (values - (doto (f (string.sub str 2 (- s 1))) - (table.insert 1 tag)) - (string.sub str (+ e 1))) - (values str "") - )))) - -(local inline-tokens { - "_" (scan-tok "_" :em) - "*" (scan-tok "*" :strong) - "@" (let [s (scan-tok "@" :code)] - (fn [_ str] (s (fn [x] [x]) str))) - - "\"" (fn [f str] - (let [(title url unparsed) - (string.match str "^\"([^\"]-)\":(%g+)%f[%G](.*)$")] - (if title - (values - (doto (f title) - (table.insert 1 [:a {:href url} ])) - unparsed) - (values "\"" (string.sub str 2)) - ))) - ;; this one must be last - "-" (scan-tok "-" :del) - }) - -(local inline-token-pattern - (.. "%f[%g]([" - (accumulate [chrs "" - c _ (pairs inline-tokens)] - (.. "%" (string.sub c 1 1) chrs)) - "])%g")) - -(fn inline-markup [str] - (if (or (not str) (= str "")) - nil - (case (string.find str inline-token-pattern) - (start _end tok) - (let [end (+ start -1 (# tok)) - (during after) - ((. inline-tokens tok) inline-markup (string.sub str end)) - marked-after (or (inline-markup after) [])] - (if (> start 1) - (list-append - [ - (string.sub str 1 (- start 1)) - during - ] - marked-after - ) - (list-append - [ - during - ] - marked-after - ))) - _ [str]))) (define-tests :inline-markup + (expect= (inline-markup "") []) + (expect= (inline-markup "foo") ["foo"]) (expect= (inline-markup "foo _emph_") ["foo " [:em "emph"] ]) + (expect= (inline-markup "foo -struck-") ["foo " [:del "struck"] ]) (expect= (inline-markup "foo @1+2+3@") ["foo " [:code "1+2+3"] ]) ;; markup in code blocks is ignored (expect= (inline-markup "foo @1+_2_+3@") ["foo " [:code "1+_2_+3"] ]) @@ -152,7 +161,7 @@ (expect= (inline-markup "foo @ example.com") ["foo @ example.com" ]) (expect= (inline-markup "say \"hello\" to my friend") - ["say " "\"" "hello\" to my friend"]) + ["say \"hello\" to my friend"]) (expect= (inline-markup "foo *strong*") ["foo " [:strong "strong"]]) @@ -178,9 +187,7 @@ image and rely on the \"kernel mtdsplit code\":https://ww.telent.net/2018/4/16/flash_ah_ah to find the root filesystem. ") ["when doing -a " - "\"" - "development\" build, now we make a single agglomerated firmware +a \"development\" build, now we make a single agglomerated firmware image and rely on the " [["a" {:href "https://ww.telent.net/2018/4/16/flash_ah_ah"}] "kernel mtdsplit @@ -190,16 +197,14 @@ filesystem. "]) (expect= (inline-markup "use the --help option to be lied to") - ["use the " "--help option to be lied to"]) - - (comment - ;; this one doesn't work and I can't see an wasy way to fix that - (expect= (inline-markup - "(@vmlinux@ is not actually required on the target, it's a leftover))") - ["( " - ["code" "vmlinux"] - " is not actually required on the target, it's a leftover))"] - )) + ["use the --help option to be lied to"]) + + (expect= (inline-markup + "(@vmlinux@ is not actually required on the target, it's a leftover))") + ["(" + ["code" "vmlinux"] + " is not actually required on the target, it's a leftover))"] + ) )