#![warn(missing_docs)]
#![allow(clippy::bool_comparison)]
#![allow(clippy::cognitive_complexity)]
#![allow(clippy::match_bool)]
macro_rules! __ { ($($arg: tt)+) => {
format!("[{tag}][{module_path}-{line}] {fmt}", tag=crate::TAG, module_path=module_path!(), line=line!(), fmt=format!($($arg)+))
};}
mod template_type;
mod templates;
use {
std::{
borrow::Cow,
env,
io::{Error, ErrorKind},
path::Path,
process,
time::{SystemTime, UNIX_EPOCH},
},
dia_args::{
Args,
Result,
licenses::gnu_gpl,
},
dia_time::Time,
template_type::TemplateType,
zeros::keccak::{self, Hash, Keccak},
};
const TAG: &str = dia_args::TAG;
const CMD_HELP: &str = "help";
const CMD_HELP_DOCS: Cow<str> = Cow::Borrowed("Prints help and exits.");
const CMD_VERSION: &str = "version";
const CMD_VERSION_DOCS: Cow<str> = Cow::Borrowed("Prints version and exits.");
const CMD_LICENSES: &str = "licenses";
const CMD_LICENSES_DOCS: Cow<str> = Cow::Borrowed("Prints licenses and exits.");
const CMD_MAKE_TEMPLATE: &str = "make-template";
macro_rules! cmd_make_template_doc_template { () => { concat!(
"Makes template and prints it to stdout.\n\n",
"This command will:\n\n",
"- Generate some constants, such as NAME, CODE_NAME, ID(1), VERSION, RELEASE_DATE, TAG...\n",
"- Generate some functions in case your type is \"{}\".\n\n",
"(1) To generate crate ID, the program will collect these data:\n\n",
"- Current time.\n",
"- Some bytes from /dev/urandom (on Unix).\n",
"- Current directory path and some of its sub files' paths.\n",
"- Temporary directory path and some of its sub files' paths.\n",
"- Environment variables.\n",
"- ...\n\n",
"Then those data will be fed to an SHA3-512 hasher. Final result will be the hash.\n",
)}}
const OPTION_TEMPLATE_TYPE: &[&str] = &["--type"];
const OPTION_TEMPLATE_TYPE_DOCS: Cow<str> = Cow::Borrowed("Template type.");
const OPTION_TEMPLATE_TYPE_VALUES: &[TemplateType] = &[TemplateType::LIB, TemplateType::BIN];
const OPTION_TEMPLATE_TYPE_DEFAULT: TemplateType = TemplateType::LIB;
const OPTION_NAME: &[&str] = &["--name"];
const OPTION_NAME_DOCS: Cow<str> = Cow::Borrowed("Crate name.");
const OPTION_CODE_NAME: &[&str] = &["--code-name"];
macro_rules! option_code_name_doc_template { () => { concat!(
"Crate code name.\n\n",
"If not provided, it will be made from {option_name:?} option, by lowering its content and replacing white spaces with hyphens (\"-\").",
)}}
fn main() {
if let Err(err) = run() {
eprintln!("{}", err);
process::exit(1);
}
}
fn run() -> Result<()> {
let args = dia_args::parse()?;
match args.cmd() {
Some(CMD_HELP) => {
ensure_args_are_empty(args.into_sub_cmd().1)?;
print_help()
},
Some(CMD_VERSION) => {
ensure_args_are_empty(args.into_sub_cmd().1)?;
print_version()
},
Some(CMD_MAKE_TEMPLATE) => make_template(args.into_sub_cmd().1),
Some(CMD_LICENSES) => print_licenses(args.into_sub_cmd().1),
Some(other) => Err(Error::new(ErrorKind::InvalidInput, format!("Unknown command: {:?}", other))),
None => Err(Error::new(ErrorKind::Other, "Not implemented yet")),
}
}
fn ensure_args_are_empty<A>(args: A) -> Result<()> where A: AsRef<Args> {
let args = args.as_ref();
if args.is_empty() {
Ok(())
} else {
Err(Error::new(ErrorKind::InvalidInput, format!("Unknown arguments: {:?}", args)))
}
}
fn make_version_string<'a>() -> Cow<'a, str> {
format!("{} {} {:?}", dia_args::NAME, dia_args::VERSION, dia_args::RELEASE_DATE).into()
}
fn print_version() -> Result<()> {
println!("{}", make_version_string());
Ok(())
}
fn print_licenses(args: Args) -> Result<()> {
ensure_args_are_empty(args)?;
dia_args::lock_write_out(format!(
"{copying}\n\n================================================================\n\n{copying_lesser}\n",
copying=include_str!("../../../COPYING"),
copying_lesser=include_str!("../../../COPYING.LESSER"),
));
Ok(())
}
fn print_help() -> Result<()> {
use dia_args::docs::{Cmd, Docs, NO_VALUES, Option, Project};
let commands = Some(dia_args::make_cmds![
Cmd::new(CMD_HELP, CMD_HELP_DOCS, None),
Cmd::new(CMD_VERSION, CMD_VERSION_DOCS, None),
Cmd::new(CMD_LICENSES, CMD_LICENSES_DOCS, None),
Cmd::new(
CMD_MAKE_TEMPLATE, format!(cmd_make_template_doc_template!(), TemplateType::BIN).into(),
Some(dia_args::make_options![
Option::new(
OPTION_TEMPLATE_TYPE, false, OPTION_TEMPLATE_TYPE_VALUES, Some(OPTION_TEMPLATE_TYPE_DEFAULT), OPTION_TEMPLATE_TYPE_DOCS,
),
Option::new(OPTION_NAME, true, NO_VALUES, None, OPTION_NAME_DOCS),
Option::new(
OPTION_CODE_NAME, false, NO_VALUES, None, format!(option_code_name_doc_template!(), option_name=OPTION_NAME).into(),
),
]),
),
]);
let project = Some(Project::new(
"https://bitbucket.org/de-marco/dia-args",
"GNU Lesser General Public License, either version 3, or (at your option) any later version",
None,
));
let mut docs = Docs::new(
make_version_string(),
gnu_gpl::make_short_terminal_notice(include_str!("templates.rs"), dia_args::NAME, CMD_LICENSES)?.into(),
);
docs.commands = commands;
docs.project = project;
docs.print()?;
Ok(())
}
fn make_template(mut args: Args) -> Result<()> {
use templates::{
CRATE_CODE_NAME_PLACEHOLDER, CRATE_ID_PLACEHOLDER, CRATE_NAME_PLACEHOLDER, DAY_PLACEHOLDER, MONTH_PLACEHOLDER, TAG_ID_PLACEHOLDER,
YEAR_PLACEHOLDER, USES_PLACEHOLDER,
};
let r#type = args.take(OPTION_TEMPLATE_TYPE)?.unwrap_or(OPTION_TEMPLATE_TYPE_DEFAULT);
let name = args.take::<String>(OPTION_NAME)?.ok_or_else(|| Error::new(ErrorKind::InvalidInput, format!("Missing {:?}", OPTION_NAME)))?;
if name.is_empty() {
return Err(Error::new(ErrorKind::InvalidInput, format!("{:?} is empty", OPTION_NAME)));
}
let code_name = match args.take::<String>(OPTION_CODE_NAME)? {
Some(code_name) => code_name.trim().to_string(),
None => name.to_lowercase().trim().split_whitespace().collect::<Vec<_>>().join(concat!('-')),
};
if code_name.is_empty() {
return Err(Error::new(ErrorKind::InvalidInput, format!("{:?} is empty", OPTION_CODE_NAME)));
}
ensure_args_are_empty(args)?;
let (id, prefix) = generate_crate_id()?;
let time = Time::make_local()?;
println!("{}", templates::header());
println!(
"{}",
templates::identifiers()
.replace(CRATE_NAME_PLACEHOLDER, &name).replace(CRATE_CODE_NAME_PLACEHOLDER, &code_name)
.replace(CRATE_ID_PLACEHOLDER, &id).replace(TAG_ID_PLACEHOLDER, &prefix)
.replace(YEAR_PLACEHOLDER, &time.year().to_string())
.replace(MONTH_PLACEHOLDER, &time.month().order().to_string())
.replace(DAY_PLACEHOLDER, &time.day().to_string())
.replace(
USES_PLACEHOLDER,
&if r#type == TemplateType::BIN { Cow::Owned(format!("\n{}", templates::uses())) } else { Cow::Borrowed(concat!()) },
)
);
if r#type == TemplateType::BIN {
println!("{}", templates::footer());
}
Ok(())
}
fn generate_crate_id() -> Result<(String, String)> {
const PREFIX_LEN: usize = 8;
macro_rules! tab { () => { concat!(' ', ' ', ' ', ' ') }}
let hex = {
let mut keccak = Hash::Sha3_512.new_keccak();
let time = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(d) => d,
Err(e) => e.duration(),
};
for n in &[time.as_secs() as u128, time.as_millis(), time.as_micros(), time.as_nanos()] {
keccak.update(&n.to_ne_bytes());
}
#[cfg(unix)]
keccak.update(keccak.hash().hash_urandom(512)?);
for path in &[env::current_dir(), env::current_exe(), Ok(env::temp_dir())] {
if let Ok(path) = path {
keccak.update(path.display().to_string());
if path.is_dir() {
collect_hashes_from_file_paths(path, &mut keccak);
}
}
}
for (k, v) in env::vars_os() {
for s in [k, v] {
#[cfg(unix)] {
use std::os::unix::prelude::OsStringExt;
keccak.update(s.into_vec());
}
#[cfg(not(unix))]
if let Some(s) = s.to_str() {
keccak.update(s);
} else {
keccak.update(format!("{:?}", s));
}
}
}
keccak.finish_as_hex()
};
let hex_len = hex.len();
if hex_len > PREFIX_LEN && hex_len / 2 == 64 {
let prefix = hex[..PREFIX_LEN].to_string();
let mut id = String::with_capacity(hex_len.saturating_mul(2));
id += concat!("concat!(\n", tab!(), '"');
for (i, c) in hex.chars().enumerate() {
id.push(c);
let k = i + 1;
if k != hex_len && k % 8 == 0 {
id.push('-');
}
if k % 64 == 0 {
id += concat!('"', ',', '\n');
id += if k < hex_len {
concat!(tab!(), '"')
} else {
concat!(')')
};
}
}
Ok((id, prefix))
} else {
Err(Error::new(ErrorKind::Other, __!("Internal error")))
}
}
fn collect_hashes_from_file_paths<P>(dir: P, keccak: &mut Keccak<{keccak::Type::Function.id()}>) where P: AsRef<Path> {
const MAX_FILES: usize = 1_000;
let file_discovery = match dia_files::find_files(dir, true, dia_files::filter::AllPaths::new()) {
Ok(file_discovery) => file_discovery,
Err(err) => {
keccak.update(err.to_string());
return;
},
};
for (i, file) in file_discovery.enumerate() {
match file {
Ok(file) => {
#[cfg(unix)] {
use std::os::unix::prelude::OsStrExt;
keccak.update(file.as_os_str().as_bytes());
}
#[cfg(not(unix))]
keccak.update(format!("{:?}", file));
},
Err(err) => drop(keccak.update(err.to_string())),
};
if i >= MAX_FILES {
return;
}
}
}