diff --git a/src/ast.rs b/src/ast.rs index e692ba6..27f2661 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -100,7 +100,9 @@ where pub name: T, pub args: Vec, pub children: Option>>, - pub(crate) _scheme: PhantomData, + pub _scheme: PhantomData, + pub is_comment: bool, + pub newline: bool, } impl Debug for Directive @@ -110,7 +112,9 @@ where { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut ds = f.debug_struct("Directive"); - ds.field("name", &self.name).field("args", &self.args); + ds.field("name", &self.name) + .field("args", &self.args) + .field("is_comment", &self.is_comment); if let Some(children) = self.children.as_ref() { ds.field("children", children); } @@ -143,31 +147,83 @@ where S: Clone + Default, T: FromLiteral + AsRef, { - pub fn query(&self, path: &str) -> Vec { + pub fn query(&mut self, path: &str) -> Vec<&mut Self> { let mut result = vec![]; - if let Some(childs) = self.children.as_ref() { - Self::inner_query(childs, path, &mut result); - } + Self::inner_query(vec![self], path, &mut result); result } - fn inner_query(dirs: &[Self], path: &str, out: &mut Vec) { + fn inner_query<'a>(dirs: Vec<&'a mut Self>, path: &str, out: &mut Vec<&'a mut Self>) { let mut pathitem = path; let mut rest = None; if let Some((i, r)) = path.split_once("/") { pathitem = i; rest.replace(r); } - for d in dirs.iter() { + for d in dirs { if d.name.as_ref().eq_ignore_ascii_case(pathitem) { if let Some(path) = rest { Self::inner_query( - d.children.as_ref().map(Vec::as_slice).unwrap_or(&[]), + d.children.iter_mut().flatten().collect::>(), path, out, ); } else { - out.push(d.clone()); + out.push(d); + } + } + } + } + + pub fn remove(&mut self, path: &str, values: Option<&[&str]>) -> &mut Self { + self.inner_remove(path, values); + self + } + + fn inner_remove(&mut self, path: &str, values: Option<&[&str]>) { + let mut pathitem = path; + let mut rest = None; + + if let Some((i, r)) = path.split_once("/") { + pathitem = i; + rest.replace(r); + } + + if self.name.as_ref().eq_ignore_ascii_case(pathitem) + && self.children.is_some() + && rest.is_some() + { + let rest = rest.unwrap(); + let mut i = 0; + let childs = self.children.as_mut().unwrap(); + while i < childs.len() { + let c = childs[i].as_mut(); + let mut removed = false; + + if rest.split_once("/").is_some() { + c.remove(rest, values); + } else if c.name.as_ref().eq_ignore_ascii_case(rest) { + let mut all_same = true; + if values.is_some() { + let values = values.unwrap(); + if values.len() == c.args.len() { + let mut ia = c.args.iter(); + values.iter().any(|x| { + all_same = ia.next().unwrap().as_ref().contains(x); + !all_same + }); + } else { + all_same = false; + } + } + if all_same { + childs.remove(i); + removed = true; + } + } + + if !removed { + i += 1; } } } @@ -212,3 +268,77 @@ where } } } + +impl Directive +where + S: Clone + Default, +{ + pub fn to_string(&self, depth: usize) -> std::string::String { + let mut result = String::new(); + + if self.newline { + result += "\n"; + result += &"\t".repeat(depth - 1); + } + + if self.children.is_some() { + result += "\n"; + result += &"\t".repeat(depth - 1); + } + + // special for '' $http_connection + let name = if self.name.is_empty() && !self.args.is_empty() { + "''".to_string() + } else { + self.name.clone() + }; + + if self.is_comment { + result += &format!("#{name}\n"); + return result; + } + + result += name.as_str(); + let mut need_com = false; + if self.args.len() > 0 { + result += " "; + result += self.args.join(" ").as_str(); + if let Some(last) = self.args.last() { + need_com = !last.ends_with(";"); + } + } + + if let Some(children) = &self.children { + if !self.name.is_empty() { + result += " {"; + if children.len() > 0 { + result += children + .iter() + .map(|x| "\t".repeat(depth).to_string() + &x.to_string(depth + 1)) + .collect::() + .as_str(); + } else { + result += "\n"; + } + result += ("\t".repeat(depth - 1).to_string() + "}\n").as_str(); + } else { + if need_com { + result += ";"; + } + if children.len() > 0 { + result += children + .iter() + .map(|x| x.to_string(depth)) + .collect::() + .as_str(); + } + } + } else { + if need_com { + result += ";"; + } + result += "\n"; + } + result + } +} diff --git a/src/nginx/lexer.rs b/src/nginx/lexer.rs index 3cc792e..144e30e 100644 --- a/src/nginx/lexer.rs +++ b/src/nginx/lexer.rs @@ -26,6 +26,7 @@ pub enum Token<'a> { NewLine, Eof, Literal(Literal<'a>), + Comment(Literal<'a>), } impl<'a> Token<'a> { @@ -62,18 +63,6 @@ fn semicolon(input: &[u8]) -> IResult<&[u8], Token> { value(Token::Semicolon, tag(b";"))(input) } -fn comment(input: &[u8]) -> IResult<&[u8], &[u8]> { - map( - tuple(( - tag("#"), - take_till(is_newline), - opt(tag(b"\r")), - opt(tag(b"\n")), - )), - |x| x.1, - )(input) -} - fn literal(input: &[u8]) -> IResult<&[u8], Token> { let (_, mut first) = be_u8(input)?; @@ -105,7 +94,7 @@ fn literal(input: &[u8]) -> IResult<&[u8], Token> { _ => { first = 0; map_res( - escaped(none_of(" \t\r\n;'\"\\"), '\\', anychar), + escaped(none_of("{ \t\r\n;'\"\\"), '\\', anychar), std::str::from_utf8, )(input) } @@ -113,22 +102,36 @@ fn literal(input: &[u8]) -> IResult<&[u8], Token> { Ok((input, Token::Literal(Literal { raw, quote: first }))) } +fn tocomment(input: &[u8]) -> IResult<&[u8], Token> { + map(tuple((tag("#"), take_till(is_newline))), |x| { + Token::Comment(Literal { + raw: std::str::from_utf8(x.1).unwrap_or_default(), + quote: b'#', + }) + })(input) +} + pub fn tokenizer(mut input: &[u8]) -> IResult<&[u8], Token> { loop { - let (rest, cmt) = space_and_comment(input)?; + let (rest, cmt) = multispace0(input)?; + // println!("{cmt:?}"); + // if let Some(c) = cmt.clone() { + // // println!("{}", String::from_utf8_lossy(rest).to_string()); + // println!("{}", String::from_utf8(c).unwrap_or_default()); + // } + // println!("{} {} {} - {:?}", rest.len(), cmt.len(), input.len(), cmt); + // println!("{}", String::from_utf8_lossy(cmt).to_string()); input = rest; - if cmt.is_none() { + if cmt.len() == 0 { break; } } + // println!("{}", String::from_utf8_lossy(input).to_string()); + if input.len() == 0 { return Ok((input, Token::Eof)); } - alt((starttag, endtag, semicolon, literal))(input) -} - -fn space_and_comment(input: &[u8]) -> IResult<&[u8], Option<&[u8]>> { - map(tuple((multispace0, opt(comment))), |x| x.1)(input) + alt((starttag, endtag, tocomment, semicolon, literal))(input) } diff --git a/src/nginx/mod.rs b/src/nginx/mod.rs index e5ef7c2..723d464 100644 --- a/src/nginx/mod.rs +++ b/src/nginx/mod.rs @@ -24,19 +24,27 @@ pub struct Nginx; impl DirectiveTrait for Directive { fn parse(input: &[u8]) -> anyhow::Result> { - let res = parse_block(input).map_err(|err| { - err.map(|e| { - let errs = e - .errors - .iter() - .map(|(i, code)| { - let ((l, c), pos) = line_column2(input, i).unwrap(); - format!("0x{pos:x}({l}:{c}) err: {:?}", code) - }) - .collect::>(); - anyhow::anyhow!("{}", errs.join("\n")) + let res = parse_block(input) + .map(|(i, r)| { + let r = Directive { + children: Some(r), + ..Directive::default() + }; + (i, vec![r]) }) - })?; + .map_err(|err| { + err.map(|e| { + let errs = e + .errors + .iter() + .map(|(i, code)| { + let ((l, c), pos) = line_column2(input, i).unwrap(); + format!("0x{pos:x}({l}:{c}) err: {:?}", code) + }) + .collect::>(); + anyhow::anyhow!("{}", errs.join("\n")) + }) + })?; Ok(res.1) } @@ -64,7 +72,14 @@ impl DirectiveTrait for Directive { .flatten() { let data = std::fs::read(res.resolve(&path)?)?; - for c in Self::parse(&data).with_context(|| format!("parse {path:?}"))? { + let mut sub = Self::parse(&data) + .with_context(|| format!("parse {path:?}"))?; + let sub = sub + .iter_mut() + .flat_map(|x|x.children.as_mut().cloned()) + .flatten() + .collect::>(); + for c in sub { c.resolve_include_inner(dir, out, res)?; } } @@ -87,6 +102,7 @@ fn parse_literal(input: &[u8]) -> IResult<&[u8], Literal<'_>> { fn parse_block(mut input: &[u8]) -> IResult<&[u8], Vec>> { let mut result = vec![]; + let mut before_is_literal = None; loop { let mut d = Directive::default(); let (rest, tag) = tokenizer(input).map_err(|err| { @@ -96,12 +112,29 @@ fn parse_block(mut input: &[u8]) -> IResult<&[u8], Vec>> { let lit = match tag { Token::Literal(lit) => lit, Token::BlockEnd | Token::Eof => break, + Token::Comment(c) => { + d.is_comment = true; + d.name = c.raw.to_string(); + if before_is_literal != Some(false) { + before_is_literal = Some(false); + d.newline = true; + } + result.push(d); + input = rest; + continue; + } _ => return fail(input), }; + + if before_is_literal != Some(true) { + d.newline = true; + } + before_is_literal = Some(true); d.name = lit.clone().into(); let (rest, args) = map(many0(parse_literal), |v| { v.into_iter().map(Into::into).collect() })(rest)?; + d.args = args; let (rest, tok) = tokenizer(rest)?; @@ -140,6 +173,7 @@ fn parse_block(mut input: &[u8]) -> IResult<&[u8], Vec>> { VerboseError::from_error_kind(input, nom::error::ErrorKind::Fail), ))); } + d.newline = false; input = rest; } _ => {