Basic pipelining is working!

This commit is contained in:
Yehuda Katz 2019-05-15 11:12:38 -05:00
parent 975ff7c2fb
commit 3040638881
18 changed files with 405 additions and 239 deletions

View file

@ -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

View file

@ -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;

View file

@ -12,10 +12,12 @@ pub trait CommandBlueprint {
) -> Result<Box<dyn Command>, ShellError>;
}
#[derive(Debug)]
pub enum CommandAction {
ChangeCwd(PathBuf),
}
#[derive(Debug)]
pub enum ReturnValue {
Value(Value),
Action(CommandAction),

View file

@ -9,9 +9,7 @@ use std::rc::Rc;
use sysinfo::SystemExt;
#[derive(new)]
pub struct PsBlueprint {
system: Rc<RefCell<sysinfo::System>>,
}
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<Box<dyn Command>, ShellError> {
Ok(Box::new(Ps::new(self.system.clone())))
Ok(Box::new(Ps::new()))
}
}
#[derive(new)]
pub struct Ps {
system: Rc<RefCell<sysinfo::System>>,
}
pub struct Ps;
impl crate::Command for Ps {
fn run(&mut self, stream: VecDeque<Value>) -> Result<VecDeque<ReturnValue>, 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::<VecDeque<_>>();
Ok(list)

51
src/commands/take.rs Normal file
View file

@ -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<Value>,
host: &dyn Host,
env: &mut Environment,
) -> Result<Box<dyn Command>, 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<Value>) -> Result<VecDeque<ReturnValue>, ShellError> {
let amount = if stream.len() > self.amount as usize {
self.amount as usize
} else {
stream.len()
};
let out: VecDeque<ReturnValue> = stream
.into_iter()
.take(amount)
.map(|v| ReturnValue::Value(v))
.collect();
Ok(out)
}
}

View file

@ -30,3 +30,10 @@ impl crate::Command for ToArray {
Ok(ReturnValue::single(Value::List(out)))
}
}
crate fn to_array(stream: VecDeque<Value>) -> VecDeque<Value> {
let out = Value::List(stream.into_iter().collect());
let mut stream = VecDeque::new();
stream.push_back(out);
stream
}

46
src/context.rs Normal file
View file

@ -0,0 +1,46 @@
use crate::prelude::*;
use std::collections::BTreeMap;
use std::error::Error;
pub type Commands = BTreeMap<String, Box<dyn crate::CommandBlueprint>>;
pub struct Context {
commands: BTreeMap<String, Box<dyn crate::CommandBlueprint>>,
crate host: Box<dyn crate::Host>,
crate env: Environment,
}
impl Context {
crate fn basic() -> Result<Context, Box<Error>> {
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<dyn crate::CommandBlueprint>)>) {
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<Value>,
) -> Result<Box<dyn Command>, 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))),
}
}
}

View file

@ -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;

View file

@ -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<Value>,
}
impl EntriesListView {
crate fn from_stream(values: VecDeque<Value>) -> EntriesListView {
EntriesListView { values }
}
}
impl RenderView for EntriesListView {
fn render_view(&self, host: &dyn Host) -> Vec<String> {
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
}
}

View file

@ -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<T> MaybeOwned<'a, T> {
}
}
type Commands = BTreeMap<String, Box<dyn crate::CommandBlueprint>>;
struct Context {
commands: BTreeMap<String, Box<dyn crate::CommandBlueprint>>,
host: Box<dyn crate::Host>,
env: Environment,
}
impl Context {
fn basic() -> Result<Context, Box<Error>> {
Ok(Context {
commands: BTreeMap::new(),
host: Box::new(crate::env::host::BasicHost),
env: crate::Environment::basic()?,
})
}
}
fn main() -> Result<(), Box<Error>> {
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::<String, Box<dyn crate::CommandBlueprint>>::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<Error>> {
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<crate::parser::Item>,
input: VecDeque<Value>,
context: &mut Context,
context: Arc<Mutex<Context>>,
) -> Result<VecDeque<Value>, 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);
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);
}
if command == &"format" {
format(input, context);
Ok(VecDeque::new())
}
} else if command == &"format-list" {
let view = EntriesListView::from_stream(input);
let mut ctx = context.lock().unwrap();
command => match context.commands.get_mut(command) {
Some(command) => {
let mut instance = command.create(arg_list, &context.host, &mut context.env)?;
crate::format::print_rendered(&view.render_view(&ctx.host), &mut ctx.host);
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<Value>, context: Arc<Mutex<Context>>) {
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<Value>) -> 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
}

View file

@ -42,6 +42,7 @@ impl ShellObject for Value {
Value::List(l) => format!("[list List]"),
}
}
fn data_descriptors(&self) -> Vec<DataDescriptor> {
match self {
Value::Primitive(p) => vec![],
@ -80,6 +81,17 @@ impl Value {
}
}
crate fn as_int(&self) -> Result<i64, ShellError> {
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<String>) -> 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<T> 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<dyn ShellObject> {
fn to_shell_string(&self) -> String {
(**self).to_shell_string()
}
fn data_descriptors(&self) -> Vec<DataDescriptor> {
(**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;
}

View file

@ -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<dyn Type>,
}
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<String>) -> DataDescriptor {
DataDescriptor {
name: name.into(),
readonly: true,
ty: Box::new(Any),
ty: Box::new(AnyShell),
}
}
}

View file

@ -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()
}

View file

@ -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::<AnyShell>()
}
}

View file

@ -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<Item>> {
separated_list(tag(" "), command_token)(s)
}
named!(
pub shell_parser(&str) -> Vec<Vec<Item>>,
complete!(
ws!(
separated_list!(tag("|"), command_args)
)
)
);
crate use self::parse::{print_items, shell_parser, Item};

32
src/parser/completer.rs Normal file
View file

@ -0,0 +1,32 @@
use rustyline::{completion, Context};
use std::collections::BTreeMap;
crate struct Completer {
commands: BTreeMap<String, Box<dyn crate::CommandBlueprint>>,
}
impl completion::Completer for Completer {
type Candidate = completion::Pair;
fn complete(
&self,
line: &str,
pos: usize,
ctx: &Context<'_>,
) -> rustyline::Result<(usize, Vec<completion::Pair>)> {
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)
}
}

82
src/parser/parse.rs Normal file
View file

@ -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<Item>> {
separated_list(tag(" "), command_token)(s)
}
named!(
pub shell_parser(&str) -> Vec<Vec<Item>>,
complete!(
ws!(
separated_list!(tag("|"), command_args)
)
)
);

View file

@ -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;