-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
3,782 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
--- | ||
title: "Blogging in Djot instead of Markdown" | ||
tags: ["Djot", "Webpage", "Rust"] | ||
--- | ||
|
||
I recently happened to see an offhand comment on [Hacker News][] about a markup language called [Djot][]. | ||
I don't really have any large issues with the {-Markdown-} [CommonMark][] I use to generate the posts for this website, but my brain saw a chance to get sidetracked yet again, and here we are. | ||
|
||
I spent some hours to port my hacky custom markup extensions to Djot and started to write my posts in Djot. | ||
Having used Markdown for many years, I'm still not comfortable with some of Djot's different syntax choices, but it ... | ||
|
||
# What and why Djot? | ||
|
||
The creator of Djot is John MacFarlane, the same philosophy professor that also created [Pandoc][] and [CommonMark][]. | ||
These might be two of the most influential projects in the markup space, so you'd get the feeling that there should be a good reason for [Djot][]'s creation. | ||
|
||
The rationale for [Djot][] is specified in the [GitHub repo][Djot] and references his blog post [Beyond Markdown][] that inspired the whole project. | ||
|
||
If I should make an attempt to summarize the goals of [Djot][], I'd say that Djot tries to fix flaws of CommonMark in two areas: | ||
|
||
1. It's much easier to parse. | ||
|
||
{author="John Macfarlane"} | ||
> So, to fully specify emphasis parsing, we need additional rules. The 17 discouragingly complex rules in the CommonMark spec are intended to force the sorts of readings that humans will find most natural. | ||
|
||
17 rules just to parse emphasis sounds like fun times... | ||
|
||
At least it's better than Markdown that's ambiguous. | ||
|
||
1. More features than CommonMark. | ||
|
||
For instance proper support for footnotes, math, divs, and attributes that can be applied to any element. | ||
|
||
I'm sympathetic to the parsing problem, and for that alone I was willing to at least try it out. | ||
But what really sold me was first-class support for attributes and divs, something I've added ugly hacks to work around. | ||
|
||
If I'm going to bring up the negative parts, it's that for a regular user Djot mostly fixes edge-cases. | ||
For me, 95% of the time it's just like writing Markdown, and (unfortunately) it'll have difficulties overcoming the "good enough" barrier of the various flavors of Markdown. | ||
|
||
# Tools | ||
|
||
Given that Djot is a relatively young project, I expected the tooling to be lacking. | ||
There are for sure some things missing, but it wasn't so bad for my use-case. | ||
|
||
I found a Djot Sublime Text grammar I can use for syntax highlighting the blog, | ||
and there is Vim syntax highlighting in the [Djot repo][Djot]. | ||
|
||
(Sad for those who don't use Vim I guess.) | ||
|
||
More annoying is that there's no treesitter implementation for Djot. | ||
This is unfortunate, as with treesitter I get proper syntax highlighting inside code blocks for Markdown and I have a general treesitter jump command that, for Markdown, jumps between headers with `]g` and `[g`. | ||
(In Rust it jumps between structs, implementations, enums, and functions.) | ||
|
||
Not a deal-breaker of course. | ||
Using the Markdown treesitter works well enough for the time being. | ||
(Maybe I need to explore how treesitter grammars work one day, and create one for Djot.) | ||
|
||
As for parsing I found [Jotdown][], which is a Rust library with an API inspired by [pulldown-cmark][], the library I use to parse CommonMark. | ||
|
||
# Abstracting away markup parsing | ||
|
||
# Customized markup | ||
|
||
For the blog I have some [markup transformations][] I apply to Markdown. | ||
This includes demoting headers (they only `h1` i want is the post title), embedding bare YouTube links and prettifying code. | ||
|
||
[markup transformations]: /blog/2022/08/29/rewriting_my_blog_in_rust_for_fun_and_profit/#markdown-transformations | ||
|
||
## Epigraph | ||
|
||
Before: | ||
|
||
```markdown | ||
> This is an epigraph | ||
{ :epigraph } | ||
``` | ||
|
||
```djot | ||
::: epigraph | ||
> This is an epigraph | ||
::: | ||
``` | ||
|
||
Produces: | ||
|
||
```html | ||
<div class="epigraph"> | ||
<blockquote> | ||
<p>This is an epigraph</p> | ||
</blockquote> | ||
</div> | ||
``` | ||
|
||
## Asides | ||
|
||
```markdown | ||
> This is an <aside> | ||
{ :notice } | ||
``` | ||
|
||
## Images and figures | ||
|
||
::: Flex | ||
/images/configura14/octree1.png | ||
/images/configura14/octree2.png | ||
::: | ||
|
||
```markdown | ||
::: Flex | ||
/images/configura14/octree1.png | ||
/images/configura14/octree2.png | ||
::: | ||
``` | ||
|
||
```djot | ||
::: flex | ||
{ height=100 } | ||
{ height=100 } | ||
::: | ||
``` | ||
|
||
[CommonMark]: https://commonmark.org/ | ||
[Pandoc]: https://pandoc.org/ | ||
[Beyond Markdown]: https://johnmacfarlane.net/beyond-markdown.html | ||
[Djot]: https://github.com/jgm/djot | ||
[pulldown-cmark]: https://crates.io/crates/pulldown-cmark | ||
[Jotdown]: https://github.com/hellux/jotdown |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
use jotdown::{Attributes, Container, Event}; | ||
|
||
pub struct QuoteTransforms<'a, I: Iterator<Item = Event<'a>>> { | ||
parent: I, | ||
event_queue: Vec<Event<'a>>, | ||
} | ||
|
||
impl<'a, I: Iterator<Item = Event<'a>>> QuoteTransforms<'a, I> { | ||
pub fn new(parent: I) -> Self { | ||
Self { | ||
parent, | ||
event_queue: vec![], | ||
} | ||
} | ||
} | ||
|
||
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for QuoteTransforms<'a, I> { | ||
type Item = Event<'a>; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
if let Some(event) = self.event_queue.pop() { | ||
return Some(event); | ||
} | ||
|
||
let author = match self.parent.next()? { | ||
Event::Start(Container::Blockquote, attrs) => { | ||
if let Some(author) = attrs.get("author") { | ||
author.to_string() | ||
} else { | ||
return Some(Event::Start(Container::Blockquote, attrs)); | ||
} | ||
} | ||
other => return Some(other), | ||
}; | ||
|
||
let mut events = Vec::new(); | ||
loop { | ||
match self.parent.next()? { | ||
// Yeah, don't support nesting for now. | ||
Event::End(Container::Blockquote) => { | ||
break; | ||
} | ||
other => events.push(other), | ||
} | ||
} | ||
|
||
let html = Container::RawBlock { format: "html" }; | ||
self.event_queue.push(Event::End(Container::Blockquote)); | ||
self.event_queue.push(Event::End(html.clone())); | ||
self.event_queue.push(Event::Str( | ||
format!( | ||
r#"<footer><span class="author">{}</span></footer>"#, | ||
html_escape::encode_text(&author), | ||
) | ||
.into(), | ||
)); | ||
self.event_queue.push(Event::Start(html, Attributes::new())); | ||
for x in events.into_iter().rev() { | ||
self.event_queue.push(x); | ||
} | ||
Some(Event::Start(Container::Blockquote, Attributes::new())) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use eyre::Result; | ||
use jotdown::{html, Parser, Render}; | ||
|
||
fn convert(s: &str) -> Result<String> { | ||
let parser = Parser::new(s); | ||
let transformed = QuoteTransforms::new(parser); | ||
let mut body = String::new(); | ||
html::Renderer::default().push(transformed, &mut body)?; | ||
Ok(body) | ||
} | ||
|
||
#[test] | ||
fn test_parse_notice() -> Result<()> { | ||
let s = "::: notice | ||
Text here | ||
:::"; | ||
assert_eq!( | ||
convert(s)?, | ||
r"<aside> | ||
<p>Text here</p> | ||
</aside> | ||
" | ||
); | ||
|
||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn test_quote_src() -> Result<()> { | ||
let s = r#" | ||
{author="John > Jane"} | ||
> Text here | ||
"#; | ||
assert_eq!( | ||
convert(s)?, | ||
r#" | ||
<blockquote> | ||
<p>Text here</p> | ||
<footer><span class="author">John > Jane</span></footer> | ||
</blockquote> | ||
"# | ||
); | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.