Welcome to this WikiBlog. All articles are listed here in descending creation time order.

MutableAliasing
Decoupling
Cohesion
SystemBus
LocalActors
ServiceLocator
TrampData
WikiBlog

Last render: Thu, 05 Aug 2021 09:22:15 +0000

Code used to generate this site:

#! /usr/bin/env rust-script
//! ```cargo
//! [dependencies]
//! chrono = "0.4"
//! maud = "0.22"
//! regex = "1.5"
//! ```

use {
    chrono::{TimeZone, Utc},
    maud::{html, PreEscaped, DOCTYPE},
    regex::Regex,
    std::{
        cmp::Reverse,
        collections::{HashMap, HashSet},
        error::Error,
        fs::{read_dir, read_to_string, remove_file, write},
        time::{Instant, UNIX_EPOCH},
    },
};

fn main() -> Result<(), Box<dyn Error>> {
    let before = Instant::now();
    let relink = Regex::new(r#"([^@]|^)@([[:alnum:]]+)"#).expect("unable to compile regex");
    let escape = Regex::new(r#"@@"#).expect("unable to compile regex");
    let mut backlinks: HashMap<String, HashSet<String>> = HashMap::new();
    let mut articles = HashSet::new();

    let mut collected = Vec::new();
    for file in read_dir("articles")? {
        let file = file?;
        let content = read_to_string(file.path())?;
        let name = file
            .file_name()
            .into_string()
            .map_err(|_| "Filename not convertible to string")?;
        for caps in relink.captures_iter(&content).map(|x| x[2].to_string()) {
            backlinks
                .entry(caps)
                .or_insert_with(Default::default)
                .insert(name.clone());
        }
        articles.insert(name.clone());

        collected.push((name, content, file.metadata()?))
    }

    fn head(title: &str) -> PreEscaped<String> {
        html! {
            (DOCTYPE)
            head {
                meta charset="UTF-8";
                link rel="icon" type="image/png" href="x/favicon.png";
                link rel="icon" type="image/png" href="x/favicon16.png";
                link rel="icon" type="image/png" href="x/favicon32.png";
                link rel="icon" type="image/png" href="x/favicon64.png";
                link rel="stylesheet" type="text/css" href="x/main.css";
                title { (title) }
            }
        }
    }

    for (name, content, meta) in &collected {
        let content = escape
            .replace_all(
                &relink.replace_all(&content, r#"$1<a href="$2">$2</a>"#),
                "@",
            )
            .to_string();

        write(name, html! {
            html {
                (head(name))
                body { div id="content" { (PreEscaped(content)) } hr; div id="footer" { 
                    p { "Created: " (Utc.timestamp_millis(meta.created()?.duration_since(UNIX_EPOCH)?.as_millis() as i64).to_rfc2822()) ", modified: " (Utc.timestamp_millis(meta.modified()?.duration_since(UNIX_EPOCH)?.as_millis() as i64).to_rfc2822())}
                    p { a href="index" { "Home" } } "Backlinks:" br; 
                    @for x in backlinks.get(name).unwrap_or(&Default::default()) {
                        a href=(x) { (x) } br;
                    }
                } }
            }
         }.into_string())?;
    }

    collected.sort_by_key(|x| Reverse(x.2.created().unwrap()));
    write("index", html! {
        html {
            (head(&read_to_string("x/title")?.replace("\n", "")))
            body {
                p { "Welcome to this WikiBlog. All articles are listed here in descending creation time order." }
                @for x in collected { a href=(x.0) { (x.0) } br; }
                hr;
                p { "Last render: " (Utc::now().to_rfc2822()) }
                p { "Code used to generate this site:" }
                pre { (read_to_string("generate")?) }
            }
        }
    }.into_string())?;

    for k in backlinks.keys() {
        if !articles.contains(k) {
            println!("Missing: {}", k);
        }
    }

    for file in read_dir(".")? {
        let file = file?;
        let name = file
            .file_name()
            .into_string()
            .map_err(|_| "Filename not convertible to string")?;
        if file.metadata()?.is_file()
            && !articles.contains(&name)
            && name != "generate"
            && name != "index"
            && name != ".gitignore"
        {
            println!("Removing file: {}", name);
            remove_file(file.path())?;
        }
    }

    let after = Instant::now();
    println!("Done! (took {:?})", after.duration_since(before));
    Ok(())
}