diff --git a/hubble/config.py b/hubble/config.py
index e3f466e..0abc6c4 100644
--- a/hubble/config.py
+++ b/hubble/config.py
@@ -12,6 +12,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+from io import StringIO
 from itertools import chain
 import os
 
@@ -101,12 +102,21 @@ class ErrorConfigParser(SafeConfigParser):
 
 def open_fd(file):
     """ Open the file if possible, else return None """
+    if not isinstance(file, string_types):
+        return file
     try:
         return open(file)
     except IOError:
         return None
 
 
+def exists(obj):
+    """Returns true if obj is a StringIO or if the path exists """
+    if isinstance(obj, StringIO):
+        return True
+    return os.path.exists(obj)
+
+
 def read_configs(files=None, default_section=None):
     """Given a list of file names, return a list of handles to succesfully
     opened files
@@ -114,7 +124,7 @@ def read_configs(files=None, default_section=None):
     """
     files = files or [os.path.expanduser('~/.hubblerc'), '.hubblerc']
     # If non of these files exist, raise an error
-    if not any([os.path.exists(rc) for rc in files]):
+    if not any([exists(rc) for rc in files]):
         return ErrorConfigParser("Unable to find config files in these"
                                  " locations [%s]" % ", ".join(files))
     return parse_configs([open_fd(file) for file in files], default_section)
diff --git a/hubble/shell.py b/hubble/shell.py
index 76f538f..fa81020 100644
--- a/hubble/shell.py
+++ b/hubble/shell.py
@@ -18,7 +18,7 @@ import argparse
 import logging
 import os
 import re
-from subprocess import CalledProcessError, check_output, PIPE, Popen
+from subprocess import check_output, PIPE, Popen
 import sys
 import textwrap
 
@@ -78,7 +78,7 @@ class Env(dict):
                 # Delete the key from the env
                 self.delete(key)
             # else add it
-            self.set(key, value, section, key.isupper())
+            self.set(key, value, section)
 
     def eval(self):
         """ Exapand all the ${variable} directives in the collection """
@@ -146,6 +146,22 @@ def green(msg):
     return "\033[92m%s\033[0m" % msg
 
 
+def get_cmd(argv, conf, env, hubble_args):
+    # If our invocation name is not 'hubble'
+    if not argv[0].endswith('hubble'):
+        return cmd_path(argv[0], conf)
+
+    if hubble_args.execute:
+        # Use the command provided
+        return hubble_args.execute
+
+    try:
+        return env['cmd'].value
+    except KeyError:
+        raise RuntimeError("Please specify a 'cmd' somewhere in "
+                           "your config")
+
+
 def get_environments(args, choice, config):
     """ Get the environment collection requested from args.env """
     sections = [choice]
@@ -172,8 +188,20 @@ def get_environments(args, choice, config):
             return "opt.%s" % i[0], str(i[1])
         # Add the args to the environment as opt.'<arg_name>'
         env.add(dict(map(f, vars(args).items())), section)
+
+        env.eval()
+
+        # Populate environment vars by running opt-cmd
+        # if -o was passed on the commandline
+        if 'opt-cmd' in env:
+            env.add(run(env['opt-cmd'].value, env))
+
+        # Populate environment vars by running the env-cmd if it exists
+        if 'env-cmd' in env:
+            env.add(run(env['env-cmd'].value, env))
+
         # Apply var expansion
-        results.append(env.eval())
+        results.append(env)
     return results
 
 
@@ -223,7 +251,7 @@ def cmd_path(cmd, conf):
     return "/usr/bin/%s" % basename
 
 
-def eval_args(conf, parser):
+def eval_args(argv, conf, parser):
     env = conf.safe_get(conf.default_section, 'default-env')
     # If no default environment set, look for an
     # environment choice on the command line
@@ -231,14 +259,46 @@ def eval_args(conf, parser):
         help = "The environment defined in ~/.hubblerc to use"
         parser.add_argument('env', nargs='?', metavar='<ENV>',
                             help=help)
-        (arg1, arg2) = parser.parse_known_args()
-        return (arg1, arg2, arg1.env)
+        (arg1, arg2) = parser.parse_known_args(args=argv[1:])
+        return arg1, arg2, arg1.env
     # Return the args with the default environment choice
