From b0a02518f97a7e59b9dd6f815169bda372c57a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Tue, 3 Sep 2019 02:43:37 -0500 Subject: [PATCH] cd can be awared inside a value entered. --- src/object/dict.rs | 2 +- src/shell/value_shell.rs | 12 +++ src/utils.rs | 218 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 217 insertions(+), 15 deletions(-) diff --git a/src/object/dict.rs b/src/object/dict.rs index 1f53d2ade5..e52ab5882a 100644 --- a/src/object/dict.rs +++ b/src/object/dict.rs @@ -102,7 +102,7 @@ impl Dictionary { #[derive(Debug)] pub struct TaggedListBuilder { - pub tag: Tag, + tag: Tag, list: Vec>, } diff --git a/src/shell/value_shell.rs b/src/shell/value_shell.rs index 4c9cab5a93..76be024dd4 100644 --- a/src/shell/value_shell.rs +++ b/src/shell/value_shell.rs @@ -6,6 +6,7 @@ use crate::commands::rm::RemoveArgs; use crate::context::SourceMap; use crate::prelude::*; use crate::shell::shell::Shell; +use crate::utils::ValueStructure; use std::ffi::OsStr; use std::path::PathBuf; @@ -103,6 +104,17 @@ impl Shell for ValueShell { } }; + let mut value_system = ValueStructure::new(); + value_system.walk_decorate(&self.value)?; + + if !value_system.exists(&PathBuf::from(&path)) { + return Err(ShellError::labeled_error( + "Can not change to path inside", + "No such path exists", + args.call_info.name_span, + )); + } + let mut stream = VecDeque::new(); stream.push_back(ReturnSuccess::change_cwd(path)); Ok(stream.into()) diff --git a/src/utils.rs b/src/utils.rs index 4ed9be2540..060b72d7a2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,9 @@ use crate::errors::ShellError; +use crate::object::meta::Tagged; +use crate::object::Value; use std::fmt; use std::ops::Div; -use std::path::{Path, PathBuf}; +use std::path::{Component, Path, PathBuf}; pub struct AbsoluteFile { inner: PathBuf, @@ -129,23 +131,131 @@ impl fmt::Display for RelativePath { } } +pub enum TaggedValueIter<'a> { + Empty, + List(indexmap::map::Iter<'a, String, Tagged>), +} + +impl<'a> Iterator for TaggedValueIter<'a> { + type Item = (&'a String, &'a Tagged); + + fn next(&mut self) -> Option { + match self { + TaggedValueIter::Empty => None, + TaggedValueIter::List(iter) => iter.next(), + } + } +} + +impl Tagged { + fn is_dir(&self) -> bool { + match self.item() { + Value::Object(_) | Value::List(_) => true, + _ => false, + } + } + + fn entries(&self) -> TaggedValueIter<'_> { + match self.item() { + Value::Object(o) => { + let iter = o.entries.iter(); + TaggedValueIter::List(iter) + } + _ => TaggedValueIter::Empty, + } + } +} + +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct ValueResource { + pub at: usize, + pub loc: PathBuf, +} + +impl ValueResource {} + +pub struct ValueStructure { + pub resources: Vec, +} + +impl ValueStructure { + pub fn new() -> ValueStructure { + ValueStructure { + resources: Vec::::new(), + } + } + + pub fn exists(&self, path: &Path) -> bool { + if path == Path::new("/") { + return true; + } + + let path = if path.starts_with("/") { + match path.strip_prefix("/") { + Ok(p) => p, + Err(_) => path, + } + } else { + path + }; + + let comps: Vec<_> = path.components().map(Component::as_os_str).collect(); + + let mut is_there = true; + + for (at, fragment) in comps.iter().enumerate() { + is_there = is_there + && self + .resources + .iter() + .any(|resource| at == resource.at && *fragment == resource.loc.as_os_str()); + } + + is_there + } + + pub fn walk_decorate(&mut self, start: &Tagged) -> Result<(), ShellError> { + self.resources = Vec::::new(); + self.build(start, 0)?; + self.resources.sort(); + + Ok(()) + } + + fn build(&mut self, src: &Tagged, lvl: usize) -> Result<(), ShellError> { + for entry in src.entries() { + let value = entry.1; + let path = entry.0; + + self.resources.push(ValueResource { + at: lvl, + loc: PathBuf::from(path), + }); + + if value.is_dir() { + self.build(value, lvl + 1)?; + } + } + + Ok(()) + } +} + #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Res { - pub loc: PathBuf, pub at: usize, + pub loc: PathBuf, } impl Res {} pub struct FileStructure { - root: PathBuf, pub resources: Vec, } impl FileStructure { pub fn new() -> FileStructure { FileStructure { - root: PathBuf::new(), resources: Vec::::new(), } } @@ -158,10 +268,6 @@ impl FileStructure { self.resources.len() > 0 } - pub fn set_root(&mut self, path: &Path) { - self.root = path.to_path_buf(); - } - pub fn paths_applying_with( &mut self, to: F, @@ -177,7 +283,6 @@ impl FileStructure { } pub fn walk_decorate(&mut self, start_path: &Path) -> Result<(), ShellError> { - self.set_root(&dunce::canonicalize(start_path)?); self.resources = Vec::::new(); self.build(start_path, 0)?; self.resources.sort(); @@ -189,7 +294,7 @@ impl FileStructure { let source = dunce::canonicalize(src)?; if source.is_dir() { - for entry in std::fs::read_dir(&source)? { + for entry in std::fs::read_dir(src)? { let entry = entry?; let path = entry.path(); @@ -215,9 +320,10 @@ impl FileStructure { #[cfg(test)] mod tests { + use super::{FileStructure, Res, ValueResource, ValueStructure}; + use crate::object::meta::{Tag, Tagged}; + use crate::object::{TaggedDictBuilder, Value}; use pretty_assertions::assert_eq; - - use super::{FileStructure, Res}; use std::path::PathBuf; fn fixtures() -> PathBuf { @@ -232,11 +338,95 @@ mod tests { } } + fn structured_sample_record(key: &str, value: &str) -> Tagged { + let mut record = TaggedDictBuilder::new(Tag::unknown()); + record.insert(key.clone(), Value::string(value)); + record.into_tagged_value() + } + + fn sample_nushell_source_code() -> Tagged { + /* + src + commands + plugins => "sys.rs" + tests + helpers => "mod.rs" + */ + + let mut src = TaggedDictBuilder::new(Tag::unknown()); + let mut record = TaggedDictBuilder::new(Tag::unknown()); + + record.insert_tagged("commands", structured_sample_record("plugins", "sys.rs")); + record.insert_tagged("tests", structured_sample_record("helpers", "mod.rs")); + src.insert_tagged("src", record.into_tagged_value()); + + src.into_tagged_value() + } + #[test] - fn prepares_and_decorates_source_files_for_copying() { + fn prepares_and_decorates_value_filesystemlike_sources() { + let mut res = ValueStructure::new(); + + res.walk_decorate(&sample_nushell_source_code()) + .expect("Can not decorate values traversal."); + + assert_eq!( + res.resources, + vec![ + ValueResource { + loc: PathBuf::from("src"), + at: 0, + }, + ValueResource { + loc: PathBuf::from("commands"), + at: 1, + }, + ValueResource { + loc: PathBuf::from("tests"), + at: 1, + }, + ValueResource { + loc: PathBuf::from("helpers"), + at: 2, + }, + ValueResource { + loc: PathBuf::from("plugins"), + at: 2, + }, + ] + ); + } + + #[test] + fn recognizes_if_path_exists_in_value_filesystemlike_sources() { + let mut res = ValueStructure::new(); + + res.walk_decorate(&sample_nushell_source_code()) + .expect("Can not decorate values traversal."); + + assert!(res.exists(&PathBuf::from("/"))); + + assert!(res.exists(&PathBuf::from("src/commands/plugins"))); + assert!(res.exists(&PathBuf::from("src/commands"))); + assert!(res.exists(&PathBuf::from("src/tests"))); + assert!(res.exists(&PathBuf::from("src/tests/helpers"))); + assert!(res.exists(&PathBuf::from("src"))); + + assert!(res.exists(&PathBuf::from("/src/commands/plugins"))); + assert!(res.exists(&PathBuf::from("/src/commands"))); + assert!(res.exists(&PathBuf::from("/src/tests"))); + assert!(res.exists(&PathBuf::from("/src/tests/helpers"))); + assert!(res.exists(&PathBuf::from("/src"))); + + assert!(!res.exists(&PathBuf::from("/not_valid"))); + assert!(!res.exists(&PathBuf::from("/src/not_valid"))); + } + + #[test] + fn prepares_and_decorates_filesystem_source_files() { let mut res = FileStructure::new(); - res.walk_decorate(fixtures().as_path()) + res.walk_decorate(&fixtures()) .expect("Can not decorate files traversal."); assert_eq!(