use exitcode;
use itertools::Itertools;
use std::{
collections::{HashMap, HashSet},
env,
fs::{File, OpenOptions},
io::{self, Write},
iter::FromIterator,
path::PathBuf,
};
use structopt::StructOpt;
enum FileMode {
Create,
Append,
Replace,
}
const GITIGNORE_FILES: &[(&str, (&str, &[u8]))] =
&include!(concat!(env!("OUT_DIR"), "/gitignore_data.rs"));
#[derive(StructOpt, Debug)]
#[structopt(name = "gib")]
struct Gib {
#[structopt(short, long)]
debug: bool,
#[structopt(short, long)]
show: bool,
#[structopt(short, long)]
append: bool,
#[structopt(short, long)]
replace: bool,
#[structopt(short, long)]
list: bool,
#[structopt(short, long, parse(from_os_str))]
output: Option<PathBuf>,
#[structopt(name = "TEMPLATE")]
templates: Vec<String>,
}
pub fn gib_cli() -> Result<(), i32> {
let gitignores: HashMap<&str, (&str, &[u8])> = GITIGNORE_FILES.iter().cloned().collect();
let opt = Gib::from_args();
if gitignores.is_empty() {
return error_exit(
"Templates unavailable. \
Please file a bug: \
https://github.com/DavSanchez/gib/issues/new",
exitcode::CONFIG,
);
}
if opt.list {
return output_list(gitignores);
}
let filtered_list: Vec<String> = opt
.templates
.iter()
.filter(|x| template_exists(gitignores.clone(), x))
.cloned()
.collect();
if filtered_list.is_empty() {
return error_exit("No valid template arguments provided.", exitcode::USAGE);
}
let mut out: Box<dyn Write> = Box::new(io::stdout());
if !opt.show {
let file_mode: FileMode;
let output_dir = match opt.output {
Some(path) => path,
None => env::current_dir().unwrap(),
};
if !output_dir.exists() || !output_dir.is_dir() {
return error_exit("Output directory does not exist.", exitcode::OSFILE);
} else if output_dir.join(".gitignore").exists() && !(opt.replace || opt.append) {
return error_exit(
".gitignore file already exists at this location.",
exitcode::CANTCREAT,
);
} else if opt.append {
file_mode = FileMode::Append;
} else if opt.replace {
file_mode = FileMode::Replace;
} else {
file_mode = FileMode::Create;
}
match open_gitignore_mode(output_dir, file_mode) {
Ok(file) => out = Box::new(file),
Err(e) => {
return error_exit(
&format!("Could not create or open file. {}", e),
exitcode::IOERR,
)
}
}
}
let template_input_set: HashSet<String> = HashSet::from_iter(filtered_list);
let mut writer_result: Result<_, _>;
for key in &template_input_set {
let contents = gitignores.get::<str>(key).unwrap();
writer_result = write_contents(&mut out, contents);
if let Err(_) = writer_result {
return error_exit("Could not write output. Aborting", exitcode::IOERR);
}
}
if let Err(e) = out.flush() {
return error_exit(
&format!("Could not flush the buffer. {}", e),
exitcode::IOERR,
);
}
if !opt.show {
println!("Created .gitignore file.");
}
Ok(())
}
fn template_exists(gitignores: HashMap<&str, (&str, &[u8])>, arg_template: &str) -> bool {
match gitignores.get::<str>(arg_template) {
Some(_) => true,
None => {
eprintln!("Unrecognized template {}.", arg_template);
false
}
}
}
fn output_list(gitignores: HashMap<&str, (&str, &[u8])>) -> Result<(), i32> {
for template_key in gitignores.keys().sorted() {
println!("{}", template_key);
}
Ok(())
}
fn open_gitignore_mode(path: PathBuf, mode: FileMode) -> Result<File, std::io::Error> {
let mut file_options = OpenOptions::new();
match mode {
FileMode::Create => file_options.write(true).create_new(true),
FileMode::Append => file_options.append(true),
FileMode::Replace => file_options.write(true),
};
file_options.open(path.join(".gitignore"))
}
fn write_contents(
mut writer: impl std::io::Write,
content: &(&str, &[u8]),
) -> Result<(), std::io::Error> {
writeln!(writer, "###############")?;
writeln!(writer, "# {}", content.0)?;
writeln!(writer, "###############")?;
writeln!(writer, "{}", String::from_utf8_lossy(content.1))?;
Ok(())
}
fn error_exit(error: &str, code: exitcode::ExitCode) -> Result<(), i32> {
eprintln!("Error: {}.", error);
Err(code)
}