mirror of
https://github.com/nushell/nushell.git
synced 2025-01-03 13:00:08 +01:00
Add support for ~ expansion
This ended up being a bit of a yak shave. The basic idea in this commit is to expand `~` in paths, but only in paths. The way this is accomplished is by doing the expansion inside of the code that parses literal syntax for `SyntaxType::Path`. As a quick refresher: every command is entitled to expand its arguments in a custom way. While this could in theory be used for general-purpose macros, today the expansion facility is limited to syntactic hints. For example, the syntax `where cpu > 0` expands under the hood to `where { $it.cpu > 0 }`. This happens because the first argument to `where` is defined as a `SyntaxType::Block`, and the parser coerces binary expressions whose left-hand-side looks like a member into a block when the command is expecting one. This is mildly more magical than what most programming languages would do, but we believe that it makes sense to allow commands to fine-tune the syntax because of the domain nushell is in (command-line shells). The syntactic expansions supported by this facility are relatively limited. For example, we don't allow `$it` to become a bare word, simply because the command asks for a string in the relevant position. That would quickly become more confusing than it's worth. This PR adds a new `SyntaxType` rule: `SyntaxType::Path`. When a command declares a parameter as a `SyntaxType::Path`, string literals and bare words passed as an argument to that parameter are processed using the path expansion rules. Right now, that only means that `~` is expanded into the home directory, but additional rules are possible in the future. By restricting this expansion to a syntactic expansion when passed as an argument to a command expecting a path, we avoid making `~` a generally reserved character. This will also allow us to give good tab completion for paths with `~` characters in them when a command is expecting a path. In order to accomplish the above, this commit changes the parsing functions to take a `Context` instead of just a `CommandRegistry`. From the perspective of macro expansion, you can think of the `CommandRegistry` as a dictionary of in-scope macros, and the `Context` as the compile-time state used in expansion. This could gain additional functionality over time as we find more uses for the expansion system.
This commit is contained in:
parent
8da82c79b8
commit
34292b282a
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -1733,6 +1733,7 @@ dependencies = [
|
||||
"serde_ini 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"shellexpand 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subprocess 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"surf 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syntect 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -2646,6 +2647,11 @@ dependencies = [
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellexpand"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.2"
|
||||
@ -3568,6 +3574,7 @@ dependencies = [
|
||||
"checksum serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "38b08a9a90e5260fe01c6480ec7c811606df6d3a660415808c3c3fa8ed95b582"
|
||||
"checksum shell-words 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39acde55a154c4cd3ae048ac78cc21c25f3a0145e44111b523279113dce0d94a"
|
||||
"checksum shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ee04b46101f57121c9da2b151988283b6beb79b34f5bb29a58ee48cb695122c"
|
||||
"checksum shellexpand 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de7a5b5a9142fd278a10e0209b021a1b85849352e6951f4f914735c976737564"
|
||||
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
"checksum sluice 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ec70d7c3b17c262d4a18f7291c6ce62bf47170915f3b795434d3c5c49a4e59b7"
|
||||
"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
|
||||
|
@ -78,6 +78,7 @@ battery = "0.7.4"
|
||||
textwrap = {version = "0.11.0", features = ["term_size"]}
|
||||
rawkey = {version = "0.1.2", optional = true }
|
||||
clipboard = {version = "0.5", optional = true }
|
||||
shellexpand = "1.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.6.1"
|
||||
|
15
src/cli.rs
15
src/cli.rs
@ -58,12 +58,17 @@ fn load_plugin(path: &std::path::Path, context: &mut Context) -> Result<(), Shel
|
||||
|
||||
let mut input = String::new();
|
||||
match reader.read_line(&mut input) {
|
||||
Ok(_) => {
|
||||
Ok(count) => {
|
||||
trace!("processing response ({} bytes)", count);
|
||||
|
||||
let response = serde_json::from_str::<JsonRpc<Result<Signature, ShellError>>>(&input);
|
||||
match response {
|
||||
Ok(jrpc) => match jrpc.params {
|
||||
Ok(params) => {
|
||||
let fname = path.to_string_lossy();
|
||||
|
||||
trace!("processing {:?}", params);
|
||||
|
||||
if params.is_filter {
|
||||
let fname = fname.to_string();
|
||||
let name = params.name.clone();
|
||||
@ -93,14 +98,18 @@ fn load_plugins_in_dir(path: &std::path::PathBuf, context: &mut Context) -> Resu
|
||||
let re_bin = Regex::new(r"^nu_plugin_[A-Za-z_]+$")?;
|
||||
let re_exe = Regex::new(r"^nu_plugin_[A-Za-z_]+\.exe$")?;
|
||||
|
||||
trace!("Looking for plugins in {:?}", path);
|
||||
|
||||
match std::fs::read_dir(path) {
|
||||
Ok(p) => {
|
||||
for entry in p {
|
||||
let entry = entry?;
|
||||
let filename = entry.file_name();
|
||||
let f_name = filename.to_string_lossy();
|
||||
|
||||
if re_bin.is_match(&f_name) || re_exe.is_match(&f_name) {
|
||||
let mut load_path = path.clone();
|
||||
trace!("Found {:?}", f_name);
|
||||
load_path.push(f_name.to_string());
|
||||
load_plugin(&load_path, context)?;
|
||||
}
|
||||
@ -504,7 +513,9 @@ fn classify_command(
|
||||
|
||||
trace!(target: "nu::build_pipeline", "classifying {:?}", config);
|
||||
|
||||
let args: hir::Call = config.parse_args(call, context.registry(), source)?;
|
||||
let args: hir::Call = config.parse_args(call, &context, source)?;
|
||||
|
||||
trace!(target: "nu::build_pipeline", "args :: {}", args.debug(source));
|
||||
|
||||
Ok(ClassifiedCommand::Internal(InternalCommand {
|
||||
command,
|
||||
|
@ -122,10 +122,11 @@ impl InternalCommand {
|
||||
self.name_span.clone(),
|
||||
context.source_map.clone(),
|
||||
self.args,
|
||||
source,
|
||||
&source,
|
||||
objects,
|
||||
);
|
||||
|
||||
let result = trace_out_stream!(target: "nu::trace_stream::internal", source: &source, "output" = result);
|
||||
let mut result = result.values;
|
||||
|
||||
let mut stream = VecDeque::new();
|
||||
|
@ -422,6 +422,25 @@ pub enum CommandAction {
|
||||
LeaveShell,
|
||||
}
|
||||
|
||||
impl ToDebug for CommandAction {
|
||||
fn fmt_debug(&self, f: &mut fmt::Formatter, _source: &str) -> fmt::Result {
|
||||
match self {
|
||||
CommandAction::ChangePath(s) => write!(f, "action:change-path={}", s),
|
||||
CommandAction::AddSpanSource(u, source) => {
|
||||
write!(f, "action:add-span-source={}@{:?}", u, source)
|
||||
}
|
||||
CommandAction::Exit => write!(f, "action:exit"),
|
||||
CommandAction::EnterShell(s) => write!(f, "action:enter-shell={}", s),
|
||||
CommandAction::EnterValueShell(t) => {
|
||||
write!(f, "action:enter-value-shell={:?}", t.debug())
|
||||
}
|
||||
CommandAction::PreviousShell => write!(f, "action:previous-shell"),
|
||||
CommandAction::NextShell => write!(f, "action:next-shell"),
|
||||
CommandAction::LeaveShell => write!(f, "action:leave-shell"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum ReturnSuccess {
|
||||
Value(Tagged<Value>),
|
||||
@ -430,6 +449,16 @@ pub enum ReturnSuccess {
|
||||
|
||||
pub type ReturnValue = Result<ReturnSuccess, ShellError>;
|
||||
|
||||
impl ToDebug for ReturnValue {
|
||||
fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result {
|
||||
match self {
|
||||
Err(err) => write!(f, "{}", err.debug(source)),
|
||||
Ok(ReturnSuccess::Value(v)) => write!(f, "{:?}", v.debug()),
|
||||
Ok(ReturnSuccess::Action(a)) => write!(f, "{}", a.debug(source)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tagged<Value>> for ReturnValue {
|
||||
fn from(input: Tagged<Value>) -> ReturnValue {
|
||||
Ok(ReturnSuccess::Value(input))
|
||||
@ -469,7 +498,7 @@ pub trait WholeStreamCommand: Send + Sync {
|
||||
Signature {
|
||||
name: self.name().to_string(),
|
||||
positional: vec![],
|
||||
rest_positional: true,
|
||||
rest_positional: None,
|
||||
named: indexmap::IndexMap::new(),
|
||||
is_filter: true,
|
||||
}
|
||||
@ -491,7 +520,7 @@ pub trait PerItemCommand: Send + Sync {
|
||||
Signature {
|
||||
name: self.name().to_string(),
|
||||
positional: vec![],
|
||||
rest_positional: true,
|
||||
rest_positional: None,
|
||||
named: indexmap::IndexMap::new(),
|
||||
is_filter: true,
|
||||
}
|
||||
|
@ -22,18 +22,21 @@ impl WholeStreamCommand for Get {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
args.process(registry, get)?.run()
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("get").rest()
|
||||
Signature::build("get").rest(SyntaxType::Member)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_member(path: &Tagged<String>, obj: &Tagged<Value>) -> Result<Tagged<Value>, ShellError> {
|
||||
let mut current = obj;
|
||||
let mut current = Some(obj);
|
||||
for p in path.split(".") {
|
||||
match current.get_data_by_key(p) {
|
||||
Some(v) => current = v,
|
||||
None => {
|
||||
if let Some(obj) = current {
|
||||
current = match obj.get_data_by_key(p) {
|
||||
Some(v) => Some(v),
|
||||
None =>
|
||||
// Before we give up, see if they gave us a path that matches a field name by itself
|
||||
{
|
||||
match obj.get_data_by_key(&path.item) {
|
||||
Some(v) => return Ok(v.clone()),
|
||||
None => {
|
||||
@ -47,8 +50,13 @@ fn get_member(path: &Tagged<String>, obj: &Tagged<Value>) -> Result<Tagged<Value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(current.clone())
|
||||
match current {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => Ok(Value::nothing().tagged(obj.tag)),
|
||||
}
|
||||
// Ok(current.clone())
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
|
@ -27,7 +27,7 @@ impl PerItemCommand for Mkdir {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("mkdir").rest()
|
||||
Signature::build("mkdir").rest(SyntaxType::Path)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,8 @@ fn run(call_info: &CallInfo, shell_manager: &ShellManager) -> Result<OutputStrea
|
||||
{
|
||||
file => file,
|
||||
};
|
||||
let path_str = path.as_string()?;
|
||||
let path_buf = path.as_path()?;
|
||||
let path_str = path_buf.display().to_string();
|
||||
let path_span = path.span();
|
||||
let name_span = call_info.name_span;
|
||||
let has_raw = call_info.args.has("raw");
|
||||
@ -426,14 +427,16 @@ pub fn parse_string_as_value(
|
||||
name_span: Span,
|
||||
) -> Result<Tagged<Value>, ShellError> {
|
||||
match extension {
|
||||
Some(x) if x == "csv" => crate::commands::from_csv::from_csv_string_to_value(
|
||||
contents,
|
||||
false,
|
||||
contents_tag,
|
||||
)
|
||||
Some(x) if x == "csv" => {
|
||||
crate::commands::from_csv::from_csv_string_to_value(contents, false, contents_tag)
|
||||
.map_err(move |_| {
|
||||
ShellError::labeled_error("Could not open as CSV", "could not open as CSV", name_span)
|
||||
}),
|
||||
ShellError::labeled_error(
|
||||
"Could not open as CSV",
|
||||
"could not open as CSV",
|
||||
name_span,
|
||||
)
|
||||
})
|
||||
}
|
||||
Some(x) if x == "toml" => {
|
||||
crate::commands::from_toml::from_toml_string_to_value(contents, contents_tag).map_err(
|
||||
move |_| {
|
||||
|
@ -17,7 +17,7 @@ impl WholeStreamCommand for Pick {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("pick").rest()
|
||||
Signature::build("pick").rest(SyntaxType::Any)
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -3,6 +3,7 @@ use crate::errors::ShellError;
|
||||
use crate::parser::registry;
|
||||
use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use log::trace;
|
||||
use serde::{self, Deserialize, Serialize};
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
@ -64,6 +65,8 @@ pub fn filter_plugin(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
trace!("filter_plugin :: {}", path);
|
||||
|
||||
let args = args.evaluate_once(registry)?;
|
||||
|
||||
let mut child = std::process::Command::new(path)
|
||||
@ -80,6 +83,8 @@ pub fn filter_plugin(
|
||||
|
||||
let call_info = args.call_info.clone();
|
||||
|
||||
trace!("filtering :: {:?}", call_info);
|
||||
|
||||
let stream = bos
|
||||
.chain(args.input.values)
|
||||
.chain(eos)
|
||||
@ -95,7 +100,14 @@ pub fn filter_plugin(
|
||||
|
||||
let request = JsonRpc::new("begin_filter", call_info.clone());
|
||||
let request_raw = serde_json::to_string(&request).unwrap();
|
||||
let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error
|
||||
match stdin.write(format!("{}\n", request_raw).as_bytes()) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
let mut result = VecDeque::new();
|
||||
result.push_back(Err(ShellError::unexpected(format!("{}", err))));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
let mut input = String::new();
|
||||
match reader.read_line(&mut input) {
|
||||
@ -140,7 +152,15 @@ pub fn filter_plugin(
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
let request: JsonRpc<std::vec::Vec<Value>> = JsonRpc::new("end_filter", vec![]);
|
||||
let request_raw = serde_json::to_string(&request).unwrap();
|
||||
let request_raw = match serde_json::to_string(&request) {
|
||||
Ok(req) => req,
|
||||
Err(err) => {
|
||||
let mut result = VecDeque::new();
|
||||
result.push_back(Err(ShellError::unexpected(format!("{}", err))));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = stdin.write(format!("{}\n", request_raw).as_bytes()); // TODO: Handle error
|
||||
|
||||
let mut input = String::new();
|
||||
|
@ -24,7 +24,7 @@ impl WholeStreamCommand for Reject {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("reject").rest()
|
||||
Signature::build("reject").rest(SyntaxType::Member)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,19 @@ use crate::prelude::*;
|
||||
|
||||
pub struct SortBy;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SortByArgs {
|
||||
rest: Vec<Tagged<String>>,
|
||||
reverse: bool,
|
||||
}
|
||||
|
||||
impl WholeStreamCommand for SortBy {
|
||||
fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
sort_by(args, registry)
|
||||
args.process(registry, sort_by)?.run()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
@ -18,43 +24,32 @@ impl WholeStreamCommand for SortBy {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("sort-by").switch("reverse")
|
||||
Signature::build("sort-by")
|
||||
.rest(SyntaxType::String)
|
||||
.switch("reverse")
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_by(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let args = args.evaluate_once(registry)?;
|
||||
let (input, args) = args.parts();
|
||||
fn sort_by(
|
||||
SortByArgs { reverse, rest }: SortByArgs,
|
||||
mut context: RunnableContext,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
Ok(OutputStream::new(async_stream_block! {
|
||||
let mut vec = context.input.drain_vec().await;
|
||||
|
||||
let fields: Result<Vec<_>, _> = args
|
||||
.positional
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|a| a.as_string())
|
||||
.collect();
|
||||
|
||||
let fields = fields?;
|
||||
|
||||
let output = input.values.collect::<Vec<_>>();
|
||||
|
||||
let reverse = args.has("reverse");
|
||||
let output = output.map(move |mut vec| {
|
||||
let calc_key = |item: &Tagged<Value>| {
|
||||
fields
|
||||
.iter()
|
||||
rest.iter()
|
||||
.map(|f| item.get_data_by_key(f).map(|i| i.clone()))
|
||||
.collect::<Vec<Option<Tagged<Value>>>>()
|
||||
};
|
||||
if reverse {
|
||||
vec.sort_by_cached_key(|item| {
|
||||
std::cmp::Reverse(calc_key(item))
|
||||
});
|
||||
vec.sort_by_cached_key(|item| std::cmp::Reverse(calc_key(item)));
|
||||
} else {
|
||||
vec.sort_by_cached_key(calc_key);
|
||||
}
|
||||
};
|
||||
|
||||
vec.into_iter().collect::<VecDeque<_>>()
|
||||
});
|
||||
|
||||
Ok(output.flatten_stream().from_input_stream())
|
||||
for item in vec {
|
||||
yield item.into();
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ impl WholeStreamCommand for SplitColumn {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("split-column")
|
||||
.required("separator", SyntaxType::Any)
|
||||
.rest()
|
||||
.rest(SyntaxType::Member)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ impl Context {
|
||||
name_span: Span,
|
||||
source_map: SourceMap,
|
||||
args: hir::Call,
|
||||
source: Text,
|
||||
source: &Text,
|
||||
input: InputStream,
|
||||
) -> OutputStream {
|
||||
let command_args = self.command_args(args, input, source, source_map, name_span);
|
||||
@ -131,13 +131,13 @@ impl Context {
|
||||
fn call_info(
|
||||
&self,
|
||||
args: hir::Call,
|
||||
source: Text,
|
||||
source: &Text,
|
||||
source_map: SourceMap,
|
||||
name_span: Span,
|
||||
) -> UnevaluatedCallInfo {
|
||||
UnevaluatedCallInfo {
|
||||
args,
|
||||
source,
|
||||
source: source.clone(),
|
||||
source_map,
|
||||
name_span,
|
||||
}
|
||||
@ -147,7 +147,7 @@ impl Context {
|
||||
&self,
|
||||
args: hir::Call,
|
||||
input: InputStream,
|
||||
source: Text,
|
||||
source: &Text,
|
||||
source_map: SourceMap,
|
||||
name_span: Span,
|
||||
) -> CommandArgs {
|
||||
|
@ -5,6 +5,7 @@ use ansi_term::Color;
|
||||
use derive_new::new;
|
||||
use language_reporting::{Diagnostic, Label, Severity};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Serialize, Deserialize)]
|
||||
pub enum Description {
|
||||
@ -56,6 +57,12 @@ pub struct ShellError {
|
||||
cause: Option<Box<ProximateShellError>>,
|
||||
}
|
||||
|
||||
impl ToDebug for ShellError {
|
||||
fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result {
|
||||
self.error.fmt_debug(f, source)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::de::Error for ShellError {
|
||||
fn custom<T>(msg: T) -> Self
|
||||
where
|
||||
@ -335,6 +342,7 @@ pub enum ProximateShellError {
|
||||
right: Tagged<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ProximateShellError {
|
||||
fn start(self) -> ShellError {
|
||||
ShellError {
|
||||
@ -344,6 +352,13 @@ impl ProximateShellError {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDebug for ProximateShellError {
|
||||
fn fmt_debug(&self, f: &mut fmt::Formatter, _source: &str) -> fmt::Result {
|
||||
// TODO: Custom debug for inner spans
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ShellDiagnostic {
|
||||
crate diagnostic: Diagnostic<Span>,
|
||||
|
@ -39,6 +39,7 @@ crate fn evaluate_baseline_expr(
|
||||
) -> Result<Tagged<Value>, ShellError> {
|
||||
match &expr.item {
|
||||
RawExpression::Literal(literal) => Ok(evaluate_literal(expr.copy_span(*literal), source)),
|
||||
RawExpression::FilePath(path) => Ok(Value::path(path.clone()).tagged(expr.span())),
|
||||
RawExpression::Synthetic(hir::Synthetic::String(s)) => Ok(Value::string(s).tagged_unknown()),
|
||||
RawExpression::Variable(var) => evaluate_reference(var, scope, source),
|
||||
RawExpression::ExternalCommand(external) => evaluate_external(external, scope, source),
|
||||
|
@ -68,7 +68,7 @@ impl TableView {
|
||||
for head in 0..headers.len() {
|
||||
let mut current_row_max = 0;
|
||||
for row in 0..values.len() {
|
||||
if entries[row][head].len() > current_row_max {
|
||||
if head > entries[row].len() && entries[row][head].len() > current_row_max {
|
||||
current_row_max = entries[row][head].len();
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ pub use crate::commands::command::{CallInfo, ReturnSuccess, ReturnValue};
|
||||
pub use crate::context::{SourceMap, SpanSource};
|
||||
pub use crate::env::host::BasicHost;
|
||||
pub use crate::object::base::OF64;
|
||||
pub use crate::parser::hir::SyntaxType;
|
||||
pub use crate::plugin::{serve_plugin, Plugin};
|
||||
pub use crate::utils::{AbsolutePath, RelativePath};
|
||||
pub use cli::cli;
|
||||
|
@ -285,6 +285,7 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is basically a legacy construct, I think
|
||||
pub fn data_descriptors(&self) -> Vec<String> {
|
||||
match self {
|
||||
Value::Primitive(_) => vec![],
|
||||
@ -542,6 +543,10 @@ impl Value {
|
||||
Value::Primitive(Primitive::String(s.into()))
|
||||
}
|
||||
|
||||
pub fn path(s: impl Into<PathBuf>) -> Value {
|
||||
Value::Primitive(Primitive::Path(s.into()))
|
||||
}
|
||||
|
||||
pub fn bytes(s: impl Into<u64>) -> Value {
|
||||
Value::Primitive(Primitive::Bytes(s.into()))
|
||||
}
|
||||
@ -577,6 +582,18 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl Tagged<Value> {
|
||||
crate fn as_path(&self) -> Result<PathBuf, ShellError> {
|
||||
match self.item() {
|
||||
Value::Primitive(Primitive::Path(path)) => Ok(path.clone()),
|
||||
other => Err(ShellError::type_error(
|
||||
"Path",
|
||||
other.type_name().tagged(self.span()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate fn select_fields(obj: &Value, fields: &[String], tag: impl Into<Tag>) -> Tagged<Value> {
|
||||
let mut out = TaggedDictBuilder::new(tag);
|
||||
|
||||
|
@ -2,7 +2,6 @@ use crate::object::base as value;
|
||||
use crate::parser::hir;
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub trait ExtractType: Sized {
|
||||
fn extract(value: &Tagged<Value>) -> Result<Self, ShellError>;
|
||||
@ -196,9 +195,9 @@ impl ExtractType for std::path::PathBuf {
|
||||
|
||||
match &value {
|
||||
Tagged {
|
||||
item: Value::Primitive(Primitive::String(p)),
|
||||
item: Value::Primitive(Primitive::Path(p)),
|
||||
..
|
||||
} => Ok(PathBuf::from(p)),
|
||||
} => Ok(p.clone()),
|
||||
other => Err(ShellError::type_error("Path", other.tagged_type_name())),
|
||||
}
|
||||
}
|
||||
|
@ -11,16 +11,22 @@ use derive_new::new;
|
||||
use getset::Getters;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::evaluate::Scope;
|
||||
|
||||
crate use self::baseline_parse::{baseline_parse_single_token, baseline_parse_token_as_string};
|
||||
crate use self::baseline_parse_tokens::{baseline_parse_next_expr, SyntaxType, TokensIterator};
|
||||
crate use self::baseline_parse::{
|
||||
baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path,
|
||||
baseline_parse_token_as_string,
|
||||
};
|
||||
crate use self::baseline_parse_tokens::{baseline_parse_next_expr, TokensIterator};
|
||||
crate use self::binary::Binary;
|
||||
crate use self::external_command::ExternalCommand;
|
||||
crate use self::named::NamedArguments;
|
||||
crate use self::path::Path;
|
||||
|
||||
pub use self::baseline_parse_tokens::SyntaxType;
|
||||
|
||||
pub fn path(head: impl Into<Expression>, tail: Vec<Tagged<impl Into<String>>>) -> Path {
|
||||
Path::new(
|
||||
head.into(),
|
||||
@ -68,6 +74,8 @@ impl ToDebug for Call {
|
||||
write!(f, "{}", named.debug(source))?;
|
||||
}
|
||||
|
||||
write!(f, ")")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -81,6 +89,7 @@ pub enum RawExpression {
|
||||
Block(Vec<Expression>),
|
||||
List(Vec<Expression>),
|
||||
Path(Box<Path>),
|
||||
FilePath(PathBuf),
|
||||
ExternalCommand(ExternalCommand),
|
||||
|
||||
#[allow(unused)]
|
||||
@ -105,6 +114,7 @@ impl RawExpression {
|
||||
match self {
|
||||
RawExpression::Literal(literal) => literal.type_name(),
|
||||
RawExpression::Synthetic(synthetic) => synthetic.type_name(),
|
||||
RawExpression::FilePath(..) => "filepath",
|
||||
RawExpression::Variable(..) => "variable",
|
||||
RawExpression::List(..) => "list",
|
||||
RawExpression::Binary(..) => "binary",
|
||||
@ -141,6 +151,10 @@ impl Expression {
|
||||
)
|
||||
}
|
||||
|
||||
crate fn file_path(path: impl Into<PathBuf>, outer: impl Into<Span>) -> Expression {
|
||||
Tagged::from_simple_spanned_item(RawExpression::FilePath(path.into()), outer.into())
|
||||
}
|
||||
|
||||
crate fn bare(span: impl Into<Span>) -> Expression {
|
||||
Tagged::from_simple_spanned_item(RawExpression::Literal(Literal::Bare), span.into())
|
||||
}
|
||||
@ -170,7 +184,8 @@ impl Expression {
|
||||
impl ToDebug for Expression {
|
||||
fn fmt_debug(&self, f: &mut fmt::Formatter, source: &str) -> fmt::Result {
|
||||
match self.item() {
|
||||
RawExpression::Literal(l) => write!(f, "{:?}", l),
|
||||
RawExpression::Literal(l) => l.tagged(self.span()).fmt_debug(f, source),
|
||||
RawExpression::FilePath(p) => write!(f, "{}", p.display()),
|
||||
RawExpression::Synthetic(Synthetic::String(s)) => write!(f, "{:?}", s),
|
||||
RawExpression::Variable(Variable::It(_)) => write!(f, "$it"),
|
||||
RawExpression::Variable(Variable::Other(s)) => write!(f, "${}", s.slice(source)),
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::context::Context;
|
||||
use crate::parser::{hir, RawToken, Token};
|
||||
use crate::Text;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Expression {
|
||||
match *token.item() {
|
||||
@ -15,6 +17,20 @@ pub fn baseline_parse_single_token(token: &Token, source: &Text) -> hir::Express
|
||||
}
|
||||
}
|
||||
|
||||
pub fn baseline_parse_token_as_number(token: &Token, source: &Text) -> hir::Expression {
|
||||
match *token.item() {
|
||||
RawToken::Variable(span) if span.slice(source) == "it" => {
|
||||
hir::Expression::it_variable(span, token.span())
|
||||
}
|
||||
RawToken::External(span) => hir::Expression::external_command(span, token.span()),
|
||||
RawToken::Variable(span) => hir::Expression::variable(span, token.span()),
|
||||
RawToken::Integer(int) => hir::Expression::int(int, token.span()),
|
||||
RawToken::Size(int, unit) => hir::Expression::size(int, unit, token.span()),
|
||||
RawToken::Bare => hir::Expression::bare(token.span()),
|
||||
RawToken::String(span) => hir::Expression::string(span, token.span()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn baseline_parse_token_as_string(token: &Token, source: &Text) -> hir::Expression {
|
||||
match *token.item() {
|
||||
RawToken::Variable(span) if span.slice(source) == "it" => {
|
||||
@ -28,3 +44,32 @@ pub fn baseline_parse_token_as_string(token: &Token, source: &Text) -> hir::Expr
|
||||
RawToken::String(span) => hir::Expression::string(span, token.span()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn baseline_parse_token_as_path(
|
||||
token: &Token,
|
||||
context: &Context,
|
||||
source: &Text,
|
||||
) -> hir::Expression {
|
||||
match *token.item() {
|
||||
RawToken::Variable(span) if span.slice(source) == "it" => {
|
||||
hir::Expression::it_variable(span, token.span())
|
||||
}
|
||||
RawToken::External(span) => hir::Expression::external_command(span, token.span()),
|
||||
RawToken::Variable(span) => hir::Expression::variable(span, token.span()),
|
||||
RawToken::Integer(_) => hir::Expression::bare(token.span()),
|
||||
RawToken::Size(_, _) => hir::Expression::bare(token.span()),
|
||||
RawToken::Bare => hir::Expression::file_path(
|
||||
expand_path(token.span().slice(source), context),
|
||||
token.span(),
|
||||
),
|
||||
RawToken::String(span) => {
|
||||
hir::Expression::file_path(expand_path(span.slice(source), context), token.span())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_path(string: &str, context: &Context) -> PathBuf {
|
||||
let expanded = shellexpand::tilde_with_context(string, || context.shell_manager.homedir());
|
||||
|
||||
PathBuf::from(expanded.as_ref())
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
use crate::context::Context;
|
||||
use crate::errors::ShellError;
|
||||
use crate::parser::registry::CommandRegistry;
|
||||
use crate::parser::{
|
||||
hir,
|
||||
hir::{baseline_parse_single_token, baseline_parse_token_as_string},
|
||||
hir::{
|
||||
baseline_parse_single_token, baseline_parse_token_as_number, baseline_parse_token_as_path,
|
||||
baseline_parse_token_as_string,
|
||||
},
|
||||
DelimitedNode, Delimiter, PathNode, RawToken, TokenNode,
|
||||
};
|
||||
use crate::{Span, Tag, Tagged, TaggedItem, Text};
|
||||
@ -12,8 +15,9 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn baseline_parse_tokens(
|
||||
token_nodes: &mut TokensIterator<'_>,
|
||||
registry: &CommandRegistry,
|
||||
context: &Context,
|
||||
source: &Text,
|
||||
syntax_type: SyntaxType,
|
||||
) -> Result<Vec<hir::Expression>, ShellError> {
|
||||
let mut exprs: Vec<hir::Expression> = vec![];
|
||||
|
||||
@ -22,7 +26,7 @@ pub fn baseline_parse_tokens(
|
||||
break;
|
||||
}
|
||||
|
||||
let expr = baseline_parse_next_expr(token_nodes, registry, source, SyntaxType::Any)?;
|
||||
let expr = baseline_parse_next_expr(token_nodes, context, source, syntax_type)?;
|
||||
exprs.push(expr);
|
||||
}
|
||||
|
||||
@ -35,7 +39,10 @@ pub enum SyntaxType {
|
||||
Any,
|
||||
List,
|
||||
Literal,
|
||||
String,
|
||||
Member,
|
||||
Variable,
|
||||
Number,
|
||||
Path,
|
||||
Binary,
|
||||
Block,
|
||||
@ -44,7 +51,7 @@ pub enum SyntaxType {
|
||||
|
||||
pub fn baseline_parse_next_expr(
|
||||
tokens: &mut TokensIterator,
|
||||
registry: &CommandRegistry,
|
||||
context: &Context,
|
||||
source: &Text,
|
||||
syntax_type: SyntaxType,
|
||||
) -> Result<hir::Expression, ShellError> {
|
||||
@ -56,7 +63,7 @@ pub fn baseline_parse_next_expr(
|
||||
|
||||
match (syntax_type, next) {
|
||||
(SyntaxType::Path, TokenNode::Token(token)) => {
|
||||
return Ok(baseline_parse_token_as_string(token, source))
|
||||
return Ok(baseline_parse_token_as_path(token, context, source))
|
||||
}
|
||||
|
||||
(SyntaxType::Path, token) => {
|
||||
@ -66,10 +73,50 @@ pub fn baseline_parse_next_expr(
|
||||
))
|
||||
}
|
||||
|
||||
_ => {}
|
||||
(SyntaxType::String, TokenNode::Token(token)) => {
|
||||
return Ok(baseline_parse_token_as_string(token, source));
|
||||
}
|
||||
|
||||
(SyntaxType::String, token) => {
|
||||
return Err(ShellError::type_error(
|
||||
"String",
|
||||
token.type_name().simple_spanned(token.span()),
|
||||
))
|
||||
}
|
||||
|
||||
(SyntaxType::Number, TokenNode::Token(token)) => {
|
||||
return Ok(baseline_parse_token_as_number(token, source));
|
||||
}
|
||||
|
||||
(SyntaxType::Number, token) => {
|
||||
return Err(ShellError::type_error(
|
||||
"Numeric",
|
||||
token.type_name().simple_spanned(token.span()),
|
||||
))
|
||||
}
|
||||
|
||||
// TODO: More legit member processing
|
||||
(SyntaxType::Member, TokenNode::Token(token)) => {
|
||||
return Ok(baseline_parse_token_as_string(token, source));
|
||||
}
|
||||
|
||||
(SyntaxType::Member, token) => {
|
||||
return Err(ShellError::type_error(
|
||||
"member",
|
||||
token.type_name().simple_spanned(token.span()),
|
||||
))
|
||||
}
|
||||
|
||||
(SyntaxType::Any, _) => {}
|
||||
(SyntaxType::List, _) => {}
|
||||
(SyntaxType::Literal, _) => {}
|
||||
(SyntaxType::Variable, _) => {}
|
||||
(SyntaxType::Binary, _) => {}
|
||||
(SyntaxType::Block, _) => {}
|
||||
(SyntaxType::Boolean, _) => {}
|
||||
};
|
||||
|
||||
let first = baseline_parse_semantic_token(next, registry, source)?;
|
||||
let first = baseline_parse_semantic_token(next, context, source)?;
|
||||
|
||||
let possible_op = tokens.peek();
|
||||
|
||||
@ -88,7 +135,7 @@ pub fn baseline_parse_next_expr(
|
||||
op.span(),
|
||||
))
|
||||
}
|
||||
Some(token) => baseline_parse_semantic_token(token, registry, source)?,
|
||||
Some(token) => baseline_parse_semantic_token(token, context, source)?,
|
||||
};
|
||||
|
||||
// We definitely have a binary expression here -- let's see if we should coerce it into a block
|
||||
@ -176,13 +223,13 @@ pub fn baseline_parse_next_expr(
|
||||
|
||||
pub fn baseline_parse_semantic_token(
|
||||
token: &TokenNode,
|
||||
registry: &CommandRegistry,
|
||||
context: &Context,
|
||||
source: &Text,
|
||||
) -> Result<hir::Expression, ShellError> {
|
||||
match token {
|
||||
TokenNode::Token(token) => Ok(baseline_parse_single_token(token, source)),
|
||||
TokenNode::Call(_call) => unimplemented!(),
|
||||
TokenNode::Delimited(delimited) => baseline_parse_delimited(delimited, registry, source),
|
||||
TokenNode::Delimited(delimited) => baseline_parse_delimited(delimited, context, source),
|
||||
TokenNode::Pipeline(_pipeline) => unimplemented!(),
|
||||
TokenNode::Operator(_op) => unreachable!(),
|
||||
TokenNode::Flag(_flag) => Err(ShellError::unimplemented(
|
||||
@ -191,20 +238,24 @@ pub fn baseline_parse_semantic_token(
|
||||
TokenNode::Member(_span) => unreachable!(),
|
||||
TokenNode::Whitespace(_span) => unreachable!(),
|
||||
TokenNode::Error(error) => Err(*error.item.clone()),
|
||||
TokenNode::Path(path) => baseline_parse_path(path, registry, source),
|
||||
TokenNode::Path(path) => baseline_parse_path(path, context, source),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn baseline_parse_delimited(
|
||||
token: &Tagged<DelimitedNode>,
|
||||
registry: &CommandRegistry,
|
||||
context: &Context,
|
||||
source: &Text,
|
||||
) -> Result<hir::Expression, ShellError> {
|
||||
match token.delimiter() {
|
||||
Delimiter::Brace => {
|
||||
let children = token.children();
|
||||
let exprs =
|
||||
baseline_parse_tokens(&mut TokensIterator::new(children), registry, source)?;
|
||||
let exprs = baseline_parse_tokens(
|
||||
&mut TokensIterator::new(children),
|
||||
context,
|
||||
source,
|
||||
SyntaxType::Any,
|
||||
)?;
|
||||
|
||||
let expr = hir::RawExpression::Block(exprs);
|
||||
Ok(Tagged::from_simple_spanned_item(expr, token.span()))
|
||||
@ -212,8 +263,12 @@ pub fn baseline_parse_delimited(
|
||||
Delimiter::Paren => unimplemented!(),
|
||||
Delimiter::Square => {
|
||||
let children = token.children();
|
||||
let exprs =
|
||||
baseline_parse_tokens(&mut TokensIterator::new(children), registry, source)?;
|
||||
let exprs = baseline_parse_tokens(
|
||||
&mut TokensIterator::new(children),
|
||||
context,
|
||||
source,
|
||||
SyntaxType::Any,
|
||||
)?;
|
||||
|
||||
let expr = hir::RawExpression::List(exprs);
|
||||
Ok(expr.tagged(Tag::unknown_origin(token.span())))
|
||||
@ -223,10 +278,10 @@ pub fn baseline_parse_delimited(
|
||||
|
||||
pub fn baseline_parse_path(
|
||||
token: &Tagged<PathNode>,
|
||||
registry: &CommandRegistry,
|
||||
context: &Context,
|
||||
source: &Text,
|
||||
) -> Result<hir::Expression, ShellError> {
|
||||
let head = baseline_parse_semantic_token(token.head(), registry, source)?;
|
||||
let head = baseline_parse_semantic_token(token.head(), context, source)?;
|
||||
|
||||
let mut tail = vec![];
|
||||
|
||||
|
@ -27,7 +27,7 @@ impl ToDebug for NamedArguments {
|
||||
for (name, value) in &self.named {
|
||||
match value {
|
||||
NamedValue::AbsentSwitch => continue,
|
||||
NamedValue::PresentSwitch(span) => write!(f, " {}", span.slice(source))?,
|
||||
NamedValue::PresentSwitch(span) => write!(f, " --{}", span.slice(source))?,
|
||||
NamedValue::AbsentValue => continue,
|
||||
NamedValue::Value(expr) => write!(f, " --{} {}", name, expr.debug(source))?,
|
||||
}
|
||||
|
@ -540,6 +540,8 @@ fn is_start_bare_char(c: char) -> bool {
|
||||
'@' => true,
|
||||
'*' => true,
|
||||
'?' => true,
|
||||
'~' => true,
|
||||
'+' => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -557,6 +559,8 @@ fn is_bare_char(c: char) -> bool {
|
||||
'*' => true,
|
||||
'?' => true,
|
||||
'=' => true,
|
||||
'~' => true,
|
||||
'+' => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::context::Context;
|
||||
use crate::errors::{ArgumentError, ShellError};
|
||||
use crate::parser::registry::{CommandRegistry, NamedType, PositionalType, Signature};
|
||||
use crate::parser::registry::{NamedType, PositionalType, Signature};
|
||||
use crate::parser::{baseline_parse_tokens, CallNode};
|
||||
use crate::parser::{
|
||||
hir::{self, NamedArguments},
|
||||
@ -10,7 +11,7 @@ use log::trace;
|
||||
|
||||
pub fn parse_command(
|
||||
config: &Signature,
|
||||
registry: &CommandRegistry,
|
||||
context: &Context,
|
||||
call: &Tagged<CallNode>,
|
||||
source: &Text,
|
||||
) -> Result<hir::Call, ShellError> {
|
||||
@ -31,7 +32,7 @@ pub fn parse_command(
|
||||
.collect()
|
||||
});
|
||||
|
||||
match parse_command_tail(&config, registry, children, source, call.span())? {
|
||||
match parse_command_tail(&config, context, children, source, call.span())? {
|
||||
None => Ok(hir::Call::new(Box::new(head), None, None)),
|
||||
Some((positional, named)) => Ok(hir::Call::new(Box::new(head), positional, named)),
|
||||
}
|
||||
@ -63,7 +64,7 @@ fn parse_command_head(head: &TokenNode) -> Result<hir::Expression, ShellError> {
|
||||
|
||||
fn parse_command_tail(
|
||||
config: &Signature,
|
||||
registry: &CommandRegistry,
|
||||
context: &Context,
|
||||
tail: Option<Vec<TokenNode>>,
|
||||
source: &Text,
|
||||
command_span: Span,
|
||||
@ -101,7 +102,7 @@ fn parse_command_tail(
|
||||
}
|
||||
|
||||
let expr =
|
||||
hir::baseline_parse_next_expr(tail, registry, source, *syntax_type)?;
|
||||
hir::baseline_parse_next_expr(tail, context, source, *syntax_type)?;
|
||||
|
||||
tail.restart();
|
||||
named.insert_mandatory(name, expr);
|
||||
@ -121,7 +122,7 @@ fn parse_command_tail(
|
||||
));
|
||||
}
|
||||
|
||||
let expr = hir::baseline_parse_next_expr(tail, registry, source, *syntax_type)?;
|
||||
let expr = hir::baseline_parse_next_expr(tail, context, source, *syntax_type)?;
|
||||
|
||||
tail.restart();
|
||||
named.insert_optional(name, Some(expr));
|
||||
@ -160,16 +161,17 @@ fn parse_command_tail(
|
||||
}
|
||||
}
|
||||
|
||||
let result = hir::baseline_parse_next_expr(tail, registry, source, arg.syntax_type())?;
|
||||
let result = hir::baseline_parse_next_expr(tail, context, source, arg.syntax_type())?;
|
||||
|
||||
positional.push(result);
|
||||
}
|
||||
|
||||
trace_remaining("after positional", tail.clone(), source);
|
||||
|
||||
// TODO: Only do this if rest params are specified
|
||||
let remainder = baseline_parse_tokens(tail, registry, source)?;
|
||||
if let Some(syntax_type) = config.rest_positional {
|
||||
let remainder = baseline_parse_tokens(tail, context, source, syntax_type)?;
|
||||
positional.extend(remainder);
|
||||
}
|
||||
|
||||
trace_remaining("after rest", tail.clone(), source);
|
||||
|
||||
@ -180,6 +182,8 @@ fn parse_command_tail(
|
||||
positional => Some(positional),
|
||||
};
|
||||
|
||||
// TODO: Error if extra unconsumed positional arguments
|
||||
|
||||
let named = match named {
|
||||
named if named.named.is_empty() => None,
|
||||
named => Some(named),
|
||||
|
@ -74,8 +74,8 @@ pub struct Signature {
|
||||
pub name: String,
|
||||
#[new(default)]
|
||||
pub positional: Vec<PositionalType>,
|
||||
#[new(value = "false")]
|
||||
pub rest_positional: bool,
|
||||
#[new(value = "None")]
|
||||
pub rest_positional: Option<SyntaxType>,
|
||||
#[new(default)]
|
||||
pub named: IndexMap<String, NamedType>,
|
||||
#[new(value = "false")]
|
||||
@ -130,8 +130,8 @@ impl Signature {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rest(mut self) -> Signature {
|
||||
self.rest_positional = true;
|
||||
pub fn rest(mut self, ty: SyntaxType) -> Signature {
|
||||
self.rest_positional = Some(ty);
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -279,10 +279,10 @@ impl Signature {
|
||||
crate fn parse_args(
|
||||
&self,
|
||||
call: &Tagged<CallNode>,
|
||||
registry: &CommandRegistry,
|
||||
context: &Context,
|
||||
source: &Text,
|
||||
) -> Result<hir::Call, ShellError> {
|
||||
let args = parse_command(self, registry, call, source)?;
|
||||
let args = parse_command(self, context, call, source)?;
|
||||
|
||||
trace!("parsed args: {:?}", args);
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu::{
|
||||
serve_plugin, CallInfo, Plugin, PositionalType, Primitive, ReturnSuccess, ReturnValue,
|
||||
ShellError, Signature, Tagged, Value,
|
||||
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
|
||||
SyntaxType, Tagged, Value,
|
||||
};
|
||||
|
||||
struct Add {
|
||||
@ -43,17 +42,12 @@ impl Add {
|
||||
|
||||
impl Plugin for Add {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature {
|
||||
name: "add".to_string(),
|
||||
positional: vec![
|
||||
PositionalType::mandatory_any("Field"),
|
||||
PositionalType::mandatory_any("Value"),
|
||||
],
|
||||
is_filter: true,
|
||||
named: IndexMap::new(),
|
||||
rest_positional: true,
|
||||
})
|
||||
Ok(Signature::build("add")
|
||||
.required("Field", SyntaxType::String)
|
||||
.required("Value", SyntaxType::String)
|
||||
.rest(SyntaxType::String).filter())
|
||||
}
|
||||
|
||||
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
if let Some(args) = call_info.args.positional {
|
||||
match &args[0] {
|
||||
|
@ -1,9 +1,6 @@
|
||||
#![feature(option_flattening)]
|
||||
use crossterm::{cursor, terminal, Attribute, RawScreen};
|
||||
use indexmap::IndexMap;
|
||||
use nu::{
|
||||
serve_plugin, CallInfo, NamedType, Plugin, ShellError, Signature, SpanSource, Tagged, Value,
|
||||
};
|
||||
use nu::{serve_plugin, CallInfo, Plugin, ShellError, Signature, SpanSource, Tagged, Value};
|
||||
use pretty_hex::*;
|
||||
|
||||
struct BinaryView;
|
||||
@ -16,15 +13,7 @@ impl BinaryView {
|
||||
|
||||
impl Plugin for BinaryView {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
let mut named = IndexMap::new();
|
||||
named.insert("lores".to_string(), NamedType::Switch);
|
||||
Ok(Signature {
|
||||
name: "binaryview".to_string(),
|
||||
positional: vec![],
|
||||
is_filter: false,
|
||||
named,
|
||||
rest_positional: false,
|
||||
})
|
||||
Ok(Signature::build("binaryview").switch("lores"))
|
||||
}
|
||||
|
||||
fn sink(&mut self, call_info: CallInfo, input: Vec<Tagged<Value>>) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu::{
|
||||
serve_plugin, CallInfo, Plugin, PositionalType, Primitive, ReturnSuccess, ReturnValue,
|
||||
ShellError, Signature, Tagged, Value,
|
||||
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
|
||||
SyntaxType, Tagged, Value,
|
||||
};
|
||||
|
||||
struct Edit {
|
||||
@ -42,17 +41,12 @@ impl Edit {
|
||||
|
||||
impl Plugin for Edit {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature {
|
||||
name: "edit".to_string(),
|
||||
positional: vec![
|
||||
PositionalType::mandatory_any("Field"),
|
||||
PositionalType::mandatory_any("Value"),
|
||||
],
|
||||
is_filter: true,
|
||||
named: IndexMap::new(),
|
||||
rest_positional: true,
|
||||
})
|
||||
Ok(Signature::build("edit")
|
||||
.required("Field", SyntaxType::String)
|
||||
.required("Value", SyntaxType::String)
|
||||
.filter())
|
||||
}
|
||||
|
||||
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
if let Some(args) = call_info.args.positional {
|
||||
match &args[0] {
|
||||
|
@ -1,7 +1,6 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu::{
|
||||
serve_plugin, CallInfo, NamedType, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError,
|
||||
Signature, Tagged, TaggedItem, Value,
|
||||
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
|
||||
SyntaxType, Tagged, TaggedItem, Value,
|
||||
};
|
||||
|
||||
enum Action {
|
||||
@ -116,19 +115,14 @@ impl Inc {
|
||||
|
||||
impl Plugin for Inc {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
let mut named = IndexMap::new();
|
||||
named.insert("major".to_string(), NamedType::Switch);
|
||||
named.insert("minor".to_string(), NamedType::Switch);
|
||||
named.insert("patch".to_string(), NamedType::Switch);
|
||||
|
||||
Ok(Signature {
|
||||
name: "inc".to_string(),
|
||||
positional: vec![],
|
||||
is_filter: true,
|
||||
named,
|
||||
rest_positional: true,
|
||||
})
|
||||
Ok(Signature::build("inc")
|
||||
.switch("major")
|
||||
.switch("minor")
|
||||
.switch("patch")
|
||||
.rest(SyntaxType::String)
|
||||
.filter())
|
||||
}
|
||||
|
||||
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
if call_info.args.has("major") {
|
||||
self.for_semver(SemVerAction::Major);
|
||||
|
@ -1,7 +1,7 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu::{
|
||||
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
|
||||
Tagged, Value,
|
||||
SyntaxType, Tagged, Value,
|
||||
};
|
||||
|
||||
struct Skip {
|
||||
@ -20,7 +20,7 @@ impl Plugin for Skip {
|
||||
positional: vec![],
|
||||
is_filter: true,
|
||||
named: IndexMap::new(),
|
||||
rest_positional: true,
|
||||
rest_positional: Some(SyntaxType::Number),
|
||||
})
|
||||
}
|
||||
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
|
@ -1,7 +1,6 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu::{
|
||||
serve_plugin, CallInfo, NamedType, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError,
|
||||
Signature, Tagged, Value,
|
||||
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
|
||||
SyntaxType, Tagged, Value,
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
@ -40,10 +39,12 @@ impl Str {
|
||||
let applied = match self.action.as_ref() {
|
||||
Some(Action::Downcase) => Value::string(input.to_ascii_lowercase()),
|
||||
Some(Action::Upcase) => Value::string(input.to_ascii_uppercase()),
|
||||
Some(Action::ToInteger) => match input.trim().parse::<i64>() {
|
||||
Some(Action::ToInteger) => match input.trim() {
|
||||
other => match other.parse::<i64>() {
|
||||
Ok(v) => Value::int(v),
|
||||
Err(_) => Value::string(input),
|
||||
},
|
||||
},
|
||||
Some(Action::Replace(ref mode)) => match mode {
|
||||
ReplaceAction::Direct => Value::string(self.first_param()),
|
||||
ReplaceAction::FindAndReplace => {
|
||||
@ -138,9 +139,7 @@ impl Str {
|
||||
Some(ref f) => {
|
||||
let replacement = match value.item.get_data_by_path(value.tag(), f) {
|
||||
Some(result) => self.strutils(result.map(|x| x.clone()))?,
|
||||
None => {
|
||||
return Err(ShellError::string("str could not find field to replace"))
|
||||
}
|
||||
None => return Ok(Tagged::from_item(Value::nothing(), value.tag)),
|
||||
};
|
||||
match value
|
||||
.item
|
||||
@ -168,20 +167,14 @@ impl Str {
|
||||
|
||||
impl Plugin for Str {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
let mut named = IndexMap::new();
|
||||
named.insert("downcase".to_string(), NamedType::Switch);
|
||||
named.insert("upcase".to_string(), NamedType::Switch);
|
||||
named.insert("to-int".to_string(), NamedType::Switch);
|
||||
named.insert("replace".to_string(), NamedType::Switch);
|
||||
named.insert("find-replace".to_string(), NamedType::Switch);
|
||||
|
||||
Ok(Signature {
|
||||
name: "str".to_string(),
|
||||
positional: vec![],
|
||||
is_filter: true,
|
||||
named,
|
||||
rest_positional: true,
|
||||
})
|
||||
Ok(Signature::build("str")
|
||||
.switch("downcase")
|
||||
.switch("upcase")
|
||||
.switch("to-int")
|
||||
.switch("replace")
|
||||
.switch("find-replace")
|
||||
.rest(SyntaxType::Member)
|
||||
.filter())
|
||||
}
|
||||
|
||||
fn begin_filter(&mut self, call_info: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
|
@ -1,4 +1,3 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu::{
|
||||
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
|
||||
Tag, Tagged, Value,
|
||||
@ -14,6 +13,7 @@ impl Sum {
|
||||
|
||||
fn sum(&mut self, value: Tagged<Value>) -> Result<(), ShellError> {
|
||||
match value.item {
|
||||
Value::Primitive(Primitive::Nothing) => Ok(()),
|
||||
Value::Primitive(Primitive::Int(i)) => {
|
||||
match self.total {
|
||||
Some(Tagged {
|
||||
@ -64,14 +64,9 @@ impl Sum {
|
||||
|
||||
impl Plugin for Sum {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature {
|
||||
name: "sum".to_string(),
|
||||
positional: vec![],
|
||||
is_filter: true,
|
||||
named: IndexMap::new(),
|
||||
rest_positional: true,
|
||||
})
|
||||
Ok(Signature::build("sum").filter())
|
||||
}
|
||||
|
||||
fn begin_filter(&mut self, _: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
use futures::executor::block_on;
|
||||
use futures::stream::StreamExt;
|
||||
use heim::{disk, memory, net};
|
||||
use indexmap::IndexMap;
|
||||
use nu::{
|
||||
serve_plugin, CallInfo, Plugin, Primitive, ReturnSuccess, ReturnValue, ShellError, Signature,
|
||||
Tag, Tagged, TaggedDictBuilder, Value,
|
||||
SyntaxType, Tag, Tagged, TaggedDictBuilder, Value,
|
||||
};
|
||||
use std::ffi::OsStr;
|
||||
|
||||
@ -251,14 +250,9 @@ async fn sysinfo(tag: Tag) -> Vec<Tagged<Value>> {
|
||||
|
||||
impl Plugin for Sys {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature {
|
||||
name: "sys".to_string(),
|
||||
positional: vec![],
|
||||
is_filter: true,
|
||||
named: IndexMap::new(),
|
||||
rest_positional: true,
|
||||
})
|
||||
Ok(Signature::build("sys").rest(SyntaxType::Any))
|
||||
}
|
||||
|
||||
fn begin_filter(&mut self, callinfo: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||
Ok(block_on(sysinfo(Tag::unknown_origin(callinfo.name_span)))
|
||||
.into_iter()
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
use crossterm::{cursor, terminal, RawScreen};
|
||||
use crossterm::{InputEvent, KeyEvent};
|
||||
use indexmap::IndexMap;
|
||||
use nu::{
|
||||
serve_plugin, CallInfo, Plugin, Primitive, ShellError, Signature, SourceMap, SpanSource,
|
||||
Tagged, Value,
|
||||
@ -29,13 +28,7 @@ impl TextView {
|
||||
|
||||
impl Plugin for TextView {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature {
|
||||
name: "textview".to_string(),
|
||||
positional: vec![],
|
||||
is_filter: false,
|
||||
named: IndexMap::new(),
|
||||
rest_positional: false,
|
||||
})
|
||||
Ok(Signature::build("textview"))
|
||||
}
|
||||
|
||||
fn sink(&mut self, call_info: CallInfo, input: Vec<Tagged<Value>>) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
use derive_new::new;
|
||||
use indexmap::IndexMap;
|
||||
use nu::{serve_plugin, CallInfo, Plugin, ShellError, Signature, Tagged, Value};
|
||||
use ptree::item::StringItem;
|
||||
use ptree::output::print_tree_with;
|
||||
@ -81,13 +80,7 @@ struct TreeViewer;
|
||||
|
||||
impl Plugin for TreeViewer {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature {
|
||||
name: "tree".to_string(),
|
||||
positional: vec![],
|
||||
is_filter: false,
|
||||
named: IndexMap::new(),
|
||||
rest_positional: true,
|
||||
})
|
||||
Ok(Signature::build("tree"))
|
||||
}
|
||||
|
||||
fn sink(&mut self, _call_info: CallInfo, input: Vec<Tagged<Value>>) {
|
||||
|
@ -32,6 +32,25 @@ macro_rules! trace_stream {
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! trace_out_stream {
|
||||
(target: $target:tt, source: $source:expr, $desc:tt = $expr:expr) => {{
|
||||
if log::log_enabled!(target: $target, log::Level::Trace) {
|
||||
use futures::stream::StreamExt;
|
||||
|
||||
let source = $source.clone();
|
||||
|
||||
let objects = $expr.values.inspect(move |o| {
|
||||
trace!(target: $target, "{} = {}", $desc, o.debug(&source));
|
||||
});
|
||||
|
||||
$crate::stream::OutputStream::new(objects)
|
||||
} else {
|
||||
$expr
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
crate use crate::cli::MaybeOwned;
|
||||
crate use crate::commands::command::{
|
||||
CallInfo, CommandAction, CommandArgs, ReturnSuccess, ReturnValue, RunnableContext,
|
||||
|
@ -72,12 +72,16 @@ impl Shell for FilesystemShell {
|
||||
"filesystem".to_string()
|
||||
}
|
||||
|
||||
fn homedir(&self) -> Option<PathBuf> {
|
||||
dirs::home_dir()
|
||||
}
|
||||
|
||||
fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let cwd = self.path();
|
||||
let mut full_path = PathBuf::from(self.path());
|
||||
|
||||
match &args.nth(0) {
|
||||
Some(Tagged { item: value, .. }) => full_path.push(Path::new(&value.as_string()?)),
|
||||
Some(value) => full_path.push(Path::new(&value.as_path()?)),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@ -176,7 +180,7 @@ impl Shell for FilesystemShell {
|
||||
}
|
||||
},
|
||||
Some(v) => {
|
||||
let target = v.as_string()?;
|
||||
let target = v.as_path()?;
|
||||
let path = PathBuf::from(self.path());
|
||||
match dunce::canonicalize(path.join(target).as_path()) {
|
||||
Ok(p) => p,
|
||||
|
@ -7,9 +7,11 @@ use crate::context::SourceMap;
|
||||
use crate::errors::ShellError;
|
||||
use crate::prelude::*;
|
||||
use crate::stream::OutputStream;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub trait Shell: std::fmt::Debug {
|
||||
fn name(&self, source_map: &SourceMap) -> String;
|
||||
fn homedir(&self) -> Option<PathBuf>;
|
||||
fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>;
|
||||
fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>;
|
||||
fn cp(&self, args: CopyArgs, name: Span, path: &str) -> Result<OutputStream, ShellError>;
|
||||
|
@ -9,6 +9,7 @@ use crate::shell::filesystem_shell::FilesystemShell;
|
||||
use crate::shell::shell::Shell;
|
||||
use crate::stream::OutputStream;
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -102,16 +103,24 @@ impl ShellManager {
|
||||
self.set_path(self.path());
|
||||
}
|
||||
|
||||
pub fn homedir(&self) -> Option<PathBuf> {
|
||||
let env = self.shells.lock().unwrap();
|
||||
|
||||
env[self.current_shell].homedir()
|
||||
}
|
||||
|
||||
pub fn ls(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let env = self.shells.lock().unwrap();
|
||||
|
||||
env[self.current_shell].ls(args)
|
||||
}
|
||||
|
||||
pub fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
let env = self.shells.lock().unwrap();
|
||||
|
||||
env[self.current_shell].cd(args)
|
||||
}
|
||||
|
||||
pub fn cp(
|
||||
&self,
|
||||
args: CopyArgs,
|
||||
|
@ -69,6 +69,10 @@ impl Shell for ValueShell {
|
||||
)
|
||||
}
|
||||
|
||||
fn homedir(&self) -> Option<PathBuf> {
|
||||
dirs::home_dir()
|
||||
}
|
||||
|
||||
fn ls(&self, _args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
|
||||
Ok(self
|
||||
.members()
|
||||
|
@ -105,7 +105,10 @@ impl FileStructure {
|
||||
self.root = path.to_path_buf();
|
||||
}
|
||||
|
||||
pub fn paths_applying_with<F>(&mut self, to: F) -> Result<Vec<(PathBuf, PathBuf)>, Box<dyn std::error::Error>>
|
||||
pub fn paths_applying_with<F>(
|
||||
&mut self,
|
||||
to: F,
|
||||
) -> Result<Vec<(PathBuf, PathBuf)>, Box<dyn std::error::Error>>
|
||||
where
|
||||
F: Fn((PathBuf, usize)) -> Result<(PathBuf, PathBuf), Box<dyn std::error::Error>>,
|
||||
{
|
||||
@ -155,6 +158,7 @@ impl FileStructure {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::{FileStructure, Res};
|
||||
use std::path::PathBuf;
|
||||
@ -175,7 +179,8 @@ mod tests {
|
||||
fn prepares_and_decorates_source_files_for_copying() {
|
||||
let mut res = FileStructure::new();
|
||||
|
||||
res.walk_decorate(fixtures().as_path()).expect("Can not decorate files traversal.");
|
||||
res.walk_decorate(fixtures().as_path())
|
||||
.expect("Can not decorate files traversal.");
|
||||
|
||||
assert_eq!(
|
||||
res.resources,
|
||||
|
@ -18,7 +18,7 @@ fn ls_lists_regular_files() {
|
||||
nu!(
|
||||
output,
|
||||
cwd(&full_path),
|
||||
"ls | get name | lines| split-column \".\" | get Column2 | str Column2 --to-int | sum | echo $it"
|
||||
r#"ls | get name | lines | split-column "." | get Column2 | str Column2 --to-int | sum | echo $it"#
|
||||
);
|
||||
|
||||
assert_eq!(output, "30");
|
||||
|
Loading…
Reference in New Issue
Block a user