From 81b6b51dbd77f76b4052c2d3935a16fb39b44765 Mon Sep 17 00:00:00 2001
From: Xavier L'Heureux <xavier.lheureux@icloud.com>
Date: Thu, 27 Jun 2019 12:25:49 -0400
Subject: [PATCH] Merge/remove error types

---
 src/binary/mod.rs              | 19 +++++++---
 src/lib/description.md         | 57 ------------------------------
 src/lib/lib.rs                 | 16 ++++++---
 src/lib/shell/flow.rs          |  4 +++
 src/lib/shell/fork.rs          |  2 +-
 src/lib/shell/mod.rs           | 63 +++++-----------------------------
 src/lib/shell/pipe_exec/mod.rs |  7 ++++
 src/lib/shell/shell_expand.rs  | 19 +++++-----
 src/main.rs                    |  9 ++++-
 9 files changed, 64 insertions(+), 132 deletions(-)
 delete mode 100644 src/lib/description.md

diff --git a/src/binary/mod.rs b/src/binary/mod.rs
index d2f1edf2..ab5521ef 100644
--- a/src/binary/mod.rs
+++ b/src/binary/mod.rs
@@ -15,7 +15,13 @@ use ion_shell::{
 };
 use itertools::Itertools;
 use liner::{Buffer, Context, KeyBindings};
-use std::{cell::RefCell, fs::OpenOptions, io, path::Path, rc::Rc};
+use std::{
+    cell::RefCell,
+    fs::{self, OpenOptions},
+    io,
+    path::Path,
+    rc::Rc,
+};
 use xdg::BaseDirectories;
 
 #[cfg(not(feature = "advanced_arg_parsing"))]
