aboutsummaryrefslogtreecommitdiff
path: root/webgen.janet
blob: 1ba3d5a738b739277a0292608a29edf427145e60 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
(import filesystem)
(import path)

(defn pairs->table
  "Convert an array of pairs into a table."
  [pairs]
  (reduce (fn [acc [key val]] (put acc key val)) @{} pairs))

(defn split-first
  "Split the string on the first occurrence of the delimeter. If the delimeter
  is not found, return the original string."
  [delim str]
  (def idx (string/find delim str))
  (if idx
    @[(string/slice str 0 (- idx 1)) (string/slice str (+ idx 1))]
    str))

(defn nesting-levels
  "Return the number of nesting levels in the given file path."
  [path]
  (length (string/find-all path/sep path)))

(defn markdown-file-to-html
  "Read and convert a markdown file to html. Return an html buffer."
  [path]
  (def quoted-path (string "\"" path "\""))
  (with [stdout (file/popen (string "pandoc -f markdown --mathjax " quoted-path) :r)]
    (file/read stdout :all)))

(defn substitute-variables
  "Perform variable substitution on the given string."
  [variables path str]
  (var str str)
  (def max-recursion-depth 5)
  (loop [i :in (range max-recursion-depth)]
    (loop [[variable body] :pairs variables]
      (def variable-str (string "${" variable "}"))
      (set str (string/replace variable-str body str))))
  (string/replace-all "${ROOT}/" (string/repeat "../" (- (nesting-levels path) 1)) str))

(defn process-html
  "Process the given HTML contents, applying the website's template and
  applying variable substitution."
  [template variables path contents]
  (substitute-variables variables path
    (string/replace "${CONTENTS}" contents
      template)))

(defn process-markdown-file
  "Process a markdown file. Return the resulting HTML."
  [process-html path]
  (process-html path (markdown-file-to-html path)))

(defn process-file
  "Process a file with the given contents processor. The process takes a file
  path and the file's contents."
  [process path]
    (process path (filesystem/read-file path)))

(defn process-files
  "Process the files in the source directory and write the results to the
  destination directory."
  [process source-dir dest-dir]
  (each file (filesystem/list-all-files source-dir)
    (process file)))

(defn make-processor
  "Create a function that processes files based on their extension."
  [src-dir build-dir template variables]
  (fn [src-filepath]
    (print "Processing file " src-filepath)
    (def ext (path/ext src-filepath))
    (def processed-contents
      (match ext
        ".css"       (process-file (partial substitute-variables variables) src-filepath)
        ".html"      (process-file (partial process-html template variables) src-filepath)
        ".md"        (process-markdown-file (partial process-html template variables) src-filepath)
        ".markdown"  (process-markdown-file (partial process-html template variables) src-filepath)
        _ nil)) # nil means we copy the file as is.
    (var dst-filepath (string/replace src-dir build-dir src-filepath))
    (set dst-filepath
      (match ext
        ".md" (string/replace ext ".html" dst-filepath)
        ".markdown" (string/replace ext ".html" dst-filepath)
        _ dst-filepath))
    (if processed-contents
      (filesystem/write-file dst-filepath processed-contents)
      (filesystem/copy-file src-filepath dst-filepath))))

(defn read-map-file
  "Read key-value pairs from a file. The file should be a text file with lines
  of the form |key = value|. Return a table."
  [path]
  (def contents (filesystem/read-file path))
  (def lines (filter (fn [x] (not (empty? x))) (string/split "\n" contents)))
  (def name-body-pairs (map (fn [line]
    (def @[name body] (split-first "=" line))
      @[(string/trim name) (string/trim body)])
    lines))
  (pairs->table name-body-pairs))

(defn usage [argv0]
  (print "Usage: " argv0 " <source dir>")
  (os/exit 0))

(defn main [argv0 &opt src-dir]
  (when (nil? src-dir) (usage argv0))
  (def src-dir (path/normalize src-dir))
  (def config (read-map-file (path/join src-dir "config.txt")))
  (def build-dir (path/normalize (string (get config "BUILD-DIR") path/sep)))
  (print "Generating website: " src-dir " -> " build-dir)
  (def template-file (get config "TEMPLATE-FILE"))
  (def variables-file (get config "VARIABLES-FILE"))
  (def template (filesystem/read-file (path/join src-dir template-file)))
  (def variables (read-map-file (path/join src-dir variables-file)))
  (def processor (make-processor src-dir build-dir template variables))
  # For clean builds, re-create the build directory.
  (filesystem/recreate-directory build-dir)
  (process-files processor src-dir build-dir))