diff --git a/.gitignore b/.gitignore index 53eaa21960..2714c13c06 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target **/*.rs.bk +history.txt \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 78c80a42d1..425b9a3d57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,6 +84,15 @@ dependencies = [ "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "byte-unit" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "byteorder" version = "1.3.1" @@ -109,6 +118,14 @@ dependencies = [ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "chrono-humanize" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "chrono-tz" version = "0.5.1" @@ -267,6 +284,11 @@ dependencies = [ "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "indexmap" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "itertools" version = "0.8.0" @@ -380,13 +402,16 @@ name = "objectshell" version = "0.1.0" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-unit 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "chrono-tz 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "conch-parser 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "derive-new 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "dunce 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "futures-core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "nom 5.0.0-beta1 (registry+https://github.com/rust-lang/crates.io-index)", "prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -744,10 +769,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +"checksum byte-unit 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6754bb4703aa167bed5381f0c6842f1cc31a9ecde3b9443f726dde3ad3afb841" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" "checksum cc 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a0c56216487bb80eec9c4516337b2588a4f2a2290d72a1416d930e4dcdb0c90d" "checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" +"checksum chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2ff48a655fe8d2dae9a39e66af7fd8ff32a879e8c4e27422c25596a8b5e90d" "checksum chrono-tz 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e0e430fad0384e4defc3dc6b1223d1b886087a8bf9b7080e5ae027f73851ea15" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum conch-parser 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a6a607cf95979f2126c094f2ce279fbc8c58b7649d3b4a25905950931eba3c9" @@ -768,6 +795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139" "checksum futures-core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7455c91eb2eae38f33b013f77ebe766c75761af333efd9d550e154045c63e225" +"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" diff --git a/Cargo.toml b/Cargo.toml index f206bcd7e4..d00068b0ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,6 @@ subprocess = "0.1.18" dunce = "1.0.0" futures = "0.1.27" futures-core = "0.2.1" +indexmap = "1.0.2" +chrono-humanize = "0.0.11" +byte-unit = "2.1.0" diff --git a/src/commands.rs b/src/commands.rs index 93ef28026b..55731b6079 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -3,6 +3,7 @@ crate mod cd; crate mod command; crate mod ls; crate mod ps; +crate mod select; crate mod take; crate mod to_array; diff --git a/src/commands/select.rs b/src/commands/select.rs new file mode 100644 index 0000000000..4c325825ad --- /dev/null +++ b/src/commands/select.rs @@ -0,0 +1,46 @@ +use crate::errors::ShellError; +use crate::object::base::select; +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 SelectBlueprint; + +impl crate::CommandBlueprint for SelectBlueprint { + 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 fields: Result<_, _> = args.iter().map(|a| a.as_string()).collect(); + + Ok(Box::new(Select { fields: fields? })) + } +} + +#[derive(new)] +pub struct Select { + fields: Vec, +} + +impl crate::Command for Select { + fn run(&mut self, stream: VecDeque) -> Result, ShellError> { + let objects = stream + .iter() + .map(|item| Value::Object(Box::new(select(item, &self.fields)))) + .map(|item| ReturnValue::Value(item)) + .collect(); + + Ok(objects) + } +} diff --git a/src/errors.rs b/src/errors.rs index 568482eb03..a8ce1de7f2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,3 +1,4 @@ +use crate::prelude::*; use crate::Value; use derive_new::new; @@ -11,6 +12,13 @@ impl ShellError { crate fn string(title: impl Into) -> ShellError { ShellError::new(title.into(), Value::nothing()) } + + crate fn copy_error(&self) -> ShellError { + ShellError { + title: self.title.clone(), + error: self.error.copy(), + } + } } impl std::fmt::Display for ShellError { diff --git a/src/format/generic.rs b/src/format/generic.rs index cf260d39e8..85b549102d 100644 --- a/src/format/generic.rs +++ b/src/format/generic.rs @@ -13,7 +13,7 @@ pub struct GenericView<'value> { impl RenderView for GenericView<'value> { fn render_view(&self, host: &dyn Host) -> Vec { match self.value { - Value::Primitive(p) => vec![p.format()], + Value::Primitive(p) => vec![p.format(None)], Value::List(l) => { let view = TableView::from_list(l); @@ -43,6 +43,8 @@ impl RenderView for GenericView<'value> { let out = view.render_view(host); out } + + Value::Error(e) => vec![format!("{}", e)], } } } diff --git a/src/format/table.rs b/src/format/table.rs index ff4e175d24..be24b5ae80 100644 --- a/src/format/table.rs +++ b/src/format/table.rs @@ -25,14 +25,15 @@ impl TableView { let item = &values[0]; let descs = item.data_descriptors(); - let headers = descs.iter().map(|d| d.name.clone()).collect(); + let headers: Vec = descs.iter().map(|d| d.name.clone()).collect(); let mut entries = vec![]; for value in values { let row = descs .iter() - .map(|d| value.get_data(d).borrow().format_leaf()) + .enumerate() + .map(|(i, d)| value.get_data(d).borrow().format_leaf(Some(&headers[i]))) .collect(); entries.push(row); diff --git a/src/main.rs b/src/main.rs index 2d56a087b2..83bf804228 100644 --- a/src/main.rs +++ b/src/main.rs @@ -66,6 +66,7 @@ fn main() -> Result<(), Box> { ("ls", Box::new(ls::LsBlueprint)), ("cd", Box::new(cd::CdBlueprint)), ("take", Box::new(take::TakeBlueprint)), + ("select", Box::new(select::SelectBlueprint)), ("to-array", Box::new(to_array::ToArrayBlueprint)), ]); } diff --git a/src/object/base.rs b/src/object/base.rs index c24bd36009..017ea2659e 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -1,28 +1,49 @@ use crate::errors::ShellError; use crate::format::{EntriesView, GenericView}; use crate::object::desc::DataDescriptor; -use chrono::NaiveDateTime; +use chrono::{DateTime, Utc}; +use chrono_humanize::Humanize; use std::fmt::Debug; +use std::time::SystemTime; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Primitive { Nothing, Int(i64), Float(f64), + Bytes(u128), String(String), Boolean(bool), - Date(NaiveDateTime), + Date(DateTime), } impl Primitive { - crate fn format(&self) -> String { + crate fn format(&self, field_name: Option<&str>) -> String { match self { Primitive::Nothing => format!("Nothing"), + Primitive::Bytes(b) => { + let byte = byte_unit::Byte::from_bytes(*b); + let byte = byte.get_appropriate_unit(true); + + match byte.get_unit() { + byte_unit::ByteUnit::B => format!("{}", byte.format(0)), + _ => format!("{}", byte.format(1)), + } + } Primitive::Int(i) => format!("{}", i), Primitive::Float(f) => format!("{}", f), - Primitive::String(s) => format!("{:?}", s), - Primitive::Boolean(b) => format!("{:?}", b), - Primitive::Date(d) => format!("{}", d), + Primitive::String(s) => format!("{}", s), + Primitive::Boolean(b) => match (b, field_name) { + (true, None) => format!("Yes"), + (false, None) => format!("No"), + (true, Some(s)) => format!("{}", s), + (false, Some(s)) => format!(""), + }, + Primitive::Date(d) => { + // let date = d.format("%-m/%-d/%-y"); + // let time = + format!("{}", d.humanize()) + } } } } @@ -32,14 +53,16 @@ pub enum Value { Primitive(Primitive), Object(Box), List(Vec), + Error(Box), } impl ShellObject for Value { fn to_shell_string(&self) -> String { match self { - Value::Primitive(p) => p.format(), + Value::Primitive(p) => p.format(None), Value::Object(o) => o.to_shell_string(), Value::List(l) => format!("[list List]"), + Value::Error(e) => format!("{}", e), } } @@ -48,6 +71,7 @@ impl ShellObject for Value { Value::Primitive(p) => vec![], Value::Object(o) => o.data_descriptors(), Value::List(l) => vec![], + Value::Error(e) => vec![], } } @@ -56,16 +80,45 @@ impl ShellObject for Value { Value::Primitive(p) => crate::MaybeOwned::Owned(Value::nothing()), Value::Object(o) => o.get_data(desc), Value::List(l) => crate::MaybeOwned::Owned(Value::nothing()), + Value::Error(e) => crate::MaybeOwned::Owned(Value::nothing()), + } + } + + fn copy(&self) -> Value { + match self { + Value::Primitive(p) => Value::Primitive(p.clone()), + Value::Object(o) => Value::Object(Box::new(o.copy())), + Value::List(l) => { + let list = l.iter().map(|i| i.copy()).collect(); + Value::List(list) + } + Value::Error(e) => Value::Error(Box::new(e.copy_error())), } } } +impl ShellObject for &Value { + 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) + } + fn copy(&self) -> Value { + (*self).copy() + } +} + impl Value { - crate fn format_leaf(&self) -> String { + crate fn format_leaf(&self, field_name: Option<&str>) -> String { match self { - Value::Primitive(p) => p.format(), + Value::Primitive(p) => p.format(field_name), Value::Object(o) => format!("[object Object]"), Value::List(l) => format!("[list List]"), + Value::Error(e) => format!("{}", e), } } @@ -96,10 +149,25 @@ impl Value { Value::Primitive(Primitive::String(s.into())) } + crate fn bytes(s: impl Into) -> Value { + Value::Primitive(Primitive::Bytes(s.into())) + } + crate fn int(s: impl Into) -> Value { Value::Primitive(Primitive::Int(s.into())) } + crate fn system_date(s: SystemTime) -> Value { + Value::Primitive(Primitive::Date(s.into())) + } + + crate fn system_date_result(s: Result) -> Value { + match s { + Ok(time) => Value::Primitive(Primitive::Date(time.into())), + Err(err) => Value::Error(Box::new(ShellError::string(format!("{}", err)))), + } + } + crate fn boolean(s: impl Into) -> Value { Value::Primitive(Primitive::Boolean(s.into())) } @@ -121,6 +189,22 @@ pub trait ShellObject: Debug { fn to_shell_string(&self) -> String; fn data_descriptors(&self) -> Vec; fn get_data(&'a self, desc: &DataDescriptor) -> crate::MaybeOwned<'a, Value>; + fn copy(&self) -> Value; +} + +crate fn select(obj: impl ShellObject, fields: &[String]) -> crate::object::Dictionary { + let mut out = crate::object::Dictionary::default(); + + let descs = obj.data_descriptors(); + + for field in fields { + match descs.iter().find(|d| d.name == *field) { + None => out.add(field.to_string(), Value::nothing()), + Some(desc) => out.add(field.to_string(), obj.get_data(desc).borrow().copy()), + } + } + + out } pub trait ToEntriesView { @@ -139,9 +223,10 @@ where let value = self.get_data(&desc); let formatted_value = match value.borrow() { - Value::Primitive(p) => p.format(), + Value::Primitive(p) => p.format(None), Value::Object(o) => format!("[object Object]"), Value::List(l) => format!("[object List]"), + Value::Error(e) => format!("{}", e), }; entries.push((desc.name.clone(), formatted_value)) @@ -162,6 +247,10 @@ impl ShellObject for Box { fn get_data(&'a self, desc: &DataDescriptor) -> crate::MaybeOwned<'a, Value> { (**self).get_data(desc) } + + fn copy(&self) -> Value { + (**self).copy() + } } pub trait ToGenericView { diff --git a/src/object/dict.rs b/src/object/dict.rs index faf0ff40d6..050b63997b 100644 --- a/src/object/dict.rs +++ b/src/object/dict.rs @@ -1,20 +1,31 @@ use crate::object::desc::DataDescriptor; use crate::object::{Primitive, Value}; +use crate::prelude::*; use crate::MaybeOwned; use derive_new::new; +use indexmap::IndexMap; use std::cell::RefCell; -use std::collections::BTreeMap; use std::rc::Rc; #[derive(Debug, Default)] pub struct Dictionary { - entries: BTreeMap, + entries: IndexMap, } impl Dictionary { crate fn add(&mut self, name: impl Into, value: Value) { self.entries.insert(name.into(), value); } + + crate fn copy_dict(&self) -> Dictionary { + let mut out = Dictionary::default(); + + for (key, value) in self.entries.iter() { + out.add(key.clone(), value.copy()); + } + + out + } } impl crate::object::ShellObject for Dictionary { @@ -37,37 +48,8 @@ impl crate::object::ShellObject for Dictionary { None => MaybeOwned::Owned(Value::Primitive(Primitive::Nothing)), } } -} -#[derive(Debug, Default)] -pub struct ScopedDictionary<'parent> { - entries: BTreeMap>, -} - -impl ScopedDictionary<'parent> { - crate fn add(&mut self, name: impl Into, value: impl Into>) { - self.entries.insert(name.into(), value.into()); - } -} - -impl crate::object::ShellObject for ScopedDictionary<'parent> { - fn to_shell_string(&self) -> String { - format!("[object Object] lol") - } - - fn data_descriptors(&self) -> Vec { - self.entries - .iter() - .map(|(name, value)| { - DataDescriptor::new(name.clone(), true, Box::new(crate::object::types::AnyShell)) - }) - .collect() - } - - fn get_data(&'a self, desc: &DataDescriptor) -> MaybeOwned<'a, Value> { - match self.entries.get(&desc.name) { - Some(v) => MaybeOwned::Borrowed(v.borrow()), - None => MaybeOwned::Owned(Value::Primitive(Primitive::Nothing)), - } + fn copy(&self) -> Value { + Value::Object(Box::new(self.copy_dict())) } } diff --git a/src/object/files.rs b/src/object/files.rs index 234dca09d6..eb32abc3da 100644 --- a/src/object/files.rs +++ b/src/object/files.rs @@ -4,13 +4,12 @@ use crate::MaybeOwned; #[derive(Debug)] pub struct DirEntry { - inner: std::fs::DirEntry, dict: Dictionary, } #[derive(Debug)] pub enum FileType { - Dir, + Directory, File, Symlink, } @@ -19,26 +18,34 @@ impl DirEntry { crate fn new(inner: std::fs::DirEntry) -> Result { let mut dict = Dictionary::default(); let filename = inner.file_name(); - dict.add("file_name", Value::string(filename.to_string_lossy())); + dict.add("file name", Value::string(filename.to_string_lossy())); let metadata = inner.metadata()?; // let file_type = inner.file_type()?; let kind = if metadata.is_dir() { - FileType::Dir + FileType::Directory } else if metadata.is_file() { FileType::File } else { FileType::Symlink }; - dict.add("file_type", Value::string(format!("{:?}", kind))); + dict.add("file type", Value::string(format!("{:?}", kind))); dict.add( "readonly", Value::boolean(metadata.permissions().readonly()), ); - Ok(DirEntry { inner, dict }) + dict.add("size", Value::bytes(metadata.len() as u128)); + + dict.add("created", Value::system_date_result(metadata.created())); + dict.add("accessed", Value::system_date_result(metadata.accessed())); + dict.add("modified", Value::system_date_result(metadata.modified())); + + // dict.add("created_at", Value::date()) + + Ok(DirEntry { dict }) } } @@ -54,4 +61,12 @@ impl ShellObject for DirEntry { fn get_data(&'a self, desc: &DataDescriptor) -> MaybeOwned<'a, Value> { self.dict.get_data(desc) } + + fn copy(&self) -> Value { + let copy = DirEntry { + dict: self.dict.copy_dict(), + }; + + Value::Object(Box::new(copy)) + } } diff --git a/src/object/process.rs b/src/object/process.rs index 9a345d37fa..7ba419d8e4 100644 --- a/src/object/process.rs +++ b/src/object/process.rs @@ -8,7 +8,6 @@ use sysinfo::ProcessExt; #[derive(Debug)] pub struct Process { - inner: sysinfo::Process, dict: Dictionary, } @@ -29,13 +28,13 @@ impl Process { dict.add("pid", Value::int(inner.pid() as i64)); dict.add("status", Value::int(inner.status() as i64)); - Process { inner, dict } + Process { dict } } } impl ShellObject for Process { fn to_shell_string(&self) -> String { - format!("{} - {}", self.inner.name(), self.inner.pid()) + format!("Process") } fn data_descriptors(&self) -> Vec { @@ -45,4 +44,10 @@ impl ShellObject for Process { fn get_data(&'a self, desc: &DataDescriptor) -> MaybeOwned<'a, Value> { self.dict.get_data(desc) } + + fn copy(&self) -> Value { + let copy = self.dict.copy_dict(); + + Value::Object(Box::new(copy)) + } } diff --git a/src/prelude.rs b/src/prelude.rs index ec0a5a1533..41ff85d90d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,5 +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 crate::object::{Primitive, ShellObject, Value}; crate use std::collections::VecDeque;