use crate::Result;
use crate::error::Error;
use crate::parser::HoconParser;
use crate::parser::read::Read;
use crate::raw::include::{Inclusion, Location};
pub(crate) const INCLUDE: &[u8] = b"include";
impl<'de, R: Read<'de>> HoconParser<R> {
pub(crate) fn parse_include(&mut self) -> Result<Inclusion> {
self.parse_include_token()?;
self.drop_horizontal_whitespace()?;
let required = self.parse_required_token()?;
let location = self.parse_location_token()?;
let include_path = self.parse_quoted_string(true)?;
for _ in [location.is_some(), required].iter().filter(|x| **x) {
self.drop_horizontal_whitespace()?;
let ch = self.reader.peek()?;
if ch != b')' {
return Err(Error::UnexpectedToken {
expected: ")",
found_beginning: ch,
});
} else {
self.reader.discard(1)?;
}
}
let inclusion = Inclusion::new(include_path.into(), required, location, None);
Ok(inclusion)
}
fn parse_include_token(&mut self) -> Result<()> {
let ch = self.reader.peek()?;
if ch != b'i' {
return Err(Error::UnexpectedToken {
expected: "include",
found_beginning: ch,
});
}
const N: usize = 7;
let bytes = self.reader.peek_n(N)?;
if bytes != INCLUDE {
let (_, ch) = bytes
.iter()
.enumerate()
.find(|(index, ch)| &&INCLUDE[*index] != ch)
.unwrap();
return Err(Error::UnexpectedToken {
expected: "include",
found_beginning: *ch,
});
}
self.reader.discard(N)?;
Ok(())
}
fn parse_required_token(&mut self) -> Result<bool> {
let mut required = false;
let ch = self.reader.peek()?;
const REQUIRED: &[u8] = b"required(";
if ch == b'r' {
for ele in REQUIRED {
let next = self.reader.next()?;
if *ele != next {
return Err(Error::UnexpectedToken {
expected: "required(",
found_beginning: next,
});
}
}
required = true
}
if required {
self.drop_horizontal_whitespace()?;
}
Ok(required)
}
fn parse_location_token(&mut self) -> Result<Option<Location>> {
let ch = self.reader.peek()?;
const FILE: &[u8] = b"file(";
let location = match ch {
b'f' => {
for ele in FILE {
let next = self.reader.next()?;
if *ele != next {
return Err(Error::UnexpectedToken {
expected: "file(",
found_beginning: next,
});
}
}
Some(Location::File)
}
b'u' => {
const URL: &[u8] = b"url(";
for ele in URL {
let next = self.reader.next()?;
if *ele != next {
return Err(Error::UnexpectedToken {
expected: "url(",
found_beginning: next,
});
}
}
Some(Location::Url)
}
b'c' => {
const CLASSPATH: &[u8] = b"classpath(";
for ele in CLASSPATH {
let next = self.reader.next()?;
if *ele != next {
return Err(Error::UnexpectedToken {
expected: "classpath(",
found_beginning: next,
});
}
}
Some(Location::Classpath)
}
b'"' => None,
ch => {
return Err(Error::UnexpectedToken {
expected: "file( or classpath( or url( or \"",
found_beginning: ch,
});
}
};
if location.is_some() {
self.drop_horizontal_whitespace()?;
}
Ok(location)
}
}
#[cfg(test)]
mod tests {
use crate::Result;
use crate::parser::HoconParser;
use crate::parser::read::StrRead;
use rstest::rstest;
#[rstest]
#[case("include \"demo\".conf", "include \"demo\"", ".conf")]
#[case("include\"demo.conf\"", "include \"demo.conf\"", "")]
#[case(
"include required( \"demo.conf\" )",
"include required(\"demo.conf\")",
""
)]
#[case(
"include required( file( \"demo.conf\" ))",
"include required(file(\"demo.conf\"))",
""
)]
fn test_valid_include(
#[case] input: &str,
#[case] expected: &str,
#[case] rest: &str,
) -> Result<()> {
let read = StrRead::new(input);
let mut parser = HoconParser::new(read);
let inclusion = parser.parse_include()?;
assert_eq!(inclusion.to_string(), expected);
assert_eq!(parser.reader.rest()?, rest);
Ok(())
}
#[rstest]
#[case("includedemo")]
#[case("include required (\"demo\")")]
#[case("include required(\"demo\",)")]
#[case("include required(\"demo\"")]
#[case("include required1(\"demo\")")]
#[case("include classpat(\"demo\")")]
#[case("include classpath(file(\"demo\"))")]
#[case("include classpath(required(\"demo\"))")]
fn test_invalid_include(#[case] input: &str) -> Result<()> {
let read = StrRead::new(input);
let mut parser = HoconParser::new(read);
let result = parser.parse_include();
assert!(result.is_err());
Ok(())
}
}