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"] = "$(URL_CLONE)"
82 TMPLV["DOWNLOAD"] = "$(URL_DOWNLOAD)"
83 TMPLV["DESCRIPTION"] = "$(DESCRIPTION)"
84 TMPLV["AUTHOR"] = "$(AUTHOR)"
85 TMPLV["ROOT"] = "$(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 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 copy_statics () {
202 split(static, STATICS, / */)
203 for (s in STATICS) {
204 printf("Copying %s\n", STATICS[s])
205 if (system("cp " STATICS[s] " $(OUTDIR)/" outfn(STATICS[s], 3)))
206 die(2, "Can't copy static file: " STATICS[s])
207 }
208}
209endef
210
211# Internal recipes
212
213$(OUTDIR): export _TMPL:=-vtemplate=$(TEMPLATE)
214$(OUTDIR): export _STYLE:=-vstyle=$(STYLE)
215$(OUTDIR): export _STATICS:=-vstatic=$(STATICS)
216$(OUTDIR): export _CHUM:=$(_CHUM)
217$(OUTDIR): $(_FILES) .chum
218 @awk "$${_TMPL}" "$${_STYLE}" "$${_STATICS}" "$${_CHUM}" $(_FILES)
219ifdef VDUMPF
220 printf '%s\t%s\t%s\n' $(NAME) $(URL_ROOT) "$(DESCRIPTION)" >> $(VDUMPF)
221endif
222
223$(TARBALL): $(_FILES) .chum
224 @printf '[build] %s\n' $(TARBALL)
225 @tar -cf $@ $(_FILES)
226
227.PHONY: build serve publish clean
228
229build: $(OUTDIR) $(TARBALL)
230
231serve: $(OUTDIR)
232 $(SERVER) $(OUTDIR)
233
234publish: $(TARBALL)
235 rsync -zaP $(OUTDIR)/ $(REMOTE)/
236
237clean:
238 rm -rf $(OUTDIR)