From 4e5ec035251bf3d8227cf180b445902cc6a033c9 Mon Sep 17 00:00:00 2001
From: Michael Murphy <mmstickman@gmail.com>
Date: Fri, 22 Jun 2018 22:11:08 -0600
Subject: [PATCH] Make an ion_builtins workspace member

---
 Cargo.lock                                    |  12 +
 Cargo.toml                                    |   3 +-
 ion_builtins/Cargo.toml                       |  12 +
 .../lib/builtins => ion_builtins/src}/calc.rs |   4 +-
 ion_builtins/src/conditionals.rs              |  18 ++
 .../lib/builtins => ion_builtins/src}/echo.rs |   2 +-
 ion_builtins/src/lib.rs                       |  12 +
 .../builtins => ion_builtins/src}/random.rs   |  13 +-
 .../lib/builtins => ion_builtins/src}/test.rs | 110 ++++++++-
 src/lib/builtins/README.md                    |  35 ---
 src/lib/builtins/conditionals.rs              |  31 ---
 src/lib/builtins/ion.rs                       |  23 --
 src/lib/builtins/man_pages.rs                 | 223 +++---------------
 src/lib/builtins/mod.rs                       |  23 +-
 src/lib/builtins/status.rs                    |   4 +-
 src/lib/builtins/time.rs                      |  63 -----
 src/lib/lib.rs                                |   1 +
 17 files changed, 223 insertions(+), 366 deletions(-)
 create mode 100644 ion_builtins/Cargo.toml
 rename {src/lib/builtins => ion_builtins/src}/calc.rs (92%)
 create mode 100644 ion_builtins/src/conditionals.rs
 rename {src/lib/builtins => ion_builtins/src}/echo.rs (98%)
 create mode 100644 ion_builtins/src/lib.rs
 rename {src/lib/builtins => ion_builtins/src}/random.rs (85%)
 rename {src/lib/builtins => ion_builtins/src}/test.rs (84%)
 delete mode 100644 src/lib/builtins/README.md
 delete mode 100644 src/lib/builtins/conditionals.rs
 delete mode 100644 src/lib/builtins/ion.rs
 delete mode 100644 src/lib/builtins/time.rs

diff --git a/Cargo.lock b/Cargo.lock
index 25806ed1..68fe9b62 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -165,6 +165,7 @@ dependencies = [
  "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ion_builtins 0.1.0",
  "ion_sys 0.1.0",
  "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -180,6 +181,17 @@ dependencies = [
  "xdg 2.1.0 (git+https://github.com/whitequark/rust-xdg)",
 ]
 
