use clap::Parser; use regex::Regex; use std::io::Write; use std::{fs::File, path::Path}; const LIST_LINE_PATTERN: &str = r"^\s*-(?: |$)"; const IGNORE_LINE_PATTERN: &str = r"^\s*(?:collapsed|id|title)::"; fn replace_video(line: &str) -> String { let re = Regex::new(r"\{\{video (.+?)\}\}").unwrap(); re.replace_all(line, "").to_string() } fn remove_image_size(line: &str) -> String { let re = Regex::new(r"!\[(.*?)\][(](.+?)[)]\{:height\s*\d+,\s*:width\s*\d+\}").unwrap(); re.replace_all(line, "").to_string() } fn remove_bullets(line: &str) -> String { let re = Regex::new(LIST_LINE_PATTERN).unwrap(); re.replace(line, "").to_string() } fn dedent(line: &str, indent: usize) -> String { let re = Regex::new(&format!(r"^\s{{0,{}}}", indent * 2)).unwrap(); re.replace(line, "").to_string() } fn process_line( line: &str, is_list: bool, indent: usize, prev_indent: usize, is_code_block: &mut bool, ) -> String { let mut line = replace_video(line); line = remove_image_size(&line); line = remove_bullets(&line); if line.starts_with("```") { *is_code_block = !*is_code_block; } if *is_code_block { return dedent(&line, indent); } line = dedent(&line, 1); let is_quoted = line.starts_with(">"); if indent == 0 { if prev_indent == 0 && is_quoted { line = format!("{}{}", " ".repeat(indent), line); } else if prev_indent != 0 { line = format!("\n{}", line); } } else if is_list { line = format!("{}- {}", " ".repeat(indent), line); } else { line = format!("{}{}", " ".repeat(indent), line); } line } fn process_input(input: &str, writer: &mut std::io::BufWriter<File>) { let ignore_regex = Regex::new(IGNORE_LINE_PATTERN).unwrap(); let list_regex = Regex::new(LIST_LINE_PATTERN).unwrap(); let mut prev_indent = 0; let mut is_code_block = false; for line in input.lines() { if ignore_regex.is_match(line) { continue; } let mut indent = line .replace('\t', " ") .chars() .take_while(|&c| c == ' ') .count() / 2; let is_list = list_regex.is_match(line); if !is_list && indent > 0 { indent -= 1; } let processed_line = process_line(line, is_list, indent, prev_indent, &mut is_code_block); writer .write_fmt(format_args!("{}\n", processed_line)) .unwrap(); prev_indent = indent; } } #[derive(Parser, Debug)] #[command(version)] struct Args { #[arg(short, long)] input: String, #[arg(short, long)] output: String, } fn main() { let args = Args::parse(); if !Path::new(&args.input).join("logseq").exists() { eprintln!("The logseq directory does not exist."); std::process::exit(1); } let input_assets = Path::new(&args.input).join("assets"); let output_path = Path::new(&args.output); let output_assets = output_path.join("assets"); std::fs::create_dir_all(&output_assets).unwrap(); for entry in input_assets.read_dir().unwrap() { let entry = entry.unwrap(); let path = entry.path(); if path.is_file() { let output_path = output_assets.join(path.file_name().unwrap()); std::fs::copy(&path, &output_path).unwrap(); } } let input_journals = Path::new(&args.input).join("journals"); let input_pages = Path::new(&args.input).join("pages"); let input_journal_files = input_journals.read_dir().unwrap(); let input_pages_files = input_pages.read_dir().unwrap(); for entry in input_journal_files { let entry = entry.unwrap(); let path = entry.path(); if !path.is_file() { continue; } let name = path .file_name() .unwrap() .to_str() .unwrap() .replace("_", "-"); let output_file = File::create(output_path.join(name)).unwrap(); let mut writer = std::io::BufWriter::new(output_file); let input = std::fs::read_to_string(&path).unwrap(); process_input(&input, &mut writer); } for entry in input_pages_files { let entry = entry.unwrap(); let path = entry.path(); if path.is_file() { let input = std::fs::read_to_string(&path).unwrap(); let name = path.file_name().unwrap(); let output_file = File::create(output_path.join(name)).unwrap(); let mut writer = std::io::BufWriter::new(output_file); process_input(&input, &mut writer); } } }