From 0d53220a95f6d2175254e97765124c64e2920310 Mon Sep 17 00:00:00 2001
From: Sag0Sag0 <Sag0Sag0@users.noreply.github.com>
Date: Sun, 26 Nov 2017 10:03:50 +1100
Subject: [PATCH] Add builtin status (#603)

The new status builtin accepts three arguments:
- **-l**: returns true if the shell is a login shell.
- **-i**: returns true if the shell is interactive.
- **-f**: prints the filename of the currently running shell script or if there is none prints stdio.
---
 examples/basic_condition.ion   |   0
 manual/src/ch11-00-builtins.md |  16 ++++-
 setup.ion                      |   2 +-
 src/builtins/mod.rs            |  17 +++++-
 src/builtins/status.rs         | 107 +++++++++++++++++++++++++++++++++
 5 files changed, 137 insertions(+), 5 deletions(-)
 mode change 100644 => 100755 examples/basic_condition.ion
 mode change 100755 => 100644 setup.ion
 create mode 100644 src/builtins/status.rs

diff --git a/examples/basic_condition.ion b/examples/basic_condition.ion
old mode 100644
new mode 100755
diff --git a/manual/src/ch11-00-builtins.md b/manual/src/ch11-00-builtins.md
index 3322ac0b..e9dd023a 100644
--- a/manual/src/ch11-00-builtins.md
+++ b/manual/src/ch11-00-builtins.md
@@ -389,4 +389,18 @@ Waits until all running background processes have completed
 which COMMAND
 ```
 
-Shows the full path of commands
\ No newline at end of file
+Shows the full path of commands
+
+## status
+
+```
+status COMMAND
+```
+
+Evaluates the current runtime status
+
+### Options
+
+- **-l**: returns true if shell is a login shell
+- **-i**: returns true if shell is interactive
+- **-f**: prints the filename of the currently running script or stdio
\ No newline at end of file
diff --git a/setup.ion b/setup.ion
old mode 100755
new mode 100644
index f3c265c0..cce3a906
--- a/setup.ion
+++ b/setup.ion
@@ -77,4 +77,4 @@ match "@args[1..3]"
         sudo rm ${SHAREDIR} -R
     case _
         bad_arg "@args[1..3]"
-end
+end
\ No newline at end of file
diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs
index 499e18bb..0c12399d 100644
--- a/src/builtins/mod.rs
+++ b/src/builtins/mod.rs
@@ -9,6 +9,7 @@ mod job_control;
 mod test;
 mod echo;
 mod set;
+mod status;
 mod exists;
 mod ion;
 
@@ -17,6 +18,7 @@ use self::echo::echo;
 use self::exists::exists;
 use self::functions::fn_;
 use self::ion::ion_docs;
+use self::status::status;
 use self::source::source;
 use self::test::test;
 use self::variables::{alias, drop_alias, drop_array, drop_variable};
@@ -52,6 +54,7 @@ macro_rules! map {
     }
 }}
 
+/// Builtins are in A-Z order.
 pub const BUILTINS: &'static BuiltinMap = &map!(
     "alias" => builtin_alias : "View, set or unset aliases",
     "and" => builtin_and : "Execute the command if the shell's previous status is success",
@@ -125,9 +128,17 @@ impl BuiltinMap {
 }
 
 // Definitions of simple builtins go here
-
-// TODO: dummy function
-fn builtin_status(_: &[&str], _: &mut Shell) -> i32 { SUCCESS }
+fn builtin_status(args: &[&str], shell: &mut Shell) -> i32 {
+    match status(args, shell) {
+        Ok(()) => SUCCESS,
+        Err(why) => {
+            let stderr = io::stderr();
+            let mut stderr = stderr.lock();
+            let _ = stderr.write_all(why.as_bytes());
+            FAILURE
+        }
+    }
+}
 
 pub fn builtin_cd(args: &[&str], shell: &mut Shell) -> i32 {
     match shell.directory_stack.cd(args, &shell.variables) {
diff --git a/src/builtins/status.rs b/src/builtins/status.rs
new file mode 100644
index 00000000..21f83d1c
--- /dev/null
+++ b/src/builtins/status.rs
@@ -0,0 +1,107 @@
+use std::env;
+use std::error::Error;
+use std::io::{Write, stdout};
+
+use shell::Shell;
+
+bitflags! {
+    struct Flags : u8 {
+        const HELP = 1;
+        const LOGIN_SHELL = 2;
+        const INTERACTIVE = 4;
+        const FILENAME = 8;
+    }
+}
+
+const MAN_PAGE: &'static str = r#"NAME
+    status - Evaluates the current runtime status
+
+SYNOPSIS
+    status [ -h | --help ] [-l] [-i]
+
+DESCRIPTION
+    With no arguments status displays the current login information of the shell.
+
+OPTIONS
+    -l
+        returns true if shell is a login shell. Also --is-login.
+    -i
+        returns true if shell is interactive. Also --is-interactive.
+    -f
+        prints the filename of the currently running script or stdio. Also --current-filename.
+"#; // @MANEND
+
+pub(crate) fn status(args: &[&str], shell: &mut Shell) -> Result<(), String> {
+    let mut flags = Flags::empty();
+    let shell_args: Vec<_> = env::args().collect();
+
+    let mut is_login = false;
+    if shell_args[0].chars().nth(0).unwrap() == '-' {
+        is_login = true;
+    }
+
+    let args_len = args.len();
+    if args_len == 1 {
+        if is_login {
+            println!("This is a login shell");
+        } else {
+            println!("This is not a login shell");
+        }
+    } else if args_len > 2 {
+        return Err("status takes one argument\n".to_string());
+    } else {
+        for arg in args {
+            match *arg {
+                "--help" => flags |= Flags::HELP,
+                "--is-login" => flags |= Flags::LOGIN_SHELL,
+                "--is-interactive" => flags |= Flags::INTERACTIVE,
+                "--current-filename" => flags |= Flags::FILENAME,
+                _ => if arg.starts_with('-') {
+                    match arg.chars().nth(1).unwrap() {
+                        'h' => flags |= Flags::HELP,
+                        'l' => flags |= Flags::LOGIN_SHELL,
+                        'i' => flags |= Flags::INTERACTIVE,
+                        'f' => flags |= Flags::FILENAME,
+                        _ => ()
+                    }
+                }
+            }
+        }
+        let err = "".to_string();
+
+        if flags.contains(Flags::LOGIN_SHELL) && !is_login {
+            return Err(err);
+        }
+
+        if flags.contains(Flags::INTERACTIVE) {
+            if shell.is_background_shell || shell.is_library {
+                return Err(err);
+            }
+        }
+
+        if flags.contains(Flags::FILENAME) {
+            // TODO: This technique will not work if ion is renamed. 
+            let sa_len = shell_args.len() - 1;
+            let last_sa = &shell_args[sa_len];
+            let last_3: String = last_sa[last_sa.len() - 3 .. last_sa.len()].to_string();
+
+            if last_3 == "ion" {
+                println!("stdio");
+            } else {
+                println!("{}", last_sa);
+            }
+        }
+
+        let stdout = stdout();
+        let mut stdout = stdout.lock();
+
+        if flags.contains(Flags::HELP) {
+            return match stdout.write_all(MAN_PAGE.as_bytes()).and_then(|_| stdout.flush()) {
+                Ok(_) => Ok(()),
+                Err(err) => Err(err.description().to_owned()),
+            }
+        }
+    }
+    Ok(())
+}
+
-- 
GitLab