uninode 0.4.4

Universal object type
Documentation
//
// Copyright (c) 2022 Oleg Lelenkov <o.lelenkov@gmail.com>
// Distributed under terms of the BSD 3-Clause license.
//

mod error;
#[cfg(feature = "toml")]
pub mod toml;
#[cfg(feature = "yaml")]
pub mod yaml;

pub use self::error::{UniNodeFmtError, UniNodeLoadError, UniNodeIoError};

use std::collections::HashMap;
use std::sync::RwLock;
use std::path::Path;

use once_cell::sync::Lazy;

use crate::value::UniNode;

type GlobalFormat = Box<dyn UniNodeFormat + Sync + Send + 'static>;
type FormatMap = HashMap<String, GlobalFormat>;

static FORMATS: Lazy<RwLock<FormatMap>> = Lazy::new(|| {
    let mut cont = FormatMap::new();
    #[cfg(feature = "yaml")]
    add_format(&mut cont, yaml::YamlFormat::default());
    #[cfg(feature = "toml")]
    add_format(&mut cont, toml::TomlFormat::default());
    RwLock::new(cont)
});

pub trait UniNodeFormat {
    fn extensions(&self) -> &[&str];

    fn parse(&self, text: &str) -> Result<UniNode, UniNodeFmtError>;

    fn build(&self, node: &UniNode) -> Result<String, UniNodeFmtError>;
}

fn add_format<L>(cont: &mut FormatMap, format: L)
where
    L: UniNodeFormat + Clone + Sync + Send + 'static,
{
    for ext in format.extensions() {
        cont.insert(ext.to_string(), Box::new(format.clone()));
    }
}

pub fn register_format<F>(format: F)
where
    F: UniNodeFormat + Clone + Sync + Send + 'static,
{
    let mut formats = FORMATS.write().unwrap();
    add_format(&mut formats, format);
}

pub fn available_extensions() -> Vec<String> {
    let formats = FORMATS.read().unwrap();
    formats.keys().cloned().collect()
}

impl UniNode {
    pub fn parse<P>(path: P, content: &str) -> Result<UniNode, UniNodeLoadError>
    where
        P: AsRef<Path>,
    {
        let path = path.as_ref();
        let ext = path
            .extension()
            .and_then(|e| e.to_str())
            .or_else(|| path.to_str())
            .ok_or_else(|| {
                let path = path.to_string_lossy();
                let err = UniNodeFmtError::NotDefinedFormat(path.into());
                UniNodeLoadError::Fmt(err)
            })?;

        let formats = FORMATS.read().unwrap();
        if !formats.contains_key(ext) {
            let err = UniNodeFmtError::NotDefinedFormat(ext.to_string());
            return Err(UniNodeLoadError::Fmt(err));
        }

        formats[ext].parse(content).map_err(UniNodeLoadError::Fmt)
    }

    pub fn load<P>(path: P) -> Result<UniNode, UniNodeLoadError>
    where
        P: AsRef<Path>,
    {
        let path = path.as_ref();
        match path.extension().and_then(|e| e.to_str()) {
            Some(ext) => {
                let formats = FORMATS.read().unwrap();
                if !formats.contains_key(ext) {
                    let err =
                        UniNodeFmtError::NotDefinedFormat(ext.to_string());
                    return Err(UniNodeLoadError::Fmt(err));
                }
                let content = std::fs::read_to_string(path)
                    .map_err(|e| UniNodeIoError::new(path, e))?;
                formats[ext]
                    .parse(content.as_ref())
                    .map_err(UniNodeLoadError::Fmt)
            },
            None => {
                for (ext, fmt) in FORMATS.read().unwrap().iter() {
                    let full_path = path.with_extension(&ext);
                    if full_path.is_file() {
                        let content = std::fs::read_to_string(&full_path)
                            .map_err(|e| UniNodeIoError::new(full_path, e))?;
                        return fmt
                            .parse(&content)
                            .map_err(UniNodeLoadError::Fmt);
                    }
                }
                Err(UniNodeLoadError::NotFoundLoader)
            },
        }
    }
}