// 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}; use std::fs::{self, File}; use std::time; fn main() -> std::io::Result<()> { // the input and output pathes let in_path = Path::new("../emile.space/in").to_path_buf(); let out_path = Path::new("../emile.space/out").to_path_buf(); println!("inpath: {}", in_path.display()); println!("outpath: {}", out_path.display()); // read the style let mut style_file = File::open("./style.css")?; let mut style = String::new(); style_file.read_to_string(&mut style)?; // read all dirs in the input path let pathes = recursive_read_dir(&in_path, false)?; println!("---"); for path in pathes { println!("\n"); println!("[i] {}", path.as_os_str().to_str().unwrap()); 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(&out_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(); 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") { println!("\tstripped_path: {:?}", stripped_path); // 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"); println!("\traw_path: {:?}", raw_path); // out + rawpath let index_path = out_path.join(raw_path); println!("\tindex_path: {:?}", index_path); // (out + rawpath) + "index.html" let index_file = index_path.join("index.html"); println!("\tindex_file: {:?}", index_file); // - 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)?; write_header(&mut file, &style)?; write_body_start(&mut file)?; write_nav(&mut file, in_path.as_path(), raw_path)?; 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)?; file.write_all("".as_bytes())?; } } Ok(()) } /// Write the html header including the style file fn write_header(file: &mut File, style: &String) -> std::io::Result<()>{ // write the header including the style file file.write_all(format!(r#"
").as_bytes())?; // cheap markdown 2 html converter for line in readme.split('\n') { if line.starts_with("---") { file.write_all(format!(r##"
"##).as_bytes())?; } else if line.starts_with("###") { let heading = line.get(4..).unwrap(); let heading_sanitized = sanitize(heading.to_string()); file.write_all(format!(r##"
"##, a = heading_sanitized, b = heading).as_bytes())?; } else if line.starts_with("##") { let heading = line.get(3..).unwrap(); let heading_sanitized = sanitize(heading.to_string()); file.write_all(format!(r##"
"##, a = heading_sanitized, b = heading).as_bytes())?; } else if line.starts_with("#") { let heading = line.get(2..).unwrap(); let heading_sanitized = sanitize(heading.to_string()); file.write_all(format!(r##"
"##, a = heading_sanitized, b = heading).as_bytes())?; } else if line.starts_with("> ") { let line = line.replace("<", "<"); let line = line.get(2..).unwrap(); file.write_all(format!("
{}
\n", line).as_bytes())?; } else if line.starts_with(":::tree") { // get all dirs in the current dir recursively let tree_files_path = Path::new(in_path).join(raw_path); let mut tree_files = recursive_read_dir(&tree_files_path, true)?; // sort them, otherwise we'll get complete chaos tree_files.sort(); for path in tree_files { // strip the inpath prefix and raw_path prefix, as we don't need // them let path = path.strip_prefix(in_path) .expect("could not strip in_file prefix") .strip_prefix(raw_path) .expect("could not strip raw_path prefix"); // write the link and the entry name to the file let link = Path::new(raw_path).join(path); let name = path.file_name().unwrap().to_str().unwrap(); if name.starts_with(".") { continue } // count the amount of segments in the path and write spaces for // each let segments = path.iter().count(); for _ in 0..(segments-1) { file.write_all(r#" "#.as_bytes())?; } // write the linke and the entry name to the file let link = Path::new(raw_path).join(path); file.write_all( format!("{}\n", link.display(), name, ).as_bytes() )?; } } else if line.starts_with(":::toc") { for line in readme.split('\n') { if line.starts_with("###") { let line = line.get(4..).unwrap(); file.write_all( format!( r##" {} "##, sanitize(line.to_string()), line ).as_bytes() )?; } else if line.starts_with("##") { let line = line.get(3..).unwrap(); file.write_all( format!( r##" {} "##, sanitize(line.to_string()), line ).as_bytes() )?; } else if line.starts_with("#") { let line = line.get(2..).unwrap(); file.write_all( format!( r##"{} "##, sanitize(line.to_string()), line ).as_bytes() )?; } } } else { // for the case that nothing of the above matches, just write the // content into the html body as it is file.write_all(format!("{}\n", line).as_bytes())?; } } Ok(()) } fn write_footer(file: &mut File) -> std::io::Result<()> { file.write_all(format!(r#"
——— emile - {:?}"#, time::SystemTime::now() .duration_since(time::SystemTime::UNIX_EPOCH).unwrap() ).as_bytes())?; Ok(()) } /// sanitize the given string (to lower + space to hypen + keep only /// [a-zA-Z0-9]) fn sanitize(input: String) -> String { let input = input.replace(" ", "-"); input .chars() .filter(|c| c.is_ascii_alphanumeric() || c.eq(&'-')) .collect::() .to_lowercase() } /// Return a list of all files in the directory, recursively. fn recursive_read_dir(dir: &PathBuf, dir_only: bool) -> io::Result > { // return an empty vec if the given path is not a directory if dir.is_dir() == false { return Ok(vec![]); } // get all entries in the gitignore file, if it exists let gitignore_entries: Vec = gitignore_entries(&dir)?; // store the child pathes let mut entries: Vec = Vec::new(); // iterate over all items in the dir, pushing the dirs pathes to the dirs // vector for returning it 'outer: for entry in fs::read_dir(dir)? { let dir_entry = &entry?; let path = dir_entry.path(); // check if the current entry is part of the gitignore, if so, skip it for gitignore_entry in &gitignore_entries { if gitignore_entry.to_str() == Some("") { continue; } if path.ends_with(gitignore_entry) { println!("gitignore: gitignore_entry: {:?}", gitignore_entry); println!("gitignore: path: {:?}", path); continue 'outer; } } if dir_only == true { if path.is_dir() { entries.push(path.to_path_buf()); } } else { entries.push(path.to_path_buf()); } // recursively push all dirs from all children to the dirs vector let subdirs = recursive_read_dir(&path, dir_only)?; for subdir in subdirs { entries.push(subdir) } } // return the dirs, the ones from this folder and the ones from all child folders Ok(entries) } // try to open the gitignore file and read all entries from there. fn gitignore_entries(dir: &PathBuf) -> io::Result > { let gitignore_path = Path::new(&dir) .join(Path::new(".gitignore")); let mut entries: Vec = Vec::new(); if let Ok(gitignore) = File::open(&gitignore_path) { let reader = BufReader::new(gitignore); for line in reader.lines() { entries.push(PathBuf::from(line?)); } } Ok(entries) }