-    (arg1, arg2) = parser.parse_known_args()
-    return (arg1, arg2, env)
-
+    (arg1, arg2) = parser.parse_known_args(args=argv[1:])
+    return arg1, arg2, env
+
+
+def execute_environment(cmd, env, hubble_args, other_args):
+    # If --debug; print out our env config and pass along the
+    # --debug arg
+    if hubble_args.debug:
+        # For cinder client debug
+        if cmd.endswith('cinder'):
+            env.add({'CINDERCLIENT_DEBUG': '1'})
+        print("%r\n" % env)
+        other_args.insert(0, '--debug')
+
+    # Grab a copy of the local environment and inject it into
+    # our environment
+    environ = os.environ.copy()
+    environ.update(env.to_dict())
 
-def main():
+    try:
+        # Run the requested command
+        p = Popen([cmd] + other_args,
+                  stdout=PIPE,
+                  stderr=PIPE,
+                  env=environ)
+    except OSError as e:
+        if e.errno == 2:
+            print("-- No such executable '%s', you must specify the "
+                  "executable in the [hubble-commands] section of the "
+                  "config (See README)" % cmd.value)
+        message = "exec failed '%s' - %s" % (cmd.value, e)
+        raise RuntimeError(message)
+
+    return p
+
+
+def main(argv=sys.argv, stdout=sys.stdout, stderr=sys.stderr, files=None):
     logging.basicConfig(format='-- %(message)s')
     log.setLevel(logging.CRITICAL)
     formatter_class = argparse.RawDescriptionHelpFormatter
@@ -250,7 +310,7 @@ def main():
             on environment variables for configuration.
 
             Use ~/.hubblerc for user wide environments then place a
