hocon-rs 0.2.0

Rust implementation of HOCON
Documentation
mod array;
mod comment;
mod include;
mod object;
pub mod read;
mod string;
mod substitution;

use crate::Result;
use crate::config_options::ConfigOptions;
use crate::error::Error;
use crate::parser::read::Read;
use crate::raw::raw_object::RawObject;

/// Low-level HOCON syntax parser.
///
/// Includes are preserved in the returned [`RawObject`] and are not loaded by
/// this type. Use [`crate::Config`] when includes should be resolved.
#[derive(Debug)]
pub struct HoconParser<R> {
    pub(crate) reader: R,
    pub(crate) scratch: Vec<u8>,
    pub(crate) options: ConfigOptions,
    pub(crate) depth: usize,
}

impl<'de, R: Read<'de>> HoconParser<R> {
    pub fn new(reader: R) -> Self {
        HoconParser {
            reader,
            scratch: vec![],
            options: Default::default(),
            depth: 0,
        }
    }

    pub fn with_options(reader: R, options: ConfigOptions) -> Self {
        HoconParser {
            reader,
            scratch: vec![],
            options,
            depth: 0,
        }
    }

    pub(crate) fn parse_horizontal_whitespace(&mut self, scratch: &mut Vec<u8>) -> Result<()> {
        loop {
            match self.reader.peek_horizontal_whitespace() {
                Ok(Some(n)) => {
                    for _ in 0..n {
                        let byte = self.reader.next()?;
                        scratch.push(byte);
                    }
                }
                Ok(None) | Err(Error::Eof) => break,
                Err(err) => return Err(err),
            }
        }
        Ok(())
    }

    pub(crate) fn drop_horizontal_whitespace(&mut self) -> Result<()> {
        loop {
            match self.reader.peek_horizontal_whitespace() {
                Ok(Some(n)) => {
                    self.reader.discard(n)?;
                }
                Ok(None) | Err(Error::Eof) => break,
                Err(err) => return Err(err),
            }
        }
        Ok(())
    }

    pub(crate) fn drop_whitespace(&mut self) -> Result<()> {
        loop {
            match self.reader.peek_whitespace() {
                Ok(Some(n)) => {
                    self.reader.discard(n)?;
                }
                Ok(None) | Err(Error::Eof) => break,
                Err(err) => return Err(err),
            }
        }
        Ok(())
    }

    pub(crate) fn drop_comma_separator(&mut self) -> Result<bool> {
        match self.reader.peek() {
            Ok(ch) => {
                if ch == b',' {
                    self.reader.discard(1)?;
                }
            }
            Err(Error::Eof) => return Ok(true),
            Err(err) => {
                return Err(err);
            }
        }
        Ok(false)
    }

    pub fn parse(&mut self) -> Result<RawObject> {
        self.drop_whitespace_and_comments()?;
        let raw_obj = match self.reader.peek() {
            Ok(ch) => {
                if ch == b'{' {
                    self.parse_object(false)?
                } else {
                    self.parse_braces_omitted_object()?
                }
            }
            Err(Error::Eof) => {
                return Ok(RawObject::default());
            }
            Err(err) => {
                return Err(err);
            }
        };
        self.drop_whitespace_and_comments()?;
        match self.reader.peek() {
            Ok(ch) => {
                return Err(Error::UnexpectedToken {
                    expected: "end of file",
                    found_beginning: ch,
                });
            }
            Err(Error::Eof) => {}
            Err(err) => {
                return Err(err);
            }
        }
        Ok(raw_obj)
    }
}

#[cfg(test)]
mod tests {
    use std::io::BufReader;

    use crate::Result;
    use crate::config_options::ConfigOptions;
    use crate::parser::HoconParser;
    use crate::parser::read::StreamRead;
    use rstest::rstest;

    #[rstest]
    #[case("resources/base.conf")]
    #[case("resources/concat.conf")]
    #[case("resources/concat2.conf")]
    #[case("resources/concat3.conf")]
    #[case("resources/demo.conf")]
    #[case("resources/deserialize.conf")]
    #[case("resources/empty.conf")]
    #[case("resources/included.conf")]
    #[case("resources/main.conf")]
    fn test_parse(#[case] path: impl AsRef<std::path::Path>) -> Result<()> {
        let file = std::fs::File::open(&path)?;
        let read = StreamRead::new(BufReader::new(file));
        let options = ConfigOptions::new(false, vec!["resources".to_string()]);
        let mut parser = HoconParser::with_options(read, options);
        parser.parse()?;
        Ok(())
    }
}