diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | project.janet | 9 | ||||
-rw-r--r-- | webgen.janet | 119 |
3 files changed, 129 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore | |||
@@ -0,0 +1 @@ | |||
build/ | |||
diff --git a/project.janet b/project.janet new file mode 100644 index 0000000..82a5880 --- /dev/null +++ b/project.janet | |||
@@ -0,0 +1,9 @@ | |||
1 | (declare-project | ||
2 | :name "webgen" | ||
3 | :description "Static web generator" | ||
4 | :dependencies ["https://github.com/janet-lang/path.git" | ||
5 | "https://github.com/jeannekamikaze/janet-filesystem.git"]) | ||
6 | |||
7 | (declare-executable | ||
8 | :name "webgen" | ||
9 | :entry "webgen.janet") | ||
diff --git a/webgen.janet b/webgen.janet new file mode 100644 index 0000000..1ba3d5a --- /dev/null +++ b/webgen.janet | |||
@@ -0,0 +1,119 @@ | |||
1 | (import filesystem) | ||
2 | (import path) | ||
3 | |||
4 | (defn pairs->table | ||
5 | "Convert an array of pairs into a table." | ||
6 | [pairs] | ||
7 | (reduce (fn [acc [key val]] (put acc key val)) @{} pairs)) | ||
8 | |||
9 | (defn split-first | ||
10 | "Split the string on the first occurrence of the delimeter. If the delimeter | ||
11 | is not found, return the original string." | ||
12 | [delim str] | ||
13 | (def idx (string/find delim str)) | ||
14 | (if idx | ||
15 | @[(string/slice str 0 (- idx 1)) (string/slice str (+ idx 1))] | ||
16 | str)) | ||
17 | |||
18 | (defn nesting-levels | ||
19 | "Return the number of nesting levels in the given file path." | ||
20 | [path] | ||
21 | (length (string/find-all path/sep path))) | ||
22 | |||
23 | (defn markdown-file-to-html | ||
24 | "Read and convert a markdown file to html. Return an html buffer." | ||
25 | [path] | ||
26 | (def quoted-path (string "\"" path "\"")) | ||
27 | (with [stdout (file/popen (string "pandoc -f markdown --mathjax " quoted-path) :r)] | ||
28 | (file/read stdout :all))) | ||
29 | |||
30 | (defn substitute-variables | ||
31 | "Perform variable substitution on the given string." | ||
32 | [variables path str] | ||
33 | (var str str) | ||
34 | (def max-recursion-depth 5) | ||
35 | (loop [i :in (range max-recursion-depth)] | ||
36 | (loop [[variable body] :pairs variables] | ||
37 | (def variable-str (string "${" variable "}")) | ||
38 | (set str (string/replace variable-str body str)))) | ||
39 | (string/replace-all "${ROOT}/" (string/repeat "../" (- (nesting-levels path) 1)) str)) | ||
40 | |||
41 | (defn process-html | ||
42 | "Process the given HTML contents, applying the website's template and | ||
43 | applying variable substitution." | ||
44 | [template variables path contents] | ||
45 | (substitute-variables variables path | ||
46 | (string/replace "${CONTENTS}" contents | ||
47 | template))) | ||
48 | |||
49 | (defn process-markdown-file | ||
50 | "Process a markdown file. Return the resulting HTML." | ||
51 | [process-html path] | ||
52 | (process-html path (markdown-file-to-html path))) | ||
53 | |||
54 | (defn process-file | ||
55 | "Process a file with the given contents processor. The process takes a file | ||
56 | path and the file's contents." | ||
57 | [process path] | ||
58 | (process path (filesystem/read-file path))) | ||
59 | |||
60 | (defn process-files | ||
61 | "Process the files in the source directory and write the results to the | ||
62 | destination directory." | ||
63 | [process source-dir dest-dir] | ||
64 | (each file (filesystem/list-all-files source-dir) | ||
65 | (process file))) | ||
66 | |||
67 | (defn make-processor | ||
68 | "Create a function that processes files based on their extension." | ||
69 | [src-dir build-dir template variables] | ||
70 | (fn [src-filepath] | ||
71 | (print "Processing file " src-filepath) | ||
72 | (def ext (path/ext src-filepath)) | ||
73 | (def processed-contents | ||
74 | (match ext | ||
75 | ".css" (process-file (partial substitute-variables variables) src-filepath) | ||
76 | ".html" (process-file (partial process-html template variables) src-filepath) | ||
77 | ".md" (process-markdown-file (partial process-html template variables) src-filepath) | ||
78 | ".markdown" (process-markdown-file (partial process-html template variables) src-filepath) | ||
79 | _ nil)) # nil means we copy the file as is. | ||
80 | (var dst-filepath (string/replace src-dir build-dir src-filepath)) | ||
81 | (set dst-filepath | ||
82 | (match ext | ||
83 | ".md" (string/replace ext ".html" dst-filepath) | ||
84 | ".markdown" (string/replace ext ".html" dst-filepath) | ||
85 | _ dst-filepath)) | ||
86 | (if processed-contents | ||
87 | (filesystem/write-file dst-filepath processed-contents) | ||
88 | (filesystem/copy-file src-filepath dst-filepath)))) | ||
89 | |||
90 | (defn read-map-file | ||
91 | "Read key-value pairs from a file. The file should be a text file with lines | ||
92 | of the form |key = value|. Return a table." | ||
93 | [path] | ||
94 | (def contents (filesystem/read-file path)) | ||
95 | (def lines (filter (fn [x] (not (empty? x))) (string/split "\n" contents))) | ||
96 | (def name-body-pairs (map (fn [line] | ||
97 | (def @[name body] (split-first "=" line)) | ||
98 | @[(string/trim name) (string/trim body)]) | ||
99 | lines)) | ||
100 | (pairs->table name-body-pairs)) | ||
101 | |||
102 | (defn usage [argv0] | ||
103 | (print "Usage: " argv0 " <source dir>") | ||
104 | (os/exit 0)) | ||
105 | |||
106 | (defn main [argv0 &opt src-dir] | ||
107 | (when (nil? src-dir) (usage argv0)) | ||
108 | (def src-dir (path/normalize src-dir)) | ||
109 | (def config (read-map-file (path/join src-dir "config.txt"))) | ||
110 | (def build-dir (path/normalize (string (get config "BUILD-DIR") path/sep))) | ||
111 | (print "Generating website: " src-dir " -> " build-dir) | ||
112 | (def template-file (get config "TEMPLATE-FILE")) | ||
113 | (def variables-file (get config "VARIABLES-FILE")) | ||
114 | (def template (filesystem/read-file (path/join src-dir template-file))) | ||
115 | (def variables (read-map-file (path/join src-dir variables-file))) | ||
116 | (def processor (make-processor src-dir build-dir template variables)) | ||
117 | # For clean builds, re-create the build directory. | ||
118 | (filesystem/recreate-directory build-dir) | ||
119 | (process-files processor src-dir build-dir)) | ||