subcomponent 0.1.0

A components orchestrator
/*
 * Copyright (c) 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.
 */

extern crate std;

use std::collections::HashMap;
use compiler::parser;
use compiler::parser::Property;
use fetcher;
use hook;
use cmd;
use std::ops::Deref;

/*
 * TODO When setting a property, we should associate a Lexeme to it.
 * Will greatly improve errors when we throw that a property in invalid!
 */

pub struct Config {
    map: HashMap<String, Box<Property>>,
    visits: std::cell::RefCell<HashMap<String, ()>>,
}

impl Config {
    pub fn new() -> Config {
        Config {
            map: HashMap::new(),
            visits: std::cell::RefCell::new(HashMap::new()),
        }
    }

    #[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
    pub fn get(&self, key: &str) -> Option<&Box<Property>>
    {
       /*
        * When we access a key, this means that the parser actually wants it.
        * If the key is wanted, it is valid. So, we mark it as visited. We
        * also have to mark as visited all its components. For instance, if 
        * /path/to/key is accessed, we have to add:
        *  -> /
        *  -> /path/
        *  -> /path/to/
        *  -> /path/to/key
        */
        if let Some(data) = self.map.get(key) {
           let mut visits = self.visits.borrow_mut();

           let visit_key = String::from(key);
           {
              /*
               * Decompose a key into all its bases. It's a bit tedious...
               */
              let mut iter = visit_key.as_str();
              let mut index_increment = 0;
              while let Some(index) = iter.find('/') {
                 let subkey = &visit_key[0..index_increment+index+1];

                 visits.insert(String::from(subkey), ());
                 iter = &iter[index+1..];
                 index_increment += index + 1;
              }
           }

           visits.insert(visit_key, ());
           Some(data)
        } else {
           None
        }
    }
    pub fn set(&mut self, key: String, prop: Box<Property>) {
        trace!("Inserting key {} in config", key);
        self.map.insert(key, prop);
    }

    pub fn collect_components(&self) -> Result<Vec<String>, parser::Error> {
        let sub_key = "/subcomponents/";

        if ! self.map.contains_key(&sub_key.to_string()) {
           error!("The \"subcomponents\" block is mandatory, but it is not defined.");
           return Err(parser::Error::NoSubcomponents);
        }

        let sub_key_len = sub_key.len();
        let mut components = Vec::new();

        /*
         * Go through all the keys in the config, so we can find components.
         * Once we have collected all the components names in a vector, we
         * can go through them one by one.
         */
        for key in self.map.keys() {
            /* Get all /subcomponents/... properties */
            if key.contains(sub_key) && (key.len() > sub_key_len) {
                let slice = &key[sub_key_len..];
                if let Some(index) = slice.find('/') {
                    /* If we have exactly this "/subcomponents/???/",
                     * this is a new component */
                    if index + 1 == slice.len() {
                        let id = &slice[0..index];
                        components.push(String::from(id));
                    }
                }
            }
        }

        Ok(components)
    }

    pub fn get_hooks_for_component(&self, component: &str) -> Result<Vec<String>, parser::Error> {
       let mut hooks: Vec<String> = Vec::new();

       let cmd_list = cmd::get_commands_list();
       for key in self.map.keys() {
          /* We are only interested in blocks */
          if ! key.ends_with('/') {
             continue;
          }
          let start_key = format!("/subcomponents/{}/", component);
          if ! key.starts_with(start_key.as_str()) {
             continue;
          }

          let mut iter = key.split('/');
          if let Some(hook_name) = iter.nth(3) {
             /* Only consider the blocks in the subcomponents/ block */
             if iter.nth(4).is_some() {
                continue;
             }

             let mut process_hook = true;

             for cmd in &cmd_list {
                if hook_name == "fetch" {
                   /*
                    * Fetch is a mandatory, special hook. It is treated by the
                    * fetch methods. We don't care about it here.
                    */
                   process_hook = false;
                } else if hook_name == *cmd {
                   error!("Hook name '{}' is reserved", hook_name);
                   return Err(parser::Error::ReservedHook);
                }
             }

             if process_hook && ! hook_name.is_empty() {
                debug!("Registering hook '{}' for component '{}'",
                       hook_name, component);
                hooks.push(String::from(hook_name));
             }
          }
       }

       Ok(hooks)
    }

    pub fn check_unused(&self) -> Result<(), parser::Error> {
       /*
        * Go through all the keys. If they are visited, they are fine.
        * If they are NOT, that's bad! We will go through ALL the keys
        * before returning an error when something is wrong, as it will
        * makes life easier for the user.
        */
       let visits = self.visits.borrow();
       let mut ret = Ok(());
       for key in self.map.keys() {
          if visits.get(key).is_none() {
             error!("Unknown propery {}", key);
             ret = Err(parser::Error::InvalidPropertyName);
          }
       }
       ret
    }

    pub fn get_fetch_property(&self, component: &str, method: &str, prop: &str) -> Option<parser::PropertyValue> {
       let key = format!("/subcomponents/{}/fetch/{}/{}",
                         component, method, prop);
       if let Some(data) = self.get(&key) {
          Some(data.deref().value_get())
       } else {
          None
       }
    }

}

pub struct Component {
    id: String,
    name: String,
    path: String,
    fetch: Vec<Box<fetcher::Method>>,
    dependencies: Vec<String>,
    hooks: Vec<hook::Hook>,
}

impl Component {
    pub fn new(id: String, name: String, path: String, dependencies: Vec<String>, hooks: Vec<hook::Hook>) -> Component {
        Component {
            id: id,
            name: name,
            path: path,
            fetch: Vec::new(),
            dependencies: dependencies,
            hooks: hooks,
        }
    }
    pub fn id_get(&self) -> &String {
        &self.id
    }
    pub fn fetch_method_add(&mut self, method: Box<fetcher::Method>) {
        self.fetch.push(method);
    }
    pub fn hooks_get(&self) -> &Vec<hook::Hook> {
       &self.hooks
    }

    pub fn fetch_methods_get(&self) -> &Vec<Box<fetcher::Method>> {
        &self.fetch
    }

    #[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
    pub fn fetch_method_get(&self, name: &str) -> &Box<fetcher::Method> {
        for method in &self.fetch {
           if method.name_get() == name {
              return method;
           }
        }
        panic!("Uhhh... corrupted database?");
    }

    pub fn path_get(&self) -> &String {
        &self.path
    }
    pub fn name_get(&self) -> &String {
        &self.name
    }
}