💾 Archived View for koyu.space › aartaka › public › this-post-is-cpp.h captured on 2024-06-16 at 12:36:53.
⬅️ Previous capture (2024-05-10)
-=-=-=-=-=-=-
#line 0 #define TITLE I Generated This Post With C Preprocessor #define DESCRIPTION "Yes, you can use C preprocessor as a website generator." #define IMAGE assets/thumbnail-614051-AA0000-F0FFFF.png #include "header.template" P() It's a new phase, so I'm re-evaluating my life and tech choices yet again. This time, I identify as a C programmer. I'm moving to C-based software and trying to script everything with C. So why not move my website from Lisp to C too? To C preprocessor, actually. P() There are several inspirations for this idea: ULI A(https://stackoverflow.com/a/1662202, Quake hack for raw file injection.) LI A(https://accu.org/journals/overload/20/108/ignatchenko_1926, An anecdote about preprocessor-based website.) LI A(https://www.reddit.com/r/C_Programming/comments/11at6d8/generate_html_in_c/, One attempt to generate HTML with C,) LI A(https://github.com/aalmkainzi/htmc, And another HTML generation library.) END(UL) P() So, technically, I'm not the first one to generate a website with C preprocessor (let's call it A(https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html, CPP.) But I'm up to the challenge of making it actually usable and pretty! The page you're looking at is generated with C preprocessor, so consider that a success. SECTION2(cpp-is-templating-engine, C Preprocessor Is a Templating Engine, Actually) P() CPP is quite dumb: it operates on code that's not even parsed yet. Which is bad if you want to make Lisp-like macros. And good if you want to embed some text or files into arbitrary text. Even if this text is HTML. So preprocessor is a templating engine of sorts. P() But CPP possesses several advantages over tools like A(https://mustache.github.io/, Mustache:) DL(Portability) C compiler with a preprocessor is available for every OS and every toolset. DD(Familiarity) Every C programmer knows how CPP works. Almost every programmer knows HTML. If they don't, they can easily learn both preprocessor and HTML. DD(Zero dependencies, no building) Again, C compilers are everywhere. And you don't need any third-party libraries to build a website with preprocessor. END(DL) P() Preprocessor also has built-in recursive file inclusion. One can write HTML files with preprocessor directives in them. Here's how a template file (say, <code>head.template</code>) might look like: PRE() LT()head> LT()title> PAGE_TITLE LT()/title> HASH()ifdef PAGE_DESCRIPTION LT()meta name="description" content=PAGE_DESCRIPTION/>; HASH()endif LT()/head> PRECAP(Hypothetical HTML file with preprocessor directives) P() You can then CD(HASH()include "head.template") from another file. SECTION2(problematic-chars, Problematic Chars) P() Preprocessor, as any templating engine, has some special chars that you have to handle. And preprocessor doesn't make it easy to work with these. DL(Hash Sign) The most obvious preprocessor offender is the hash sign. Preprocessor interprets hash sign as a directive. And fails silently if it cannot interpret the directive properly. ULI If you use hash in plaintext content, just replace it with CD(&num;) and enjoy: CD(safe hash sign: #) LI If you need hash in element IDs/fragments, quote it as per HTML attribute syntax and it will be recognized as C quoted string (nice feature of the preprocessor!): <code>LT()a href="#link-fragment">...LT()/a></code> LI Hash in HTML entities—you don't need it, because you have Unicode. END(UL) END(DD) DD(Unicode Chars) GCC in particular is bad at it. It expands 😃 to <code>U0001f603</code>, for example. That's why I use Clang. DD(Comments) Compilation stages before preprocessor remove comments, unless you instruct preprocessor not to. You simply have to provide a CD(-C) flag. GCC is making it hard again: it's adding its own comments to the output. That's why I use Clang. END(DL) SECTION2(worth-it, Is That Worth It?) P() Given all these problems with chars and the fact that preprocessor is scary, is it worth it? Actually, yes. The preprocessor-based setup abstracts away the repetitive parts, while keeping things simple and portable enough. And, however painful it is to acknowledge, it's simpler than A(this-post-is-lisp, my previous Lispy setup.) P() You can review all of my build code here: ULI Makefile A(makefile, Makefile that builds blog posts). LI Templates for page A(head.template, head,) A(header.template, header,) LI and a A(footer.template, copyright footer.) LI And the exact source file for this page: A(this-post-is-cpp.h, this-post-is-cpp.h) END(UL) #include "footer.template"