-            .hubblerc in a local directory to overide ~/.hubblerc
+            .hubblerc in a local directory to override ~/.hubblerc
             """))
     parser.add_argument('-o', '--option',
                         help="an argument to pass to the opt-cmd")
@@ -262,109 +322,47 @@ def main():
                         help="Adds CINDERCLIENT_DEBUG=1 to the environment "
                         "and passes --debug to selected command")
 
-    try:
-        # Read the configs
-        conf = read_configs(default_section='hubble')
-        # Evaluate the command line arguments and return our args
-        # the commands args and the environment choice the user made
-        hubble_args, other_args, choice = eval_args(conf, parser)
-        # Do this so we pass along the -h to the command
-        # if we are using invocation discovery
-        if hubble_args.help and (choice is None):
-            return parser.print_help()
-
-        # If there was an error
-        if conf.get_error():
-            print(conf.get_error())
-            return 1
-
-        if choice is None:
-            print("Environments Configured: %s" % ",".join(conf.sections()))
-            print("See --help for usage")
-            return 1
-        if hubble_args.help:
-            other_args.append('--help')
-
-        # Set our log level
-        if hubble_args.debug:
-            log.setLevel(logging.DEBUG)
-
-        # Read environment values from config files
-        environments = get_environments(hubble_args, choice, conf)
-        processes = []
-        for env in environments:
-            # Populate environment vars by running opt-cmd
-            # if -o was passed on the commandline
-            if hubble_args.option:
-                if 'opt-cmd' not in env:
-                    message = "provided -o|--option, but 'opt-cmd' is not " \
-                              "defined in '%s' section" % env['section'].value
-                    raise RuntimeError(message)
-                env.add(run(env['opt-cmd'].value, env))
-
-            # Populate environment vars by running the env-cmd if it exists
-            if 'env-cmd' in env:
-                env.add(run(env['env-cmd'].value, env))
-
-            # If querying multiple environments, display the env name
-            if hubble_args.debug:
-                print("-- [%s] --" % green(env['section'].value))
-
-            # If our invocation name is not 'hubble'
-            if not sys.argv[0].endswith('hubble'):
-                # Use the invocation name as our 'cmd'
-                env.add({'cmd': cmd_path(sys.argv[0], conf)})
-
-            if hubble_args.execute:
-                # Use the command provided
-                env.add({'cmd': hubble_args.execute})
-
-            # At this point we should know our 'cmd' to run
-            if 'cmd' not in env:
-                raise RuntimeError("Please specify a 'cmd' somewhere in "
-                                   "your config")
-
-            # If --debug; print out our env config and pass along the
-            # --debug arg
-            if hubble_args.debug:
-                # For cinder client debug
-                if env['cmd'].endswith('cinder'):
-                    env.add({'CINDERCLIENT_DEBUG': '1'})
-                print("%r\n" % env)
-                other_args.insert(0, '--debug')
-
-            # Grab a copy of the local environment and inject it into
-            # our environment
-            environ = os.environ.copy()
-            environ.update(env.to_dict())
-
-            try:
-                # Run the requested command
-                p = Popen([env['cmd'].value] + other_args,
-                          stdout=PIPE,
-                          stderr=PIPE,
-                          env=environ)
-                processes.append((p, env['section'].value))
-            except OSError as e:
-                if e.errno == 2:
-                    print("-- No such executable '%s', you must specify the "
-                          "executable in the [hubble-commands] section of the "
-                          "config (See README)" % env['cmd'].value)
-                message = "exec failed '%s' - %s" % (env['cmd'].value, e)
-                raise RuntimeError(message)
-
-        for p, env in processes:
-            if len(environments) != 1:
-                print("-- [%s] --" % green(env))
-            # Wait for the command to complete
-            stdout, stderr = p.communicate()
-            sys.stdout.write(stdout.decode('utf-8'))
-            sys.stderr.write(stderr.decode('utf-8'))
-
-    except (RuntimeError, NoSectionError) as e:
-        log.critical(e)
+    # Read the configs
+    conf = read_configs(files=files, default_section='hubble')
+    # Evaluate the command line arguments and return our args
+    # the commands args and the environment choice the user made
+    hubble_args, other_args, choice = eval_args(argv, conf, parser)
+    # Do this so we pass along the -h to the command
+    # if we are using invocation discovery
+    if hubble_args.help and (choice is None):
+        return parser.print_help()
+
+    # If there was an error
+    if conf.get_error():
+        print(conf.get_error())
         return 1
-    except CalledProcessError as e:
-        log.critical(e.output)
-        log.critical(e)
+
+    if choice is None:
+        print("Environments Configured: %s" % ",".join(conf.sections()))
+        print("See --help for usage")
         return 1
+    if hubble_args.help:
+        other_args.append('--help')
+
+    # Set our log level
+    if hubble_args.debug:
+        log.setLevel(logging.DEBUG)
+
+    # Collect all environments from our config file
+    environments = get_environments(hubble_args, choice, conf)
+    processes = []
+    for env in environments:
+        # Get the command to execute
+        cmd = get_cmd(argv, conf, env, hubble_args)
+        # Create the selected environment to execute our command in
+        p = execute_environment(cmd, env, hubble_args, other_args)
+        processes.append((p, env['section'].value))
+
+    for p, env in processes:
+        if len(environments) != 1:
+            print("-- [%s] --" % green(env))
+        # Wait for the command to complete
+        out, err = p.communicate()
+        stdout.write(out.decode('utf-8'))
+        stderr.write(err.decode('utf-8'))
+    return 0
diff --git a/tox.ini b/tox.ini
index b5fc229..c204552 100644
--- a/tox.ini
+++ b/tox.ini
@@ -30,6 +30,6 @@ commands =
 
 [flake8]
 ignore = E731
-#max-complexity = 10
+max-complexity = 10
 import-order-style = google
 application-import-names = hubble

enable max-complexity gate
We currently have flake8 `max-complexity` disabled because it would break the flake8 gate. This is because the `main` function in `hubble.shell` is absolutely absurdly complicated:

```
./hubble/shell.py:240:1: C901 'main' is too complex (24)
```

We should refactor that function in to something less stupid, and enable the `max-complexity` gate.
diff --git a/hubble/tests/test_shell.py b/hubble/tests/test_shell.py
new file mode 100644
index 0000000..a432967
--- /dev/null
+++ b/hubble/tests/test_shell.py
@@ -0,0 +1,73 @@
+#   Copyright 2012 Derrick J. Wippler
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from io import StringIO
+import os
+import tempfile
+import unittest
+
+from hubble.shell import main
+
+
+def safe_eval(io_out):
+    str_out = io_out.getvalue()
+    print(str_out)
+    if len(str_out) != 0:
+        return eval(str_out)
+    return {}
+
+
+def run_main(config, argv):
+    stdout = StringIO()
+    stderr = StringIO()
+    ret = main(argv, stdout=stdout, stderr=stderr, files=[config])
+    return ret, safe_eval(stdout), safe_eval(stderr)
+
+
+def generate_test_cmd(print_stmt):
+    with tempfile.NamedTemporaryFile(delete=False) as fd:
+        fd.write("#! /usr/bin/env python\n"
+                 "from __future__ import print_function\n"
+                 "import os\n"
+                 "{0}\n".format(print_stmt).encode())
+        fd.flush()
+    os.chmod(fd.name, 0o755)
+    return fd.name
+
+
+class TestHubble(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        cls.opt_cmd = generate_test_cmd("print('opt-injected-key=value\\n')")
+        cls.cmd = generate_test_cmd("print(dict(os.environ))")
+
+    @classmethod
+    def tearDownClass(cls):
+        os.unlink(cls.opt_cmd)
+        os.unlink(cls.cmd)
+
+    def test_opt_cmd(self):
+        config = StringIO(u"""[hubble]
+            name=Test
+            opt-cmd={0} ${{opt.option}} ${{name}}
+            [dfw]
+            cmd={1}
+            SOME=Thing""".format(self.opt_cmd, self.cmd))
+        config.name = "test.conf"
+
+        ret, out, err = run_main(config, ["hubble", "-o", "my-option",
+                                          "dfw", "--debug"])
+        self.assertEqual(ret, 0)
+        self.assertEqual(out['SOME'], "Thing")
+        self.assertEqual(out['opt-injected-key'], "value")
diff --git a/hubble/tests/unit/test_hubble.py b/hubble/tests/unit/test_hubble.py
index 2de761f..0091d82 100644
--- a/hubble/tests/unit/test_hubble.py
+++ b/hubble/tests/unit/test_hubble.py
@@ -13,10 +13,9 @@
 #   limitations under the License.
 
 import argparse
+from io import StringIO
 import unittest
 
-from six.moves import StringIO
-
 from hubble.config import parse_configs
 from hubble.shell import empty, Env, get_environments, run, to_dict
 
@@ -43,9 +42,10 @@ class TestEnv(unittest.TestCase):
         parser = argparse.ArgumentParser()
         parser.add_argument('env')
         parser.add_argument('--user', default='', required=False)
+        parser.add_argument('--option')
         args = parser.parse_args(['blah'])
 
-        file = StringIO("[hubble]\n"
+        file = StringIO(u"[hubble]\n"
                         "name=My name is ${FIRST} ${last}\n"
                         "[prod-meta]\n"
                         "meta=['name', 'place']\n"
diff --git a/test-requirements.txt b/test-requirements.txt
index 8f9608c..fcd3c2f 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -7,3 +7,7 @@ pytest-cov>=2.3.0
 # flake8 plugins
 pep8-naming>=0.4.1
 flake8-import-order>=0.8
+
+# Tox
+tox>=2.3.1
+tox-pyenv>=1.0.3
\ No newline at end of file

pip install -e .
parse_log_pytest
requirements.txt
pytest>=2.9.2 pytest-flake8>=0.5 pytest-cov>=2.3.0 pep8-naming>=0.4.1 flake8-import-order>=0.8 pytest
apt-get update apt-get install -y gcc
3.5
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
cffi==1.15.1
configparser==5.2.0
coverage==6.2
cryptography==40.0.2
flake8==5.0.4
flake8-import-order==0.18.2
-e git+https://github.com/thrawn01/hubble.git@22a4cd51ed0a312efb3ef79e39a558fe87c970f7#egg=hubble
importlib-metadata==4.2.0
iniconfig==1.1.1
jeepney==0.7.1
keyring==23.4.1
mccabe==0.7.0
packaging==21.3
pep8-naming==0.13.1
pluggy==1.0.0
py==1.11.0
pycodestyle==2.9.1
pycparser==2.21
pyflakes==2.5.0
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
pytest-flake8==1.1.1
SecretStorage==3.3.3
six==1.17.0
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: hubble
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - cffi==1.15.1
      - configparser==5.2.0
      - coverage==6.2
      - cryptography==40.0.2
      - flake8==5.0.4
      - flake8-import-order==0.18.2
      - importlib-metadata==4.2.0
      - iniconfig==1.1.1
      - jeepney==0.7.1
      - keyring==23.4.1
      - mccabe==0.7.0
      - packaging==21.3
      - pep8-naming==0.13.1
      - pluggy==1.0.0
      - py==1.11.0
      - pycodestyle==2.9.1
      - pycparser==2.21
      - pyflakes==2.5.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - pytest-flake8==1.1.1
      - secretstorage==3.3.3
      - six==1.17.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/hubble

hubble/tests/test_shell.py::TestHubble::test_opt_cmd
hubble/tests/unit/test_hubble.py::TestEnv::test_env hubble/tests/unit/test_hubble.py::TestEnv::test_env_to_dict hubble/tests/unit/test_hubble.py::TestEnv::test_get_environments hubble/tests/unit/test_hubble.py::TestHubble::test_empty hubble/tests/unit/test_hubble.py::TestHubble::test_run hubble/tests/unit/test_hubble.py::TestHubble::test_to_dict
tox.ini hubble/config.py hubble/shell.py
tox.ini hubble/config.py hubble/shell.py