Unixy Pikchr command-line preprocessor for any Markdown formatter (C)
(1) By Michael Thornburgh (zenomt) on 2023-08-06 22:10:51 [link] [source]
hello friends. i only recently discovered Pikchr after long thinking something like it needed to exist (and it turns out it does!). i particularly appreciate how easy it is to integrate into other tools.
as near as i can tell, however, none of the existing integrations or tools take the form of a pic(1)
-like Unix filter/preprocessor that reads (for example) Markdown+Pikchr and writes Markdown+SVG for further processing by insert your favorite Markdown formatter. so i wrote one. :) it's super straightforward and nearly (but not entirely) trivial, so i'm surprised that one doesn't exist already. if i missed one out in the wild, please correct me.
https://github.com/zenomt/pikchr-cmd
in the spirit of pic(1)
, it searches its standard input for Pikchr start & end delimiters, transforming each delimited diagram into an inline SVG (inside a <div>
to style the max-width
), and copies everything else verbatim to the standard output. for simplicity, delimiters must be at the beginning of a line (not indented as allowed by Markdown) and have exactly three tildes or backticks. PIC delimiters (.PS
and .PE
) are also recognized.
it's just one C file to compile with pikchr.c
, and requires POSIX regular expressions (that is, regcomp(3)
and regexec(3)
) which should already be in your standard library.
arrow right 225% "Markdown+Pikchr" "Source" Tool: box rad 5px "pikchr" mono "Preprocessor" "(main.c)" mono fit arrow same "Markdown+SVG" "Intermediate" box same "Any Markdown" "Formatter" fit arrow same "HTML+SVG" "Output" arrow <-> down from Tool.s; "C API" ljust at 4pt right of last arrow box with .n at last arrow.s same "Pikchr" "Formatter" "(pikchr.c)" mono fit→ /pikchrshow
sadly i discovered that GitHub suppresses inline SVGs when browsing a repo's Markdown files, though they appear properly in GitHub Pages.
-michael thornburgh
(2) By Stephan Beal (stephan) on 2023-08-07 00:50:06 in reply to 1 [link] [source]
https://github.com/zenomt/pikchr-cmd
Just FYI: a link to your project was added to the "external projects" list on the home page. If its description is wrong, please post a proposed replacement.
(3) By Michael Thornburgh (zenomt) on 2023-08-07 00:55:27 in reply to 2 [link] [source]
awesome, thanks! i hope folks find it useful.
(4) By Michael Thornburgh (zenomt) on 2023-08-09 16:04:31 in reply to 2 [link] [source]
If its description is wrong, please post a proposed replacement.
while the description is technically correct, i think it doesn't quite convey what i think is the most important aspect for a one-liner elevator pitch. instead how about:
pikchr-cmd, by Michael Thornburgh, is a command-line Pikchr preprocessor tool for use with any Markdown formatter.
(adjust capitalization as you like for Pikchr and Markdown :) )
(5) By Stephan Beal (stephan) on 2023-08-09 16:38:48 in reply to 4 [link] [source]
... is a command-line Pikchr preprocessor tool for use with any Markdown formatter.
It's changed now.
(adjust capitalization as you like for Pikchr and Markdown :) )
Apparently the project's standard is to capitalize both. Good catch.
(6) By Michael Thornburgh (zenomt) on 2023-08-12 23:15:43 in reply to 1 [link] [source]
i've made a few additions to improve the tool's utility:
- start and end delimiters now consist of 3 or more consecutive tildes or backticks (though delimiters still need to start at the beginning of a line, so the tool doesn't need to implement a complete Markdown parser and so it's possible to include delimited Pikchr source in a code block)
- "
svg-only
" (no enclosing DIV with max-width) can be selected per-diagram with a start-delimiter modifier - the "
requote
" modifier signals to include the original Pikchr source in an indented code block in the output (with or without the start and end delimiters according to the "delimiters
" modifier) after the compiled SVG - arbitrary other modifiers can be added in the start delimiter to be used as names/tags, and can be matched with the
-N
command-line option for extracting and compiling a specific diagram from the input document; this is useful for the GitHub case where an SVG can only be used in an external IMG reference but you'd still like the Pikchr source in the original document for plain-text consumption
(7) By Michael Thornburgh (zenomt) on 2025-06-08 22:26:54 in reply to 6 [source]
i've added an extension to my command-line tool to approximate Fossil's "click to see source" behavior in plain-ol' HTML (no JavaScript). if you give the "details
" modifier along with requote
, the Pikchr source will be emitted inside of an HTML <details>
element. its <summary>
sub-element's content can be changed with a command-line option. the "open
" modifier sets the <details>
element to start out "open" (or visible or disclosed) -- the default is for it to be closed/hidden. it would look like this (click "Pikchr Source" or its "▶" to show or hide the source):
box→ /pikchrshow
``` pikchr svg-only requote delimiters details
box
```
(note: the open
attribute on a <details>
doesn't seem to work here in the forum so i didn't use it for this example)
Input to the command-line tool:
``` pikchr svg-only requote delimiters details
box
```
Output:
<svg xmlns='http://www.w3.org/2000/svg' viewBox="0 0 112.32 76.32" data-pikchr-date="20250321215505" style='font-size:initial;'>
<path d="M2.16,74.16L110.16,74.16L110.16,2.16L2.16,2.16Z" style="fill:none;stroke-width:2.16;stroke:rgb(0,0,0);" />
</svg>
<details markdown="1">
<summary>Pikchr Source</summary>
``` pikchr svg-only requote delimiters details
box
```
</details>
(note: the markdown="1"
is for kramdown compatibility, needed for GitHub Pages; it wouldn't be needed for CommonMark-compliant formatters)
(8.1) By Stephan Beal (stephan) on 2025-06-08 23:00:04 edited from 8.0 in reply to 7 [link] [source]
(note: the open attribute on a
<details>
doesn't seem to work here in the forum so i didn't use it for this example)
There's an internal whitelist of attributes which are permitted, and that one is apparently not on the list. My quick attempt to add it revealed that i'll need to shift bitmasks of many enum elements to add a bit for it (in properly name-sorted order, in the middle of a longer bitmask list).
Anyway, this post is a reminder to self to fix that after some sleep.
Edit: that change is checked in to a branch now and is pending debate with the team on whether we want to squander our few remaining bitmask bits this way. (FWIW, it seems reasonable to me.)
(9) By Michael Thornburgh (zenomt) on 2025-06-09 00:26:53 in reply to 8.1 [link] [source]
that change is checked in to a branch now
looking at that check-in, while i see the definition of AMSK_OPEN
, that doesn't seem to be added (or |
ed) into the allowed attributes for details
.
(11) By Stephan Beal (stephan) on 2025-06-09 08:00:22 in reply to 9 [link] [source]
i see the definition of AMSK_OPEN, that doesn't seem to be added ...
Indeed, nice catch. How it works without that is not clear to me, but it does. It shouldn't, IMO.
That fix is checked in now.
(13) By Michael Thornburgh (zenomt) on 2025-06-09 16:05:13 in reply to 11 [link] [source]
How it works without that is not clear to me, but it does. It shouldn't, IMO.
that should be very concerning.
(10.1) By Michael Thornburgh (zenomt) on 2025-06-09 01:31:35 edited from 10.0 in reply to 8.1 [link] [source]
pending debate with the team on whether we want to squander our few remaining bitmask bits this way
it looks like the way the attribute checking works is: for each attribute, it's looked up with a binary search in a list with about 30 items (so about 4 strcmp()
s on average per attribute) to see if it's even there, and if it is you have its bit, and you can then check if the bit is in the mask for the tag under consideration.
given the number of strcmp()
s you're doing now and that the number of allowed attributes for almost all tags is small (except for img
which allows 9), i bet it wouldn't be any more expensive, and also be more general, if you were to:
- have a function like my
strword()
to look for a "word" in a string (strstr(3)
isn't quite what you want, because it would match if an attribute-under-consideration was a prefix of an allowed attribute) - skip checking whether the attribute is one you have a bit for
- for each tag, have a simple string of allowed attributes separated by spaces
- an attribute is allowed if
strword(allowedForTag, attr)
answers not-NULL
while that last part would be O(N) in "number of allowed attributes" instead of O(log M) in "number of known attributes", it's probably a wash since N is usually like 3 or fewer, and M is 30.
(12) By Stephan Beal (stephan) on 2025-06-09 08:02:21 in reply to 10.1 [link] [source]
it looks like the way the attribute checking works is ...
That lives in a corner of the tree we very rarely need to touch, so it's one of those "if it ain't broke, don't fix it" things. That said: patches improving it would be thoughtfully considered :).
(14) By Michael Thornburgh (zenomt) on 2025-06-09 16:21:26 in reply to 12 [link] [source]
it's one of those "if it ain't broke, don't fix it" things
it will be broken when it comes time to add a new attribute after one more after this one. at that time, an approach like i described above would make future maintenance much easier (adding a new attribute would be one word in one line per tag it needed to be allowed in) with, i expect, no measurable performance impact (which appears to have been a consideration given the current code).
patches improving it would be thoughtfully considered :)
since i don't run Fossil myself, i don't have either of "enough skin in the game" or "sufficient experience with the code to recognize whether i made things worse". and by just suggesting an algorithm without actually coding it myself (beyond an illustrative example of one possible implementation of one part), there's no IPR consideration for y'all or for me.
perhaps the time of "add a new attribute after one more after this one" will never come. but if it does, hopefully this thread will be remembered. :)