use chrono::{
DateTime, Utc
};
use inflector::{
cases::{
snakecase::{to_snake_case}
},
};
use proc_macro2::{
TokenStream,
Span,
};
use quote::{
quote
};
use std::{
env,
fs::{
DirBuilder, File,
},
io::{
prelude::*,
},
path::{
PathBuf
},
process::{
Command,
},
string::{
ToString,
},
};
use syn::{
Ident,
};
pub static TIMESTAMP_FORMAT: &str = "out_%Y_%m%d_%H%S";
pub fn procout(
code_block: &TokenStream,
module_ident: Option<Ident>,
output_path: Option<&str>,
) {
if cfg!(any(feature = "procout", feature="procout_messy")) {
let mut target_path: PathBuf = output_path.map_or_else(
|| {
let mut local_path = env::current_dir().expect("Must identify current dir");
local_path.push("tests");
local_path
},
|path_str| {
PathBuf::from(path_str)
}
);
DirBuilder::new()
.recursive(true)
.create(target_path.clone())
.expect("Creates macro output dir");
let module_ident: Ident = module_ident.unwrap_or_else(
|| {
let now: DateTime<Utc> = Utc::now();
let timestamp: String = format!("{}", now.format(&TIMESTAMP_FORMAT));
Ident::new(×tamp, Span::mixed_site())
}
);
let file_name = format!("{}.rs", to_snake_case(&module_ident.to_string()));
target_path.push(file_name);
let target_path_str = target_path.to_str().expect("Must create string from target path");
let mut target_file = File::create(target_path.clone())
.expect("Creates macro output file");
target_file.write_all(&format!(
"{}",
quote!{
#code_block
#[test]
#[allow(unused)]
fn macro_test() {
use #module_ident::*;
}
}
).as_bytes())
.expect("Writes macro to file as test");
if cfg!(feature = "notification") {
std::println!("Wrote macro to `{}` ", target_path_str);
}
if cfg!(feature = "formatted") {
match Command::new("rustfmt").arg(target_path_str).output() {
Ok(output) => std::println!("rustfmt status: {}", output.status),
Err(err) => std::println!("Could not rustfmt \"{}\":\n {:#?}", target_path_str, err),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_procout() {
let target_module = "test_procout_module";
let module_ident = Ident::new(&target_module, Span::mixed_site());
let code_block: proc_macro2::TokenStream = quote!{
pub mod #module_ident {
const CUSS: &str = "SPIT";
}
};
procout(&code_block, Some(module_ident), None);
procout(&code_block, None, Some("tests/blah"));
let target_output = format!(
"#![allow(unused_imports)]\
\n#![allow(dead_code)]\
\npub mod {} {{\
\n const CUSS: &str = \"SPIT\";\n\
}}\n\
#[test]\n\
fn macro_test() {{\
\n use {}::*;\n\
}}\n",
target_module,
target_module,
);
let mut target_path: PathBuf = env::current_dir().expect("Must identify current dir");
target_path.push("tests");
target_path.push(format!("{}.rs", target_module));
let mut target_file = File::open(target_path).expect("Must open target file");
let mut contents = String::new();
target_file.read_to_string(&mut contents).expect("Test must read file to string");
assert_eq!(
contents,
target_output,
"Must write target output to file in tests directory corresponding to module Ident"
);
}
}