diff options
-rw-r--r-- | Makefile | 40 | ||||
-rw-r--r-- | jimmy.egg | 17 | ||||
-rw-r--r-- | src/emit.scm | 109 | ||||
-rw-r--r-- | src/html.scm | 3 | ||||
-rw-r--r-- | src/read.scm | 74 | ||||
-rw-r--r-- | src/util.scm | 37 | ||||
-rw-r--r-- | src/wrap.scm | 13 | ||||
-rw-r--r-- | tests/run.scm | 61 |
8 files changed, 354 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0b8e141 --- /dev/null +++ b/Makefile | |||
@@ -0,0 +1,40 @@ | |||
1 | NAME = jimmy | ||
2 | |||
3 | CSC = /usr/bin/csc | ||
4 | CSI = /usr/bin/csi | ||
5 | CSC_OPTIONS = \ | ||
6 | -host \ | ||
7 | -D compiling-extension \ | ||
8 | -emit-all-import-libraries \ | ||
9 | -dynamic \ | ||
10 | -regenerate-import-libraries \ | ||
11 | -setup-mode \ | ||
12 | -I $(PWD) \ | ||
13 | -C -I$(PWD) \ | ||
14 | |||
15 | CSC_OPTIONS_EXTRA = \ | ||
16 | -X utf8 \ | ||
17 | -X module-declarations | ||
18 | |||
19 | BUILD = $(PWD)/build | ||
20 | |||
21 | .PHONY: all test | ||
22 | build: $(patsubst src/%.scm,$(BUILD)/%.so,$(wildcard src/*.scm)) | ||
23 | |||
24 | test: build | ||
25 | $(CSI) -s $(PWD)/tests/run.scm $(NAME) | ||
26 | |||
27 | # Program! | ||
28 | |||
29 | # Libraries! | ||
30 | |||
31 | $(BUILD)/%.so: src/%.scm | ||
32 | mkdir -p "$(dir $@)" | ||
33 | $(CSC) $(CSC_OPTIONS) $(CSC_OPTIONS_EXTRA) $< -o $@ | ||
34 | |||
35 | ## Library dependency graph | ||
36 | # here's a convenience macro | ||
37 | lib = $(BUILD)/$(NAME).$(1).so | ||
38 | |||
39 | $(call lib,read): $(call lib,util) | ||
40 | $(call lib,emit): $(call lib,util) | ||
diff --git a/jimmy.egg b/jimmy.egg new file mode 100644 index 0000000..108cf7d --- /dev/null +++ b/jimmy.egg | |||
@@ -0,0 +1,17 @@ | |||
1 | ((author "Case Duckworth") | ||
2 | (synopsis "The ssg king") | ||
3 | (dependencies (chicken "5.3.0") | ||
4 | module-declarations | ||
5 | utf8) | ||
6 | (test-dependencies test) | ||
7 | (component-options | ||
8 | (csc-options -X utf8 -X module-declarations)) | ||
9 | (components | ||
10 | (extension jimmy.util | ||
11 | (source src/util.scm)) | ||
12 | (extension jimmy.read | ||
13 | (source src/read.scm) | ||
14 | (component-dependencies jimmy.util)) | ||
15 | (extension jimmy.emit | ||
16 | (source src/emit.scm) | ||
17 | (component-dependencies jimmy.util)))) | ||
diff --git a/src/emit.scm b/src/emit.scm new file mode 100644 index 0000000..aa36eb5 --- /dev/null +++ b/src/emit.scm | |||
@@ -0,0 +1,109 @@ | |||
1 | (declare (module (jimmy emit))) | ||
2 | |||
3 | (import scheme (chicken base) | ||
4 | (jimmy util) | ||
5 | (chicken format) | ||
6 | (chicken irregex) | ||
7 | (chicken string)) | ||
8 | |||
9 | (define-public (emit document) | ||
10 | (for-each display | ||
11 | (map format-block document))) | ||
12 | |||
13 | (define-public formats | ||
14 | ;;; (type line-format block-format [line-in-block-format]) | ||
15 | ;; these default to gemtext | ||
16 | '((para (line . "~A ") | ||
17 | (block . "~A~%~%")) | ||
18 | (verb (line . "~A~%") | ||
19 | (block . "```~%~A```~%~%")) | ||
20 | (link (line . "=> ~A ~A~%") | ||
21 | (block . "~A~%") | ||
22 | (inline . "~%=> ~A ~A~%")) ;TODO: have 2 args to format | ||
23 | (list (line . "* ~A~%") | ||
24 | (block . "~A~%")) | ||
25 | (quot (line . "~A ") | ||
26 | (block . "> ~A~%~%")) | ||
27 | (hdr1 (line . "# ~A~%") | ||
28 | (block . "~A~%")) | ||
29 | (hdr2 (line . "## ~A~%") | ||
30 | (block . "~A~%")) | ||
31 | (hdr3 (line . "### ~A~%") | ||
32 | (block . "~A~%")) | ||
33 | (default | ||
34 | (line . "~A") | ||
35 | (block . "~A~%~%")))) | ||
36 | |||
37 | (define (string-join ss #!optional sep) | ||
38 | (if (string? ss) ss | ||
39 | (string-intersperse ss (or sep " ")))) | ||
40 | |||
41 | (define (char->tag char beg end) | ||
42 | (lambda (str) | ||
43 | (irregex-replace/all `(: ($ (or bos space)) | ||
44 | ,char ($ (+ (~ ,char))) ,char | ||
45 | ($ (or space eos))) | ||
46 | str | ||
47 | 1 beg 2 end 3))) | ||
48 | |||
49 | (define-public filters | ||
50 | `((para | ||
51 | (line . ,(o (char->tag "*" "<strong>" "</strong>") | ||
52 | (char->tag "_" "<em>" "</em>") | ||
53 | (char->tag "`" "<code>" "</code>") | ||
54 | string-join)) | ||
55 | (block . ,(lambda (ln) | ||
56 | (irregex-replace/all '(: bol (* " ")) ln "")))) | ||
57 | (link | ||
58 | (line . ,(lambda (ln) | ||
59 | (let ((ws (cond ((list? ln) ln) | ||
60 | ((string? ln) (string-split ln))))) | ||
61 | (list (car ws) (string-join (cdr ws))))))) | ||
62 | (default | ||
63 | (line . ,list) | ||
64 | (block . ,identity)))) | ||
65 | |||
66 | (define (get-from from type subtype) | ||
67 | (or (alist-walk from type subtype) | ||
68 | (if (eq? subtype 'inline) | ||
69 | (alist-walk from type 'list) | ||
70 | (lambda _ '(""))))) | ||
71 | |||
72 | (define (get-format type subtype) (get-from formats type subtype)) | ||
73 | (define (get-filter type subtype) (get-from filters type subtype)) | ||
74 | |||
75 | (define (format-line fmt line type) | ||
76 | (cond | ||
77 | ;; if LINE is a string, wrap it in a list | ||
78 | ((string? line) | ||
79 | (set! line (list line))) | ||
80 | ;; if it's a list of strings, join them together and filter them | ||
81 | ((and (list? line) | ||
82 | (string? (car line))) | ||
83 | (set! line ((get-filter type 'line) line))) | ||
84 | ;; if the car of LINE is a symbol, it's an inline thing. | ||
85 | ((and (list? line) | ||
86 | (symbol? (car line))) | ||
87 | (set! line (format-line (get-format (car line) 'inline) | ||
88 | ((get-filter (car line) 'line) (cdr line)) | ||
89 | type))) | ||
90 | (else (error "Malformed line" line))) | ||
91 | (apply sprintf fmt line)) | ||
92 | |||
93 | (define (format-block block) | ||
94 | (if (assq (car block) formats) | ||
95 | (let* ((type (car block)) | ||
96 | (data (cdr block)) | ||
97 | (text (cond | ||
98 | ((string? data) data) | ||
99 | ((list? data) | ||
100 | (apply string-append | ||
101 | (map (lambda (ln) | ||
102 | (format-line (get-format type 'line) | ||
103 | ln | ||
104 | type)) | ||
105 | data))) | ||
106 | (else (error "Malformed block" block))))) | ||
107 | (sprintf (get-format type 'block) | ||
108 | ((get-filter type 'block) text))) | ||
109 | "")) | ||
diff --git a/src/html.scm b/src/html.scm new file mode 100644 index 0000000..371d407 --- /dev/null +++ b/src/html.scm | |||
@@ -0,0 +1,3 @@ | |||
1 | (declare (module (jimmy html))) | ||
2 | |||
3 | |||
diff --git a/src/read.scm b/src/read.scm new file mode 100644 index 0000000..00ffad4 --- /dev/null +++ b/src/read.scm | |||
@@ -0,0 +1,74 @@ | |||
1 | (declare (module (jimmy read))) | ||
2 | |||
3 | (import scheme (chicken base) | ||
4 | (jimmy util) | ||
5 | (only (chicken condition) handle-exceptions) | ||
6 | (only (chicken io) read-lines) | ||
7 | (only (chicken string) string-split)) | ||
8 | |||
9 | (define-public line-types | ||
10 | ;; (sigil type inlines preproc) | ||
11 | '(("=>" link) | ||
12 | (">" quot) | ||
13 | ("#" hdr1) | ||
14 | ("##" hdr2) | ||
15 | ("###" hdr3) | ||
16 | ("*" list) | ||
17 | ("```" verb) | ||
18 | ;; extra! | ||
19 | (":" meta))) | ||
20 | |||
21 | (define-public (parse inport) | ||
22 | (parse-lines (read-lines inport) '())) | ||
23 | |||
24 | (define (line-type line) | ||
25 | (let ((it (assoc (car (string-split line)) line-types))) | ||
26 | (if it (cadr it) | ||
27 | 'para))) | ||
28 | |||
29 | (define (parse-lines lines document) | ||
30 | (if (null? lines) (reverse document) | ||
31 | (let ((words (string-split (car lines)))) | ||
32 | (cond | ||
33 | ((null? words) | ||
34 | (parse-lines (cdr lines) document)) | ||
35 | ((equal? (car words) "```") | ||
36 | (parse-verbatim (cdr lines) document '())) | ||
37 | ((assoc (car words) line-types) | ||
38 | => (lambda (it) | ||
39 | (apply parse-block lines document '() (cdr it)))) | ||
40 | (else | ||
41 | (parse-block lines document '() 'para '(link) identity)))))) | ||
42 | |||
43 | (define (parse-verbatim lines document verb) | ||
44 | (cond | ||
45 | ((null? lines) | ||
46 | (parse-lines lines (cons (cons 'verb (reverse verb)) document))) | ||
47 | ((equal? (car lines) "```") | ||
48 | (parse-lines (cdr lines) (cons (cons 'verb (reverse verb)) document))) | ||
49 | (else | ||
50 | (parse-verbatim (cdr lines) document (cons (car lines) verb))))) | ||
51 | |||
52 | (define (parse-block lines document block type #!optional inlines preproc) | ||
53 | (let ((inlines (or inlines '())) | ||
54 | (preproc (or preproc (lambda (ln) (cdr (string-split ln)))))) | ||
55 | (cond | ||
56 | ((null? lines) | ||
57 | (parse-lines lines (cons (cons type (reverse block)) document))) | ||
58 | ((equal? (car lines) "") | ||
59 | (parse-lines (cdr lines) (cons (cons type (reverse block)) document))) | ||
60 | ((and (not (eq? type (line-type (car lines)))) | ||
61 | (not (memq (line-type (car lines)) inlines))) | ||
62 | (parse-lines lines (cons (cons type (reverse block)) document))) | ||
63 | ((memq (line-type (car lines)) inlines) | ||
64 | (let* ((ln (car lines)) | ||
65 | (ws (string-split ln)) | ||
66 | (lt (cdr (assoc (car ws) line-types)))) | ||
67 | (parse-block (cdr lines) document | ||
68 | (cons (cons (car lt) | ||
69 | ((or (ignore-errors (caddr lt)) cdr) ws)) | ||
70 | block) | ||
71 | type inlines preproc))) | ||
72 | (else | ||
73 | (parse-block (cdr lines) document (cons (preproc (car lines)) block) | ||
74 | type inlines preproc))))) | ||
diff --git a/src/util.scm b/src/util.scm new file mode 100644 index 0000000..7bf89ac --- /dev/null +++ b/src/util.scm | |||
@@ -0,0 +1,37 @@ | |||
1 | (module (jimmy util) * | ||
2 | |||
3 | (import scheme (chicken base) | ||
4 | (chicken condition)) | ||
5 | |||
6 | (define-syntax define-public | ||
7 | (syntax-rules () | ||
8 | ((define-public (name . arg) forms ...) | ||
9 | (begin (export name) | ||
10 | (define (name . arg) forms ...))) | ||
11 | ((define-public (name args ...) forms ...) | ||
12 | (begin (export name) | ||
13 | (define (name args ...) forms ...))) | ||
14 | ((define-public name value) | ||
15 | (begin (export name) | ||
16 | (define name value))))) | ||
17 | |||
18 | (define-syntax ignore-errors | ||
19 | (syntax-rules () | ||
20 | ((ignore-errors x) | ||
21 | (handle-exceptions e #f x)))) | ||
22 | |||
23 | (define (alist-walk lis . keys) | ||
24 | (if (null? keys) | ||
25 | lis | ||
26 | (let ((kv (assoc (car keys) lis))) | ||
27 | (cond | ||
28 | ((not kv) #f) | ||
29 | ((atom? (cdr kv)) | ||
30 | (and (null? (cdr keys)) ; this shouldn't error... | ||
31 | (cdr kv))) | ||
32 | ((list? (cdr kv)) | ||
33 | (apply alist-walk (cdr kv) (cdr keys))))))) | ||
34 | |||
35 | ) | ||
36 | |||
37 | |||
diff --git a/src/wrap.scm b/src/wrap.scm new file mode 100644 index 0000000..3537dea --- /dev/null +++ b/src/wrap.scm | |||
@@ -0,0 +1,13 @@ | |||
1 | (declare (module (jimmy wrap))) | ||
2 | |||
3 | (import scheme (chicken base) | ||
4 | (jimmy util) | ||
5 | (chicken format)) | ||
6 | |||
7 | ;;; open question: how to do templating? | ||
8 | |||
9 | (define-public (wrap document template) | ||
10 | #f) | ||
11 | |||
12 | (define (meta-get key document) | ||
13 | (alist-walk document 'meta key)) | ||
diff --git a/tests/run.scm b/tests/run.scm new file mode 100644 index 0000000..aa5dab1 --- /dev/null +++ b/tests/run.scm | |||
@@ -0,0 +1,61 @@ | |||
1 | (import scheme | ||
2 | (chicken base) | ||
3 | (chicken load) | ||
4 | (chicken port) | ||
5 | (chicken process-context) | ||
6 | test) | ||
7 | |||
8 | ;;; Setup | ||
9 | |||
10 | (import (jimmy emit) | ||
11 | (jimmy read) | ||
12 | #;(jimmy wrap)) | ||
13 | |||
14 | (define test-doc #<<end-document | ||
15 | : title a test document | ||
16 | : date 2024-05-13T03:02:45Z | ||
17 | : uuid b3daebf1-440b-4828-a4d9-9089c7bd7c61 | ||
18 | |||
19 | # a test document of some kind | ||
20 | |||
21 | here is a test document. | ||
22 | it has paragraphs | ||
23 | => example.com with links! | ||
24 | and other things. | ||
25 | |||
26 | ## a code example | ||
27 | ``` | ||
28 | for (a=1;a<=4;a++) { | ||
29 | printf("%d\n", a); | ||
30 | } | ||
31 | ``` | ||
32 | |||
33 | ### other examples | ||
34 | |||
35 | > a blockquote is a quote | ||
36 | > that is blocky. | ||
37 | |||
38 | * list 1 | ||
39 | * list 2 | ||
40 | * list 3 | ||
41 | => example.com link list 1 | ||
42 | => example.com link list 2 | ||
43 | => example.com link list 3 | ||
44 | |||
45 | ok, now for another test: | ||
46 | will *strong* in-line text be converted? | ||
47 | as well as `code`, _emph_ and such? | ||
48 | what if *i _nest_ them* | ||
49 | what if *i _nest them* wrong_ ? | ||
50 | what about *breaking them | ||
51 | over two lines?* | ||
52 | end-document | ||
53 | ) | ||
54 | |||
55 | ;;; Tests | ||
56 | |||
57 | (test "read" | ||
58 | '((meta ("title" "a" "test" "document") ("date" "2024-05-13T03:02:45Z") ("uuid" "b3daebf1-440b-4828-a4d9-9089c7bd7c61")) (hdr1 ("a" "test" "document" "of" "some" "kind")) (para "here is a test document." "it has paragraphs" (link "example.com" "with" "links!") "and other things.") (hdr2 ("a" "code" "example")) (verb "for (a=1;a<=4;a++) {" "\tprintf(\"%d\\n\", a);" "}") (hdr3 ("other" "examples")) (quot ("a" "blockquote" "is" "a" "quote") ("that" "is" "blocky.")) (list ("list" "1") ("list" "2") ("list" "3")) (link ("example.com" "link" "list" "1") ("example.com" "link" "list" "2") ("example.com" "link" "list" "3")) (para "ok, now for another test:" "will *strong* in-line text be converted?" "as well as `code`, _emph_ and such?" "what if *i _nest_ them*" "what if *i _nest them* wrong_ ?" "what about *breaking them" "over two lines?*")) | ||
59 | (call-with-input-string test-doc parse)) | ||
60 | |||
61 | (test-exit) | ||