Merge branch 'main' into feature/nu_plugin_once
This commit is contained in:
commit
489965cdc9
33
Cargo.lock
generated
33
Cargo.lock
generated
|
@ -605,7 +605,7 @@ dependencies = [
|
|||
"encoding_rs",
|
||||
"log",
|
||||
"once_cell",
|
||||
"quick-xml",
|
||||
"quick-xml 0.31.0",
|
||||
"serde",
|
||||
"zip",
|
||||
]
|
||||
|
@ -3094,7 +3094,7 @@ dependencies = [
|
|||
"pretty_assertions",
|
||||
"print-positions",
|
||||
"procfs",
|
||||
"quick-xml",
|
||||
"quick-xml 0.31.0",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"rand",
|
||||
|
@ -3476,12 +3476,14 @@ dependencies = [
|
|||
name = "nu_plugin_formats"
|
||||
version = "0.96.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"eml-parser",
|
||||
"ical",
|
||||
"indexmap",
|
||||
"nu-plugin",
|
||||
"nu-plugin-test-support",
|
||||
"nu-protocol",
|
||||
"plist",
|
||||
"rust-ini",
|
||||
]
|
||||
|
||||
|
@ -4158,6 +4160,19 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plist"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap",
|
||||
"quick-xml 0.32.0",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polars"
|
||||
version = "0.41.2"
|
||||
|
@ -4826,6 +4841,15 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quickcheck"
|
||||
version = "1.0.3"
|
||||
|
@ -5016,8 +5040,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "reedline"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f8c676a3f3814a23c6a0fc9dff6b6c35b2e04df8134aae6f3929cc34de21a53"
|
||||
source = "git+https://github.com/nushell/reedline?branch=main#919292e40fd417e3da882692021961b444150c59"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"chrono",
|
||||
|
@ -6883,7 +6906,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quick-xml 0.31.0",
|
||||
"quote",
|
||||
]
|
||||
|
||||
|
|
|
@ -306,8 +306,8 @@ bench = false
|
|||
|
||||
# To use a development version of a dependency please use a global override here
|
||||
# changing versions in each sub-crate of the workspace is tedious
|
||||
# [patch.crates-io]
|
||||
# reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||
[patch.crates-io]
|
||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
|
||||
|
||||
# Run all benchmarks with `cargo bench`
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy},
|
||||
completions::{Completer, CompletionOptions, MatchAlgorithm},
|
||||
SuggestionKind,
|
||||
};
|
||||
use nu_parser::FlatShape;
|
||||
|
@ -193,11 +193,7 @@ impl Completer for CommandCompletion {
|
|||
};
|
||||
|
||||
if !subcommands.is_empty() {
|
||||
return sort_suggestions(
|
||||
&String::from_utf8_lossy(&prefix),
|
||||
subcommands,
|
||||
SortBy::LevenshteinDistance,
|
||||
);
|
||||
return sort_suggestions(&String::from_utf8_lossy(&prefix), subcommands, options);
|
||||
}
|
||||
|
||||
let config = working_set.get_config();
|
||||
|
@ -222,11 +218,7 @@ impl Completer for CommandCompletion {
|
|||
vec![]
|
||||
};
|
||||
|
||||
sort_suggestions(
|
||||
&String::from_utf8_lossy(&prefix),
|
||||
commands,
|
||||
SortBy::LevenshteinDistance,
|
||||
)
|
||||
sort_suggestions(&String::from_utf8_lossy(&prefix), commands, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ impl NuCompleter {
|
|||
let options = CompletionOptions {
|
||||
case_sensitive: config.case_sensitive_completions,
|
||||
match_algorithm: config.completion_algorithm.into(),
|
||||
sort: config.completion_sort,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
|
@ -2,17 +2,18 @@ use crate::{
|
|||
completions::{matches, CompletionOptions},
|
||||
SemanticSuggestion,
|
||||
};
|
||||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||
use nu_ansi_term::Style;
|
||||
use nu_engine::env_to_string;
|
||||
use nu_path::{expand_to_real_path, home_dir};
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
levenshtein_distance, Span,
|
||||
CompletionSort, Span,
|
||||
};
|
||||
use nu_utils::get_ls_colors;
|
||||
use std::path::{is_separator, Component, Path, PathBuf, MAIN_SEPARATOR as SEP};
|
||||
|
||||
use super::SortBy;
|
||||
use super::MatchAlgorithm;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PathBuiltFromString {
|
||||
|
@ -20,12 +21,21 @@ pub struct PathBuiltFromString {
|
|||
isdir: bool,
|
||||
}
|
||||
|
||||
fn complete_rec(
|
||||
/// Recursively goes through paths that match a given `partial`.
|
||||
/// built: State struct for a valid matching path built so far.
|
||||
///
|
||||
/// `isdir`: whether the current partial path has a trailing slash.
|
||||
/// Parsing a path string into a pathbuf loses that bit of information.
|
||||
///
|
||||
/// want_directory: Whether we want only directories as completion matches.
|
||||
/// Some commands like `cd` can only be run on directories whereas others
|
||||
/// like `ls` can be run on regular files as well.
|
||||
pub fn complete_rec(
|
||||
partial: &[&str],
|
||||
built: &PathBuiltFromString,
|
||||
cwd: &Path,
|
||||
options: &CompletionOptions,
|
||||
dir: bool,
|
||||
want_directory: bool,
|
||||
isdir: bool,
|
||||
) -> Vec<PathBuiltFromString> {
|
||||
let mut completions = vec![];
|
||||
|
@ -35,7 +45,7 @@ fn complete_rec(
|
|||
let mut built = built.clone();
|
||||
built.parts.push(base.to_string());
|
||||
built.isdir = true;
|
||||
return complete_rec(rest, &built, cwd, options, dir, isdir);
|
||||
return complete_rec(rest, &built, cwd, options, want_directory, isdir);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,24 +66,41 @@ fn complete_rec(
|
|||
built.parts.push(entry_name.clone());
|
||||
built.isdir = entry_isdir;
|
||||
|
||||
if !dir || entry_isdir {
|
||||
if !want_directory || entry_isdir {
|
||||
entries.push((entry_name, built));
|
||||
}
|
||||
}
|
||||
|
||||
let prefix = partial.first().unwrap_or(&"");
|
||||
let sorted_entries = sort_completions(prefix, entries, SortBy::Ascending, |(entry, _)| entry);
|
||||
let sorted_entries = sort_completions(prefix, entries, options, |(entry, _)| entry);
|
||||
|
||||
for (entry_name, built) in sorted_entries {
|
||||
match partial.split_first() {
|
||||
Some((base, rest)) => {
|
||||
if matches(base, &entry_name, options) {
|
||||
// We use `isdir` to confirm that the current component has
|
||||
// at least one next component or a slash.
|
||||
// Serves as confirmation to ignore longer completions for
|
||||
// components in between.
|
||||
if !rest.is_empty() || isdir {
|
||||
completions.extend(complete_rec(rest, &built, cwd, options, dir, isdir));
|
||||
completions.extend(complete_rec(
|
||||
rest,
|
||||
&built,
|
||||
cwd,
|
||||
options,
|
||||
want_directory,
|
||||
isdir,
|
||||
));
|
||||
} else {
|
||||
completions.push(built);
|
||||
}
|
||||
}
|
||||
if entry_name.eq(base)
|
||||
&& matches!(options.match_algorithm, MatchAlgorithm::Prefix)
|
||||
&& isdir
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
completions.push(built);
|
||||
|
@ -279,33 +306,37 @@ pub fn adjust_if_intermediate(
|
|||
pub fn sort_suggestions(
|
||||
prefix: &str,
|
||||
items: Vec<SemanticSuggestion>,
|
||||
sort_by: SortBy,
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<SemanticSuggestion> {
|
||||
sort_completions(prefix, items, sort_by, |it| &it.suggestion.value)
|
||||
sort_completions(prefix, items, options, |it| &it.suggestion.value)
|
||||
}
|
||||
|
||||
/// # Arguments
|
||||
/// * `prefix` - What the user's typed, for sorting by Levenshtein distance
|
||||
/// * `prefix` - What the user's typed, for sorting by fuzzy matcher score
|
||||
pub fn sort_completions<T>(
|
||||
prefix: &str,
|
||||
mut items: Vec<T>,
|
||||
sort_by: SortBy,
|
||||
options: &CompletionOptions,
|
||||
get_value: fn(&T) -> &str,
|
||||
) -> Vec<T> {
|
||||
// Sort items
|
||||
match sort_by {
|
||||
SortBy::LevenshteinDistance => {
|
||||
items.sort_by(|a, b| {
|
||||
let a_distance = levenshtein_distance(prefix, get_value(a));
|
||||
let b_distance = levenshtein_distance(prefix, get_value(b));
|
||||
a_distance.cmp(&b_distance)
|
||||
});
|
||||
}
|
||||
SortBy::Ascending => {
|
||||
items.sort_by(|a, b| get_value(a).cmp(get_value(b)));
|
||||
}
|
||||
SortBy::None => {}
|
||||
};
|
||||
if options.sort == CompletionSort::Smart && options.match_algorithm == MatchAlgorithm::Fuzzy {
|
||||
let mut matcher = SkimMatcherV2::default();
|
||||
if options.case_sensitive {
|
||||
matcher = matcher.respect_case();
|
||||
} else {
|
||||
matcher = matcher.ignore_case();
|
||||
};
|
||||
items.sort_by(|a, b| {
|
||||
let a_str = get_value(a);
|
||||
let b_str = get_value(b);
|
||||
let a_score = matcher.fuzzy_match(a_str, prefix).unwrap_or_default();
|
||||
let b_score = matcher.fuzzy_match(b_str, prefix).unwrap_or_default();
|
||||
b_score.cmp(&a_score).then(a_str.cmp(b_str))
|
||||
});
|
||||
} else {
|
||||
items.sort_by(|a, b| get_value(a).cmp(get_value(b)));
|
||||
}
|
||||
|
||||
items
|
||||
}
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
|
||||
use nu_parser::trim_quotes_str;
|
||||
use nu_protocol::CompletionAlgorithm;
|
||||
use nu_protocol::{CompletionAlgorithm, CompletionSort};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum SortBy {
|
||||
LevenshteinDistance,
|
||||
Ascending,
|
||||
None,
|
||||
}
|
||||
|
||||
/// Describes how suggestions should be matched.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum MatchAlgorithm {
|
||||
/// Only show suggestions which begin with the given input
|
||||
///
|
||||
|
@ -96,6 +89,7 @@ pub struct CompletionOptions {
|
|||
pub case_sensitive: bool,
|
||||
pub positional: bool,
|
||||
pub match_algorithm: MatchAlgorithm,
|
||||
pub sort: CompletionSort,
|
||||
}
|
||||
|
||||
impl Default for CompletionOptions {
|
||||
|
@ -104,6 +98,7 @@ impl Default for CompletionOptions {
|
|||
case_sensitive: true,
|
||||
positional: true,
|
||||
match_algorithm: MatchAlgorithm::Prefix,
|
||||
sort: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use crate::completions::{
|
||||
completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm,
|
||||
SemanticSuggestion, SortBy,
|
||||
SemanticSuggestion,
|
||||
};
|
||||
use nu_engine::eval_call;
|
||||
use nu_protocol::{
|
||||
ast::{Argument, Call, Expr, Expression},
|
||||
debugger::WithoutDebug,
|
||||
engine::{Stack, StateWorkingSet},
|
||||
PipelineData, Span, Type, Value,
|
||||
CompletionSort, PipelineData, Span, Type, Value,
|
||||
};
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
use std::collections::HashMap;
|
||||
|
@ -18,7 +18,6 @@ pub struct CustomCompletion {
|
|||
stack: Stack,
|
||||
decl_id: usize,
|
||||
line: String,
|
||||
sort_by: SortBy,
|
||||
}
|
||||
|
||||
impl CustomCompletion {
|
||||
|
@ -27,7 +26,6 @@ impl CustomCompletion {
|
|||
stack,
|
||||
decl_id,
|
||||
line,
|
||||
sort_by: SortBy::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,10 +91,6 @@ impl Completer for CustomCompletion {
|
|||
.and_then(|val| val.as_bool().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
if should_sort {
|
||||
self.sort_by = SortBy::Ascending;
|
||||
}
|
||||
|
||||
custom_completion_options = Some(CompletionOptions {
|
||||
case_sensitive: options
|
||||
.get("case_sensitive")
|
||||
|
@ -114,6 +108,11 @@ impl Completer for CustomCompletion {
|
|||
.unwrap_or(MatchAlgorithm::Prefix),
|
||||
None => completion_options.match_algorithm,
|
||||
},
|
||||
sort: if should_sort {
|
||||
CompletionSort::Alphabetical
|
||||
} else {
|
||||
CompletionSort::Smart
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -124,12 +123,11 @@ impl Completer for CustomCompletion {
|
|||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let suggestions = if let Some(custom_completion_options) = custom_completion_options {
|
||||
filter(&prefix, suggestions, &custom_completion_options)
|
||||
} else {
|
||||
filter(&prefix, suggestions, completion_options)
|
||||
};
|
||||
sort_suggestions(&String::from_utf8_lossy(&prefix), suggestions, self.sort_by)
|
||||
let options = custom_completion_options
|
||||
.as_ref()
|
||||
.unwrap_or(completion_options);
|
||||
let suggestions = filter(&prefix, suggestions, completion_options);
|
||||
sort_suggestions(&String::from_utf8_lossy(&prefix), suggestions, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use nu_protocol::{
|
|||
use reedline::Suggestion;
|
||||
use std::path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR};
|
||||
|
||||
use super::{completion_common::sort_suggestions, SemanticSuggestion, SortBy};
|
||||
use super::{completion_common::sort_suggestions, SemanticSuggestion};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct DotNuCompletion {}
|
||||
|
@ -130,6 +130,6 @@ impl Completer for DotNuCompletion {
|
|||
})
|
||||
.collect();
|
||||
|
||||
sort_suggestions(&prefix_str, output, SortBy::Ascending)
|
||||
sort_suggestions(&prefix_str, output, options)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use crate::completions::{
|
||||
completion_common::sort_suggestions, Completer, CompletionOptions, SortBy,
|
||||
};
|
||||
use crate::completions::{completion_common::sort_suggestions, Completer, CompletionOptions};
|
||||
use nu_protocol::{
|
||||
ast::{Expr, Expression},
|
||||
engine::{Stack, StateWorkingSet},
|
||||
|
@ -90,7 +88,7 @@ impl Completer for FlagCompletion {
|
|||
}
|
||||
}
|
||||
|
||||
return sort_suggestions(&String::from_utf8_lossy(&prefix), output, SortBy::Ascending);
|
||||
return sort_suggestions(&String::from_utf8_lossy(&prefix), output, options);
|
||||
}
|
||||
|
||||
vec![]
|
||||
|
|
|
@ -13,7 +13,7 @@ mod variable_completions;
|
|||
pub use base::{Completer, SemanticSuggestion, SuggestionKind};
|
||||
pub use command_completions::CommandCompletion;
|
||||
pub use completer::NuCompleter;
|
||||
pub use completion_options::{CompletionOptions, MatchAlgorithm, SortBy};
|
||||
pub use completion_options::{CompletionOptions, MatchAlgorithm};
|
||||
pub use custom_completions::CustomCompletion;
|
||||
pub use directory_completions::DirectoryCompletion;
|
||||
pub use dotnu_completions::DotNuCompletion;
|
||||
|
|
|
@ -9,7 +9,7 @@ use nu_protocol::{
|
|||
use reedline::Suggestion;
|
||||
use std::str;
|
||||
|
||||
use super::{completion_common::sort_suggestions, SortBy};
|
||||
use super::completion_common::sort_suggestions;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VariableCompletion {
|
||||
|
@ -72,7 +72,7 @@ impl Completer for VariableCompletion {
|
|||
}
|
||||
}
|
||||
|
||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||
return sort_suggestions(&prefix_str, output, options);
|
||||
}
|
||||
} else {
|
||||
// No nesting provided, return all env vars
|
||||
|
@ -93,7 +93,7 @@ impl Completer for VariableCompletion {
|
|||
}
|
||||
}
|
||||
|
||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||
return sort_suggestions(&prefix_str, output, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ impl Completer for VariableCompletion {
|
|||
}
|
||||
}
|
||||
|
||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||
return sort_suggestions(&prefix_str, output, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ impl Completer for VariableCompletion {
|
|||
}
|
||||
}
|
||||
|
||||
return sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||
return sort_suggestions(&prefix_str, output, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ impl Completer for VariableCompletion {
|
|||
}
|
||||
}
|
||||
|
||||
output = sort_suggestions(&prefix_str, output, SortBy::Ascending);
|
||||
output = sort_suggestions(&prefix_str, output, options);
|
||||
|
||||
output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use nu_engine::documentation::get_flags_section;
|
||||
use nu_engine::documentation::{get_flags_section, HelpStyle};
|
||||
use nu_protocol::{engine::EngineState, levenshtein_distance, Config};
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
use reedline::{Completer, Suggestion};
|
||||
|
@ -20,6 +20,9 @@ impl NuHelpCompleter {
|
|||
fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let folded_line = line.to_folded_case();
|
||||
|
||||
let mut help_style = HelpStyle::default();
|
||||
help_style.update_from_config(&self.engine_state, &self.config);
|
||||
|
||||
let mut commands = self
|
||||
.engine_state
|
||||
.get_decls_sorted(false)
|
||||
|
@ -60,12 +63,9 @@ impl NuHelpCompleter {
|
|||
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
|
||||
|
||||
if !sig.named.is_empty() {
|
||||
long_desc.push_str(&get_flags_section(
|
||||
Some(&self.engine_state),
|
||||
Some(&self.config),
|
||||
&sig,
|
||||
|v| v.to_parsable_string(", ", &self.config),
|
||||
))
|
||||
long_desc.push_str(&get_flags_section(&sig, &help_style, |v| {
|
||||
v.to_parsable_string(", ", &self.config)
|
||||
}))
|
||||
}
|
||||
|
||||
if !sig.required_positional.is_empty()
|
||||
|
|
|
@ -89,14 +89,12 @@ fn subcommand_completer() -> NuCompleter {
|
|||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Use fuzzy matching, because subcommands are sorted by Levenshtein distance,
|
||||
// and that's not very useful with prefix matching
|
||||
let commands = r#"
|
||||
$env.config.completions.algorithm = "fuzzy"
|
||||
def foo [] {}
|
||||
def "foo bar" [] {}
|
||||
def "foo abaz" [] {}
|
||||
def "foo aabrr" [] {}
|
||||
def "foo aabcrr" [] {}
|
||||
def food [] {}
|
||||
"#;
|
||||
assert!(support::merge_input(commands.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
@ -105,6 +103,22 @@ fn subcommand_completer() -> NuCompleter {
|
|||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||
}
|
||||
|
||||
/// Use fuzzy completions but sort in alphabetical order
|
||||
#[fixture]
|
||||
fn fuzzy_alpha_sort_completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
let config = r#"
|
||||
$env.config.completions.algorithm = "fuzzy"
|
||||
$env.config.completions.sort = "alphabetical"
|
||||
"#;
|
||||
assert!(support::merge_input(config.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(Arc::new(engine), Arc::new(stack))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variables_dollar_sign_with_variablecompletion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
@ -774,7 +788,7 @@ fn subcommand_completions(mut subcommand_completer: NuCompleter) {
|
|||
let prefix = "foo br";
|
||||
let suggestions = subcommand_completer.complete(prefix, prefix.len());
|
||||
match_suggestions(
|
||||
&vec!["foo bar".to_string(), "foo aabrr".to_string()],
|
||||
&vec!["foo bar".to_string(), "foo aabcrr".to_string()],
|
||||
&suggestions,
|
||||
);
|
||||
|
||||
|
@ -783,8 +797,8 @@ fn subcommand_completions(mut subcommand_completer: NuCompleter) {
|
|||
match_suggestions(
|
||||
&vec![
|
||||
"foo bar".to_string(),
|
||||
"foo aabcrr".to_string(),
|
||||
"foo abaz".to_string(),
|
||||
"foo aabrr".to_string(),
|
||||
],
|
||||
&suggestions,
|
||||
);
|
||||
|
@ -1270,6 +1284,17 @@ fn custom_completer_triggers_cursor_after_word(mut custom_completer: NuCompleter
|
|||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn sort_fuzzy_completions_in_alphabetical_order(mut fuzzy_alpha_sort_completer: NuCompleter) {
|
||||
let suggestions = fuzzy_alpha_sort_completer.complete("ls nu", 5);
|
||||
// Even though "nushell" is a better match, it should come second because
|
||||
// the completions should be sorted in alphabetical order
|
||||
match_suggestions(
|
||||
&vec!["custom_completion.nu".into(), "nushell".into()],
|
||||
&suggestions,
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore = "was reverted, still needs fixing"]
|
||||
#[rstest]
|
||||
fn alias_offset_bug_7648() {
|
||||
|
|
|
@ -379,42 +379,47 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||
|
||||
// If input is not a timestamp, try parsing it as a string
|
||||
let span = input.span();
|
||||
match input {
|
||||
Value::String { val, .. } => {
|
||||
match dateformat {
|
||||
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
||||
Ok(d) => Value::date ( d, head ),
|
||||
Err(reason) => {
|
||||
match NaiveDateTime::parse_from_str(val, &dt.0) {
|
||||
Ok(d) => Value::date (
|
||||
DateTime::from_naive_utc_and_offset(
|
||||
d,
|
||||
*Local::now().offset(),
|
||||
),
|
||||
head,
|
||||
|
||||
let parse_as_string = |val: &str| {
|
||||
match dateformat {
|
||||
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
|
||||
Ok(d) => Value::date ( d, head ),
|
||||
Err(reason) => {
|
||||
match NaiveDateTime::parse_from_str(val, &dt.0) {
|
||||
Ok(d) => Value::date (
|
||||
DateTime::from_naive_utc_and_offset(
|
||||
d,
|
||||
*Local::now().offset(),
|
||||
),
|
||||
Err(_) => {
|
||||
Value::error (
|
||||
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) },
|
||||
head,
|
||||
)
|
||||
}
|
||||
head,
|
||||
),
|
||||
Err(_) => {
|
||||
Value::error (
|
||||
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) },
|
||||
head,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
// Tries to automatically parse the date
|
||||
// (i.e. without a format string)
|
||||
// and assumes the system's local timezone if none is specified
|
||||
None => match parse_date_from_string(val, span) {
|
||||
Ok(date) => Value::date (
|
||||
date,
|
||||
span,
|
||||
),
|
||||
Err(err) => err,
|
||||
},
|
||||
}
|
||||
// Tries to automatically parse the date
|
||||
// (i.e. without a format string)
|
||||
// and assumes the system's local timezone if none is specified
|
||||
None => match parse_date_from_string(val, span) {
|
||||
Ok(date) => Value::date (
|
||||
date,
|
||||
span,
|
||||
),
|
||||
Err(err) => err,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
match input {
|
||||
Value::String { val, .. } => parse_as_string(val),
|
||||
Value::Int { val, .. } => parse_as_string(&val.to_string()),
|
||||
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::error(
|
||||
|
@ -575,6 +580,24 @@ mod tests {
|
|||
assert_eq!(actual, expected)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn takes_int_with_formatstring() {
|
||||
let date_int = Value::test_int(1_614_434_140);
|
||||
let fmt_options = Some(DatetimeFormat("%s".to_string()));
|
||||
let args = Arguments {
|
||||
zone_options: None,
|
||||
format_options: fmt_options,
|
||||
cell_paths: None,
|
||||
};
|
||||
let actual = action(&date_int, &args, Span::test_data());
|
||||
let expected = Value::date(
|
||||
DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z").unwrap(),
|
||||
Span::test_data(),
|
||||
);
|
||||
|
||||
assert_eq!(actual, expected)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn takes_timestamp() {
|
||||
let date_str = Value::test_string("1614434140000000000");
|
||||
|
|
|
@ -397,6 +397,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||
RandomFloat,
|
||||
RandomInt,
|
||||
RandomUuid,
|
||||
RandomBinary
|
||||
};
|
||||
|
||||
// Generators
|
||||
|
|
64
crates/nu-command/src/random/binary.rs
Normal file
64
crates/nu-command/src/random/binary.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use nu_engine::command_prelude::*;
|
||||
|
||||
use rand::{thread_rng, RngCore};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"random binary"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("random binary")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Binary)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("length", SyntaxShape::Int, "Length of the output binary.")
|
||||
.category(Category::Random)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Generate random bytes."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["generate", "bytes"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let length = call.req(engine_state, stack, 0)?;
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let mut out = vec![0u8; length];
|
||||
rng.fill_bytes(&mut out);
|
||||
|
||||
Ok(Value::binary(out, call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Generate 16 random bytes",
|
||||
example: "random bytes 16",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
mod binary;
|
||||
mod bool;
|
||||
mod chars;
|
||||
mod dice;
|
||||
|
@ -6,6 +7,7 @@ mod int;
|
|||
mod random_;
|
||||
mod uuid;
|
||||
|
||||
pub use self::binary::SubCommand as RandomBinary;
|
||||
pub use self::bool::SubCommand as RandomBool;
|
||||
pub use self::chars::SubCommand as RandomChars;
|
||||
pub use self::dice::SubCommand as RandomDice;
|
||||
|
|
|
@ -3,25 +3,28 @@ use nu_protocol::{
|
|||
ast::{Argument, Call, Expr, Expression, RecordItem},
|
||||
debugger::WithoutDebug,
|
||||
engine::{Command, EngineState, Stack, UNKNOWN_SPAN_ID},
|
||||
record, Category, Config, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId,
|
||||
Spanned, SyntaxShape, Type, Value,
|
||||
record, Category, Config, Example, IntoPipelineData, PipelineData, PositionalArg, Signature,
|
||||
Span, SpanId, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
use std::{collections::HashMap, fmt::Write};
|
||||
use terminal_size::{Height, Width};
|
||||
|
||||
/// ANSI style reset
|
||||
const RESET: &str = "\x1b[0m";
|
||||
/// ANSI set default color (as set in the terminal)
|
||||
const DEFAULT_COLOR: &str = "\x1b[39m";
|
||||
|
||||
pub fn get_full_help(
|
||||
command: &dyn Command,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> String {
|
||||
let config = stack.get_config(engine_state);
|
||||
let doc_config = DocumentationConfig {
|
||||
no_subcommands: false,
|
||||
no_color: !config.use_ansi_coloring,
|
||||
brief: false,
|
||||
};
|
||||
|
||||
// Precautionary step to capture any command output generated during this operation. We
|
||||
// internally call several commands (`table`, `ansi`, `nu-highlight`) and get their
|
||||
// `PipelineData` using this `Stack`, any other output should not be redirected like the main
|
||||
// execution.
|
||||
let stack = &mut stack.start_capture();
|
||||
|
||||
let signature = command.signature().update_from_command(command);
|
||||
|
||||
get_documentation(
|
||||
|
@ -29,19 +32,11 @@ pub fn get_full_help(
|
|||
&command.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
&doc_config,
|
||||
command.is_keyword(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DocumentationConfig {
|
||||
no_subcommands: bool,
|
||||
no_color: bool,
|
||||
brief: bool,
|
||||
}
|
||||
|
||||
// Utility returns nu-highlighted string
|
||||
/// Syntax highlight code using the `nu-highlight` command if available
|
||||
fn nu_highlight_string(code_string: &str, engine_state: &EngineState, stack: &mut Stack) -> String {
|
||||
if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) {
|
||||
let decl = engine_state.get_decl(highlighter);
|
||||
|
@ -68,35 +63,15 @@ fn get_documentation(
|
|||
examples: &[Example],
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
config: &DocumentationConfig,
|
||||
is_parser_keyword: bool,
|
||||
) -> String {
|
||||
let nu_config = stack.get_config(engine_state);
|
||||
|
||||
// Create ansi colors
|
||||
//todo make these configurable -- pull from enginestate.config
|
||||
let help_section_name: String = get_ansi_color_for_component_or_default(
|
||||
engine_state,
|
||||
&nu_config,
|
||||
"shape_string",
|
||||
"\x1b[32m",
|
||||
); // default: green
|
||||
|
||||
let help_subcolor_one: String = get_ansi_color_for_component_or_default(
|
||||
engine_state,
|
||||
&nu_config,
|
||||
"shape_external",
|
||||
"\x1b[36m",
|
||||
); // default: cyan
|
||||
// was const bb: &str = "\x1b[1;34m"; // bold blue
|
||||
let help_subcolor_two: String = get_ansi_color_for_component_or_default(
|
||||
engine_state,
|
||||
&nu_config,
|
||||
"shape_block",
|
||||
"\x1b[94m",
|
||||
); // default: light blue (nobold, should be bolding the *names*)
|
||||
|
||||
const RESET: &str = "\x1b[0m"; // reset
|
||||
let mut help_style = HelpStyle::default();
|
||||
help_style.update_from_config(engine_state, &nu_config);
|
||||
let help_section_name = &help_style.section_name;
|
||||
let help_subcolor_one = &help_style.subcolor_one;
|
||||
|
||||
let cmd_name = &sig.name;
|
||||
let mut long_desc = String::new();
|
||||
|
@ -107,44 +82,46 @@ fn get_documentation(
|
|||
long_desc.push_str("\n\n");
|
||||
}
|
||||
|
||||
let extra_usage = if config.brief { "" } else { &sig.extra_usage };
|
||||
let extra_usage = &sig.extra_usage;
|
||||
if !extra_usage.is_empty() {
|
||||
long_desc.push_str(extra_usage);
|
||||
long_desc.push_str("\n\n");
|
||||
}
|
||||
|
||||
let mut subcommands = vec![];
|
||||
if !config.no_subcommands {
|
||||
let signatures = engine_state.get_signatures(true);
|
||||
for sig in signatures {
|
||||
if sig.name.starts_with(&format!("{cmd_name} "))
|
||||
// Don't display removed/deprecated commands in the Subcommands list
|
||||
&& !matches!(sig.category, Category::Removed)
|
||||
{
|
||||
subcommands.push(format!(
|
||||
" {help_subcolor_one}{}{RESET} - {}",
|
||||
sig.name, sig.usage
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !sig.search_terms.is_empty() {
|
||||
let text = format!(
|
||||
"{help_section_name}Search terms{RESET}: {help_subcolor_one}{}{}\n\n",
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
"{help_section_name}Search terms{RESET}: {help_subcolor_one}{}{RESET}\n\n",
|
||||
sig.search_terms.join(", "),
|
||||
RESET
|
||||
);
|
||||
let _ = write!(long_desc, "{text}");
|
||||
}
|
||||
|
||||
let text = format!(
|
||||
"{}Usage{}:\n > {}\n",
|
||||
help_section_name,
|
||||
RESET,
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
"{help_section_name}Usage{RESET}:\n > {}\n",
|
||||
sig.call_signature()
|
||||
);
|
||||
let _ = write!(long_desc, "{text}");
|
||||
|
||||
// TODO: improve the subcommand name resolution
|
||||
// issues:
|
||||
// - Aliases are included
|
||||
// - https://github.com/nushell/nushell/issues/11657
|
||||
// - Subcommands are included violating module scoping
|
||||
// - https://github.com/nushell/nushell/issues/11447
|
||||
// - https://github.com/nushell/nushell/issues/11625
|
||||
let mut subcommands = vec![];
|
||||
let signatures = engine_state.get_signatures(true);
|
||||
for sig in signatures {
|
||||
// Don't display removed/deprecated commands in the Subcommands list
|
||||
if sig.name.starts_with(&format!("{cmd_name} "))
|
||||
&& !matches!(sig.category, Category::Removed)
|
||||
{
|
||||
subcommands.push(format!(
|
||||
" {help_subcolor_one}{}{RESET} - {}",
|
||||
sig.name, sig.usage
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if !subcommands.is_empty() {
|
||||
let _ = write!(long_desc, "\n{help_section_name}Subcommands{RESET}:\n");
|
||||
|
@ -154,12 +131,9 @@ fn get_documentation(
|
|||
}
|
||||
|
||||
if !sig.named.is_empty() {
|
||||
long_desc.push_str(&get_flags_section(
|
||||
Some(engine_state),
|
||||
Some(&nu_config),
|
||||
sig,
|
||||
|v| nu_highlight_string(&v.to_parsable_string(", ", &nu_config), engine_state, stack),
|
||||
))
|
||||
long_desc.push_str(&get_flags_section(sig, &help_style, |v| {
|
||||
nu_highlight_string(&v.to_parsable_string(", ", &nu_config), engine_state, stack)
|
||||
}))
|
||||
}
|
||||
|
||||
if !sig.required_positional.is_empty()
|
||||
|
@ -168,70 +142,38 @@ fn get_documentation(
|
|||
{
|
||||
let _ = write!(long_desc, "\n{help_section_name}Parameters{RESET}:\n");
|
||||
for positional in &sig.required_positional {
|
||||
let text = match &positional.shape {
|
||||
SyntaxShape::Keyword(kw, shape) => {
|
||||
format!(
|
||||
" {help_subcolor_one}\"{}\" + {RESET}<{help_subcolor_two}{}{RESET}>: {}",
|
||||
String::from_utf8_lossy(kw),
|
||||
document_shape(*shape.clone()),
|
||||
positional.desc
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
format!(
|
||||
" {help_subcolor_one}{}{RESET} <{help_subcolor_two}{}{RESET}>: {}",
|
||||
positional.name,
|
||||
document_shape(positional.shape.clone()),
|
||||
positional.desc
|
||||
)
|
||||
}
|
||||
};
|
||||
let _ = writeln!(long_desc, "{text}");
|
||||
write_positional(
|
||||
&mut long_desc,
|
||||
positional,
|
||||
PositionalKind::Required,
|
||||
&help_style,
|
||||
&nu_config,
|
||||
engine_state,
|
||||
stack,
|
||||
);
|
||||
}
|
||||
for positional in &sig.optional_positional {
|
||||
let text = match &positional.shape {
|
||||
SyntaxShape::Keyword(kw, shape) => {
|
||||
format!(
|
||||
" {help_subcolor_one}\"{}\" + {RESET}<{help_subcolor_two}{}{RESET}>: {} (optional)",
|
||||
String::from_utf8_lossy(kw),
|
||||
document_shape(*shape.clone()),
|
||||
positional.desc
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
let opt_suffix = if let Some(value) = &positional.default_value {
|
||||
format!(
|
||||
" (optional, default: {})",
|
||||
nu_highlight_string(
|
||||
&value.to_parsable_string(", ", &nu_config),
|
||||
engine_state,
|
||||
stack
|
||||
)
|
||||
)
|
||||
} else {
|
||||
(" (optional)").to_string()
|
||||
};
|
||||
|
||||
format!(
|
||||
" {help_subcolor_one}{}{RESET} <{help_subcolor_two}{}{RESET}>: {}{}",
|
||||
positional.name,
|
||||
document_shape(positional.shape.clone()),
|
||||
positional.desc,
|
||||
opt_suffix,
|
||||
)
|
||||
}
|
||||
};
|
||||
let _ = writeln!(long_desc, "{text}");
|
||||
write_positional(
|
||||
&mut long_desc,
|
||||
positional,
|
||||
PositionalKind::Optional,
|
||||
&help_style,
|
||||
&nu_config,
|
||||
engine_state,
|
||||
stack,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(rest_positional) = &sig.rest_positional {
|
||||
let text = format!(
|
||||
" ...{help_subcolor_one}{}{RESET} <{help_subcolor_two}{}{RESET}>: {}",
|
||||
rest_positional.name,
|
||||
document_shape(rest_positional.shape.clone()),
|
||||
rest_positional.desc
|
||||
write_positional(
|
||||
&mut long_desc,
|
||||
rest_positional,
|
||||
PositionalKind::Rest,
|
||||
&help_style,
|
||||
&nu_config,
|
||||
engine_state,
|
||||
stack,
|
||||
);
|
||||
let _ = writeln!(long_desc, "{text}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,36 +242,12 @@ fn get_documentation(
|
|||
long_desc.push_str(" ");
|
||||
long_desc.push_str(example.description);
|
||||
|
||||
if config.no_color {
|
||||
if !nu_config.use_ansi_coloring {
|
||||
let _ = write!(long_desc, "\n > {}\n", example.example);
|
||||
} else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) {
|
||||
let decl = engine_state.get_decl(highlighter);
|
||||
let call = Call::new(Span::unknown());
|
||||
|
||||
match decl.run(
|
||||
engine_state,
|
||||
stack,
|
||||
&(&call).into(),
|
||||
Value::string(example.example, Span::unknown()).into_pipeline_data(),
|
||||
) {
|
||||
Ok(output) => {
|
||||
let result = output.into_value(Span::unknown());
|
||||
match result.and_then(Value::coerce_into_string) {
|
||||
Ok(s) => {
|
||||
let _ = write!(long_desc, "\n > {s}\n");
|
||||
}
|
||||
_ => {
|
||||
let _ = write!(long_desc, "\n > {}\n", example.example);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = write!(long_desc, "\n > {}\n", example.example);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let _ = write!(long_desc, "\n > {}\n", example.example);
|
||||
}
|
||||
let code_string = nu_highlight_string(example.example, engine_state, stack);
|
||||
let _ = write!(long_desc, "\n > {code_string}\n");
|
||||
};
|
||||
|
||||
if let Some(result) = &example.result {
|
||||
let mut table_call = Call::new(Span::unknown());
|
||||
|
@ -395,19 +313,19 @@ fn get_documentation(
|
|||
|
||||
long_desc.push('\n');
|
||||
|
||||
if config.no_color {
|
||||
if !nu_config.use_ansi_coloring {
|
||||
nu_utils::strip_ansi_string_likely(long_desc)
|
||||
} else {
|
||||
long_desc
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ansi_color_for_component_or_default(
|
||||
fn update_ansi_from_config(
|
||||
ansi_code: &mut String,
|
||||
engine_state: &EngineState,
|
||||
nu_config: &Config,
|
||||
theme_component: &str,
|
||||
default: &str,
|
||||
) -> String {
|
||||
) {
|
||||
if let Some(color) = &nu_config.color_config.get(theme_component) {
|
||||
let caller_stack = &mut Stack::new().capture();
|
||||
let span = Span::unknown();
|
||||
|
@ -430,14 +348,12 @@ fn get_ansi_color_for_component_or_default(
|
|||
PipelineData::Empty,
|
||||
) {
|
||||
if let Ok((str, ..)) = result.collect_string_strict(span) {
|
||||
return str;
|
||||
*ansi_code = str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default.to_string()
|
||||
}
|
||||
|
||||
fn get_argument_for_color_value(
|
||||
|
@ -491,151 +407,174 @@ fn get_argument_for_color_value(
|
|||
}
|
||||
}
|
||||
|
||||
// document shape helps showing more useful information
|
||||
pub fn document_shape(shape: SyntaxShape) -> SyntaxShape {
|
||||
/// Contains the settings for ANSI colors in help output
|
||||
///
|
||||
/// By default contains a fixed set of (4-bit) colors
|
||||
///
|
||||
/// Can reflect configuration using [`HelpStyle::update_from_config`]
|
||||
pub struct HelpStyle {
|
||||
section_name: String,
|
||||
subcolor_one: String,
|
||||
subcolor_two: String,
|
||||
}
|
||||
|
||||
impl Default for HelpStyle {
|
||||
fn default() -> Self {
|
||||
HelpStyle {
|
||||
// default: green
|
||||
section_name: "\x1b[32m".to_string(),
|
||||
// default: cyan
|
||||
subcolor_one: "\x1b[36m".to_string(),
|
||||
// default: light blue
|
||||
subcolor_two: "\x1b[94m".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HelpStyle {
|
||||
/// Pull colors from the [`Config`]
|
||||
///
|
||||
/// Uses some arbitrary `shape_*` settings, assuming they are well visible in the terminal theme.
|
||||
///
|
||||
/// Implementation detail: currently executes `ansi` command internally thus requiring the
|
||||
/// [`EngineState`] for execution.
|
||||
/// See <https://github.com/nushell/nushell/pull/10623> for details
|
||||
pub fn update_from_config(&mut self, engine_state: &EngineState, nu_config: &Config) {
|
||||
update_ansi_from_config(
|
||||
&mut self.section_name,
|
||||
engine_state,
|
||||
nu_config,
|
||||
"shape_string",
|
||||
);
|
||||
update_ansi_from_config(
|
||||
&mut self.subcolor_one,
|
||||
engine_state,
|
||||
nu_config,
|
||||
"shape_external",
|
||||
);
|
||||
update_ansi_from_config(
|
||||
&mut self.subcolor_two,
|
||||
engine_state,
|
||||
nu_config,
|
||||
"shape_block",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Make syntax shape presentable by stripping custom completer info
|
||||
fn document_shape(shape: &SyntaxShape) -> &SyntaxShape {
|
||||
match shape {
|
||||
SyntaxShape::CompleterWrapper(inner_shape, _) => *inner_shape,
|
||||
SyntaxShape::CompleterWrapper(inner_shape, _) => inner_shape,
|
||||
_ => shape,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum PositionalKind {
|
||||
Required,
|
||||
Optional,
|
||||
Rest,
|
||||
}
|
||||
|
||||
fn write_positional(
|
||||
long_desc: &mut String,
|
||||
positional: &PositionalArg,
|
||||
arg_kind: PositionalKind,
|
||||
help_style: &HelpStyle,
|
||||
nu_config: &Config,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
) {
|
||||
let help_subcolor_one = &help_style.subcolor_one;
|
||||
let help_subcolor_two = &help_style.subcolor_two;
|
||||
|
||||
// Indentation
|
||||
long_desc.push_str(" ");
|
||||
if arg_kind == PositionalKind::Rest {
|
||||
long_desc.push_str("...");
|
||||
}
|
||||
match &positional.shape {
|
||||
SyntaxShape::Keyword(kw, shape) => {
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
"{help_subcolor_one}\"{}\" + {RESET}<{help_subcolor_two}{}{RESET}>",
|
||||
String::from_utf8_lossy(kw),
|
||||
document_shape(shape),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
"{help_subcolor_one}{}{RESET} <{help_subcolor_two}{}{RESET}>",
|
||||
positional.name,
|
||||
document_shape(&positional.shape),
|
||||
);
|
||||
}
|
||||
};
|
||||
if !positional.desc.is_empty() || arg_kind == PositionalKind::Optional {
|
||||
let _ = write!(long_desc, ": {}", positional.desc);
|
||||
}
|
||||
if arg_kind == PositionalKind::Optional {
|
||||
if let Some(value) = &positional.default_value {
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
" (optional, default: {})",
|
||||
nu_highlight_string(
|
||||
&value.to_parsable_string(", ", nu_config),
|
||||
engine_state,
|
||||
stack
|
||||
)
|
||||
);
|
||||
} else {
|
||||
long_desc.push_str(" (optional)");
|
||||
};
|
||||
}
|
||||
long_desc.push('\n');
|
||||
}
|
||||
|
||||
pub fn get_flags_section<F>(
|
||||
engine_state_opt: Option<&EngineState>,
|
||||
nu_config_opt: Option<&Config>,
|
||||
signature: &Signature,
|
||||
help_style: &HelpStyle,
|
||||
mut value_formatter: F, // format default Value (because some calls cant access config or nu-highlight)
|
||||
) -> String
|
||||
where
|
||||
F: FnMut(&nu_protocol::Value) -> String,
|
||||
{
|
||||
//todo make these configurable -- pull from enginestate.config
|
||||
let help_section_name: String;
|
||||
let help_subcolor_one: String;
|
||||
let help_subcolor_two: String;
|
||||
|
||||
// Sometimes we want to get the flags without engine_state
|
||||
// For example, in nu-plugin. In that case, we fall back on default values
|
||||
if let Some(engine_state) = engine_state_opt {
|
||||
let nu_config = nu_config_opt.unwrap_or_else(|| engine_state.get_config());
|
||||
help_section_name = get_ansi_color_for_component_or_default(
|
||||
engine_state,
|
||||
nu_config,
|
||||
"shape_string",
|
||||
"\x1b[32m",
|
||||
); // default: green
|
||||
help_subcolor_one = get_ansi_color_for_component_or_default(
|
||||
engine_state,
|
||||
nu_config,
|
||||
"shape_external",
|
||||
"\x1b[36m",
|
||||
); // default: cyan
|
||||
// was const bb: &str = "\x1b[1;34m"; // bold blue
|
||||
help_subcolor_two = get_ansi_color_for_component_or_default(
|
||||
engine_state,
|
||||
nu_config,
|
||||
"shape_block",
|
||||
"\x1b[94m",
|
||||
);
|
||||
// default: light blue (nobold, should be bolding the *names*)
|
||||
} else {
|
||||
help_section_name = "\x1b[32m".to_string();
|
||||
help_subcolor_one = "\x1b[36m".to_string();
|
||||
help_subcolor_two = "\x1b[94m".to_string();
|
||||
}
|
||||
|
||||
const RESET: &str = "\x1b[0m"; // reset
|
||||
const D: &str = "\x1b[39m"; // default
|
||||
let help_section_name = &help_style.section_name;
|
||||
let help_subcolor_one = &help_style.subcolor_one;
|
||||
let help_subcolor_two = &help_style.subcolor_two;
|
||||
|
||||
let mut long_desc = String::new();
|
||||
let _ = write!(long_desc, "\n{help_section_name}Flags{RESET}:\n");
|
||||
for flag in &signature.named {
|
||||
let default_str = if let Some(value) = &flag.default_value {
|
||||
format!(
|
||||
" (default: {help_subcolor_two}{}{RESET})",
|
||||
&value_formatter(value)
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
let msg = if let Some(arg) = &flag.arg {
|
||||
if let Some(short) = flag.short {
|
||||
if flag.required {
|
||||
format!(
|
||||
" {help_subcolor_one}-{}{}{RESET} (required parameter) {:?} - {}{}\n",
|
||||
short,
|
||||
if !flag.long.is_empty() {
|
||||
format!("{D},{RESET} {help_subcolor_one}--{}", flag.long)
|
||||
} else {
|
||||
"".into()
|
||||
},
|
||||
arg,
|
||||
flag.desc,
|
||||
default_str,
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" {help_subcolor_one}-{}{}{RESET} <{help_subcolor_two}{:?}{RESET}> - {}{}\n",
|
||||
short,
|
||||
if !flag.long.is_empty() {
|
||||
format!("{D},{RESET} {help_subcolor_one}--{}", flag.long)
|
||||
} else {
|
||||
"".into()
|
||||
},
|
||||
arg,
|
||||
flag.desc,
|
||||
default_str,
|
||||
)
|
||||
}
|
||||
} else if flag.required {
|
||||
format!(
|
||||
" {help_subcolor_one}--{}{RESET} (required parameter) <{help_subcolor_two}{:?}{RESET}> - {}{}\n",
|
||||
flag.long, arg, flag.desc, default_str,
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" {help_subcolor_one}--{}{RESET} <{help_subcolor_two}{:?}{RESET}> - {}{}\n",
|
||||
flag.long, arg, flag.desc, default_str,
|
||||
)
|
||||
// Indentation
|
||||
long_desc.push_str(" ");
|
||||
// Short flag shown before long flag
|
||||
if let Some(short) = flag.short {
|
||||
let _ = write!(long_desc, "{help_subcolor_one}-{}{RESET}", short);
|
||||
if !flag.long.is_empty() {
|
||||
let _ = write!(long_desc, "{DEFAULT_COLOR},{RESET} ");
|
||||
}
|
||||
} else if let Some(short) = flag.short {
|
||||
if flag.required {
|
||||
format!(
|
||||
" {help_subcolor_one}-{}{}{RESET} (required parameter) - {}{}\n",
|
||||
short,
|
||||
if !flag.long.is_empty() {
|
||||
format!("{D},{RESET} {help_subcolor_one}--{}", flag.long)
|
||||
} else {
|
||||
"".into()
|
||||
},
|
||||
flag.desc,
|
||||
default_str,
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" {help_subcolor_one}-{}{}{RESET} - {}{}\n",
|
||||
short,
|
||||
if !flag.long.is_empty() {
|
||||
format!("{D},{RESET} {help_subcolor_one}--{}", flag.long)
|
||||
} else {
|
||||
"".into()
|
||||
},
|
||||
flag.desc,
|
||||
default_str
|
||||
)
|
||||
}
|
||||
} else if flag.required {
|
||||
format!(
|
||||
" {help_subcolor_one}--{}{RESET} (required parameter) - {}{}\n",
|
||||
flag.long, flag.desc, default_str,
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
" {help_subcolor_one}--{}{RESET} - {}\n",
|
||||
flag.long, flag.desc
|
||||
)
|
||||
};
|
||||
long_desc.push_str(&msg);
|
||||
}
|
||||
if !flag.long.is_empty() {
|
||||
let _ = write!(long_desc, "{help_subcolor_one}--{}{RESET}", flag.long);
|
||||
}
|
||||
if flag.required {
|
||||
long_desc.push_str(" (required parameter)")
|
||||
}
|
||||
// Type/Syntax shape info
|
||||
if let Some(arg) = &flag.arg {
|
||||
let _ = write!(
|
||||
long_desc,
|
||||
" <{help_subcolor_two}{}{RESET}>",
|
||||
document_shape(arg)
|
||||
);
|
||||
}
|
||||
let _ = write!(long_desc, " - {}", flag.desc);
|
||||
if let Some(value) = &flag.default_value {
|
||||
let _ = write!(long_desc, " (default: {})", &value_formatter(value));
|
||||
}
|
||||
long_desc.push('\n');
|
||||
}
|
||||
long_desc
|
||||
}
|
||||
|
|
|
@ -1195,17 +1195,17 @@ mod tests {
|
|||
assert_json_include!(
|
||||
actual: result,
|
||||
expected: serde_json::json!([
|
||||
{
|
||||
"label": "def",
|
||||
"textEdit": {
|
||||
"newText": "def",
|
||||
"range": {
|
||||
"start": { "character": 0, "line": 0 },
|
||||
"end": { "character": 2, "line": 0 }
|
||||
}
|
||||
},
|
||||
"kind": 14
|
||||
}
|
||||
{
|
||||
"label": "overlay",
|
||||
"textEdit": {
|
||||
"newText": "overlay",
|
||||
"range": {
|
||||
"start": { "character": 0, "line": 0 },
|
||||
"end": { "character": 2, "line": 0 }
|
||||
}
|
||||
},
|
||||
"kind": 14
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::{
|
|||
thread,
|
||||
};
|
||||
|
||||
use nu_engine::documentation::get_flags_section;
|
||||
use nu_engine::documentation::{get_flags_section, HelpStyle};
|
||||
use nu_plugin_core::{
|
||||
ClientCommunicationIo, CommunicationMode, InterfaceManager, PluginEncoder, PluginRead,
|
||||
PluginWrite,
|
||||
|
@ -657,6 +657,7 @@ fn print_help(plugin: &impl Plugin, encoder: impl PluginEncoder) {
|
|||
println!("Encoder: {}", encoder.name());
|
||||
|
||||
let mut help = String::new();
|
||||
let help_style = HelpStyle::default();
|
||||
|
||||
plugin.commands().into_iter().for_each(|command| {
|
||||
let signature = command.signature();
|
||||
|
@ -670,7 +671,7 @@ fn print_help(plugin: &impl Plugin, encoder: impl PluginEncoder) {
|
|||
}
|
||||
})
|
||||
.and_then(|_| {
|
||||
let flags = get_flags_section(None, None, &signature, |v| format!("{:#?}", v));
|
||||
let flags = get_flags_section(&signature, &help_style, |v| format!("{:#?}", v));
|
||||
write!(help, "{flags}")
|
||||
})
|
||||
.and_then(|_| writeln!(help, "\nParameters:"))
|
||||
|
|
|
@ -35,6 +35,35 @@ impl ReconstructVal for CompletionAlgorithm {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub enum CompletionSort {
|
||||
#[default]
|
||||
Smart,
|
||||
Alphabetical,
|
||||
}
|
||||
|
||||
impl FromStr for CompletionSort {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"smart" => Ok(Self::Smart),
|
||||
"alphabetical" => Ok(Self::Alphabetical),
|
||||
_ => Err("expected either 'smart' or 'alphabetical'"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ReconstructVal for CompletionSort {
|
||||
fn reconstruct_value(&self, span: Span) -> Value {
|
||||
let str = match self {
|
||||
Self::Smart => "smart",
|
||||
Self::Alphabetical => "alphabetical",
|
||||
};
|
||||
Value::string(str, span)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn reconstruct_external_completer(config: &Config, span: Span) -> Value {
|
||||
if let Some(closure) = config.external_completer.as_ref() {
|
||||
Value::closure(closure.clone(), span)
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::{record, ShellError, Span, Value};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use self::completer::CompletionAlgorithm;
|
||||
pub use self::completer::{CompletionAlgorithm, CompletionSort};
|
||||
pub use self::helper::extract_value;
|
||||
pub use self::hooks::Hooks;
|
||||
pub use self::output::ErrorStyle;
|
||||
|
@ -69,6 +69,7 @@ pub struct Config {
|
|||
pub quick_completions: bool,
|
||||
pub partial_completions: bool,
|
||||
pub completion_algorithm: CompletionAlgorithm,
|
||||
pub completion_sort: CompletionSort,
|
||||
pub edit_mode: EditBindings,
|
||||
pub history: HistoryConfig,
|
||||
pub keybindings: Vec<ParsedKeybinding>,
|
||||
|
@ -141,6 +142,7 @@ impl Default for Config {
|
|||
quick_completions: true,
|
||||
partial_completions: true,
|
||||
completion_algorithm: CompletionAlgorithm::default(),
|
||||
completion_sort: CompletionSort::default(),
|
||||
enable_external_completion: true,
|
||||
max_external_completion_results: 100,
|
||||
recursion_limit: 50,
|
||||
|
@ -341,6 +343,13 @@ impl Value {
|
|||
"case_sensitive" => {
|
||||
process_bool_config(value, &mut errors, &mut config.case_sensitive_completions);
|
||||
}
|
||||
"sort" => {
|
||||
process_string_enum(
|
||||
&mut config.completion_sort,
|
||||
&[key, key2],
|
||||
value,
|
||||
&mut errors);
|
||||
}
|
||||
"external" => {
|
||||
if let Value::Record { val, .. } = value {
|
||||
val.to_mut().retain_mut(|key3, value|
|
||||
|
@ -401,6 +410,7 @@ impl Value {
|
|||
"partial" => Value::bool(config.partial_completions, span),
|
||||
"algorithm" => config.completion_algorithm.reconstruct_value(span),
|
||||
"case_sensitive" => Value::bool(config.case_sensitive_completions, span),
|
||||
"sort" => config.completion_sort.reconstruct_value(span),
|
||||
"external" => reconstruct_external(&config, span),
|
||||
"use_ls_colors" => Value::bool(config.use_ls_colors_completions, span),
|
||||
},
|
||||
|
|
|
@ -206,6 +206,7 @@ $env.config = {
|
|||
quick: true # set this to false to prevent auto-selecting completions when only one remains
|
||||
partial: true # set this to false to prevent partial filling of the prompt
|
||||
algorithm: "prefix" # prefix or fuzzy
|
||||
sort: "smart" # "smart" (alphabetical for prefix matching, fuzzy score for fuzzy matching) or "alphabetical"
|
||||
external: {
|
||||
enable: true # set to false to prevent nushell looking into $env.PATH to find more suggestions, `false` recommended for WSL users as this look up may be very slow
|
||||
max_results: 100 # setting it lower can improve completion performance at the cost of omitting some options
|
||||
|
|
|
@ -16,6 +16,8 @@ indexmap = { workspace = true }
|
|||
eml-parser = "0.1"
|
||||
ical = "0.11"
|
||||
rust-ini = "0.21.0"
|
||||
plist = "1.7"
|
||||
chrono = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.2" }
|
||||
nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.2" }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::FromCmds;
|
||||
use crate::FormatCmdsPlugin;
|
||||
use eml_parser::eml::*;
|
||||
use eml_parser::EmlParser;
|
||||
use indexmap::IndexMap;
|
||||
|
@ -12,7 +12,7 @@ const DEFAULT_BODY_PREVIEW: usize = 50;
|
|||
pub struct FromEml;
|
||||
|
||||
impl SimplePluginCommand for FromEml {
|
||||
type Plugin = FromCmds;
|
||||
type Plugin = FormatCmdsPlugin;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"from eml"
|
||||
|
@ -40,7 +40,7 @@ impl SimplePluginCommand for FromEml {
|
|||
|
||||
fn run(
|
||||
&self,
|
||||
_plugin: &FromCmds,
|
||||
_plugin: &FormatCmdsPlugin,
|
||||
_engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
input: &Value,
|
||||
|
@ -176,5 +176,5 @@ fn from_eml(input: &Value, body_preview: usize, head: Span) -> Result<Value, Lab
|
|||
fn test_examples() -> Result<(), nu_protocol::ShellError> {
|
||||
use nu_plugin_test_support::PluginTest;
|
||||
|
||||
PluginTest::new("formats", crate::FromCmds.into())?.test_command_examples(&FromEml)
|
||||
PluginTest::new("formats", crate::FormatCmdsPlugin.into())?.test_command_examples(&FromEml)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::FromCmds;
|
||||
use crate::FormatCmdsPlugin;
|
||||
|
||||
use ical::{parser::ical::component::*, property::Property};
|
||||
use indexmap::IndexMap;
|
||||
|
@ -11,7 +11,7 @@ use std::io::BufReader;
|
|||
pub struct FromIcs;
|
||||
|
||||
impl SimplePluginCommand for FromIcs {
|
||||
type Plugin = FromCmds;
|
||||
type Plugin = FormatCmdsPlugin;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"from ics"
|
||||
|
@ -33,7 +33,7 @@ impl SimplePluginCommand for FromIcs {
|
|||
|
||||
fn run(
|
||||
&self,
|
||||
_plugin: &FromCmds,
|
||||
_plugin: &FormatCmdsPlugin,
|
||||
_engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
input: &Value,
|
||||
|
@ -274,5 +274,5 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, span: Span) -> Value {
|
|||
fn test_examples() -> Result<(), nu_protocol::ShellError> {
|
||||
use nu_plugin_test_support::PluginTest;
|
||||
|
||||
PluginTest::new("formats", crate::FromCmds.into())?.test_command_examples(&FromIcs)
|
||||
PluginTest::new("formats", crate::FormatCmdsPlugin.into())?.test_command_examples(&FromIcs)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::FromCmds;
|
||||
use crate::FormatCmdsPlugin;
|
||||
|
||||
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
|
||||
use nu_protocol::{
|
||||
|
@ -8,7 +8,7 @@ use nu_protocol::{
|
|||
pub struct FromIni;
|
||||
|
||||
impl SimplePluginCommand for FromIni {
|
||||
type Plugin = FromCmds;
|
||||
type Plugin = FormatCmdsPlugin;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"from ini"
|
||||
|
@ -30,7 +30,7 @@ impl SimplePluginCommand for FromIni {
|
|||
|
||||
fn run(
|
||||
&self,
|
||||
_plugin: &FromCmds,
|
||||
_plugin: &FormatCmdsPlugin,
|
||||
_engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
input: &Value,
|
||||
|
@ -101,5 +101,5 @@ b=2' | from ini",
|
|||
fn test_examples() -> Result<(), nu_protocol::ShellError> {
|
||||
use nu_plugin_test_support::PluginTest;
|
||||
|
||||
PluginTest::new("formats", crate::FromCmds.into())?.test_command_examples(&FromIni)
|
||||
PluginTest::new("formats", crate::FormatCmdsPlugin.into())?.test_command_examples(&FromIni)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub mod eml;
|
||||
pub mod ics;
|
||||
pub mod ini;
|
||||
pub mod vcf;
|
||||
pub(crate) mod eml;
|
||||
pub(crate) mod ics;
|
||||
pub(crate) mod ini;
|
||||
pub(crate) mod plist;
|
||||
pub(crate) mod vcf;
|
||||
|
|
240
crates/nu_plugin_formats/src/from/plist.rs
Normal file
240
crates/nu_plugin_formats/src/from/plist.rs
Normal file
|
@ -0,0 +1,240 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use chrono::{DateTime, FixedOffset, Offset, Utc};
|
||||
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
|
||||
use nu_protocol::{
|
||||
record, Category, Example, LabeledError, Record, Signature, Span, Value as NuValue,
|
||||
};
|
||||
use plist::{Date as PlistDate, Dictionary, Value as PlistValue};
|
||||
|
||||
use crate::FormatCmdsPlugin;
|
||||
|
||||
pub struct FromPlist;
|
||||
|
||||
impl SimplePluginCommand for FromPlist {
|
||||
type Plugin = FormatCmdsPlugin;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"from plist"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert plist to Nushell values"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
example: r#"'<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>a</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
</plist>' | from plist"#,
|
||||
description: "Convert a table into a plist file",
|
||||
result: Some(NuValue::test_record(record!( "a" => NuValue::test_int(3)))),
|
||||
}]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(PluginCommand::name(self)).category(Category::Formats)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_plugin: &FormatCmdsPlugin,
|
||||
_engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
input: &NuValue,
|
||||
) -> Result<NuValue, LabeledError> {
|
||||
match input {
|
||||
NuValue::String { val, .. } => {
|
||||
let plist = plist::from_bytes(val.as_bytes())
|
||||
.map_err(|e| build_label_error(format!("{}", e), input.span()))?;
|
||||
let converted = convert_plist_value(&plist, call.head)?;
|
||||
Ok(converted)
|
||||
}
|
||||
NuValue::Binary { val, .. } => {
|
||||
let plist = plist::from_bytes(val)
|
||||
.map_err(|e| build_label_error(format!("{}", e), input.span()))?;
|
||||
let converted = convert_plist_value(&plist, call.head)?;
|
||||
Ok(converted)
|
||||
}
|
||||
_ => Err(build_label_error(
|
||||
format!("Invalid input, must be string not: {:?}", input),
|
||||
call.head,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_label_error(msg: impl Into<String>, span: Span) -> LabeledError {
|
||||
LabeledError::new("Could not load plist").with_label(msg, span)
|
||||
}
|
||||
|
||||
fn convert_plist_value(plist_val: &PlistValue, span: Span) -> Result<NuValue, LabeledError> {
|
||||
match plist_val {
|
||||
PlistValue::String(s) => Ok(NuValue::string(s.to_owned(), span)),
|
||||
PlistValue::Boolean(b) => Ok(NuValue::bool(*b, span)),
|
||||
PlistValue::Real(r) => Ok(NuValue::float(*r, span)),
|
||||
PlistValue::Date(d) => Ok(NuValue::date(convert_date(d), span)),
|
||||
PlistValue::Integer(i) => {
|
||||
let signed = i
|
||||
.as_signed()
|
||||
.ok_or_else(|| build_label_error(format!("Cannot convert {i} to i64"), span))?;
|
||||
Ok(NuValue::int(signed, span))
|
||||
}
|
||||
PlistValue::Uid(uid) => Ok(NuValue::float(uid.get() as f64, span)),
|
||||
PlistValue::Data(data) => Ok(NuValue::binary(data.to_owned(), span)),
|
||||
PlistValue::Array(arr) => Ok(NuValue::list(convert_array(arr, span)?, span)),
|
||||
PlistValue::Dictionary(dict) => Ok(convert_dict(dict, span)?),
|
||||
_ => Ok(NuValue::nothing(span)),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_dict(dict: &Dictionary, span: Span) -> Result<NuValue, LabeledError> {
|
||||
let cols: Vec<String> = dict.keys().cloned().collect();
|
||||
let vals: Result<Vec<NuValue>, LabeledError> = dict
|
||||
.values()
|
||||
.map(|v| convert_plist_value(v, span))
|
||||
.collect();
|
||||
Ok(NuValue::record(
|
||||
Record::from_raw_cols_vals(cols, vals?, span, span)?,
|
||||
span,
|
||||
))
|
||||
}
|
||||
|
||||
fn convert_array(plist_array: &[PlistValue], span: Span) -> Result<Vec<NuValue>, LabeledError> {
|
||||
plist_array
|
||||
.iter()
|
||||
.map(|v| convert_plist_value(v, span))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn convert_date(plist_date: &PlistDate) -> DateTime<FixedOffset> {
|
||||
// In the docs the plist date object is listed as a utc timestamp, so this
|
||||
// conversion should be fine
|
||||
let plist_sys_time: SystemTime = plist_date.to_owned().into();
|
||||
let utc_date: DateTime<Utc> = plist_sys_time.into();
|
||||
let utc_offset = utc_date.offset().fix();
|
||||
utc_date.with_timezone(&utc_offset)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use chrono::Datelike;
|
||||
use plist::Uid;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use nu_plugin_test_support::PluginTest;
|
||||
use nu_protocol::ShellError;
|
||||
|
||||
#[test]
|
||||
fn test_convert_string() {
|
||||
let plist_val = PlistValue::String("hello".to_owned());
|
||||
let result = convert_plist_value(&plist_val, Span::test_data());
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(NuValue::string("hello".to_owned(), Span::test_data()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_boolean() {
|
||||
let plist_val = PlistValue::Boolean(true);
|
||||
let result = convert_plist_value(&plist_val, Span::test_data());
|
||||
assert_eq!(result, Ok(NuValue::bool(true, Span::test_data())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_real() {
|
||||
let plist_val = PlistValue::Real(3.14);
|
||||
let result = convert_plist_value(&plist_val, Span::test_data());
|
||||
assert_eq!(result, Ok(NuValue::float(3.14, Span::test_data())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_integer() {
|
||||
let plist_val = PlistValue::Integer(42.into());
|
||||
let result = convert_plist_value(&plist_val, Span::test_data());
|
||||
assert_eq!(result, Ok(NuValue::int(42, Span::test_data())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_uid() {
|
||||
let v = 12345678_u64;
|
||||
let uid = Uid::new(v);
|
||||
let plist_val = PlistValue::Uid(uid);
|
||||
let result = convert_plist_value(&plist_val, Span::test_data());
|
||||
assert_eq!(result, Ok(NuValue::float(v as f64, Span::test_data())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_data() {
|
||||
let data = vec![0x41, 0x42, 0x43];
|
||||
let plist_val = PlistValue::Data(data.clone());
|
||||
let result = convert_plist_value(&plist_val, Span::test_data());
|
||||
assert_eq!(result, Ok(NuValue::binary(data, Span::test_data())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_date() {
|
||||
let epoch = SystemTime::UNIX_EPOCH;
|
||||
let plist_date = epoch.into();
|
||||
|
||||
let datetime = convert_date(&plist_date);
|
||||
assert_eq!(1970, datetime.year());
|
||||
assert_eq!(1, datetime.month());
|
||||
assert_eq!(1, datetime.day());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_dict() {
|
||||
let mut dict = Dictionary::new();
|
||||
dict.insert("a".to_string(), PlistValue::String("c".to_string()));
|
||||
dict.insert("b".to_string(), PlistValue::String("d".to_string()));
|
||||
let nu_dict = convert_dict(&dict, Span::test_data()).unwrap();
|
||||
assert_eq!(
|
||||
nu_dict,
|
||||
NuValue::record(
|
||||
Record::from_raw_cols_vals(
|
||||
vec!["a".to_string(), "b".to_string()],
|
||||
vec![
|
||||
NuValue::string("c".to_string(), Span::test_data()),
|
||||
NuValue::string("d".to_string(), Span::test_data())
|
||||
],
|
||||
Span::test_data(),
|
||||
Span::test_data(),
|
||||
)
|
||||
.expect("failed to create record"),
|
||||
Span::test_data(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_array() {
|
||||
let mut arr = Vec::new();
|
||||
arr.push(PlistValue::String("a".to_string()));
|
||||
arr.push(PlistValue::String("b".to_string()));
|
||||
let nu_arr = convert_array(&arr, Span::test_data()).unwrap();
|
||||
assert_eq!(
|
||||
nu_arr,
|
||||
vec![
|
||||
NuValue::string("a".to_string(), Span::test_data()),
|
||||
NuValue::string("b".to_string(), Span::test_data())
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples() -> Result<(), ShellError> {
|
||||
let plugin = FormatCmdsPlugin {};
|
||||
let cmd = FromPlist {};
|
||||
|
||||
let mut plugin_test = PluginTest::new("polars", plugin.into())?;
|
||||
plugin_test.test_command_examples(&cmd)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::FromCmds;
|
||||
use crate::FormatCmdsPlugin;
|
||||
|
||||
use ical::{parser::vcard::component::*, property::Property};
|
||||
use indexmap::IndexMap;
|
||||
|
@ -10,7 +10,7 @@ use nu_protocol::{
|
|||
pub struct FromVcf;
|
||||
|
||||
impl SimplePluginCommand for FromVcf {
|
||||
type Plugin = FromCmds;
|
||||
type Plugin = FormatCmdsPlugin;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"from vcf"
|
||||
|
@ -32,7 +32,7 @@ impl SimplePluginCommand for FromVcf {
|
|||
|
||||
fn run(
|
||||
&self,
|
||||
_plugin: &FromCmds,
|
||||
_plugin: &FormatCmdsPlugin,
|
||||
_engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
input: &Value,
|
||||
|
@ -164,5 +164,5 @@ fn params_to_value(params: Vec<(String, Vec<String>)>, span: Span) -> Value {
|
|||
fn test_examples() -> Result<(), nu_protocol::ShellError> {
|
||||
use nu_plugin_test_support::PluginTest;
|
||||
|
||||
PluginTest::new("formats", crate::FromCmds.into())?.test_command_examples(&FromVcf)
|
||||
PluginTest::new("formats", crate::FormatCmdsPlugin.into())?.test_command_examples(&FromVcf)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
mod from;
|
||||
mod to;
|
||||
|
||||
use nu_plugin::{Plugin, PluginCommand};
|
||||
|
||||
pub use from::eml::FromEml;
|
||||
pub use from::ics::FromIcs;
|
||||
pub use from::ini::FromIni;
|
||||
pub use from::vcf::FromVcf;
|
||||
use from::eml::FromEml;
|
||||
use from::ics::FromIcs;
|
||||
use from::ini::FromIni;
|
||||
use from::plist::FromPlist;
|
||||
use from::vcf::FromVcf;
|
||||
use to::plist::IntoPlist;
|
||||
|
||||
pub struct FromCmds;
|
||||
pub struct FormatCmdsPlugin;
|
||||
|
||||
impl Plugin for FromCmds {
|
||||
impl Plugin for FormatCmdsPlugin {
|
||||
fn version(&self) -> String {
|
||||
env!("CARGO_PKG_VERSION").into()
|
||||
}
|
||||
|
@ -20,6 +23,8 @@ impl Plugin for FromCmds {
|
|||
Box::new(FromIcs),
|
||||
Box::new(FromIni),
|
||||
Box::new(FromVcf),
|
||||
Box::new(FromPlist),
|
||||
Box::new(IntoPlist),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use nu_plugin::{serve_plugin, MsgPackSerializer};
|
||||
use nu_plugin_formats::FromCmds;
|
||||
use nu_plugin_formats::FormatCmdsPlugin;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&FromCmds, MsgPackSerializer {})
|
||||
serve_plugin(&FormatCmdsPlugin, MsgPackSerializer {})
|
||||
}
|
||||
|
|
1
crates/nu_plugin_formats/src/to/mod.rs
Normal file
1
crates/nu_plugin_formats/src/to/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub(crate) mod plist;
|
113
crates/nu_plugin_formats/src/to/plist.rs
Normal file
113
crates/nu_plugin_formats/src/to/plist.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
|
||||
use nu_protocol::{Category, Example, LabeledError, Record, Signature, Span, Value as NuValue};
|
||||
use plist::{Integer, Value as PlistValue};
|
||||
|
||||
use crate::FormatCmdsPlugin;
|
||||
|
||||
pub(crate) struct IntoPlist;
|
||||
|
||||
impl SimplePluginCommand for IntoPlist {
|
||||
type Plugin = FormatCmdsPlugin;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"to plist"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert Nu values into plist"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
example: "{ a: 3 } | to plist",
|
||||
description: "Convert a table into a plist file",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(PluginCommand::name(self))
|
||||
.switch("binary", "Output plist in binary format", Some('b'))
|
||||
.category(Category::Formats)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_plugin: &FormatCmdsPlugin,
|
||||
_engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
input: &NuValue,
|
||||
) -> Result<NuValue, LabeledError> {
|
||||
let plist_val = convert_nu_value(input)?;
|
||||
let mut out = Vec::new();
|
||||
if call.has_flag("binary")? {
|
||||
plist::to_writer_binary(&mut out, &plist_val)
|
||||
.map_err(|e| build_label_error(format!("{}", e), input.span()))?;
|
||||
Ok(NuValue::binary(out, input.span()))
|
||||
} else {
|
||||
plist::to_writer_xml(&mut out, &plist_val)
|
||||
.map_err(|e| build_label_error(format!("{}", e), input.span()))?;
|
||||
Ok(NuValue::string(
|
||||
String::from_utf8(out)
|
||||
.map_err(|e| build_label_error(format!("{}", e), input.span()))?,
|
||||
input.span(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_label_error(msg: String, span: Span) -> LabeledError {
|
||||
LabeledError::new("Cannot convert plist").with_label(msg, span)
|
||||
}
|
||||
|
||||
fn convert_nu_value(nu_val: &NuValue) -> Result<PlistValue, LabeledError> {
|
||||
let span = Span::test_data();
|
||||
match nu_val {
|
||||
NuValue::String { val, .. } => Ok(PlistValue::String(val.to_owned())),
|
||||
NuValue::Bool { val, .. } => Ok(PlistValue::Boolean(*val)),
|
||||
NuValue::Float { val, .. } => Ok(PlistValue::Real(*val)),
|
||||
NuValue::Int { val, .. } => Ok(PlistValue::Integer(Into::<Integer>::into(*val))),
|
||||
NuValue::Binary { val, .. } => Ok(PlistValue::Data(val.to_owned())),
|
||||
NuValue::Record { val, .. } => convert_nu_dict(val),
|
||||
NuValue::List { vals, .. } => Ok(PlistValue::Array(
|
||||
vals.iter()
|
||||
.map(convert_nu_value)
|
||||
.collect::<Result<_, _>>()?,
|
||||
)),
|
||||
NuValue::Date { val, .. } => Ok(PlistValue::Date(SystemTime::from(val.to_owned()).into())),
|
||||
NuValue::Filesize { val, .. } => Ok(PlistValue::Integer(Into::<Integer>::into(*val))),
|
||||
_ => Err(build_label_error(
|
||||
format!("{:?} is not convertible", nu_val),
|
||||
span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_nu_dict(record: &Record) -> Result<PlistValue, LabeledError> {
|
||||
Ok(PlistValue::Dictionary(
|
||||
record
|
||||
.iter()
|
||||
.map(|(k, v)| convert_nu_value(v).map(|v| (k.to_owned(), v)))
|
||||
.collect::<Result<_, _>>()?,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use nu_plugin_test_support::PluginTest;
|
||||
use nu_protocol::ShellError;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() -> Result<(), ShellError> {
|
||||
let plugin = FormatCmdsPlugin {};
|
||||
let cmd = IntoPlist {};
|
||||
|
||||
let mut plugin_test = PluginTest::new("polars", plugin.into())?;
|
||||
plugin_test.test_command_examples(&cmd)
|
||||
}
|
||||
}
|
|
@ -281,6 +281,10 @@ fn select_cells(
|
|||
let scraped = element.select(selector).map(cell_content);
|
||||
let mut dehtmlized: Vec<String> = Vec::new();
|
||||
for item in scraped {
|
||||
if item.is_empty() {
|
||||
dehtmlized.push(item);
|
||||
continue;
|
||||
}
|
||||
let frag = Html::parse_fragment(&item);
|
||||
for node in frag.tree {
|
||||
if let scraper::node::Node::Text(text) = node {
|
||||
|
@ -411,6 +415,7 @@ mod tests {
|
|||
<tr><td>John</td><td>20</td></tr>
|
||||
<tr><td>May</td><td>30</td><td>foo</td></tr>
|
||||
<tr></tr>
|
||||
<tr><td></td><td></td><td></td></tr>
|
||||
<tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
|
||||
</table>
|
||||
"#;
|
||||
|
@ -425,6 +430,7 @@ mod tests {
|
|||
<tr><td>John</td><td>20</td></tr>
|
||||
<tr><td>May</td><td>30</td><td>foo</td></tr>
|
||||
<tr></tr>
|
||||
<tr><td></td><td></td><td></td></tr>
|
||||
<tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
|
||||
</table>
|
||||
<table>
|
||||
|
@ -432,6 +438,7 @@ mod tests {
|
|||
<tr><td>Carpenter</td><td>Single</td></tr>
|
||||
<tr><td>Mechanic</td><td>Married</td><td>bar</td></tr>
|
||||
<tr></tr>
|
||||
<tr><td></td><td></td><td></td></tr>
|
||||
<tr><td>e</td><td>f</td><td>g</td><td>h</td></tr>
|
||||
</table>
|
||||
</body>
|
||||
|
@ -808,7 +815,7 @@ mod tests {
|
|||
assert_eq!(2, WebTable::find_first(TABLE_TD_TD).unwrap().iter().count());
|
||||
assert_eq!(1, WebTable::find_first(TABLE_TH_TH).unwrap().iter().count());
|
||||
assert_eq!(
|
||||
4,
|
||||
5,
|
||||
WebTable::find_first(TABLE_COMPLEX).unwrap().iter().count()
|
||||
);
|
||||
}
|
||||
|
@ -823,7 +830,7 @@ mod tests {
|
|||
|
||||
let table = WebTable::find_first(TABLE_COMPLEX).unwrap();
|
||||
assert_eq!(
|
||||
vec![false, false, true, false],
|
||||
vec![false, false, true, false, false],
|
||||
table.iter().map(|r| r.is_empty()).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
@ -835,7 +842,7 @@ mod tests {
|
|||
|
||||
let table = WebTable::find_first(TABLE_COMPLEX).unwrap();
|
||||
assert_eq!(
|
||||
vec![2, 3, 0, 4],
|
||||
vec![2, 3, 0, 3, 4],
|
||||
table.iter().map(|r| r.len()).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
@ -854,11 +861,11 @@ mod tests {
|
|||
let table_1 = tables_iter.next().unwrap();
|
||||
let table_2 = tables_iter.next().unwrap();
|
||||
assert_eq!(
|
||||
vec![2, 3, 0, 4],
|
||||
vec![2, 3, 0, 3, 4],
|
||||
table_1.iter().map(|r| r.len()).collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(
|
||||
vec![2, 3, 0, 4],
|
||||
vec![2, 3, 0, 3, 4],
|
||||
table_2.iter().map(|r| r.len()).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
@ -911,6 +918,11 @@ mod tests {
|
|||
assert_eq!(None, row.get("Age"));
|
||||
assert_eq!(None, row.get("Extra"));
|
||||
|
||||
let row = iter.next().unwrap();
|
||||
assert_eq!(Some(""), row.get("Name"));
|
||||
assert_eq!(Some(""), row.get("Age"));
|
||||
assert_eq!(Some(""), row.get("Extra"));
|
||||
|
||||
let row = iter.next().unwrap();
|
||||
assert_eq!(Some("a"), row.get("Name"));
|
||||
assert_eq!(Some("b"), row.get("Age"));
|
||||
|
@ -955,6 +967,15 @@ mod tests {
|
|||
assert_eq!(None, row_table_2.get("Age"));
|
||||
assert_eq!(None, row_table_2.get("Extra"));
|
||||
|
||||
let row_table_1 = iter_1.next().unwrap();
|
||||
let row_table_2 = iter_2.next().unwrap();
|
||||
assert_eq!(Some(""), row_table_1.get("Name"));
|
||||
assert_eq!(Some(""), row_table_1.get("Age"));
|
||||
assert_eq!(Some(""), row_table_1.get("Extra"));
|
||||
assert_eq!(Some(""), row_table_2.get("Profession"));
|
||||
assert_eq!(Some(""), row_table_2.get("Civil State"));
|
||||
assert_eq!(Some(""), row_table_2.get("Extra"));
|
||||
|
||||
let row_table_1 = iter_1.next().unwrap();
|
||||
let row_table_2 = iter_2.next().unwrap();
|
||||
assert_eq!(Some("a"), row_table_1.get("Name"));
|
||||
|
@ -1028,6 +1049,7 @@ mod tests {
|
|||
assert_eq!(&["John", "20"], iter.next().unwrap().as_slice());
|
||||
assert_eq!(&["May", "30", "foo"], iter.next().unwrap().as_slice());
|
||||
assert_eq!(&empty, iter.next().unwrap().as_slice());
|
||||
assert_eq!(&["", "", ""], iter.next().unwrap().as_slice());
|
||||
assert_eq!(&["a", "b", "c", "d"], iter.next().unwrap().as_slice());
|
||||
assert_eq!(None, iter.next());
|
||||
}
|
||||
|
@ -1045,6 +1067,7 @@ mod tests {
|
|||
assert_eq!(&["John", "20"], iter_1.next().unwrap().as_slice());
|
||||
assert_eq!(&["May", "30", "foo"], iter_1.next().unwrap().as_slice());
|
||||
assert_eq!(&empty, iter_1.next().unwrap().as_slice());
|
||||
assert_eq!(&["", "", ""], iter_1.next().unwrap().as_slice());
|
||||
assert_eq!(&["a", "b", "c", "d"], iter_1.next().unwrap().as_slice());
|
||||
assert_eq!(None, iter_1.next());
|
||||
assert_eq!(&["Carpenter", "Single"], iter_2.next().unwrap().as_slice());
|
||||
|
@ -1053,6 +1076,7 @@ mod tests {
|
|||
iter_2.next().unwrap().as_slice()
|
||||
);
|
||||
assert_eq!(&empty, iter_2.next().unwrap().as_slice());
|
||||
assert_eq!(&["", "", ""], iter_2.next().unwrap().as_slice());
|
||||
assert_eq!(&["e", "f", "g", "h"], iter_2.next().unwrap().as_slice());
|
||||
assert_eq!(None, iter_2.next());
|
||||
}
|
||||
|
@ -1109,6 +1133,13 @@ mod tests {
|
|||
let mut iter = row.iter();
|
||||
assert_eq!(None, iter.next());
|
||||
|
||||
let row = table_iter.next().unwrap();
|
||||
let mut iter = row.iter();
|
||||
assert_eq!(Some(""), iter.next().map(String::as_str));
|
||||
assert_eq!(Some(""), iter.next().map(String::as_str));
|
||||
assert_eq!(Some(""), iter.next().map(String::as_str));
|
||||
assert_eq!(None, iter.next());
|
||||
|
||||
let row = table_iter.next().unwrap();
|
||||
let mut iter = row.iter();
|
||||
assert_eq!(Some("a"), iter.next().map(String::as_str));
|
||||
|
@ -1156,6 +1187,19 @@ mod tests {
|
|||
assert_eq!(None, iter_1.next());
|
||||
assert_eq!(None, iter_2.next());
|
||||
|
||||
let row_1 = table_1.next().unwrap();
|
||||
let row_2 = table_2.next().unwrap();
|
||||
let mut iter_1 = row_1.iter();
|
||||
let mut iter_2 = row_2.iter();
|
||||
assert_eq!(Some(""), iter_1.next().map(String::as_str));
|
||||
assert_eq!(Some(""), iter_1.next().map(String::as_str));
|
||||
assert_eq!(Some(""), iter_1.next().map(String::as_str));
|
||||
assert_eq!(None, iter_1.next());
|
||||
assert_eq!(Some(""), iter_2.next().map(String::as_str));
|
||||
assert_eq!(Some(""), iter_2.next().map(String::as_str));
|
||||
assert_eq!(Some(""), iter_2.next().map(String::as_str));
|
||||
assert_eq!(None, iter_2.next());
|
||||
|
||||
let row_1 = table_1.next().unwrap();
|
||||
let row_2 = table_2.next().unwrap();
|
||||
let mut iter_1 = row_1.iter();
|
||||
|
|
2
tests/fixtures/lsp/completion/keyword.nu
vendored
2
tests/fixtures/lsp/completion/keyword.nu
vendored
|
@ -1 +1 @@
|
|||
de
|
||||
ov
|
||||
|
|
Loading…
Reference in New Issue
Block a user