/*
* 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
}
}