aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeanne-Kamikaze <jeannekamikaze@gmail.com>2020-05-30 13:20:29 -0700
committerJeanne-Kamikaze <jeannekamikaze@gmail.com>2020-05-30 13:20:29 -0700
commit2608753d9cc08d133c2ba50e5f53104220dd3229 (patch)
treee1549b270f7e56524602880be5d087401ac72bab
parent9a325e40b48e10d61f880e953dfd5504345f18f5 (diff)
Initial commit.
-rw-r--r--.gitignore1
-rw-r--r--project.janet9
-rw-r--r--webgen.janet119
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))