clone url: git://git.m455.casa/wg
dists/wg.lua
1 | #!/usr/bin/env lua |
2 | -- wg - A static website generator in Fennel |
3 | -- Author: Jesse Laprade (m455) |
4 | -- License: AGPL3 (https://www.gnu.org/licenses/agpl-3.0.en.html) |
5 | -- Source: https://git.m455.casa/m455/wg |
6 | local ffs |
7 | package.preload["ffs"] = package.preload["ffs"] or function(...) |
8 | local ffs = {} |
9 | ffs["shell->sequence"] = function(command) |
10 | local file_handle = io.popen(command, "r") |
11 | local function close_handlers_0_(ok_0_, ...) |
12 | file_handle:close() |
13 | if ok_0_ then |
14 | return ... |
15 | else |
16 | return error(..., 0) |
17 | end |
18 | end |
19 | local function _0_() |
20 | local tbl_0_ = {} |
21 | for i in file_handle:lines() do |
22 | tbl_0_[(#tbl_0_ + 1)] = i |
23 | end |
24 | return tbl_0_ |
25 | end |
26 | return close_handlers_0_(xpcall(_0_, (package.loaded.fennel or debug).traceback)) |
27 | end |
28 | ffs["directory-exists?"] = function(directory) |
29 | local command = string.format("test -d %s && echo exists", directory) |
30 | if (ffs["shell->sequence"](command))[1] then |
31 | return true |
32 | else |
33 | return false |
34 | end |
35 | end |
36 | ffs["directory-create"] = function(dir) |
37 | return os.execute(string.format("mkdir -p %s", dir)) |
38 | end |
39 | ffs["directory-contents"] = function(dir) |
40 | return ffs["shell->sequence"](string.format("ls %s", dir)) |
41 | end |
42 | ffs["file-exists?"] = function(file) |
43 | local command = string.format("test -f %s && echo exists", file) |
44 | if (ffs["shell->sequence"](command))[1] then |
45 | return true |
46 | else |
47 | return false |
48 | end |
49 | end |
50 | ffs["file-create"] = function(file) |
51 | return io.close(io.open(file, "w")) |
52 | end |
53 | ffs["file-write"] = function(file, data, mode) |
54 | local file_out = io.open(file, mode) |
55 | local function close_handlers_0_(ok_0_, ...) |
56 | file_out:close() |
57 | if ok_0_ then |
58 | return ... |
59 | else |
60 | return error(..., 0) |
61 | end |
62 | end |
63 | local function _0_() |
64 | return file_out:write(data) |
65 | end |
66 | return close_handlers_0_(xpcall(_0_, (package.loaded.fennel or debug).traceback)) |
67 | end |
68 | ffs["file->lines"] = function(file) |
69 | local tbl_0_ = {} |
70 | for line in io.lines(file) do |
71 | tbl_0_[(#tbl_0_ + 1)] = line |
72 | end |
73 | return tbl_0_ |
74 | end |
75 | ffs["path-exists?"] = function(path) |
76 | return (ffs["file-exists?"](path) or ffs["directory-exists?"](path)) |
77 | end |
78 | ffs["path-copy"] = function(source, dest) |
79 | return os.execute(string.format("cp -r %s %s", source, dest)) |
80 | end |
81 | ffs["path-delete"] = function(path) |
82 | return os.execute(string.format("rm -rf %s", path)) |
83 | end |
84 | ffs["paths-missing"] = function(mode, required_paths) |
85 | local func |
86 | do |
87 | local _0_0 = mode |
88 | if (_0_0 == "files") then |
89 | func = ffs["file-exists?"] |
90 | elseif (_0_0 == "directories") then |
91 | func = ffs["directory-exists?"] |
92 | else |
93 | func = nil |
94 | end |
95 | end |
96 | local tbl_0_ = {} |
97 | for _, path in ipairs(required_paths) do |
98 | local _1_ |
99 | if not func(path) then |
100 | _1_ = path |
101 | else |
102 | _1_ = nil |
103 | end |
104 | tbl_0_[(#tbl_0_ + 1)] = _1_ |
105 | end |
106 | return tbl_0_ |
107 | end |
108 | return ffs |
109 | end |
110 | ffs = require("ffs") |
111 | local directories_required = {"build", "layout", "copy", "convert"} |
112 | local files_required = {footer = "layout/footer.md", header = "layout/header.md", index = "convert/index.md", style = "copy/style.css"} |
113 | local contents_index = ("This is some default text found in `convert/index.md`.\n\n" .. "If you want to change the look of your website, you can\n" .. "find the CSS styles in `copy/style.css`.\n\n" .. "To change the header and footer, check out the `layout/` directory.") |
114 | local contents_style = ("body {\n" .. " background-color: pink;\n" .. "}\n\n" .. "code {\n" .. " background-color: black;\n" .. " color: pink;\n" .. "}") |
115 | local contents_header = ("---\n" .. "title: 'The title of your website'\n" .. "lang: en\n" .. "header-includes:\n" .. " <meta name=\"author\" content=\"Your name\"/>\n" .. " <meta name=\"description\" content=\"Some description\"/>\n" .. " <meta name=\"keywords\" content=\"programming, documentation\"/>\n" .. "---" .. "\n" .. "<nav>[Home](/) - [Another home link](/index.html)</nav>\n" .. "<hr/>\n" .. "<main>\n") |
116 | local contents_footer = ("</main>\n" .. "<hr/>\n" .. "<footer>This website was built with [wg](https://git.m455.casa/m455/wg)</footer>\n") |
117 | local function print_format(str, ...) |
118 | return print(string.format(str, ...)) |
119 | end |
120 | local function print_missing_files() |
121 | print("Error: wg has not been initialized or there are missing files.") |
122 | print("If wg has been initialized, try running the following command:") |
123 | print(" wg repair") |
124 | print("If wg has not been initialized, try running the following command:") |
125 | print(" wg init") |
126 | print("For more help, type the following command:") |
127 | return print(" wg help") |
128 | end |
129 | local function required_paths_missing_3f() |
130 | return ((#ffs["paths-missing"]("directories", directories_required) > 0) or (#ffs["paths-missing"]("files", files_required) > 0)) |
131 | end |
132 | local function build_directory_has_contents_3f() |
133 | return (#ffs["directory-contents"]("build") > 0) |
134 | end |
135 | local function init_2fstart() |
136 | for _, dir in ipairs(directories_required) do |
137 | print_format("Creating '%s/'...", dir) |
138 | ffs["directory-create"](dir) |
139 | end |
140 | for _, file in ipairs({files_required.index, files_required.style, files_required.header, files_required.footer}) do |
141 | print_format("Creating '%s'...", file) |
142 | ffs["file-create"](file) |
143 | end |
144 | ffs["file-write"](files_required.index, contents_index, "w") |
145 | ffs["file-write"](files_required.style, contents_style, "w") |
146 | ffs["file-write"](files_required.header, contents_header, "w") |
147 | ffs["file-write"](files_required.footer, contents_footer, "w") |
148 | return print("Initialization complete!") |
149 | end |
150 | local function init_2fread_input() |
151 | local input = io.read(1) |
152 | if (input == "y") then |
153 | return init_2fstart() |
154 | else |
155 | return print("Cancelled the creation of the required directories and files.") |
156 | end |
157 | end |
158 | local function init_2fprompt() |
159 | print("The following directories and files will be created:") |
160 | for _, dir in ipairs(directories_required) do |
161 | print_format(" %s/", dir) |
162 | end |
163 | for _, file in pairs(files_required) do |
164 | print_format(" %s", file) |
165 | end |
166 | print("This will overwrite any files with the same names as the files above.") |
167 | print("Are you sure you want to do this? (y/n)") |
168 | io.write("> ") |
169 | return init_2fread_input() |
170 | end |
171 | local function init() |
172 | if required_paths_missing_3f() then |
173 | return init_2fprompt() |
174 | else |
175 | return print("The required directories and files already exist.") |
176 | end |
177 | end |
178 | local function serve() |
179 | if required_paths_missing_3f() then |
180 | return print_missing_files() |
181 | else |
182 | if build_directory_has_contents_3f() then |
183 | return os.execute("python3 -m http.server 8000 --directory build/") |
184 | else |
185 | print("Error: 'build/' directory has no contents.") |
186 | print("Try running the following command first:") |
187 | return print(" wg build") |
188 | end |
189 | end |
190 | end |
191 | local function clean_2fstart() |
192 | print("Deleting contents of 'build/' directory...") |
193 | ffs["path-delete"](("build" .. "/*")) |
194 | return print("Cleaning complete!") |
195 | end |
196 | local function clean_2fprompt() |
197 | print("Cleaning will delete everything in the 'build/' directory.") |
198 | print("Do you want to continue? (y/n)") |
199 | io.write("> ") |
200 | local input = io.read(1) |
201 | if (input == "y") then |
202 | return clean_2fstart() |
203 | else |
204 | return print("Cancelled the deletion of the 'build/' directory's contents.") |
205 | end |
206 | end |
207 | local function clean() |
208 | if required_paths_missing_3f() then |
209 | return print_missing_files() |
210 | else |
211 | if build_directory_has_contents_3f() then |
212 | return clean_2fprompt() |
213 | else |
214 | return print("'build/' directory empty. Nothing to clean.") |
215 | end |
216 | end |
217 | end |
218 | local function markdown__3ehtml(source_file, destination_file) |
219 | return os.execute(string.format("pandoc -s -c /style.css %s %s %s -o %s --shift-heading-level-by=1", files_required.header, source_file, files_required.footer, destination_file)) |
220 | end |
221 | local function build_2fconvert(dir) |
222 | for _, path in ipairs(ffs["directory-contents"](dir)) do |
223 | local source_path = (dir .. "/" .. path) |
224 | if (ffs["file-exists?"](source_path) and string.match(source_path, ".md")) then |
225 | local destination_dir = string.gsub(string.gsub(source_path, "(.*/).*.md", "%1"), "^convert/", "build/") |
226 | local destination_file = string.gsub(string.gsub(source_path, ".md", ".html"), "^convert/", "build/") |
227 | if not ffs["directory-exists?"](destination_dir) then |
228 | ffs["directory-create"](destination_dir) |
229 | end |
230 | markdown__3ehtml(source_path, destination_file) |
231 | else |
232 | if ffs["directory-exists?"](source_path) then |
233 | build_2fconvert(source_path) |
234 | end |
235 | end |
236 | end |
237 | return nil |
238 | end |
239 | local function build() |
240 | if required_paths_missing_3f() then |
241 | return print_missing_files() |
242 | else |
243 | if (#ffs["directory-contents"]("copy") == 0) then |
244 | print("No directories or files found in the 'copy/' directory. Skipping...") |
245 | else |
246 | print("Copying files in 'copy/' directory...") |
247 | ffs["path-copy"](("copy" .. "/*"), "build") |
248 | print("Copying complete!") |
249 | end |
250 | if (#ffs["directory-contents"]("convert") == 0) then |
251 | return print("No directories or files found in the 'convert/' directory. Skipping...") |
252 | else |
253 | print("Converting files in 'convert/' directory...") |
254 | build_2fconvert("convert") |
255 | return print("Conversion complete!") |
256 | end |
257 | end |
258 | end |
259 | local function repair_2fstart() |
260 | for _, dir in ipairs(directories_required) do |
261 | if not ffs["directory-exists?"](dir) then |
262 | print_format("Creating '%s/'...", dir) |
263 | ffs["directory-create"](dir) |
264 | end |
265 | end |
266 | for _, file in ipairs({files_required.index, files_required.style, files_required.header, files_required.footer}) do |
267 | if not ffs["file-exists?"](file) then |
268 | print_format("Creating '%s'...", file) |
269 | ffs["file-create"](file) |
270 | local make_file |
271 | local function _0_(file0, contents) |
272 | return ffs["file-write"](file0, contents, "w") |
273 | end |
274 | make_file = _0_ |
275 | local _1_0 = file |
276 | if (_1_0 == files_required.index) then |
277 | make_file(files_required.index, contents_index) |
278 | elseif (_1_0 == files_required.style) then |
279 | make_file(files_required.style, contents_style) |
280 | elseif (_1_0 == files_required.header) then |
281 | make_file(files_required.header, contents_header) |
282 | elseif (_1_0 == files_required.footer) then |
283 | make_file(files_required.footer, contents_footer) |
284 | end |
285 | end |
286 | end |
287 | return print("Repair complete!") |
288 | end |
289 | local function repair_2fread_input() |
290 | local input = io.read(1) |
291 | if (input == "y") then |
292 | return repair_2fstart() |
293 | else |
294 | return print("Cancelled the repairs.") |
295 | end |
296 | end |
297 | local function repair_2fprompt() |
298 | print("The following files or directories are missing:") |
299 | for _, dir in ipairs(directories_required) do |
300 | if not ffs["directory-exists?"](dir) then |
301 | print_format(" %s/", dir) |
302 | end |
303 | end |
304 | for _, file in pairs(files_required) do |
305 | if not ffs["file-exists?"](file) then |
306 | print_format(" %s", file) |
307 | end |
308 | end |
309 | print("Do you want to create these files? (y/n)") |
310 | io.write("> ") |
311 | return repair_2fread_input() |
312 | end |
313 | local function repair() |
314 | if required_paths_missing_3f() then |
315 | return repair_2fprompt() |
316 | else |
317 | return print("The required directories and files already exist.") |
318 | end |
319 | end |
320 | local function help() |
321 | return print(("wg\n" .. " A static website generator written in Fennel.\n" .. "\n" .. "Author\n" .. " Jesse Laprade (m455)\n" .. "\n" .. "License\n" .. " AGPL3 (https://www.gnu.org/licenses/agpl-3.0.en.html)\n" .. "\n" .. "Source\n" .. " https://git.m455.casa/m455/wg\n" .. "\n" .. "Commands\n" .. " init\n" .. " Creates required directories and files in the current directory.\n" .. "\n" .. " build\n" .. " Recursively copies directories and files from the 'copy/'\n" .. " directory into the 'build/' directory, preserving the directory\n" .. " structure of the 'copy/' directory.\n" .. "\n" .. " Recursively converts Markdown files in the 'convert/' directory\n" .. " to HTML files in the 'build/' directory, preserving the\n" .. " directory structure of the 'convert/' directory.\n" .. "\n" .. " serve\n" .. " Serves files in the 'build/' directory on port 8000, allowing\n" .. " you to see how your website will look locally before it goes\n" .. " live.\n" .. "\n" .. " clean\n" .. " Deletes all contents of the 'build/' directory.\n" .. "\n" .. " repair\n" .. " Looks for and creates missing files or directories.\n" .. "\n" .. " help\n" .. " Displays this help message.\n" .. "\n" .. "Example usage\n" .. " wg init\n" .. " wg build\n" .. " wg serve\n" .. " wg clean\n" .. " wg help\n")) |
322 | end |
323 | local function main(arg_tbl) |
324 | local _0_0 = arg_tbl |
325 | if ((type(_0_0) == "table") and ((_0_0)[1] == "init") and ((_0_0)[2] == nil)) then |
326 | return init() |
327 | elseif ((type(_0_0) == "table") and ((_0_0)[1] == "build") and ((_0_0)[2] == nil)) then |
328 | return build() |
329 | elseif ((type(_0_0) == "table") and ((_0_0)[1] == "serve") and ((_0_0)[2] == nil)) then |
330 | return serve() |
331 | elseif ((type(_0_0) == "table") and ((_0_0)[1] == "clean") and ((_0_0)[2] == nil)) then |
332 | return clean() |
333 | elseif ((type(_0_0) == "table") and ((_0_0)[1] == "repair") and ((_0_0)[2] == nil)) then |
334 | return repair() |
335 | elseif ((type(_0_0) == "table") and ((_0_0)[1] == "help") and ((_0_0)[2] == nil)) then |
336 | return help() |
337 | else |
338 | local _ = _0_0 |
339 | print("For more information, type the following command:") |
340 | return print(" wg help") |
341 | end |
342 | end |
343 | return main(arg) |