diff --git a/README.md b/README.md index 8ffcb24..48b5594 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ The path API can be seen as an generalization of the concepts behind xpath, json :warning: Hial is **currently under construction.** Some things don't work yet and some things will change. + ### What can it do? ##### 1. Select or search for pieces of data in a structured way. @@ -34,6 +35,7 @@ Print all services with inaccessible images in a Docker compose file: # shell hial './config.yaml^yaml/services/*[ /image^split[":"]/[0]^http[HEAD]@status/code>=400 ]' # 🚧 todo: split interpretation (regex[( ([^:]*): )*] +# 🚧 todo: get from docker image to docker url ``` ```rust @@ -54,7 +56,7 @@ Print the structure of a rust file (struct, enum, type, functions) as a tree: ```bash hial './src/tests/rust.rs^rust/**[#type^split["_"]/[-1]=="item"]/*[name|parameters|return_type]' # 🚧 todo: search results as tree -# 🚧 todo: boolean filter combinator +# 🚧 todo: split interpretation ``` ##### 2. Modify data selected as above. @@ -78,6 +80,7 @@ Change the user's docker configuration: ```bash # shell hial '~/.docker/config.json^json/auths/docker.io/username = "newuser"' +# 🚧 todo: assign operator ``` ```rust // rust @@ -93,7 +96,7 @@ Copy a string from some json object entry which is embedded in a zip file, into ```bash # shell -hial 'copy ./assets.zip^zip/data.json^json/meshes/sphere ./src/assets/sphere.rs^rust/**[#type=="let_declaration"][/pattern=sphere]/value' +hial 'copy ./assets.zip^zip/data.json^json/meshes/sphere ./src/assets/sphere.rs^rust/**[:let_declaration][/pattern=sphere]/value' # 🚧 todo: support copy # 🚧 todo: support zip # 🚧 todo: /**[filter] should match leaves only @@ -103,7 +106,7 @@ Split a markdown file into sections and put each in a separate file: ```bash # shell -`hial 'copy ./book.md^md/*[#type=="heading1"][as x] ./{label(x)}.md' +`hial 'copy ./book.md^md/*[:heading1][as x] ./{label(x)}.md' # 🚧 todo: support copy # 🚧 todo: support markdown # 🚧 todo: support interpolation in destination @@ -225,7 +228,7 @@ Examples: - `./src/main.rs@size` is the size of this file (the `size` attribute of the file). - `./src/main.rs^rust` represents the rust AST tree. -- `./src/main.rs^rust/*[#type=='function_item']` are all the top-level cells representing functions in the `main.rs` rust file. +- `./src/main.rs^rust/*[:function_item]` are all the top-level cells representing functions in the `main.rs` rust file. - `http://api.github.com` is a url cell. - `http://api.github.com^http` is the http response of a GET request to this url. diff --git a/doc/issues.md b/doc/issues.md index 932a7b0..8f4623d 100644 --- a/doc/issues.md +++ b/doc/issues.md @@ -1,14 +1,11 @@ # List of Todos and other Issues -- support ~ -- support type selector: `hial './src/tests/rust.rs^rust/*[:function_item]'` +- add http interpretation params: method=HEAD, accept="" - add split(":") and regex interpretations -- /*[name|parameters|return_type] ?? - set value on the command line: '/username = "newuser"' - https://raw.githubusercontent.com/rust-lang/rust/master/src/tools/rustfmt/src/lib.rs^http^rust does not work - '**[filter]' must be work as '**/*[filter]' (filter to be applied only on leaves) - support rust/ts write: `hial './src/tests/rust.rs^rust/*[:function_item].label = "modified_fn_name"'` -- add http interpretation params: method=HEAD, accept="" - support zip, markdown - support 'copy source destination' - support ^json^tree^xml diff --git a/src/search/parse.rs b/src/search/parse.rs index e3622e8..04f9526 100644 --- a/src/search/parse.rs +++ b/src/search/parse.rs @@ -1,6 +1,7 @@ use crate::{api::*, guard_ok, search::path::*, search::url::*}; use nom::character::complete::space0; use nom::error::VerboseErrorKind; +use nom::multi::separated_list1; use nom::{ branch::alt, bytes::complete::{escaped, tag}, @@ -174,20 +175,45 @@ fn filter(input: &str) -> NomRes<&str, Filter> { fn expression(input: &str) -> NomRes<&str, Expression> { context( "expression", - tuple((path_items, opt(tuple((operation, value))))), + tuple(( + space0, + separated_list1(tag("|"), alt((type_expression, ternary_expression))), + )), + )(input) + .map(|(next_input, res)| { + ( + next_input, + if res.1.len() == 1 { + res.1.into_iter().next().unwrap() + } else { + Expression::Or { expressions: res.1 } + }, + ) + }) +} + +fn ternary_expression(input: &str) -> NomRes<&str, Expression> { + context( + "ternary expression", + tuple((path_items, space0, opt(tuple((operation, space0, rvalue))))), )(input) .map(|(next_input, res)| { ( next_input, - Expression { + Expression::Ternary { left: res.0, - op: res.1.map(|x| x.0), - right: res.1.map(|x| x.1), + op: res.2.map(|x| x.0), + right: res.2.map(|x| x.2), }, ) }) } +fn type_expression(input: &str) -> NomRes<&str, Expression> { + context("type expression", tuple((tag(":"), identifier_code_points)))(input) + .map(|(next_input, res)| (next_input, Expression::Type { ty: res.1 })) +} + fn interpretation_param(input: &str) -> NomRes<&str, InterpretationParam> { context( "interpretation parameter", @@ -197,7 +223,7 @@ fn interpretation_param(input: &str) -> NomRes<&str, InterpretationParam> { space0, identifier_code_points, space0, - opt(tuple((tag("="), space0, value))), + opt(tuple((tag("="), space0, rvalue))), )), tag("]"), ), @@ -226,8 +252,13 @@ fn relation(input: &str) -> NomRes<&str, char> { .map(|(next_input, res)| (next_input, res.chars().next().unwrap())) } -fn value(input: &str) -> NomRes<&str, Value> { - context("value", alt((value_string, value_uint)))(input) +fn rvalue(input: &str) -> NomRes<&str, Value> { + context("value", alt((value_ident, value_string, value_uint)))(input) +} + +fn value_ident(input: &str) -> NomRes<&str, Value> { + context("value ident", identifier_code_points)(input) + .map(|(next_input, res)| (next_input, Value::Str(res))) } fn value_string(input: &str) -> NomRes<&str, Value> { diff --git a/src/search/path.rs b/src/search/path.rs index 6856b21..207c350 100644 --- a/src/search/path.rs +++ b/src/search/path.rs @@ -47,22 +47,39 @@ pub(crate) struct Filter<'a> { } #[derive(Clone, Debug, PartialEq)] -pub(crate) struct Expression<'a> { - pub(crate) left: Path<'a>, - pub(crate) op: Option<&'a str>, - pub(crate) right: Option>, +pub(crate) enum Expression<'a> { + Ternary { + left: Path<'a>, + op: Option<&'a str>, + right: Option>, + }, + Type { + ty: &'a str, + }, + Or { + expressions: Vec>, + }, } impl Display for Path<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - fmt_path_items(&self.0, f)?; + for it in &self.0 { + write!(f, "{}", it)?; + } Ok(()) } } impl Display for PathItem<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - fmt_path_item(self, f)?; + match self { + PathItem::Elevation(e) => { + write!(f, "{}", e)?; + } + PathItem::Normal(n) => { + write!(f, "{}", n)?; + } + } Ok(()) } } @@ -120,14 +137,27 @@ impl Display for Filter<'_> { impl Display for Expression<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - fmt_path_items(&self.left.0, f)?; - if let Some(op) = self.op { - write!(f, "{}", op)?; - } - match self.right { - Some(Value::Str(s)) => write!(f, "'{}'", s)?, - Some(v) => write!(f, "{}", v)?, - None => {} + match self { + Expression::Ternary { left, op, right } => { + write!(f, "{}", left)?; + if let Some(op) = op { + write!(f, "{}", op)?; + } + if let Some(right) = right { + write!(f, "{:?}", right)?; + } + } + Expression::Type { ty } => { + write!(f, ":{}", ty)?; + } + Expression::Or { expressions } => { + for (i, expr) in expressions.iter().enumerate() { + write!(f, "{}", expr)?; + if i < expressions.len() - 1 { + write!(f, "|")?; + } + } + } } Ok(()) } @@ -148,7 +178,7 @@ pub(crate) struct DisplayRefPath<'a, 'b: 'a, 'c: 'b>(pub(crate) &'c Vec<&'b Path impl<'a, 'b: 'a, 'c: 'b> Display for DisplayRefPath<'a, 'b, 'c> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { for it in self.0 { - fmt_path_item(it, f)? + write!(f, "{}", it)?; } Ok(()) } @@ -159,31 +189,12 @@ pub(crate) struct DisplayPath<'a, 'b: 'a>(pub(crate) &'b Vec>); impl<'a, 'b: 'a> Display for DisplayPath<'a, 'b> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { for it in self.0 { - fmt_path_item(it, f)? + write!(f, "{}", it)?; } Ok(()) } } -fn fmt_path_items(path_items: &Vec, f: &mut Formatter<'_>) -> std::fmt::Result { - for it in path_items { - fmt_path_item(it, f)? - } - Ok(()) -} - -fn fmt_path_item(path_item: &PathItem, f: &mut Formatter<'_>) -> std::fmt::Result { - match path_item { - PathItem::Elevation(e) => { - write!(f, "{}", e)?; - } - PathItem::Normal(n) => { - write!(f, "{}", n)?; - } - } - Ok(()) -} - impl<'a> PathStart<'a> { pub fn eval(&self) -> Res { match self { diff --git a/src/search/searcher.rs b/src/search/searcher.rs index bea39eb..f511dba 100644 --- a/src/search/searcher.rs +++ b/src/search/searcher.rs @@ -374,7 +374,7 @@ impl<'s> Searcher<'s> { fn eval_filters_match(subcell: &Xell, path_item: &NormalPathItem) -> bool { for filter in &path_item.filters { - match Searcher::eval_bool_expression(subcell.clone(), &filter.expr) { + match Searcher::eval_expression(subcell.clone(), &filter.expr) { Err(e) => { ifdebug!(println!("eval_bool_expression failed")); debug_err!(e); @@ -394,9 +394,31 @@ impl<'s> Searcher<'s> { true } - fn eval_bool_expression(cell: Xell, expr: &Expression<'s>) -> Res { + fn eval_expression(cell: Xell, expr: &Expression<'s>) -> Res { + match expr { + Expression::Ternary { left, op, right } => { + Self::eval_ternary_expression(cell, left.clone(), *op, *right) + } + Expression::Type { ty } => cell.read().ty().map(|t| t == *ty), + Expression::Or { expressions } => { + for expr in expressions { + if Self::eval_expression(cell.clone(), expr)? { + return Ok(true); + } + } + Ok(false) + } + } + } + + fn eval_ternary_expression( + cell: Xell, + left: Path<'s>, + op: Option<&'s str>, + right: Option>, + ) -> Res { ifdebug!(println!( - "{{{{\neval_bool_expression cell `{}` for expr `{}`", + "{{{{\neval_ternary_expression cell `{}` for expr `{}`", cell.debug_string(), expr )); @@ -412,14 +434,14 @@ impl<'s> Searcher<'s> { } } - let eval_iter_left = Self::new(cell, expr.left.clone()); + let eval_iter_left = Self::new(cell, left); for cell in eval_iter_left { let cell = guard_ok!(cell, err => { debug_err!(err); continue; }); - if let Some(op) = expr.op { - if let Some(right) = expr.right { + if let Some(op) = op { + if let Some(right) = right { let reader = guard_ok!(cell.read().err(), err => { debug_err!(err); continue; diff --git a/src/search/url.rs b/src/search/url.rs index b2a7470..566b23b 100644 --- a/src/search/url.rs +++ b/src/search/url.rs @@ -253,15 +253,12 @@ where T: InputTakeAtPosition, ::Item: AsChar, { + let accept = + |c: char| c == '-' || c == '_' || c == '.' || c == ':' || c == '*' || c.is_alphanum(); i.split_at_position1_complete( |item| { let char_item = item.as_char(); - !(char_item == '-' - || char_item == '_' - || char_item == '.' - || char_item == ':' - || char_item == '*' - || char_item.is_alphanum()) + !accept(char_item) }, ErrorKind::AlphaNumeric, ) @@ -272,10 +269,11 @@ where T: InputTakeAtPosition, ::Item: AsChar, { + let accept = |c: char| c == '-' || c == '.' || c.is_alphanum(); i.split_at_position1_complete( |item| { let char_item = item.as_char(); - !(char_item == '-' || char_item.is_alphanum() || char_item == '.') + !accept(char_item) // ... actual ascii code points and url encoding...: https://infra.spec.whatwg.org/#ascii-code-point }, ErrorKind::AlphaNumeric, @@ -287,10 +285,11 @@ where T: InputTakeAtPosition, ::Item: AsChar, { + let accept = |c: char| c == '_' || c.is_alphanumeric(); i.split_at_position1_complete( |item| { let char_item = item.as_char(); - !(char_item.is_alphanum()) + !accept(char_item) }, ErrorKind::AlphaNumeric, ) diff --git a/src/tests/search.rs b/src/tests/search.rs index edd88dc..ca904db 100644 --- a/src/tests/search.rs +++ b/src/tests/search.rs @@ -51,7 +51,7 @@ fn path_simple_elevation() -> Res<()> { // } #[test] -fn path_simple_item() -> Res<()> { +fn path_simple_selector() -> Res<()> { let path = Path::parse("/a[2]")?; assert_eq!( path.0.as_slice(), @@ -76,6 +76,86 @@ fn path_simple_item() -> Res<()> { Ok(()) } +#[test] +fn path_simple_type_expr() -> Res<()> { + let path = Path::parse("/a[:fn_item0]")?; + assert_eq!( + path.0.as_slice(), + &[PathItem::Normal(NormalPathItem { + relation: Relation::Sub, + selector: Some(Selector::Str("a")), + index: None, + filters: vec![Filter { + expr: Expression::Type { ty: "fn_item0" } + }], + })] + ); + Ok(()) +} + +#[test] +fn path_ternary_expr() -> Res<()> { + let path = Path::parse("/a[@attr==1][/x]")?; + assert_eq!( + path.0.as_slice(), + &[PathItem::Normal(NormalPathItem { + relation: Relation::Sub, + selector: Some(Selector::Str("a")), + index: None, + filters: vec![ + Filter { + expr: Expression::Ternary { + left: Path(vec![PathItem::Normal(NormalPathItem { + relation: Relation::Attr, + selector: Some(Selector::Str("attr")), + index: None, + filters: vec![], + })]), + op: Some("=="), + right: Some(Value::Int(1.into())) + } + }, + Filter { + expr: Expression::Ternary { + left: Path(vec![PathItem::Normal(NormalPathItem { + relation: Relation::Sub, + selector: Some(Selector::Str("x")), + index: None, + filters: vec![], + })]), + op: None, + right: None, + } + } + ], + })] + ); + Ok(()) +} + +#[test] +fn path_simple_or_expr() -> Res<()> { + let path = Path::parse("/a[:x|:y|:z]")?; + assert_eq!( + path.0.as_slice(), + &[PathItem::Normal(NormalPathItem { + relation: Relation::Sub, + selector: Some(Selector::Str("a")), + index: None, + filters: vec![Filter { + expr: Expression::Or { + expressions: vec![ + Expression::Type { ty: "x" }, + Expression::Type { ty: "y" }, + Expression::Type { ty: "z" } + ] + } + }], + })] + ); + Ok(()) +} + #[test] fn path_items() -> Res<()> { let path = Path::parse("/a@name/[2]/*[#value=='3'][/x]")?; @@ -106,7 +186,7 @@ fn path_items() -> Res<()> { index: None, filters: vec![ Filter { - expr: Expression { + expr: Expression::Ternary { left: Path(vec![PathItem::Normal(NormalPathItem { relation: Relation::Field, selector: Some("value".into()), @@ -118,7 +198,7 @@ fn path_items() -> Res<()> { } }, Filter { - expr: Expression { + expr: Expression::Ternary { left: Path(vec![PathItem::Normal(NormalPathItem { relation: Relation::Sub, selector: Some("x".into()),