From f40089f29b2150a26441afbf1d0c734856689b3e Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Fri, 14 Jun 2019 09:47:25 +1200 Subject: [PATCH 1/3] Better cd and ls --- src/cli.rs | 26 ++++--- src/commands.rs | 2 + src/commands/cd.rs | 97 ++++++++++++++++--------- src/commands/classified.rs | 23 ++++-- src/commands/command.rs | 8 ++- src/commands/enter.rs | 137 ++++++++++++++++++++++++++++++++++++ src/commands/exit.rs | 12 ++++ src/commands/ls.rs | 80 +++++++++++++++------ src/commands/open.rs | 9 ++- src/commands/save.rs | 10 ++- src/commands/size.rs | 9 ++- src/commands/view.rs | 9 ++- src/context.rs | 4 +- src/env/environment.rs | 19 +++-- src/format/generic.rs | 5 ++ src/format/tree.rs | 1 + src/object/base.rs | 8 ++- src/object/serialization.rs | 1 + 18 files changed, 378 insertions(+), 82 deletions(-) create mode 100644 src/commands/enter.rs create mode 100644 src/commands/exit.rs diff --git a/src/cli.rs b/src/cli.rs index ccda0d2e0a..1aedf1b152 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -60,6 +60,8 @@ pub async fn cli() -> Result<(), Box> { command("from-yaml", from_yaml::from_yaml), command("get", get::get), command("open", open::open), + command("enter", enter::enter), + command("exit", exit::exit), command("pick", pick::pick), command("split-column", split_column::split_column), command("split-row", split_row::split_row), @@ -109,14 +111,22 @@ pub async fn cli() -> Result<(), Box> { continue; } - let readline = rl.readline(&format!( - "{}{}> ", - context.env.lock().unwrap().cwd().display(), - match current_branch() { - Some(s) => format!("({})", s), - None => "".to_string(), - } - )); + let (obj, cwd) = { + let env = context.env.lock().unwrap(); + let last = env.last().unwrap(); + (last.obj().clone(), last.path().display().to_string()) + }; + let readline = match obj { + Value::Filesystem => rl.readline(&format!( + "{}{}> ", + cwd, + match current_branch() { + Some(s) => format!("({})", s), + None => "".to_string(), + } + )), + _ => rl.readline(&format!("{}{}> ", obj.type_name(), cwd)), + }; match process_line(readline, &mut context).await { LineResult::Success(line) => { diff --git a/src/commands.rs b/src/commands.rs index d88ac62233..5c21cfc20b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -5,6 +5,8 @@ crate mod classified; crate mod clip; crate mod command; crate mod config; +crate mod enter; +crate mod exit; crate mod first; crate mod from_json; crate mod from_toml; diff --git a/src/commands/cd.rs b/src/commands/cd.rs index 7fbe5d3a05..d69f71e2bc 100644 --- a/src/commands/cd.rs +++ b/src/commands/cd.rs @@ -1,44 +1,77 @@ use crate::errors::ShellError; use crate::prelude::*; use std::env; +use std::path::PathBuf; pub fn cd(args: CommandArgs) -> Result { - let cwd = args.env.lock().unwrap().cwd().to_path_buf(); - let path = match args.positional.first() { - None => match dirs::home_dir() { - Some(o) => o, - _ => return Err(ShellError::string("Can not change to home directory")), - }, - Some(v) => { - let target = v.as_string()?.clone(); - match dunce::canonicalize(cwd.join(&target).as_path()) { - Ok(p) => p, + let env = args.env.lock().unwrap(); + let latest = env.last().unwrap(); + + match latest.obj { + Value::Filesystem => { + let cwd = latest.path().to_path_buf(); + let path = match args.positional.first() { + None => match dirs::home_dir() { + Some(o) => o, + _ => return Err(ShellError::string("Can not change to home directory")), + }, + Some(v) => { + let target = v.as_string()?.clone(); + match dunce::canonicalize(cwd.join(&target).as_path()) { + Ok(p) => p, + Err(_) => { + return Err(ShellError::labeled_error( + "Can not change to directory", + "directory not found", + args.positional[0].span.clone(), + )); + } + } + } + }; + + let mut stream = VecDeque::new(); + match env::set_current_dir(&path) { + Ok(_) => {} Err(_) => { - return Err(ShellError::labeled_error( - "Can not change to directory", - "directory not found", - args.positional[0].span.clone(), - )); + if args.positional.len() > 0 { + return Err(ShellError::labeled_error( + "Can not change to directory", + "directory not found", + args.positional[0].span.clone(), + )); + } else { + return Err(ShellError::string("Can not change to directory")); + } } } + stream.push_back(ReturnValue::change_cwd(path)); + Ok(stream.boxed()) } - }; - - let mut stream = VecDeque::new(); - match env::set_current_dir(&path) { - Ok(_) => {} - Err(_) => { - if args.positional.len() > 0 { - return Err(ShellError::labeled_error( - "Can not change to directory", - "directory not found", - args.positional[0].span.clone(), - )); - } else { - return Err(ShellError::string("Can not change to directory")); - } + _ => { + let mut stream = VecDeque::new(); + match args.positional.first() { + None => { + stream.push_back(ReturnValue::change_cwd(PathBuf::from("/"))); + } + Some(v) => { + let mut cwd = latest.path().to_path_buf(); + let target = v.as_string()?.clone(); + match target { + x if x == ".." => { + cwd.pop(); + } + _ => match target.chars().nth(0) { + Some(x) if x == '/' => cwd = PathBuf::from(target), + _ => { + cwd.push(target); + } + }, + } + stream.push_back(ReturnValue::change_cwd(cwd)); + } + }; + Ok(stream.boxed()) } } - stream.push_back(ReturnValue::change_cwd(path)); - Ok(stream.boxed()) } diff --git a/src/commands/classified.rs b/src/commands/classified.rs index d29a903619..75e3877eff 100644 --- a/src/commands/classified.rs +++ b/src/commands/classified.rs @@ -6,9 +6,9 @@ use crate::prelude::*; use bytes::{BufMut, BytesMut}; use futures_codec::{Decoder, Encoder, Framed}; use std::io::{Error, ErrorKind}; +use std::path::PathBuf; use std::sync::Arc; use subprocess::Exec; - /// A simple `Codec` implementation that splits up data into lines. pub struct LinesCodec {} @@ -119,8 +119,23 @@ impl InternalCommand { let stream = result.filter_map(move |v| match v { ReturnValue::Action(action) => match action { - CommandAction::ChangeCwd(cwd) => { - env.lock().unwrap().cwd = cwd; + CommandAction::ChangePath(path) => { + env.lock().unwrap().last_mut().map(|x| { + x.path = path; + x + }); + futures::future::ready(None) + } + CommandAction::Enter(obj) => { + let new_env = Environment { + obj: obj, + path: PathBuf::from("/"), + }; + env.lock().unwrap().push(new_env); + futures::future::ready(None) + } + CommandAction::Exit => { + let _ = env.lock().unwrap().pop(); futures::future::ready(None) } }, @@ -210,7 +225,7 @@ impl ExternalCommand { } process = Exec::shell(new_arg_string); } - process = process.cwd(context.env.lock().unwrap().cwd()); + process = process.cwd(context.env.lock().unwrap().first().unwrap().path()); let mut process = match stream_next { StreamNext::Last => process, diff --git a/src/commands/command.rs b/src/commands/command.rs index 2aff777692..2d9faff09e 100644 --- a/src/commands/command.rs +++ b/src/commands/command.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; pub struct CommandArgs { pub host: Arc>, - pub env: Arc>, + pub env: Arc>>, pub name_span: Option, pub positional: Vec>, pub named: indexmap::IndexMap, @@ -25,7 +25,9 @@ pub struct SinkCommandArgs { #[derive(Debug)] pub enum CommandAction { - ChangeCwd(PathBuf), + ChangePath(PathBuf), + Enter(Value), + Exit, } #[derive(Debug)] @@ -36,7 +38,7 @@ pub enum ReturnValue { impl ReturnValue { crate fn change_cwd(path: PathBuf) -> ReturnValue { - ReturnValue::Action(CommandAction::ChangeCwd(path)) + ReturnValue::Action(CommandAction::ChangePath(path)) } } diff --git a/src/commands/enter.rs b/src/commands/enter.rs new file mode 100644 index 0000000000..d1bbef5da2 --- /dev/null +++ b/src/commands/enter.rs @@ -0,0 +1,137 @@ +use crate::commands::command::CommandAction; +use crate::errors::ShellError; +use crate::object::{Primitive, Value}; +use crate::parser::lexer::Spanned; +use crate::prelude::*; +use std::path::{Path, PathBuf}; + +pub fn enter(args: CommandArgs) -> Result { + if args.positional.len() == 0 { + return Err(ShellError::string("open requires a filepath or url")); + } + + let cwd = args + .env + .lock() + .unwrap() + .first() + .unwrap() + .path() + .to_path_buf(); + let mut full_path = PathBuf::from(cwd); + + let (file_extension, contents) = match &args.positional[0].item { + Value::Primitive(Primitive::String(s)) => { + if s.starts_with("http:") || s.starts_with("https:") { + let response = reqwest::get(s); + match response { + Ok(mut r) => match r.text() { + Ok(s) => { + let fname = r + .url() + .path_segments() + .and_then(|segments| segments.last()) + .and_then(|name| if name.is_empty() { None } else { Some(name) }) + .and_then(|name| { + PathBuf::from(name) + .extension() + .map(|name| name.to_string_lossy().to_string()) + }); + (fname, s) + } + Err(_) => { + return Err(ShellError::labeled_error( + "Web page contents corrupt", + "received garbled data", + args.positional[0].span, + )); + } + }, + Err(_) => { + return Err(ShellError::labeled_error( + "URL could not be opened", + "url not found", + args.positional[0].span, + )); + } + } + } else { + full_path.push(Path::new(&s)); + match std::fs::read_to_string(&full_path) { + Ok(s) => ( + full_path + .extension() + .map(|name| name.to_string_lossy().to_string()), + s, + ), + Err(_) => { + return Err(ShellError::labeled_error( + "File cound not be opened", + "file not found", + args.positional[0].span, + )); + } + } + } + } + _ => { + return Err(ShellError::labeled_error( + "Expected string value for filename", + "expected filename", + args.positional[0].span, + )); + } + }; + + let mut stream = VecDeque::new(); + + let open_raw = match args.positional.get(1) { + Some(Spanned { + item: Value::Primitive(Primitive::String(s)), + .. + }) if s == "--raw" => true, + Some(v) => { + return Err(ShellError::labeled_error( + "Unknown flag for open", + "unknown flag", + v.span, + )) + } + _ => false, + }; + + match file_extension { + Some(x) if x == "toml" && !open_raw => { + stream.push_back(ReturnValue::Action(CommandAction::Enter( + crate::commands::from_toml::from_toml_string_to_value(contents), + ))); + } + Some(x) if x == "json" && !open_raw => { + stream.push_back(ReturnValue::Action(CommandAction::Enter( + crate::commands::from_json::from_json_string_to_value(contents), + ))); + } + Some(x) if x == "xml" && !open_raw => { + stream.push_back(ReturnValue::Action(CommandAction::Enter( + crate::commands::from_xml::from_xml_string_to_value(contents), + ))); + } + Some(x) if x == "yml" && !open_raw => { + stream.push_back(ReturnValue::Action(CommandAction::Enter( + crate::commands::from_yaml::from_yaml_string_to_value(contents), + ))); + } + Some(x) if x == "yaml" && !open_raw => { + stream.push_back(ReturnValue::Action(CommandAction::Enter( + crate::commands::from_yaml::from_yaml_string_to_value(contents), + ))); + } + _ => { + stream.push_back(ReturnValue::Action(CommandAction::Enter(Value::Primitive( + Primitive::String(contents), + )))); + } + } + + Ok(stream.boxed()) +} diff --git a/src/commands/exit.rs b/src/commands/exit.rs new file mode 100644 index 0000000000..0669adee76 --- /dev/null +++ b/src/commands/exit.rs @@ -0,0 +1,12 @@ +use crate::commands::command::CommandAction; +use crate::errors::ShellError; +use crate::object::{Primitive, Value}; +use crate::parser::lexer::Spanned; +use crate::prelude::*; +use std::path::{Path, PathBuf}; + +pub fn exit(args: CommandArgs) -> Result { + let mut stream = VecDeque::new(); + stream.push_back(ReturnValue::Action(CommandAction::Exit)); + Ok(stream.boxed()) +} diff --git a/src/commands/ls.rs b/src/commands/ls.rs index 63927fdb4c..67f9b15f4a 100644 --- a/src/commands/ls.rs +++ b/src/commands/ls.rs @@ -2,11 +2,14 @@ use crate::errors::ShellError; use crate::object::{dir_entry_dict, Primitive, Value}; use crate::parser::lexer::Spanned; use crate::prelude::*; +use std::ffi::OsStr; use std::path::{Path, PathBuf}; pub fn ls(args: CommandArgs) -> Result { - let cwd = args.env.lock().unwrap().cwd().to_path_buf(); - let mut full_path = PathBuf::from(cwd); + let env = args.env.lock().unwrap(); + let path = env.last().unwrap().path.to_path_buf(); + let obj = &env.last().unwrap().obj; + let mut full_path = PathBuf::from(path); match &args.positional.get(0) { Some(Spanned { item: Value::Primitive(Primitive::String(s)), @@ -15,29 +18,60 @@ pub fn ls(args: CommandArgs) -> Result { _ => {} } - let entries = std::fs::read_dir(&full_path); + match obj { + Value::Filesystem => { + let entries = std::fs::read_dir(&full_path); - let entries = match entries { - Err(e) => { - if let Some(s) = args.positional.get(0) { - return Err(ShellError::labeled_error( - e.to_string(), - e.to_string(), - s.span, - )); - } else { - return Err(ShellError::string(e.to_string())); + let entries = match entries { + Err(e) => { + if let Some(s) = args.positional.get(0) { + return Err(ShellError::labeled_error( + e.to_string(), + e.to_string(), + s.span, + )); + } else { + return Err(ShellError::string(e.to_string())); + } + } + Ok(o) => o, + }; + + let mut shell_entries = VecDeque::new(); + + for entry in entries { + let value = Value::Object(dir_entry_dict(&entry?)?); + shell_entries.push_back(ReturnValue::Value(value)) } + Ok(shell_entries.boxed()) + } + _ => { + let mut entries = VecDeque::new(); + let mut viewed = obj; + 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 => match viewed.get_data_by_key(step.to_str().unwrap()) { + Some(v) => { + viewed = v; + } + _ => println!("Obj not Some"), + }, + } + } + match viewed { + Value::List(l) => { + for item in l { + entries.push_back(ReturnValue::Value(item.copy())); + } + } + x => { + entries.push_back(ReturnValue::Value(x.clone())); + } + } + Ok(entries.boxed()) } - Ok(o) => o, - }; - - let mut shell_entries = VecDeque::new(); - - for entry in entries { - let value = Value::Object(dir_entry_dict(&entry?)?); - shell_entries.push_back(ReturnValue::Value(value)) } - - Ok(shell_entries.boxed()) } diff --git a/src/commands/open.rs b/src/commands/open.rs index 40fe1a8e4d..15392e48d3 100644 --- a/src/commands/open.rs +++ b/src/commands/open.rs @@ -9,7 +9,14 @@ pub fn open(args: CommandArgs) -> Result { return Err(ShellError::string("open requires a filepath or url")); } - let cwd = args.env.lock().unwrap().cwd().to_path_buf(); + let cwd = args + .env + .lock() + .unwrap() + .first() + .unwrap() + .path() + .to_path_buf(); let mut full_path = PathBuf::from(cwd); let (file_extension, contents) = match &args.positional[0].item { diff --git a/src/commands/save.rs b/src/commands/save.rs index 30ed386faf..5d94496cf3 100644 --- a/src/commands/save.rs +++ b/src/commands/save.rs @@ -9,7 +9,15 @@ pub fn save(args: SinkCommandArgs) -> Result<(), ShellError> { return Err(ShellError::string("save requires a filepath")); } - let cwd = args.ctx.env.lock().unwrap().cwd().to_path_buf(); + let cwd = args + .ctx + .env + .lock() + .unwrap() + .first() + .unwrap() + .path() + .to_path_buf(); let mut full_path = PathBuf::from(cwd); match &(args.positional[0].item) { Value::Primitive(Primitive::String(s)) => full_path.push(Path::new(s)), diff --git a/src/commands/size.rs b/src/commands/size.rs index baf3c33037..4fe90e25ff 100644 --- a/src/commands/size.rs +++ b/src/commands/size.rs @@ -9,7 +9,14 @@ pub fn size(args: CommandArgs) -> Result { if args.positional.is_empty() { return Err(ShellError::string("size requires at least one file")); } - let cwd = args.env.lock().unwrap().cwd().to_path_buf(); + let cwd = args + .env + .lock() + .unwrap() + .first() + .unwrap() + .path() + .to_path_buf(); let mut contents = String::new(); diff --git a/src/commands/view.rs b/src/commands/view.rs index e7c13efbaa..30dce91d88 100644 --- a/src/commands/view.rs +++ b/src/commands/view.rs @@ -30,7 +30,14 @@ pub fn view(args: CommandArgs) -> Result { } }; - let cwd = args.env.lock().unwrap().cwd().to_path_buf(); + let cwd = args + .env + .lock() + .unwrap() + .first() + .unwrap() + .path() + .to_path_buf(); let printer = PrettyPrinter::default() .line_numbers(false) diff --git a/src/context.rs b/src/context.rs index 7beba4a748..ff571369d4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -13,7 +13,7 @@ pub struct Context { commands: IndexMap>, sinks: IndexMap>, crate host: Arc>, - crate env: Arc>, + crate env: Arc>>, } impl Context { @@ -22,7 +22,7 @@ impl Context { commands: indexmap::IndexMap::new(), sinks: indexmap::IndexMap::new(), host: Arc::new(Mutex::new(crate::env::host::BasicHost)), - env: Arc::new(Mutex::new(Environment::basic()?)), + env: Arc::new(Mutex::new(vec![Environment::basic()?])), }) } diff --git a/src/env/environment.rs b/src/env/environment.rs index 065574eb68..9b0875e651 100644 --- a/src/env/environment.rs +++ b/src/env/environment.rs @@ -1,18 +1,27 @@ +use crate::object::base::Value; use std::path::{Path, PathBuf}; #[derive(Debug, Clone)] pub struct Environment { - crate cwd: PathBuf, + crate obj: Value, + crate path: PathBuf, } impl Environment { pub fn basic() -> Result { - let cwd = std::env::current_dir()?; + let path = std::env::current_dir()?; - Ok(Environment { cwd }) + Ok(Environment { + obj: Value::Filesystem, + path, + }) } - pub fn cwd(&self) -> &Path { - self.cwd.as_path() + pub fn path(&self) -> &Path { + self.path.as_path() + } + + pub fn obj(&self) -> &Value { + &self.obj } } diff --git a/src/format/generic.rs b/src/format/generic.rs index da5954398f..06032cf713 100644 --- a/src/format/generic.rs +++ b/src/format/generic.rs @@ -40,6 +40,11 @@ impl RenderView for GenericView<'value> { host.stdout(&format!("{:?}", e)); Ok(()) } + + Value::Filesystem => { + host.stdout(""); + Ok(()) + } } } } diff --git a/src/format/tree.rs b/src/format/tree.rs index 9afd138a0c..6bf7206a94 100644 --- a/src/format/tree.rs +++ b/src/format/tree.rs @@ -38,6 +38,7 @@ impl TreeView { } Value::Block(_) => {} Value::Error(_) => {} + Value::Filesystem => {} } } crate fn from_value(value: &Value) -> TreeView { diff --git a/src/object/base.rs b/src/object/base.rs index 28675f0be0..aaeb883a19 100644 --- a/src/object/base.rs +++ b/src/object/base.rs @@ -154,6 +154,7 @@ pub enum Value { Object(crate::object::Dictionary), List(Vec), Block(Block), + Filesystem, #[allow(unused)] Error(Box), @@ -167,6 +168,7 @@ impl Value { Value::List(_) => format!("list"), Value::Block(_) => format!("block"), Value::Error(_) => format!("error"), + Value::Filesystem => format!("filesystem"), } } @@ -177,6 +179,7 @@ impl Value { Value::Block(_) => vec![DataDescriptor::value_of()], Value::List(_) => vec![], Value::Error(_) => vec![DataDescriptor::value_of()], + Value::Filesystem => vec![], } } @@ -189,7 +192,7 @@ impl Value { Value::Object(o) => match o.get_data_by_key(name) { Some(v) => return Some(v), None => {} - } + }, _ => {} } } @@ -202,6 +205,7 @@ impl Value { crate fn get_data(&'a self, desc: &DataDescriptor) -> MaybeOwned<'a, Value> { match self { p @ Value::Primitive(_) => MaybeOwned::Borrowed(p), + p @ Value::Filesystem => MaybeOwned::Borrowed(p), Value::Object(o) => o.get_data(desc), Value::Block(_) => MaybeOwned::Owned(Value::nothing()), Value::List(_) => MaybeOwned::Owned(Value::nothing()), @@ -219,6 +223,7 @@ impl Value { Value::List(list) } Value::Error(e) => Value::Error(Box::new(e.copy_error())), + Value::Filesystem => Value::Filesystem, } } @@ -229,6 +234,7 @@ impl Value { Value::Object(_) => format!("[object Object]"), Value::List(_) => format!("[list List]"), Value::Error(e) => format!("{}", e), + Value::Filesystem => format!(""), } } diff --git a/src/object/serialization.rs b/src/object/serialization.rs index 99ff83ea29..85e7743a97 100644 --- a/src/object/serialization.rs +++ b/src/object/serialization.rs @@ -44,6 +44,7 @@ impl Serialize for Value { Value::List(l) => l.serialize(serializer), Value::Block(b) => b.serialize(serializer), Value::Error(e) => e.serialize(serializer), + Value::Filesystem => "".serialize(serializer), } } } From d94e0d436e89cc65739afe14cd36149c01aaa6d6 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Fri, 14 Jun 2019 10:49:16 +1200 Subject: [PATCH 2/3] Add exit --- src/cli.rs | 2 -- src/commands/classified.rs | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 1aedf1b152..24e654da34 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -217,8 +217,6 @@ impl std::ops::Try for LineResult { async fn process_line(readline: Result, ctx: &mut Context) -> LineResult { match &readline { - Ok(line) if line.trim() == "exit" => LineResult::Break, - Ok(line) if line.trim() == "" => LineResult::Success(line.clone()), Ok(line) => { diff --git a/src/commands/classified.rs b/src/commands/classified.rs index 75e3877eff..fcd9586f2d 100644 --- a/src/commands/classified.rs +++ b/src/commands/classified.rs @@ -135,7 +135,11 @@ impl InternalCommand { futures::future::ready(None) } CommandAction::Exit => { - let _ = env.lock().unwrap().pop(); + let mut v = env.lock().unwrap(); + if v.len() == 1 { + std::process::exit(0); + } + v.pop(); futures::future::ready(None) } }, From 50fe77968f0425dce0509bf34cd3c252cdfe6ab6 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Fri, 14 Jun 2019 10:52:36 +1200 Subject: [PATCH 3/3] Fix test --- tests/json_roundtrip.out | 2 +- tests/json_roundtrip.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/json_roundtrip.out b/tests/json_roundtrip.out index 1d242fb50c..10a6a46d7a 100644 --- a/tests/json_roundtrip.out +++ b/tests/json_roundtrip.out @@ -1 +1 @@ -"S" +markup diff --git a/tests/json_roundtrip.txt b/tests/json_roundtrip.txt index 91130616d6..505c2a98db 100644 --- a/tests/json_roundtrip.txt +++ b/tests/json_roundtrip.txt @@ -1,3 +1,3 @@ cd tests -open test.json | to-json | from-json | get glossary.GlossDiv.title | echo $it +open test.json | get glossary.GlossDiv.GlossList.GlossEntry.GlossSee | echo $it exit