RETRO's source files generally use a subset of Markdown for
formatting. This is a small tool to convert this into HTML.
Features
Recognizes:
• lists
• indented code blocks
• paragraphs
• headers
• fenced code blocks
• horizontal rules
• inline formatting elements
Limitations
This only supports a limited subset of full Markdown. I am
not adding support for the various linking formats, ordered
lists, underlined headers, doubled asterisk, doubled
underscores, multiple line/paragraph list entries, or images.
The CSS used is extracted from the end of this file. I only
do a tiny bit of styling by default; just enough to ensure
that code blocks are easily identified.
Begin by locating and extracting the CSS.
~~~'<style> s:put nl
FALSE script:name [ over [ '##_CSS s:eq? or ] -if; s:put nl ] file:for-each-line
drop
'</style> s:put nl
~~~
The first couple of words are a variation of s:put that
generates HTML codes for specific characters. This ensures
that code output displays correctly.
~~~:c:put<code>
$< [ '< s:put ] case
$> [ '> s:put ] case
$& [ '& s:put ] case
ASCII:SPACE [ ' s:put ] case
c:put ;
:s:put<code> [ c:put<code> ] s:for-each ;
~~~
For regular text, there are a couple of inline formatting things
to deal with.
~~~'Emphasis var
'Strong var
'Escape var
'Code var
:format
@Escape [ &Escape v:on ] if;
$` [ @Escape [ &Escape v:off $* c:put ] if;
@Code n:zero? [ '<tt_style='display:inline'> &Code v:on ]
[ '</tt> &Code v:off ] choose s:put ] case
$* [ @Escape @Code or [ &Escape v:off $* c:put ] if;
@Strong n:zero? [ '<strong> &Strong v:on ]
[ '</strong> &Strong v:off ] choose s:put ] case
$_ [ @Escape @Code or [ &Escape v:off $_ c:put ] if;
@Emphasis n:zero? [ '<em> &Emphasis v:on ]
[ '</em> &Emphasis v:off ] choose s:put ] case
c:put ;
:s:put<formatted> [ format ] s:for-each ;
~~~
Next, handling of code blocks.
The fences need to start and end with ~~~ on a line by
itself. (This will also handle test blocks using three
bacticks as well)
~~~'Block var
:in-block? @Block ;
:block? dup [ '~~~ s:eq? ] [ '``` s:eq? ] bi or ;
:toggle drop @Block n:zero? !Block ;
:format:block '<tt> s:put s:put<code> '</tt> s:put nl ;
~~~
After this, I define detection and formatting of headers. The
headers should look like:
# Level 1
## Level 2
### Level 3
~~~:header?
dup [ '# s:begins-with? ]
[ '## s:begins-with? ]
[ '### s:begins-with? ] tri or or ;
:format:head
ASCII:SPACE s:split/char
'# [ '<h1> s:put n:inc s:put '</h1> s:put nl ] s:case
'## [ '<h2> s:put n:inc s:put '</h2> s:put nl ] s:case
'### [ '<h3> s:put n:inc s:put '</h3> s:put nl ] s:case
drop ;
~~~
Indented code blocks are lines indented by four spaces.
~~~:code? dup '____ s:begins-with? ;
:format:code '<tt> s:put #4 + s:put<code> '</tt> s:put nl ;
~~~
Horizonal rules consist of four or more - characters on
a line. E.g.,
----
--------
~~~:rule? dup '---- s:begins-with? ;
:format:rule drop '<hr> s:put nl ;
~~~
Lists start with a - or *, followed by a space, then
the item text.
~~~:list? dup [ '-_ s:begins-with? ] [ '*_ s:begins-with? ] bi or ;
:format:list '<li> s:put #2 + s:put<formatted> '</li> s:put nl ;
~~~
Blank lines denote paragraph breaks.
~~~:blank? dup s:length n:zero? ;
~~~
~~~:format
block? [ toggle ] if;
in-block? [ format:block ] if;
blank? [ drop '<p> s:put nl ] if;
header? [ format:head ] if;
code? [ format:code ] if;
list? [ format:list ] if;
rule? [ format:rule ] if;
s:put<formatted> nl ;
#0 script:get-argument [ format ] file:for-each-line
~~~
This concludes the Markdown (subset) in RETRO utility.
CSS
tt, pre {
background: #eee;
color: #111;
font-family: monospace;
white-space: pre;
display: block;
width: 100%;
}