mirror of
https://github.com/nushell/nushell.git
synced 2024-11-25 01:43:47 +01:00
Extract out xpath to a plugin. (#2661)
This commit is contained in:
parent
2573441e28
commit
4e931fa73f
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -2890,6 +2890,7 @@ dependencies = [
|
|||||||
"nu_plugin_to_bson",
|
"nu_plugin_to_bson",
|
||||||
"nu_plugin_to_sqlite",
|
"nu_plugin_to_sqlite",
|
||||||
"nu_plugin_tree",
|
"nu_plugin_tree",
|
||||||
|
"nu_plugin_xpath",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"serde 1.0.115",
|
"serde 1.0.115",
|
||||||
"toml",
|
"toml",
|
||||||
@ -3367,6 +3368,21 @@ dependencies = [
|
|||||||
"ptree",
|
"ptree",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu_plugin_xpath"
|
||||||
|
version = "0.20.0"
|
||||||
|
dependencies = [
|
||||||
|
"bigdecimal",
|
||||||
|
"indexmap",
|
||||||
|
"nu-errors",
|
||||||
|
"nu-plugin",
|
||||||
|
"nu-protocol",
|
||||||
|
"nu-source",
|
||||||
|
"nu-test-support",
|
||||||
|
"sxd-document",
|
||||||
|
"sxd-xpath",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num"
|
name = "num"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -43,6 +43,7 @@ nu_plugin_textview = {version = "0.20.0", path = "./crates/nu_plugin_textview",
|
|||||||
nu_plugin_to_bson = {version = "0.20.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
nu_plugin_to_bson = {version = "0.20.0", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||||
nu_plugin_to_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
nu_plugin_to_sqlite = {version = "0.20.0", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||||
nu_plugin_tree = {version = "0.20.0", path = "./crates/nu_plugin_tree", optional = true}
|
nu_plugin_tree = {version = "0.20.0", path = "./crates/nu_plugin_tree", optional = true}
|
||||||
|
nu_plugin_xpath = {version = "0.20.0", path = "./crates/nu_plugin_xpath", optional = true}
|
||||||
|
|
||||||
# Required to bootstrap the main binary
|
# Required to bootstrap the main binary
|
||||||
clap = "2.33.3"
|
clap = "2.33.3"
|
||||||
@ -88,7 +89,7 @@ default = [
|
|||||||
"fetch",
|
"fetch",
|
||||||
"rich-benchmark",
|
"rich-benchmark",
|
||||||
]
|
]
|
||||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart"]
|
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart", "xpath"]
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
|
|
||||||
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
|
wasi = ["inc", "match", "directories-support", "ptree-support", "match", "tree", "rustyline-support"]
|
||||||
@ -114,6 +115,7 @@ sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
|||||||
start = ["nu_plugin_start"]
|
start = ["nu_plugin_start"]
|
||||||
trash-support = ["nu-cli/trash-support"]
|
trash-support = ["nu-cli/trash-support"]
|
||||||
tree = ["nu_plugin_tree"]
|
tree = ["nu_plugin_tree"]
|
||||||
|
xpath = ["nu_plugin_xpath"]
|
||||||
|
|
||||||
# Core plugins that ship with `cargo install nu` by default
|
# Core plugins that ship with `cargo install nu` by default
|
||||||
# Currently, Cargo limits us to installing only one binary
|
# Currently, Cargo limits us to installing only one binary
|
||||||
@ -185,6 +187,11 @@ name = "nu_plugin_extra_chart_line"
|
|||||||
path = "src/plugins/nu_plugin_extra_chart_line.rs"
|
path = "src/plugins/nu_plugin_extra_chart_line.rs"
|
||||||
required-features = ["chart"]
|
required-features = ["chart"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_extra_xpath"
|
||||||
|
path = "src/plugins/nu_plugin_extra_xpath.rs"
|
||||||
|
required-features = ["xpath"]
|
||||||
|
|
||||||
# Main nu binary
|
# Main nu binary
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu"
|
name = "nu"
|
||||||
|
@ -245,7 +245,6 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
|
|||||||
whole_stream_command(FromURL),
|
whole_stream_command(FromURL),
|
||||||
whole_stream_command(FromXLSX),
|
whole_stream_command(FromXLSX),
|
||||||
whole_stream_command(FromXML),
|
whole_stream_command(FromXML),
|
||||||
whole_stream_command(XPath),
|
|
||||||
whole_stream_command(FromYAML),
|
whole_stream_command(FromYAML),
|
||||||
whole_stream_command(FromYML),
|
whole_stream_command(FromYML),
|
||||||
whole_stream_command(FromIcs),
|
whole_stream_command(FromIcs),
|
||||||
|
@ -126,7 +126,6 @@ pub(crate) mod where_;
|
|||||||
pub(crate) mod which_;
|
pub(crate) mod which_;
|
||||||
pub(crate) mod with_env;
|
pub(crate) mod with_env;
|
||||||
pub(crate) mod wrap;
|
pub(crate) mod wrap;
|
||||||
pub(crate) mod xpath;
|
|
||||||
|
|
||||||
pub(crate) use autoview::Autoview;
|
pub(crate) use autoview::Autoview;
|
||||||
pub(crate) use cd::Cd;
|
pub(crate) use cd::Cd;
|
||||||
@ -271,7 +270,6 @@ pub(crate) use where_::Where;
|
|||||||
pub(crate) use which_::Which;
|
pub(crate) use which_::Which;
|
||||||
pub(crate) use with_env::WithEnv;
|
pub(crate) use with_env::WithEnv;
|
||||||
pub(crate) use wrap::Wrap;
|
pub(crate) use wrap::Wrap;
|
||||||
pub(crate) use xpath::XPath;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -1,150 +0,0 @@
|
|||||||
extern crate sxd_document;
|
|
||||||
extern crate sxd_xpath;
|
|
||||||
use crate::commands::WholeStreamCommand;
|
|
||||||
use crate::prelude::*;
|
|
||||||
use bigdecimal::FromPrimitive;
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
|
|
||||||
use nu_source::Tagged;
|
|
||||||
use sxd_document::parser;
|
|
||||||
use sxd_xpath::{Context, Factory};
|
|
||||||
|
|
||||||
pub struct XPath;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct XPathArgs {
|
|
||||||
query: Tagged<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl WholeStreamCommand for XPath {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"xpath"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::build("xpath").required("query", SyntaxShape::String, "xpath query")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
|
||||||
"execute xpath query on xml"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
|
||||||
vec![Example {
|
|
||||||
description: "find items with name attribute",
|
|
||||||
example: r#"echo '<?xml version="1.0" encoding="UTF-8"?><main><nushell rocks="true"/></main>' | from xml | to xml | xpath '//nushell/@rocks'"#,
|
|
||||||
result: None,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(
|
|
||||||
&self,
|
|
||||||
args: CommandArgs,
|
|
||||||
registry: &CommandRegistry,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
|
||||||
let tag = args.call_info.name_tag.clone();
|
|
||||||
let (XPathArgs { query }, input) = args.process(®istry).await?;
|
|
||||||
|
|
||||||
let query_string = query.as_str();
|
|
||||||
let input_string = input.collect_string(tag.clone()).await?.item;
|
|
||||||
let result_string = execute_xpath_query(input_string, query_string.to_string());
|
|
||||||
|
|
||||||
match result_string {
|
|
||||||
Some(r) => Ok(
|
|
||||||
futures::stream::iter(r.into_iter().map(ReturnSuccess::value)).to_output_stream(),
|
|
||||||
),
|
|
||||||
None => Err(ShellError::labeled_error(
|
|
||||||
"xpath query error",
|
|
||||||
"xpath query error",
|
|
||||||
query.tag(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn execute_xpath_query(input_string: String, query_string: String) -> Option<Vec<Value>> {
|
|
||||||
let xpath = build_xpath(&query_string);
|
|
||||||
let package = parser::parse(&input_string).expect("failed to parse xml");
|
|
||||||
let document = package.as_document();
|
|
||||||
let context = Context::new();
|
|
||||||
|
|
||||||
// leaving this here for augmentation at some point
|
|
||||||
// build_variables(&arguments, &mut context);
|
|
||||||
// build_namespaces(&arguments, &mut context);
|
|
||||||
|
|
||||||
let res = xpath.evaluate(&context, document.root());
|
|
||||||
|
|
||||||
// Some xpath statements can be long, so let's truncate it with ellipsis
|
|
||||||
let mut key = query_string.clone();
|
|
||||||
if query_string.len() >= 20 {
|
|
||||||
key.truncate(17);
|
|
||||||
key += "...";
|
|
||||||
} else {
|
|
||||||
key = query_string;
|
|
||||||
};
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Ok(r) => {
|
|
||||||
let rows: Vec<Value> = match r {
|
|
||||||
sxd_xpath::Value::Nodeset(ns) => ns
|
|
||||||
.into_iter()
|
|
||||||
.map(|a| {
|
|
||||||
let mut row = TaggedDictBuilder::new(Tag::unknown());
|
|
||||||
row.insert_value(&key, UntaggedValue::string(a.string_value()));
|
|
||||||
row.into_value()
|
|
||||||
})
|
|
||||||
.collect::<Vec<Value>>(),
|
|
||||||
sxd_xpath::Value::Boolean(b) => {
|
|
||||||
let mut row = TaggedDictBuilder::new(Tag::unknown());
|
|
||||||
row.insert_value(&key, UntaggedValue::boolean(b));
|
|
||||||
vec![row.into_value()]
|
|
||||||
}
|
|
||||||
sxd_xpath::Value::Number(n) => {
|
|
||||||
let mut row = TaggedDictBuilder::new(Tag::unknown());
|
|
||||||
row.insert_value(
|
|
||||||
&key,
|
|
||||||
UntaggedValue::decimal(BigDecimal::from_f64(n).expect("error with f64"))
|
|
||||||
.into_untagged_value(),
|
|
||||||
);
|
|
||||||
|
|
||||||
vec![row.into_value()]
|
|
||||||
}
|
|
||||||
sxd_xpath::Value::String(s) => {
|
|
||||||
let mut row = TaggedDictBuilder::new(Tag::unknown());
|
|
||||||
row.insert_value(&key, UntaggedValue::string(s));
|
|
||||||
vec![row.into_value()]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !rows.is_empty() {
|
|
||||||
Some(rows)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_xpath(xpath_str: &str) -> sxd_xpath::XPath {
|
|
||||||
let factory = Factory::new();
|
|
||||||
|
|
||||||
factory
|
|
||||||
.build(xpath_str)
|
|
||||||
.unwrap_or_else(|e| panic!("Unable to compile XPath {}: {}", xpath_str, e))
|
|
||||||
.expect("error with building the xpath factory")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::ShellError;
|
|
||||||
use super::XPath;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn examples_work_as_expected() -> Result<(), ShellError> {
|
|
||||||
use crate::examples::test as test_examples;
|
|
||||||
|
|
||||||
Ok(test_examples(XPath {})?)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
|
|
||||||
use nu_test_support::playground::Playground;
|
|
||||||
use nu_test_support::{nu, pipeline};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn position_function_in_predicate() {
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: ".", pipeline(
|
|
||||||
r#"
|
|
||||||
echo "<?xml version="1.0" encoding="UTF-8"?><a><b/><b/></a>" | from xml | to xml | xpath "count(//a/*[position() = 2])"
|
|
||||||
"#
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "1.0000");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn functions_implicitly_coerce_argument_types() {
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: ".", pipeline(
|
|
||||||
r#"
|
|
||||||
echo "<?xml version="1.0" encoding="UTF-8"?><a>true</a>" | from xml | to xml | xpath "count(//*[contains(., true)])"
|
|
||||||
"#
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "1.0000");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn find_guid_permilink_is_true() {
|
|
||||||
let actual = nu!(
|
|
||||||
cwd: "tests/fixtures/formats", pipeline(
|
|
||||||
r#"
|
|
||||||
open jonathan.xml
|
|
||||||
| to xml
|
|
||||||
| xpath '//guid/@isPermaLink'
|
|
||||||
"#
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(actual.out, "true");
|
|
||||||
}
|
|
24
crates/nu_plugin_xpath/Cargo.toml
Normal file
24
crates/nu_plugin_xpath/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["The Nu Project Contributors"]
|
||||||
|
description = "Traverses xml"
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
name = "nu_plugin_xpath"
|
||||||
|
version = "0.20.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nu-plugin = {path = "../nu-plugin", version = "0.20.0"}
|
||||||
|
nu-errors = {version = "0.20.0", path = "../nu-errors"}
|
||||||
|
nu-protocol = {version = "0.20.0", path = "../nu-protocol"}
|
||||||
|
nu-source = {version = "0.20.0", path = "../nu-source"}
|
||||||
|
|
||||||
|
sxd-xpath = "0.4.2"
|
||||||
|
sxd-document = "0.3.2"
|
||||||
|
bigdecimal = {version = "0.2.0", features = ["serde"]}
|
||||||
|
indexmap = {version = "1.6.0", features = ["serde-1"]}
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
nu-test-support = {path = "../nu-test-support", version = "0.20.0"}
|
4
crates/nu_plugin_xpath/src/lib.rs
Normal file
4
crates/nu_plugin_xpath/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
mod nu;
|
||||||
|
mod xpath;
|
||||||
|
|
||||||
|
pub use xpath::Xpath;
|
6
crates/nu_plugin_xpath/src/main.rs
Normal file
6
crates/nu_plugin_xpath/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use nu_plugin::serve_plugin;
|
||||||
|
use nu_plugin_xpath::Xpath;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
serve_plugin(&mut Xpath::new());
|
||||||
|
}
|
49
crates/nu_plugin_xpath/src/nu/mod.rs
Normal file
49
crates/nu_plugin_xpath/src/nu/mod.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_plugin::Plugin;
|
||||||
|
use nu_protocol::{
|
||||||
|
CallInfo, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape, UntaggedValue, Value,
|
||||||
|
};
|
||||||
|
use nu_source::TaggedItem;
|
||||||
|
|
||||||
|
use crate::{xpath::string_to_value, Xpath};
|
||||||
|
|
||||||
|
impl Plugin for Xpath {
|
||||||
|
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||||
|
Ok(Signature::build("xpath")
|
||||||
|
.desc("execute xpath query on xml")
|
||||||
|
.required("query", SyntaxShape::String, "xpath query")
|
||||||
|
.filter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||||
|
let tag = call_info.name_tag;
|
||||||
|
|
||||||
|
let query = call_info.args.nth(0).ok_or_else(|| {
|
||||||
|
ShellError::labeled_error("xpath query not passed", "xpath query not passed", &tag)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.query = query.as_string()?;
|
||||||
|
self.tag = tag;
|
||||||
|
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
|
||||||
|
match input {
|
||||||
|
Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::String(s)),
|
||||||
|
..
|
||||||
|
} => Ok(string_to_value(s, (*self.query).tagged(&self.tag))?
|
||||||
|
.into_iter()
|
||||||
|
.map(ReturnSuccess::value)
|
||||||
|
.collect()),
|
||||||
|
Value { tag, .. } => Err(ShellError::labeled_error_with_secondary(
|
||||||
|
"Expected text from pipeline",
|
||||||
|
"requires text input",
|
||||||
|
&self.tag,
|
||||||
|
"value originates from here",
|
||||||
|
tag,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
166
crates/nu_plugin_xpath/src/xpath.rs
Normal file
166
crates/nu_plugin_xpath/src/xpath.rs
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
|
||||||
|
use nu_source::{Tag, Tagged};
|
||||||
|
|
||||||
|
use bigdecimal::{BigDecimal, FromPrimitive};
|
||||||
|
|
||||||
|
use sxd_document::parser;
|
||||||
|
use sxd_xpath::{Context, Factory};
|
||||||
|
|
||||||
|
pub struct Xpath {
|
||||||
|
pub query: String,
|
||||||
|
pub tag: Tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Xpath {
|
||||||
|
pub fn new() -> Xpath {
|
||||||
|
Xpath {
|
||||||
|
query: String::new(),
|
||||||
|
tag: Tag::unknown(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Xpath {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn string_to_value(raw: String, query: Tagged<&str>) -> Result<Vec<Value>, ShellError> {
|
||||||
|
execute_xpath_query(raw, query.item.to_string(), query.tag())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_xpath_query(
|
||||||
|
input_string: String,
|
||||||
|
query_string: String,
|
||||||
|
tag: impl Into<Tag>,
|
||||||
|
) -> Result<Vec<Value>, ShellError> {
|
||||||
|
let tag = tag.into();
|
||||||
|
let xpath = build_xpath(&query_string)?;
|
||||||
|
|
||||||
|
let package = parser::parse(&input_string);
|
||||||
|
|
||||||
|
if package.is_err() {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"invalid xml document",
|
||||||
|
"invalid xml document",
|
||||||
|
tag.span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let package = package.expect("invalid xml document");
|
||||||
|
|
||||||
|
let document = package.as_document();
|
||||||
|
let context = Context::new();
|
||||||
|
|
||||||
|
// leaving this here for augmentation at some point
|
||||||
|
// build_variables(&arguments, &mut context);
|
||||||
|
// build_namespaces(&arguments, &mut context);
|
||||||
|
|
||||||
|
let res = xpath.evaluate(&context, document.root());
|
||||||
|
|
||||||
|
// Some xpath statements can be long, so let's truncate it with ellipsis
|
||||||
|
let mut key = query_string.clone();
|
||||||
|
if query_string.len() >= 20 {
|
||||||
|
key.truncate(17);
|
||||||
|
key += "...";
|
||||||
|
} else {
|
||||||
|
key = query_string;
|
||||||
|
};
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(r) => {
|
||||||
|
let rows: Vec<Value> = match r {
|
||||||
|
sxd_xpath::Value::Nodeset(ns) => ns
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| {
|
||||||
|
let mut row = TaggedDictBuilder::new(Tag::unknown());
|
||||||
|
row.insert_value(&key, UntaggedValue::string(a.string_value()));
|
||||||
|
row.into_value()
|
||||||
|
})
|
||||||
|
.collect::<Vec<Value>>(),
|
||||||
|
sxd_xpath::Value::Boolean(b) => {
|
||||||
|
let mut row = TaggedDictBuilder::new(Tag::unknown());
|
||||||
|
row.insert_value(&key, UntaggedValue::boolean(b));
|
||||||
|
vec![row.into_value()]
|
||||||
|
}
|
||||||
|
sxd_xpath::Value::Number(n) => {
|
||||||
|
let mut row = TaggedDictBuilder::new(Tag::unknown());
|
||||||
|
row.insert_value(
|
||||||
|
&key,
|
||||||
|
UntaggedValue::decimal(BigDecimal::from_f64(n).expect("error with f64"))
|
||||||
|
.into_untagged_value(),
|
||||||
|
);
|
||||||
|
|
||||||
|
vec![row.into_value()]
|
||||||
|
}
|
||||||
|
sxd_xpath::Value::String(s) => {
|
||||||
|
let mut row = TaggedDictBuilder::new(Tag::unknown());
|
||||||
|
row.insert_value(&key, UntaggedValue::string(s));
|
||||||
|
vec![row.into_value()]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(rows)
|
||||||
|
}
|
||||||
|
Err(_) => Err(ShellError::labeled_error(
|
||||||
|
"xpath query error",
|
||||||
|
"xpath query error",
|
||||||
|
tag,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_xpath(xpath_str: &str) -> Result<sxd_xpath::XPath, ShellError> {
|
||||||
|
let factory = Factory::new();
|
||||||
|
|
||||||
|
match factory.build(xpath_str) {
|
||||||
|
Ok(xpath) => xpath.ok_or_else(|| ShellError::untagged_runtime_error("invalid xpath query")),
|
||||||
|
Err(_) => Err(ShellError::untagged_runtime_error(
|
||||||
|
"expected valid xpath query",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::string_to_value as query;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_source::{Span, TaggedItem};
|
||||||
|
use nu_test_support::value::{decimal_from_float, row};
|
||||||
|
|
||||||
|
use indexmap::indexmap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn position_function_in_predicate() -> Result<(), ShellError> {
|
||||||
|
let text = String::from(r#"<?xml version="1.0" encoding="UTF-8"?><a><b/><b/></a>"#);
|
||||||
|
|
||||||
|
let actual = query(text, "count(//a/*[position() = 2])".tagged_unknown())?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
actual[0],
|
||||||
|
row(
|
||||||
|
indexmap! { "count(//a/*[posit...".into() => decimal_from_float(1.0, Span::unknown()) }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn functions_implicitly_coerce_argument_types() -> Result<(), ShellError> {
|
||||||
|
let text = String::from(r#"<?xml version="1.0" encoding="UTF-8"?><a>true</a>"#);
|
||||||
|
|
||||||
|
let actual = query(text, "count(//*[contains(., true)])".tagged_unknown())?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
actual[0],
|
||||||
|
row(
|
||||||
|
indexmap! { "count(//*[contain...".into() => decimal_from_float(1.0, Span::unknown()) }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
6
src/plugins/nu_plugin_extra_xpath.rs
Normal file
6
src/plugins/nu_plugin_extra_xpath.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use nu_plugin::serve_plugin;
|
||||||
|
use nu_plugin_xpath::Xpath;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
serve_plugin(&mut Xpath::new());
|
||||||
|
}
|
@ -7,7 +7,8 @@ fn plugins_are_declared_with_wix() {
|
|||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
cwd: ".", pipeline(
|
cwd: ".", pipeline(
|
||||||
r#"
|
r#"
|
||||||
echo $(open wix/main.wxs --raw | from xml
|
open Cargo.toml | get bin.name | drop | sort-by | wrap cargo | merge {
|
||||||
|
open wix/main.wxs --raw | from xml
|
||||||
| get Wix.children.Product.children.0.Directory.children.0
|
| get Wix.children.Product.children.0.Directory.children.0
|
||||||
| where Directory.attributes.Id == "$(var.PlatformProgramFilesFolder)"
|
| where Directory.attributes.Id == "$(var.PlatformProgramFilesFolder)"
|
||||||
| get Directory.children.Directory.children.0 | last
|
| get Directory.children.Directory.children.0 | last
|
||||||
@ -18,13 +19,9 @@ fn plugins_are_declared_with_wix() {
|
|||||||
| str substring [_, -4] File.attributes.Name
|
| str substring [_, -4] File.attributes.Name
|
||||||
| get File.attributes.Name
|
| get File.attributes.Name
|
||||||
| sort-by
|
| sort-by
|
||||||
| wrap wix) | merge {
|
| wrap wix
|
||||||
open Cargo.toml |
|
|
||||||
get bin.name |
|
|
||||||
drop |
|
|
||||||
sort-by |
|
|
||||||
wrap cargo
|
|
||||||
}
|
}
|
||||||
|
| default wix _
|
||||||
| if $it.wix != $it.cargo { = 1 } { = 0 }
|
| if $it.wix != $it.cargo { = 1 } { = 0 }
|
||||||
| math sum
|
| math sum
|
||||||
"#
|
"#
|
||||||
|
@ -232,6 +232,14 @@
|
|||||||
Source='target\$(var.Profile)\nu_plugin_extra_chart_line.exe'
|
Source='target\$(var.Profile)\nu_plugin_extra_chart_line.exe'
|
||||||
KeyPath='yes'/>
|
KeyPath='yes'/>
|
||||||
</Component>
|
</Component>
|
||||||
|
<Component Id='binary18' Guid='*' Win64='$(var.Win64)'>
|
||||||
|
<File
|
||||||
|
Id='exe17'
|
||||||
|
Name='nu_plugin_extra_xpath.exe'
|
||||||
|
DiskId='1'
|
||||||
|
Source='target\$(var.Profile)\nu_plugin_extra_xpath.exe'
|
||||||
|
KeyPath='yes'/>
|
||||||
|
</Component>
|
||||||
</Directory>
|
</Directory>
|
||||||
</Directory>
|
</Directory>
|
||||||
</Directory>
|
</Directory>
|
||||||
|
Loading…
Reference in New Issue
Block a user