#![feature(io_error_more)]
#![feature(file_create_new)]
pub mod result;
use std::path::{PathBuf, Path};
use crate::result::Result;
use regex::Regex;
use tempfile::NamedTempFile;
use std::io::{self, Write, BufRead};
use std::fs::{self, File};
use std::ffi::OsStr;
pub fn main(
path: &Path,
dir: bool,
with_test: bool,
add_to_super: bool,
super_main: bool,
public: bool
) -> Result {
if path.exists() {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists, "file already exists"
).into());
}
let mod_path;
if dir {
mod_path = make_mod_dir(path, with_test)?;
} else {
mod_path = make_mod_file(path, with_test)?;
}
if add_to_super {
crate::add_to_super(&mod_path, super_main, public)?;
}
Ok(())
}
pub fn make_mod_file(path: &Path, with_test: bool) -> Result<PathBuf> {
let name = match path.file_name() {
Some(p) => p,
None => return Err(io::Error::new(
io::ErrorKind::InvalidFilename, "module name could not be derived from path"
).into()),
};
let name = match name.to_str() {
Some(p) => p,
None => return Err(io::Error::new(
io::ErrorKind::InvalidFilename, "module name could not convert module name to string"
).into()),
};
let mod_path = path.with_extension("rs");
let mut file = File::create_new(&mod_path)?;
if with_test {
let path_str = match path.to_str() {
Some(p) => p,
None => return Err(io::Error::new(
io::ErrorKind::InvalidFilename, "path could not be converted to string"
).into()),
};
let test_path = format!("{}_test.rs", path_str);
File::create(test_path)?;
let content = file_template_with_test(name);
let content = content.into_bytes();
file.write(&content)?;
}
Ok(mod_path)
}
pub fn make_mod_dir(path: &Path, with_test: bool) -> Result<PathBuf> {
fs::create_dir(path)?;
let mod_path = path.join("mod");
make_mod_file(&mod_path, with_test)?;
Ok(path.to_path_buf())
}
pub fn add_to_super(path: &Path, super_main: bool, public: bool) -> Result {
let super_file = super_path(path, super_main)?;
let mod_name = match path.file_stem() {
Some(p) => p,
None => return Err(io::Error::new(
io::ErrorKind::InvalidFilename, "could not derive module name from path"
).into()),
};
add_module_to(&mod_name, &super_file, public)
}
fn super_path(path: &Path, super_main: bool) -> Result<PathBuf> {
let abs_path = path.canonicalize()?;
let parent = match abs_path.parent() {
Some(p) => p,
None => return Err(io::Error::new(
io::ErrorKind::InvalidFilename, "parent could not be found from path"
).into()),
};
let g_parent = match parent.parent() {
Some(p) => p,
None => return Err(io::Error::new(
io::ErrorKind::InvalidFilename, "grandparent could not be found from path"
).into()),
};
let cargo_file = g_parent.join("Cargo.toml");
let parent_is_root = cargo_file.exists();
let super_file: PathBuf;
if parent_is_root{
if super_main {
super_file = parent.join("main.rs");
} else {
let lib_file = parent.join("lib.rs");
if lib_file.exists() {
super_file = lib_file;
} else {
super_file = parent.join("main.rs");
}
}
} else {
super_file = parent.join("mod.rs");
}
if !super_file.exists() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput, "parent module does not exist"
).into());
}
Ok(super_file)
}
fn add_module_to(mod_name: &OsStr, path: &Path, public: bool) -> Result {
let mod_name = match mod_name.to_str() {
Some(p) => p,
None => return Err(io::Error::new(
io::ErrorKind::InvalidFilename, "invalid module name"
).into()),
};
let (
preamble_exists,
preamble_end,
header_comment_exists,
header_comment_end
) = file_info(path)?;
let insert;
if preamble_exists {
if preamble_end.is_none() {
insert = None;
} else {
insert = Some(preamble_end.unwrap() + 1);
}
} else if header_comment_exists {
if header_comment_end.is_none() {
insert = None;
} else {
insert = Some(header_comment_end.unwrap() + 1);
}
} else {
insert = Some(0);
}
insert_mod_at_line(&mod_name, insert, path, public)
}
fn file_info(path: &Path) -> Result<(bool, Option<usize>, bool, Option<usize>)> {
let file = File::open(path)?;
let re_use = Regex::new(r"^\s*use\s+")?;
let re_mod = Regex::new(r"^\s*(?:pub)?\s*mod")?;
let re_comment = Regex::new(r"^\s*//")?;
let lines = io::BufReader::new(file).lines();
let mut preamble_exists = false;
let mut header_comment_exists = false;
let mut content_start = false;
let mut body_start = false;
let mut preamble_end = None;
let mut header_comment_end = None;
for (l_num, line) in lines.enumerate() {
if let Err(err) = line {
return Err(err.into());
}
let line = line.unwrap();
if !content_start && line.trim().is_empty() {
continue;
}
content_start = true;
if !body_start {
let comment_line = re_comment.is_match(&line);
if comment_line {
if !header_comment_exists {
header_comment_exists = true;
}
continue;
}
else if !comment_line {
if header_comment_exists {
header_comment_end = Some(l_num - 1);
}
body_start = true;
}
}
let preamble_line = re_use.is_match(&line) || re_mod.is_match(&line);
match (preamble_line, preamble_exists) {
(true, false) => preamble_exists = true,
(false, true) => {
preamble_end = Some(l_num - 1);
break;
},
_ => {},
}
}
Ok((preamble_exists, preamble_end, header_comment_exists, header_comment_end))
}
fn insert_mod_at_line(mod_name: &str, insert: Option<usize>, path: &Path, public: bool) -> Result {
let mod_str = match public {
true => format!("pub mod {mod_name};"),
false => format!("mod {mod_name};"),
};
let mut tmp = NamedTempFile::new()?;
let file = File::open(path)?;
let md = file.metadata()?;
let lines = io::BufReader::new(file).lines();
for (l_num, line) in lines.enumerate() {
if let Err(err) = line {
return Err(err.into());
}
if insert == Some(l_num) {
writeln!(tmp, "{}", &mod_str)?;
}
let line = line.unwrap();
writeln!(tmp, "{}", &line)?;
}
if insert == None || md.len() == 0 {
writeln!(tmp, "{}", &mod_str)?;
}
fs::rename(tmp.path(), path)?;
Ok(())
}
fn file_template_with_test(name: &str) -> String {
format!(r#"
#[cfg(test)]
#[path = "./{}_test.rs"]
mod {}_test;
"#, name, name)
}
#[cfg(test)]
#[path = "lib_test.rs"]
mod lib_test;