From: Lucian Mogosanu Date: Mon, 1 Jul 2019 07:29:42 +0000 (+0300) Subject: Add cl-who demo draft X-Git-Tag: v0.11~40 X-Git-Url: https://git.mogosanu.ro/?a=commitdiff_plain;h=ca954205d81bb4ec31fd7d4bc5acfeab3800997e;p=thetarpit.git Add cl-who demo draft --- diff --git a/drafts/000-cl-who-ii.markdown b/drafts/000-cl-who-ii.markdown new file mode 100644 index 0000000..27e6c52 --- /dev/null +++ b/drafts/000-cl-who-ii.markdown @@ -0,0 +1,229 @@ +--- +postid: 000 +title: CL-WHO demo: The Coad Pit +date: June 29, 2019 +author: Lucian Mogoșanu +tags: tech, tmsr +--- + +In the midst of an [ongoing battle][btcbase-1919627] with the monster +that is [Hunchentoot][hunchentoot-i], I have received the suggestion +that I can easily communicate intermediate results by simply posting +said code on the web in an [annotatable form][btcbase-1919634]. I +judge this to be a great idea, even though the items in question +represent merely coad[^1]. Moreover, I've previously promised to +provide examples for [other WWWisms][cl-who], and it occurs to me that +this is just the right occasion to do so. + +Thus this post answers the following couple of questions: a. how can +one use CL-WHO to generate a simple web site? and b. given a code base +consisting of source code, how can we turn that into a HTMListic +representation with per-line references, *using our tool*[^2]? + +[As previously described][cl-who-again] CL-WHO gives us a template +language, wrapped around the `with-html-output` macro. For example, to +write a simple web page to `*standard-output*`, one would write: + +~~~~ {.commonlisp} +(cl-who:with-html-output (*standard-output*) + (:html (:body + (:b (cl-who:str "Hello, world!"))))) +~~~~ + +which yields the output: + +~~~~ +Hello, world! +~~~~ + +First, let's make some sense of what we just wrote. The full +description of the template language can be found in [the +docs][cl-who-syntax], but in short, we: 1. define a context for HTML +output; in which we 2. generate a HTML whose body contains; 3. a +"bold" context, containing 4. the string (`str`) "Hello, world!". + +Now let's say we want to do something fancier, such as looping over a +list of items: + +~~~~ {.commonlisp} +(cl-who:with-html-output (*standard-output* nil) + (:html (:body + (:ul + (loop for x in '(a b c d e f) + for i = 1 then (1+ i) do + (cl-who:htm + (:li (cl-who:esc + (format nil "[~d] ~s" i x)))))))))) +~~~~ + +gives us: + +~~~~ + +~~~~ + +In addition to the loop itself, this example has two unknowns, namely +the `htm` and the `esc` keywords. Firstly, `esc` is very similar to +the previously-presented `str`, only that it does a HTML escape (of +e.g. ">" into ">"). + +Secondly, `htm` is a sort of a shorthand for `with-html-output` that +is necessary for the following reason: the HTML generation mechanism +of WHO looks at cons cells whose first element is a keyword +(interpreted as a HTML tag), and it collects them, until it reaches +something else (e.g. our loop construct above). When it gets there, it +treats the input as a standard S-expression, to be evaluated by the +Lisp evaluator as-is. However, this S-expression now also contains the +definition of `htm` in its macro-scope, which causes further +occurences of `htm` to be expanded, which allows nested +HTML-in-Lisp-in-HTML-... expressions. + +To put this convoluted[^3] explanation in simpler words: +`with-html-output` throws us into a HTML template context; `loop` gets +us out of there, but `htm` brings us back in, which is precisely the +"mix HTML and CL" machinery at work. Use it wisely, and it shall serve +you well. + +Now that we have these basics fixed, we can get to what really +interests us, that is, a HTML representation of source code with +line-by-line references. For the sake of simplicity and modularity, we +are going to break this into a set of macros, each doing one +thing. For example, the CL-WHO template for a line number: + +~~~~ {.commonlisp} +(defmacro tcp-line-number (n &optional (padding 0)) + "Template for the line number." + `(cl-who:str (format nil "~vd " ,padding ,n))) +~~~~ + +Note: the `~vd` format string allows us to do variable padding with +spaces, which is quite useful here to keep our lines aligned. Next, +the line content and the actual line: + +~~~~ {.commonlisp} +(defmacro tcp-line-content (line) + "Template for the line content." + `(cl-who:esc ,line)) + +(defmacro tcp-line (number padding content) + "Template for the line." + `(cl-who:htm + (:span :class "line" + :id (format nil "L~d" ,number) + (:a :href (format nil "#L~d" ,number) + :class "lineno" + (tcp-line-number ,number ,padding)) + (:span :class "linecont" + (tcp-line-content ,content))) + (:br))) +~~~~ + +Next we need to put all the lines together in a code block, which for +our convenience will be a `pre` tag: + +~~~~ {.commonlisp} +(defmacro tcp-block (lines) + "Template for generating a code block out of a list of lines." + (with-gensyms (i padding line) + `(cl-who:htm + (:pre :class "coadblock" + (loop for ,padding = (length (format nil "~d" (length ,lines))) + for ,i = 1 then (1+ ,i) + for ,line in ,lines do + (tcp-line ,i ,padding ,line)))))) +~~~~ + +... and, for even more convenience, we'll output a HTML header with +some CSS to bellify the coad loox: + +~~~~ {.commonlisp} +(defmacro tcp-html-header (title) + `(cl-who:htm + (:title (cl-who:str ,title)) + (:style :type "text/css" + (cl-who:str " +.coadblock {background-color:#e8e8e8; + border-style:solid;border-width:1px;padding-left:5px;} +.line {float:left; width:100%;} +.lineno {font-weight:bold;text-decoration:none;color:black;} +.lineno:visited {color:black;} +:target {background-color:lightyellow;}")))) +~~~~ + +Finally, we put everything together in a function that receives a page +title, a list of strings, each string a line in the source file, and +outputs them into a file: + +~~~~ {.commonlisp} +(defun write-code-page (title lines out-path) + "Write LINES to OUT-PATH as a HTML file." + (with-open-file (out out-path + :direction :output + :if-exists :supersede) + (cl-who:with-html-output (out nil + :prologue t + :indent nil) + (cl-who:htm + (:html (:head (tcp-html-header title)) + (:body (:b (cl-who:str title)) + (tcp-block lines))))))) +~~~~ + +That's it, really, our coad-pit-generating coad. Go ahead and test +it. This isn't quite the whole thing, but I'm deliberately omitting it +since there's a lot of it that's out of the scope of a CL-WHO demo: we +need to a. take a code base given as input, and; b. based on its input +path and output path and; c. based on its path inside the website +we're deploying to; d. we must process the code tree; and e. for each +source file, output the generated page, and f. for each directory, +create the output directory and output an index page. I initially +thought this would fit in about two hundred lines of Lisp, but it runs +a bit over three hundred, so I'm illustrating the demo by having it +generate itself. + +The full coad can be examined on this [demo page][cl-who-demo]. For +the future, I am preparing a new site, The Coad Pit, that will be soon +available over at [coad.thetarpit.org][coad], containing e.g. CL-WHO, +Hunchentoot and other coads and codes that I've gathered over time. + +[^1]: "Coad" is a TMSR term of art, representing, well... [let's + see][btcbase-1760554]: + + > **mircea_popescu**: is "coad" code ? + > **asciilifeform**: yea, but think 'with lower case c' + > **asciilifeform**: i.e snippets + + In other words, coad is code that comes with no guarantees that + it does what the author says it does, nor, in some cases, that + it does anything useful at all, nor that it's usable, readable, + fittable in head and so on -- making its value as a published + item somewhat questionable. However, [some] coad may have the + potential to be turned into actual code, i.e. into something + that I for example could sign without thinking twice, which is + why putting coad on the 'net in some form or the other is not at + all a useless endeavour. + +[^2]: There's nothing fundamentally new in "code sites". Shithub does + it, owing to ye olde [LXR][lxr]; meanwhile, Phf's btcbase has a + very neat [V patch explorer][btcbase-patches] that's been in + Republican use for years now. + + So the only thing I'm adding to it is the didactic aspect, I + guess. + +[^3]: The reader might get a reasonably good insight on what happens + behind the scenes by running `macroexpand-1` on the example + presented. Other than that, it's just CL macros all the way + down, and this unfortunately can't be properly explained in this + humble article. + +[btcbase-1919627]: http://btcbase.org/log/2019-06-23#1919627 +[hunchentoot-i]: /posts/y05/093-hunchentoot-i.html +[btcbase-1919634]: http://btcbase.org/log/2019-06-23#1919634 +[btcbase-1760554]: http://btcbase.org/log/2017-12-29#1760554 +[cl-who]: /posts/y05/092-cl-who.html#selection-71.157-75.46 +[lxr]: http://archive.is/O6l48 +[btcbase-patches]: http://btcbase.org/patches[cl-who-again]: http://thetarpit.org/posts/y05/092-cl-who.html#selection-67.223-67.610 +[cl-who-syntax]: http://archive.is/3kH5V#selection-835.0-835.20 +[cl-who-demo]: /uploads/2019/06/cl-who-demo/ +[coad]: http://coad.thetarpit.org/