+[[package]]
+name = "ion_builtins"
+version = "0.1.0"
+dependencies = [
+ "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "calculate 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smallvec 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "ion_sys"
 version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index 496d6d1d..ee2c9710 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,7 @@ repository = "https://gitlab.redox-os.org/redox-os/ion"
 version = "1.0.0-alpha"
 
 [workspace]
-members = ["ion_sys"]
+members = [ "ion_builtins", "ion_sys" ]
 
 [[bin]]
 name = "ion"
@@ -44,6 +44,7 @@ smallstring = "0.1"
 smallvec = "0.6"
 unicode-segmentation = "1.2"
 xdg = { git = "https://github.com/whitequark/rust-xdg" }
+ion_builtins = { path = "ion_builtins" }
 ion_sys = { path = "ion_sys" }
 
 [lib]
diff --git a/ion_builtins/Cargo.toml b/ion_builtins/Cargo.toml
new file mode 100644
index 00000000..25a796d1
--- /dev/null
+++ b/ion_builtins/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "ion_builtins"
+version = "0.1.0"
+authors = ["Michael Murphy <mmstickman@gmail.com>"]
+publish = false
+
+[dependencies]
+bitflags = "1.0"
+calculate = "0.5"
+rand = "0.5"
+smallstring = "0.1"
+smallvec = "0.6"
diff --git a/src/lib/builtins/calc.rs b/ion_builtins/src/calc.rs
similarity index 92%
rename from src/lib/builtins/calc.rs
rename to ion_builtins/src/calc.rs
index 95d90499..62edabd8 100644
--- a/src/lib/builtins/calc.rs
+++ b/ion_builtins/src/calc.rs
@@ -1,4 +1,4 @@
-use calc::{eval, eval_polish, CalcError, Value};
+use calculate::{eval, eval_polish, CalcError, Value};
 use std::io::{self, Write};
 
 fn calc_or_polish_calc(args: &str) -> Result<Value, CalcError> {
@@ -8,7 +8,7 @@ fn calc_or_polish_calc(args: &str) -> Result<Value, CalcError> {
     }
 }
 
-pub(crate) fn calc(args: &[String]) -> Result<(), String> {
+pub fn calc(args: &[String]) -> Result<(), String> {
     let stdout = io::stdout();
     let mut stdout = stdout.lock();
     if !args.is_empty() {
diff --git a/ion_builtins/src/conditionals.rs b/ion_builtins/src/conditionals.rs
new file mode 100644
index 00000000..e4f5751c
--- /dev/null
+++ b/ion_builtins/src/conditionals.rs
@@ -0,0 +1,18 @@
+macro_rules! string_function {
+    ($method:tt) => {
+        pub fn $method(args: &[String]) -> i32 {
+            match args.len() {
+                0...2 => {
+                    eprintln!("ion: {}: two arguments must be supplied", args[0]);
+                    return 2;
+                }
+                3 => if args[1].$method(&args[2]) { 0 } else { 1 },
+                _ => if args[2..].iter().any(|arg| args[1].$method(arg)) { 0 } else { 1 }
+            }
+        }
+    };
+}
+
+string_function!(starts_with);
+string_function!(ends_with);
+string_function!(contains);
diff --git a/src/lib/builtins/echo.rs b/ion_builtins/src/echo.rs
similarity index 98%
rename from src/lib/builtins/echo.rs
rename to ion_builtins/src/echo.rs
index 6b7f948d..52a2af0d 100644
--- a/src/lib/builtins/echo.rs
+++ b/ion_builtins/src/echo.rs
@@ -9,7 +9,7 @@ bitflags! {
     }
 }
 
-pub(crate) fn echo(args: &[String]) -> Result<(), io::Error> {
+pub fn echo(args: &[String]) -> Result<(), io::Error> {
     let mut flags = Flags::empty();
     let mut data: SmallVec<[&str; 16]> = SmallVec::with_capacity(16);
 
diff --git a/ion_builtins/src/lib.rs b/ion_builtins/src/lib.rs
new file mode 100644
index 00000000..a2cce04c
--- /dev/null
+++ b/ion_builtins/src/lib.rs
@@ -0,0 +1,12 @@
+#[macro_use]
+extern crate bitflags;
+extern crate calc as calculate;
+extern crate rand;
+extern crate smallstring;
+extern crate smallvec;
+
+pub mod calc;
+pub mod conditionals;
+pub mod echo;
+pub mod random;
+pub mod test;
diff --git a/src/lib/builtins/random.rs b/ion_builtins/src/random.rs
similarity index 85%
rename from src/lib/builtins/random.rs
rename to ion_builtins/src/random.rs
index 9acedf41..fade40ba 100644
--- a/src/lib/builtins/random.rs
+++ b/ion_builtins/src/random.rs
@@ -1,5 +1,4 @@
-extern crate rand;
-use self::rand::Rng;
+use rand::{thread_rng, Rng};
 use std::io::{self, Write};
 
 #[allow(unused_must_use)]
@@ -12,7 +11,7 @@ fn rand_list(args: &[String]) -> Result<(), String> {
         Err(_) => return Err(String::from("Invalid argument for random")),
     };
     while output.len() < arg1 {
-        let rand_num = rand::thread_rng().gen_range(1, args.len());
+        let rand_num = thread_rng().gen_range(1, args.len());
         output.push(&*args[rand_num]);
         output.dedup();
     }
@@ -23,12 +22,12 @@ fn rand_list(args: &[String]) -> Result<(), String> {
     Ok(())
 }
 #[allow(unused_must_use)]
-pub(crate) fn random(args: &[String]) -> Result<(), String> {
+pub fn random(args: &[String]) -> Result<(), String> {
     let stdout = io::stdout();
     let mut stdout = stdout.lock();
     match args.len() {
         0 => {
-            let rand_num = rand::thread_rng().gen_range(0, 32767);
+            let rand_num = thread_rng().gen_range(0, 32767);
             writeln!(stdout, "{}", rand_num);
         }
         1 => {
@@ -49,7 +48,7 @@ pub(crate) fn random(args: &[String]) -> Result<(), String> {
             if arg2 <= arg1 {
                 return Err(String::from("END must be greater than START"));
             }
-            let rand_num = rand::thread_rng().gen_range(arg1, arg2);
+            let rand_num = thread_rng().gen_range(arg1, arg2);
             writeln!(stdout, "{}", rand_num);
         }
         3 => {
@@ -70,7 +69,7 @@ pub(crate) fn random(args: &[String]) -> Result<(), String> {
                     if arg1 / arg2 >= end {
                         end += 1;
                     }
-                    let rand_num = rand::thread_rng().gen_range(arg1 / arg2, end);
+                    let rand_num = thread_rng().gen_range(arg1 / arg2, end);
                     writeln!(stdout, "{}", rand_num * arg2);
                 }
                 Err(_) => return rand_list(args),
diff --git a/src/lib/builtins/test.rs b/ion_builtins/src/test.rs
similarity index 84%
rename from src/lib/builtins/test.rs
rename to ion_builtins/src/test.rs
index 0ed3d466..df246358 100644
--- a/src/lib/builtins/test.rs
+++ b/ion_builtins/src/test.rs
@@ -1,10 +1,114 @@
-use super::man_pages::{print_man, MAN_TEST};
 use smallstring::SmallString;
 use std::{
     fs, os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}, path::Path, time::SystemTime,
 };
 
-pub(crate) fn test(args: &[String]) -> Result<bool, String> {
+pub const MAN_TEST: &str = r#"NAME
+    test - perform tests on files and text
+
+SYNOPSIS
+    test [EXPRESSION]
+
+DESCRIPTION
+    Tests the expressions given and returns an exit status of 0 if true, else 1.
+
+OPTIONS
+    -n STRING
+        the length of STRING is nonzero
+
+    STRING
+        equivalent to -n STRING
+
+    -z STRING
+        the length of STRING is zero
+
+    STRING = STRING
+        the strings are equivalent
+
+    STRING != STRING
+        the strings are not equal
+
+    INTEGER -eq INTEGER
+        the integers are equal
+
+    INTEGER -ge INTEGER
+        the first INTEGER is greater than or equal to the first INTEGER
+
+    INTEGER -gt INTEGER
+        the first INTEGER is greater than the first INTEGER
+
+    INTEGER -le INTEGER
+        the first INTEGER is less than or equal to the first INTEGER
+
+    INTEGER -lt INTEGER
+        the first INTEGER is less than the first INTEGER
+
+    INTEGER -ne INTEGER
+        the first INTEGER is not equal to the first INTEGER
+
+    FILE -ef FILE
+        both files have the same device and inode numbers
+
+    FILE -nt FILE
+        the first FILE is newer than the second FILE
+
+    FILE -ot FILE
+        the first file is older than the second FILE
+
+    -b FILE
+        FILE exists and is a block device
+
+    -c FILE
+        FILE exists and is a character device
+
+    -d FILE
+        FILE exists and is a directory
+
+    -e FILE
+        FILE exists
+
+    -f FILE
+        FILE exists and is a regular file
+
+    -h FILE
+        FILE exists and is a symbolic link (same as -L)
+
+    -L FILE
+        FILE exists and is a symbolic link (same as -h)
+
+    -r FILE
+        FILE exists and read permission is granted
+
+    -s FILE
+        FILE exists and has a file size greater than zero
+
+    -S FILE
+        FILE exists and is a socket
+
+    -w FILE
+        FILE exists and write permission is granted
+
+    -x FILE
+        FILE exists and execute (or search) permission is granted
+
+EXAMPLES
+    Test if the file exists:
+        test -e FILE && echo "The FILE exists" || echo "The FILE does not exist"
+
+    Test if the file exists and is a regular file, and if so, write to it:
+        test -f FILE && echo "Hello, FILE" >> FILE || echo "Cannot write to a directory"
+
+    Test if 10 is greater than 5:
+        test 10 -gt 5 && echo "10 is greater than 5" || echo "10 is not greater than 5"
+
+    Test if the user is running a 64-bit OS (POSIX environment only):
+        test $(getconf LONG_BIT) = 64 && echo "64-bit OS" || echo "32-bit OS"
+
+AUTHOR
+    Written by Michael Murphy.
+"#;
+
+pub fn test(args: &[String]) -> Result<bool, String> {
     let arguments = &args[1..];
     evaluate_arguments(arguments)
 }
@@ -26,7 +130,7 @@ fn evaluate_arguments(arguments: &[String]) -> Result<bool, String> {
         Some(ref s) if *s == "--help" => {
             // "--help" only makes sense if it is the first option. Only look for it
             // in the first position.
-            print_man(MAN_TEST);
+            println!("{}", MAN_TEST);
             Ok(true)
         }
         Some(arg) => {
diff --git a/src/lib/builtins/README.md b/src/lib/builtins/README.md
deleted file mode 100644
index 480ad68f..00000000
--- a/src/lib/builtins/README.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Ion Shell Builtins
-
-This directory contains the source code of Ion's builtin commands and documentation for their usage.
-
-## calc.rs
-
-Source code for the calc command, which allows for basic command-line f32-based arithmetic.
-
-## echo.rs
-
-Source code for the echo command, included for performance reasons.
-
-## functions.rs
-
-Functions for printing a list of function when the fn keyword is called by itself.
-
-## source.rs
-
-The source command evaluates a supplied script.
-
-## test.rs
-
-Source code for the test command, which is also included for performance reasons.
-
-## time.rs
-
-Source code for the time command, which is used to evaluate the time spent running an external process.
-
-## variables.rs
-
-The **variables.rs** module contains commands relating to setting and removing aliases, variables, and exports.
-
-## status.rs
-
-The source for status command, which is used to get information at runtime about the shell.
diff --git a/src/lib/builtins/conditionals.rs b/src/lib/builtins/conditionals.rs
deleted file mode 100644
index 9f21e199..00000000
--- a/src/lib/builtins/conditionals.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use shell::{status::*, Shell};
-
-macro_rules! string_function {
-    ($method:tt) => {
-        pub(crate) fn $method(args: &[String], _: &mut Shell) -> i32 {
-            match args.len() {
-                0...2 => {
-                    eprintln!("ion: {}: two arguments must be supplied", args[0]);
-                    return BAD_ARG;
-                }
-                3 => if args[1].$method(&args[2]) {
-                    SUCCESS
-                } else {
-                    FAILURE
-                },
-                _ => {
-                    for arg in args[2..].iter() {
-                        if args[1].$method(arg) {
-                            return SUCCESS;
-                        }
-                    }
-                    FAILURE
-                }
-            }
-        }
-    };
-}
-
-string_function!(starts_with);
-string_function!(ends_with);
-string_function!(contains);
diff --git a/src/lib/builtins/ion.rs b/src/lib/builtins/ion.rs
deleted file mode 100644
index f2c5565a..00000000
--- a/src/lib/builtins/ion.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use shell::{status::*, Shell};
-use std::path::Path;
-
-use std::process::Command;
-
-const DOCPATH: &str = "/usr/share/ion/docs/index.html";
-
-pub(crate) fn ion_docs(_: &[String], shell: &mut Shell) -> i32 {
-    if !Path::new(DOCPATH).exists() {
-        eprintln!("ion: ion shell documentation is not installed");
-        return FAILURE;
-    }
-
-    if let Some(cmd) = shell.get_var("BROWSER") {
-        if Command::new(&cmd).arg(DOCPATH).spawn().is_ok() {
-            return SUCCESS;
-        }
-    } else {
-        eprintln!("ion: BROWSER variable isn't defined");
-    }
-
-    FAILURE
-}
diff --git a/src/lib/builtins/man_pages.rs b/src/lib/builtins/man_pages.rs
index 11dd2349..b0cabcd7 100644
--- a/src/lib/builtins/man_pages.rs
+++ b/src/lib/builtins/man_pages.rs
@@ -1,29 +1,14 @@
-use std::{
-    error::Error, io::{stdout, Write},
-};
-
-pub(crate) fn print_man(man_page: &'static str) {
-    let stdout = stdout();
-    let mut stdout = stdout.lock();
-    match stdout
-        .write_all(man_page.as_bytes())
-        .and_then(|_| stdout.flush())
-    {
-        Ok(_) => (),
-        Err(err) => panic!("{}", err.description().to_owned()),
-    }
-}
-
 pub(crate) fn check_help(args: &[String], man_page: &'static str) -> bool {
     for arg in args {
         if &**arg == "-h" || &**arg == "--help" {
-            print_man(man_page);
+            println!("{}", man_page);
             return true;
         }
     }
     false
 }
 
+
 pub(crate) const MAN_STATUS: &str = r#"NAME
     status - Evaluates the current runtime status
 
@@ -39,8 +24,7 @@ OPTIONS
     -i
         returns true if the shell is interactive. Also --is-interactive.
     -f
-        prints the filename of the currently running script or else stdio. Also --current-filename.
-"#;
+        prints the filename of the currently running script or else stdio. Also --current-filename."#;
 
 pub(crate) const MAN_CD: &str = r#"NAME
     cd - Change directory.
@@ -52,7 +36,6 @@ DESCRIPTION
     Without arguments cd changes the working directory to your home directory.
 
     With arguments cd changes the working directory to the directory you provided.
-
 "#;
 
 pub(crate) const MAN_BOOL: &str = r#"NAME
@@ -62,8 +45,7 @@ SYNOPSIS
     bool VALUE
 
 DESCRIPTION
-    Returns true if the value given to it is equal to '1' or 'true'.
-"#;
+    Returns true if the value given to it is equal to '1' or 'true'."#;
 
 pub(crate) const MAN_IS: &str = r#"NAME
     is - Checks if two arguments are the same
@@ -76,8 +58,7 @@ DESCRIPTION
 
 OPTIONS
     not
-        returns 0 if the two arguments are not equal.
-"#;
+        returns 0 if the two arguments are not equal."#;
 
 pub(crate) const MAN_ISATTY: &str = r#"
     isatty - Checks if argument is a file descriptor
@@ -86,8 +67,7 @@ SYNOPSIS
     isatty [FD]
 
 DESCRIPTION
-    Returns 0 exit status if the supplied file descriptor is a tty.
-"#;
+    Returns 0 exit status if the supplied file descriptor is a tty."#;
 
 pub(crate) const MAN_DIRS: &str = r#"NAME
     dirs - prints the directory stack
@@ -96,8 +76,7 @@ SYNOPSIS
     dirs
 
 DESCRIPTION
-    dirs prints the current directory stack.
-"#;
+    dirs prints the current directory stack."#;
 
 pub(crate) const MAN_PUSHD: &str = r#"NAME
     pushd - push a directory to the directory stack
@@ -106,8 +85,7 @@ SYNOPSIS
     pushd DIRECTORY
 
 DESCRIPTION
-    pushd pushes a directory to the directory stack.
-"#;
+    pushd pushes a directory to the directory stack."#;
 
 pub(crate) const MAN_POPD: &str = r#"NAME
     popd - shift through the directory stack
@@ -116,9 +94,8 @@ SYNOPSIS
     popd
 
 DESCRIPTION
-    popd removes the top directory from the directory stack and changes the working directory to the new top directory. 
-    pushd adds directories to the stack.
-"#;
+    popd removes the top directory from the directory stack and changes the working directory to the new top directory.
+    pushd adds directories to the stack."#;
 
 // pub(crate) const MAN_FN: &str = r#"NAME
 // fn - print a list of all functions or create a function
@@ -146,7 +123,7 @@ DESCRIPTION
 // end
 //
 // example 1
-// "#;
+//"#;
 
 pub(crate) const MAN_READ: &str = r#"NAME
     read - read a line of input into some variables
@@ -155,8 +132,7 @@ SYNOPSIS
     read VARIABLES...
 
 DESCRIPTION
-    For each variable reads from standard input and stores the results in the variable.
-"#;
+    For each variable reads from standard input and stores the results in the variable."#;
 
 pub(crate) const MAN_DROP: &str = r#"NAME
     drop - delete some variables or arrays
@@ -170,8 +146,7 @@ DESCRIPTION
 
 OPTIONS
     -a
-        Instead of deleting variables deletes arrays.
-"#;
+        Instead of deleting variables deletes arrays."#;
 
 pub(crate) const MAN_SET: &str = r#"NAME
     set - Set or unset values of shell options and positional parameters.
@@ -194,8 +169,7 @@ OPTIONS
         If no argument are supplied, arguments will be unset.
 
     -   Following arguments will be set as positional arguments in the shell.
-        If no arguments are suppled, arguments will not be unset.
-"#;
+        If no arguments are suppled, arguments will not be unset."#;
 
 pub(crate) const MAN_EQ: &str = r#"NAME
     eq - Checks if two arguments are the same
@@ -208,8 +182,7 @@ DESCRIPTION
 
 OPTIONS
     not
-        returns 0 if the two arguments are not equal.
-"#;
+        returns 0 if the two arguments are not equal."#;
 
 pub(crate) const MAN_EVAL: &str = r#"NAME
     eval - evaluates the specified commands
@@ -219,8 +192,7 @@ SYNOPSIS
 
 DESCRIPTION
     eval evaluates the given arguments as a command. If more than one argument is given,
-    all arguments are joined using a space as a separator.
-"#;
+    all arguments are joined using a space as a separator."#;
 
 pub(crate) const MAN_EXEC: &str = r#"NAME
     exec - Replace the shell with the given command.
@@ -234,8 +206,7 @@ DESCRIPTION
     <command>.
 
 OPTIONS
-    -c  Execute command with an empty environment.
-"#;
+    -c  Execute command with an empty environment."#;
 
 pub(crate) const MAN_HISTORY: &str = r#"NAME
     history - print command history
@@ -244,8 +215,7 @@ SYNOPSIS
     history
 
 DESCRIPTION
-    Prints the command history.
-"#;
+    Prints the command history."#;
 
 pub(crate) const MAN_SOURCE: &str = r#"NAME
     source - evaluates given file
@@ -254,9 +224,8 @@ SYNOPSIS
     source FILEPATH
 
 DESCRIPTION
-    Evaluates the commands in a specified file in the current shell. All changes in shell 
-    variables will affect the current shell because of this.
-"#;
+    Evaluates the commands in a specified file in the current shell. All changes in shell
+    variables will affect the current shell because of this."#;
 
 pub(crate) const MAN_ECHO: &str = r#"NAME
     echo - display a line of text
@@ -286,113 +255,7 @@ OPTIONS
         \n  new line
         \r  carriage return
         \t  horizontal tab (HT)
-        \v  vertical tab (VT)
-"#;
-
-pub(crate) const MAN_TEST: &str = r#"NAME
-    test - perform tests on files and text
-
-SYNOPSIS
-    test [EXPRESSION]
-
-DESCRIPTION
-    Tests the expressions given and returns an exit status of 0 if true, else 1.
-
-OPTIONS
-    -n STRING
-        the length of STRING is nonzero
-
-    STRING
-        equivalent to -n STRING
-
-    -z STRING
-        the length of STRING is zero
-
-    STRING = STRING
-        the strings are equivalent
-
-    STRING != STRING
-        the strings are not equal
-
-    INTEGER -eq INTEGER
-        the integers are equal
-
-    INTEGER -ge INTEGER
-        the first INTEGER is greater than or equal to the first INTEGER
-
-    INTEGER -gt INTEGER
-        the first INTEGER is greater than the first INTEGER
-
-    INTEGER -le INTEGER
-        the first INTEGER is less than or equal to the first INTEGER
-
-    INTEGER -lt INTEGER
-        the first INTEGER is less than the first INTEGER
-
-    INTEGER -ne INTEGER
-        the first INTEGER is not equal to the first INTEGER
-
-    FILE -ef FILE
-        both files have the same device and inode numbers
-
-    FILE -nt FILE
-        the first FILE is newer than the second FILE
-
-    FILE -ot FILE
-        the first file is older than the second FILE
-
-    -b FILE
-        FILE exists and is a block device
-
-    -c FILE
-        FILE exists and is a character device
-
-    -d FILE
-        FILE exists and is a directory
-
-    -e FILE
-        FILE exists
-
-    -f FILE
-        FILE exists and is a regular file
-
-    -h FILE
-        FILE exists and is a symbolic link (same as -L)
-
-    -L FILE
-        FILE exists and is a symbolic link (same as -h)
-
-    -r FILE
-        FILE exists and read permission is granted
-
-    -s FILE
-        FILE exists and has a file size greater than zero
-
-    -S FILE
-        FILE exists and is a socket
-
-    -w FILE
-        FILE exists and write permission is granted
-
-    -x FILE
-        FILE exists and execute (or search) permission is granted
-
-EXAMPLES
-    Test if the file exists:
-        test -e FILE && echo "The FILE exists" || echo "The FILE does not exist"
-
-    Test if the file exists and is a regular file, and if so, write to it:
-        test -f FILE && echo "Hello, FILE" >> FILE || echo "Cannot write to a directory"
-
-    Test if 10 is greater than 5:
-        test 10 -gt 5 && echo "10 is greater than 5" || echo "10 is not greater than 5"
-
-    Test if the user is running a 64-bit OS (POSIX environment only):
-        test $(getconf LONG_BIT) = 64 && echo "64-bit OS" || echo "32-bit OS"
-
-AUTHOR
-    Written by Michael Murphy.
-"#;
+        \v  vertical tab (VT)"#;
 
 pub(crate) const MAN_RANDOM: &str = r#"NAME
     random - generate a random number
@@ -403,9 +266,8 @@ SYNOPSIS
 
 DESCRIPTION
     random generates a pseudo-random integer. IT IS NOT SECURE.
-    The range depends on what arguments you pass. If no arguments are given the range is [0, 32767]. 
-    If two arguments are given the range is [START, END].
-"#;
+    The range depends on what arguments you pass. If no arguments are given the range is [0, 32767].
+    If two arguments are given the range is [START, END]."#;
 
 pub(crate) const MAN_TRUE: &str = r#"NAME
     true - does nothing successfully
@@ -414,8 +276,7 @@ SYNOPSIS
     true
 
 DESCRIPTION
-    Sets the exit status to 0.
-"#;
+    Sets the exit status to 0."#;
 
 pub(crate) const MAN_FALSE: &str = r#"NAME
     false - does nothing unsuccessfully
@@ -424,8 +285,7 @@ SYNOPSIS
     false
 
 DESCRIPTION
-    Sets the exit status to 1.
-"#;
+    Sets the exit status to 1."#;
 
 pub(crate) const MAN_JOBS: &str = r#"NAME
     jobs - list all jobs running in the background
@@ -434,8 +294,7 @@ SYNOPSIS
     jobs
 
 DESCRIPTION
-    Prints a list of all jobs running in the background.
-"#;
+    Prints a list of all jobs running in the background."#;
 
 pub(crate) const MAN_BG: &str = r#"NAME
     bg - sends jobs to background
@@ -444,8 +303,7 @@ SYNOPSIS
     bg PID
 
 DESCRIPTION
-    bg sends the job to the background resuming it if it has stopped.
-"#;
+    bg sends the job to the background resuming it if it has stopped."#;
 
 pub(crate) const MAN_FG: &str = r#"NAME
     fg - bring job to foreground
@@ -454,8 +312,7 @@ SYNOPSIS
     fg PID
 
 DESCRIPTION
-    fg brings the specified job to foreground resuming it if it has stopped.
-"#;
+    fg brings the specified job to foreground resuming it if it has stopped."#;
 
 pub(crate) const MAN_SUSPEND: &str = r#"NAME
     suspend - suspend the current shell
@@ -464,9 +321,8 @@ SYNOPSIS
     suspend
 
 DESCRIPTION
-    Suspends the current shell by sending it the SIGTSTP signal, 
-    returning to the parent process. It can be resumed by sending it SIGCONT.
-"#;
+    Suspends the current shell by sending it the SIGTSTP signal,
+    returning to the parent process. It can be resumed by sending it SIGCONT."#;
 
 pub(crate) const MAN_DISOWN: &str = r#"NAME
     disown - Disown processes
@@ -480,8 +336,7 @@ DESCRIPTION
 OPTIONS
     -r  Remove all running jobs from the background process list.
     -h  Specifies that each job supplied will not receive the SIGHUP signal when the shell receives a SIGHUP.
-    -a  If no job IDs were supplied, remove all jobs from the background process list.
-"#;
+    -a  If no job IDs were supplied, remove all jobs from the background process list."#;
 
 pub(crate) const MAN_EXIT: &str = r#"NAME
     exit - exit the shell
@@ -490,8 +345,7 @@ SYNOPSIS
     exit
 
 DESCRIPTION
-    Makes ion exit. The exit status will be that of the last command executed.
-"#;
+    Makes ion exit. The exit status will be that of the last command executed."#;
 
 pub(crate) const MAN_MATCHES: &str = r#"NAME
     matches - checks if the second argument contains any portion of the first.
@@ -507,8 +361,7 @@ EXAMPLES
     Returns true:
         matches xs x
     Returns false:
-        matches x xs
-"#;
+        matches x xs"#;
 
 pub(crate) const MAN_EXISTS: &str = r#"NAME
     exists - check whether items exist
@@ -564,16 +417,14 @@ EXAMPLES
 
 AUTHOR
     Written by Fabian Würfl.
-    Heavily based on implementation of the test builtin, which was written by Michael Murph.
-"#;
+    Heavily based on implementation of the test builtin, which was written by Michael Murph."#;
 
 pub(crate) const MAN_WHICH: &str = r#"NAME
     which - locate a program file in the current user's path
 
 SYNOPSIS
-    which PROGRAM 
+    which PROGRAM
 
 DESCRIPTION
-    The which utility takes a list of command names and searches for the 
-    alias/builtin/function/executable that would be executed if you ran that command.
-"#;
+    The which utility takes a list of command names and searches for the
+    alias/builtin/function/executable that would be executed if you ran that command."#;
diff --git a/src/lib/builtins/mod.rs b/src/lib/builtins/mod.rs
index abe61c8d..4b7a9bf0 100644
--- a/src/lib/builtins/mod.rs
+++ b/src/lib/builtins/mod.rs
@@ -1,25 +1,21 @@
-pub mod calc;
 pub mod functions;
-pub mod random;
 pub mod source;
 pub mod variables;
 
 mod command_info;
-mod conditionals;
-mod echo;
 mod exec;
 mod exists;
-mod ion;
 mod is;
 mod job_control;
 mod man_pages;
 mod set;
 mod status;
-mod test;
+
+use ion_builtins::{calc, conditionals, echo, random, test};
 
 use self::{
-    command_info::*, conditionals::{contains, ends_with, starts_with}, echo::echo, exec::exec,
-    exists::exists, functions::fn_, ion::ion_docs, is::is, man_pages::*, source::source,
+    command_info::*, echo::echo, exec::exec,
+    exists::exists, functions::fn_, is::is, man_pages::*, source::source,
     status::status, test::test, variables::{alias, drop_alias, drop_array, drop_variable},
 };
 
@@ -83,7 +79,6 @@ pub const BUILTINS: &BuiltinMap = &map!(
     "fn" => builtin_fn : "Print list of functions",
     "help" => builtin_help : HELP_DESC,
     "history" => builtin_history : "Display a log of all commands previously executed",
-    "ion-docs" => ion_docs : "Opens the Ion manual",
     "is" => builtin_is : "Simple alternative to == and !=",
     "isatty" => builtin_isatty : "Returns 0 exit status if the supplied FD is a tty",
     "jobs" => builtin_jobs : "Displays all jobs that are attached to the background",
@@ -136,6 +131,10 @@ impl BuiltinMap {
     }
 }
 
+fn starts_with(args: &[String], _: &mut Shell) -> i32 { conditionals::starts_with(args) }
+fn ends_with(args: &[String], _: &mut Shell) -> i32 { conditionals::ends_with(args) }
+fn contains(args: &[String], _: &mut Shell) -> i32 { conditionals::contains(args) }
+
 // Definitions of simple builtins go here
 fn builtin_status(args: &[String], shell: &mut Shell) -> i32 {
     match status(args, shell) {
@@ -199,8 +198,8 @@ fn builtin_bool(args: &[String], shell: &mut Shell) -> i32 {
         _ => match &*args[1] {
             "1" => (),
             "true" => (),
-            "--help" => print_man(MAN_BOOL),
-            "-h" => print_man(MAN_BOOL),
+            "--help" => println!("{}", MAN_BOOL),
+            "-h" => println!("{}", MAN_BOOL),
             _ => return FAILURE,
         },
     }
@@ -453,7 +452,7 @@ fn builtin_suspend(args: &[String], _: &mut Shell) -> i32 {
 fn builtin_disown(args: &[String], shell: &mut Shell) -> i32 {
     for arg in args {
         if *arg == "--help" {
-            print_man(MAN_DISOWN);
+            println!("{}", MAN_DISOWN);
             return SUCCESS;
         }
     }
diff --git a/src/lib/builtins/status.rs b/src/lib/builtins/status.rs
index 0838f894..3063f716 100644
--- a/src/lib/builtins/status.rs
+++ b/src/lib/builtins/status.rs
@@ -1,4 +1,4 @@
-use builtins::man_pages::{print_man, MAN_STATUS};
+use builtins::man_pages::{MAN_STATUS};
 use shell::Shell;
 
 use std::env;
@@ -69,7 +69,7 @@ pub(crate) fn status(args: &[String], shell: &mut Shell) -> Result<(), String> {
         }
 
         if flags.contains(Flags::HELP) {
-            print_man(MAN_STATUS);
+            println!("{}", MAN_STATUS);
         }
     }
     Ok(())
diff --git a/src/lib/builtins/time.rs b/src/lib/builtins/time.rs
deleted file mode 100644
index 5b73b28a..00000000
--- a/src/lib/builtins/time.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-use std::error::Error;
-use std::io::{Write, stdout};
-use std::process::Command;
-use std::time::Instant;
-
-const MAN_PAGE: &'static str = r#"NAME
-    time - timer for commands
-
-SYNOPSIS
-    time [ -h | --help ][COMMAND] [ARGUEMENT]...
-
-DESCRIPTION
-    Runs the command taken as the first arguement and outputs the time the command took to execute.
-
-OPTIONS
-    -h
-    --help
-        display this help and exit
-"#;
-
-pub(crate) fn time(args: &[&str]) -> Result<(), String> {
-    let stdout = stdout();
-    let mut stdout = stdout.lock();
-
-    for arg in args {
-        if *arg == "-h" || *arg == "--help" {
-            return match stdout.write_all(MAN_PAGE.as_bytes()).and_then(
-                |_| stdout.flush(),
-            ) {
-                Ok(_) => Ok(()),
-                Err(err) => Err(err.description().to_owned()),
-            };
-        }
-    }
-
-    let time = Instant::now();
-
-    if !args.is_empty() {
-        let mut command = Command::new(&args[0]);
-        for arg in &args[1..] {
-            command.arg(arg);
-        }
-        command.spawn().and_then(|mut child| child.wait()).map_err(
-            |err| {
-                format!("time: {:?}", err)
-            },
-        )?;
-    }
-
-    let duration = time.elapsed();
-    let seconds = duration.as_secs();
-    let nanoseconds = duration.subsec_nanos();
-
-    if seconds > 60 {
-        write!(stdout, "real    {}m{:02}.{:09}s\n", seconds / 60, seconds % 60, nanoseconds)
-            .map_err(|x| x.description().to_owned())?;
-    } else {
-        write!(stdout, "real    {}.{:09}s\n", seconds, nanoseconds)
-            .map_err(|x| x.description().to_owned())?;
-    }
-
-    Ok(())
-}
diff --git a/src/lib/lib.rs b/src/lib/lib.rs
index e98fe96a..731ea13c 100644
--- a/src/lib/lib.rs
+++ b/src/lib/lib.rs
@@ -31,6 +31,7 @@ extern crate unicode_segmentation;
 extern crate xdg;
 
 pub extern crate ion_sys as sys;
+extern crate ion_builtins;
 
 #[macro_use]
 mod types;
-- 
GitLab