Run commands

I have some use cases which can be solved using running commands:

  • Get dimensions of the video using ffmpeg
  • Reencode video to smaller size using ffmpeg
  • Reencode gif to mp4 using ffmpeg
  • Generate svg math formulas using tex2svg

So, there should be two options:

  • Silently run command
  • Run command and get data from it

Features like with the resize_image:

  • Not to run commands based on input file hash (when it’s not changed) and command hash.
  • Get a unique name for an input file and command hash combination.

For safety reasons commands can be run only when a user writes command = true in its config.toml.

If the command is not found or it returns an error code, then this is considered as an error, and the site is not built.

It looks like plugins, but much simpler and safer. What do you think?

1 Like

Calls to external tools go against the idea of Zola ("Forget dependencies. Everything you need in one binary. ").
I think in most cases, it can be done as pre-steps in a Makefile or similar, except the math formulas which would be nice to have built-in.

1 Like

This can be already done without any features, but with an extra step (writing rust service).

This is done using load_data(url=...) function.

For example, how to generate svg formulas in .md files.

Step 0. Install tex2svg

sudo apt install nodejs npm
sudo npm install --global mathjax-node-cli

Commands are taken from here.

Step 1. Create Rust service

Cargo.toml:

[package]
name = "zola_ext"
version = "0.1.0"
edition = "2018"

[dependencies]
rocket = "0.5.0-rc.1"
base64 = "0.13.0"

main.rs

use rocket::*;
use std::hash::Hash;
use std::process::Command;

const PATH: &str = "/home/zorax/my/zola/static";

fn calculate_hash<T: Hash>(t: &T) -> u64 {
    use std::collections::hash_map::DefaultHasher;
    use std::hash::Hasher;

    let mut s = DefaultHasher::new();
    t.hash(&mut s);
    s.finish()
}

#[get("/tex2svg/<formula>")]
fn tex2svg(formula: &str) -> Option<String> {
    let formula = String::from_utf8(base64::decode(formula).ok()?).ok()?;
    let file = format!("formulas/h{}.svg", calculate_hash(&formula));
    let full_file = format!("{}/{}", PATH, file);
    if !std::path::Path::new(&full_file).exists() {
        let result = Command::new("tex2svg").args(&[&formula]).output().ok()?;
        if result.stdout.is_empty() {
            return None;
        }
        std::fs::write(&full_file, result.stdout).ok()?;
    }
    Some(format!("{{\"file\": \"{}\"}}", file))
}

#[launch]
fn rocket() -> Rocket<Build> {
    let config = Config {
        port: 1234,
        ..Config::debug_default()
    };
    rocket::custom(&config).mount("/", routes![tex2svg])
}

Modify PATH variable to /static directory of your zola site.

This code already does:

  • Use a hash of input to generate a file.
  • Don’t run the command when a file exists.

Notice that you can return information in json format to zola.

Also, base64 is just convenient to escape.

Unfortunately, if you return Result<String, BadRequest<String>> from tex2svg, the error will not be shown in zola, only that it is 403. So, you need to print an error by yourself in this app.

Step 2. Create shortcode

templates/shortcodes/formula.html:

{% set formula =  formula | base64_encode %}
{% set url = "http://127.0.0.1:1234/tex2svg/" ~ formula %}
{% set result = load_data(url=url, format="json") %}
<img src="{{"/" ~ result.file}}">

Step 3. Use

Write this in any your .md file:

{{ formula(formula="\sin^2{\theta} + \cos^2{\theta} = 1") }}

And this will just work.

1 Like