macos-shortcuts 1.1.0

This crate enables access to Apple Shortcuts for Mac
Documentation
use std::fmt::{self, Display};

use serde::{de::{self, DeserializeSeed, SeqAccess, Visitor}, forward_to_deserialize_any, Deserialize};

const BACKSLASH: u8 = b'\\';
const CLOSE_BRACE: u8 = b'}';
const DATA_PREFIX: &str = "«data TIFF";
const DATA_SUFFIX: &str = "»";
const DOUBLE_ARROW_PREFIX: u8 = "«".as_bytes()[0];
const MISSING_DATA: &str = "missing value";
const NEWLINE: u8 = b'\n';
const OPEN_BRACE: u8 = b'{';
const QUOTE: u8 = b'"';

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug)]
pub enum Error {
    Eof,
    Message(String),
    InvalidUtf8,
    Syntax(char),
    TrailingCharacters,
    UnexpectedCharacter(char, char),
}

impl de::Error for Error {
    fn custom<T: Display>(msg: T) -> Self {
        Error::Message(msg.to_string())
    }
}

impl Display for Error {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::Eof => formatter.write_str("unexpected end of input"),
            Error::InvalidUtf8 => formatter.write_str("invalid utf8"),
            Error::Message(msg) => formatter.write_str(msg),
            Error::Syntax(c) => formatter.write_fmt(format_args!("syntax error '{}'", c)),
            Error::TrailingCharacters => formatter.write_str("trailing characters"),
            Error::UnexpectedCharacter(expected, found) => formatter.write_fmt(format_args!("expected '{expected}', found '{found}'")),
        }
    }
}

impl std::error::Error for Error {}

pub struct AppleScriptDeserializer<'de> {
    input: &'de[u8],
}

impl <'de> AppleScriptDeserializer<'de> {
    pub fn from_bytes(input: &'de[u8]) -> Self {
        AppleScriptDeserializer { input }
    }
}

pub fn from_bytes<'a, T>(input: &'a[u8]) -> Result<T>
where
    T: Deserialize<'a>,
{
    let mut deserializer = AppleScriptDeserializer::from_bytes(input);
    let t = T::deserialize(&mut deserializer)?;
    if deserializer.input.len() == 1 && deserializer.input[0] == NEWLINE {
        Ok(t)
    } else {
        Err(Error::TrailingCharacters)
    }
}

impl <'de> AppleScriptDeserializer<'de> {
    fn expect_byte(&mut self, b: u8) -> Result<bool> {
        if self.input[0] == b {
            self.input = &self.input[1..];
            Ok(true)
        } else {
            Err(Error::UnexpectedCharacter(char::from(b), char::from(self.input[0])))
        }
    }

    fn expect_str(&mut self, s: &str) -> Result<()> {
        for c in s.bytes() {
            self.expect_byte(c)?;
        }
        Ok(())
    }

    fn peek_byte(&self) -> Result<u8> {
        self.input.first().copied().ok_or(Error::Eof)
    }

    fn seek_byte(&mut self, b: u8) -> Result<&[u8]> {
        let mut result = Err(Error::Eof);
        let mut idx = 0;
        while result.is_err() && idx < self.input.len() {
            if self.input[idx] == b && idx > 0 && self.input[idx - 1] != BACKSLASH {
                result = Ok(&self.input[..idx]);
                self.input = &self.input[idx..];
            }
            idx += 1;
        }
        result
    }
}

