diff options
author | Case Duckworth | 2024-06-05 09:21:25 -0500 |
---|---|---|
committer | Case Duckworth | 2024-06-05 09:21:25 -0500 |
commit | 423ac382f9e73bf1ca7fc6b400f98db087cd7d22 (patch) | |
tree | 1992e3dc7e71cd40eb7cdbc0b6d0c3cdf82c4332 /lib | |
parent | Update README, add COPYING (diff) | |
download | jimmy-423ac382f9e73bf1ca7fc6b400f98db087cd7d22.tar.gz jimmy-423ac382f9e73bf1ca7fc6b400f98db087cd7d22.zip |
Write executable
This involved moving `src' to `lib' and making `bin'. `bin' holds the program, which only imports `jimmy.main' from lib.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/emit.scm | 94 | ||||
-rw-r--r-- | lib/html.scm | 61 | ||||
-rw-r--r-- | lib/main.scm | 19 | ||||
-rw-r--r-- | lib/read.scm | 88 | ||||
-rw-r--r-- | lib/util.scm | 49 | ||||
-rw-r--r-- | lib/wrap.scm | 21 |
6 files changed, 332 insertions, 0 deletions
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 @@ | |||
1 | (declare (module (jimmy emit))) | ||
2 | |||
3 | (import scheme (chicken base) | ||
4 | (chicken format) | ||
5 | (chicken irregex) | ||
6 | (chicken port) | ||
7 | (chicken string) | ||
8 | (only utf8-srfi-13 string-join) | ||
9 | (jimmy util)) | ||
10 | |||
11 | (define-public (emit doc) | ||
12 | (for-each display (map format-stanza doc))) | ||
13 | |||
14 | (define-public (emit-string doc) | ||
15 | (with-output-to-string | ||
16 | (lambda () (emit doc)))) | ||
17 | |||
18 | (define-public formats | ||
19 | (make-parameter | ||
20 | ;; (TYPE (line . LINE-FMT) (stanza . STANZA-FMT) (inline . INLINE-FMT)) | ||
21 | '((para (line . "~A") | ||
22 | (stanza . "~A~%~%")) | ||
23 | (verb (line . "~A~%") | ||
24 | (stanza . "```~%~A```~%~%")) | ||
25 | (link (line . "=> ~A ~A~%") ; Note: link has 2 format arguments | ||
26 | (stanza . "~A~%") | ||
27 | (inline . "~%=> ~A ~A~%")) | ||
28 | (list (line . "* ~A~%") | ||
29 | (stanza . "~A~%")) | ||
30 | (quot (line . "~A") | ||
31 | (stanza . "> ~A~%~%")) | ||
32 | (hdr1 (line . "# ~A~%") | ||
33 | (stanza . "~A~%")) | ||
34 | (hdr2 (line . "## ~A~%") | ||
35 | (stanza . "~A~%")) | ||
36 | (hdr3 (line . "### ~A~%") | ||
37 | (stanza . "~A~%"))))) | ||
38 | |||
39 | (define-public filters | ||
40 | (make-parameter | ||
41 | ;; (TYPE (line . LINE-FILTER) (stanza . STANZA-FILTER)) | ||
42 | ;; line-filter : (lambda (list-of-strs) ...) -> list-of-strs (for format) | ||
43 | ;; stanza-filter : (lambda (list-of-strs) ...) -> str | ||
44 | `((verb (line . ,identity) | ||
45 | (stanza . ,join-lines)) | ||
46 | (default | ||
47 | (line . ,identity) | ||
48 | (stanza . ,flush-lines-left))))) | ||
49 | |||
50 | (define (format-line line el) | ||
51 | (cond | ||
52 | ((string? (car line)) ; regular stanza line | ||
53 | (sprintf* (get-format el 'line) | ||
54 | ((get-filter el 'line) line))) | ||
55 | ((symbol? (car line)) ; inline element | ||
56 | (sprintf* (get-format (car line) 'inline) | ||
57 | ((get-filter (car line) 'line) (cdr line)))) | ||
58 | (else (error "Malformed line" line)))) | ||
59 | |||
60 | (define (format-stanza stanza) | ||
61 | (let* ((type (car stanza)) | ||
62 | (data (cdr stanza)) | ||
63 | (text (map (lambda (ln) | ||
64 | (format-line ln type)) | ||
65 | data))) | ||
66 | (sprintf (get-format type 'stanza) | ||
67 | ((get-filter type 'stanza) text)))) | ||
68 | |||
69 | ;;; Utilities | ||
70 | |||
71 | (define (get-from alist el scope) | ||
72 | (or (alist-walk alist el scope) | ||
73 | (alist-walk alist 'default scope) | ||
74 | (and (eq? scope 'inline) | ||
75 | (alist-walk alist 'default 'line)))) | ||
76 | |||
77 | (define (get-format el scope) | ||
78 | (or (get-from (formats) el scope) | ||
79 | "")) | ||
80 | (define (get-filter el scope) (get-from (filters) el scope)) | ||
81 | |||
82 | (define (sprintf* fmt lis) | ||
83 | (let loop ((num (length (irregex-extract "~[aA]" fmt))) | ||
84 | (lis lis) | ||
85 | (out '())) | ||
86 | (cond | ||
87 | ((null? lis) | ||
88 | (apply sprintf fmt (reverse out))) | ||
89 | ((= 1 num) | ||
90 | (loop 0 '() (cons (string-join lis) out))) | ||
91 | (else | ||
92 | (loop (- num 1) | ||
93 | (cdr lis) | ||
94 | (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 @@ | |||
1 | (declare (module (jimmy html))) | ||
2 | |||
3 | (import scheme (chicken base) | ||
4 | (chicken irregex) | ||
5 | (jimmy emit) | ||
6 | (jimmy util)) | ||
7 | |||
8 | (define (escape-entities s) | ||
9 | (irregex-replace/all "[&<>]" s | ||
10 | (lambda (m) | ||
11 | (let ((c (irregex-match-substring m))) | ||
12 | (cond | ||
13 | ((equal? c "&") "&") | ||
14 | ((equal? c "<") "<") | ||
15 | ((equal? c ">") ">")))))) | ||
16 | |||
17 | (define (add-inline-markup s) | ||
18 | (define (char->tag ch tag) | ||
19 | (lambda (s) | ||
20 | (irregex-replace/all `(: ,ch ($ (* (~ ,ch))) ,ch) s | ||
21 | "<" tag ">" 1 "</" tag ">"))) | ||
22 | |||
23 | ((o (char->tag "*" "b") | ||
24 | (char->tag "_" "i") | ||
25 | (char->tag "`" "code")) s)) | ||
26 | |||
27 | (formats | ||
28 | '((para (line . "~a~%") | ||
29 | (stanza . "<p>~% ~a</p>~%")) | ||
30 | (verb (line . "~a~%") | ||
31 | (stanza . "<pre><code>~a</code></pre>~%")) | ||
32 | (link (line . "<li><a href=\"~a\">~a</a></li>~%") | ||
33 | (stanza . "<ul>~% ~a</ul>~%") | ||
34 | (inline . "<a href=\"~a\">~a</a>~%")) | ||
35 | (list (line . "<li>~a</li>~%") | ||
36 | (stanza . "<ul>~% ~a</ul>~%")) | ||
37 | (quot (line . "~a~%") | ||
38 | (stanza . "<blockquote>~% ~a</blockquote>~%")) | ||
39 | (hdr1 (line . "~a") | ||
40 | (stanza . "<h1>~a</h1>~%")) | ||
41 | (hdr2 (line . "~a") | ||
42 | (stanza . "<h2>~a</h2>~%")) | ||
43 | (hdr3 (line . "~a") | ||
44 | (stanza . "<h3>~a</h3>~%")))) | ||
45 | |||
46 | (filters | ||
47 | `((verb (line . ,identity) | ||
48 | (stanza . ,join-lines)) | ||
49 | (link (line . ,(lambda (ln) | ||
50 | (cons (car ln) | ||
51 | ((o list | ||
52 | add-inline-markup | ||
53 | escape-entities | ||
54 | string-join) | ||
55 | (cdr ln)))))) | ||
56 | (default | ||
57 | (line . ,(o list | ||
58 | add-inline-markup | ||
59 | escape-entities | ||
60 | string-join)) | ||
61 | (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 @@ | |||
1 | (declare (module (jimmy main))) | ||
2 | |||
3 | (import scheme (chicken base) | ||
4 | (chicken port) | ||
5 | (jimmy util) | ||
6 | (jimmy read) | ||
7 | (jimmy emit) | ||
8 | (jimmy wrap)) | ||
9 | |||
10 | (define-public (jimmy #!optional file template) | ||
11 | (parameterize ((current-input-port | ||
12 | (if file | ||
13 | (open-input-file file) | ||
14 | (current-input-port)))) | ||
15 | (let ((doc (parse)) | ||
16 | (post-proc (if template | ||
17 | (cut wrap-with <> template) | ||
18 | emit-string))) | ||
19 | (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 @@ | |||
1 | (declare (module (jimmy read))) | ||
2 | |||
3 | (import scheme (chicken base) | ||
4 | (jimmy util) | ||
5 | (only (chicken io) read-lines) | ||
6 | (only (chicken string) string-split)) | ||
7 | |||
8 | (define-public line-types | ||
9 | ;; (sigil type inlines word-select) | ||
10 | `((default para (link) ,identity) ; if nothing else matches | ||
11 | ("```" verb) | ||
12 | ("=>" link) | ||
13 | (">" quot) | ||
14 | ("#" hdr1) | ||
15 | ("##" hdr2) | ||
16 | ("###" hdr3) | ||
17 | ("*" list) | ||
18 | ;; extra! | ||
19 | (":" meta))) | ||
20 | |||
21 | (define-public (parse #!optional port) | ||
22 | (parse-lines (read-lines (or port (current-input-port))) '())) | ||
23 | |||
24 | (define (line-type line) | ||
25 | (let ((lin (if (string? line) (string-split line) line)) | ||
26 | (def (cdr (assoc 'default line-types)))) | ||
27 | (cond | ||
28 | ((null? lin) def) ; empty line | ||
29 | ((assoc (car lin) line-types) => cdr) ; a line type exists | ||
30 | (else def)))) ; otherwise ... | ||
31 | |||
32 | (define (parse-lines lines doc) | ||
33 | (if (null? lines) (reverse doc) | ||
34 | (let ((words (string-split (car lines)))) | ||
35 | (cond | ||
36 | ((null? words) ; empty line | ||
37 | (parse-lines (cdr lines) doc)) | ||
38 | ((equal? (car words) "```") ; verbatim | ||
39 | ;; Format for verbatim header: | ||
40 | ;; ``` ?html | command ... | ||
41 | ;; -- only run command on block with html output. | ||
42 | ;; other outputs process the block normally | ||
43 | ;; ``` ?!html | command ... | ||
44 | ;; -- only run command on block when *not* outputting html. | ||
45 | ;; html processes the block normally | ||
46 | ;; ``` ?:html | command ... | ||
47 | ;; -- like ?html, but ignore the block in non-html outputs. | ||
48 | ;;;; FIXME: I think this necessitates a special emit-verbatim | ||
49 | ;;;; function. | ||
50 | (parse-verbatim (cdr lines) doc '() | ||
51 | #; (if (< 1 (length words)) | ||
52 | (cons 'verb (cdr words)) | ||
53 | 'verb) | ||
54 | 'verb)) | ||
55 | (else ; another line type | ||
56 | (apply parse-stanza lines doc '() (line-type words))))))) | ||
57 | |||
58 | (define (parse-verbatim lines doc block bhead) | ||
59 | (define (close-verbatim) (cons (cons bhead (reverse block)) doc)) | ||
60 | (cond | ||
61 | ((null? lines) ; end of document | ||
62 | (parse-lines lines (close-verbatim))) | ||
63 | ((equal? (car lines) "```") ; end of verbatim block | ||
64 | (parse-lines (cdr lines) (close-verbatim))) | ||
65 | (else ; verbatim block continues | ||
66 | (parse-verbatim (cdr lines) doc (cons (list (car lines)) block) bhead)))) | ||
67 | |||
68 | (define (parse-stanza lines doc stanza st-type | ||
69 | #!optional (st-inlines '()) (st-words cdr)) | ||
70 | (define (close-stanza) (cons (cons st-type (reverse stanza)) doc)) | ||
71 | (if (null? lines) ; end of document | ||
72 | (parse-lines lines (close-stanza)) | ||
73 | (let* ((ln (car lines)) | ||
74 | (ws (string-split ln)) | ||
75 | (lt (line-type ln))) | ||
76 | (cond | ||
77 | ((null? ws) ; end of stanza (blank line) | ||
78 | (parse-lines (cdr lines) (close-stanza))) | ||
79 | ((memq (car lt) st-inlines) ; in-line for *this* stanza | ||
80 | (parse-stanza (cdr lines) doc | ||
81 | (cons (cons (car lt) (cdr ws)) stanza) | ||
82 | st-type st-inlines st-words)) | ||
83 | ((not (eq? st-type (car (line-type ws)))) ; beginning of a new stanza | ||
84 | (parse-lines lines (close-stanza))) | ||
85 | (else ; continue this stanza | ||
86 | (parse-stanza (cdr lines) doc | ||
87 | (cons (st-words ws) stanza) | ||
88 | 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 @@ | |||
1 | (module (jimmy util) * | ||
2 | |||
3 | (import scheme (chicken base) | ||
4 | (chicken condition) | ||
5 | (only (chicken irregex) irregex-replace/all) | ||
6 | (chicken string)) | ||
7 | |||
8 | (define-syntax define-public | ||
9 | (syntax-rules () | ||
10 | ((define-public (name . arg) forms ...) | ||
11 | (begin (export name) | ||
12 | (define (name . arg) forms ...))) | ||
13 | ((define-public (name args ...) forms ...) | ||
14 | (begin (export name) | ||
15 | (define (name args ...) forms ...))) | ||
16 | ((define-public name value) | ||
17 | (begin (export name) | ||
18 | (define name value))))) | ||
19 | |||
20 | (define-syntax ignore-errors | ||
21 | (syntax-rules () | ||
22 | ((ignore-errors x) | ||
23 | (handle-exceptions e #f x)))) | ||
24 | |||
25 | (define (alist-walk lis . keys) | ||
26 | (if (null? keys) | ||
27 | lis | ||
28 | (let ((kv (assoc (car keys) lis))) | ||
29 | (cond | ||
30 | ((not kv) #f) | ||
31 | ((atom? (cdr kv)) | ||
32 | (and (null? (cdr keys)) ; this shouldn't error... | ||
33 | (cdr kv))) | ||
34 | ((list? (cdr kv)) | ||
35 | (apply alist-walk (cdr kv) (cdr keys))))))) | ||
36 | |||
37 | (define (string-join ss #!optional (sep " ")) | ||
38 | (string-intersperse ss sep)) | ||
39 | |||
40 | (define (flush-lines-left lines) | ||
41 | (irregex-replace/all '(: bol (* space)) | ||
42 | (string-join lines) "")) | ||
43 | |||
44 | (define (join-lines lines) | ||
45 | (apply string-append lines)) | ||
46 | |||
47 | ) | ||
48 | |||
49 | |||
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 @@ | |||
1 | (declare (module (jimmy wrap))) | ||
2 | |||
3 | (import scheme (chicken base) | ||
4 | (jimmy emit) | ||
5 | (jimmy util) | ||
6 | (only (chicken io) read-string) | ||
7 | (only (chicken port) with-output-to-string) | ||
8 | (only (chicken string) string-translate* string-intersperse)) | ||
9 | |||
10 | ;; templates are strings with variables interpolated with "{{variables}}" | ||
11 | |||
12 | (define-public (wrap document template) | ||
13 | (let* ((meta (map (lambda (el) | ||
14 | (cons (string-append "{{" (car el) "}}") | ||
15 | (string-intersperse (cdr el) " "))) | ||
16 | (alist-walk document 'meta))) | ||
17 | (body (cons "{{body}}" (emit-string document)))) | ||
18 | (string-translate* template (cons body meta)))) | ||
19 | |||
20 | (define-public (wrap-with document file) | ||
21 | (wrap document (with-input-from-file file read-string))) | ||