uci_rs 0.1.2

an openwrt's UCI (Unified Configuration Interface) parser and serializer.
Documentation
use std::io::{BufWriter, Write};

use crate::utils::{Error, Result};

use super::tree::*;

pub struct Uci {
    config: UciConfig,
}

impl Uci {
    #[must_use]
    pub fn new(name: &str) -> Self {
        Self::default(name)
    }

    fn default(name: &str) -> Self {
        Self {
            config: UciConfig::new(name),
        }
    }

    pub(super) fn insert_config(&mut self, config: UciConfig) {
        self.config = config;
    }

    fn _lookup_values(&self, section: &str, option: &str) -> Result<&Vec<String>> {
        match self._lookup_option(section, option) {
            Ok(option) => Ok(&option.values),
            Err(err) => Err(err),
        }
    }

    fn _lookup_option(&self, section: &str, option: &str) -> Result<&UciOption> {
        match self.config.get(section) {
            Ok(Some(sec)) => match sec.get(option) {
                Some(opt) => Ok(opt),
                None => Err(Error::new(format!(
                    "option of {}.{} not found",
                    section, option
                ))),
            },
            Ok(None) => Err(Error::new(format!(
                "option of {}.{} not found",
                section, option
            ))),
            Err(err) => Err(err),
        }
    }

    fn _set_option_with_type(
        &mut self,
        section: &str,
        option: &str,
        opt_type: UciOptionType,
        values: Vec<String>,
    ) -> Result<()> {
        let sec_opt = self.config.get_mut(section)?;
        match sec_opt {
            Some(sec) => match sec.get_mut(option) {
                Some(opt) => {
                    opt.set_values(values);
                    Ok(())
                }
                None => {
                    sec.add(UciOption::new(option, opt_type, values));
                    Ok(())
                }
            },
            None => Err(Error::new(format!("section '{}' not found", section))),
        }
    }
}

pub trait UciCommand {
    fn add_section(&mut self, typ: &str, name: &str) -> Result<()>;
    fn del_option(&mut self, section: &str, option: &str) -> Result<()>;
    fn del_all(&mut self, typ: &str) -> Result<()>;
    fn del_section(&mut self, section: &str) -> Result<()>;
    fn get_option(&self, section: &str, option: &str) -> Result<(String, &Vec<String>)>;
    fn get_all_options(&self, section: &str) -> Result<Vec<(String, &Vec<String>)>>;
    fn get_option_last(&self, section: &str, option: &str) -> Result<(String, Option<String>)>;
    fn get_option_first(&self, section: &str, option: &str) -> Result<(String, Option<String>)>;
    fn get_section(&self, section: &str) -> Result<(String, String)>;
    fn get_all(&self, typ: &str) -> Vec<(String, String)>;
    fn get_all_sections(&self) -> Vec<(String, String)>;
    fn get_section_first(&self, typ: &str) -> Option<(String, String)>;
    fn get_section_last(&self, typ: &str) -> Option<(String, String)>;
    fn set_package(&mut self, package: &str) -> Result<()>;
    fn get_package(&self) -> String;
    fn set_option(&mut self, section: &str, option: &str, values: Vec<&str>) -> Result<()>;
    fn for_each<F>(&self, typ: &str, func: F)
    where
        F: FnMut(&UciSection);
    fn write_in<W: Write>(&self, buf: &mut BufWriter<W>) -> Result<()>;
}

impl UciCommand for Uci {
    fn write_in<W: Write>(&self, buf: &mut BufWriter<W>) -> Result<()> {
        if !self.config.name.is_empty() {
            buf.write_fmt(format_args!("\npackage '{}'\n", self.config.name))?;
        }

        for sec in self.config.sections.iter() {
            if sec.name.is_empty() {
                buf.write_fmt(format_args!("\nconfig {}\n", sec.sec_type))?;
            } else {
                buf.write_fmt(format_args!("\nconfig {} '{}'\n", sec.sec_type, sec.name))?;
            }

            for opt in sec.options.iter() {
                match opt.opt_type {
                    UciOptionType::TypeOption => {
                        buf.write_fmt(format_args!("\toption {} '{}'\n", opt.name, opt.values[0]))?;
                    }
                    UciOptionType::TypeList => {
                        for v in opt.values.iter() {
                            buf.write_fmt(format_args!("\tlist {} '{}'\n", opt.name, v))?;
                        }
                    }
                }
            }
        }

        buf.write_all(b"\n")?;
        Ok(())
    }

    fn get_option(&self, section: &str, option: &str) -> Result<(String, &Vec<String>)> {
        self._lookup_option(section, option)
            .map(|opt| (opt.name.to_owned(), opt.values.as_ref()))
    }

