diff --git a/.gitignore b/.gitignore
index e1411e08dd036f8f049b239834637a54733a5ae8..b1068495b4c6331373495a0fe922331423f12776 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ manual/book
 vendor/
 vendor.tar.xz
 /window-config.ion
+manual/builtins/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0b397596fb475a8d5bc9f4afbdfdcff9c945006f..9a728e6b9a9b01e53768af1ac13cb037c7dc8a00 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -45,6 +45,7 @@ pages:
   image: hrektts/mdbook
   stage: deploy
   script:
+  - make manual
   - mdbook build -d ../public manual
   artifacts:
     paths:
diff --git a/Cargo.lock b/Cargo.lock
index e6952f11945e9f5e9f7b174b37e481abef3a7e79..23d3549711237d438f01d87f6eab6278cc37ce97 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -156,6 +156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 name = "builtins-proc"
 version = "0.1.0"
 dependencies = [
+ "darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ion-shell 1.0.0-alpha",
  "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -379,6 +380,38 @@ dependencies = [
  "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "darling"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "decimal"
 version = "2.0.4"
@@ -702,6 +735,11 @@ dependencies = [
  "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "image"
 version = "0.21.2"
@@ -1675,6 +1713,11 @@ dependencies = [
  "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "strsim"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "strsim"
 version = "0.8.0"
@@ -2037,6 +2080,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c"
 "checksum csv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9044e25afb0924b5a5fc5511689b0918629e85d68ea591e5e87fbf1e85ea1b3b"
 "checksum csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5cdef62f37e6ffe7d1f07a381bc0db32b7a3ff1cac0de56cb0d81e71f53d65"
+"checksum darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfbcb0c5961907597a7d1148e3af036268f2b773886b8bb3eeb1e1281d3d3d6"
+"checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c"
+"checksum darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d8dac1c6f1d29a41c4712b4400f878cb4fcc4c7628f298dd75038e024998d1"
 "checksum decimal 2.0.4 (git+https://github.com/alkis/decimal.git)" = "<none>"
 "checksum deflate 0.7.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8a6abb26e16e8d419b5c78662aa9f82857c2386a073da266840e474d5055ec86"
 "checksum derivative 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6073e9676dbebdddeabaeb63e3b7cefd23c86f5c41d381ee1237cc77b1079898"
@@ -2074,6 +2120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum glutin_wgl_sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f801bbc91efc22dd1c4818a47814fc72bf74d024510451b119381579bfa39021"
 "checksum hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1de41fb8dba9714efd92241565cdff73f78508c95697dd56787d3cba27e2353"
 "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
+"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 "checksum image 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "99198e595d012efccf12abf4abc08da2d97be0b0355a2b08d101347527476ba4"
 "checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
 "checksum interpolation 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b7357d2bbc5ee92f8e899ab645233e43d21407573cceb37fed8bc3dede2c02"
@@ -2182,6 +2229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum stackvector 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1c4725650978235083241fab0fdc8e694c3de37821524e7534a1a9061d1068af"
 "checksum static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5"
 "checksum stb_truetype 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "69b7df505db8e81d54ff8be4693421e5b543e08214bd8d99eb761fcb4d5668ba"
+"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
 "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
 "checksum structopt 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "fa19a5a708e22bb5be31c1b6108a2a902f909c4b9ba85cba44c06632386bc0ff"
 "checksum structopt-derive 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d59d0ae8ef8de16e49e3ca7afa16024a3e0dfd974a75ef93fdc5464e34523f"
diff --git a/Cargo.toml b/Cargo.toml
index 6cb85614d4c5bffe14f3bd6d9306f6f9c93aeb1b..924d852a4ee68d06d0375e856855e7c031a43fa5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,6 +29,7 @@ maintenance = { status = "experimental" }
 
 [features]
 advanced_arg_parsing = []
+man = ["builtins-proc/man"]
 
 [workspace]
 members = [ "members/builtins-proc", "members/ranges", "members/scopes-rs", "members/types-rs" ]
diff --git a/Makefile b/Makefile
index 00b23fe1bdcef9987ce0afaec8217beee7c54fd3..3b320dd188678c37817f1212576a8e512f406824 100644
--- a/Makefile
+++ b/Makefile
@@ -30,7 +30,7 @@ ifeq ($(RUSTUP),1)
 	TOOLCHAIN_ARG = +$(TOOLCHAIN)
 endif
 
-.PHONY: tests all clean distclean install uninstall
+.PHONY: tests all clean distclean install uninstall manual
 
 all: $(SRC) $(GIT_REVISION)
 ifeq ($(REDOX),1)
@@ -42,6 +42,14 @@ ifeq ($(VENDORED),1)
 endif
 	cargo $(TOOLCHAIN_ARG) build $(ARGS) $(ARGSV)
 
+manual:
+	cargo build --features man
+	echo -n "# Builtin commands" > manual/src/builtins.md
+	for man in manual/builtins/*; do \
+		echo -n "\n\n## " >> manual/src/builtins.md; \
+		cat $$man >> manual/src/builtins.md; \
+	done
+
 clean:
 	cargo clean
 
diff --git a/manual/src/builtins.md b/manual/src/builtins.md
index ebe9edce43fbc58bb6336f5fa4c72dd2e717b2e2..543b69e5ca2929ffcd5d2f966bfbfa0c8e62e6da 100644
--- a/manual/src/builtins.md
+++ b/manual/src/builtins.md
@@ -1,445 +1,607 @@
-# Builtin Commands
+# Builtin commands
 
-## alias
+## bg - sends jobs to background
 
-```
-alias NAME=DEFINITION
-alias NAME DEFINITION
+```txt
+SYNOPSIS
+    bg PID
+
+DESCRIPTION
+    bg sends the job to the background resuming it if it has stopped.
 ```
 
-View, set or unset aliases
+## bool - Returns true if the value given to it is equal to '1' or 'true'.
 
-## and
+```txt
+SYNOPSIS
+    bool VALUE
 
-```
-COMMAND; and COMMAND
+DESCRIPTION
+    Returns true if the value given to it is equal to '1' or 'true'.
 ```
 
-Execute the command if the shell's previous status is success
+## calc - Floating-point calculator
 
-## bg
+```txt
+SYNOPSIS
+    calc [EXPRESSION]
 
-```
-bg [PID]
-```
+DESCRIPTION
+    Evaluates arithmetic expressions
 
-Resumes a stopped background process. If no process is specified, the previous
-job will resume.
+SPECIAL EXPRESSIONS
+    help (only in interactive mode)
+        prints this help text
 
-## calc
+    --help (only in non-interactive mode)
+        prints this help text
 
-```
-calc [EXPRESSION]
-```
+    exit (only in interactive mode)
+        exits the program
 
-Calculate a mathematical expression. If no expression is given, it will open
-an interactive expression engine. Type exit to leave the engine.
+NOTATIONS
+    infix notation
+        e.g. 3 * 4 + 5
 
-## cd
+    polish notation
+        e.g. + * 3 4 5
 
-```
-cd [PATH]
+EXAMPLES
+    Add two plus two in infix notation
+        calc 2+2
+
+    Add two plus two in polish notation
+        calc + 2 2
+
+AUTHOR
+    Written by Hunter Goldstein.
 ```
 
-Change the current directory and push it to the stack.
-Omit the directory to change to home
+## cd - Change directory.
 
-## contains
+```txt
+SYNOPSIS
+    cd DIRECTORY
 
-```
-contains KEY [VALUE...]
+DESCRIPTION
+    Without arguments cd changes the working directory to your home directory.
+    With arguments cd changes the working directory to the directory you provided.
 ```
 
-Evaluates if the supplied argument contains a given string
+## contains - check if a given string starts with another one
 
-## dirs
+```txt
+SYNOPSIS
+    starts_with <PATTERN> tests...
 
-```
-dirs
+DESCRIPTION
+    Returns 0 if any argument starts_with contains the first argument, else returns 0
 ```
 
-Display the current directory stack
+## dir_depth - set the dir stack depth
 
-## disown
+```txt
+SYNOPSYS
+    dir_depth [DEPTH]
 
-```
-disown [-r | -h | -a ][PID...]
+DESCRIPTION
+    If DEPTH is given, set the dir stack max depth to DEPTH, else remove the limit
 ```
 
-Disowning a process removes that process from the shell's background process table.
-If no process is specified, the most recently-used job is removed
+## dirs - prints the directory stack
 
-## drop
+```txt
+SYNOPSIS
+    dirs
 
-```
-drop VARIABLE
-drop -a ARRAY_VARIABLE
+DESCRIPTION
+    dirs prints the current directory stack.
 ```
 
-Drops a variable from the shell's variable map. By default, this will drop string variables from
-the string variable map. If the `-a` flag is specified, array variables will be dropped from the
-array variable map instead.
+## disown - disown processes
 
-## echo
+```txt
+SYNOPSIS
+    disown [ --help | -r | -h | -a ][PID...]
 
-```
-echo [ -h | --help ] [-e] [-n] [-s] [STRING]...
+DESCRIPTION
+    Disowning a process removes that process from the shell's background process table.
+
+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.
 ```
 
-Display a line of text
+## drop - delete some variables or arrays
 
-#### Options
+```txt
+SYNOPSIS
+    drop VARIABLES...
 
-- **-e**: enable the interpretation of backslash escapes
-- **-n**: do not output the trailing newline
-- **-s**: do not separate arguments with spaces
+DESCRIPTION
+    Deletes the variables given to it as arguments. The variables name must be supplied.
+    Instead of '$x' use 'x'.
+```
 
-#### Escape Sequences
+## echo - display text
 
-When the -e argument is used, the following sequences will be interpreted:
+```txt
+SYNOPSIS
+    echo [ -h | --help ] [-e] [-n] [-s] [STRING]...
 
-- **\\**: backslash
-- **\a**: alert (BEL)
-- **\b**: backspace (BS)
-- **\c**: produce no further output
-- **\e**: escape (ESC)
-- **\f**: form feed (FF)
-- **\n**: new line
-- **\r**: carriage return
-- **\t**: horizontal tab (HT)
-- **\v**: vertical tab (VT)
+DESCRIPTION
+    Print the STRING(s) to standard output.
 
-## ends-with
+OPTIONS
+    -e
+        enable the interpretation of backslash escapes
+    -n
+        do not output the trailing newline
+    -s
+        do not separate arguments with spaces
 
-```
-ends-with KEY [VALUE...]
+    Escape Sequences
+        When the -e argument is used, the following sequences will be interpreted:
+        \\  backslash
+        \a  alert (BEL)
+        \b  backspace (BS)
+        \c  produce no further output
+        \e  escape (ESC)
+        \f  form feed (FF)
+        \n  new line
+        \r  carriage return
+        \t  horizontal tab (HT)
+        \v  vertical tab (VT)
 ```
 
-Evaluates if the supplied argument ends with a given string
+## ends_with - check if a given string starts with another one
 
-## eq
+```txt
+SYNOPSIS
+    starts_with <PATTERN> tests...
 
-```
-eq [ -h | --help ] [not]
+DESCRIPTION
+    Returns 0 if any argument starts_with contains the first argument, else returns 0
 ```
 
-Returns 0 if the two arguments are equal
+## eval - evaluates the specified commands
 
-## eval
+```txt
+SYNOPSIS
+    eval COMMANDS...
 
-```
-eval COMMAND
+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.
 ```
 
-evaluates the evaluated expression
+## exec - replace the shell with the given command
 
-## exists
+```txt
+SYNOPSIS
+    exec [-ch] [--help] [command [arguments ...]]
 
-```
-exists [-a ARRAY] [-b BINARY] [-d PATH] [--fn FUNCTION] [[-s] STRING]
+DESCRIPTION
+    Execute <command>, replacing the shell with the specified program.
+    The <arguments> following the command become the arguments to
+    <command>.
+
+OPTIONS
+    -c  Execute command with an empty environment.
 ```
 
-Performs tests on files and text
+## exists - check whether items exist
 
-## exec
+```txt
+SYNOPSIS
+    exists [EXPRESSION]
 
-```
-exec [-ch] [--help] [command [arguments ...]]
-```
+DESCRIPTION
+    Checks whether the given item exists and returns an exit status of 0 if it does, else 1.
 
-Execute a command, replacing the shell with the specified program.
-The arguments following the command become the arguments to the command.
+OPTIONS
+    -a ARRAY
+        array var is not empty
 
-#### options
+    -b BINARY
+        binary is in PATH
 
-- **-a ARRAY**:      array var is not empty
-- **-b BINARY**:     binary is in PATH
-- **-d PATH**:       path is a directory
-- **-f PATH**:       path is a file
-- **--fn FUNCTION**: function is defined
-- **-s STRING**:     string var is not empty
-- **STRING**:        string is not empty
+    -d PATH
+        path is a directory
+        This is the same as test -d
 
-## exit
+    -f PATH
+        path is a file
+        This is the same as test -f
 
-```
-exit
-```
+    --fn FUNCTION
+        function is defined
 
-Exits the current session and kills all background tasks
+    -s STRING
+        string var is not empty
 
-## false
+    STRING
+        string is not empty
+        This is the same as test -n
 
-```
-false
-```
+EXAMPLES
+    Test if the file exists:
+        exists -f FILE && echo 'The FILE exists' || echo 'The FILE does not exist'
 
-Do nothing, unsuccessfully
+    Test if some-command exists in the path and is executable:
+        exists -b some-command && echo 'some-command exists' || echo 'some-command does not exist'
 
-## fg
+    Test if variable exists AND is not empty
+        exists -s myVar && echo "myVar exists: $myVar" || echo 'myVar does not exist or is empty'
+        NOTE: Don't use the '$' sigil, but only the name of the variable to check
 
-```
-fg [PID]
+    Test if array exists and is not empty
+        exists -a myArr && echo "myArr exists: @myArr" || echo 'myArr does not exist or is empty'
+        NOTE: Don't use the '@' sigil, but only the name of the array to check
+
+    Test if a function named 'myFunc' exists
+        exists --fn myFunc && myFunc || echo 'No function with name myFunc found'
+
+AUTHOR
+    Written by Fabian Würfl.
+    Heavily based on implementation of the test builtin, which was written by Michael Murphy.
 ```
 
-Resumes and sets a background process as the active process. If no process is specified, the previous job will be the active process.
+## exit - exit the shell
 
-## fn
+```txt
+SYNOPSIS
+    exit
 
-```
-fn
+DESCRIPTION
+    Makes ion exit. The exit status will be that of the last command executed.
 ```
 
-Print list of functions
+## false - does nothing unsuccessfully
 
-## help
+```txt
+SYNOPSIS
+    false
 
-```
-help COMMAND
+DESCRIPTION
+    Sets the exit status to 1.
 ```
 
-Display helpful information about a given command or list commands if
-none specified
+## fg - bring job to the foreground
 
-## history
+```txt
+SYNOPSIS
+    fg PID
 
+DESCRIPTION
+    fg brings the specified job to foreground resuming it if it has stopped.
 ```
-history
-````
 
-Display a log of all commands previously executed
+## fn - print a short description of every defined function
 
-## ion_docs
+```txt
+SYNOPSIS
+    fn [ -h | --help ]
 
-```
-ion_docs
+DESCRIPTION
+    Prints all the defined functions along with their help, if provided
 ```
 
-Opens the Ion manual
+## help - get help for builtins
 
-## jobs
+```txt
+SYNOPSIS
+    help [BUILTIN]
 
-```
-jobs
+DESCRIPTION
+    Get the short description for BUILTIN. If no argument is provided, list all the builtins
 ```
 
-Displays all jobs that are attached to the background
+## eq, is - checks if two arguments are the same
 
-## matches
+```txt
+SYNOPSIS
+    is [ -h | --help ] [not]
 
-```
-matches VARIABLE REGEX
+DESCRIPTION
+    Returns 0 if the two arguments are equal
+
+OPTIONS
+    not
+        returns 0 if the two arguments are not equal.
 ```
 
-Checks if a string matches a given regex
+## isatty - checks if the provided file descriptor is a tty
 
-## not
+```txt
+SYNOPSIS
+    isatty [FD]
 
+DESCRIPTION
+    Returns 0 exit status if the supplied file descriptor is a tty.
 ```
-not COMMAND
-```
-Reverses the exit status value of the given command.
 
-## or
+## jobs - list all jobs running in the background
 
-```
-COMMAND; or COMMAND
+```txt
+SYNOPSIS
+    jobs
+
+DESCRIPTION
+    Prints a list of all jobs running in the background.
 ```
 
-Execute the command if the shell's previous status is failure
+## matches - checks if the second argument contains any proportion of the first
 
-## popd
+```txt
+SYNOPSIS
+    matches VALUE VALUE
 
-```
-popd
+DESCRIPTION
+    Makes the exit status equal 0 if the first argument contains the second.
+    Otherwise matches makes the exit status equal 1.
+
+EXAMPLES
+    Returns true:
+        matches xs x
+    Returns false:
+        matches x xs
 ```
 
-Pop a directory from the stack and returns to the previous directory
+## popd - shift through the directory stack
 
-## pushd
+```txt
+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.
 ```
-pushd DIRECTORY
-```
-Push a directory to the stack.
 
-## random
+## pushd - push a directory to the directory stack
 
-```
-random
-random SEED
-random START END
-random START STEP END
-random choice [ITEMS...]
-```
+```txt
+SYNOPSIS
+    pushd DIRECTORY
 
-RANDOM generates a pseudo-random integer from a uniform distribution. The range (inclusive) is
-dependent on the arguments passed. No arguments indicate a range of [0; 32767]. If one argument
-is specified, the internal engine will be seeded with the argument for future invocations of
-RANDOM and no output will be produced. Two arguments indicate a range of [START; END]. Three
-arguments indicate a range of [START; END] with a spacing of STEP between possible outputs.
-RANDOM choice will select one random item from the succeeding arguments.
+DESCRIPTION
+    pushd pushes a directory to the directory stack.
+```
 
-> Due to limitations in the rand crate, seeding is not yet implemented
+## random - generate a random number
 
-## read
+```txt
+SYNOPSIS
+    random
+    random START END
 
+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].
 ```
-read VARIABLE
 
-```
-Read some variables
+## read - read a line of input into some variables
 
-## set
+```txt
+SYNOPSIS
+    read VARIABLES...
 
-```
-set [ --help ] [-e | +e] [- | --] [STRING]...
+DESCRIPTION
+    For each variable reads from standard input and stores the results in the variable.
 ```
 
-Set or unset values of shell options and positional parameters.
-Shell options may be set using the '-' character,
-and unset using the '+' character.
+## set - Set or unset values of shell options and positional parameters.
 
-### OPTIONS
+```txt
+SYNOPSIS
+    set [ --help ] [-e | +e] [-x | +x] [-o [vi | emacs]] [- | --] [STRING]...
 
-- **e**: Exit immediately if a command exits with a non-zero status.
+DESCRIPTION
+    Shell options may be set using the '-' character, and unset using the '+' character.
 
-- **--**: Following arguments will be set as positional arguments in the shell.
-    - If no argument are supplied, arguments will be unset.
+OPTIONS
+    -e  Exit immediately if a command exits with a non-zero status.
 
-- **-**: Following arguments will be set as positional arguments in the shell.
-    - If no arguments are suppled, arguments will not be unset.
+    -o  Specifies that an argument will follow that sets the key map.
+        The keymap argument may be either `vi` or `emacs`.
 
-## source
+    -x  Specifies that commands will be printed as they are executed.
 
-```
-source [PATH]
+    --  Following arguments will be set as positional arguments in the shell.
+        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.
 ```
 
-Evaluate the file following the command or re-initialize the init file
+## source - evaluates given file
 
-## starts-with
+```txt
+SYNOPSIS
+    source FILEPATH
 
-```
-ends-with KEY [VALUE...]
+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 if the supplied argument starts with a given string
+## starts_with - check if a given string starts with another one
 
-## suspend
+```txt
+SYNOPSIS
+    starts_with <PATTERN> tests...
 
+DESCRIPTION
+    Returns 0 if any argument starts_with contains the first argument, else returns 0
 ```
-suspend
-```
 
-Suspends the shell with a SIGTSTOP signal
+## status - Evaluates the current runtime status
+
+```txt
+SYNOPSIS
+    status [ -h | --help ] [-l] [-i]
 
-## test
+DESCRIPTION
+    With no arguments status displays the current login information of the shell.
 
+OPTIONS
+    -l
+        returns true if the shell is a login shell. Also --is-login.
+    -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.
 ```
-test [EXPRESSION]
+
+## suspend - suspend the current shell
+
+```txt
+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.
 ```
 
-Performs tests on files and text
+## test - perform tests on files and text
 
-#### Options
+```txt
+SYNOPSIS
+    test [EXPRESSION]
 
-- **-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
+DESCRIPTION
+    Tests the expressions given and returns an exit status of 0 if true, else 1.
 
-## true
+OPTIONS
+    --help
+        prints this help text
 
-```
-true
-```
+    -n STRING
+        the length of STRING is nonzero
 
-Do nothing, successfully
+    STRING
+        equivalent to -n STRING
 
-## unalias
+    -z STRING
+        the length of STRING is zero
 
-```
-unalias VARIABLE...
-```
+    STRING = STRING
+        the strings are equivalent
 
-Delete an alias
+    STRING != STRING
+        the strings are not equal
 
-## wait
+    INTEGER -eq INTEGER
+        the integers are equal
 
-```
-wait
-```
+    INTEGER -ge INTEGER
+        the first INTEGER is greater than or equal to the first INTEGER
 
-Waits until all running background processes have completed
+    INTEGER -gt INTEGER
+        the first INTEGER is greater than the first INTEGER
 
-## which
+    INTEGER -le INTEGER
+        the first INTEGER is less than or equal to the first INTEGER
 
-```
-which COMMAND
-```
+    INTEGER -lt INTEGER
+        the first INTEGER is less than the first INTEGER
 
-Shows the full path of commands
+    INTEGER -ne INTEGER
+        the first INTEGER is not equal to the first INTEGER
 
-## status
+    FILE -ef FILE
+        both files have the same device and inode numbers
 
-```
-status COMMAND
-```
+    FILE -nt FILE
+        the first FILE is newer than the second FILE
 
-Evaluates the current runtime status
+    FILE -ot FILE
+        the first file is older than the second FILE
 
-### Options
+    -b FILE
+        FILE exists and is a block device
 
-- **-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
+    -c FILE
+        FILE exists and is a character device
 
-## bool
+    -d FILE
+        FILE exists and is a directory
 
-```
-bool VALUE
-```
+    -e FILE
+        FILE exists
 
-If the value is '1' or 'true', returns the 0 exit status
+    -f FILE
+        FILE exists and is a regular file
 
-## is
+    -h FILE
+        FILE exists and is a symbolic link (same as -L)
 
-```
-is VALUE VALUE
+    -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.
 ```
 
-Returns 0 if the two arguments are equal
+## true - does nothing sucessfully
 
-## isatty
+```txt
+SYNOPSIS
+    true
 
+DESCRIPTION
+    Sets the exit status to 0.
 ```
-isatty [FD]
+
+## wait - wait for a background job
+
+```txt
+SYNOPSIS
+    wait
+
+DESCRIPTION
+    Wait for the background jobs to finish
 ```
 
-Returns 0 exit status if the supplied file descriptor is a tty.
+## which, type - locate a program file in the current user's path
+
+```txt
+SYNOPSIS
+    which PROGRAM
 
-### Options
-- **not**: returns 0 if the two arguments are not equal.
+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.
+```
\ No newline at end of file
diff --git a/members/builtins-proc/Cargo.toml b/members/builtins-proc/Cargo.toml
index 327516ba4c4ac2cdcac0410847c1379b687f4088..c5b370e60148fb07d5cc2a7017ed5e0a81c4e3c8 100644
--- a/members/builtins-proc/Cargo.toml
+++ b/members/builtins-proc/Cargo.toml
@@ -7,9 +7,13 @@ edition = "2018"
 [dependencies]
 quote = "0.6"
 syn = { version = "0.15", features = ["full"] }
+darling = "0.9"
 
 [dev-dependencies]
 ion-shell = { path = "../.." }
 
 [lib]
 proc-macro = true
+
+[features]
+man = []
diff --git a/members/builtins-proc/src/lib.rs b/members/builtins-proc/src/lib.rs
index 3f892b7961d7ad0a1529d0e7e0f817d9f0a81116..71c04781601fbea1a35d637025bb970c22a848a8 100644
--- a/members/builtins-proc/src/lib.rs
+++ b/members/builtins-proc/src/lib.rs
@@ -1,8 +1,22 @@
 extern crate proc_macro;
+use darling::{util::Flag, FromMeta};
 use proc_macro::TokenStream;
 use quote::quote;
+use std::{fs::File, io::Write};
 use syn;
 
+#[derive(Debug, FromMeta)]
+struct MacroArgs {
+    #[darling(default)]
+    names: Option<String>,
+    #[darling(rename = "man")]
+    help: String,
+    #[darling(default)]
+    authors: Flag,
+    #[darling(rename = "desc")]
+    short_description: String,
+}
+
 // TODO: It would be better if Man pages could be parsed of comments
 #[proc_macro_attribute]
 pub fn builtin(attr: TokenStream, item: TokenStream) -> TokenStream {
@@ -10,47 +24,16 @@ pub fn builtin(attr: TokenStream, item: TokenStream) -> TokenStream {
     let attrs = syn::parse_macro_input!(attr as syn::AttributeArgs);
     let syn::ItemFn { vis, decl, block, ident, .. } = &input;
     let syn::FnDecl { ref fn_token, ref inputs, ref output, .. } = **decl;
-    let mut help = None;
-    let mut short_description = None;
-    let mut names = None;
-    let mut authors = true;
+
+    let args = match MacroArgs::from_list(&attrs) {
+        Ok(v) => v,
+        Err(e) => return e.write_errors().into(),
+    };
 
     let name = syn::Ident::new(&format!("builtin_{}", &ident), input.ident.span());
 
-    for attr in attrs {
-        match attr {
-            syn::NestedMeta::Meta(syn::Meta::NameValue(ref attr)) if attr.ident == "man" => {
-                if let syn::Lit::Str(h) = &attr.lit {
-                    help = Some(h.value());
-                } else {
-                    panic!("`man` attribute should be a string variable");
-                }
-            }
-            syn::NestedMeta::Meta(syn::Meta::NameValue(ref attr)) if attr.ident == "desc" => {
-                if let syn::Lit::Str(h) = &attr.lit {
-                    short_description = Some(h.value());
-                } else {
-                    panic!("`desc` attribute should be a string variable");
-                }
-            }
-            syn::NestedMeta::Meta(syn::Meta::NameValue(ref attr)) if attr.ident == "names" => {
-                if let syn::Lit::Str(h) = &attr.lit {
-                    names = Some(h.value());
-                } else {
-                    panic!("`desc` attribute should be a string variable");
-                }
-            }
-            syn::NestedMeta::Meta(syn::Meta::Word(ref ident)) if ident == "no_authors" => {
-                authors = false;
-            }
-            _ => panic!("Only the `man` and `desc` attributes are allowed"),
-        }
-    }
-    let help = help.expect("A man page is required! Please add an attribute with name `man`");
-    let help = help.trim();
-    let short_description = short_description
-        .expect("A short description is required! Please add an attribute with name `desc`");
-    let names = names.unwrap_or_else(|| ident.to_string());
+    let help = args.help.trim();
+    let names = args.names.unwrap_or_else(|| ident.to_string());
 
     let bugs = "BUGS
     Please report all bugs at https://gitlab.redox-os.org/redox-os/ion/issues.
@@ -65,12 +48,17 @@ AUTHORS
     let man = format!(
         "NAME\n    {names} - {short_description}\n\n{help}\n\n{bugs}{extra}",
         names = names,
-        short_description = short_description,
+        short_description = args.short_description,
         help = help,
         bugs = bugs,
-        extra = if authors { &extra } else { "" },
+        extra = if args.authors.is_none() { &extra } else { "" },
     );
-    let help = format!("{} - {}\n\n```txt\n{}\n```", names, short_description, help);
+    let help = format!("{} - {}\n\n```txt\n{}\n```", names, args.short_description, help);
+
+    if cfg!(feature = "man") {
+        let mut man = File::create(format!("manual/builtins/{}.1", &ident)).unwrap();
+        man.write(help.as_bytes()).unwrap();
+    }
 
     let result = quote! {
         #[doc = #help]