diff --git a/history.txt b/history.txt deleted file mode 100644 index 5b5eb51df6..0000000000 --- a/history.txt +++ /dev/null @@ -1,100 +0,0 @@ -cargo install cargo-edit -ls -ls | foo | bar -ls -ls | ap -ls ab cd -ls ab cd | de -ls ab cd | grep 1243 -ls ab cd|grep 1243 -ls -hello world -hello world | zomg -ls -ls | zomgt -ls | zomg -ls soms -ls something -ls something | grep -dir -npm --help -cd target -dir -ls -cd target -cd .. -dir -cd target -cd .. -cd target -cd .. -ls -dir -cd target -ls -cd .. -ls -cd target -cd .. -cd target -cd .. -cd target -cd .. -cd target -cd .. -cd target -cd .. -cd target -cd .. -cd target -cd .. -ls -cd target -cd .. -cargo build -cargo build --verbose -ls -cargo -cargo build -cargo run -git status -git add . -git commit -git push -git status -git add . -git commit -git push -git config --global core.autocrlf input -git config -ls -cd target -ls -cd target -cd .. -cd target -ls -git status -git add . -git commit -git push -cd target -cd .. -exit -ls -ps -to-array -exit -ls -dir -ls -cd target -cd .. -ps -ls -dir -ls -ls | to-array -ls | format -ls | to-array | format -git status diff --git a/src/commands.rs b/src/commands.rs index c3f07e0da9..93ef28026b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -3,6 +3,8 @@ crate mod cd; crate mod command; crate mod ls; crate mod ps; +crate mod take; crate mod to_array; crate use command::Command; +crate use to_array::to_array; diff --git a/src/commands/command.rs b/src/commands/command.rs index f259901a04..753135f869 100644 --- a/src/commands/command.rs +++ b/src/commands/command.rs @@ -12,10 +12,12 @@ pub trait CommandBlueprint { ) -> Result, ShellError>; } +#[derive(Debug)] pub enum CommandAction { ChangeCwd(PathBuf), } +#[derive(Debug)] pub enum ReturnValue { Value(Value), Action(CommandAction), diff --git a/src/commands/ps.rs b/src/commands/ps.rs index 559f979c5e..58c06c66be 100644 --- a/src/commands/ps.rs +++ b/src/commands/ps.rs @@ -9,9 +9,7 @@ use std::rc::Rc; use sysinfo::SystemExt; #[derive(new)] -pub struct PsBlueprint { - system: Rc>, -} +pub struct PsBlueprint; impl crate::CommandBlueprint for PsBlueprint { fn create( @@ -20,18 +18,16 @@ impl crate::CommandBlueprint for PsBlueprint { host: &dyn crate::Host, env: &mut crate::Environment, ) -> Result, ShellError> { - Ok(Box::new(Ps::new(self.system.clone()))) + Ok(Box::new(Ps::new())) } } #[derive(new)] -pub struct Ps { - system: Rc>, -} +pub struct Ps; impl crate::Command for Ps { fn run(&mut self, stream: VecDeque) -> Result, ShellError> { - let mut system = self.system.borrow_mut(); + let mut system = sysinfo::System::new(); system.refresh_all(); let list = system.get_process_list(); @@ -41,7 +37,6 @@ impl crate::Command for Ps { .map(|(_, process)| { ReturnValue::Value(Value::Object(Box::new(Process::new(process.clone())))) }) - .take(5) .collect::>(); Ok(list) diff --git a/src/commands/take.rs b/src/commands/take.rs new file mode 100644 index 0000000000..669b37e323 --- /dev/null +++ b/src/commands/take.rs @@ -0,0 +1,51 @@ +use crate::errors::ShellError; +use crate::object::process::Process; +use crate::object::{DirEntry, ShellObject, Value}; +use crate::prelude::*; +use crate::Args; +use derive_new::new; +use std::path::{Path, PathBuf}; +use sysinfo::SystemExt; + +#[derive(new)] +pub struct TakeBlueprint; + +impl crate::CommandBlueprint for TakeBlueprint { + fn create( + &self, + args: Vec, + host: &dyn Host, + env: &mut Environment, + ) -> Result, ShellError> { + if args.is_empty() { + return Err(ShellError::string("take requires an integer")); + } + + let amount = args[0].as_int()?; + + Ok(Box::new(Take { amount })) + } +} + +#[derive(new)] +pub struct Take { + amount: i64, +} + +impl crate::Command for Take { + fn run(&mut self, stream: VecDeque) -> Result, ShellError> { + let amount = if stream.len() > self.amount as usize { + self.amount as usize + } else { + stream.len() + }; + + let out: VecDeque = stream + .into_iter() + .take(amount) + .map(|v| ReturnValue::Value(v)) + .collect(); + + Ok(out) + } +} diff --git a/src/commands/to_array.rs b/src/commands/to_array.rs index fd02921e89..e2c9f2de8c 100644 --- a/src/commands/to_array.rs +++ b/src/commands/to_array.rs @@ -30,3 +30,10 @@ impl crate::Command for ToArray { Ok(ReturnValue::single(Value::List(out))) } } + +crate fn to_array(stream: VecDeque) -> VecDeque { + let out = Value::List(stream.into_iter().collect()); + let mut stream = VecDeque::new(); + stream.push_back(out); + stream +} diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000000..9bafe5c0ee --- /dev/null +++ b/src/context.rs @@ -0,0 +1,46 @@ +use crate::prelude::*; +use std::collections::BTreeMap; +use std::error::Error; + +pub type Commands = BTreeMap>; + +pub struct Context { + commands: BTreeMap>, + crate host: Box, + crate env: Environment, +} + +impl Context { + crate fn basic() -> Result> { + Ok(Context { + commands: BTreeMap::new(), + host: Box::new(crate::env::host::BasicHost), + env: crate::Environment::basic()?, + }) + } + + pub fn add_commands(&mut self, commands: Vec<(&str, Box)>) { + for (name, command) in commands { + self.commands.insert(name.to_string(), command); + } + } + + crate fn get_command(&mut self, name: &str) -> Option<&dyn crate::CommandBlueprint> { + self.commands.get(name).map(|c| &**c) + } + + crate fn has_command(&mut self, name: &str) -> bool { + self.commands.contains_key(name) + } + + crate fn create_command( + &mut self, + name: &str, + arg_list: Vec, + ) -> Result, ShellError> { + match self.commands.get(name) { + Some(command) => Ok(command.create(arg_list, &self.host, &mut self.env)?), + None => Err(ShellError::string(format!("Missing command {}", name))), + } + } +} diff --git a/src/format.rs b/src/format.rs index 7bb02b50ac..a946833bc0 100644 --- a/src/format.rs +++ b/src/format.rs @@ -6,7 +6,7 @@ crate mod table; use crate::object::Value; use crate::prelude::*; -crate use entries::EntriesView; +crate use entries::{EntriesListView, EntriesView}; crate use generic::GenericView; crate use list::ListView; crate use table::TableView; diff --git a/src/format/entries.rs b/src/format/entries.rs index e48bade774..99816b1fdb 100644 --- a/src/format/entries.rs +++ b/src/format/entries.rs @@ -1,5 +1,8 @@ use crate::format::RenderView; +use crate::object::base::ToEntriesView; +use crate::prelude::*; use crate::Host; + use derive_new::new; // An entries list is printed like this: @@ -26,3 +29,38 @@ impl RenderView for EntriesView { .collect() } } + +pub struct EntriesListView { + values: VecDeque, +} + +impl EntriesListView { + crate fn from_stream(values: VecDeque) -> EntriesListView { + EntriesListView { values } + } +} + +impl RenderView for EntriesListView { + fn render_view(&self, host: &dyn Host) -> Vec { + if self.values.len() == 0 { + return vec![]; + } + + let mut strings = vec![]; + + let last = self.values.len() - 1; + + for (i, item) in self.values.iter().enumerate() { + let view = item.to_entries_view(); + let out = view.render_view(host); + + strings.extend(out); + + if i != last { + strings.push("\n".to_string()); + } + } + + strings + } +} diff --git a/src/main.rs b/src/main.rs index 92cde65cb9..2d56a087b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ #![allow(unused)] mod commands; +mod context; mod env; mod errors; mod format; @@ -13,11 +14,12 @@ mod prelude; crate use crate::commands::args::{Args, Streams}; use crate::commands::command::ReturnValue; crate use crate::commands::command::{Command, CommandAction, CommandBlueprint}; +use crate::context::Context; crate use crate::env::{Environment, Host}; crate use crate::errors::ShellError; -crate use crate::format::RenderView; +crate use crate::format::{EntriesListView, RenderView}; use crate::object::base::{ToEntriesView, ToGenericView}; -use crate::object::Value; +use crate::object::{ShellObject, Value}; use ansi_term::Color; use conch_parser::lexer::Lexer; @@ -29,6 +31,7 @@ use std::collections::BTreeMap; use std::collections::VecDeque; use std::error::Error; use std::rc::Rc; +use std::sync::{Arc, Mutex}; use subprocess::Exec; use sysinfo::{self, SystemExt}; @@ -47,51 +50,30 @@ impl MaybeOwned<'a, T> { } } -type Commands = BTreeMap>; - -struct Context { - commands: BTreeMap>, - host: Box, - env: Environment, -} - -impl Context { - fn basic() -> Result> { - Ok(Context { - commands: BTreeMap::new(), - host: Box::new(crate::env::host::BasicHost), - env: crate::Environment::basic()?, - }) - } -} - fn main() -> Result<(), Box> { let mut rl = Editor::<()>::new(); if rl.load_history("history.txt").is_err() { println!("No previous history."); } - let mut context = Context::basic()?; + let mut context = Arc::new(Mutex::new(Context::basic()?)); - // let mut commands = BTreeMap::>::new(); + { + use crate::commands::*; - let mut system = Rc::new(RefCell::new(sysinfo::System::new())); - let mut ps = crate::commands::ps::PsBlueprint::new(system); - let mut ls = crate::commands::ls::LsBlueprint; - let mut cd = crate::commands::cd::CdBlueprint; - let mut to_array = crate::commands::to_array::ToArrayBlueprint; - - context.commands.insert("ps".to_string(), Box::new(ps)); - context.commands.insert("ls".to_string(), Box::new(ls)); - context.commands.insert("cd".to_string(), Box::new(cd)); - context - .commands - .insert("to-array".to_string(), Box::new(to_array)); + context.lock().unwrap().add_commands(vec![ + ("ps", Box::new(ps::PsBlueprint)), + ("ls", Box::new(ls::LsBlueprint)), + ("cd", Box::new(cd::CdBlueprint)), + ("take", Box::new(take::TakeBlueprint)), + ("to-array", Box::new(to_array::ToArrayBlueprint)), + ]); + } loop { let readline = rl.readline(&format!( "{}> ", - Color::Green.paint(context.env.cwd().display().to_string()) + Color::Green.paint(context.lock().unwrap().env.cwd().display().to_string()) )); match readline { @@ -105,16 +87,23 @@ fn main() -> Result<(), Box> { let mut input = VecDeque::new(); + let last = parsed.len() - 1; + for item in parsed { - // println!("Processing {:?}", item); input = process_command( crate::parser::print_items(&item), item.clone(), input, - &mut context, + context.clone(), )?; + } - // println!("OUTPUT: {:?}", input); + if input.len() > 0 { + if equal_shapes(&input) { + format(crate::commands::to_array(input), context.clone()); + } else { + format(input, context.clone()); + } } } Err(ReadlineError::Interrupted) => { @@ -140,31 +129,30 @@ fn process_command( line: String, parsed: Vec, input: VecDeque, - context: &mut Context, + context: Arc>, ) -> Result, ShellError> { let command = &parsed[0].name(); - let arg_list = parsed[1..] - .iter() - .map(|i| Value::string(i.name().to_string())) - .collect(); + let arg_list = parsed[1..].iter().map(|i| i.as_value()).collect(); let streams = Streams::new(); - // let args = Args::new(arg_list); + if command == &"format" { + format(input, context); - match *command { - "format" => { - for item in input { - let view = item.to_generic_view(); - crate::format::print_rendered(&view.render_view(&context.host), &mut context.host); - } + Ok(VecDeque::new()) + } else if command == &"format-list" { + let view = EntriesListView::from_stream(input); + let mut ctx = context.lock().unwrap(); - Ok(VecDeque::new()) - } + crate::format::print_rendered(&view.render_view(&ctx.host), &mut ctx.host); - command => match context.commands.get_mut(command) { - Some(command) => { - let mut instance = command.create(arg_list, &context.host, &mut context.env)?; + Ok(VecDeque::new()) + } else { + let mut ctx = context.lock().unwrap(); + + match ctx.has_command(*command) { + true => { + let mut instance = ctx.create_command(command, arg_list)?; let mut result = instance.run(input)?; let mut next = VecDeque::new(); @@ -172,7 +160,7 @@ fn process_command( for v in result { match v { ReturnValue::Action(action) => match action { - crate::CommandAction::ChangeCwd(cwd) => context.env.cwd = cwd, + crate::CommandAction::ChangeCwd(cwd) => ctx.env.cwd = cwd, }, ReturnValue::Value(v) => next.push_back(v), @@ -182,10 +170,43 @@ fn process_command( Ok(next) } - other => { - Exec::shell(line).cwd(context.env.cwd()).join().unwrap(); + false => { + Exec::shell(line).cwd(ctx.env.cwd()).join().unwrap(); Ok(VecDeque::new()) } - }, + } } } + +fn format(input: VecDeque, context: Arc>) { + let mut ctx = context.lock().unwrap(); + + let last = input.len() - 1; + for (i, item) in input.iter().enumerate() { + let view = item.to_generic_view(); + crate::format::print_rendered(&view.render_view(&ctx.host), &mut ctx.host); + + if last != i { + println!(""); + } + } +} + +fn equal_shapes(input: &VecDeque) -> bool { + let mut items = input.iter(); + + let item = match items.next() { + Some(item) => item, + None => return false, + }; + + let desc = item.data_descriptors(); + + for item in items { + if desc != item.data_descriptors() { + return false; + } + } + + true +} diff --git a/src/object/base.rs b/src/object/base.rs index 15c0bf62b4..c24bd36009 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -42,6 +42,7 @@ impl ShellObject for Value { Value::List(l) => format!("[list List]"), } } + fn data_descriptors(&self) -> Vec { match self { Value::Primitive(p) => vec![], @@ -80,6 +81,17 @@ impl Value { } } + crate fn as_int(&self) -> Result { + match self { + Value::Primitive(Primitive::Int(i)) => Ok(*i), + // TODO: this should definitely be more general with better errors + other => Err(ShellError::string(format!( + "Expected integer, got {:?}", + other + ))), + } + } + crate fn string(s: impl Into) -> Value { Value::Primitive(Primitive::String(s.into())) } @@ -115,7 +127,10 @@ pub trait ToEntriesView { fn to_entries_view(&self) -> EntriesView; } -impl ToEntriesView for ShellObject { +impl ToEntriesView for T +where + T: ShellObject, +{ fn to_entries_view(&self) -> EntriesView { let descs = self.data_descriptors(); let mut entries = vec![]; @@ -136,6 +151,19 @@ impl ToEntriesView for ShellObject { } } +impl ShellObject for Box { + fn to_shell_string(&self) -> String { + (**self).to_shell_string() + } + fn data_descriptors(&self) -> Vec { + (**self).data_descriptors() + } + + fn get_data(&'a self, desc: &DataDescriptor) -> crate::MaybeOwned<'a, Value> { + (**self).get_data(desc) + } +} + pub trait ToGenericView { fn to_generic_view(&self) -> GenericView; } diff --git a/src/object/desc.rs b/src/object/desc.rs index d20909c10d..0bce4103a6 100644 --- a/src/object/desc.rs +++ b/src/object/desc.rs @@ -1,4 +1,4 @@ -use crate::object::types::{Any, Type}; +use crate::object::types::{AnyShell, Type}; use derive_new::new; #[derive(new)] @@ -8,12 +8,18 @@ pub struct DataDescriptor { crate ty: Box, } +impl PartialEq for DataDescriptor { + fn eq(&self, other: &DataDescriptor) -> bool { + self.name == other.name && self.readonly == other.readonly && self.ty.equal(&*other.ty) + } +} + impl DataDescriptor { crate fn any(name: impl Into) -> DataDescriptor { DataDescriptor { name: name.into(), readonly: true, - ty: Box::new(Any), + ty: Box::new(AnyShell), } } } diff --git a/src/object/dict.rs b/src/object/dict.rs index 508199c6b6..faf0ff40d6 100644 --- a/src/object/dict.rs +++ b/src/object/dict.rs @@ -26,7 +26,7 @@ impl crate::object::ShellObject for Dictionary { self.entries .iter() .map(|(name, value)| { - DataDescriptor::new(name.clone(), true, Box::new(crate::object::types::Any)) + DataDescriptor::new(name.clone(), true, Box::new(crate::object::types::AnyShell)) }) .collect() } @@ -59,7 +59,7 @@ impl crate::object::ShellObject for ScopedDictionary<'parent> { self.entries .iter() .map(|(name, value)| { - DataDescriptor::new(name.clone(), true, Box::new(crate::object::types::Any)) + DataDescriptor::new(name.clone(), true, Box::new(crate::object::types::AnyShell)) }) .collect() } diff --git a/src/object/types.rs b/src/object/types.rs index 7350ac6bd9..8ff2678ec1 100644 --- a/src/object/types.rs +++ b/src/object/types.rs @@ -1,5 +1,19 @@ -pub trait Type {} +use std::any::{Any, TypeId}; -pub struct Any; +pub trait Type { + fn as_any(&self) -> &dyn Any; + fn equal(&self, other: &dyn Type) -> bool; +} -impl Type for Any {} +#[derive(Eq, PartialEq)] +pub struct AnyShell; + +impl Type for AnyShell { + fn as_any(&self) -> &dyn Any { + self as &dyn Any + } + + fn equal(&self, other: &dyn Type) -> bool { + other.as_any().is::() + } +} diff --git a/src/parser.rs b/src/parser.rs index 525e70169f..76adf67e6c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,63 +1,4 @@ -use nom::branch::alt; -use nom::bytes::complete::{escaped, is_not, tag}; -use nom::character::complete::one_of; -use nom::multi::separated_list; -use nom::sequence::{preceded, terminated}; -use nom::IResult; -use nom::{complete, named, separated_list, ws}; +crate mod completer; +crate mod parse; -#[derive(Debug, Clone)] -pub enum Item { - Quoted(String), - Bare(String), -} - -crate fn print_items(items: &[Item]) -> String { - let mut out = String::new(); - - let formatted = items.iter().map(|item| match item { - Item::Bare(s) => format!("{}", s), - Item::Quoted(s) => format!("{:?}", s), - }); - - itertools::join(formatted, " ") -} - -impl Item { - crate fn name(&self) -> &str { - match self { - Item::Quoted(s) => s, - Item::Bare(s) => s, - } - } -} - -fn esc(s: &str) -> IResult<&str, &str> { - escaped(is_not("\\\""), '\\', one_of("\"n\\"))(s) -} - -fn quoted(s: &str) -> IResult<&str, Item> { - terminated(preceded(tag("\""), esc), tag("\""))(s) - .map(|(a, b)| (a, Item::Quoted(b.to_string()))) -} - -fn unquoted(s: &str) -> IResult<&str, Item> { - is_not(" |")(s).map(|(a, b)| (a, Item::Bare(b.to_string()))) -} - -fn command_token(s: &str) -> IResult<&str, Item> { - alt((quoted, unquoted))(s) -} - -fn command_args(s: &str) -> IResult<&str, Vec> { - separated_list(tag(" "), command_token)(s) -} - -named!( - pub shell_parser(&str) -> Vec>, - complete!( - ws!( - separated_list!(tag("|"), command_args) - ) - ) -); +crate use self::parse::{print_items, shell_parser, Item}; diff --git a/src/parser/completer.rs b/src/parser/completer.rs new file mode 100644 index 0000000000..09d3162ab7 --- /dev/null +++ b/src/parser/completer.rs @@ -0,0 +1,32 @@ +use rustyline::{completion, Context}; +use std::collections::BTreeMap; + +crate struct Completer { + commands: BTreeMap>, +} + +impl completion::Completer for Completer { + type Candidate = completion::Pair; + + fn complete( + &self, + line: &str, + pos: usize, + ctx: &Context<'_>, + ) -> rustyline::Result<(usize, Vec)> { + let pairs = self + .commands + .keys() + .map(|k| completion::Pair { + display: k.clone(), + replacement: k.clone(), + }) + .collect(); + Ok((0, pairs)) + } + + fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) { + let end = line.pos(); + line.replace(start..end, elected) + } +} diff --git a/src/parser/parse.rs b/src/parser/parse.rs new file mode 100644 index 0000000000..75bddf88ee --- /dev/null +++ b/src/parser/parse.rs @@ -0,0 +1,82 @@ +use crate::prelude::*; +use nom::branch::alt; +use nom::bytes::complete::{escaped, is_a, is_not, tag}; +use nom::character::complete::one_of; +use nom::multi::separated_list; +use nom::sequence::{preceded, terminated}; +use nom::IResult; +use nom::{complete, named, separated_list, ws}; +use std::str::FromStr; + +#[derive(Debug, Clone)] +pub enum Item { + Quoted(String), + Bare(String), + Int(i64), +} + +impl Item { + crate fn as_value(&self) -> Value { + match self { + Item::Quoted(s) => Value::Primitive(Primitive::String(s.clone())), + Item::Bare(s) => Value::Primitive(Primitive::String(s.clone())), + Item::Int(i) => Value::Primitive(Primitive::Int(*i)), + } + } +} + +crate fn print_items(items: &[Item]) -> String { + let mut out = String::new(); + + let formatted = items.iter().map(|item| match item { + Item::Bare(s) => format!("{}", s), + Item::Quoted(s) => format!("{:?}", s), + Item::Int(i) => format!("{:?}", i), + }); + + itertools::join(formatted, " ") +} + +impl Item { + crate fn name(&self) -> &str { + match self { + Item::Quoted(s) => s, + Item::Bare(s) => s, + Item::Int(i) => unimplemented!(), + } + } +} + +fn esc(s: &str) -> IResult<&str, &str> { + escaped(is_not("\\\""), '\\', one_of("\"n\\"))(s) +} + +fn quoted(s: &str) -> IResult<&str, Item> { + terminated(preceded(tag("\""), esc), tag("\""))(s) + .map(|(a, b)| (a, Item::Quoted(b.to_string()))) +} + +fn unquoted(s: &str) -> IResult<&str, Item> { + is_not(" |")(s).map(|(a, b)| (a, Item::Bare(b.to_string()))) +} + +fn int(s: &str) -> IResult<&str, Item> { + is_a("1234567890")(s).map(|(a, b)| (a, Item::Int(FromStr::from_str(b).unwrap()))) +} + +fn command_token(s: &str) -> IResult<&str, Item> { + alt((int, quoted, unquoted))(s) +} + +fn command_args(s: &str) -> IResult<&str, Vec> { + separated_list(tag(" "), command_token)(s) +} + +named!( + pub shell_parser(&str) -> Vec>, + complete!( + ws!( + separated_list!(tag("|"), command_args) + ) + ) +); diff --git a/src/prelude.rs b/src/prelude.rs index ba4f2c44e5..ec0a5a1533 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,4 +3,5 @@ crate use crate::commands::command::{Command, CommandAction, CommandBlueprint, R crate use crate::env::{Environment, Host}; crate use crate::errors::ShellError; crate use crate::format::RenderView; +crate use crate::object::{Primitive, Value}; crate use std::collections::VecDeque;