#![warn(missing_docs)]
pub mod args;
pub mod config;
pub mod error;
pub mod parser;
pub mod utils;
use crate::{
args::{Crud, Opt, SubOpt},
config::get_config_path,
parser::{parse_toml, Entry},
utils::CommandParams,
};
use error::Error;
use std::{
fs::{self, File},
io::Write,
path::PathBuf,
process::Output,
vec,
};
use structopt::StructOpt;
use utils::{exec_command, get_current_dir};
fn get_config_path_and_str(config_path: Option<PathBuf>) -> Result<(PathBuf, String), Error> {
let config_path = match config_path {
Some(c) => Some(c),
None => get_config_path()?,
};
match config_path {
Some(e) => {
let config_str = match fs::read_to_string(&e) {
Err(a) => return Err(Error::NoFile(e, a)),
Ok(s) => s,
};
Ok((e, config_str))
}
None => Err(Error::NoConfigFile),
}
}
pub fn run() -> Result<(), Error> {
let opt = Opt::from_args();
let config_path = opt.config_path;
let debug = opt.debug;
if let Some(sub_cmd) = opt.sub_cmd {
match sub_cmd {
SubOpt::Init => {
let config_path = match config_path {
Some(c) => Some(c),
None => get_config_path()?,
};
match config_path {
Some(c) => return Err(Error::ConfigExist(c)),
None => {
let curr_dir = get_current_dir()?;
let new_p = curr_dir.join(".d.toml");
let mut f = match File::create(&new_p) {
Err(e) => return Err(Error::ConfigFileCreation(new_p, e)),
Ok(e) => e,
};
if let Err(e) = writeln!(f, "[\"{}\"]", curr_dir.display()) {
return Err(Error::ConfigFileWrite(new_p, e));
}
if let Err(e) = f.flush() {
return Err(Error::ConfigFileWrite(new_p, e));
}
}
};
}
SubOpt::Edit => {
let (config_path, _config_str) = get_config_path_and_str(config_path)?;
start_editor(config_path)?;
}
SubOpt::Crud(crud) => {
let (config_path, config_str) = get_config_path_and_str(config_path)?;
let pwd = get_current_dir()?;
let entries = parse_toml(&config_str)?;
let mut this_dir = vec![];
for entry in &entries {
let entry_path = config_path.join(entry.0).canonicalize().expect("asdf");
if pwd.starts_with(entry_path) {
this_dir.push(entry)
}
}
if this_dir.is_empty() {
return Err(Error::NoConfigForPath(pwd));
}
this_dir.sort_by(|a, b| b.0.cmp(a.0));
let (_path, _entry) = this_dir[0];
let mut new_entries = entries.clone();
let entry = new_entries.get_mut(_path).unwrap();
match crud {
Crud::List { name } => list_entries(entry, name)?,
Crud::Add { name, cmd } => {
if let Some(c) = entry.get(&name) {
return Err(Error::CmdStringExists(get_current_dir()?, c.to_string()));
}
entry.insert(name, cmd);
}
Crud::Delete { name } => {
if entry.get(&name).is_none() {
return Err(Error::NoCmdStringFound(get_current_dir()?, name));
}
entry.remove(&name);
}
Crud::Update { name, cmd } => {
if entry.get(&name).is_none() {
return Err(Error::NoCmdStringFound(get_current_dir()?, name));
}
entry.insert(name, cmd);
}
}
let new_config_str = match toml::to_string_pretty(&new_entries) {
Err(e) => return Err(Error::UnableToSerialize(e)),
Ok(s) => s,
};
if let Err(e) = fs::write(&config_path, new_config_str) {
return Err(Error::ConfigFileWrite(config_path, e));
}
}
SubOpt::Other(cmd) => {
let (config_path, config_str) = get_config_path_and_str(config_path)?;
let pwd = get_current_dir()?;
let entries = parse_toml(&config_str)?;
let mut this_dir = vec![];
for entry in &entries {
let entry_path = config_path.join(entry.0).canonicalize().expect("asdf");
if pwd.starts_with(entry_path) {
this_dir.push(entry)
}
}
if this_dir.is_empty() {
return Err(Error::NoConfigForPath(pwd));
}
this_dir.sort_by(|a, b| b.0.cmp(a.0));
let (_path, entry) = this_dir[0];
run_cmd(cmd, entry, debug, opt.no_op)?
}
}
}
Ok(())
}
fn run_cmd(cmd_list: Vec<String>, entry: &Entry, debug: bool, no_op: bool) -> Result<(), Error> {
let index_of_splitter = cmd_list.iter().position(|s| s == "--");
let cmd_params = match index_of_splitter {
Some(idx) => {
let cmd = &cmd_list[..idx - 1];
let mut cmd_params = cmd
.iter()
.map(|c| CommandParams {
cmd_str: c.to_string(),
params: vec![],
})
.collect::<Vec<CommandParams>>();
let arg_cmd = &cmd_list[idx - 1];
let args = &cmd_list[idx + 1..];
cmd_params.push(CommandParams {
cmd_str: arg_cmd.to_string(),
params: args.into(),
});
cmd_params
}
None => cmd_list
.iter()
.map(|c| CommandParams {
cmd_str: c.to_string(),
params: vec![],
})
.collect::<Vec<CommandParams>>(),
};
for mut cmd in cmd_params {
let cmd_str = get_cmd_str(entry, &cmd.cmd_str)?;
cmd.cmd_str = cmd_str.clone();
if debug {
println!("`{}`", cmd);
}
if no_op {
continue;
}
exec_command(cmd)?;
}
Ok(())
}
fn start_editor(config_path: PathBuf) -> Result<Output, Error> {
exec_command(CommandParams {
cmd_str: format!("$EDITOR {}", config_path.display()),
params: vec![],
})
}
fn list_entries(entry: &Entry, name: Option<String>) -> Result<(), Error> {
if let Some(name) = name {
match entry.get(&name) {
None => return Err(Error::NoCmdStringFound(get_current_dir()?, name)),
Some(cmd) => {
println!("{}: {}", name, cmd);
}
}
} else {
println!("{:#?}", entry);
}
Ok(())
}
fn get_cmd_str(entry: &Entry, cmd: &str) -> Result<String, Error> {
match entry.get(cmd) {
Some(s) => Ok(s.to_owned()),
None => Err(Error::NoCmdStringFound(get_current_dir()?, cmd.to_string())),
}
}