    fn get_option_last(&self, section: &str, option: &str) -> Result<(String, Option<String>)> {
        let (name, values) = self.get_option(section, option)?;
        Ok((name, values.last().cloned()))
    }

    fn set_option(&mut self, section: &str, option: &str, values: Vec<&str>) -> Result<()> {
        if values.len() > 1 {
            self._set_option_with_type(
                section,
                option,
                UciOptionType::TypeList,
                values.into_iter().map(|s| s.to_string()).collect(),
            )
        } else {
            self._set_option_with_type(
                section,
                option,
                UciOptionType::TypeOption,
                values.into_iter().map(|s| s.to_string()).collect(),
            )
        }
    }

    fn del_option(&mut self, section: &str, option: &str) -> Result<()> {
        let sec_opt = self.config.get_mut(section)?;
        match sec_opt {
            Some(sec) => {
                self.config.modified = sec.del(option);
                Ok(())
            }
            None => Ok(()),
        }
    }

    fn add_section(&mut self, typ: &str, name: &str) -> Result<()> {
        if name.is_empty() {
            self.config.add(UciSection::new(typ, name));
            self.config.modified = true;
            Ok(())
        } else {
            match self.config.get(name) {
                Ok(Some(sec)) => {
                    if sec.sec_type != typ {
                        self.config.del(name);
                        self.config.add(UciSection::new(typ, name));
                        self.config.modified = true;
                    }
                    Ok(())
                }
                _ => {
                    self.config.add(UciSection::new(typ, name));
                    self.config.modified = true;
                    Ok(())
                }
            }
        }
    }

    fn del_section(&mut self, section: &str) -> Result<()> {
        self.config.del(section);
        self.config.modified = true;
        Ok(())
    }

    fn set_package(&mut self, package: &str) -> Result<()> {
        self.config.set_name(package);
        Ok(())
    }

    fn get_package(&self) -> String {
        self.config.name.clone()
    }

    fn del_all(&mut self, typ: &str) -> Result<()> {
        self.config.del_all(typ);
        Ok(())
    }

    fn get_all_options(&self, section: &str) -> Result<Vec<(String, &Vec<String>)>> {
        let sec_opt = self.config.get(section)?;
        match sec_opt {
            Some(sec) => Ok(sec
                .options
                .iter()
                .map(|opt| (opt.name.clone(), opt.values.as_ref()))
                .collect()),
            None => Ok(vec![]),
        }
    }

    fn get_option_first(&self, section: &str, option: &str) -> Result<(String, Option<String>)> {
        let opt = self._lookup_option(section, option)?;

        Ok((opt.name.clone(), opt.values.first().cloned()))
    }

    fn get_section(&self, section: &str) -> Result<(String, String)> {
        let sec_opt = self.config.get(section)?;
        if let Some(sec) = sec_opt {
            Ok((
                sec.sec_type.clone(),
                self.config.get_section_name(sec),
            ))
        } else {
            Err(Error::new("not found target section"))
        }
    }

    fn get_all_sections(&self) -> Vec<(String, String)> {
        self.config
            .sections
            .iter()
            .map(|sec| (sec.sec_type.clone(), self.config.get_section_name(sec)))
            .collect()
    }

    fn get_all(&self, typ: &str) -> Vec<(String, String)> {
        self.config
            .sections
            .iter()
            .filter(|sec| sec.sec_type == typ)
            .map(|sec| (sec.sec_type.clone(), self.config.get_section_name(sec)))
            .collect()
    }

    fn get_section_first(&self, typ: &str) -> Option<(String, String)> {
        self.config.sections.iter().find_map(|sec| {
            if sec.sec_type == typ {
                Some((sec.sec_type.clone(), self.config.get_section_name(sec)))
            } else {
                None
            }
        })
    }

    fn get_section_last(&self, typ: &str) -> Option<(String, String)> {
        self.config
            .sections
            .iter()
            .filter_map(|sec| {
                if sec.sec_type == typ {
                    Some((sec.sec_type.clone(), self.config.get_section_name(sec)))
                } else {
                    None
                }
            })
            .last()
    }

    fn for_each<F>(&self, typ: &str, func: F)
    where
        F: FnMut(&UciSection),
    {
        self.config
            .sections
            .iter()
            .filter(|sec| sec.sec_type == typ)
            .for_each(func)
    }
}


pub fn is_bool_value(value: &str) -> bool {
    match value {
        "1" => true,
        "on" => true,
        "true" => true,
        "yes" => true,
        "enabled" => true,
        "0" => false,
        "false" => false,
        "no" => false,
        "disabled" => false,
        _ => false,
    }
}