yamlette 0.1.1

Complete and comprehensive YAML 1.2 processor, written in pure rust
Documentation
extern crate skimmer;

use crate::model::style::CommonStyles;
use crate::model::{
    model_issue_rope, EncodedString, Model, Node, Renderer, Rope, Tagged, TaggedValue,
};

use std::any::Any;
use std::borrow::Cow;
use std::default::Default;
use std::iter::Iterator;

pub static TAG: &'static str = "tag:yaml.org,2002:null";

#[derive(Copy, Clone, Debug)]
pub struct Null;

impl Null {
    pub fn get_tag() -> Cow<'static, str> {
        Cow::from(TAG)
    }

    fn read_null(&self, value: &[u8], ptr: usize) -> usize {
        match value.get(ptr).map(|b| *b) {
            Some(b'~') => 1,
            Some(b'n') => {
                if value[ptr..].starts_with("null".as_bytes()) {
                    4
                } else {
                    0
                }
            }
            Some(b'N') => {
                if value[ptr..].starts_with("Null".as_bytes())
                    || value[ptr..].starts_with("NULL".as_bytes())
                {
                    4
                } else {
                    0
                }
            }
            _ => 0,
        }
    }
}

impl Model for Null {
    fn get_tag(&self) -> Cow<'static, str> {
        Self::get_tag()
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_mut_any(&mut self) -> &mut dyn Any {
        self
    }

    fn is_decodable(&self) -> bool {
        true
    }

    fn is_encodable(&self) -> bool {
        true
    }

    fn has_default(&self) -> bool {
        true
    }

    fn get_default(&self) -> TaggedValue {
        TaggedValue::from(NullValue::default())
    }

    fn encode(
        &self,
        _renderer: &Renderer,
        value: TaggedValue,
        tags: &mut dyn Iterator<Item = &(Cow<'static, str>, Cow<'static, str>)>,
    ) -> Result<Rope, TaggedValue> {
        let mut val: NullValue =
            match <TaggedValue as Into<Result<NullValue, TaggedValue>>>::into(value) {
                Ok(value) => value,
                Err(value) => return Err(value),
            };

        let issue_tag = val.issue_tag();
        let alias = val.take_alias();

        let node = Node::String(EncodedString::from("~".as_bytes()));

        Ok(model_issue_rope(self, node, issue_tag, alias, tags))
    }

    fn decode(&self, explicit: bool, value: &[u8]) -> Result<TaggedValue, ()> {
        if value.len() == 0 {
            return Ok(TaggedValue::from(NullValue::default()));
        }

        let mut ptr: usize = 0;
        let mut quote_state: u8 = 0;

        if explicit {
            match value.get(ptr).map(|b| *b) {
                Some(b'\'') => {
                    ptr += 1;
                    quote_state = 1;
                }
                Some(b'"') => {
                    ptr += 1;
                    quote_state = 2;
                }
                _ => (),
            };
        }

        let maybe_null = self.read_null(value, ptr);

        if maybe_null > 0 {
            ptr += maybe_null;

            if quote_state > 0 {
                match value.get(ptr).map(|b| *b) {
                    Some(b'\'') if quote_state == 1 => (),
                    Some(b'"') if quote_state == 2 => (),
                    _ => return Err(()),
                };
            }

            return Ok(TaggedValue::from(NullValue::default()));
        }

        if quote_state > 0 {
            match value.get(ptr).map(|b| *b) {
                Some(b'\'') if quote_state == 1 => Ok(TaggedValue::from(NullValue::default())),
                Some(b'"') if quote_state == 2 => Ok(TaggedValue::from(NullValue::default())),
                _ => Err(()),
            }
        } else {
            Err(())
        }
    }
}

#[derive(Clone, Debug)]
pub struct NullValue {
    style: u8,
    alias: Option<Cow<'static, str>>,
}

impl NullValue {
    pub fn new(styles: CommonStyles, alias: Option<Cow<'static, str>>) -> NullValue {
        NullValue {
            style: if styles.issue_tag() { 1 } else { 0 },
            alias,
        }
    }

    pub fn take_alias(&mut self) -> Option<Cow<'static, str>> {
        self.alias.take()
    }

    pub fn issue_tag(&self) -> bool {
        self.style & 1 == 1
    }

    pub fn set_issue_tag(&mut self, val: bool) {
        if val {
            self.style |= 1;
        } else {
            self.style &= !1;
        }
    }
}

impl Default for NullValue {
    fn default() -> NullValue {
        NullValue {
            style: 0,
            alias: None,
        }
    }
}

impl Tagged for NullValue {
    fn get_tag(&self) -> Cow<'static, str> {
        Cow::from(TAG)
    }

    fn as_any(&self) -> &dyn Any {
        self as &dyn Any
    }

    fn as_mut_any(&mut self) -> &mut dyn Any {
        self as &mut dyn Any
    }
}

impl AsRef<str> for NullValue {
    fn as_ref(&self) -> &'static str {
        "~"
    }
}

#[cfg(all(test, not(feature = "dev")))]
mod tests {
    use super::*;

    use crate::model::{Renderer, Tagged};

    use std::iter;

    #[test]
    fn tag() {
        let null = Null;

        assert_eq!(null.get_tag(), TAG);
    }

    #[test]
    fn encode() {
        let renderer = Renderer;
        let null = Null;

        if let Ok(rope) = null.encode(
            &renderer,
            TaggedValue::from(NullValue::default()),
            &mut iter::empty(),
        ) {
            let encode = rope.render(&renderer);
            assert_eq!(encode, "~".as_bytes());
        } else {
            assert!(false)
        }
    }

    #[test]
    fn decode() {
        let null = Null;

        let options = ["", "~", "null", "Null", "NULL"];

        for i in 0..options.len() {
            if let Ok(tagged) = null.decode(true, options[i].as_bytes()) {
                assert_eq!(tagged.get_tag(), Cow::from(TAG));

                if let None = tagged.as_any().downcast_ref::<NullValue>() {
                    assert!(false)
                }
            } else {
                assert!(false)
            }
        }

        let decode = null.decode(true, "nil".as_bytes());
        assert!(decode.is_err());
    }
}