rash_core 2.18.3

Declarative shell scripting using Rust native bindings
Documentation
use crate::context::GlobalParams;
use crate::error::{Error, ErrorKind, Result};
use crate::task::Task;

use std::collections::{HashMap, HashSet};

use serde_norway::Value as YamlValue;

#[derive(Debug, Clone)]
pub struct Handler<'a> {
    name: String,
    task: Task<'a>,
}

impl<'a> Handler<'a> {
    pub fn new(name: String, task: Task<'a>) -> Self {
        Handler { name, task }
    }

    pub fn get_name(&self) -> &str {
        &self.name
    }

    pub fn get_task(&self) -> &Task<'a> {
        &self.task
    }
}

#[derive(Debug, Clone, Default)]
pub struct Handlers<'a> {
    handlers: HashMap<String, Handler<'a>>,
}

impl<'a> Handlers<'a> {
    pub fn new() -> Self {
        Handlers {
            handlers: HashMap::new(),
        }
    }

    pub fn from_yaml(yaml: &[YamlValue], global_params: &'a GlobalParams) -> Result<Self> {
        let mut handlers = HashMap::new();

        for handler_yaml in yaml {
            let name = handler_yaml
                .get("name")
                .and_then(|n| n.as_str())
                .ok_or_else(|| {
                    Error::new(
                        ErrorKind::InvalidData,
                        "Handler must have a 'name' attribute",
                    )
                })?
                .to_string();

            let task = Task::new(handler_yaml, global_params)?;
            let handler = Handler::new(name.clone(), task);
            handlers.insert(name, handler);
        }

        Ok(Handlers { handlers })
    }

    pub fn get(&self, name: &str) -> Option<&Handler<'a>> {
        self.handlers.get(name)
    }

    pub fn get_handler_names(&self) -> Vec<&str> {
        self.handlers.keys().map(|s| s.as_str()).collect()
    }
}

#[derive(Debug, Clone, Default)]
pub struct PendingHandlers {
    pending: HashSet<String>,
}

impl PendingHandlers {
    pub fn new() -> Self {
        PendingHandlers {
            pending: HashSet::new(),
        }
    }

    pub fn notify(&mut self, handler_names: &[String]) {
        for name in handler_names {
            self.pending.insert(name.clone());
        }
    }

    pub fn get_pending(&self) -> &HashSet<String> {
        &self.pending
    }

    pub fn clear(&mut self) {
        self.pending.clear();
    }

    pub fn is_empty(&self) -> bool {
        self.pending.is_empty()
    }

    pub fn take_pending(&mut self) -> HashSet<String> {
        std::mem::take(&mut self.pending)
    }
}

pub fn parse_notify_value(value: &YamlValue) -> Option<Vec<String>> {
    match value {
        YamlValue::String(s) => Some(vec![s.clone()]),
        YamlValue::Sequence(seq) => Some(
            seq.iter()
                .filter_map(|v| v.as_str().map(String::from))
                .collect(),
        ),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::context::GlobalParams;

    fn create_test_global_params() -> GlobalParams<'static> {
        GlobalParams::default()
    }

    #[test]
    fn test_parse_notify_value_string() {
        let value = YamlValue::String("handler1".to_string());
        let result = parse_notify_value(&value);
        assert_eq!(result, Some(vec!["handler1".to_string()]));
    }

    #[test]
    fn test_parse_notify_value_sequence() {
        let value = YamlValue::Sequence(vec![
            YamlValue::String("handler1".to_string()),
            YamlValue::String("handler2".to_string()),
        ]);
        let result = parse_notify_value(&value);
        assert_eq!(
            result,
            Some(vec!["handler1".to_string(), "handler2".to_string()])
        );
    }

    #[test]
    fn test_parse_notify_value_invalid() {
        let value = YamlValue::Number(42.into());
        let result = parse_notify_value(&value);
        assert_eq!(result, None);
    }

    #[test]
    fn test_pending_handlers_notify() {
        let mut pending = PendingHandlers::new();
        assert!(pending.is_empty());

        pending.notify(&["handler1".to_string(), "handler2".to_string()]);
        assert!(!pending.is_empty());
        assert!(pending.get_pending().contains("handler1"));
        assert!(pending.get_pending().contains("handler2"));

        pending.notify(&["handler1".to_string()]);
        assert_eq!(pending.get_pending().len(), 2);
    }

    #[test]
    fn test_pending_handlers_take_pending() {
        let mut pending = PendingHandlers::new();
        pending.notify(&["handler1".to_string()]);

        let taken = pending.take_pending();
        assert!(taken.contains("handler1"));
        assert!(pending.is_empty());
    }

    #[test]
    fn test_handlers_from_yaml() {
        let yaml_str = r#"
        - name: restart service
          command: systemctl restart myservice
        - name: reload config
          command: systemctl reload myservice
        "#;
        let handlers_yaml: Vec<YamlValue> = serde_norway::from_str(yaml_str).unwrap();
        let global_params = create_test_global_params();

        let handlers = Handlers::from_yaml(&handlers_yaml, &global_params).unwrap();
        assert!(handlers.get("restart service").is_some());
        assert!(handlers.get("reload config").is_some());
        assert!(handlers.get("nonexistent").is_none());
    }

    #[test]
    fn test_handlers_missing_name() {
        let yaml_str = r#"
        - command: echo test
        "#;
        let handlers_yaml: Vec<YamlValue> = serde_norway::from_str(yaml_str).unwrap();
        let global_params = create_test_global_params();

        let result = Handlers::from_yaml(&handlers_yaml, &global_params);
        assert!(result.is_err());
    }
}