@@ -183,11 +189,14 @@ impl<'a> InteractiveShell<'a> {
     fn exec_init_file(shell: &mut Shell) {
         match BaseDirectories::with_prefix("ion") {
             Ok(base_dirs) => match base_dirs.find_config_file(Self::CONFIG_FILE_NAME) {
-                Some(initrc) => {
-                    if let Err(err) = shell.execute_file(&initrc) {
-                        eprintln!("ion: {}", err)
+                Some(initrc) => match fs::File::open(initrc) {
+                    Ok(script) => {
+                        if let Err(err) = shell.execute_command(std::io::BufReader::new(script)) {
+                            eprintln!("ion: {}", err);
+                        }
                     }
-                }
+                    Err(cause) => println!("ion: init file was not found: {}", cause),
+                },
                 None => {
                     if let Err(err) = Self::create_config_file(base_dirs, Self::CONFIG_FILE_NAME) {
                         eprintln!("ion: could not create config file: {}", err);
diff --git a/src/lib/description.md b/src/lib/description.md
deleted file mode 100644
index 05437c95..00000000
--- a/src/lib/description.md
+++ /dev/null
@@ -1,57 +0,0 @@
-# Ion - the pipe-oriented embedded language
-
-Ion is an embeddable shell for rust. This means your users can benefit from a fully-fledged programming language to configure your application, rather than predefined layouts like yaml. This also means the configuration can be completely responsive and react to events in any way the user sees fit.
-
-## Getting started
-
-```toml
-[dependencies]
-ion_shell = "1.0"
-```
-
-## Demo
-
-```rust
-use ion_shell::{BuiltinMap, BuiltinFunction, Shell, status::Status, types};
-use std::{thread, time, cell::RefCell, rc::Rc};
-
-enum Layout {
-  Simple,
-  Complex(String),
-}
-
-fn main() {
-  let mut i = 0;
-  let layout = RefCell::new(Layout::Simple); // A state for your application
-
-  // Create a custom callback to update your state when called by a script
-  let set_layout: BuiltinFunction = &move |args: &[types::Str], shell: &mut Shell| {
-    *layout.borrow_mut() = if let Some(text) = args.get(0) {
-      Layout::Complex(text.to_string())
-    } else {
-      Layout::Simple
-    };
-    Status::SUCCESS
-  };
-
-  // Create a shell
-  let mut shell = Shell::library();
-
-  // Register the builtins
-  shell.builtins_mut().add("layout", set_layout, "Set the application layout");
-
-  // Read a file and execute it
-  shell.execute_file("./bob");
-
-  for _ in 0..255 {
-    i += 1;
-    // call a user-defined callback function named on_update
-    let _ = shell.execute_function("on_update", &["ion", &i.to_string()]);
-
-    thread::sleep(time::Duration::from_millis(5));
-  }
-}
-```
-
-## Documentation
- - [Ion programming language manual](https://doc.redox-os.org/ion-manual/)
diff --git a/src/lib/lib.rs b/src/lib/lib.rs
index 2babeb11..c2459cd8 100644
--- a/src/lib/lib.rs
+++ b/src/lib/lib.rs
@@ -18,8 +18,8 @@
 //! ## Demo
 //!
 //! ```rust
-//! use ion_shell::{builtins::Status, types, BuiltinFunction, BuiltinMap, Shell};
-//! use std::{cell::RefCell, rc::Rc, thread, time};
+//! use ion_shell::{builtins::Status, Value, types, BuiltinFunction, BuiltinMap, Shell};
+//! use std::{cell::RefCell, rc::Rc, thread, time, fs::File};
 //!
 //! enum Layout {
 //!     Simple,
@@ -47,12 +47,20 @@
 //!     shell.builtins_mut().add("layout", set_layout, "Set the application layout");
 //!
 //!     // Read a file and execute it
-//!     shell.execute_file("/home/user/.config/my-application/config.ion");
+//!     if let Ok(file) = File::open("/home/user/.config/my-application/config.ion") {
+//!         if let Err(why) = shell.execute_command(file) {
+//!             println!("ERROR: my-application: error in config file: {}", why);
+//!         }
+//!     }
 //!
 //!     for _ in 0..255 {
 //!         i += 1;
 //!         // call a user-defined callback function named on_update
-//!         let _ = shell.execute_function("on_update", &["ion", &i.to_string()]);
+//!         if let Some(Value::Function(function)) = shell.variables().get("on_update") {
+//!             if let Err(why) = shell.execute_function(&function.clone(), &["ion", &i.to_string()]) {
+//!                 println!("ERROR: my-application: error in on_update callback: {}", why);
+//!             }
+//!         }
 //!
 //!         thread::sleep(time::Duration::from_millis(5));
 //!     }
diff --git a/src/lib/shell/flow.rs b/src/lib/shell/flow.rs
index 05665785..18d62087 100644
--- a/src/lib/shell/flow.rs
+++ b/src/lib/shell/flow.rs
@@ -58,6 +58,10 @@ pub enum BlockError {
     /// Found a continue outside a loop
     #[error(display = "found Continue without loop body")]
     UnmatchedContinue,
+
+    /// Unclosed block
+    #[error(display = "expected end block for `{}`", _0)]
+    UnclosedBlock(String),
 }
 
 impl<'a> Shell<'a> {
diff --git a/src/lib/shell/fork.rs b/src/lib/shell/fork.rs
index e36f7e33..cda66cd3 100644
--- a/src/lib/shell/fork.rs
+++ b/src/lib/shell/fork.rs
@@ -69,7 +69,7 @@ impl<'a, 'b> Fork<'a, 'b> {
     pub fn exec<F: FnMut(&mut Shell<'b>) -> Result<(), IonError> + 'a>(
         self,
         mut child_func: F,
-    ) -> Result<IonResult, IonError> {
+    ) -> nix::Result<IonResult> {
         sys::signals::block();
 
         // If we are to capture stdout, create a pipe for capturing outputs.
diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs
index 4e2acff6..3f4ae84c 100644
--- a/src/lib/shell/mod.rs
+++ b/src/lib/shell/mod.rs
@@ -41,10 +41,8 @@ use err_derive::Error;
 use itertools::Itertools;
 use nix::sys::signal::{self, SigHandler};
 use std::{
-    fs,
     io::{self, Write},
     ops::{Deref, DerefMut},
-    path::Path,
     sync::{atomic::Ordering, Arc, Mutex},
     time::SystemTime,
 };
@@ -52,26 +50,10 @@ use std::{
 /// Errors from execution
 #[derive(Debug, Error)]
 pub enum IonError {
-    /// The fork failed
-    #[error(display = "failed to fork: {}", _0)]
-    Fork(#[error(cause)] nix::Error),
-    /// Failed to setup capturing for function
-    #[error(display = "error reading stdout of child: {}", _0)]
-    CaptureFailed(#[error(cause)] io::Error),
-    /// The variable/function does not exist
-    #[error(display = "element does not exist")]
-    DoesNotExist,
-    /// The input is not properly terminated
-    #[error(display = "input was not terminated")]
-    Unterminated,
     /// Function execution error
     #[error(display = "function error: {}", _0)]
     Function(#[error(cause)] FunctionError),
 
-    /// Unclosed block
-    #[error(display = "unexpected end of script: expected end block for `{}`", _0)]
-    UnclosedBlock(String),
-
     /// Parsing failed
     #[error(display = "syntax error: {}", _0)]
     InvalidSyntax(#[error(cause)] ParseError),
@@ -84,12 +66,6 @@ pub enum IonError {
     #[error(display = "statement error: {}", _0)]
     UnterminatedStatementError(#[error(cause)] StatementError),
 
-    /// Found end without associated block
-    #[error(display = "could not exit the current block since it does not exist!")]
-    EmptyBlock,
-    /// Could not execute file
-    #[error(display = "could not execute file '{}': {}", _0, _1)]
-    FileExecutionError(String, #[error(cause)] io::Error),
     /// Failed to run a pipeline
     #[error(display = "pipeline execution error: {}", _0)]
     PipelineExecutionError(#[error(cause)] PipelineError),
@@ -118,10 +94,6 @@ impl From<PipelineError> for IonError {
     fn from(cause: PipelineError) -> Self { IonError::PipelineExecutionError(cause) }
 }
 
-impl From<nix::Error> for IonError {
-    fn from(cause: nix::Error) -> Self { IonError::Fork(cause) }
-}
-
 impl From<expansion::Error<IonError>> for IonError {
     fn from(cause: expansion::Error<Self>) -> Self { IonError::ExpansionError(cause) }
 }
@@ -249,8 +221,8 @@ impl<'a> Shell<'a> {
     pub fn reset_flow(&mut self) { self.flow_control.clear(); }
 
     /// Exit the current block
-    pub fn exit_block(&mut self) -> Result<(), IonError> {
-        self.flow_control.pop().map(|_| ()).ok_or(IonError::EmptyBlock)
+    pub fn exit_block(&mut self) -> Result<(), BlockError> {
+        self.flow_control.pop().map(|_| ()).ok_or(BlockError::UnmatchedEnd)
     }
 
     /// Get the depth of the current block
@@ -263,39 +235,22 @@ impl<'a> Shell<'a> {
     /// The method is non-blocking, and therefore will immediately return file handles to the
     /// stdout and stderr of the child. The PID of the child is returned, which may be used to
     /// wait for and obtain the exit status.
-    pub fn fork<F: FnMut(&mut Self) -> Result<(), IonError>>(
+    fn fork<F: FnMut(&mut Self) -> Result<(), IonError>>(
         &self,
         capture: Capture,
         child_func: F,
-    ) -> Result<IonResult, IonError> {
+    ) -> nix::Result<IonResult> {
         Fork::new(self, capture).exec(child_func)
     }
 
-    /// A method for executing a function with the given `name`, using `args` as the input.
-    /// If the function does not exist, an `IonError::DoesNotExist` is returned.
+    /// A method for executing a function, using `args` as the input.
     pub fn execute_function<S: AsRef<str>>(
         &mut self,
-        name: &str,
+        function: &Function<'a>,
         args: &[S],
     ) -> Result<Status, IonError> {
-        if let Some(Value::Function(function)) = self.variables.get(name).cloned() {
-            function.execute(self, args)?;
-            Ok(self.previous_status)
-        } else {
-            Err(IonError::DoesNotExist)
-        }
-    }
-
-    /// A method for executing scripts in the Ion shell without capturing. Given a `Path`, this
-    /// method will attempt to execute that file as a script, and then returns the final exit
-    /// status of the evaluated script.
-    pub fn execute_file<P: AsRef<Path>>(&mut self, script: P) -> Result<Status, IonError> {
-        match fs::File::open(script.as_ref()) {
-            Ok(script) => self.execute_command(std::io::BufReader::new(script)),
-            Err(cause) => {
-                Err(IonError::FileExecutionError(script.as_ref().to_string_lossy().into(), cause))
-            }
-        }
+        function.clone().execute(self, args)?;
+        Ok(self.previous_status)
     }
 
     /// A method for executing commands in the Ion shell without capturing. It takes command(s)
@@ -316,7 +271,7 @@ impl<'a> Shell<'a> {
 
         if let Some(block) = self.flow_control.last().map(Statement::to_string) {
             self.previous_status = Status::from_exit_code(1);
-            Err(IonError::UnclosedBlock(block))
+            Err(IonError::StatementFlowError(BlockError::UnclosedBlock(block)))
         } else {
             Ok(self.previous_status)
         }
diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs
index f604fb6a..f7ca7064 100644
--- a/src/lib/shell/pipe_exec/mod.rs
+++ b/src/lib/shell/pipe_exec/mod.rs
@@ -68,6 +68,13 @@ pub enum RedirectError {
 /// This is created when Ion fails to create a pipeline
 #[derive(Debug, Error)]
 pub enum PipelineError {
+    /// The fork failed
+    #[error(display = "failed to fork: {}", _0)]
+    Fork(#[error(cause)] nix::Error),
+    /// Failed to setup capturing for function
+    #[error(display = "error reading stdout of child: {}", _0)]
+    CaptureFailed(#[error(cause)] io::Error),
+
     /// Could not set the pipe as a redirection
     #[error(display = "{}", _0)]
     RedirectPipeError(#[error(cause)] RedirectError),
diff --git a/src/lib/shell/shell_expand.rs b/src/lib/shell/shell_expand.rs
index f9eb6f6c..a1d4efff 100644
--- a/src/lib/shell/shell_expand.rs
+++ b/src/lib/shell/shell_expand.rs
@@ -1,4 +1,4 @@
-use super::{fork::Capture, sys::variables, variables::Value, IonError, Shell};
+use super::{fork::Capture, sys::variables, variables::Value, IonError, PipelineError, Shell};
 use crate::{
     expansion::{Error, Expander, Result, Select},
     types,
@@ -11,19 +11,18 @@ impl<'a, 'b> Expander for Shell<'b> {
 
     /// Uses a subshell to expand a given command.
     fn command(&self, command: &str) -> Result<types::Str, Self::Error> {
-        let output = self
+        let result = self
             .fork(Capture::StdoutThenIgnoreStderr, move |shell| shell.on_command(command))
-            .and_then(|result| {
-                let mut string = String::with_capacity(1024);
-                match result.stdout.unwrap().read_to_string(&mut string) {
-                    Ok(_) => Ok(string),
-                    Err(why) => Err(IonError::CaptureFailed(why)),
-                }
-            });
+            .map_err(|err| Error::Subprocess(Box::new(PipelineError::Fork(err).into())))?;
+        let mut string = String::with_capacity(1024);
+        let output = match result.stdout.unwrap().read_to_string(&mut string) {
+            Ok(_) => Ok(string.into()),
+            Err(why) => Err(Error::Subprocess(Box::new(PipelineError::CaptureFailed(why).into()))),
+        };
 
         // Ensure that the parent retains ownership of the terminal before exiting.
         let _ = tcsetpgrp(nix::libc::STDIN_FILENO, Pid::this());
-        output.map(Into::into).map_err(|err| Error::Subprocess(Box::new(err)))
+        output
     }
 
     /// Expand a string variable given if its quoted / unquoted
diff --git a/src/main.rs b/src/main.rs
index 0f20a53b..2f00d859 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,6 +7,7 @@ use nix::{
     unistd,
 };
 use std::{
+    fs,
     io::{stdin, BufReader},
     process,
 };
@@ -175,7 +176,13 @@ fn main() {
     let err = if let Some(command) = command_line_args.command {
         shell.execute_command(command.as_bytes())
     } else if let Some(path) = script_path {
-        shell.execute_file(path)
+        match fs::File::open(&path) {
+            Ok(script) => shell.execute_command(std::io::BufReader::new(script)),
+            Err(cause) => {
+                println!("ion: could not execute '{}': {}", path, cause);
+                process::exit(1);
+            }
+        }
     } else if stdin_is_a_tty || command_line_args.interactive {
         let mut interactive = InteractiveShell::new(shell);
         if let Some(key_bindings) = command_line_args.key_bindings {
-- 
GitLab