subcomponent 0.1.0

A components orchestrator
/*
 * Copyright (c) 2016-2017 Jean Guyomarc'h
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#[macro_use] extern crate log;
extern crate getopts;
extern crate crypto;

mod logger;
mod utils;
mod common;
mod config;
mod cmd;
mod hook;
#[macro_use] mod compiler;
mod fetcher;
mod unpacker;
mod subprocess;

use cmd::Errno;

const PROLOG: &'static str = "Usage:
    subcomponent [options]
    subcomponent <command> [options]

Commands:
    fetch               Fetch locally the requested components
    status              Tell the status of the components
    template            Create a subcomponent template
";

fn usage(opts: &getopts::Options, exit: i32) -> ! {
    println!("{}", opts.usage(PROLOG));
    std::process::exit(exit);
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let mut opts = getopts::Options::new();
    let mut loglevel = log::LogLevelFilter::Info;

    /* When we hit a positional argument, we will stop interpreting what
     * follows as options. These will be options to the command that is
     * identified by the hit positional argument */
    opts.parsing_style(getopts::ParsingStyle::StopAtFirstFree);

    /* Description of the expected options */
    opts.optflag("h", "help", "Display this message");
    opts.optflag("", "debug", "Increase log verbosity for debug");
    opts.optopt("C", "", "Execute subcomponent at path PATH", "PATH");
    opts.optopt("f", "file", "Use FILE instead of the default one", "FILE");

    /* Getopt */
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m },
        Err(err) => {
            logger::init(log::LogLevelFilter::Error);
            error!("Options parsing failed: {}", err);
            usage(&opts, 1);
        }
    };

    /* Always start with the --debug option, as it will set the logger */
    if matches.opt_present("debug") {
        loglevel = log::LogLevelFilter::Trace;
    }
    logger::init(loglevel);

    /* --help: print usage and exit */
    if matches.opt_present("help") {
        usage(&opts, 0);
    }

    /* If no command are provided, this is an error */
    if matches.free.is_empty() {
        error!("A command name is required");
        std::process::exit(1);
    }

    /*
     * If the '-C' option is present, change directory to the specified
     * path. If we do not succeed, we will stop right here.
     */
    if let Some(path_opt) = matches.opt_str("C") {
       let path = std::path::Path::new(&path_opt);
       if let Err(err) = std::env::set_current_dir(&path) {
          error!("Failed to change directory to \"{}\". {}",
                 path.display(), err);
          let errcode = match err.raw_os_error() {
             Some(code) => code,
             None => 127
          };
          std::process::exit(errcode);
       } else {
          debug!("Working directory is now {:?}", path);
       }
    }

    /* Option -f: use a different file than the default one */
    let mut entry = std::path::PathBuf::new();
    match matches.opt_str("f") {
        None => {
            entry.push(common::sub_dir_get());
            entry.push(common::sub_file_get());
        },
        Some(file_opt) => {
            entry.push(file_opt);
        }
    }

    /*
     * Compile the subcomponent's description file.
     * If no file is available, the parser's result will be None. Otherwise,
     * it will be a valid Some().
     */
    let parser_opt = if entry.exists() {
       match compiler::compile(&entry) {
          Ok(parser) => Some(parser),
          Err(err) => {
             error!("Compilation of {} failed: {}", entry.display(), err);
             std::process::exit(127);
          }
       }
    } else {
       None
    };


    /* Run sub-getopts for the command */
    let cmds_opt = match cmd::getopts(&matches.free, &parser_opt) {
        Ok(c) => c,
        Err(err) => {
            error!("Command failed: {}", err);
            std::process::exit(127);
        }
    };


    /* Run the command(s) */
    let mut return_code = -1;
    if let Some(cmds) = cmds_opt {
       for cmd in cmds {
          if let Err(ret) = cmd.run(&parser_opt) {
                error!("Command failed: {}", ret);
                if let Some(os_err) = ret.errno() {
                   return_code = os_err;
                }
                std::process::exit(return_code);
          }
       }
    }

    /*
     * At this point, we have not encountered errors.
     */
    std::process::exit(0);
}