Skip to content
This repository
Newer
Older
100644 393 lines (343 sloc) 13.495 kb
c2c0268c »
2010-03-10 first working version, for some definition of "working"
1 #!/bin/sh
a69bf331 »
2010-03-12 wrap up docs
2 # **shocco** is a quick-and-dirty, literate-programming-style documentation
3 # generator written for and in __POSIX shell__. It borrows liberally from
4 # [Docco][do], the original Q&D literate-programming-style doc generator.
c2c0268c »
2010-03-10 first working version, for some definition of "working"
5 #
a69bf331 »
2010-03-12 wrap up docs
6 # `shocco(1)` reads shell scripts and produces annotated source documentation
c2c0268c »
2010-03-10 first working version, for some definition of "working"
7 # in HTML format. Comments are formatted with Markdown and presented
8 # alongside syntax highlighted code so as to give an annotation effect. This
a69bf331 »
2010-03-12 wrap up docs
9 # page is the result of running `shocco` against [its own source file][sh].
c2c0268c »
2010-03-10 first working version, for some definition of "working"
10 #
a69bf331 »
2010-03-12 wrap up docs
11 # shocco is built with `make(1)` and installs under `/usr/local` by default:
c2c0268c »
2010-03-10 first working version, for some definition of "working"
12 #
13 # git clone git://github.com/rtomayko/schocco.git
a69bf331 »
2010-03-12 wrap up docs
14 # cd schocco
15 # make
16 # sudo make install
17 # # or just copy 'shocco' wherever you need it
c2c0268c »
2010-03-10 first working version, for some definition of "working"
18 #
19 # Once installed, the `shocco` program can be used to generate documentation
20 # for a shell script:
21 #
22 # shocco shocco.sh
23 #
a69bf331 »
2010-03-12 wrap up docs
24 # The generated HTML is written to `stdout`.
cb97ebad »
2010-03-10 link to shocco source
25 #
a69bf331 »
2010-03-12 wrap up docs
26 # [do]: http://jashkenas.github.com/docco/
cb97ebad »
2010-03-10 link to shocco source
27 # [sh]: https://github.com/rtomayko/shocco/blob/master/shocco.sh#commit
c2c0268c »
2010-03-10 first working version, for some definition of "working"
28
a69bf331 »
2010-03-12 wrap up docs
29 # Usage and Prerequisites
30 # -----------------------
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
31
c2c0268c »
2010-03-10 first working version, for some definition of "working"
32 # The most important line in any shell program.
33 set -e
34
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
35 # There's a lot of different ways to do usage messages in shell scripts.
36 # This is my favorite: you write the usage message in a comment --
37 # typically right after the shebang line -- *BUT*, use a special comment prefix
38 # like `#/` so that its easy to pull these lines out.
39 #
40 # This also illustrates one of shocco's corner features. Only comment lines
41 # padded with a space are considered documentation. A `#` followed by any
42 # other character is considered code.
43 #
c225c62a »
2010-03-16 -t <title> sets the title in <head> and <h1>
44 #/ Usage: shocco [-t <title>] [<source>]
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
45 #/ Create literate-programming-style documentation for shell scripts.
46 #/
c225c62a »
2010-03-16 -t <title> sets the title in <head> and <h1>
47 #/ The shocco program reads a shell script from <source> and writes
48 #/ generated documentation in HTML format to stdout. When <source> is
a69bf331 »
2010-03-12 wrap up docs
49 #/ '-' or not specified, shocco reads from stdin.
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
50
51 # This is the second part of the usage message technique: `grep` yourself
52 # for the usage message comment prefix and then cut off the first few
53 # characters so that everything lines up.
eb5f1eaa »
2010-03-16 allow --help anywhere in argv
54 expr "$*" : ".*--help" >/dev/null && {
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
55 grep '^#/' <"$0" | cut -c4-
56 exit 0
57 }
58
c225c62a »
2010-03-16 -t <title> sets the title in <head> and <h1>
59 # A custom title may be specified with the `-t` option. We use the filename
60 # as the title if none is given.
61 test "$1" = '-t' && {
62 title="$2"
63 shift;shift
64 }
65
66 # Next argument should be the `<source>` file. Grab it, and use its basename
67 # as the title if none was given with the `-t` option.
68 file="$1"
b60c7edb »
2010-03-16 fix title not being set properly
69 : ${title:=$(basename "$file")}
c225c62a »
2010-03-16 -t <title> sets the title in <head> and <h1>
70
c2a27424 »
2010-03-16 munge in configured markdown and pygmentize
71 # These are replaced with the full paths to real utilities by the
b60c7edb »
2010-03-16 fix title not being set properly
72 # configure/make system.
c2a27424 »
2010-03-16 munge in configured markdown and pygmentize
73 MARKDOWN='@@MARKDOWN@@'
74 PYGMENTIZE='@@PYGMENTIZE@@'
75
a69bf331 »
2010-03-12 wrap up docs
76 # We're going to need a `markdown` command to run comments through. This can
77 # be [Gruber's `Markdown.pl`][md] (included in the shocco distribution) or
78 # Discount's super fast `markdown(1)` in C. Try to figure out if either are
79 # available and then bail if we can't find anything.
80 #
81 # [md]: http://daringfireball.net/projects/markdown/
82 # [ds]: http://www.pell.portland.or.us/~orc/Code/discount/
c2a27424 »
2010-03-16 munge in configured markdown and pygmentize
83 command -v "$MARKDOWN" >/dev/null || {
a69bf331 »
2010-03-12 wrap up docs
84 if command -v Markdown.pl >/dev/null
85 then alias markdown='Markdown.pl'
86 elif test -f "$(dirname $0)/Markdown.pl"
87 then alias markdown="perl $(dirname $0)/Markdown.pl"
88 else echo "$(basename $0): markdown command not found." 1>&2
89 exit 1
90 fi
91 }
92
93 # Check that [Pygments][py] is installed for syntax highlighting.
94 #
95 # This is a fairly hefty prerequisite. Eventually, I'd like to fallback
96 # on a simple non-highlighting preformatter when Pygments isn't available. For
97 # now, just bail out if we can't find the `pygmentize` program.
98 #
99 # [py]: http://pygments.org/
c2a27424 »
2010-03-16 munge in configured markdown and pygmentize
100 command -v "$PYGMENTIZE" >/dev/null || {
a69bf331 »
2010-03-12 wrap up docs
101 echo "$(basename $0): pygmentize command not found." 1>&2
102 exit 1
103 }
104
105 # Work and Cleanup
106 # ----------------
107
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
108 # Make sure we have a `TMPDIR` set. The `:=` parameter expansion assigns
109 # the value if `TMPDIR` is unset or null.
110 : ${TMPDIR:=/tmp}
111
112 # Create a temporary directory for doing work. Use `mktemp(1)` if
113 # available; but, since `mktemp(1)` is not POSIX specified, fallback on naive
114 # (and insecure) temp dir generation using the program's basename and pid.
115 : ${WORK:=$(
116 if command -v mktemp 1>/dev/null 2>&1
117 then
72412730 »
2010-03-16 sanity check the work dir was created properly
118 mktemp -dt "$(basename $0)"
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
119 else
120 dir="$TMPDIR/$(basename $0).$$"
121 mkdir "$dir"
122 echo "$dir"
123 fi
124 )}
125
72412730 »
2010-03-16 sanity check the work dir was created properly
126 # We want to be absolutely sure we're not going to do something stupid like
127 # use `.` or `/` as a work dir. Better safe than sorry.
128 test -z "$WORK" -o "$WORK" = '/' && {
129 echo "$(basename $0): could not create a temp work dir."
130 exit 1
131 }
132
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
133 # We're about to create a ton of shit under our `$WORK` directory. Register
134 # an `EXIT` trap that cleans everything up. This guarantees we don't leave
135 # anything hanging around unless we're killed with a `SIGKILL`.
ff2fcaab »
2010-03-16 since we mention EXIT already
136 trap "rm -rf $WORK" 0
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
137
138 # Preformatting
139 # -------------
140
141 # We slurp the input file, apply some light preformatting to make the
142 # code and doc formatting phases a bit easier, and then write the result
143 # out to a temp file under the `$WORK` directory.
144 #
145 # Generally speaking, I like to avoid temp files but the two-pass formatting
146 # logic makes that hard in this case. We may be reading from `stdin` or a
147 # fifo, so we don't want to assume _input_ can be read more than once.
148 cat "$1" |
c2c0268c »
2010-03-10 first working version, for some definition of "working"
149
150 # Remove comment leader text from all comment lines. Then prefix all
151 # comment lines with "DOCS" and interpreted / code lines with "CODE".
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
152 # The stream text might look like this after moving through the `sed`
153 # filters:
c2c0268c »
2010-03-10 first working version, for some definition of "working"
154 #
155 # CODE #!/bin/sh
156 # CODE #/ Usage: schocco <file>
157 # DOCS Docco for and in POSIX sh.
158 # CODE
159 # CODE PATH="/bin:/usr/bin"
160 # CODE
161 # DOCS Start by numbering all lines in the input file...
162 # ...
163 #
164 sed -n '
165 s/^/:/
166 s/^: \{0,\}# /DOCS /p
167 s/^: \{0,\}#$/DOCS /p
168 s/^:/CODE /p
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
169 ' |
170
c2c0268c »
2010-03-10 first working version, for some definition of "working"
171
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
172 # Write the result out to a temp file. We'll take two passes over it: one
173 # to extract and format the documentation comments and another to extract
174 # and syntax highlight the source code.
175 cat > "$WORK/raw"
c2c0268c »
2010-03-10 first working version, for some definition of "working"
176
177
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
178 # Now that we've read and formatted our input file for further parsing,
179 # change into the work directory. The program will finish up in there.
180 cd "$WORK"
c2c0268c »
2010-03-10 first working version, for some definition of "working"
181
182 # First Pass: Comment Formatting
183 # ------------------------------
184
a69bf331 »
2010-03-12 wrap up docs
185 # Start a pipeline going on our preformatted input.
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
186 cat raw |
c2c0268c »
2010-03-10 first working version, for some definition of "working"
187
a69bf331 »
2010-03-12 wrap up docs
188 # Replace all CODE lines with entirely blank lines. We're not interested
189 # in code right now, other than knowing where comments end and code begins
190 # and code begins and comments end.
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
191 sed 's/^CODE.*//' |
c2c0268c »
2010-03-10 first working version, for some definition of "working"
192
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
193 # Now squeeze multiple blank lines into a single blank line.
194 #
195 # __TODO:__ `cat -s` is not POSIX and doesn't squeeze lines on BSD. Use
196 # the sed line squeezing code mentioned in the POSIX `cat(1)` manual page
197 # instead.
198 cat -s |
c2c0268c »
2010-03-10 first working version, for some definition of "working"
199
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
200
201 # At this point in the pipeline, our stream text looks something like this:
202 #
203 # DOCS Now that we've read and formatted ...
204 # DOCS change into the work directory. The rest ...
205 # DOCS in there.
206 #
207 # DOCS First Pass: Comment Formatting
208 # DOCS ------------------------------
209 #
210 # Blank lines represent code segments. We want to replace all blank lines
211 # with a dividing marker and remove the "DOCS" prefix from docs lines.
c2c0268c »
2010-03-10 first working version, for some definition of "working"
212 sed '
213 s/^$/##### DIVIDER/
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
214 s/^DOCS //' |
215
216 # The current stream text is suitable for input to `markdown(1)`. It takes
217 # our doc text with embedded `DIVIDER`s and outputs HTML.
c2a27424 »
2010-03-16 munge in configured markdown and pygmentize
218 $MARKDOWN |
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
219
220 # Now this where shit starts to get a little crazy. We use `csplit(1)` to
221 # split the HTML into a bunch of individual files. The files are named
222 # as `docs0000`, `docs0001`, `docs0002`, ... Each file includes a single
223 # *section*. These files will sit here while we take a similar pass over the
224 # source code.
225 (
226 csplit -sk \
227 -f docs \
228 -n 4 \
229 - '/<h5>DIVIDER<\/h5>/' '{9999}' \
230 2>/dev/null ||
231 true
232 )
c2c0268c »
2010-03-10 first working version, for some definition of "working"
233
234 # Second Pass: Code Formatting
235 # ----------------------------
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
236 #
a69bf331 »
2010-03-12 wrap up docs
237 # This is exactly like the first pass but we're focusing on code instead of
238 # comments. We use the same basic technique to separate the two and isolate
239 # the code blocks.
c2c0268c »
2010-03-10 first working version, for some definition of "working"
240
241 # Get another pipeline going on our performatted input file.
74bae39e »
2010-03-16 various shell fixes
242 cat raw |
c2c0268c »
2010-03-10 first working version, for some definition of "working"
243
244 # Replace DOCS lines with blank lines.
74bae39e »
2010-03-16 various shell fixes
245 sed 's/^DOCS.*//' |
c2c0268c »
2010-03-10 first working version, for some definition of "working"
246
a69bf331 »
2010-03-12 wrap up docs
247 # Squeeze multiple blank lines into a single blank line.
74bae39e »
2010-03-16 various shell fixes
248 cat -s |
c2c0268c »
2010-03-10 first working version, for some definition of "working"
249
a69bf331 »
2010-03-12 wrap up docs
250 # Replace blank lines with a DIVIDER marker and remove prefix from CODE lines.
c2c0268c »
2010-03-10 first working version, for some definition of "working"
251 sed '
252 s/^$/# DIVIDER/
74bae39e »
2010-03-16 various shell fixes
253 s/^CODE //' |
c2c0268c »
2010-03-10 first working version, for some definition of "working"
254
a69bf331 »
2010-03-12 wrap up docs
255 # Now pass the code through pygments for syntax highlighting. We tell it the
256 # the input is `sh` and that we want HTML output.
c2a27424 »
2010-03-16 munge in configured markdown and pygmentize
257 $PYGMENTIZE -l sh -f html |
c2c0268c »
2010-03-10 first working version, for some definition of "working"
258
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
259 # Post filter the pygments output to remove partial `<pre>` blocks. We add
260 # these back in at each section when we build the output document.
c2c0268c »
2010-03-10 first working version, for some definition of "working"
261 sed '
262 s/<div class="highlight"><pre>//
74bae39e »
2010-03-16 various shell fixes
263 s/^<\/pre><\/div>//' |
c2c0268c »
2010-03-10 first working version, for some definition of "working"
264
a69bf331 »
2010-03-12 wrap up docs
265 # Again with the `csplit(1)`. Each code section is written to a separate
266 # file, this time with a `codeXXX` prefix. There should be the same number
267 # of `codeXXX` files as there are `docsXXX` files.
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
268 (
269 csplit -sk \
270 -f code \
271 -n 4 - \
272 '%# DIVIDER%' '/<span class="c"># DIVIDER</span>/' '{9999}' \
273 2>/dev/null ||
274 true
275 )
c2c0268c »
2010-03-10 first working version, for some definition of "working"
276
277 # At this point, we have separate files for each docs section and separate
278 # files for each code section.
a69bf331 »
2010-03-12 wrap up docs
279
280 # HTML Template
281 # -------------
282
283 # Create a function for apply the standard [Docco][do] HTML layout, using
284 # [jashkenas][ja]'s gorgeous CSS for styles. Wrapping the layout in a function
285 # lets us apply it elsewhere simply by piping in a body.
286 #
287 # [ja]: http://github.com/jashkenas/
288 # [do]: http://jashkenas.github.com/docco/
289 layout () {
290 cat <<HTML
c2c0268c »
2010-03-10 first working version, for some definition of "working"
291 <!DOCTYPE html>
292 <html>
293 <head>
294 <meta http-eqiv='content-type' content='text/html;charset=utf-8'>
c225c62a »
2010-03-16 -t <title> sets the title in <head> and <h1>
295 <title>$1</title>
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
296 <link rel=stylesheet href="http://jashkenas.github.com/docco/resources/docco.css">
c2c0268c »
2010-03-10 first working version, for some definition of "working"
297 </head>
298 <body>
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
299 <div id=container>
300 <div id=background></div>
c2c0268c »
2010-03-10 first working version, for some definition of "working"
301 <table cellspacing=0 cellpadding=0>
302 <thead>
303 <tr>
c225c62a »
2010-03-16 -t <title> sets the title in <head> and <h1>
304 <th class=docs><h1>$1</h1></th>
c2c0268c »
2010-03-10 first working version, for some definition of "working"
305 <th class=code></th>
306 </tr>
307 </thead>
308 <tbody>
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
309 <tr style=display:none><td><div><pre>
a69bf331 »
2010-03-12 wrap up docs
310 $(cat)
311 </pre></div></td></tr>
312 </tbody>
313 </table>
314 </div>
315 </body>
316 </html>
c2c0268c »
2010-03-10 first working version, for some definition of "working"
317 HTML
a69bf331 »
2010-03-12 wrap up docs
318 }
c2c0268c »
2010-03-10 first working version, for some definition of "working"
319
a69bf331 »
2010-03-12 wrap up docs
320 # Recombining
321 # -----------
322
323 # Alright, we have separate files for each docs section and separate
324 # files for each code section. We've defined a function to wrap the
325 # results in the standard layout. All that's left to do now is put
326 # everything back together.
327
328 # Start the pipeline with a simple list of split out temp filename. One file
329 # per line.
74bae39e »
2010-03-16 various shell fixes
330 ls -1 docs[0-9]* code[0-9]* 2>/dev/null |
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
331
aaad1c08 »
2010-03-10 more doc toward the crazy end part there
332 # Now sort the list of files by the *number* first and then by the type. The
a69bf331 »
2010-03-12 wrap up docs
333 # list will look something like this when `sort(1)` is done with it:
aaad1c08 »
2010-03-10 more doc toward the crazy end part there
334 #
335 # docs0000
336 # code0000
337 # docs0001
338 # code0001
339 # docs0002
340 # code0002
341 # ...
342 #
74bae39e »
2010-03-16 various shell fixes
343 sort -n -k1.5 -k1.1r |
aaad1c08 »
2010-03-10 more doc toward the crazy end part there
344
a69bf331 »
2010-03-12 wrap up docs
345 # And if we pass those files to `cat(1)` in that order, it concatenates them
346 # in exactly the way we need. `xargs(1)` reads from `stdin` and passes each
347 # line of input as a separate argument to the program given.
348 #
349 # We could also have written this as:
aaad1c08 »
2010-03-10 more doc toward the crazy end part there
350 #
351 # cat $(ls -1 docs* code* | sort -n -k1.5 -k1.1r)
352 #
353 # I like to keep things to a simple flat pipeline when possible, hence the
354 # `xargs` approach.
74bae39e »
2010-03-16 various shell fixes
355 xargs cat |
52c1454b »
2010-03-10 clean up csplit spew on EXIT. a ton more docs
356
c2c0268c »
2010-03-10 first working version, for some definition of "working"
357
a69bf331 »
2010-03-12 wrap up docs
358 # Run a quick substitution on the embedded dividers to turn them into table
359 # rows and cells. This also wraps each code block in a `<div class=highlight>`
360 # so that the CSS kicks in properly.
aaad1c08 »
2010-03-10 more doc toward the crazy end part there
361 sed '
362 s/<h5>DIVIDER<\/h5>/<\/pre><\/div><\/td><\/tr><tr><td class=docs>/
363 s/<span class="c"># DIVIDER<\/span>/<\/td><td class=code><div class=highlight><pre>/
74bae39e »
2010-03-16 various shell fixes
364 ' |
aaad1c08 »
2010-03-10 more doc toward the crazy end part there
365
a69bf331 »
2010-03-12 wrap up docs
366 # Pipe our recombined HTML into the layout and let it write the result to
367 # `stdout`.
c225c62a »
2010-03-16 -t <title> sets the title in <head> and <h1>
368 layout "$title"
aaad1c08 »
2010-03-10 more doc toward the crazy end part there
369
a69bf331 »
2010-03-12 wrap up docs
370 # More
371 # ----
372 #
373 # **shocco** is the third tool in a growing family of quick-and-dirty,
374 # literate-programming-style documentation generators:
375 #
376 # * [Docco][do] - The original. Written in CoffeeScript and generates
377 # documentation for CoffeeScript, JavaScript, and Ruby.
378 # * [Rocco][ro] - A port of Docco to Ruby.
379 #
380 # If you like this sort of thing, you may also find interesting Knuth's
381 # massive body of work on literate programming:
382 #
383 # * [Knuth: Literate Programming][kn]
384 # * [Literate Programming on Wikipedia][wi]
385 #
386 # [ro]: http://rtomayko.github.com/rocco/
387 # [do]: http://jashkenas.github.com/docco/
388 # [kn]: http://www-cs-faculty.stanford.edu/~knuth/lp.html
389 # [wi]: http://en.wikipedia.org/wiki/Literate_programming
390
391 # Copyright (C) [Ryan Tomayko <tomayko.com/about>](http://tomayko.com/about)<br>
392 # This is Free Software distributed under the MIT license.
393 :
Something went wrong with that request. Please try again.