< Home

Welcome To My Blog!

This is a very simple "blog" written in Rust. You can see the source code here.

While I call this a blog, I'm not sure that I really consider it one. I built it with the intention to host tutorials and other mostly informational things. I'll probably not post any sort of opinion based things, and if I do it will be very few.

This welcome page will go into detail about the parts of the tool and how I made it, but if you're not interested in that, there may be other posts of interest here! :D

The Technical Details

This page itself is statically generated using a tool that I wrote in Rust, called blog-rs. It reads markdown files and uses them to fill in a template using a template engine called Upon.

Markdown

For parsing markdown, I used the pulldown-cmark crate since it's used in mdbook and it seems pretty reliable. Unfortunately, it does not natively support maths expressions via KaTeX\KaTeX which are pretty important to me since I'd like to post about some maths things here.

To get around this, I did a dirty hack with just using a RegEx to replace each instance of maths in each markdown file with the generated maths expressions:

impl Replacer for MathReplacer {
    fn replace_append(&mut self, caps: &regex::Captures<'_>, dst: &mut String) {
        if caps.get(1).is_some() {
            dst.push_str(&caps[2]);
            return;
        }

        match katex::render_with_opts(&caps[2], &opts) {
            Ok(ref ml) => {
                dst.push_str(
                    &ml.replace("_", r"\_")
                        .replace("*", r"\*")
                        .replace("~", r"\~"),
                );
            }
            Err(err) => {
                eprintln!("Maths error: {:?}", err);
                dst.push_str(&format!(
                    r#"<span style="red">Maths Error: {:?}</span>"#,
                    err
                ));
            }
        }
    }
}

lazy_static::lazy_static! {
    static ref MATH_BLOCK: Regex = Regex::new(r#"(?s)(\\)?\$\$(.+?)\$\$"#).unwrap();
    static ref MATH_INLINE: Regex = Regex::new(r#"(?s)(\\)?\$(.+?)\$"#).unwrap();
}

fn preprocess_content(content: &str) -> String {
    let out = MATH_BLOCK.replace_all(content, MathReplacer::Block);
    let out = MATH_INLINE.replace_all(&out, MathReplacer::Inline);
    out.into()
}

This means that I'm loading the full markdown file, then running the replacements, then parsing/rendering the markdown, and finally writing back to the file. This is unfortunate and quite disgusting, but it works and I'm too lazy to figure out the correct way to do it. There has been a lot of discussion in pulldown-cmark and many unreviewed/unmerged PRs, such as this one, this one, and this one.

The currently possible way to do this using pulldown-cmark itself would be to look for math code blocks and render the content inside of them. This would work, but I don't like how the syntax differs from other common places, such as Pandoc and more.

Math block:

`​``math
E=mc^2
`​``

\$ syntax:

\$$
E=mc^2
\$$

which should both render to

E=mc2 E = mc^2

Syntax Highlighting

It's a similar issue when it comes to syntax highlighting. There is no crate that does great syntax highlighting. The syntect crate appears to be the best, and is used by a lot of crates, but it doesn't to a great job at highlighting things.

I've done a lot of web development in my past, and I'm still a really big fan of highlight.js. It does some pretty decent syntax highlighting and I'd love to see it get ported to Rust (maybe I'll do this at some point, we'll see). Since there is no crate of similar quality (that I could find), I decided to just use highlight.js on the site. This has the very unfortunate side effect that it needs to be loaded by the client and that the client needs to have JavaScript enabled.

This is okay to me, since it's basically the only JavaScript that runs on the page. I say 'basically' because there is also some simple code that will localise dates on the page. The complete extent of the JavaScript which runs on the page (excluding the highlight.js import):

hljs.highlightAll();

const dtf = Intl.DateTimeFormat(undefined, {
    day: 'numeric',
    month: 'long',
    year: 'numeric',
});

document.querySelectorAll('time')
    .forEach(elt => {
        if (elt.hasAttribute('datetime')) {
            elt.innerText = dtf.format(new Date(elt.getAttribute('datetime')))
        }
    });

I am considering adding some extra JS on the page to allow configuration of how shows, such as:

Compressing the HTML

I'm using an HTML minification crate aptly named minify-html. Why did I use that one? It's the first one that I saw and it is decently configurable.

Minification is not super necessary, since the files are already very small, given that they're just plain html/css/js with minimal room for compression, but I figured that it couldn't hurt, since it's static generation and only takes around 100ms to generate the pages anyways.

Future Improvements

There is a lot of room for improvement in this project, and I'd be happy to take suggestions or bug reports at the repo.

Conclusion

Let me know your thoughts on this site! If you have suggestions or anything, feel free to create an issue!