1#!/bin/sh
2trampoline=; define()(:) # -*- makefile -*-
3###########################################
4## C H U M : Code Htmlizer Using Make(1) ##
5## Make a website out of a code repo, ez ##
6## Quickstart: cd project; vi .chum ---> ##
7## [change variables given below]. see ##
8## README for details #####################
9###########################################
10define trampoline
11if command -v gmake >/dev/null 2>&1
12then exec gmake -kr -f "$0" "$@"
13else exec make -kr -f "$0" "$@"
14fi
15endef
16####################### code starts here ##
17# Project metadata
18NAME = $(notdir $(PWD))
19AUTHOR =
20DESCRIPTION =
21
22# Build config
23OUTDIR = out
24SERVER = @printf 'Generated files are in %s/\n'
25VDUMPF = # used by chums
26
27# Project files
28SOURCES =
29README =
30READMEFILTER = cat
31LICENSE =
32TARBALL = $(OUTDIR)/$(NAME).tar.gz
33STATICS =
34
35# Project web stuff
36REMOTE =
37URL_ROOT = /
38URL_CLONE =
39URL_DOWNLOAD = $(URL_ROOT)$(notdir $(TARBALL))
40
41define TEMPLATE =
42<!DOCTYPE html>
43<html><head>
44<meta charset="utf-8">
45<title>{{FILENAME}}</title>
46<style>{{STYLE}}</style></head>
47<body>
48<h1><!--NORMAL-->{{DIRECTORY}}/<!--/NORMAL-->{{FILENAME}}</h1>
49<!--INDEX-->{{DESCRIPTION}}<!--/INDEX-->
50<!--NORMAL-->
51<nav><ul>
52<li><a href="{{ROOT}}">index</a></li>
53<li><a href="{{RAWFILE}}">source</a></li>
54</ul></nav>
55<!--/NORMAL-->
56<main>{{CONTENT}}</main>
57<footer><p>(C) {{AUTHOR}}.
58<a href="{{CLONE}}">clone</a>
59<a href="{{DOWNLOAD}}">download</a>
60</p></footer>
61</body>
62</html>
63endef
64
65define STYLE =
66.ll{display:inline-block;width:3em;
67padding-right:0.5em;margin-right:0.5em;}
68endef
69
70# Internal stuff
71
72-include .chum
73
74_FILES = $(README) $(LICENSE) $(SOURCES)
75
76define _CHUM
77BEGIN {
78 if (template) TEMPLATE = template
79 else TEMPLATE = "<h1>{{FILENAME}}</h1>{{CONTENT}}"
80 TMPLV["STYLE"] = style
81 TMPLV["CLONE"] = escape("$(URL_CLONE)")
82 TMPLV["DOWNLOAD"] = escape("$(URL_DOWNLOAD)")
83 TMPLV["DESCRIPTION"] = "$(DESCRIPTION)"
84 TMPLV["AUTHOR"] = "$(AUTHOR)"
85 TMPLV["ROOT"] = escape("$(URL_ROOT)")
86 "pwd" | getline TMPLV["DIRECTORY"]; close("pwd")
87 TMPLV["DIRECTORY"] = outfn(TMPLV["DIRECTORY"], 3)
88 for (f in ARGV)
89 if (f && system("test -f " ARGV[f]))
90 delete ARGV[f]
91}
92
93FNR == 1 {
94 if (NR > 1) finish()
95 OUTFILE = outfn(FILENAME)
96 sub(/^\.\.?\//, "", FILENAME)
97 TMPLV["FILENAME"] = FILENAME
98 TMPLV["OUTFILE"] = OUTFILE
99 FILES[f++] = FILENAME
100 printf("%-24s -> %s\n", FILENAME, OUTFILE)
101 if (system("mkdir -p $(OUTDIR)/" outfn(FILENAME, 2)))
102 die(1, "Can't make directory: " outfn(FILENAME, 2))
103 if (!system("cp " FILENAME " " outfn(FILENAME, 4) ".txt"))
104 TMPLV["RAWFILE"] = outfn(FILENAME, 3) ".txt"
105 OUTSTR = ""
106}
107
108END {
109 if (dead) exit dead
110 finish()
111 doindex()
112 copy_statics()
113}
114
115{
116 gsub(/&/, "\&"); gsub(/</, "\<"); gsub(/>/, "\>")
117 OUTSTR = OUTSTR sprintf("<span class=\"ln\" id=\"l%d\">" \
118 "<a class=\"ll\" href=\"#l%d\">%d</a>" \
119 "%s</span>\n",
120 FNR, FNR, FNR, $$0)
121}
122
123function slurp (file, o) {
124 if (!file) return 0
125 while ((getline < file) > 0)
126 o = o (o?"\n":"") $$0
127 return o
128}
129
130function outfn (file, mod) {
131 if (!mod) { # foo.txt => $(OUTDIR)/foo.txt.html
132 sub(/^/, "$(OUTDIR)/", file)
133 sub(/$$/, ".html", file)
134 } else if (mod == 1) { # foo.txt => foo.txt.html
135 sub(/$$/, ".html", file)
136 } else if (mod == 2) { # foo.txt => / ; foo/bar.txt => foo/
137 if (!sub(/\/[^\/]*$$/, "/", file))
138 file = "/"
139 } else if (mod == 3) { # foo/bar.txt => bar.txt (basename)
140 sub(/.*\//, "", file)
141 } else if (mod == 4) { # foo/bar.txt => $(OUTDIR)/foo/bar.txt
142 sub(/^/, "$(OUTDIR)/", file)
143 }
144 return file
145}
146
147function template_replace (str) {
148 if (OUTFILE == "$(OUTDIR)/index.html") {
149 gsub(/<!--\/NORMAL/, "", str)
150 gsub(/NORMAL-->/, "", str)
151 } else {
152 gsub(/<!--\/INDEX/, "", str)
153 gsub(/INDEX-->/, "", str)
154 }
155 for (ts in TMPLV) {
156 gsub("{{"ts"}}", TMPLV[ts], str)
157 }
158 gsub(/&/, "\\&", OUTSTR)
159 sub("{{CONTENT}}", OUTSTR, str)
160 return str
161}
162
163function finish () {
164 sub("\n$$", "", OUTSTR)
165 if (OUTFILE == "$(OUTDIR)/index.html") {
166 TMPLV["FILENAME"] = TMPLV["DIRECTORY"]
167 OUTSTR = "<section id=\"files\"><ul>" \
168 OUTSTR \
169 "</ul></section>"
170 if ("$(README)") {
171 OUTSTR = OUTSTR "<section id=\"readme\">"
172 while ((("$(READMEFILTER) $(README)") | getline) > 0)
173 OUTSTR = OUTSTR "\n" $$0
174 OUTSTR = OUTSTR "</section>"
175 }
176 } else {
177 OUTSTR = "<pre><code>" OUTSTR "</pre></code>"
178 }
179 print(template_replace(TEMPLATE)) > OUTFILE
180 close(OUTFILE)
181}
182
183function doindex () {
184 OUTFILE = "$(OUTDIR)/index.html"
185 OUTSTR = ""
186 printf("[build] %s\n", OUTFILE)
187 for (f in FILES) {
188 OUTSTR = OUTSTR \
189 sprintf("<li><a href=\"%s\">%s</a></li>\n",
190 escape(outfn(FILES[f], 1)), FILES[f])
191 }
192 finish()
193}
194
195function die(code, message) {
196 print(message) > "/dev/stderr"
197 dead = code
198 exit code
199}
200
201function escape(str, c, len, res) {
202 ord[" "] = 32; ord["!"] = 33; ord["\""] = 34; ord["#"] = 35; ord["$$"] = 36
203 ord["%"] = 37; ord["&"] = 38; ord["'"] = 39; ord["("] = 40; ord[")"] = 41
204 ord["*"] = 42; ord["+"] = 43; ord[","] = 44; ord["-"] = 45; ord["."] = 46
205 ord[":"] = 58; ord[";"] = 59; ord["<"] = 60; ord["="] = 61; ord[">"] = 62
206 ord["?"] = 63; ord["@"] = 64; ord["["] = 91; ord["\\"] = 92; ord["]"] = 93
207 ord["^"] = 94; ord["_"] = 95; ord["`"] = 96; ord["{"] = 123; ord["|"] = 124
208 ord["}"] = 125; ord["~"] = 126
209 len = length(str)
210 res = ""
211 for (i = 1; i <= len; i++) {
212 c = substr(str, i, 1);
213 if (c ~ /[0-9A-Za-z\/]/)
214 res = res c
215 else
216 res = res "%" sprintf("%02X", ord[c])
217 }
218 return res
219}
220
221function copy_statics () {
222 split(static, STATICS, / */)
223 for (s in STATICS) {
224 printf("Copying %s\n", STATICS[s])
225 if (system("cp " STATICS[s] " $(OUTDIR)/" outfn(STATICS[s], 3)))
226 die(2, "Can't copy static file: " STATICS[s])
227 }
228}
229endef
230
231# Internal recipes
232
233$(OUTDIR): export _TMPL:=-vtemplate=$(TEMPLATE)
234$(OUTDIR): export _STYLE:=-vstyle=$(STYLE)
235$(OUTDIR): export _STATICS:=-vstatic=$(STATICS)
236$(OUTDIR): export _CHUM:=$(_CHUM)
237$(OUTDIR): $(_FILES) .chum
238 @awk "$${_TMPL}" "$${_STYLE}" "$${_STATICS}" "$${_CHUM}" $(_FILES)
239ifdef VDUMPF
240 printf '%s\t%s\t%s\n' $(NAME) $(URL_ROOT) "$(DESCRIPTION)" >> $(VDUMPF)
241endif
242
243$(TARBALL): $(_FILES) .chum
244 @printf '[build] %s\n' $(TARBALL)
245 @tar -cf $@ $(_FILES)
246
247.PHONY: build serve publish clean
248
249build: $(OUTDIR) $(TARBALL)
250
251serve: $(OUTDIR)
252 $(SERVER) $(OUTDIR)
253
254publish: $(TARBALL)
255 rsync -zaP $(OUTDIR)/ $(REMOTE)/
256
257clean:
258 rm -rf $(OUTDIR)