From cea6896788a42c0ea40f99deb4b5987d6741e360 Mon Sep 17 00:00:00 2001 From: Emile Date: Fri, 16 Aug 2024 23:33:53 +0200 Subject: big dump, forgot to commit... --- nix/pkgs/vokobe/src/main.rs | 922 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 922 insertions(+) create mode 100644 nix/pkgs/vokobe/src/main.rs (limited to 'nix/pkgs/vokobe/src/main.rs') diff --git a/nix/pkgs/vokobe/src/main.rs b/nix/pkgs/vokobe/src/main.rs new file mode 100644 index 0000000..ab26457 --- /dev/null +++ b/nix/pkgs/vokobe/src/main.rs @@ -0,0 +1,922 @@ +/* +pull the std into scope and inline it so that we get documentation for it, +even when running offline +*/ +#[doc(inline)] +pub use std; + +use std::path::{Path, PathBuf}; +use std::io::{self, Read, Write, BufRead, BufReader}; +use std::fs::{self, File}; +use std::time; +use std::collections::HashMap; + +use structopt::StructOpt; +use regex::Regex; + +#[derive(Debug, StructOpt)] +#[structopt(name = "vokobe", about = "A static site generator")] +struct Opt { + /// Input path + #[structopt(parse(from_os_str))] + input_path: PathBuf, + + /// Output path + #[structopt(parse(from_os_str))] + output_path: PathBuf, + + /// Site name (e.g. emile.space) + site_name: String, + + /// Activate sending analytics to stats.emile.space + // -a and --analytics will be generated + // analytics are sent to stats.emile.space + #[structopt(short, long)] + analytics: bool, +} + +fn main() -> std::io::Result<()> { + + let mut internal_links: HashMap> = HashMap::new(); + + let opt = Opt::from_args(); + + let in_path = opt.input_path; + let output_path = opt.output_path; + + // read the style + let style_path = Path::new(&in_path).join("style.css"); + let mut style_file = File::open(style_path) + .expect("could not open style file"); + let mut style = String::new(); + style_file.read_to_string(&mut style) + .expect("could not read style file to string"); + + // read all dirs in the input path + let pathes = recursive_read_dir(&in_path, false)?; + + // pass 1: store the backlinks + + for path in &pathes { + if path.ends_with("README.md") { + // open the file and read it as a string + let mut readme_file = File::open(path)?; + let mut readme = String::new(); + readme_file.read_to_string(&mut readme)?; + + let internal_links_in_file + = parse_internal_links(readme.as_str()); + + for link in internal_links_in_file { + + internal_links.entry(link).or_insert_with(Vec::new).push(path.to_string_lossy().into_owned()) + } + } + } + + + // for each markdown_file in markdown_files { + // let internal_links_in_file = parse_internal_links(markdown_file); + // internal_links.insert(markdown_file, internal_links_in_file); + // } + + // pass 2: create the html + + println!("Got {} files", pathes.len()); + let mut readme_counter = 0; + + for path in pathes { + let stripped_path = path.strip_prefix(&in_path) + .expect(format!( + "could not strip the in_path prefix: {:?}", in_path).as_str()); + + // copy images and other files to the output folder + if path.is_file() { + + // define the source and destination + let src = Path::new(&in_path).join(stripped_path); + let dst = Path::new(&output_path).join(stripped_path); + + // define the destination folder (the dst path without the file) and create it + let mut dst_folder = dst.clone(); + dst_folder.pop(); // remove the file itself from the path + fs::create_dir_all(dst_folder)?; + + // copy the file to the destination + std::fs::copy(src, dst.as_path())?; + } + + if stripped_path.ends_with("README.md") { + readme_counter += 1; + + // define the "raw" path (no infile prefix, no file) + let mut ancestors = stripped_path.ancestors(); + ancestors.next(); + + let raw_path = ancestors.next() + .expect("could not extract next ancestor"); + + // out + rawpath + let index_path = output_path.join(raw_path); + + // (out + rawpath) + "index.html" + let index_file = index_path.join("index.html"); + + // - create the dir for the index.html as well as the index.html + // itself + fs::create_dir_all(index_path)?; + let mut file = File::create(&index_file)?; + + // this is the main block calling all other smaller functions. The + // whole output is compsed here + write_header(&mut file, &opt.site_name, &style)?; + write_body_start(&mut file, &opt.site_name)?; + write_nav(&mut file, in_path.as_path(), raw_path, opt.analytics)?; + write_same_level(&mut file, in_path.as_path(), raw_path)?; + write_readme_content(&mut file, in_path.as_path(), raw_path)?; + write_footer(&mut file, raw_path, &internal_links)?; + + file.write_all("".as_bytes())?; + } + } + + println!("Got {readme_counter} README.md files"); + + Ok(()) +} + +fn parse_internal_links(markdown_file: &str) -> Vec { + // Define a regular expression to match markdown-style links + let link_regex = Regex::new(r"\[([^\]]+)\]\(([^)]+)\)").unwrap(); + + // Initialize a vector to store internal links found in the markdown file + let mut internal_links = Vec::new(); + + // Iterate over each match of the regular expression in the markdown content + for capture in link_regex.captures_iter(&markdown_file) { + // Extract the link text and URL from the capture groups + // let link_text = &capture[1]; + let mut link_url = &capture[2]; + + // Check if the link is an internal link (e.g., relative URL) + // You can customize this condition based on your site's URL structure + if link_url.starts_with('/') || link_url.starts_with("../") { + if link_url.ends_with('/') { + link_url = link_url.trim_end_matches('/'); + } + internal_links.push(link_url.to_string()); + } + } + + internal_links +} + +/// Write the html header including the style file +/// TODO: Don't add the style file into each compiled html output, as the +/// style can be included allowing the user to cache the style file in their +/// browser. +fn write_header(file: &mut File, site_name: &String, style: &String) -> std::io::Result<()>{ + + // write the header including the style file + file.write_all(format!(r#" + + + + + + {} + + + + "#, site_name, style).as_bytes())?; + + Ok(()) +} + +/// write the start of the html body tag and the header linking back to the +/// site itself. +fn write_body_start(file: &mut File, site_name: &String) -> std::io::Result<()>{ + file.write_all(format!(r#" + +
+ {} +
"#, site_name).as_bytes())?; + + Ok(()) +} + +/// Write the navigation section to the given file +fn write_nav(file: &mut File, in_path: &Path, raw_path: &Path, analytics: bool) + -> std::io::Result<()> { + + if analytics == true { + /* + file.write_all(format!(r#" + +