mirror of
https://github.com/nushell/nushell.git
synced 2024-11-28 19:33:47 +01:00
Fix new clippy warnings (#2760)
* Fix new clippy warnings * Fork serde-hjson and bring in * Fork serde-hjson and bring in * Fix clippy lint again
This commit is contained in:
parent
63d4df9810
commit
930f9f0063
1788
Cargo.lock
generated
1788
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@ doctest = false
|
||||
[dependencies]
|
||||
nu-data = {version = "0.22.0", path = "../nu-data"}
|
||||
nu-errors = {version = "0.22.0", path = "../nu-errors"}
|
||||
nu-json = {version = "0.22.0", path = "../nu-json"}
|
||||
nu-parser = {version = "0.22.0", path = "../nu-parser"}
|
||||
nu-plugin = {version = "0.22.0", path = "../nu-plugin"}
|
||||
nu-protocol = {version = "0.22.0", path = "../nu-protocol"}
|
||||
@ -70,7 +71,6 @@ roxmltree = "0.13.0"
|
||||
rust-embed = "5.6.0"
|
||||
rustyline = {version = "6.3.0", optional = true}
|
||||
serde = {version = "1.0.115", features = ["derive"]}
|
||||
serde-hjson = "0.9.1"
|
||||
serde_bytes = "0.11.5"
|
||||
serde_ini = "0.2.0"
|
||||
serde_json = "1.0.57"
|
||||
|
@ -845,8 +845,8 @@ fn rustyline_hinter(config: &dyn nu_data::config::Conf) -> Option<rustyline::hin
|
||||
}
|
||||
|
||||
fn chomp_newline(s: &str) -> &str {
|
||||
if s.ends_with('\n') {
|
||||
&s[..s.len() - 1]
|
||||
if let Some(s) = s.strip_suffix('\n') {
|
||||
s
|
||||
} else {
|
||||
s
|
||||
}
|
||||
@ -863,8 +863,8 @@ pub enum LineResult {
|
||||
}
|
||||
|
||||
pub async fn parse_and_eval(line: &str, ctx: &mut EvaluationContext) -> Result<String, ShellError> {
|
||||
let line = if line.ends_with('\n') {
|
||||
&line[..line.len() - 1]
|
||||
let line = if let Some(s) = line.strip_suffix('\n') {
|
||||
s
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
@ -63,7 +63,7 @@ pub async fn clip(
|
||||
let mut first = true;
|
||||
for i in values.iter() {
|
||||
if !first {
|
||||
new_copy_data.push_str("\n");
|
||||
new_copy_data.push('\n');
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
@ -37,26 +37,26 @@ impl WholeStreamCommand for FromJSON {
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -> Value {
|
||||
fn convert_json_value_to_nu_value(v: &nu_json::Value, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
let span = tag.span;
|
||||
|
||||
match v {
|
||||
serde_hjson::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
||||
serde_hjson::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
||||
serde_hjson::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
|
||||
serde_hjson::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
serde_hjson::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
serde_hjson::Value::String(s) => {
|
||||
nu_json::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
|
||||
nu_json::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
|
||||
nu_json::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
|
||||
nu_json::Value::U64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
nu_json::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
|
||||
nu_json::Value::String(s) => {
|
||||
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag)
|
||||
}
|
||||
serde_hjson::Value::Array(a) => UntaggedValue::Table(
|
||||
nu_json::Value::Array(a) => UntaggedValue::Table(
|
||||
a.iter()
|
||||
.map(|x| convert_json_value_to_nu_value(x, &tag))
|
||||
.collect(),
|
||||
)
|
||||
.into_value(tag),
|
||||
serde_hjson::Value::Object(o) => {
|
||||
nu_json::Value::Object(o) => {
|
||||
let mut collected = TaggedDictBuilder::new(&tag);
|
||||
for (k, v) in o.iter() {
|
||||
collected.insert_value(k.clone(), convert_json_value_to_nu_value(v, &tag));
|
||||
@ -67,8 +67,8 @@ fn convert_json_value_to_nu_value(v: &serde_hjson::Value, tag: impl Into<Tag>) -
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> serde_hjson::Result<Value> {
|
||||
let v: serde_hjson::Value = serde_hjson::from_str(&s)?;
|
||||
pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> nu_json::Result<Value> {
|
||||
let v: nu_json::Value = nu_json::from_str(&s)?;
|
||||
Ok(convert_json_value_to_nu_value(&v, tag))
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ async fn from_json(
|
||||
Err(e) => {
|
||||
let mut message = "Could not parse as JSON (".to_string();
|
||||
message.push_str(&e.to_string());
|
||||
message.push_str(")");
|
||||
message.push(')');
|
||||
|
||||
Some(Err(ShellError::labeled_error_with_secondary(
|
||||
message,
|
||||
@ -125,7 +125,7 @@ async fn from_json(
|
||||
Err(e) => {
|
||||
let mut message = "Could not parse as JSON (".to_string();
|
||||
message.push_str(&e.to_string());
|
||||
message.push_str(")");
|
||||
message.push(')');
|
||||
|
||||
Ok(OutputStream::one(Err(
|
||||
ShellError::labeled_error_with_secondary(
|
||||
|
@ -68,13 +68,12 @@ fn convert_yaml_value_to_nu_value(
|
||||
Ok(match v {
|
||||
serde_yaml::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(tag),
|
||||
serde_yaml::Value::Number(n) if n.is_i64() => {
|
||||
UntaggedValue::int(n.as_i64().ok_or_else(|| err_not_compatible_number)?).into_value(tag)
|
||||
UntaggedValue::int(n.as_i64().ok_or(err_not_compatible_number)?).into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Number(n) if n.is_f64() => {
|
||||
UntaggedValue::decimal_from_float(n.as_f64().ok_or(err_not_compatible_number)?, span)
|
||||
.into_value(tag)
|
||||
}
|
||||
serde_yaml::Value::Number(n) if n.is_f64() => UntaggedValue::decimal_from_float(
|
||||
n.as_f64().ok_or_else(|| err_not_compatible_number)?,
|
||||
span,
|
||||
)
|
||||
.into_value(tag),
|
||||
serde_yaml::Value::String(s) => UntaggedValue::string(s).into_value(tag),
|
||||
serde_yaml::Value::Sequence(a) => {
|
||||
let result: Result<Vec<Value>, ShellError> = a
|
||||
|
@ -264,7 +264,7 @@ fn string_from(input: &[Value]) -> String {
|
||||
let mut first = true;
|
||||
for i in input.iter() {
|
||||
if !first {
|
||||
save_data.push_str("\n");
|
||||
save_data.push('\n');
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
@ -304,7 +304,7 @@ fn print_seq(
|
||||
let before_dec = istr.find('.').unwrap_or(ilen);
|
||||
if pad && before_dec < padding {
|
||||
for _ in 0..(padding - before_dec) {
|
||||
ret_str.push_str("0");
|
||||
ret_str.push('0');
|
||||
}
|
||||
}
|
||||
ret_str.push_str(&istr);
|
||||
|
@ -127,22 +127,22 @@ fn get_output_string(
|
||||
let mut output_string = String::new();
|
||||
|
||||
if !headers.is_empty() {
|
||||
output_string.push_str("|");
|
||||
output_string.push('|');
|
||||
|
||||
for i in 0..headers.len() {
|
||||
if pretty {
|
||||
output_string.push_str(" ");
|
||||
output_string.push(' ');
|
||||
output_string.push_str(&get_padded_string(
|
||||
headers[i].clone(),
|
||||
column_widths[i],
|
||||
' ',
|
||||
));
|
||||
output_string.push_str(" ");
|
||||
output_string.push(' ');
|
||||
} else {
|
||||
output_string.push_str(headers[i].as_str());
|
||||
}
|
||||
|
||||
output_string.push_str("|");
|
||||
output_string.push('|');
|
||||
}
|
||||
|
||||
output_string.push_str("\n|");
|
||||
@ -150,43 +150,43 @@ fn get_output_string(
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..headers.len() {
|
||||
if pretty {
|
||||
output_string.push_str(" ");
|
||||
output_string.push(' ');
|
||||
output_string.push_str(&get_padded_string(
|
||||
String::from("-"),
|
||||
column_widths[i],
|
||||
'-',
|
||||
));
|
||||
output_string.push_str(" ");
|
||||
output_string.push(' ');
|
||||
} else {
|
||||
output_string.push_str("-");
|
||||
output_string.push('-');
|
||||
}
|
||||
|
||||
output_string.push_str("|");
|
||||
output_string.push('|');
|
||||
}
|
||||
|
||||
output_string.push_str("\n");
|
||||
output_string.push('\n');
|
||||
}
|
||||
|
||||
for row in rows {
|
||||
if !headers.is_empty() {
|
||||
output_string.push_str("|");
|
||||
output_string.push('|');
|
||||
}
|
||||
|
||||
for i in 0..row.len() {
|
||||
if pretty {
|
||||
output_string.push_str(" ");
|
||||
output_string.push(' ');
|
||||
output_string.push_str(&get_padded_string(row[i].clone(), column_widths[i], ' '));
|
||||
output_string.push_str(" ");
|
||||
output_string.push(' ');
|
||||
} else {
|
||||
output_string.push_str(row[i].as_str());
|
||||
}
|
||||
|
||||
if !headers.is_empty() {
|
||||
output_string.push_str("|");
|
||||
output_string.push('|');
|
||||
}
|
||||
}
|
||||
|
||||
output_string.push_str("\n");
|
||||
output_string.push('\n');
|
||||
}
|
||||
|
||||
output_string
|
||||
|
@ -157,7 +157,7 @@ async fn process_row(
|
||||
None => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"update could not find place to insert column",
|
||||
"column name",
|
||||
field.maybe_span().unwrap_or_else(|| tag.span),
|
||||
field.maybe_span().unwrap_or(tag.span),
|
||||
))),
|
||||
},
|
||||
Value { value: _, ref tag } => {
|
||||
@ -166,7 +166,7 @@ async fn process_row(
|
||||
None => OutputStream::one(Err(ShellError::labeled_error(
|
||||
"update could not find place to insert column",
|
||||
"column name",
|
||||
field.maybe_span().unwrap_or_else(|| tag.span),
|
||||
field.maybe_span().unwrap_or(tag.span),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ pub fn get_documentation(
|
||||
let mut long_desc = String::new();
|
||||
|
||||
long_desc.push_str(&cmd.usage());
|
||||
long_desc.push_str("\n");
|
||||
long_desc.push('\n');
|
||||
|
||||
let mut subcommands = vec![];
|
||||
if !config.no_subcommands {
|
||||
@ -144,7 +144,7 @@ pub fn get_documentation(
|
||||
|
||||
let mut one_liner = String::new();
|
||||
one_liner.push_str(&signature.name);
|
||||
one_liner.push_str(" ");
|
||||
one_liner.push(' ');
|
||||
|
||||
for positional in &signature.positional {
|
||||
match &positional.0 {
|
||||
@ -175,7 +175,7 @@ pub fn get_documentation(
|
||||
long_desc.push_str("\nSubcommands:\n");
|
||||
subcommands.sort();
|
||||
long_desc.push_str(&subcommands.join("\n"));
|
||||
long_desc.push_str("\n");
|
||||
long_desc.push('\n');
|
||||
}
|
||||
|
||||
if !signature.positional.is_empty() || signature.rest_positional.is_some() {
|
||||
@ -205,7 +205,7 @@ pub fn get_documentation(
|
||||
long_desc.push_str("\nExamples:");
|
||||
}
|
||||
for example in examples {
|
||||
long_desc.push_str("\n");
|
||||
long_desc.push('\n');
|
||||
long_desc.push_str(" ");
|
||||
long_desc.push_str(example.description);
|
||||
|
||||
@ -218,7 +218,7 @@ pub fn get_documentation(
|
||||
}
|
||||
}
|
||||
|
||||
long_desc.push_str("\n");
|
||||
long_desc.push('\n');
|
||||
|
||||
long_desc
|
||||
}
|
||||
|
2
crates/nu-cli/src/env/environment_syncer.rs
vendored
2
crates/nu-cli/src/env/environment_syncer.rs
vendored
@ -50,7 +50,7 @@ impl EnvironmentSyncer {
|
||||
|
||||
pub fn did_config_change(&mut self) -> bool {
|
||||
let config = self.config.lock();
|
||||
config.is_modified().unwrap_or_else(|_| false)
|
||||
config.is_modified().unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn reload(&mut self) {
|
||||
|
2
crates/nu-cli/src/env/host.rs
vendored
2
crates/nu-cli/src/env/host.rs
vendored
@ -126,7 +126,7 @@ impl Host for BasicHost {
|
||||
}
|
||||
|
||||
fn width(&self) -> usize {
|
||||
let (mut term_width, _) = term_size::dimensions().unwrap_or_else(|| (80, 20));
|
||||
let (mut term_width, _) = term_size::dimensions().unwrap_or((80, 20));
|
||||
term_width -= 1;
|
||||
term_width
|
||||
}
|
||||
|
@ -191,8 +191,8 @@ pub fn test_anchors(cmd: Command) -> Result<(), ShellError> {
|
||||
|
||||
/// Parse and run a nushell pipeline
|
||||
fn parse_line(line: &str, ctx: &mut EvaluationContext) -> Result<ClassifiedBlock, ShellError> {
|
||||
let line = if line.ends_with('\n') {
|
||||
&line[..line.len() - 1]
|
||||
let line = if let Some(line) = line.strip_suffix('\n') {
|
||||
line
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
@ -156,7 +156,7 @@ pub fn scan(paths: Vec<std::path::PathBuf>) -> Result<Vec<crate::commands::Comma
|
||||
|
||||
if is_valid_name && is_executable {
|
||||
trace!(target: "nu::load", "plugin infrastructure -> Trying {:?}", path.display());
|
||||
build_plugin_command(&path).unwrap_or_else(|_| None)
|
||||
build_plugin_command(&path).unwrap_or(None)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ mod tests {
|
||||
);
|
||||
|
||||
//If this fails for you, check for any special unicode characters in your ~ path
|
||||
assert!(actual.out.chars().filter(|c| c.clone() == '/').count() == 2);
|
||||
assert!(actual.out.chars().filter(|c| *c == '/').count() == 2);
|
||||
#[cfg(target_os = "linux")]
|
||||
assert!(actual.out.contains("home"));
|
||||
#[cfg(target_os = "macos")]
|
||||
|
@ -33,29 +33,17 @@ fn figures_out_intelligently_where_to_write_out_with_metadata() {
|
||||
#[test]
|
||||
fn writes_out_csv() {
|
||||
Playground::setup("save_test_2", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"cargo_sample.json",
|
||||
r#"
|
||||
{
|
||||
"package": {
|
||||
"name": "nu",
|
||||
"version": "0.14",
|
||||
"description": "A new type of shell",
|
||||
"license": "MIT",
|
||||
"edition": "2018"
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)]);
|
||||
sandbox.with_files(vec![]);
|
||||
|
||||
let expected_file = dirs.test().join("cargo_sample.csv");
|
||||
|
||||
nu!(
|
||||
cwd: dirs.root(),
|
||||
"open save_test_2/cargo_sample.json | get package | save save_test_2/cargo_sample.csv",
|
||||
r#"echo [[name, version, description, license, edition]; [nu, "0.14", "A new type of shell", "MIT", "2018"]] | save save_test_2/cargo_sample.csv"#,
|
||||
);
|
||||
|
||||
let actual = file_contents(expected_file);
|
||||
println!("{}", actual);
|
||||
assert!(actual.contains("nu,0.14,A new type of shell,MIT,2018"));
|
||||
})
|
||||
}
|
||||
|
@ -150,18 +150,24 @@ fn uniq_counting() {
|
||||
| from json
|
||||
| wrap item
|
||||
| uniq --count
|
||||
| where item == A
|
||||
| get count
|
||||
"#
|
||||
));
|
||||
let expected = nu!(
|
||||
assert_eq!(actual.out, "2");
|
||||
|
||||
let actual = nu!(
|
||||
cwd: "tests/fixtures/formats", pipeline(
|
||||
r#"
|
||||
echo '[{"item": "A", "count": 2}, {"item": "B", "count": 1}]'
|
||||
echo '["A", "B", "A"]'
|
||||
| from json
|
||||
| wrap item
|
||||
| uniq --count
|
||||
| where item == B
|
||||
| get count
|
||||
"#
|
||||
));
|
||||
print!("{}", actual.out);
|
||||
print!("{}", expected.out);
|
||||
assert_eq!(actual.out, expected.out);
|
||||
assert_eq!(actual.out, "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
15
crates/nu-json/Cargo.toml
Normal file
15
crates/nu-json/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors", "Christian Zangl <laktak@cdak.net>"]
|
||||
description = "Fork of serde-hjson"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-json"
|
||||
version = "0.22.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = "^0.8.0"
|
||||
num-traits = "~0.1.32"
|
||||
regex = "^1.0"
|
||||
lazy_static = "1"
|
29
crates/nu-json/LICENSE
Normal file
29
crates/nu-json/LICENSE
Normal file
@ -0,0 +1,29 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 The Rust Project Developers
|
||||
Copyright (c) 2016 Christian Zangl
|
||||
Copyright (c) 2020 The Nu Project Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
121
crates/nu-json/src/builder.rs
Normal file
121
crates/nu-json/src/builder.rs
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use serde::ser;
|
||||
|
||||
use crate::value::{self, Map, Value};
|
||||
|
||||
/// This structure provides a simple interface for constructing a JSON array.
|
||||
pub struct ArrayBuilder {
|
||||
array: Vec<Value>,
|
||||
}
|
||||
|
||||
impl Default for ArrayBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrayBuilder {
|
||||
/// Construct an `ObjectBuilder`.
|
||||
pub fn new() -> ArrayBuilder {
|
||||
ArrayBuilder { array: Vec::new() }
|
||||
}
|
||||
|
||||
/// Return the constructed `Value`.
|
||||
pub fn unwrap(self) -> Value {
|
||||
Value::Array(self.array)
|
||||
}
|
||||
|
||||
/// Insert a value into the array.
|
||||
pub fn push<T: ser::Serialize>(mut self, v: T) -> ArrayBuilder {
|
||||
self.array.push(value::to_value(&v));
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates and passes an `ArrayBuilder` into a closure, then inserts the resulting array into
|
||||
/// this array.
|
||||
pub fn push_array<F>(mut self, f: F) -> ArrayBuilder
|
||||
where
|
||||
F: FnOnce(ArrayBuilder) -> ArrayBuilder,
|
||||
{
|
||||
let builder = ArrayBuilder::new();
|
||||
self.array.push(f(builder).unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates and passes an `ArrayBuilder` into a closure, then inserts the resulting object into
|
||||
/// this array.
|
||||
pub fn push_object<F>(mut self, f: F) -> ArrayBuilder
|
||||
where
|
||||
F: FnOnce(ObjectBuilder) -> ObjectBuilder,
|
||||
{
|
||||
let builder = ObjectBuilder::new();
|
||||
self.array.push(f(builder).unwrap());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// This structure provides a simple interface for constructing a JSON object.
|
||||
pub struct ObjectBuilder {
|
||||
object: Map<String, Value>,
|
||||
}
|
||||
|
||||
impl Default for ObjectBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectBuilder {
|
||||
/// Construct an `ObjectBuilder`.
|
||||
pub fn new() -> ObjectBuilder {
|
||||
ObjectBuilder { object: Map::new() }
|
||||
}
|
||||
|
||||
/// Return the constructed `Value`.
|
||||
pub fn unwrap(self) -> Value {
|
||||
Value::Object(self.object)
|
||||
}
|
||||
|
||||
/// Insert a key-value pair into the object.
|
||||
pub fn insert<S, V>(mut self, key: S, value: V) -> ObjectBuilder
|
||||
where
|
||||
S: Into<String>,
|
||||
V: ser::Serialize,
|
||||
{
|
||||
self.object.insert(key.into(), value::to_value(&value));
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates and passes an `ObjectBuilder` into a closure, then inserts the resulting array into
|
||||
/// this object.
|
||||
pub fn insert_array<S, F>(mut self, key: S, f: F) -> ObjectBuilder
|
||||
where
|
||||
S: Into<String>,
|
||||
F: FnOnce(ArrayBuilder) -> ArrayBuilder,
|
||||
{
|
||||
let builder = ArrayBuilder::new();
|
||||
self.object.insert(key.into(), f(builder).unwrap());
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates and passes an `ObjectBuilder` into a closure, then inserts the resulting object into
|
||||
/// this object.
|
||||
pub fn insert_object<S, F>(mut self, key: S, f: F) -> ObjectBuilder
|
||||
where
|
||||
S: Into<String>,
|
||||
F: FnOnce(ObjectBuilder) -> ObjectBuilder,
|
||||
{
|
||||
let builder = ObjectBuilder::new();
|
||||
self.object.insert(key.into(), f(builder).unwrap());
|
||||
self
|
||||
}
|
||||
}
|
951
crates/nu-json/src/de.rs
Normal file
951
crates/nu-json/src/de.rs
Normal file
@ -0,0 +1,951 @@
|
||||
//! Hjson Deserialization
|
||||
//!
|
||||
//! This module provides for Hjson deserialization with the type `Deserializer`.
|
||||
|
||||
use std::char;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::str;
|
||||
|
||||
use serde::de;
|
||||
|
||||
use super::error::{Error, ErrorCode, Result};
|
||||
use super::util::StringReader;
|
||||
use super::util::{Number, ParseNumber};
|
||||
|
||||
enum State {
|
||||
Normal,
|
||||
Root,
|
||||
Keyname,
|
||||
}
|
||||
|
||||
/// A structure that deserializes Hjson into Rust values.
|
||||
pub struct Deserializer<Iter: Iterator<Item = u8>> {
|
||||
rdr: StringReader<Iter>,
|
||||
str_buf: Vec<u8>,
|
||||
state: State,
|
||||
}
|
||||
|
||||
// macro_rules! try_or_invalid {
|
||||
// ($self_:expr, $e:expr) => {
|
||||
// match $e {
|
||||
// Some(v) => v,
|
||||
// None => { return Err($self_.error(ErrorCode::InvalidNumber)); }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<Iter> Deserializer<Iter>
|
||||
where
|
||||
Iter: Iterator<Item = u8>,
|
||||
{
|
||||
/// Creates the Hjson parser from an `std::iter::Iterator`.
|
||||
#[inline]
|
||||
pub fn new(rdr: Iter) -> Deserializer<Iter> {
|
||||
Deserializer {
|
||||
rdr: StringReader::new(rdr),
|
||||
str_buf: Vec::with_capacity(128),
|
||||
state: State::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the Hjson parser from an `std::iter::Iterator`.
|
||||
#[inline]
|
||||
pub fn new_for_root(rdr: Iter) -> Deserializer<Iter> {
|
||||
let mut res = Deserializer::new(rdr);
|
||||
res.state = State::Root;
|
||||
res
|
||||
}
|
||||
|
||||
/// The `Deserializer::end` method should be called after a value has been fully deserialized.
|
||||
/// This allows the `Deserializer` to validate that the input stream is at the end or that it
|
||||
/// only has trailing whitespace.
|
||||
#[inline]
|
||||
pub fn end(&mut self) -> Result<()> {
|
||||
self.rdr.parse_whitespace()?;
|
||||
if self.rdr.eof()? {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.rdr.error(ErrorCode::TrailingCharacters))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_punctuator_char(&mut self, ch: u8) -> bool {
|
||||
matches!(ch, b'{' | b'}' | b'[' | b']' | b',' | b':')
|
||||
}
|
||||
|
||||
fn parse_keyname<V>(&mut self, mut visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor,
|
||||
{
|
||||
// quotes for keys are optional in Hjson
|
||||
// unless they include {}[],: or whitespace.
|
||||
// assume whitespace was already eaten
|
||||
|
||||
self.str_buf.clear();
|
||||
|
||||
let mut space: Option<usize> = None;
|
||||
loop {
|
||||
let ch = self.rdr.next_char_or_null()?;
|
||||
|
||||
if ch == b':' {
|
||||
if self.str_buf.is_empty() {
|
||||
return Err(self.rdr.error(ErrorCode::Custom(
|
||||
"Found ':' but no key name (for an empty key name use quotes)".to_string(),
|
||||
)));
|
||||
} else if space.is_some()
|
||||
&& space.expect("Internal error: json parsing") != self.str_buf.len()
|
||||
{
|
||||
return Err(self.rdr.error(ErrorCode::Custom(
|
||||
"Found whitespace in your key name (use quotes to include)".to_string(),
|
||||
)));
|
||||
}
|
||||
self.rdr.uneat_char(ch);
|
||||
let s = str::from_utf8(&self.str_buf).expect("Internal error: json parsing");
|
||||
return visitor.visit_str(s);
|
||||
} else if ch <= b' ' {
|
||||
if ch == 0 {
|
||||
return Err(self.rdr.error(ErrorCode::EOFWhileParsingObject));
|
||||
} else if space.is_none() {
|
||||
space = Some(self.str_buf.len());
|
||||
}
|
||||
} else if self.is_punctuator_char(ch) {
|
||||
return Err(self.rdr.error(ErrorCode::Custom("Found a punctuator where a key name was expected (check your syntax or use quotes if the key name includes {}[],: or whitespace)".to_string())));
|
||||
} else {
|
||||
self.str_buf.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_value<V>(&mut self, mut visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor,
|
||||
{
|
||||
self.rdr.parse_whitespace()?;
|
||||
|
||||
if self.rdr.eof()? {
|
||||
return Err(self.rdr.error(ErrorCode::EOFWhileParsingValue));
|
||||
}
|
||||
|
||||
match self.state {
|
||||
State::Keyname => {
|
||||
self.state = State::Normal;
|
||||
return self.parse_keyname(visitor);
|
||||
}
|
||||
State::Root => {
|
||||
self.state = State::Normal;
|
||||
return visitor.visit_map(MapVisitor::new(self, true));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let value = match self.rdr.peek_or_null()? {
|
||||
/*
|
||||
b'-' => {
|
||||
self.rdr.eat_char();
|
||||
self.parse_integer(false, visitor)
|
||||
}
|
||||
b'0' ..= b'9' => {
|
||||
self.parse_integer(true, visitor)
|
||||
}
|
||||
*/
|
||||
b'"' => {
|
||||
self.rdr.eat_char();
|
||||
self.parse_string()?;
|
||||
let s = str::from_utf8(&self.str_buf).expect("Internal error: json parsing");
|
||||
visitor.visit_str(s)
|
||||
}
|
||||
b'[' => {
|
||||
self.rdr.eat_char();
|
||||
visitor.visit_seq(SeqVisitor::new(self))
|
||||
}
|
||||
b'{' => {
|
||||
self.rdr.eat_char();
|
||||
visitor.visit_map(MapVisitor::new(self, false))
|
||||
}
|
||||
b'\x00' => Err(self.rdr.error(ErrorCode::ExpectedSomeValue)),
|
||||
_ => self.parse_tfnns(visitor),
|
||||
};
|
||||
|
||||
match value {
|
||||
Ok(value) => Ok(value),
|
||||
Err(Error::Syntax(code, _, _)) => Err(self.rdr.error(code)),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ident(&mut self, ident: &[u8]) -> Result<()> {
|
||||
for c in ident {
|
||||
if Some(*c) != self.rdr.next_char()? {
|
||||
return Err(self.rdr.error(ErrorCode::ExpectedSomeIdent));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_tfnns<V>(&mut self, mut visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor,
|
||||
{
|
||||
// Hjson strings can be quoteless
|
||||
// returns string, true, false, or null.
|
||||
self.str_buf.clear();
|
||||
|
||||
let first = self.rdr.peek()?.expect("Internal error: json parsing");
|
||||
|
||||
if self.is_punctuator_char(first) {
|
||||
return Err(self.rdr.error(ErrorCode::PunctuatorInQlString));
|
||||
}
|
||||
|
||||
loop {
|
||||
let ch = self.rdr.next_char_or_null()?;
|
||||
|
||||
let is_eol = ch == b'\r' || ch == b'\n' || ch == b'\x00';
|
||||
let is_comment = ch == b'#'
|
||||
|| if ch == b'/' {
|
||||
let next = self.rdr.peek_or_null()?;
|
||||
next == b'/' || next == b'*'
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if is_eol || is_comment || ch == b',' || ch == b'}' || ch == b']' {
|
||||
let chf = self.str_buf[0];
|
||||
match chf {
|
||||
b'f' => {
|
||||
if str::from_utf8(&self.str_buf)
|
||||
.expect("Internal error: json parsing")
|
||||
.trim()
|
||||
== "false"
|
||||
{
|
||||
self.rdr.uneat_char(ch);
|
||||
return visitor.visit_bool(false);
|
||||
}
|
||||
}
|
||||
b'n' => {
|
||||
if str::from_utf8(&self.str_buf)
|
||||
.expect("Internal error: json parsing")
|
||||
.trim()
|
||||
== "null"
|
||||
{
|
||||
self.rdr.uneat_char(ch);
|
||||
return visitor.visit_unit();
|
||||
}
|
||||
}
|
||||
b't' => {
|
||||
if str::from_utf8(&self.str_buf)
|
||||
.expect("Internal error: json parsing")
|
||||
.trim()
|
||||
== "true"
|
||||
{
|
||||
self.rdr.uneat_char(ch);
|
||||
return visitor.visit_bool(true);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if chf == b'-' || chf >= b'0' && chf <= b'9' {
|
||||
let mut pn = ParseNumber::new(self.str_buf.iter().cloned());
|
||||
match pn.parse(false) {
|
||||
Ok(Number::F64(v)) => {
|
||||
self.rdr.uneat_char(ch);
|
||||
return visitor.visit_f64(v);
|
||||
}
|
||||
Ok(Number::U64(v)) => {
|
||||
self.rdr.uneat_char(ch);
|
||||
return visitor.visit_u64(v);
|
||||
}
|
||||
Ok(Number::I64(v)) => {
|
||||
self.rdr.uneat_char(ch);
|
||||
return visitor.visit_i64(v);
|
||||
}
|
||||
Err(_) => {} // not a number, continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_eol {
|
||||
// remove any whitespace at the end (ignored in quoteless strings)
|
||||
return visitor.visit_str(
|
||||
str::from_utf8(&self.str_buf)
|
||||
.expect("Internal error: json parsing")
|
||||
.trim(),
|
||||
);
|
||||
}
|
||||
}
|
||||
self.str_buf.push(ch);
|
||||
|
||||
if self.str_buf == vec![b'\''; 3] {
|
||||
return self.parse_ml_string(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_hex_escape(&mut self) -> Result<u16> {
|
||||
let mut i = 0;
|
||||
let mut n = 0u16;
|
||||
while i < 4 && !(self.rdr.eof()?) {
|
||||
n = match self.rdr.next_char_or_null()? {
|
||||
c @ b'0'..=b'9' => n * 16_u16 + ((c as u16) - (b'0' as u16)),
|
||||
b'a' | b'A' => n * 16_u16 + 10_u16,
|
||||
b'b' | b'B' => n * 16_u16 + 11_u16,
|
||||
b'c' | b'C' => n * 16_u16 + 12_u16,
|
||||
b'd' | b'D' => n * 16_u16 + 13_u16,
|
||||
b'e' | b'E' => n * 16_u16 + 14_u16,
|
||||
b'f' | b'F' => n * 16_u16 + 15_u16,
|
||||
_ => {
|
||||
return Err(self.rdr.error(ErrorCode::InvalidEscape));
|
||||
}
|
||||
};
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Error out if we didn't parse 4 digits.
|
||||
if i != 4 {
|
||||
return Err(self.rdr.error(ErrorCode::InvalidEscape));
|
||||
}
|
||||
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn ml_skip_white(&mut self) -> Result<bool> {
|
||||
match self.rdr.peek_or_null()? {
|
||||
b' ' | b'\t' | b'\r' => {
|
||||
self.rdr.eat_char();
|
||||
Ok(true)
|
||||
}
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn ml_skip_indent(&mut self, indent: usize) -> Result<()> {
|
||||
let mut skip = indent;
|
||||
while self.ml_skip_white()? && skip > 0 {
|
||||
skip -= 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_ml_string<V>(&mut self, mut visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor,
|
||||
{
|
||||
self.str_buf.clear();
|
||||
|
||||
// Parse a multiline string value.
|
||||
let mut triple = 0;
|
||||
|
||||
// we are at ''' +1 - get indent
|
||||
let (_, col) = self.rdr.pos();
|
||||
let indent = col - 4;
|
||||
|
||||
// skip white/to (newline)
|
||||
while self.ml_skip_white()? {}
|
||||
if self.rdr.peek_or_null()? == b'\n' {
|
||||
self.rdr.eat_char();
|
||||
self.ml_skip_indent(indent)?;
|
||||
}
|
||||
|
||||
// When parsing multiline string values, we must look for ' characters.
|
||||
loop {
|
||||
if self.rdr.eof()? {
|
||||
return Err(self.rdr.error(ErrorCode::EOFWhileParsingString));
|
||||
} // todo error("Bad multiline string");
|
||||
let ch = self.rdr.next_char_or_null()?;
|
||||
|
||||
if ch == b'\'' {
|
||||
triple += 1;
|
||||
if triple == 3 {
|
||||
if self.str_buf.last() == Some(&b'\n') {
|
||||
self.str_buf.pop();
|
||||
}
|
||||
let res = str::from_utf8(&self.str_buf).expect("Internal error: json parsing");
|
||||
//todo if (self.str_buf.slice(-1) === '\n') self.str_buf=self.str_buf.slice(0, -1); // remove last EOL
|
||||
return visitor.visit_str(res);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
while triple > 0 {
|
||||
self.str_buf.push(b'\'');
|
||||
triple -= 1;
|
||||
}
|
||||
|
||||
if ch != b'\r' {
|
||||
self.str_buf.push(ch);
|
||||
}
|
||||
if ch == b'\n' {
|
||||
self.ml_skip_indent(indent)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_string(&mut self) -> Result<()> {
|
||||
self.str_buf.clear();
|
||||
|
||||
loop {
|
||||
let ch = match self.rdr.next_char()? {
|
||||
Some(ch) => ch,
|
||||
None => {
|
||||
return Err(self.rdr.error(ErrorCode::EOFWhileParsingString));
|
||||
}
|
||||
};
|
||||
|
||||
match ch {
|
||||
b'"' => {
|
||||
return Ok(());
|
||||
}
|
||||
b'\\' => {
|
||||
let ch = match self.rdr.next_char()? {
|
||||
Some(ch) => ch,
|
||||
None => {
|
||||
return Err(self.rdr.error(ErrorCode::EOFWhileParsingString));
|
||||
}
|
||||
};
|
||||
|
||||
match ch {
|
||||
b'"' => self.str_buf.push(b'"'),
|
||||
b'\\' => self.str_buf.push(b'\\'),
|
||||
b'/' => self.str_buf.push(b'/'),
|
||||
b'b' => self.str_buf.push(b'\x08'),
|
||||
b'f' => self.str_buf.push(b'\x0c'),
|
||||
b'n' => self.str_buf.push(b'\n'),
|
||||
b'r' => self.str_buf.push(b'\r'),
|
||||
b't' => self.str_buf.push(b'\t'),
|
||||
b'u' => {
|
||||
let c = match self.decode_hex_escape()? {
|
||||
0xDC00..=0xDFFF => {
|
||||
return Err(self
|
||||
.rdr
|
||||
.error(ErrorCode::LoneLeadingSurrogateInHexEscape));
|
||||
}
|
||||
|
||||
// Non-BMP characters are encoded as a sequence of
|
||||
// two hex escapes, representing UTF-16 surrogates.
|
||||
n1 @ 0xD800..=0xDBFF => {
|
||||
match (self.rdr.next_char()?, self.rdr.next_char()?) {
|
||||
(Some(b'\\'), Some(b'u')) => (),
|
||||
_ => {
|
||||
return Err(self
|
||||
.rdr
|
||||
.error(ErrorCode::UnexpectedEndOfHexEscape));
|
||||
}
|
||||
}
|
||||
|
||||
let n2 = self.decode_hex_escape()?;
|
||||
|
||||
if n2 < 0xDC00 || n2 > 0xDFFF {
|
||||
return Err(self
|
||||
.rdr
|
||||
.error(ErrorCode::LoneLeadingSurrogateInHexEscape));
|
||||
}
|
||||
|
||||
let n = (((n1 - 0xD800) as u32) << 10 | (n2 - 0xDC00) as u32)
|
||||
+ 0x1_0000;
|
||||
|
||||
match char::from_u32(n as u32) {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
return Err(self
|
||||
.rdr
|
||||
.error(ErrorCode::InvalidUnicodeCodePoint));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
n => match char::from_u32(n as u32) {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
return Err(self
|
||||
.rdr
|
||||
.error(ErrorCode::InvalidUnicodeCodePoint));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// FIXME: this allocation is required in order to be compatible with stable
|
||||
// rust, which doesn't support encoding a `char` into a stack buffer.
|
||||
let mut buf = String::new();
|
||||
buf.push(c);
|
||||
self.str_buf.extend(buf.bytes());
|
||||
}
|
||||
_ => {
|
||||
return Err(self.rdr.error(ErrorCode::InvalidEscape));
|
||||
}
|
||||
}
|
||||
}
|
||||
ch => {
|
||||
self.str_buf.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_object_colon(&mut self) -> Result<()> {
|
||||
self.rdr.parse_whitespace()?;
|
||||
|
||||
match self.rdr.next_char()? {
|
||||
Some(b':') => Ok(()),
|
||||
Some(_) => Err(self.rdr.error(ErrorCode::ExpectedColon)),
|
||||
None => Err(self.rdr.error(ErrorCode::EOFWhileParsingObject)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Iter> de::Deserializer for Deserializer<Iter>
|
||||
where
|
||||
Iter: Iterator<Item = u8>,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn deserialize<V>(&mut self, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor,
|
||||
{
|
||||
self.parse_value(visitor)
|
||||
}
|
||||
|
||||
/// Parses a `null` as a None, and any other values as a `Some(...)`.
|
||||
#[inline]
|
||||
fn deserialize_option<V>(&mut self, mut visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor,
|
||||
{
|
||||
self.rdr.parse_whitespace()?;
|
||||
|
||||
match self.rdr.peek_or_null()? {
|
||||
b'n' => {
|
||||
self.rdr.eat_char();
|
||||
self.parse_ident(b"ull")?;
|
||||
visitor.visit_none()
|
||||
}
|
||||
_ => visitor.visit_some(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a newtype struct as the underlying value.
|
||||
#[inline]
|
||||
fn deserialize_newtype_struct<V>(&mut self, _name: &str, mut visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor,
|
||||
{
|
||||
visitor.visit_newtype_struct(self)
|
||||
}
|
||||
|
||||
forward_to_deserialize! {
|
||||
deserialize_bool();
|
||||
deserialize_usize();
|
||||
deserialize_u8();
|
||||
deserialize_u16();
|
||||
deserialize_u32();
|
||||
deserialize_u64();
|
||||
deserialize_isize();
|
||||
deserialize_i8();
|
||||
deserialize_i16();
|
||||
deserialize_i32();
|
||||
deserialize_i64();
|
||||
deserialize_f32();
|
||||
deserialize_f64();
|
||||
deserialize_char();
|
||||
deserialize_str();
|
||||
deserialize_string();
|
||||
deserialize_unit();
|
||||
deserialize_seq();
|
||||
deserialize_seq_fixed_size(len: usize);
|
||||
deserialize_bytes();
|
||||
deserialize_map();
|
||||
deserialize_unit_struct(name: &'static str);
|
||||
deserialize_tuple_struct(name: &'static str, len: usize);
|
||||
deserialize_struct(name: &'static str, fields: &'static [&'static str]);
|
||||
deserialize_struct_field();
|
||||
deserialize_tuple(len: usize);
|
||||
deserialize_enum(name: &'static str, variants: &'static [&'static str]);
|
||||
deserialize_ignored_any();
|
||||
}
|
||||
}
|
||||
|
||||
struct SeqVisitor<'a, Iter: 'a + Iterator<Item = u8>> {
|
||||
de: &'a mut Deserializer<Iter>,
|
||||
}
|
||||
|
||||
impl<'a, Iter: Iterator<Item = u8>> SeqVisitor<'a, Iter> {
|
||||
fn new(de: &'a mut Deserializer<Iter>) -> Self {
|
||||
SeqVisitor { de }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Iter> de::SeqVisitor for SeqVisitor<'a, Iter>
|
||||
where
|
||||
Iter: Iterator<Item = u8>,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn visit<T>(&mut self) -> Result<Option<T>>
|
||||
where
|
||||
T: de::Deserialize,
|
||||
{
|
||||
self.de.rdr.parse_whitespace()?;
|
||||
|
||||
match self.de.rdr.peek()? {
|
||||
Some(b']') => {
|
||||
return Ok(None);
|
||||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
return Err(self.de.rdr.error(ErrorCode::EOFWhileParsingList));
|
||||
}
|
||||
}
|
||||
|
||||
let value = de::Deserialize::deserialize(self.de)?;
|
||||
|
||||
// in Hjson the comma is optional and trailing commas are allowed
|
||||
self.de.rdr.parse_whitespace()?;
|
||||
if self.de.rdr.peek()? == Some(b',') {
|
||||
self.de.rdr.eat_char();
|
||||
self.de.rdr.parse_whitespace()?;
|
||||
}
|
||||
|
||||
Ok(Some(value))
|
||||
}
|
||||
|
||||
fn end(&mut self) -> Result<()> {
|
||||
self.de.rdr.parse_whitespace()?;
|
||||
|
||||
match self.de.rdr.next_char()? {
|
||||
Some(b']') => Ok(()),
|
||||
Some(_) => Err(self.de.rdr.error(ErrorCode::TrailingCharacters)),
|
||||
None => Err(self.de.rdr.error(ErrorCode::EOFWhileParsingList)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MapVisitor<'a, Iter: 'a + Iterator<Item = u8>> {
|
||||
de: &'a mut Deserializer<Iter>,
|
||||
first: bool,
|
||||
root: bool,
|
||||
}
|
||||
|
||||
impl<'a, Iter: Iterator<Item = u8>> MapVisitor<'a, Iter> {
|
||||
fn new(de: &'a mut Deserializer<Iter>, root: bool) -> Self {
|
||||
MapVisitor {
|
||||
de,
|
||||
first: true,
|
||||
root,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Iter> de::MapVisitor for MapVisitor<'a, Iter>
|
||||
where
|
||||
Iter: Iterator<Item = u8>,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn visit_key<K>(&mut self) -> Result<Option<K>>
|
||||
where
|
||||
K: de::Deserialize,
|
||||
{
|
||||
self.de.rdr.parse_whitespace()?;
|
||||
|
||||
if self.first {
|
||||
self.first = false;
|
||||
} else if self.de.rdr.peek()? == Some(b',') {
|
||||
// in Hjson the comma is optional and trailing commas are allowed
|
||||
self.de.rdr.eat_char();
|
||||
self.de.rdr.parse_whitespace()?;
|
||||
}
|
||||
|
||||
match self.de.rdr.peek()? {
|
||||
Some(b'}') => return Ok(None), // handled later for root
|
||||
Some(_) => {}
|
||||
None => {
|
||||
if self.root {
|
||||
return Ok(None);
|
||||
} else {
|
||||
return Err(self.de.rdr.error(ErrorCode::EOFWhileParsingObject));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.de.rdr.peek()? {
|
||||
Some(ch) => {
|
||||
self.de.state = if ch == b'"' {
|
||||
State::Normal
|
||||
} else {
|
||||
State::Keyname
|
||||
};
|
||||
Ok(Some(de::Deserialize::deserialize(self.de)?))
|
||||
}
|
||||
None => Err(self.de.rdr.error(ErrorCode::EOFWhileParsingValue)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_value<V>(&mut self) -> Result<V>
|
||||
where
|
||||
V: de::Deserialize,
|
||||
{
|
||||
self.de.parse_object_colon()?;
|
||||
|
||||
Ok(de::Deserialize::deserialize(self.de)?)
|
||||
}
|
||||
|
||||
fn end(&mut self) -> Result<()> {
|
||||
self.de.rdr.parse_whitespace()?;
|
||||
|
||||
match self.de.rdr.next_char()? {
|
||||
Some(b'}') => {
|
||||
if !self.root {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.de.rdr.error(ErrorCode::TrailingCharacters))
|
||||
} // todo
|
||||
}
|
||||
Some(_) => Err(self.de.rdr.error(ErrorCode::TrailingCharacters)),
|
||||
None => {
|
||||
if self.root {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.de.rdr.error(ErrorCode::EOFWhileParsingObject))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn missing_field<V>(&mut self, field: &'static str) -> Result<V>
|
||||
where
|
||||
V: de::Deserialize,
|
||||
{
|
||||
struct MissingFieldDeserializer(&'static str);
|
||||
|
||||
impl de::Deserializer for MissingFieldDeserializer {
|
||||
type Error = de::value::Error;
|
||||
|
||||
fn deserialize<V>(&mut self, _visitor: V) -> std::result::Result<V::Value, Self::Error>
|
||||
where
|
||||
V: de::Visitor,
|
||||
{
|
||||
let &mut MissingFieldDeserializer(field) = self;
|
||||
Err(de::value::Error::MissingField(field))
|
||||
}
|
||||
|
||||
fn deserialize_option<V>(
|
||||
&mut self,
|
||||
mut visitor: V,
|
||||
) -> std::result::Result<V::Value, Self::Error>
|
||||
where
|
||||
V: de::Visitor,
|
||||
{
|
||||
visitor.visit_none()
|
||||
}
|
||||
|
||||
forward_to_deserialize! {
|
||||
deserialize_bool();
|
||||
deserialize_usize();
|
||||
deserialize_u8();
|
||||
deserialize_u16();
|
||||
deserialize_u32();
|
||||
deserialize_u64();
|
||||
deserialize_isize();
|
||||
deserialize_i8();
|
||||
deserialize_i16();
|
||||
deserialize_i32();
|
||||
deserialize_i64();
|
||||
deserialize_f32();
|
||||
deserialize_f64();
|
||||
deserialize_char();
|
||||
deserialize_str();
|
||||
deserialize_string();
|
||||
deserialize_unit();
|
||||
deserialize_seq();
|
||||
deserialize_seq_fixed_size(len: usize);
|
||||
deserialize_bytes();
|
||||
deserialize_map();
|
||||
deserialize_unit_struct(name: &'static str);
|
||||
deserialize_newtype_struct(name: &'static str);
|
||||
deserialize_tuple_struct(name: &'static str, len: usize);
|
||||
deserialize_struct(name: &'static str, fields: &'static [&'static str]);
|
||||
deserialize_struct_field();
|
||||
deserialize_tuple(len: usize);
|
||||
deserialize_enum(name: &'static str, variants: &'static [&'static str]);
|
||||
deserialize_ignored_any();
|
||||
}
|
||||
}
|
||||
|
||||
let mut de = MissingFieldDeserializer(field);
|
||||
Ok(de::Deserialize::deserialize(&mut de)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Iter> de::VariantVisitor for Deserializer<Iter>
|
||||
where
|
||||
Iter: Iterator<Item = u8>,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn visit_variant<V>(&mut self) -> Result<V>
|
||||
where
|
||||
V: de::Deserialize,
|
||||
{
|
||||
let val = de::Deserialize::deserialize(self)?;
|
||||
self.parse_object_colon()?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn visit_unit(&mut self) -> Result<()> {
|
||||
de::Deserialize::deserialize(self)
|
||||
}
|
||||
|
||||
fn visit_newtype<T>(&mut self) -> Result<T>
|
||||
where
|
||||
T: de::Deserialize,
|
||||
{
|
||||
de::Deserialize::deserialize(self)
|
||||
}
|
||||
|
||||
fn visit_tuple<V>(&mut self, _len: usize, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor,
|
||||
{
|
||||
de::Deserializer::deserialize(self, visitor)
|
||||
}
|
||||
|
||||
fn visit_struct<V>(&mut self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: de::Visitor,
|
||||
{
|
||||
de::Deserializer::deserialize(self, visitor)
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Iterator that deserializes a stream into multiple Hjson values.
|
||||
pub struct StreamDeserializer<T, Iter>
|
||||
where
|
||||
Iter: Iterator<Item = u8>,
|
||||
T: de::Deserialize,
|
||||
{
|
||||
deser: Deserializer<Iter>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, Iter> StreamDeserializer<T, Iter>
|
||||
where
|
||||
Iter: Iterator<Item = u8>,
|
||||
T: de::Deserialize,
|
||||
{
|
||||
/// Returns an `Iterator` of decoded Hjson values from an iterator over
|
||||
/// `Iterator<Item=u8>`.
|
||||
pub fn new(iter: Iter) -> StreamDeserializer<T, Iter> {
|
||||
StreamDeserializer {
|
||||
deser: Deserializer::new(iter),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Iter> Iterator for StreamDeserializer<T, Iter>
|
||||
where
|
||||
Iter: Iterator<Item = u8>,
|
||||
T: de::Deserialize,
|
||||
{
|
||||
type Item = Result<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Result<T>> {
|
||||
// skip whitespaces, if any
|
||||
// this helps with trailing whitespaces, since whitespaces between
|
||||
// values are handled for us.
|
||||
if let Err(e) = self.deser.rdr.parse_whitespace() {
|
||||
return Some(Err(e));
|
||||
};
|
||||
|
||||
match self.deser.rdr.eof() {
|
||||
Ok(true) => None,
|
||||
Ok(false) => match de::Deserialize::deserialize(&mut self.deser) {
|
||||
Ok(v) => Some(Ok(v)),
|
||||
Err(e) => Some(Err(e)),
|
||||
},
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Decodes a Hjson value from an iterator over an iterator
|
||||
/// `Iterator<Item=u8>`.
|
||||
pub fn from_iter<I, T>(iter: I) -> Result<T>
|
||||
where
|
||||
I: Iterator<Item = io::Result<u8>>,
|
||||
T: de::Deserialize,
|
||||
{
|
||||
let fold: io::Result<Vec<_>> = iter.collect();
|
||||
if let Err(e) = fold {
|
||||
return Err(Error::Io(e));
|
||||
}
|
||||
|
||||
let bytes = fold.expect("Internal error: json parsing");
|
||||
|
||||
// deserialize tries first to decode with legacy support (new_for_root)
|
||||
// and then with the standard method if this fails.
|
||||
// todo: add compile switch
|
||||
|
||||
// deserialize and make sure the whole stream has been consumed
|
||||
let mut de = Deserializer::new_for_root(bytes.iter().cloned());
|
||||
let value = match de::Deserialize::deserialize(&mut de).and_then(|x| {
|
||||
de.end()?;
|
||||
Ok(x)
|
||||
}) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(_) => {
|
||||
let mut de2 = Deserializer::new(bytes.iter().cloned());
|
||||
match de::Deserialize::deserialize(&mut de2).and_then(|x| {
|
||||
de2.end()?;
|
||||
Ok(x)
|
||||
}) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* without legacy support:
|
||||
// deserialize and make sure the whole stream has been consumed
|
||||
let mut de = Deserializer::new(bytes.iter().map(|b| *b));
|
||||
let value = match de::Deserialize::deserialize(&mut de)
|
||||
.and_then(|x| { de.end()); Ok(x) })
|
||||
{
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
*/
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
/// Decodes a Hjson value from a `std::io::Read`.
|
||||
pub fn from_reader<R, T>(rdr: R) -> Result<T>
|
||||
where
|
||||
R: io::Read,
|
||||
T: de::Deserialize,
|
||||
{
|
||||
from_iter(rdr.bytes())
|
||||
}
|
||||
|
||||
/// Decodes a Hjson value from a byte slice `&[u8]`.
|
||||
pub fn from_slice<T>(v: &[u8]) -> Result<T>
|
||||
where
|
||||
T: de::Deserialize,
|
||||
{
|
||||
from_iter(v.iter().map(|byte| Ok(*byte)))
|
||||
}
|
||||
|
||||
/// Decodes a Hjson value from a `&str`.
|
||||
pub fn from_str<T>(s: &str) -> Result<T>
|
||||
where
|
||||
T: de::Deserialize,
|
||||
{
|
||||
from_slice(s.as_bytes())
|
||||
}
|
243
crates/nu-json/src/error.rs
Normal file
243
crates/nu-json/src/error.rs
Normal file
@ -0,0 +1,243 @@
|
||||
//! JSON Errors
|
||||
//!
|
||||
//! This module is centered around the `Error` and `ErrorCode` types, which represents all possible
|
||||
//! `serde_hjson` errors.
|
||||
|
||||
use std::error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::result;
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
use serde::de;
|
||||
use serde::ser;
|
||||
|
||||
/// The errors that can arise while parsing a JSON stream.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum ErrorCode {
|
||||
/// Catchall for syntax error messages
|
||||
Custom(String),
|
||||
|
||||
/// Incorrect type from value
|
||||
InvalidType(de::Type),
|
||||
|
||||
/// Incorrect value
|
||||
InvalidValue(String),
|
||||
|
||||
/// Invalid length
|
||||
InvalidLength(usize),
|
||||
|
||||
/// Unknown variant in an enum.
|
||||
UnknownVariant(String),
|
||||
|
||||
/// Unknown field in struct.
|
||||
UnknownField(String),
|
||||
|
||||
/// Struct is missing a field.
|
||||
MissingField(&'static str),
|
||||
|
||||
/// EOF while parsing a list.
|
||||
EOFWhileParsingList,
|
||||
|
||||
/// EOF while parsing an object.
|
||||
EOFWhileParsingObject,
|
||||
|
||||
/// EOF while parsing a string.
|
||||
EOFWhileParsingString,
|
||||
|
||||
/// EOF while parsing a JSON value.
|
||||
EOFWhileParsingValue,
|
||||
|
||||
/// Expected this character to be a `':'`.
|
||||
ExpectedColon,
|
||||
|
||||
/// Expected this character to be either a `','` or a `]`.
|
||||
ExpectedListCommaOrEnd,
|
||||
|
||||
/// Expected this character to be either a `','` or a `}`.
|
||||
ExpectedObjectCommaOrEnd,
|
||||
|
||||
/// Expected to parse either a `true`, `false`, or a `null`.
|
||||
ExpectedSomeIdent,
|
||||
|
||||
/// Expected this character to start a JSON value.
|
||||
ExpectedSomeValue,
|
||||
|
||||
/// Invalid hex escape code.
|
||||
InvalidEscape,
|
||||
|
||||
/// Invalid number.
|
||||
InvalidNumber,
|
||||
|
||||
/// Invalid unicode code point.
|
||||
InvalidUnicodeCodePoint,
|
||||
|
||||
/// Object key is not a string.
|
||||
KeyMustBeAString,
|
||||
|
||||
/// Lone leading surrogate in hex escape.
|
||||
LoneLeadingSurrogateInHexEscape,
|
||||
|
||||
/// JSON has non-whitespace trailing characters after the value.
|
||||
TrailingCharacters,
|
||||
|
||||
/// Unexpected end of hex excape.
|
||||
UnexpectedEndOfHexEscape,
|
||||
|
||||
/// Found a punctuator character when expecting a quoteless string.
|
||||
PunctuatorInQlString,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ErrorCode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
//use std::fmt::Debug;
|
||||
|
||||
match *self {
|
||||
ErrorCode::Custom(ref msg) => write!(f, "{}", msg),
|
||||
ErrorCode::InvalidType(ref ty) => write!(f, "invalid type: {:?}", ty),
|
||||
ErrorCode::InvalidValue(ref msg) => write!(f, "invalid value: {}", msg),
|
||||
ErrorCode::InvalidLength(ref len) => write!(f, "invalid value length {}", len),
|
||||
ErrorCode::UnknownVariant(ref variant) => write!(f, "unknown variant \"{}\"", variant),
|
||||
ErrorCode::UnknownField(ref field) => write!(f, "unknown field \"{}\"", field),
|
||||
ErrorCode::MissingField(ref field) => write!(f, "missing field \"{}\"", field),
|
||||
ErrorCode::EOFWhileParsingList => "EOF while parsing a list".fmt(f),
|
||||
ErrorCode::EOFWhileParsingObject => "EOF while parsing an object".fmt(f),
|
||||
ErrorCode::EOFWhileParsingString => "EOF while parsing a string".fmt(f),
|
||||
ErrorCode::EOFWhileParsingValue => "EOF while parsing a value".fmt(f),
|
||||
ErrorCode::ExpectedColon => "expected `:`".fmt(f),
|
||||
ErrorCode::ExpectedListCommaOrEnd => "expected `,` or `]`".fmt(f),
|
||||
ErrorCode::ExpectedObjectCommaOrEnd => "expected `,` or `}`".fmt(f),
|
||||
ErrorCode::ExpectedSomeIdent => "expected ident".fmt(f),
|
||||
ErrorCode::ExpectedSomeValue => "expected value".fmt(f),
|
||||
ErrorCode::InvalidEscape => "invalid escape".fmt(f),
|
||||
ErrorCode::InvalidNumber => "invalid number".fmt(f),
|
||||
ErrorCode::InvalidUnicodeCodePoint => "invalid unicode code point".fmt(f),
|
||||
ErrorCode::KeyMustBeAString => "key must be a string".fmt(f),
|
||||
ErrorCode::LoneLeadingSurrogateInHexEscape => {
|
||||
"lone leading surrogate in hex escape".fmt(f)
|
||||
}
|
||||
ErrorCode::TrailingCharacters => "trailing characters".fmt(f),
|
||||
ErrorCode::UnexpectedEndOfHexEscape => "unexpected end of hex escape".fmt(f),
|
||||
ErrorCode::PunctuatorInQlString => {
|
||||
"found a punctuator character when expecting a quoteless string".fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This type represents all possible errors that can occur when serializing or deserializing a
|
||||
/// value into JSON.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// The JSON value had some syntatic error.
|
||||
Syntax(ErrorCode, usize, usize),
|
||||
|
||||
/// Some IO error occurred when serializing or deserializing a value.
|
||||
Io(io::Error),
|
||||
|
||||
/// Some UTF8 error occurred while serializing or deserializing a value.
|
||||
FromUtf8(FromUtf8Error),
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn cause(&self) -> Option<&dyn error::Error> {
|
||||
match *self {
|
||||
Error::Io(ref error) => Some(error),
|
||||
Error::FromUtf8(ref error) => Some(error),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::Syntax(ref code, line, col) => {
|
||||
write!(fmt, "{:?} at line {} column {}", code, line, col)
|
||||
}
|
||||
Error::Io(ref error) => fmt::Display::fmt(error, fmt),
|
||||
Error::FromUtf8(ref error) => fmt::Display::fmt(error, fmt),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(error: io::Error) -> Error {
|
||||
Error::Io(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for Error {
|
||||
fn from(error: FromUtf8Error) -> Error {
|
||||
Error::FromUtf8(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<de::value::Error> for Error {
|
||||
fn from(error: de::value::Error) -> Error {
|
||||
match error {
|
||||
de::value::Error::Custom(e) => Error::Syntax(ErrorCode::Custom(e), 0, 0),
|
||||
de::value::Error::EndOfStream => de::Error::end_of_stream(),
|
||||
de::value::Error::InvalidType(ty) => Error::Syntax(ErrorCode::InvalidType(ty), 0, 0),
|
||||
de::value::Error::InvalidValue(msg) => {
|
||||
Error::Syntax(ErrorCode::InvalidValue(msg), 0, 0)
|
||||
}
|
||||
de::value::Error::InvalidLength(len) => {
|
||||
Error::Syntax(ErrorCode::InvalidLength(len), 0, 0)
|
||||
}
|
||||
de::value::Error::UnknownVariant(variant) => {
|
||||
Error::Syntax(ErrorCode::UnknownVariant(variant), 0, 0)
|
||||
}
|
||||
de::value::Error::UnknownField(field) => {
|
||||
Error::Syntax(ErrorCode::UnknownField(field), 0, 0)
|
||||
}
|
||||
de::value::Error::MissingField(field) => {
|
||||
Error::Syntax(ErrorCode::MissingField(field), 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl de::Error for Error {
|
||||
fn custom<T: Into<String>>(msg: T) -> Error {
|
||||
Error::Syntax(ErrorCode::Custom(msg.into()), 0, 0)
|
||||
}
|
||||
|
||||
fn end_of_stream() -> Error {
|
||||
Error::Syntax(ErrorCode::EOFWhileParsingValue, 0, 0)
|
||||
}
|
||||
|
||||
fn invalid_type(ty: de::Type) -> Error {
|
||||
Error::Syntax(ErrorCode::InvalidType(ty), 0, 0)
|
||||
}
|
||||
|
||||
fn invalid_value(msg: &str) -> Error {
|
||||
Error::Syntax(ErrorCode::InvalidValue(msg.to_owned()), 0, 0)
|
||||
}
|
||||
|
||||
fn invalid_length(len: usize) -> Error {
|
||||
Error::Syntax(ErrorCode::InvalidLength(len), 0, 0)
|
||||
}
|
||||
|
||||
fn unknown_variant(variant: &str) -> Error {
|
||||
Error::Syntax(ErrorCode::UnknownVariant(String::from(variant)), 0, 0)
|
||||
}
|
||||
|
||||
fn unknown_field(field: &str) -> Error {
|
||||
Error::Syntax(ErrorCode::UnknownField(String::from(field)), 0, 0)
|
||||
}
|
||||
|
||||
fn missing_field(field: &'static str) -> Error {
|
||||
Error::Syntax(ErrorCode::MissingField(field), 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ser::Error for Error {
|
||||
/// Raised when there is general error when deserializing a type.
|
||||
fn custom<T: Into<String>>(msg: T) -> Error {
|
||||
Error::Syntax(ErrorCode::Custom(msg.into()), 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper alias for `Result` objects that return a JSON `Error`.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
38
crates/nu-json/src/forward.rs
Normal file
38
crates/nu-json/src/forward.rs
Normal file
@ -0,0 +1,38 @@
|
||||
#[macro_export]
|
||||
/// Create a function to forward a specific serialize call to the generic deserialize
|
||||
macro_rules! forward_to_deserialize {
|
||||
($(
|
||||
$name:ident ( $( $arg:ident : $ty:ty ),* );
|
||||
)*) => {
|
||||
$(
|
||||
forward_to_deserialize!{
|
||||
func: $name ( $( $arg: $ty ),* );
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
(func: deserialize_enum ( $( $arg:ident : $ty:ty ),* );) => {
|
||||
fn deserialize_enum<V>(
|
||||
&mut self,
|
||||
$(_: $ty,)*
|
||||
_visitor: V,
|
||||
) -> ::std::result::Result<V::Value, Self::Error>
|
||||
where V: ::serde::de::EnumVisitor
|
||||
{
|
||||
Err(::serde::de::Error::invalid_type(::serde::de::Type::Enum))
|
||||
}
|
||||
};
|
||||
|
||||
(func: $name:ident ( $( $arg:ident : $ty:ty ),* );) => {
|
||||
#[inline]
|
||||
fn $name<V>(
|
||||
&mut self,
|
||||
$(_: $ty,)*
|
||||
visitor: V,
|
||||
) -> ::std::result::Result<V::Value, Self::Error>
|
||||
where V: ::serde::de::Visitor
|
||||
{
|
||||
self.deserialize(visitor)
|
||||
}
|
||||
};
|
||||
}
|
16
crates/nu-json/src/lib.rs
Normal file
16
crates/nu-json/src/lib.rs
Normal file
@ -0,0 +1,16 @@
|
||||
pub use self::de::{
|
||||
from_iter, from_reader, from_slice, from_str, Deserializer, StreamDeserializer,
|
||||
};
|
||||
pub use self::error::{Error, ErrorCode, Result};
|
||||
pub use self::ser::{to_string, to_vec, to_writer, Serializer};
|
||||
pub use self::value::{from_value, to_value, Map, Value};
|
||||
|
||||
#[macro_use]
|
||||
mod forward;
|
||||
|
||||
pub mod builder;
|
||||
pub mod de;
|
||||
pub mod error;
|
||||
pub mod ser;
|
||||
mod util;
|
||||
pub mod value;
|
1058
crates/nu-json/src/ser.rs
Normal file
1058
crates/nu-json/src/ser.rs
Normal file
File diff suppressed because it is too large
Load Diff
328
crates/nu-json/src/util.rs
Normal file
328
crates/nu-json/src/util.rs
Normal file
@ -0,0 +1,328 @@
|
||||
use std::io;
|
||||
use std::str;
|
||||
|
||||
use super::error::{Error, ErrorCode, Result};
|
||||
|
||||
pub struct StringReader<Iter: Iterator<Item = u8>> {
|
||||
iter: Iter,
|
||||
line: usize,
|
||||
col: usize,
|
||||
ch: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<Iter> StringReader<Iter>
|
||||
where
|
||||
Iter: Iterator<Item = u8>,
|
||||
{
|
||||
#[inline]
|
||||
pub fn new(iter: Iter) -> Self {
|
||||
StringReader {
|
||||
iter,
|
||||
line: 1,
|
||||
col: 0,
|
||||
ch: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Option<io::Result<u8>> {
|
||||
match self.iter.next() {
|
||||
None => None,
|
||||
Some(b'\n') => {
|
||||
self.line += 1;
|
||||
self.col = 0;
|
||||
Some(Ok(b'\n'))
|
||||
}
|
||||
Some(c) => {
|
||||
self.col += 1;
|
||||
Some(Ok(c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pos(&mut self) -> (usize, usize) {
|
||||
(self.line, self.col)
|
||||
}
|
||||
|
||||
pub fn eof(&mut self) -> Result<bool> {
|
||||
Ok(self.peek()?.is_none())
|
||||
}
|
||||
|
||||
pub fn peek_next(&mut self, idx: usize) -> Result<Option<u8>> {
|
||||
while self.ch.len() <= idx {
|
||||
match self.next() {
|
||||
Some(Err(err)) => return Err(Error::Io(err)),
|
||||
Some(Ok(ch)) => self.ch.push(ch),
|
||||
None => return Ok(None),
|
||||
}
|
||||
}
|
||||
Ok(Some(self.ch[idx]))
|
||||
}
|
||||
|
||||
pub fn peek(&mut self) -> Result<Option<u8>> {
|
||||
self.peek_next(0)
|
||||
}
|
||||
|
||||
pub fn peek_or_null(&mut self) -> Result<u8> {
|
||||
Ok(self.peek()?.unwrap_or(b'\x00'))
|
||||
}
|
||||
|
||||
pub fn eat_char(&mut self) -> u8 {
|
||||
self.ch.remove(0)
|
||||
}
|
||||
|
||||
pub fn uneat_char(&mut self, ch: u8) {
|
||||
self.ch.insert(0, ch);
|
||||
}
|
||||
|
||||
pub fn next_char(&mut self) -> Result<Option<u8>> {
|
||||
match self.ch.first() {
|
||||
Some(&ch) => {
|
||||
self.eat_char();
|
||||
Ok(Some(ch))
|
||||
}
|
||||
None => match self.next() {
|
||||
Some(Err(err)) => Err(Error::Io(err)),
|
||||
Some(Ok(ch)) => Ok(Some(ch)),
|
||||
None => Ok(None),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_char_or_null(&mut self) -> Result<u8> {
|
||||
Ok(self.next_char()?.unwrap_or(b'\x00'))
|
||||
}
|
||||
|
||||
fn eat_line(&mut self) -> Result<()> {
|
||||
loop {
|
||||
match self.peek()? {
|
||||
Some(b'\n') | None => return Ok(()),
|
||||
_ => {}
|
||||
}
|
||||
self.eat_char();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_whitespace(&mut self) -> Result<()> {
|
||||
loop {
|
||||
match self.peek_or_null()? {
|
||||
b' ' | b'\n' | b'\t' | b'\r' => {
|
||||
self.eat_char();
|
||||
}
|
||||
b'#' => self.eat_line()?,
|
||||
b'/' => {
|
||||
match self.peek_next(1)? {
|
||||
Some(b'/') => self.eat_line()?,
|
||||
Some(b'*') => {
|
||||
self.eat_char();
|
||||
self.eat_char();
|
||||
while !(self.peek()?.unwrap_or(b'*') == b'*'
|
||||
&& self.peek_next(1)?.unwrap_or(b'/') == b'/')
|
||||
{
|
||||
self.eat_char();
|
||||
}
|
||||
self.eat_char();
|
||||
self.eat_char();
|
||||
}
|
||||
Some(_) => {
|
||||
self.eat_char();
|
||||
}
|
||||
None => return Err(self.error(ErrorCode::TrailingCharacters)), //todo
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error(&mut self, reason: ErrorCode) -> Error {
|
||||
Error::Syntax(reason, self.line, self.col)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Number {
|
||||
I64(i64),
|
||||
U64(u64),
|
||||
F64(f64),
|
||||
}
|
||||
|
||||
pub struct ParseNumber<Iter: Iterator<Item = u8>> {
|
||||
rdr: StringReader<Iter>,
|
||||
result: Vec<u8>,
|
||||
}
|
||||
|
||||
// macro_rules! try_or_invalid {
|
||||
// ($e:expr) => {
|
||||
// match $e {
|
||||
// Some(v) => v,
|
||||
// None => { return Err(Error::Syntax(ErrorCode::InvalidNumber, 0, 0)); }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<Iter: Iterator<Item = u8>> ParseNumber<Iter> {
|
||||
#[inline]
|
||||
pub fn new(iter: Iter) -> Self {
|
||||
ParseNumber {
|
||||
rdr: StringReader::new(iter),
|
||||
result: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&mut self, stop_at_next: bool) -> Result<Number> {
|
||||
match self.try_parse() {
|
||||
Ok(()) => {
|
||||
self.rdr.parse_whitespace()?;
|
||||
|
||||
let mut ch = self.rdr.next_char_or_null()?;
|
||||
|
||||
if stop_at_next {
|
||||
let ch2 = self.rdr.peek_or_null()?;
|
||||
// end scan if we find a punctuator character like ,}] or a comment
|
||||
if ch == b','
|
||||
|| ch == b'}'
|
||||
|| ch == b']'
|
||||
|| ch == b'#'
|
||||
|| ch == b'/' && (ch2 == b'/' || ch2 == b'*')
|
||||
{
|
||||
ch = b'\x00';
|
||||
}
|
||||
}
|
||||
|
||||
match ch {
|
||||
b'\x00' => {
|
||||
let res =
|
||||
str::from_utf8(&self.result).expect("Internal error: json parsing");
|
||||
|
||||
let mut is_float = false;
|
||||
for ch in res.chars() {
|
||||
if ch == '.' || ch == 'e' || ch == 'E' {
|
||||
is_float = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if is_float {
|
||||
Ok(Number::F64(
|
||||
res.parse::<f64>().expect("Internal error: json parsing"),
|
||||
))
|
||||
} else if res.starts_with('-') {
|
||||
Ok(Number::I64(
|
||||
res.parse::<i64>().expect("Internal error: json parsing"),
|
||||
))
|
||||
} else {
|
||||
Ok(Number::U64(
|
||||
res.parse::<u64>().expect("Internal error: json parsing"),
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => Err(Error::Syntax(ErrorCode::InvalidNumber, 0, 0)),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_parse(&mut self) -> Result<()> {
|
||||
if self.rdr.peek_or_null()? == b'-' {
|
||||
self.result.push(self.rdr.eat_char());
|
||||
}
|
||||
|
||||
let mut has_value = false;
|
||||
|
||||
if self.rdr.peek_or_null()? == b'0' {
|
||||
self.result.push(self.rdr.eat_char());
|
||||
has_value = true;
|
||||
// There can be only one leading '0'.
|
||||
if let b'0'..=b'9' = self.rdr.peek_or_null()? {
|
||||
return Err(Error::Syntax(ErrorCode::InvalidNumber, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.rdr.peek_or_null()? {
|
||||
b'0'..=b'9' => {
|
||||
self.result.push(self.rdr.eat_char());
|
||||
has_value = true;
|
||||
}
|
||||
b'.' => {
|
||||
if !has_value {
|
||||
return Err(Error::Syntax(ErrorCode::InvalidNumber, 0, 0));
|
||||
}
|
||||
self.rdr.eat_char();
|
||||
return self.try_decimal();
|
||||
}
|
||||
b'e' | b'E' => {
|
||||
if !has_value {
|
||||
return Err(Error::Syntax(ErrorCode::InvalidNumber, 0, 0));
|
||||
}
|
||||
self.rdr.eat_char();
|
||||
return self.try_exponent();
|
||||
}
|
||||
_ => {
|
||||
if !has_value {
|
||||
return Err(Error::Syntax(ErrorCode::InvalidNumber, 0, 0));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_decimal(&mut self) -> Result<()> {
|
||||
self.result.push(b'.');
|
||||
|
||||
// Make sure a digit follows the decimal place.
|
||||
match self.rdr.next_char_or_null()? {
|
||||
c @ b'0'..=b'9' => {
|
||||
self.result.push(c);
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::Syntax(ErrorCode::InvalidNumber, 0, 0));
|
||||
}
|
||||
};
|
||||
|
||||
while let b'0'..=b'9' = self.rdr.peek_or_null()? {
|
||||
self.result.push(self.rdr.eat_char());
|
||||
}
|
||||
|
||||
match self.rdr.peek_or_null()? {
|
||||
b'e' | b'E' => {
|
||||
self.rdr.eat_char();
|
||||
self.try_exponent()
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_exponent(&mut self) -> Result<()> {
|
||||
self.result.push(b'e');
|
||||
|
||||
match self.rdr.peek_or_null()? {
|
||||
b'+' => {
|
||||
self.result.push(self.rdr.eat_char());
|
||||
}
|
||||
b'-' => {
|
||||
self.result.push(self.rdr.eat_char());
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
// Make sure a digit follows the exponent place.
|
||||
match self.rdr.next_char_or_null()? {
|
||||
c @ b'0'..=b'9' => {
|
||||
self.result.push(c);
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::Syntax(ErrorCode::InvalidNumber, 0, 0));
|
||||
}
|
||||
};
|
||||
|
||||
while let b'0'..=b'9' = self.rdr.peek_or_null()? {
|
||||
self.result.push(self.rdr.eat_char());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
1349
crates/nu-json/src/value.rs
Normal file
1349
crates/nu-json/src/value.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -307,7 +307,7 @@ pub fn format_primitive(primitive: &Primitive, field_name: Option<&String>) -> S
|
||||
);
|
||||
|
||||
for member in members {
|
||||
f.push_str(".");
|
||||
f.push('.');
|
||||
f.push_str(&member.display())
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ use std::cmp::Ordering;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A "Text" is like a string except that it can be cheaply cloned.
|
||||
/// You can also "extract" subtexts quite cheaply. You can also deref
|
||||
@ -12,7 +11,7 @@ use std::sync::Arc;
|
||||
/// Used to represent the value of an input file.
|
||||
#[derive(Clone)]
|
||||
pub struct Text {
|
||||
text: Arc<String>,
|
||||
text: String,
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
@ -39,11 +38,11 @@ impl Text {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<String>> for Text {
|
||||
fn from(text: Arc<String>) -> Self {
|
||||
impl From<&str> for Text {
|
||||
fn from(text: &str) -> Self {
|
||||
let end = text.len();
|
||||
Self {
|
||||
text,
|
||||
text: text.to_string(),
|
||||
start: 0,
|
||||
end,
|
||||
}
|
||||
@ -58,20 +57,13 @@ impl AsRef<str> for Text {
|
||||
|
||||
impl From<String> for Text {
|
||||
fn from(text: String) -> Self {
|
||||
Text::from(Arc::new(text))
|
||||
let end = text.len();
|
||||
Self {
|
||||
text,
|
||||
start: 0,
|
||||
end,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&String> for Text {
|
||||
fn from(text: &String) -> Self {
|
||||
Text::from(text.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Text {
|
||||
fn from(text: &str) -> Self {
|
||||
Text::from(text.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Text> for Text {
|
||||
|
@ -508,7 +508,7 @@ pub fn forgiving_insert_data_at_column_path(
|
||||
}
|
||||
UnspannedPathMember::Int(int) => {
|
||||
let mut rows = vec![];
|
||||
let size = int.to_usize().unwrap_or_else(|| 0);
|
||||
let size = int.to_usize().unwrap_or(0);
|
||||
|
||||
for _ in 0..=size {
|
||||
rows.push(
|
||||
|
@ -149,7 +149,7 @@ impl RenderContext {
|
||||
}
|
||||
}
|
||||
pub fn update(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let terminal_size = crossterm::terminal::size().unwrap_or_else(|_| (80, 24));
|
||||
let terminal_size = crossterm::terminal::size().unwrap_or((80, 24));
|
||||
|
||||
if (self.width != terminal_size.0 as usize) || (self.height != terminal_size.1 as usize) {
|
||||
let _ = std::io::stdout().execute(crossterm::cursor::Hide);
|
||||
|
@ -119,9 +119,8 @@ impl<'a> Line<'a> {
|
||||
.marker(marker)
|
||||
.graph_type(GraphType::Line)
|
||||
.style(
|
||||
Style::default().fg(*DEFAULT_LINE_COLORS
|
||||
.get(idx)
|
||||
.unwrap_or_else(|| &DEFAULT_COLOR)),
|
||||
Style::default()
|
||||
.fg(*DEFAULT_LINE_COLORS.get(idx).unwrap_or(&DEFAULT_COLOR)),
|
||||
)
|
||||
.data(data_series)
|
||||
})
|
||||
|
@ -284,7 +284,7 @@ impl SubCommand {
|
||||
let formatter = if self.format.is_some() {
|
||||
let default = String::from("%b-%Y");
|
||||
|
||||
let string_fmt = self.format.as_ref().unwrap_or_else(|| &default);
|
||||
let string_fmt = self.format.as_ref().unwrap_or(&default);
|
||||
|
||||
Some(nu_data::utils::helpers::date_formatter(
|
||||
string_fmt.to_string(),
|
||||
@ -331,7 +331,7 @@ impl SubCommand {
|
||||
let formatter = if self.format.is_some() {
|
||||
let default = String::from("%b-%Y");
|
||||
|
||||
let string_fmt = self.format.as_ref().unwrap_or_else(|| &default);
|
||||
let string_fmt = self.format.as_ref().unwrap_or(&default);
|
||||
|
||||
Some(nu_data::utils::helpers::date_formatter(
|
||||
string_fmt.to_string(),
|
||||
|
@ -282,7 +282,7 @@ impl SubCommand {
|
||||
let formatter = if self.format.is_some() {
|
||||
let default = String::from("%b-%Y");
|
||||
|
||||
let string_fmt = self.format.as_ref().unwrap_or_else(|| &default);
|
||||
let string_fmt = self.format.as_ref().unwrap_or(&default);
|
||||
|
||||
Some(nu_data::utils::helpers::date_formatter(
|
||||
string_fmt.to_string(),
|
||||
@ -329,7 +329,7 @@ impl SubCommand {
|
||||
let formatter = if self.format.is_some() {
|
||||
let default = String::from("%b-%Y");
|
||||
|
||||
let string_fmt = self.format.as_ref().unwrap_or_else(|| &default);
|
||||
let string_fmt = self.format.as_ref().unwrap_or(&default);
|
||||
|
||||
Some(nu_data::utils::helpers::date_formatter(
|
||||
string_fmt.to_string(),
|
||||
|
@ -13,7 +13,7 @@ impl TextView {
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn view_text_value(value: &Value) {
|
||||
let (mut term_width, _) = term_size::dimensions().unwrap_or_else(|| (80, 20));
|
||||
let (mut term_width, _) = term_size::dimensions().unwrap_or((80, 20));
|
||||
let mut tab_width: u64 = 4;
|
||||
let mut colored_output = true;
|
||||
let mut true_color = true;
|
||||
|
Loading…
Reference in New Issue
Block a user