logseq-export/src/main.rs

177 lines
4.6 KiB
Rust

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, "![]($1)").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, "![$1]($2)").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);
}
}
}