kg-tree 0.2.1

Generic object tree with Opath query language, similar to XPath.
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use std::path::Path;
use std::rc::{Rc, Weak};
use std::cell::Cell;

use super::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum FileFormat {
    Binary,
    Text,
    Json,
    Yaml,
    Toml,
}

impl FileFormat {
    pub fn from(f: &str) -> FileFormat {
        if f.eq_ignore_ascii_case("text") || f.eq_ignore_ascii_case("txt") {
            FileFormat::Text
        } else if f.eq_ignore_ascii_case("json") {
            FileFormat::Json
        } else if f.eq_ignore_ascii_case("yaml") || f.eq_ignore_ascii_case("yml") {
            FileFormat::Yaml
        } else if f.eq_ignore_ascii_case("toml") {
            FileFormat::Toml
        } else {
            FileFormat::Binary
        }
    }
}

impl<'a> std::convert::From<&'a str> for FileFormat {
    fn from(s: &'a str) -> Self {
        FileFormat::from(s)
    }
}

impl std::str::FromStr for FileFormat {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(FileFormat::from(s))
    }
}

impl std::fmt::Display for FileFormat {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match *self {
            FileFormat::Binary => write!(f, "binary"),
            FileFormat::Text => write!(f, "text"),
            FileFormat::Json => write!(f, "json"),
            FileFormat::Yaml => write!(f, "yaml"),
            FileFormat::Toml => write!(f, "toml"),
        }
    }
}

impl Default for FileFormat {
    fn default() -> Self {
        FileFormat::Binary
    }
}


#[derive(Debug)]
struct FileInfoInner {
    file_path: PathBuf,
    file_type: FileType,
    file_format: Cell<FileFormat>,
}

#[derive(Clone)]
pub struct FileInfo(Rc<FileInfoInner>);

impl FileInfo {
    pub fn new<P: Into<PathBuf> + AsRef<Path>>(
        file_path: P,
        file_type: FileType,
        file_format: FileFormat,
    ) -> FileInfo {
        debug_assert!(file_path.as_ref().is_absolute());

        FileInfo(Rc::new(FileInfoInner {
            file_path: file_path.into(),
            file_type,
            file_format: Cell::new(file_format),
        }))
    }

    pub fn new_file<P: Into<PathBuf> + AsRef<Path>>(
        file_path: P,
        file_format: FileFormat,
    ) -> FileInfo {
        debug_assert!(file_path.as_ref().is_absolute());

        FileInfo(Rc::new(FileInfoInner {
            file_path: file_path.into(),
            file_type: FileType::File,
            file_format: Cell::new(file_format),
        }))
    }

    pub fn new_dir<P: Into<PathBuf> + AsRef<Path>>(
        file_path: P,
    ) -> FileInfo {
        debug_assert!(file_path.as_ref().is_absolute());

        FileInfo(Rc::new(FileInfoInner {
            file_path: file_path.into(),
            file_type: FileType::Dir,
            file_format: Cell::new(FileFormat::default()),
        }))
    }

    pub fn file_path_abs(&self) -> &Path {
        &self.0.file_path
    }

    pub fn file_path(&self) -> &Path {
        crate::relative_path(&self.0.file_path)
    }

    pub fn file_type(&self) -> FileType {
        self.0.file_type
    }

    pub fn file_format(&self) -> FileFormat {
        self.0.file_format.get()
    }

    pub fn set_file_format(&mut self, file_format: FileFormat) {
        self.0.file_format.set(file_format);
    }
}

impl std::fmt::Debug for FileInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        std::fmt::Debug::fmt(&self.0, f)
    }
}

impl std::fmt::Display for FileInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        if f.alternate() {
            match self.file_type() {
                FileType::Dir => write!(f, "{}:{}", self.file_type(), self.file_path().display()),
                FileType::File => write!(
                    f,
                    "{}<{}>:{}",
                    self.file_type(),
                    self.file_format(),
                    self.file_path().display()
                ),
                _ => unreachable!(),
            }
        } else {
            match self.file_type() {
                FileType::Dir => write!(
                    f,
                    "{}:{}",
                    self.file_type(),
                    crate::relative_path(&self.file_path()).display()
                ),
                FileType::File => write!(
                    f,
                    "{}<{}>:{}",
                    self.file_type(),
                    self.file_format(),
                    crate::relative_path(&self.file_path()).display()
                ),
                _ => unreachable!(),
            }
        }
    }
}

impl PartialEq<FileInfo> for FileInfo {
    fn eq(&self, other: &FileInfo) -> bool {
        if Rc::ptr_eq(&self.0, &other.0) {
            true
        } else {
            self.file_path() == other.file_path()
        }
    }
}

impl Eq for FileInfo {}

impl PartialOrd<FileInfo> for FileInfo {
    fn partial_cmp(&self, other: &FileInfo) -> Option<Ordering> {
        self.file_path().partial_cmp(other.file_path())
    }
}

impl Ord for FileInfo {
    fn cmp(&self, other: &Self) -> Ordering {
        self.file_path().cmp(other.file_path())
    }
}

impl Hash for FileInfo {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.file_path().hash(state);
    }
}

impl HeapSizeOf for FileInfo {
    fn heap_size_of_children(&self) -> usize {
        PathBufHeapSize(&self.0.file_path).heap_size_of_children()
    }
}

#[derive(Debug)]
pub struct Metadata {
    parent: Option<Weak<RefCell<Node>>>,
    index: usize,
    key: Symbol,
    file: Option<FileInfo>,
    span: Option<Box<Span>>,
}

impl Metadata {
    pub(super) fn new() -> Metadata {
        Metadata {
            parent: None,
            index: 0,
            key: Symbol::default(),
            file: None,
            span: None,
        }
    }

    pub fn parent(&self) -> Option<NodeRef> {
        match self.parent {
            Some(ref p) => Some(NodeRef::wrap(p.upgrade().unwrap())),
            None => None,
        }
    }

    pub fn has_parent(&self) -> bool {
        self.parent.is_some()
    }

    pub fn set_parent(&mut self, p: Option<&NodeRef>) {
        self.parent = p.map(|p| Rc::downgrade(p.unwrap()));
    }

    pub fn key(&self) -> &str {
        self.key.as_ref()
    }

    pub fn set_key(&mut self, key: Cow<str>) {
        self.key = Symbol::from(key);
    }

    pub fn index(&self) -> usize {
        self.index
    }

    pub fn set_index(&mut self, index: usize) {
        self.index = index;
    }

    pub fn file(&self) -> Option<&FileInfo> {
        self.file.as_ref()
    }

    pub fn set_file(&mut self, file: Option<FileInfo>) {
        self.file = file;
    }

    pub fn span(&self) -> Option<Span> {
        self.span.as_ref().map(|s| **s)
    }

    pub fn set_span(&mut self, span: Option<Span>) {
        self.span = span.map(|s| Box::new(s));
    }

    pub(super) fn detach(&mut self) {
        self.parent = None;
        self.index = 0;
        self.key = Symbol::default();
    }

    pub(super) fn deep_copy(&self) -> Metadata {
        Metadata {
            parent: None,
            index: 0,
            key: Symbol::default(),
            file: self.file.clone(),
            span: self.span.clone(),
        }
    }
}

impl HeapSizeOf for Metadata {
    fn heap_size_of_children(&self) -> usize {
        if self.span.is_some() {
            std::mem::size_of::<Span>()
        } else {
            0
        }
    }
}