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

DeterministicProgramming
ReverseSSHTunnel
WaylandCursor
GodIsAMetaphor
WhyIsThereAnythingAtAll
MutableAliasing
Decoupling
Cohesion
SystemBus
LocalActors
ServiceLocator
TrampData
WikiBlog

Last render: Mon, 08 Aug 2022 09:43:06 +0000

Code used to generate this site:

flake.nix

{
  description = "Development environment for WikiBlog";

  nixConfig.bash-prompt-suffix = "\[nix\] ";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };
      in
      {
        devShell = pkgs.mkShell {
          packages = with pkgs; [
            cargo
            entr
            gcc
            lld
            rust-script
          ];
        };
      });
}

rust script

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

use {
    chrono::{DateTime, 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},
        io::ErrorKind,
        path::PathBuf,
        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 date = DateTime::parse_from_rfc2822("Thu, 01 Jan 1970 00:00:00 +0000")?;

    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()?, date))
    }

    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, date) in &mut collected {
        let content = escape
            .replace_all(
                &relink.replace_all(&content, r#"$1<a href="$2">$2</a>"#),
                "@",
            )
            .to_string();

        let mut path = PathBuf::new();
        path.push("creation");
        path.push(&name);
        let time = match read_to_string(&path) {
            Ok(time) => time,
            Err(err) => {
                if err.kind() == ErrorKind::PermissionDenied {
                    Err(format!("Unable to read: {:?}", path))?;
                }
                let time =
                    Utc.timestamp_millis(
                        meta.created()?.duration_since(UNIX_EPOCH)?.as_millis() as i64
                    )
                    .to_rfc2822();
                write(&path, &time)?;
                time
            }
        };

        *date = DateTime::parse_from_rfc2822(time.trim_end())?;

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

    collected.sort_by_key(|x| Reverse(x.3));
    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:" }
                h2 { "flake.nix" }
                pre { (read_to_string("flake.nix")?) }
                h2 { "rust script" }
                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 != ".gitignore"
            && name != "flake.lock"
            && name != "flake.nix"
            && name != "generate"
            && name != "index"
            && name != "regenerate"
        {
            println!("Removing file: {}", name);
            remove_file(file.path())?;
        }
    }

    for file in read_dir("creation")? {
        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)
        {
            println!("Removing file: creation/{}", name);
            remove_file(file.path())?;
        }
    }

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