impl<'de, 'a> de::Deserializer<'de> for &'a mut AppleScriptDeserializer<'de> {
    type Error = Error;

    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
    where
        V: Visitor<'de>,
    {
        match self.peek_byte()? {
            b'm' => self.deserialize_option(visitor),
            QUOTE => self.deserialize_str(visitor),
            OPEN_BRACE => self.deserialize_seq(visitor),
            DOUBLE_ARROW_PREFIX => self.deserialize_bytes(visitor),
            b => Err(Error::Syntax(char::from(b))),
        }
    }

    fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value>
        where
            V: Visitor<'de> {
        fn try_from_hex(c: u8) -> Result<u8> {
            match c {
                48..=58 => Ok(c - 48),
                65..=71 => Ok(c - 55),
                97..=103 => Ok(c - 87),
                _ => Err(Error::UnexpectedCharacter(char::from(c), 'x')),
            }
        }
        self.expect_str(DATA_PREFIX)?;
        let hex = self.seek_byte(DOUBLE_ARROW_PREFIX)?;
        let mut binary = Vec::with_capacity(hex.len() / 2);
        for i in 0..binary.capacity() {
            binary.push(16 * try_from_hex(hex[i * 2])? + try_from_hex(hex[i * 2 + 1])?);
        }
        let result = visitor.visit_byte_buf(binary)?;
        self.expect_str(DATA_SUFFIX)?;
        Ok(result)
    }

    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value> 
        where
            V: Visitor<'de> {
        if self.peek_byte()? == MISSING_DATA.as_bytes()[0] {
            self.expect_str(MISSING_DATA)?;
            visitor.visit_none()
        } else {
            visitor.visit_some(self)
        }
    }

    fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
        where
            V: Visitor<'de> {
        self.expect_byte(OPEN_BRACE)?;
        let value = visitor.visit_seq(CommaSeparated::new(self))?;
        self.expect_byte(CLOSE_BRACE)?;
        Ok(value)
    }

    fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
        where
            V: Visitor<'de> {
        self.expect_byte(QUOTE)?;
        let value = match std::str::from_utf8(self.seek_byte(QUOTE)?) {
            Ok(s) => visitor.visit_str(s),
            Err(_) => Err(Error::InvalidUtf8)
        };
        self.expect_byte(QUOTE)?;
        value
    }

    forward_to_deserialize_any! {
        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char string
        byte_buf unit unit_struct newtype_struct tuple
        tuple_struct map struct enum identifier ignored_any
    }
}

struct CommaSeparated<'a, 'de: 'a> {
    de: &'a mut AppleScriptDeserializer<'de>,
    first: bool,
}

impl<'a, 'de> CommaSeparated<'a, 'de> {
    fn new(de: &'a mut AppleScriptDeserializer<'de>) -> Self {
        CommaSeparated {
            de,
            first: true,
        }
    }
}

impl<'de, 'a> SeqAccess<'de> for CommaSeparated<'a, 'de> {
    type Error = Error;

    fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
    where
        T: DeserializeSeed<'de>,
    {
        if self.de.peek_byte()? == CLOSE_BRACE {
            return Ok(None);
        }
        if !self.first {
            self.de.expect_byte(b',')?;
            self.de.expect_byte(b' ')?;
        }
        self.first = false;
        seed.deserialize(&mut *self.de).map(Some)
    }
}


#[cfg(test)]
mod tests {
    use std::io::Cursor;
    use image::DynamicImage;
    use crate::{deserializer::{from_bytes, Result, DATA_PREFIX, DATA_SUFFIX, NEWLINE}, dynamic_image_ext::DynamicImageExt};

    #[test]
    fn deserialize_image() {
        let sample = DynamicImage::new(1, 2, image::ColorType::L8);
        let mut str = String::new();
        str.push_str(DATA_PREFIX);
        let mut cursor = Cursor::new(Vec::new());
        sample.write_to(&mut cursor, image::ImageFormat::Tiff).unwrap();
        for c in cursor.into_inner() {
            str.push_str(&format!("{:02x}", c));
        }
        str.push_str(DATA_SUFFIX);
        str.push_str(std::str::from_utf8(&[NEWLINE]).unwrap());

        let image: DynamicImageExt = from_bytes(str.as_bytes()).unwrap();

        assert_eq!(1, image.width());
        assert_eq!(2, image.height());
    }

    #[test]
    fn deserialize_optional() {
        let optional: Result<Option<String>> = from_bytes("missing value\n".as_bytes());

        assert_eq!(None, optional.unwrap());
    }

    #[test]
    fn deserialize_str() {
        let str: Result<String> = from_bytes("\"test\"\n".as_bytes());

        assert_eq!("test", str.unwrap());
    }

    #[test]
    fn deserialize_seq_empty() {
        let seq: Result<Vec<String>> = from_bytes("{}\n".as_bytes());

        assert_eq!(Vec::<String>::new(), seq.unwrap());
    }

    #[test]
    fn deserialize_seq_optional_str() {
        let seq_optional_str: Result<Vec<Option<String>>> = from_bytes("{\"test\", missing value}\n".as_bytes());

        assert_eq!(vec![Some("test".to_string()), None], seq_optional_str.unwrap());
    }
}