output-template.fnl (2444B)
1 (local svc (require :anoia.svc)) 2 3 (fn json-escape [s] 4 ;; All Unicode characters may be placed within the quotation marks, 5 ;; except for the characters that MUST be escaped: 6 ;; quotation mark, reverse solidus, and the control characters (U+0000 7 ;; through U+001F). (RFC 8259) 8 (-> s 9 (string.gsub 10 "[\"\b\f\n\r\t]" { 11 "\b" "\\b" 12 "\"" "\\\"" 13 "\f" "\\f" 14 "\n" "\\n" 15 "\r" "\\r" 16 "\t" "\\t" 17 }) 18 (string.gsub 19 "([\x00-\x1b])" 20 (fn [x] (string.format "\\u%04X" (string.byte x)))))) 21 22 23 (fn substitute [text opening closing] 24 (let [delim (.. opening "(.-)" closing) 25 myenv { 26 : string 27 : table 28 : math 29 : ipairs 30 : tonumber 31 : type 32 33 :output 34 (fn [service-path path default] 35 (let [s (assert (svc.open service-path))] 36 (or (s:output path) default))) 37 :lua_quote #(string.format "%q" $1) 38 :json_quote (fn [x] (.. "\"" (json-escape x) "\"")) 39 }] 40 (string.gsub text delim 41 (fn [x] 42 (let [chunk (if (= (x:sub 1 1) ";") 43 (x:sub 2) 44 (.. "return " x))] 45 (assert ((load chunk x :t myenv)) 46 (string.format "missing value for %q" x))))))) 47 48 (fn run [] 49 (let [[opening closing] arg 50 out (substitute (: (io.input) :read "*a") opening closing)] 51 (io.write out))) 52 53 (import-macros { : define-tests : expect : expect= } :anoia.assert) 54 (define-tests 55 (expect= (pick-values 1 (substitute "var={{ 2 + 3 }}" "{{" "}}")) "var=5") 56 (expect= (pick-values 1 (substitute "{{ json_quote(\"o'reilly\") }}" "{{" "}}")) 57 "\"o'reilly\"") 58 59 (expect= (pick-values 1 (substitute "{{; local a=9; return a }}" "{{" "}}")) "9") 60 61 ;; "globals" set in one interpolation are available in subsequent ones 62 (expect= (pick-values 1 (substitute "{{; a=42; return a }} {{ a and 999 or 0 }}" "{{" "}}")) "42 999") 63 64 (fn slurp [name] 65 (with-open [f (assert (io.open name))] (f:read "*a"))) 66 (expect= 67 (pick-values 1 (substitute (slurp "example.ini") "{{" "}}")) 68 (slurp "example.ini.expected"))) 69 70 { : run }