| use std::collections::BTreeMap; |
| use std::env; |
| use std::fs::{File, create_dir, read_dir}; |
| use std::io; |
| use std::io::{Read, Write}; |
| use std::path::{Path, PathBuf}; |
| use std::process::exit; |
| |
| use lazy_static::lazy_static; |
| use regex::Regex; |
| |
| static PATTERNS: &[(&str, &str)] = &[ |
| (r"ch(\d\d)-\d\d-.*\.md", "chapter$1.md"), |
| (r"appendix-(\d\d).*\.md", "appendix.md"), |
| ]; |
| |
| lazy_static! { |
| static ref MATCHERS: Vec<(Regex, &'static str)> = { |
| PATTERNS |
| .iter() |
| .map(|&(expr, repl)| (Regex::new(expr).unwrap(), repl)) |
| .collect() |
| }; |
| } |
| |
| fn main() { |
| let args: Vec<String> = env::args().collect(); |
| |
| if args.len() < 3 { |
| println!("Usage: {} <src-dir> <target-dir>", args[0]); |
| exit(1); |
| } |
| |
| let source_dir = ensure_dir_exists(&args[1]).unwrap(); |
| let target_dir = ensure_dir_exists(&args[2]).unwrap(); |
| |
| let mut matched_files = match_files(source_dir, target_dir); |
| matched_files.sort(); |
| |
| for (target_path, source_paths) in group_by_target(matched_files) { |
| concat_files(source_paths, target_path).unwrap(); |
| } |
| } |
| |
| fn match_files( |
| source_dir: &Path, |
| target_dir: &Path, |
| ) -> Vec<(PathBuf, PathBuf)> { |
| read_dir(source_dir) |
| .expect("Unable to read source directory") |
| .filter_map(|maybe_entry| maybe_entry.ok()) |
| .filter_map(|entry| { |
| let source_filename = entry.file_name(); |
| let source_filename = |
| &source_filename.to_string_lossy().into_owned(); |
| for &(ref regex, replacement) in MATCHERS.iter() { |
| if regex.is_match(source_filename) { |
| let target_filename = |
| regex.replace_all(source_filename, replacement); |
| let source_path = entry.path(); |
| let mut target_path = PathBuf::from(&target_dir); |
| target_path.push(target_filename.to_string()); |
| return Some((source_path, target_path)); |
| } |
| } |
| None |
| }) |
| .collect() |
| } |
| |
| fn group_by_target( |
| matched_files: Vec<(PathBuf, PathBuf)>, |
| ) -> BTreeMap<PathBuf, Vec<PathBuf>> { |
| let mut grouped: BTreeMap<PathBuf, Vec<PathBuf>> = BTreeMap::new(); |
| for (source, target) in matched_files { |
| if let Some(source_paths) = grouped.get_mut(&target) { |
| source_paths.push(source); |
| continue; |
| } |
| let source_paths = vec![source]; |
| grouped.insert(target.clone(), source_paths); |
| } |
| grouped |
| } |
| |
| fn concat_files( |
| source_paths: Vec<PathBuf>, |
| target_path: PathBuf, |
| ) -> io::Result<()> { |
| println!("Concatenating into {}:", target_path.to_string_lossy()); |
| let mut target = File::create(target_path)?; |
| |
| write!( |
| target, |
| "\ |
| <!-- DO NOT EDIT THIS FILE. |
| |
| This file is periodically generated from the content in the `/src/` |
| directory, so all fixes need to be made in `/src/`. |
| --> |
| |
| [TOC] |
| " |
| )?; |
| |
| for path in source_paths { |
| println!(" {}", path.to_string_lossy()); |
| let mut source = File::open(path)?; |
| let mut contents: Vec<u8> = Vec::new(); |
| source.read_to_end(&mut contents)?; |
| |
| target.write_all(b"\n")?; |
| target.write_all(&contents)?; |
| target.write_all(b"\n")?; |
| } |
| Ok(()) |
| } |
| |
| fn ensure_dir_exists(dir_string: &str) -> io::Result<&Path> { |
| let path = Path::new(dir_string); |
| if !path.exists() { |
| create_dir(path)?; |
| } |
| Ok(path) |
| } |