Welcome to this WikiBlog. All articles are listed here in descending creation time order.
ReverseSSHTunnelLast render: Fri, 22 Apr 2022 11:39:33 +0000
Code used to generate this site:
{ 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 ]; }; }); }
#! /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(()) }