Move most of the root package into a subcrate. (#1445)

This improves incremental build time when working on what was previously
the root package. For example, previously all plugins would be rebuilt
with a change to `src/commands/classified/external.rs`, but now only
`nu-cli` will have to be rebuilt (and anything that depends on it).
This commit is contained in:
Jason Gedge
2020-03-04 13:58:20 -05:00
committed by GitHub
parent c731a5b628
commit b2c5af457e
210 changed files with 239 additions and 206 deletions

View File

@ -0,0 +1,154 @@
use crate::context::CommandRegistry;
use derive_new::new;
use nu_parser::ExpandContext;
use nu_source::{HasSpan, Text};
use rustyline::completion::{Completer, FilenameCompleter};
use std::path::PathBuf;
#[derive(new)]
pub(crate) struct NuCompleter {
pub file_completer: FilenameCompleter,
pub commands: CommandRegistry,
pub homedir: Option<PathBuf>,
}
impl NuCompleter {
pub fn complete(
&self,
line: &str,
pos: usize,
context: &rustyline::Context,
) -> rustyline::Result<(usize, Vec<rustyline::completion::Pair>)> {
let text = Text::from(line);
let expand_context =
ExpandContext::new(Box::new(self.commands.clone()), &text, self.homedir.clone());
#[allow(unused)]
// smarter completions
let shapes = nu_parser::pipeline_shapes(line, expand_context);
let commands: Vec<String> = self.commands.names();
let line_chars: Vec<_> = line[..pos].chars().collect();
let mut replace_pos = line_chars.len();
while replace_pos > 0 {
if line_chars[replace_pos - 1] == ' ' {
break;
}
replace_pos -= 1;
}
let mut completions;
// See if we're a flag
if pos > 0 && replace_pos < line_chars.len() && line_chars[replace_pos] == '-' {
completions = self.get_matching_arguments(&line_chars, line, replace_pos, pos);
} else {
completions = self.file_completer.complete(line, pos, context)?.1;
for completion in &mut completions {
if completion.replacement.contains("\\ ") {
completion.replacement = completion.replacement.replace("\\ ", " ");
}
if completion.replacement.contains("\\(") {
completion.replacement = completion.replacement.replace("\\(", "(");
}
if completion.replacement.contains(' ') || completion.replacement.contains('(') {
if !completion.replacement.starts_with('\"') {
completion.replacement = format!("\"{}", completion.replacement);
}
if !completion.replacement.ends_with('\"') {
completion.replacement = format!("{}\"", completion.replacement);
}
}
}
};
for command in commands.iter() {
let mut pos = replace_pos;
let mut matched = true;
if pos < line_chars.len() {
for chr in command.chars() {
if line_chars[pos] != chr {
matched = false;
break;
}
pos += 1;
if pos == line_chars.len() {
break;
}
}
}
if matched {
completions.push(rustyline::completion::Pair {
display: command.clone(),
replacement: command.clone(),
});
}
}
Ok((replace_pos, completions))
}
fn get_matching_arguments(
&self,
line_chars: &[char],
line: &str,
replace_pos: usize,
pos: usize,
) -> Vec<rustyline::completion::Pair> {
let mut matching_arguments = vec![];
let mut line_copy = line.to_string();
let substring = line_chars[replace_pos..pos].iter().collect::<String>();
let replace_string = (replace_pos..pos).map(|_| " ").collect::<String>();
line_copy.replace_range(replace_pos..pos, &replace_string);
if let Ok(val) = nu_parser::parse(&line_copy) {
let source = Text::from(line);
let pipeline_list = vec![val.clone()];
let expand_context = nu_parser::ExpandContext {
homedir: None,
registry: Box::new(self.commands.clone()),
source: &source,
};
let mut iterator =
nu_parser::TokensIterator::new(&pipeline_list, expand_context, val.span());
let result = iterator.expand_infallible(nu_parser::PipelineShape);
if result.failed.is_none() {
for command in result.commands.list {
if let nu_parser::ClassifiedCommand::Internal(nu_parser::InternalCommand {
args,
..
}) = command
{
if replace_pos >= args.span.start() && replace_pos <= args.span.end() {
if let Some(named) = args.named {
for (name, _) in named.iter() {
let full_flag = format!("--{}", name);
if full_flag.starts_with(&substring) {
matching_arguments.push(rustyline::completion::Pair {
display: full_flag.clone(),
replacement: full_flag,
});
}
}
}
}
}
}
}
}
matching_arguments
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,261 @@
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
use crate::commands::cp::CopyArgs;
use crate::commands::ls::LsArgs;
use crate::commands::mkdir::MkdirArgs;
use crate::commands::mv::MoveArgs;
use crate::commands::rm::RemoveArgs;
use crate::data::command_dict;
use crate::prelude::*;
use crate::shell::shell::Shell;
use nu_errors::ShellError;
use nu_parser::ExpandContext;
use nu_protocol::{
Primitive, ReturnSuccess, ShellTypeName, TaggedDictBuilder, UntaggedValue, Value,
};
use std::ffi::OsStr;
use std::path::PathBuf;
#[derive(Clone, Debug)]
pub struct HelpShell {
pub(crate) path: String,
pub(crate) value: Value,
}
impl HelpShell {
pub fn index(registry: &CommandRegistry) -> Result<HelpShell, ShellError> {
let mut cmds = TaggedDictBuilder::new(Tag::unknown());
let mut specs = Vec::new();
for cmd in registry.names() {
if let Some(cmd_value) = registry.get_command(&cmd) {
let mut spec = TaggedDictBuilder::new(Tag::unknown());
let value = command_dict(cmd_value, Tag::unknown());
spec.insert_untagged("name", cmd);
spec.insert_untagged(
"description",
value
.get_data_by_key("usage".spanned_unknown())
.ok_or_else(|| {
ShellError::untagged_runtime_error(
"Internal error: expected to find usage",
)
})?
.as_string()?,
);
spec.insert_value("details", value);
specs.push(spec.into_value());
} else {
}
}
cmds.insert_untagged("help", UntaggedValue::Table(specs));
Ok(HelpShell {
path: "/help".to_string(),
value: cmds.into_value(),
})
}
pub fn for_command(cmd: Value, registry: &CommandRegistry) -> Result<HelpShell, ShellError> {
let mut sh = HelpShell::index(&registry)?;
if let Value {
value: UntaggedValue::Primitive(Primitive::String(name)),
..
} = cmd
{
sh.set_path(format!("/help/{:}/details", name));
}
Ok(sh)
}
fn commands(&self) -> VecDeque<Value> {
let mut cmds = VecDeque::new();
let full_path = PathBuf::from(&self.path);
let mut viewed = self.value.clone();
let sep_string = std::path::MAIN_SEPARATOR.to_string();
let sep = OsStr::new(&sep_string);
for p in full_path.iter() {
match p {
x if x == sep => {}
step => {
let step: &str = &step.to_string_lossy().to_string();
let value = viewed.get_data_by_key(step.spanned_unknown());
if let Some(v) = value {
viewed = v.clone();
}
}
}
}
match viewed {
Value {
value: UntaggedValue::Table(l),
..
} => {
for item in l {
cmds.push_back(item.clone());
}
}
x => {
cmds.push_back(x);
}
}
cmds
}
}
impl Shell for HelpShell {
fn name(&self) -> String {
let anchor_name = self.value.anchor_name();
match anchor_name {
Some(x) => format!("{{{}}}", x),
None => format!("<{}>", self.value.type_name()),
}
}
fn homedir(&self) -> Option<PathBuf> {
dirs::home_dir()
}
fn path(&self) -> String {
self.path.clone()
}
fn pwd(&self, _: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::empty())
}
fn set_path(&mut self, path: String) {
let _ = std::env::set_current_dir(&path);
self.path = path;
}
fn ls(
&self,
_args: LsArgs,
_context: &RunnablePerItemContext,
) -> Result<OutputStream, ShellError> {
let output = self
.commands()
.into_iter()
.map(ReturnSuccess::value)
.collect::<VecDeque<_>>();
Ok(output.into())
}
fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
let path = match args.nth(0) {
None => "/".to_string(),
Some(v) => {
let target = v.as_path()?;
let mut cwd = PathBuf::from(&self.path);
if target == PathBuf::from("..") {
cwd.pop();
} else {
match target.to_str() {
Some(target) => match target.chars().nth(0) {
Some(x) if x == '/' => cwd = PathBuf::from(target),
_ => cwd.push(target),
},
None => cwd.push(target),
}
}
cwd.to_string_lossy().to_string()
}
};
let mut stream = VecDeque::new();
stream.push_back(ReturnSuccess::change_cwd(path));
Ok(stream.into())
}
fn cp(&self, _args: CopyArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
Ok(OutputStream::empty())
}
fn mv(&self, _args: MoveArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
Ok(OutputStream::empty())
}
fn mkdir(&self, _args: MkdirArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
Ok(OutputStream::empty())
}
fn rm(&self, _args: RemoveArgs, _name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
Ok(OutputStream::empty())
}
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> {
let mut completions = vec![];
let mut possible_completion = vec![];
let commands = self.commands();
for cmd in commands {
match cmd {
Value { value, .. } => {
for desc in value.data_descriptors() {
possible_completion.push(desc);
}
}
}
}
let line_chars: Vec<_> = line.chars().collect();
let mut replace_pos = pos;
while replace_pos > 0 {
if line_chars[replace_pos - 1] == ' ' {
break;
}
replace_pos -= 1;
}
for command in possible_completion.iter() {
let mut pos = replace_pos;
let mut matched = true;
if pos < line_chars.len() {
for chr in command.chars() {
if line_chars[pos] != chr {
matched = false;
break;
}
pos += 1;
if pos == line_chars.len() {
break;
}
}
}
if matched {
completions.push(rustyline::completion::Pair {
display: command.to_string(),
replacement: command.to_string(),
});
}
}
Ok((replace_pos, completions))
}
fn hint(
&self,
_line: &str,
_pos: usize,
_ctx: &rustyline::Context<'_>,
_context: ExpandContext,
) -> Option<String> {
None
}
}

View File

@ -0,0 +1,210 @@
use crate::context::Context;
use ansi_term::{Color, Style};
use log::log_enabled;
use nu_parser::{FlatShape, PipelineShape, ShapeResult, Token, TokensIterator};
use nu_protocol::{errln, outln};
use nu_source::{nom_input, HasSpan, Tag, Tagged, Text};
use rustyline::completion::Completer;
use rustyline::error::ReadlineError;
use rustyline::highlight::Highlighter;
use rustyline::hint::Hinter;
use std::borrow::Cow::{self, Owned};
pub(crate) struct Helper {
context: Context,
pub colored_prompt: String,
}
impl Helper {
pub(crate) fn new(context: Context) -> Helper {
Helper {
context,
colored_prompt: String::new(),
}
}
}
impl Completer for Helper {
type Candidate = rustyline::completion::Pair;
fn complete(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<rustyline::completion::Pair>), ReadlineError> {
self.context.shell_manager.complete(line, pos, ctx)
}
}
impl Hinter for Helper {
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
let text = Text::from(line);
self.context
.shell_manager
.hint(line, pos, ctx, self.context.expand_context(&text))
}
}
impl Highlighter for Helper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
) -> Cow<'b, str> {
use std::borrow::Cow::Borrowed;
if default {
Borrowed(&self.colored_prompt)
} else {
Borrowed(prompt)
}
}
fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
Owned("\x1b[1m".to_owned() + hint + "\x1b[m")
}
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
let tokens = nu_parser::pipeline(nom_input(line));
match tokens {
Err(_) => Cow::Borrowed(line),
Ok((_rest, v)) => {
let pipeline = match v.as_pipeline() {
Err(_) => return Cow::Borrowed(line),
Ok(v) => v,
};
let text = Text::from(line);
let expand_context = self.context.expand_context(&text);
let tokens = vec![Token::Pipeline(pipeline).into_spanned(v.span())];
let mut tokens = TokensIterator::new(&tokens[..], expand_context, v.span());
let shapes = {
// We just constructed a token list that only contains a pipeline, so it can't fail
let result = tokens.expand_infallible(PipelineShape);
if let Some(failure) = result.failed {
errln!(
"BUG: PipelineShape didn't find a pipeline :: {:#?}",
failure
);
}
tokens.finish_tracer();
tokens.state().shapes()
};
if log_enabled!(target: "nu::expand_syntax", log::Level::Debug) {
outln!("");
let _ =
ptree::print_tree(&tokens.expand_tracer().clone().print(Text::from(line)));
outln!("");
}
let mut painter = Painter::new();
for shape in shapes {
painter.paint_shape(&shape, line);
}
Cow::Owned(painter.into_string())
}
}
}
fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
true
}
}
#[allow(unused)]
fn vec_tag<T>(input: Vec<Tagged<T>>) -> Option<Tag> {
let mut iter = input.iter();
let first = iter.next()?.tag.clone();
let last = iter.last();
Some(match last {
None => first,
Some(last) => first.until(&last.tag),
})
}
struct Painter {
current: Style,
buffer: String,
}
impl Painter {
fn new() -> Painter {
Painter {
current: Style::default(),
buffer: String::new(),
}
}
fn into_string(self) -> String {
self.buffer
}
fn paint_shape(&mut self, shape: &ShapeResult, line: &str) {
let style = match &shape {
ShapeResult::Success(shape) => match shape.item {
FlatShape::OpenDelimiter(_) => Color::White.normal(),
FlatShape::CloseDelimiter(_) => Color::White.normal(),
FlatShape::ItVariable | FlatShape::Keyword => Color::Purple.bold(),
FlatShape::Variable | FlatShape::Identifier => Color::Purple.normal(),
FlatShape::Type => Color::Blue.bold(),
FlatShape::CompareOperator => Color::Yellow.normal(),
FlatShape::DotDot => Color::Yellow.bold(),
FlatShape::Dot => Style::new().fg(Color::White),
FlatShape::InternalCommand => Color::Cyan.bold(),
FlatShape::ExternalCommand => Color::Cyan.normal(),
FlatShape::ExternalWord => Color::Green.bold(),
FlatShape::BareMember => Color::Yellow.bold(),
FlatShape::StringMember => Color::Yellow.bold(),
FlatShape::String => Color::Green.normal(),
FlatShape::Path => Color::Cyan.normal(),
FlatShape::GlobPattern => Color::Cyan.bold(),
FlatShape::Word => Color::Green.normal(),
FlatShape::Pipe => Color::Purple.bold(),
FlatShape::Flag => Color::Blue.bold(),
FlatShape::ShorthandFlag => Color::Blue.bold(),
FlatShape::Int => Color::Purple.bold(),
FlatShape::Decimal => Color::Purple.bold(),
FlatShape::Whitespace | FlatShape::Separator => Color::White.normal(),
FlatShape::Comment => Color::Green.bold(),
FlatShape::Garbage => Style::new().fg(Color::White).on(Color::Red),
FlatShape::Size { number, unit } => {
let number = number.slice(line);
let unit = unit.slice(line);
self.paint(Color::Purple.bold(), number);
self.paint(Color::Cyan.bold(), unit);
return;
}
},
ShapeResult::Fallback { shape, .. } => match shape.item {
FlatShape::Whitespace | FlatShape::Separator => Color::White.normal(),
_ => Style::new().fg(Color::White).on(Color::Red),
},
};
self.paint(style, shape.span().slice(line));
}
fn paint(&mut self, style: Style, body: &str) {
let infix = self.current.infix(style);
self.current = style;
self.buffer
.push_str(&format!("{}{}", infix, style.paint(body)));
}
}
impl rustyline::Helper for Helper {}
// Use default validator for normal single line behaviour
// In the future we can implement this for custom multi-line support
impl rustyline::validate::Validator for Helper {}

