git.m455.casa

wg

clone url: git://git.m455.casa/wg


src/wg.fnl

1 (local ffs (require :ffs))
2
3 (local directories-required [:build :layout :copy :convert])
4
5 (local files-required {:index "convert/index.md"
6 :style "copy/style.css"
7 :header "layout/header.md"
8 :footer "layout/footer.md"})
9
10 (local contents-index
11 (.. "This is some default text found in `convert/index.md`.\n\n"
12 "If you want to change the look of your website, you can\n"
13 "find the CSS styles in `copy/style.css`.\n\n"
14 "To change the header and footer, check out the `layout/` directory."))
15
16 (local contents-style
17 (.. "body {\n"
18 " background-color: pink;\n"
19 "}\n\n"
20 "code {\n"
21 " background-color: black;\n"
22 " color: pink;\n"
23 "}"))
24
25 (local contents-header
26 (.. "---\n"
27 "title: 'The title of your website'\n"
28 "lang: en\n"
29 "header-includes:\n"
30 " <meta name=\"author\" content=\"Your name\"/>\n"
31 " <meta name=\"description\" content=\"Some description\"/>\n"
32 " <meta name=\"keywords\" content=\"programming, documentation\"/>\n"
33 "---"
34 "\n"
35 "<nav>[Home](/) - [Another home link](/index.html)</nav>\n"
36 "<hr/>\n"
37 "<main>\n"))
38
39 (local contents-footer
40 (.. "</main>\n"
41 "<hr/>\n"
42 "<footer>This website was built with [wg](https://git.m455.casa/m455/wg)</footer>\n"))
43
44 (fn print-format [str ...]
45 (print (string.format str ...)))
46
47 (fn print-missing-files []
48 (print "Error: wg has not been initialized or there are missing files.")
49 (print "If wg has been initialized, try running the following command:")
50 (print " wg repair")
51 (print "If wg has not been initialized, try running the following command:")
52 (print " wg init")
53 (print "For more help, type the following command:")
54 (print " wg help"))
55
56 ;; ---------------------------------------
57 ;; Path utils
58 ;; ---------------------------------------
59 (fn required-paths-missing? []
60 (or (> (length (ffs.paths-missing :directories directories-required)) 0)
61 (> (length (ffs.paths-missing :files files-required)) 0)))
62
63 (fn build-directory-has-contents? []
64 (> (length (ffs.directory-contents :build)) 0))
65
66 ;; ---------------------------------------
67 ;; init
68 ;; ---------------------------------------
69 (fn init/start []
70 ;; Create required directories
71 (each [_ dir (ipairs directories-required)]
72 (print-format "Creating '%s/'..." dir)
73 (ffs.directory-create dir))
74 ;; Create and populate required files
75 ;; Create required files
76 (each [_ file (ipairs [files-required.index
77 files-required.style
78 files-required.header
79 files-required.footer])]
80 (print-format "Creating '%s'..." file)
81 (ffs.file-create file))
82 ;; Populate required files
83 (ffs.file-write files-required.index contents-index :w)
84 (ffs.file-write files-required.style contents-style :w)
85 (ffs.file-write files-required.header contents-header :w)
86 (ffs.file-write files-required.footer contents-footer :w)
87 (print "Initialization complete!"))
88
89 (fn init/read-input []
90 (let [input (io.read 1)]
91 (if (= input :y)
92 (init/start)
93 (print "Cancelled the creation of the required directories and files."))))
94
95 (fn init/prompt []
96 (print "The following directories and files will be created:")
97 (each [_ dir (ipairs directories-required)]
98 (print-format " %s/" dir))
99 (each [_ file (pairs files-required)]
100 (print-format " %s" file))
101 (print "This will overwrite any files with the same names as the files above.")
102 (print "Are you sure you want to do this? (y/n)")
103 (io.write "> ")
104 (init/read-input))
105
106 (fn init []
107 (if (required-paths-missing?)
108 (init/prompt)
109 (print "The required directories and files already exist.")))
110
111 ;; ---------------------------------------
112 ;; serve
113 ;; ---------------------------------------
114 (fn serve []
115 (if (required-paths-missing?)
116 (print-missing-files)
117 (if (build-directory-has-contents?)
118 (os.execute "python3 -m http.server 8000 --directory build/")
119 (do (print "Error: 'build/' directory has no contents.")
120 (print "Try running the following command first:")
121 (print " wg build")))))
122
123 ;; ---------------------------------------
124 ;; clean
125 ;; ---------------------------------------
126 (fn clean/start []
127 (print "Deleting contents of 'build/' directory...")
128 (ffs.path-delete (.. :build "/*"))
129 (print "Cleaning complete!"))
130
131 (fn clean/prompt []
132 (print "Cleaning will delete everything in the 'build/' directory.")
133 (print "Do you want to continue? (y/n)")
134 (io.write "> ")
135 (let [input (io.read 1)]
136 (if (= input :y)
137 (clean/start)
138 (print "Cancelled the deletion of the 'build/' directory's contents."))))
139
140 (fn clean []
141 (if (required-paths-missing?)
142 (print-missing-files)
143 (if (build-directory-has-contents?)
144 (clean/prompt)
145 (print "'build/' directory empty. Nothing to clean."))))
146
147 ;; ---------------------------------------
148 ;; build
149 ;; ---------------------------------------
150 ;; Note: source and destination should both be full paths starting from
151 ;; where wg.fnl is ran
152 (fn markdown->html [source-file destination-file]
153 (os.execute
154 (string.format "pandoc -s -c /style.css %s %s %s -o %s --shift-heading-level-by=1"
155 (. files-required :header)
156 source-file
157 (. files-required :footer)
158 destination-file)))
159
160 (fn build/convert [dir]
161 (each [_ path (ipairs (ffs.directory-contents dir))]
162 (let [source-path (.. dir "/" path)]
163 (if (and (ffs.file-exists? source-path)
164 (string.match source-path ".md"))
165 (let [destination-dir (-> source-path
166 (string.gsub "(.*/).*.md" "%1") ;; => convert/some/path/
167 (string.gsub "^convert/" "build/")) ;; => build/some/path/
168 destination-file (-> source-path
169 (string.gsub ".md" ".html") ;; => convert/some/path/file.html
170 (string.gsub "^convert/" "build/"))] ;; => build/some/path/file.html
171 (when (not (ffs.directory-exists? destination-dir))
172 (ffs.directory-create destination-dir))
173 (markdown->html source-path destination-file))
174 (when (ffs.directory-exists? source-path)
175 (build/convert source-path))))))
176
177 (fn build []
178 (if (required-paths-missing?)
179 (print-missing-files)
180 (do ;; Copy paths
181 (if (= (length (ffs.directory-contents :copy)) 0)
182 (print "No directories or files found in the 'copy/' directory. Skipping...")
183 (do (print "Copying files in 'copy/' directory...")
184 (ffs.path-copy (.. :copy "/*") :build)
185 (print "Copying complete!")))
186 ;; Convert paths
187 (if (= (length (ffs.directory-contents :convert)) 0)
188 (print "No directories or files found in the 'convert/' directory. Skipping...")
189 (do (print "Converting files in 'convert/' directory...")
190 (build/convert :convert)
191 (print "Conversion complete!"))))))
192
193 (fn repair/start []
194 (each [_ dir (ipairs directories-required)]
195 (when (not (ffs.directory-exists? dir))
196 (print-format "Creating '%s/'..." dir)
197 (ffs.directory-create dir)))
198 (each [_ file (ipairs [files-required.index
199 files-required.style
200 files-required.header
201 files-required.footer])]
202 (when (not (ffs.file-exists? file))
203 (print-format "Creating '%s'..." file)
204 (ffs.file-create file)
205 (let [make-file (fn [file contents]
206 (ffs.file-write file contents :w))]
207 (match file
208 files-required.index (make-file files-required.index contents-index)
209 files-required.style (make-file files-required.style contents-style)
210 files-required.header (make-file files-required.header contents-header)
211 files-required.footer (make-file files-required.footer contents-footer)))))
212 (print "Repair complete!"))
213
214 (fn repair/read-input []
215 (let [input (io.read 1)]
216 (if (= input :y)
217 (repair/start)
218 (print "Cancelled the repairs."))))
219
220 (fn repair/prompt []
221 (print "The following files or directories are missing:")
222 (each [_ dir (ipairs directories-required)]
223 (when (not (ffs.directory-exists? dir))
224 (print-format " %s/" dir)))
225 (each [_ file (pairs files-required)]
226 (when (not (ffs.file-exists? file))
227 (print-format " %s" file)))
228 (print "Do you want to create these files? (y/n)")
229 (io.write "> ")
230 (repair/read-input))
231
232 (fn repair []
233 (if (required-paths-missing?)
234 (repair/prompt)
235 (print "The required directories and files already exist.")))
236
237 (fn help []
238 (print
239 (.. "wg\n"
240 " A static website generator written in Fennel.\n"
241 "\n"
242 "Author\n"
243 " Jesse Laprade (m455)\n"
244 "\n"
245 "License\n"
246 " AGPL3 (https://www.gnu.org/licenses/agpl-3.0.en.html)\n"
247 "\n"
248 "Source\n"
249 " https://git.m455.casa/m455/wg\n"
250 "\n"
251 "Commands\n"
252 " init\n"
253 " Creates required directories and files in the current directory.\n"
254 "\n"
255 " build\n"
256 " Recursively copies directories and files from the 'copy/'\n"
257 " directory into the 'build/' directory, preserving the directory\n"
258 " structure of the 'copy/' directory.\n"
259 "\n"
260 " Recursively converts Markdown files in the 'convert/' directory\n"
261 " to HTML files in the 'build/' directory, preserving the\n"
262 " directory structure of the 'convert/' directory.\n"
263 "\n"
264 " serve\n"
265 " Serves files in the 'build/' directory on port 8000, allowing\n"
266 " you to see how your website will look locally before it goes\n"
267 " live.\n"
268 "\n"
269 " clean\n"
270 " Deletes all contents of the 'build/' directory.\n"
271 "\n"
272 " repair\n"
273 " Looks for and creates missing files or directories.\n"
274 "\n"
275 " help\n"
276 " Displays this help message.\n"
277 "\n"
278 "Example usage\n"
279 " wg init\n"
280 " wg build\n"
281 " wg serve\n"
282 " wg clean\n"
283 " wg help\n")))
284
285 ;; ---------------------------------------
286 ;; Arg parsing
287 ;; ---------------------------------------
288 (fn main [arg-tbl]
289 (match arg-tbl
290 [:init nil] (init)
291 [:build nil] (build)
292 [:serve nil] (serve)
293 [:clean nil] (clean)
294 [:repair nil] (repair)
295 [:help nil] (help)
296 _ (do (print "For help, type the following command:")
297 (print " wg help"))))
298
299 (main arg)