extern crate getopts;
extern crate std;
extern crate term;
macro_rules! foreach_cmd {
($mac:ident) => {
$mac!(template);
$mac!(fetch);
$mac!(status);
}
}
macro_rules! declare_mod {
($name:ident) => { pub mod $name; }
}
foreach_cmd!(declare_mod);
use std::error::Error as FmtError;
use self::Error::*;
use subprocess;
use compiler;
use fetcher;
pub trait Errno {
fn errno(&self) -> Option<i32>;
}
#[derive(Debug)]
pub enum Error {
CmdGetoptsError(getopts::Fail),
CmdInvalid,
CmdIOError(std::io::Error),
CompileError(compiler::Error),
FetchError(fetcher::Error),
NoSubcomponentFile,
TermError(term::Error),
ProcessError(subprocess::Error),
InvalidComponentName,
InvalidHookName,
}
pub fn get_commands_list() -> Vec<&'static str> {
let mut list = Vec::new();
macro_rules! register_cmd {
($name:ident) => {
list.push(stringify!($name))
}
}
foreach_cmd!(register_cmd);
list
}
pub trait Cmd {
fn help(&self, opts: &getopts::Options);
fn getopts_set(&self, opts: &mut getopts::Options);
fn getopts_matches_use(&mut self, matches: &getopts::Matches);
fn run(&self, parser_arg: &Option<compiler::parser::Parser>) -> Result<(), Error>;
}
impl Errno for Error {
fn errno(&self) -> Option<i32> {
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
match *self {
CmdInvalid => Some(127),
CmdIOError(ref err) => err.raw_os_error(),
CmdGetoptsError(_) => Some(1),
CompileError(_) => Some(1),
FetchError(_) => Some(1),
NoSubcomponentFile => Some(2),
TermError(_) => Some(10),
ProcessError(_) => Some(1),
InvalidComponentName => Some(1),
InvalidHookName => Some(1),
}
}
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
CmdIOError(error)
}
}
impl From<subprocess::Error> for Error {
fn from(error: subprocess::Error) -> Self {
ProcessError(error)
}
}
impl From<term::Error> for Error {
fn from(error: term::Error) -> Self {
TermError(error)
}
}
impl From<getopts::Fail> for Error {
fn from(error: getopts::Fail) -> Self {
CmdGetoptsError(error)
}
}
impl From<compiler::Error> for Error {
fn from(error: compiler::Error) -> Self {
CompileError(error)
}
}
impl From<fetcher::Error> for Error {
fn from(error: fetcher::Error) -> Self {
FetchError(error)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.description())
}
}
impl std::error::Error for Error {
fn description(&self) -> &str {
match *self {
CmdGetoptsError(ref err) => err.description(),
CmdIOError(ref err) => err.description(),
CmdInvalid => "Invalid command",
CompileError(ref err) => err.description(),
FetchError(ref err) => err.description(),
NoSubcomponentFile => "No subcomponent file was found",
TermError(ref err) => err.description(),
ProcessError(ref err) => err.description(),
InvalidComponentName => "Invalid component name",
InvalidHookName => "Invalid hook name",
}
}
}
fn command_get(name: &str) -> Option<Box<Cmd>>
{
macro_rules! match_cmd {
($name:ident) => {
if name == stringify!($name) {
return Some(Box::new($name::new()));
}
}
}
foreach_cmd!(match_cmd);
None
}
fn hook_for_all_components_get(hook_name: &str, parser: &compiler::parser::Parser) -> Option<Vec<Box<Cmd>>> {
let mut hooks: Vec<Box<Cmd>> = Vec::new();
for component in parser.components_get().iter() {
for hook in component.borrow().hooks_get().iter() {
if hook_name == hook.name_get() {
hooks.push(Box::new((*hook).clone()));
break;
}
}
}
if hooks.is_empty() {
None
} else {
Some(hooks)
}
}
fn hook_for_named_components_get(hook_name: &str,
components: &[String],
parser: &compiler::parser::Parser)
-> Result<Vec<Box<Cmd>>, Error>
{
let mut hooks: Vec<Box<Cmd>> = Vec::new();
for component_name in components.iter() {
let mut is_found = false;
for component in parser.components_get().iter() {
let comp = component.borrow();
if component_name == comp.id_get() {
is_found = true;
let mut hook_found = false;
for hook in comp.hooks_get().iter() {
if hook_name == hook.name_get() {
hook_found = true;
hooks.push(Box::new((*hook).clone()));
break;
}
}
if ! hook_found {
error!("Failed to find hook '{}' for component '{}'",
hook_name, component_name);
return Err(Error::InvalidHookName);
}
break;
}
}
if ! is_found {
error!("Failed to find component '{}'", component_name);
return Err(Error::InvalidComponentName);
}
}
Ok(hooks)
}
fn hook_commands_get(name: &str, components: &[String], parser_opt: &Option<compiler::parser::Parser>)
-> Result< Option<Vec<Box<Cmd>>>, Error >
{
if let Some(ref parser) = *parser_opt {
if components.is_empty() {
return Ok(hook_for_all_components_get(name, parser));
} else {
let ret = hook_for_named_components_get(name, components, parser);
return ret.map(Some);
}
}
Ok(None)
}
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
fn handle_command(args: &[String], cmd: &mut Box<Cmd>) -> Result<bool, Error> {
let mut opts = getopts::Options::new();
opts.optflag("h", "help", "Display this message");
cmd.getopts_set(&mut opts);
let matches = opts.parse(&args[1..])?;
if matches.opt_present("help") {
cmd.help(&opts);
return Ok(false);
}
cmd.getopts_matches_use(&matches);
Ok(true)
}
pub fn getopts(args: &[String], parser_opt: &Option<compiler::parser::Parser>) -> Result<Option<Vec<Box<Cmd>>>, Error> {
let cmd_name = args[0].as_str();
let components = &args[1..];
if let Some(mut cmd) = command_get(cmd_name) {
if handle_command(args, &mut cmd)? {
Ok(Some(vec![cmd]))
} else {
Ok(None)
}
} else if let Some(mut hooks) = hook_commands_get(cmd_name, components, parser_opt)? {
if handle_command(args, &mut hooks[0])? {
Ok(Some(hooks))
} else {
Ok(None)
}
} else {
error!("Command '{}' is not defined.", cmd_name);
Err(CmdInvalid)
}
}