View File

@ -0,0 +1,45 @@
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
use crate::commands::cp::CopyArgs;
use crate::commands::ls::LsArgs;
use crate::commands::mkdir::MkdirArgs;
use crate::commands::mv::MoveArgs;
use crate::commands::rm::RemoveArgs;
use crate::prelude::*;
use crate::stream::OutputStream;
use nu_errors::ShellError;
use nu_parser::ExpandContext;
use std::path::PathBuf;
pub trait Shell: std::fmt::Debug {
fn name(&self) -> String;
fn homedir(&self) -> Option<PathBuf>;
fn ls(
&self,
args: LsArgs,
context: &RunnablePerItemContext,
) -> Result<OutputStream, ShellError>;
fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>;
fn cp(&self, args: CopyArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
fn mkdir(&self, args: MkdirArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
fn mv(&self, args: MoveArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
fn rm(&self, args: RemoveArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
fn path(&self) -> String;
fn pwd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>;
fn set_path(&mut self, path: String);
fn complete(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError>;
fn hint(
&self,
_line: &str,
_pos: usize,
_ctx: &rustyline::Context<'_>,
_context: ExpandContext,
) -> Option<String>;
}

View File

@ -0,0 +1,194 @@
use crate::commands::command::{EvaluatedWholeStreamCommandArgs, RunnablePerItemContext};
use crate::commands::cp::CopyArgs;
use crate::commands::ls::LsArgs;
use crate::commands::mkdir::MkdirArgs;
use crate::commands::mv::MoveArgs;
use crate::commands::rm::RemoveArgs;
use crate::prelude::*;
use crate::shell::filesystem_shell::FilesystemShell;
use crate::shell::shell::Shell;
use crate::stream::OutputStream;
use nu_errors::ShellError;
use nu_parser::ExpandContext;
use parking_lot::Mutex;
use std::error::Error;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct ShellManager {
pub(crate) current_shell: Arc<AtomicUsize>,
pub(crate) shells: Arc<Mutex<Vec<Box<dyn Shell + Send>>>>,
}
impl ShellManager {
pub fn basic(commands: CommandRegistry) -> Result<ShellManager, Box<dyn Error>> {
Ok(ShellManager {
current_shell: Arc::new(AtomicUsize::new(0)),
shells: Arc::new(Mutex::new(vec![Box::new(FilesystemShell::basic(
commands,
)?)])),
})
}
pub fn insert_at_current(&mut self, shell: Box<dyn Shell + Send>) {
self.shells.lock().push(shell);
self.current_shell
.store(self.shells.lock().len() - 1, Ordering::SeqCst);
self.set_path(self.path());
}
pub fn current_shell(&self) -> usize {
self.current_shell.load(Ordering::SeqCst)
}
pub fn remove_at_current(&mut self) {
{
let mut shells = self.shells.lock();
if shells.len() > 0 {
if self.current_shell() == shells.len() - 1 {
shells.pop();
let new_len = shells.len();
if new_len > 0 {
self.current_shell.store(new_len - 1, Ordering::SeqCst);
} else {
return;
}
} else {
shells.remove(self.current_shell());
}
}
}
self.set_path(self.path())
}
pub fn is_empty(&self) -> bool {
self.shells.lock().is_empty()
}
pub fn path(&self) -> String {
self.shells.lock()[self.current_shell()].path()
}
pub fn pwd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
let env = self.shells.lock();
env[self.current_shell()].pwd(args)
}
pub fn set_path(&mut self, path: String) {
self.shells.lock()[self.current_shell()].set_path(path)
}
pub fn complete(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> {
self.shells.lock()[self.current_shell()].complete(line, pos, ctx)
}
pub fn hint(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
context: ExpandContext,
) -> Option<String> {
self.shells.lock()[self.current_shell()].hint(line, pos, ctx, context)
}
pub fn next(&mut self) {
{
let shell_len = self.shells.lock().len();
if self.current_shell() == (shell_len - 1) {
self.current_shell.store(0, Ordering::SeqCst);
} else {
self.current_shell
.store(self.current_shell() + 1, Ordering::SeqCst);
}
}
self.set_path(self.path())
}
pub fn prev(&mut self) {
{
let shell_len = self.shells.lock().len();
if self.current_shell() == 0 {
self.current_shell.store(shell_len - 1, Ordering::SeqCst);
} else {
self.current_shell
.store(self.current_shell() - 1, Ordering::SeqCst);
}
}
self.set_path(self.path())
}
pub fn homedir(&self) -> Option<PathBuf> {
let env = self.shells.lock();
env[self.current_shell()].homedir()
}
pub fn ls(
&self,
args: LsArgs,
context: &RunnablePerItemContext,
) -> Result<OutputStream, ShellError> {
let env = self.shells.lock();
env[self.current_shell()].ls(args, context)
}
pub fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
let env = self.shells.lock();
env[self.current_shell()].cd(args)
}
pub fn cp(
&self,
args: CopyArgs,
context: &RunnablePerItemContext,
) -> Result<OutputStream, ShellError> {
let shells = self.shells.lock();
let path = shells[self.current_shell()].path();
shells[self.current_shell()].cp(args, context.name.clone(), &path)
}
pub fn rm(
&self,
args: RemoveArgs,
context: &RunnablePerItemContext,
) -> Result<OutputStream, ShellError> {
let shells = self.shells.lock();
let path = shells[self.current_shell()].path();
shells[self.current_shell()].rm(args, context.name.clone(), &path)
}
pub fn mkdir(
&self,
args: MkdirArgs,
context: &RunnablePerItemContext,
) -> Result<OutputStream, ShellError> {
let shells = self.shells.lock();
let path = shells[self.current_shell()].path();
shells[self.current_shell()].mkdir(args, context.name.clone(), &path)
}
pub fn mv(
&self,
args: MoveArgs,
context: &RunnablePerItemContext,
) -> Result<OutputStream, ShellError> {
let shells = self.shells.lock();
let path = shells[self.current_shell()].path();
shells[self.current_shell()].mv(args, context.name.clone(), &path)
}
}

View File

@ -0,0 +1,295 @@
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
use crate::commands::cp::CopyArgs;
use crate::commands::ls::LsArgs;
use crate::commands::mkdir::MkdirArgs;
use crate::commands::mv::MoveArgs;
use crate::commands::rm::RemoveArgs;
use crate::prelude::*;
use crate::shell::shell::Shell;
use crate::utils::ValueStructure;
use nu_errors::ShellError;
use nu_parser::ExpandContext;
use nu_protocol::{ReturnSuccess, ShellTypeName, UntaggedValue, Value};
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
#[derive(Clone)]
pub struct ValueShell {
pub(crate) path: String,
pub(crate) last_path: String,
pub(crate) value: Value,
}
impl std::fmt::Debug for ValueShell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ValueShell @ {}", self.path)
}
}
impl ValueShell {
pub fn new(value: Value) -> ValueShell {
ValueShell {
path: "/".to_string(),
last_path: "/".to_string(),
value,
}
}
fn members_under(&self, path: &Path) -> VecDeque<Value> {
let mut shell_entries = VecDeque::new();
let full_path = path.to_path_buf();
let mut viewed = self.value.clone();
let sep_string = std::path::MAIN_SEPARATOR.to_string();
let sep = OsStr::new(&sep_string);
for p in full_path.iter() {
match p {
x if x == sep => {}
step => {
let name: &str = &step.to_string_lossy().to_string();
let value = viewed.get_data_by_key(name.spanned_unknown());
if let Some(v) = value {
viewed = v.clone();
}
}
}
}
match viewed {
Value {
value: UntaggedValue::Table(l),
..
} => {
for item in l {
shell_entries.push_back(item.clone());
}
}
x => {
shell_entries.push_back(x);
}
}
shell_entries
}
fn members(&self) -> VecDeque<Value> {
self.members_under(Path::new("."))
}
}
impl Shell for ValueShell {
fn name(&self) -> String {
let anchor_name = self.value.anchor_name();
match anchor_name {
Some(x) => format!("{{{}}}", x),
None => format!("<{}>", self.value.type_name()),
}
}
fn homedir(&self) -> Option<PathBuf> {
Some(PathBuf::from("/"))
}
fn ls(
&self,
LsArgs { path, .. }: LsArgs,
context: &RunnablePerItemContext,
) -> Result<OutputStream, ShellError> {
let mut full_path = PathBuf::from(self.path());
let name_tag = context.name.clone();
if let Some(value) = &path {
full_path.push(value.as_ref());
}
let mut value_system = ValueStructure::new();
value_system.walk_decorate(&self.value)?;
if !value_system.exists(&full_path) {
if let Some(target) = &path {
return Err(ShellError::labeled_error(
"Can not list entries inside",
"No such path exists",
target.tag(),
));
}
return Err(ShellError::labeled_error(
"Can not list entries inside",
"No such path exists",
name_tag,
));
}
let output = self
.members_under(full_path.as_path())
.into_iter()
.map(ReturnSuccess::value)
.collect::<VecDeque<_>>();
Ok(output.into())
}
fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
let destination = args.nth(0);
let path = match destination {
None => "/".to_string(),
Some(v) => {
let target = v.as_path()?;
let mut cwd = PathBuf::from(&self.path);
if target == PathBuf::from("..") {
cwd.pop();
} else if target == PathBuf::from("-") {
cwd = PathBuf::from(&self.last_path);
} else {
match target.to_str() {
Some(target) => match target.chars().nth(0) {
Some(x) if x == '/' => cwd = PathBuf::from(target),
_ => cwd.push(target),
},
None => cwd.push(target),
}
}
cwd.to_string_lossy().to_string()
}
};
let mut value_system = ValueStructure::new();
value_system.walk_decorate(&self.value)?;
if !value_system.exists(&PathBuf::from(&path)) {
if let Some(destination) = destination {
return Err(ShellError::labeled_error(
"Can not change to path inside",
"No such path exists",
destination.tag(),
));
}
return Err(ShellError::labeled_error(
"Can not change to path inside",
"No such path exists",
&args.call_info.name_tag,
));
}
let mut stream = VecDeque::new();
stream.push_back(ReturnSuccess::change_cwd(path));
Ok(stream.into())
}
fn cp(&self, _args: CopyArgs, name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
Err(ShellError::labeled_error(
"cp not currently supported on values",
"not currently supported",
name,
))
}
fn mv(&self, _args: MoveArgs, name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
Err(ShellError::labeled_error(
"mv not currently supported on values",
"not currently supported",
name,
))
}
fn mkdir(&self, _args: MkdirArgs, name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
Err(ShellError::labeled_error(
"mkdir not currently supported on values",
"not currently supported",
name,
))
}
fn rm(&self, _args: RemoveArgs, name: Tag, _path: &str) -> Result<OutputStream, ShellError> {
Err(ShellError::labeled_error(
"rm not currently supported on values",
"not currently supported",
name,
))
}
fn path(&self) -> String {
self.path.clone()
}
fn pwd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError> {
let mut stream = VecDeque::new();
stream.push_back(ReturnSuccess::value(
UntaggedValue::string(self.path()).into_value(&args.call_info.name_tag),
));
Ok(stream.into())
}
fn set_path(&mut self, path: String) {
self.last_path = self.path.clone();
self.path = path;
}
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> {
let mut completions = vec![];
let mut possible_completion = vec![];
let members = self.members();
for member in members {
match member {
Value { value, .. } => {
for desc in value.data_descriptors() {
possible_completion.push(desc);
}
}
}
}
let line_chars: Vec<_> = line.chars().collect();
let mut replace_pos = pos;
while replace_pos > 0 {
if line_chars[replace_pos - 1] == ' ' {
break;
}
replace_pos -= 1;
}
for command in possible_completion.iter() {
let mut pos = replace_pos;
let mut matched = true;
if pos < line_chars.len() {
for chr in command.chars() {
if line_chars[pos] != chr {
matched = false;
break;
}
pos += 1;
if pos == line_chars.len() {
break;
}
}
}
if matched {
completions.push(rustyline::completion::Pair {
display: command.to_string(),
replacement: command.to_string(),
});
}
}
Ok((replace_pos, completions))
}
fn hint(
&self,
_line: &str,
_pos: usize,
_ctx: &rustyline::Context<'_>,
_context: ExpandContext,
) -> Option<String> {
None
}
}