From 423ac382f9e73bf1ca7fc6b400f98db087cd7d22 Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Wed, 5 Jun 2024 09:21:25 -0500 Subject: Write executable This involved moving `src' to `lib' and making `bin'. `bin' holds the program, which only imports `jimmy.main' from lib. --- lib/emit.scm | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/html.scm | 61 +++++++++++++++++++++++++++++++++++++++ lib/main.scm | 19 ++++++++++++ lib/read.scm | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/util.scm | 49 +++++++++++++++++++++++++++++++ lib/wrap.scm | 21 ++++++++++++++ 6 files changed, 332 insertions(+) create mode 100644 lib/emit.scm create mode 100644 lib/html.scm create mode 100644 lib/main.scm create mode 100644 lib/read.scm create mode 100644 lib/util.scm create mode 100644 lib/wrap.scm (limited to 'lib') diff --git a/lib/emit.scm b/lib/emit.scm new file mode 100644 index 0000000..2a8ab97 --- /dev/null +++ b/lib/emit.scm @@ -0,0 +1,94 @@ +(declare (module (jimmy emit))) + +(import scheme (chicken base) + (chicken format) + (chicken irregex) + (chicken port) + (chicken string) + (only utf8-srfi-13 string-join) + (jimmy util)) + +(define-public (emit doc) + (for-each display (map format-stanza doc))) + +(define-public (emit-string doc) + (with-output-to-string + (lambda () (emit doc)))) + +(define-public formats + (make-parameter + ;; (TYPE (line . LINE-FMT) (stanza . STANZA-FMT) (inline . INLINE-FMT)) + '((para (line . "~A") + (stanza . "~A~%~%")) + (verb (line . "~A~%") + (stanza . "```~%~A```~%~%")) + (link (line . "=> ~A ~A~%") ; Note: link has 2 format arguments + (stanza . "~A~%") + (inline . "~%=> ~A ~A~%")) + (list (line . "* ~A~%") + (stanza . "~A~%")) + (quot (line . "~A") + (stanza . "> ~A~%~%")) + (hdr1 (line . "# ~A~%") + (stanza . "~A~%")) + (hdr2 (line . "## ~A~%") + (stanza . "~A~%")) + (hdr3 (line . "### ~A~%") + (stanza . "~A~%"))))) + +(define-public filters + (make-parameter + ;; (TYPE (line . LINE-FILTER) (stanza . STANZA-FILTER)) + ;; line-filter : (lambda (list-of-strs) ...) -> list-of-strs (for format) + ;; stanza-filter : (lambda (list-of-strs) ...) -> str + `((verb (line . ,identity) + (stanza . ,join-lines)) + (default + (line . ,identity) + (stanza . ,flush-lines-left))))) + +(define (format-line line el) + (cond + ((string? (car line)) ; regular stanza line + (sprintf* (get-format el 'line) + ((get-filter el 'line) line))) + ((symbol? (car line)) ; inline element + (sprintf* (get-format (car line) 'inline) + ((get-filter (car line) 'line) (cdr line)))) + (else (error "Malformed line" line)))) + +(define (format-stanza stanza) + (let* ((type (car stanza)) + (data (cdr stanza)) + (text (map (lambda (ln) + (format-line ln type)) + data))) + (sprintf (get-format type 'stanza) + ((get-filter type 'stanza) text)))) + +;;; Utilities + +(define (get-from alist el scope) + (or (alist-walk alist el scope) + (alist-walk alist 'default scope) + (and (eq? scope 'inline) + (alist-walk alist 'default 'line)))) + +(define (get-format el scope) + (or (get-from (formats) el scope) + "")) +(define (get-filter el scope) (get-from (filters) el scope)) + +(define (sprintf* fmt lis) + (let loop ((num (length (irregex-extract "~[aA]" fmt))) + (lis lis) + (out '())) + (cond + ((null? lis) + (apply sprintf fmt (reverse out))) + ((= 1 num) + (loop 0 '() (cons (string-join lis) out))) + (else + (loop (- num 1) + (cdr lis) + (cons (car lis) out)))))) diff --git a/lib/html.scm b/lib/html.scm new file mode 100644 index 0000000..07cd921 --- /dev/null +++ b/lib/html.scm @@ -0,0 +1,61 @@ +(declare (module (jimmy html))) + +(import scheme (chicken base) + (chicken irregex) + (jimmy emit) + (jimmy util)) + +(define (escape-entities s) + (irregex-replace/all "[&<>]" s + (lambda (m) + (let ((c (irregex-match-substring m))) + (cond + ((equal? c "&") "&") + ((equal? c "<") "<") + ((equal? c ">") ">")))))) + +(define (add-inline-markup s) + (define (char->tag ch tag) + (lambda (s) + (irregex-replace/all `(: ,ch ($ (* (~ ,ch))) ,ch) s + "<" tag ">" 1 ""))) + + ((o (char->tag "*" "b") + (char->tag "_" "i") + (char->tag "`" "code")) s)) + +(formats + '((para (line . "~a~%") + (stanza . "

~% ~a

~%")) + (verb (line . "~a~%") + (stanza . "
~a
~%")) + (link (line . "
  • ~a
  • ~%") + (stanza . "~%") + (inline . "~a~%")) + (list (line . "
  • ~a
  • ~%") + (stanza . "~%")) + (quot (line . "~a~%") + (stanza . "
    ~% ~a
    ~%")) + (hdr1 (line . "~a") + (stanza . "

    ~a

    ~%")) + (hdr2 (line . "~a") + (stanza . "

    ~a

    ~%")) + (hdr3 (line . "~a") + (stanza . "

    ~a

    ~%")))) + +(filters + `((verb (line . ,identity) + (stanza . ,join-lines)) + (link (line . ,(lambda (ln) + (cons (car ln) + ((o list + add-inline-markup + escape-entities + string-join) + (cdr ln)))))) + (default + (line . ,(o list + add-inline-markup + escape-entities + string-join)) + (stanza . ,string-join)))) diff --git a/lib/main.scm b/lib/main.scm new file mode 100644 index 0000000..b97d66a --- /dev/null +++ b/lib/main.scm @@ -0,0 +1,19 @@ +(declare (module (jimmy main))) + +(import scheme (chicken base) + (chicken port) + (jimmy util) + (jimmy read) + (jimmy emit) + (jimmy wrap)) + +(define-public (jimmy #!optional file template) + (parameterize ((current-input-port + (if file + (open-input-file file) + (current-input-port)))) + (let ((doc (parse)) + (post-proc (if template + (cut wrap-with <> template) + emit-string))) + (post-proc doc)))) diff --git a/lib/read.scm b/lib/read.scm new file mode 100644 index 0000000..1b611bb --- /dev/null +++ b/lib/read.scm @@ -0,0 +1,88 @@ +(declare (module (jimmy read))) + +(import scheme (chicken base) + (jimmy util) + (only (chicken io) read-lines) + (only (chicken string) string-split)) + +(define-public line-types + ;; (sigil type inlines word-select) + `((default para (link) ,identity) ; if nothing else matches + ("```" verb) + ("=>" link) + (">" quot) + ("#" hdr1) + ("##" hdr2) + ("###" hdr3) + ("*" list) + ;; extra! + (":" meta))) + +(define-public (parse #!optional port) + (parse-lines (read-lines (or port (current-input-port))) '())) + +(define (line-type line) + (let ((lin (if (string? line) (string-split line) line)) + (def (cdr (assoc 'default line-types)))) + (cond + ((null? lin) def) ; empty line + ((assoc (car lin) line-types) => cdr) ; a line type exists + (else def)))) ; otherwise ... + +(define (parse-lines lines doc) + (if (null? lines) (reverse doc) + (let ((words (string-split (car lines)))) + (cond + ((null? words) ; empty line + (parse-lines (cdr lines) doc)) + ((equal? (car words) "```") ; verbatim + ;; Format for verbatim header: + ;; ``` ?html | command ... + ;; -- only run command on block with html output. + ;; other outputs process the block normally + ;; ``` ?!html | command ... + ;; -- only run command on block when *not* outputting html. + ;; html processes the block normally + ;; ``` ?:html | command ... + ;; -- like ?html, but ignore the block in non-html outputs. + ;;;; FIXME: I think this necessitates a special emit-verbatim + ;;;; function. + (parse-verbatim (cdr lines) doc '() + #; (if (< 1 (length words)) + (cons 'verb (cdr words)) + 'verb) + 'verb)) + (else ; another line type + (apply parse-stanza lines doc '() (line-type words))))))) + +(define (parse-verbatim lines doc block bhead) + (define (close-verbatim) (cons (cons bhead (reverse block)) doc)) + (cond + ((null? lines) ; end of document + (parse-lines lines (close-verbatim))) + ((equal? (car lines) "```") ; end of verbatim block + (parse-lines (cdr lines) (close-verbatim))) + (else ; verbatim block continues + (parse-verbatim (cdr lines) doc (cons (list (car lines)) block) bhead)))) + +(define (parse-stanza lines doc stanza st-type + #!optional (st-inlines '()) (st-words cdr)) + (define (close-stanza) (cons (cons st-type (reverse stanza)) doc)) + (if (null? lines) ; end of document + (parse-lines lines (close-stanza)) + (let* ((ln (car lines)) + (ws (string-split ln)) + (lt (line-type ln))) + (cond + ((null? ws) ; end of stanza (blank line) + (parse-lines (cdr lines) (close-stanza))) + ((memq (car lt) st-inlines) ; in-line for *this* stanza + (parse-stanza (cdr lines) doc + (cons (cons (car lt) (cdr ws)) stanza) + st-type st-inlines st-words)) + ((not (eq? st-type (car (line-type ws)))) ; beginning of a new stanza + (parse-lines lines (close-stanza))) + (else ; continue this stanza + (parse-stanza (cdr lines) doc + (cons (st-words ws) stanza) + st-type st-inlines st-words)))))) diff --git a/lib/util.scm b/lib/util.scm new file mode 100644 index 0000000..c71c600 --- /dev/null +++ b/lib/util.scm @@ -0,0 +1,49 @@ +(module (jimmy util) * + + (import scheme (chicken base) + (chicken condition) + (only (chicken irregex) irregex-replace/all) + (chicken string)) + + (define-syntax define-public + (syntax-rules () + ((define-public (name . arg) forms ...) + (begin (export name) + (define (name . arg) forms ...))) + ((define-public (name args ...) forms ...) + (begin (export name) + (define (name args ...) forms ...))) + ((define-public name value) + (begin (export name) + (define name value))))) + + (define-syntax ignore-errors + (syntax-rules () + ((ignore-errors x) + (handle-exceptions e #f x)))) + + (define (alist-walk lis . keys) + (if (null? keys) + lis + (let ((kv (assoc (car keys) lis))) + (cond + ((not kv) #f) + ((atom? (cdr kv)) + (and (null? (cdr keys)) ; this shouldn't error... + (cdr kv))) + ((list? (cdr kv)) + (apply alist-walk (cdr kv) (cdr keys))))))) + + (define (string-join ss #!optional (sep " ")) + (string-intersperse ss sep)) + + (define (flush-lines-left lines) + (irregex-replace/all '(: bol (* space)) + (string-join lines) "")) + + (define (join-lines lines) + (apply string-append lines)) + + ) + + diff --git a/lib/wrap.scm b/lib/wrap.scm new file mode 100644 index 0000000..f801029 --- /dev/null +++ b/lib/wrap.scm @@ -0,0 +1,21 @@ +(declare (module (jimmy wrap))) + +(import scheme (chicken base) + (jimmy emit) + (jimmy util) + (only (chicken io) read-string) + (only (chicken port) with-output-to-string) + (only (chicken string) string-translate* string-intersperse)) + +;; templates are strings with variables interpolated with "{{variables}}" + +(define-public (wrap document template) + (let* ((meta (map (lambda (el) + (cons (string-append "{{" (car el) "}}") + (string-intersperse (cdr el) " "))) + (alist-walk document 'meta))) + (body (cons "{{body}}" (emit-string document)))) + (string-translate* template (cons body meta)))) + +(define-public (wrap-with document file) + (wrap document (with-input-from-file file read-string))) -- cgit 1.4.1-21-gabe81