diff --git a/bin/spack b/bin/spack
index c63178b19130e547e56eaf4c93456848d575cfb4..f2b5d1d58387b02f39554def98946fe22d15f175 100755
--- a/bin/spack
+++ b/bin/spack
@@ -75,6 +75,13 @@ for cmd in spack.cmd.commands:
     module = spack.cmd.get_module(cmd)
     subparser = subparsers.add_parser(cmd, help=module.description)
     module.setup_parser(subparser)
+
+# Just print help and exit if run with no arguments at all
+if len(sys.argv) == 1:
+    parser.print_help()
+    sys.exit(1)
+
+# actually parse the args.
 args = parser.parse_args()
 
 # Set up environment based on args.
diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index 0b69ccde38fae7883ff66561bbf62bf74df4597b..be38971c309fddf93fe3d4f8dda556fef6dfbc72 100644
--- a/lib/spack/spack/__init__.py
+++ b/lib/spack/spack/__init__.py
@@ -58,8 +58,6 @@
 stage_path     = join_path(var_path, "stage")
 install_path   = join_path(prefix, "opt")
 share_path     = join_path(prefix, "share", "spack")
-dotkit_path    = join_path(share_path, "dotkit")
-tclmodule_path    = join_path(share_path, "modules")
 
 #
 # Set up the packages database.
diff --git a/lib/spack/spack/cmd/load.py b/lib/spack/spack/cmd/load.py
index 1389740df1590560ebe9694b6bb66b50ac480b1c..5bc6b15784c6c537c5465f641fdb1aff6d1dca8a 100644
--- a/lib/spack/spack/cmd/load.py
+++ b/lib/spack/spack/cmd/load.py
@@ -23,28 +23,16 @@
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 import argparse
-import llnl.util.tty as tty
-import spack
+import spack.modules
 
-description ="Add package to environment using module."
+description ="Add package to environment using modules."
 
 def setup_parser(subparser):
+    """Parser is only constructed so that this prints a nice help
+       message with -h. """
     subparser.add_argument(
-        'spec', nargs=argparse.REMAINDER, help='Spec of package to add.')
-
-
-def print_help():
-    tty.msg("Spack module support is not initialized.",
-            "",
-            "To use module with Spack, you must first run the command",
-            "below, which you can copy and paste:",
-            "",
-            "For bash:",
-            "    . %s/setup-env.bash" % spack.share_path,
-            "",
-            "ksh/csh/tcsh shells are currently unsupported",
-            "")
+        'spec', nargs=argparse.REMAINDER, help='Spec of package to load with modules.')
 
 
 def load(parser, args):
-    print_help()
+    spack.modules.print_help()
diff --git a/lib/spack/spack/cmd/dotkit.py b/lib/spack/spack/cmd/module.py
similarity index 60%
rename from lib/spack/spack/cmd/dotkit.py
rename to lib/spack/spack/cmd/module.py
index 7a691ae5c04f2b0a2e31485aef109474223a22c7..ead3b9abac8e026845dce5b444d0b011936a50f7 100644
--- a/lib/spack/spack/cmd/dotkit.py
+++ b/lib/spack/spack/cmd/module.py
@@ -32,34 +32,43 @@
 from llnl.util.filesystem import mkdirp
 
 import spack.cmd
-import spack.hooks.dotkit
+import spack.modules
+from spack.util.string import *
+
 from spack.spec import Spec
 
+description ="Manipulate modules and dotkits."
+
+module_types = {
+    'dotkit' : spack.modules.Dotkit,
+    'tcl'    : spack.modules.TclModule
+}
 
-description ="Find dotkits for packages if they exist."
 
 def setup_parser(subparser):
-    subparser.add_argument(
-        '--refresh', action='store_true', help='Regenerate all dotkits')
+    sp = subparser.add_subparsers(metavar='SUBCOMMAND', dest='module_command')
 
-    subparser.add_argument(
-        'spec', nargs=argparse.REMAINDER, help='spec to find a dotkit for.')
+    refresh_parser = sp.add_parser('refresh', help='Regenerate all module files.')
 
+    find_parser = sp.add_parser('find', help='Find module files for packages.')
+    find_parser.add_argument(
+        'module_type', help="Type of module to find file for. [" + '|'.join(module_types) + "]")
+    find_parser.add_argument('spec', nargs='+', help='spec to find a module file for.')
 
-def dotkit_find(parser, args):
-    if not args.spec:
-        parser.parse_args(['dotkit', '-h'])
 
-    spec = spack.cmd.parse_specs(args.spec)
-    if len(spec) > 1:
+def module_find(mtype, spec_array):
+    specs = spack.cmd.parse_specs(spec_array)
+    if len(specs) > 1:
         tty.die("You can only pass one spec.")
-    spec = spec[0]
+    spec = specs[0]
 
     if not spack.db.exists(spec.name):
         tty.die("No such package: %s" % spec.name)
 
-    specs = [s for s in spack.db.installed_package_specs() if s.satisfies(spec)]
+    if mtype not in module_types:
+        tty.die("Invalid module type: '%s'.  Options are " + comma_and(module_types))
 
+    specs = [s for s in spack.db.installed_package_specs() if s.satisfies(spec)]
     if len(specs) == 0:
         tty.die("No installed packages match spec %s" % spec)
 
@@ -69,31 +78,27 @@ def dotkit_find(parser, args):
             sys.stderr.write(s.tree(color=True))
         sys.exit(1)
 
-    match = specs[0]
-    if not os.path.isfile(spack.hooks.dotkit.dotkit_file(match.package)):
+    mt = module_types[mtype]
+    mod = mt(spec.package)
+    if not os.path.isfile(mod.file_name):
         tty.die("No dotkit is installed for package %s." % spec)
 
-    print match.format('$_$@$+$%@$=$#')
+    print mod.file_name
 
 
-def dotkit_refresh(parser, args):
-    query_specs = spack.cmd.parse_specs(args.spec)
+def module_refresh():
+    shutil.rmtree(spack.dotkit_path, ignore_errors=False)
+    mkdirp(spack.dotkit_path)
 
     specs = spack.db.installed_package_specs()
-    if query_specs:
-        specs = [s for s in specs
-                 if any(s.satisfies(q) for q in query_specs)]
-    else:
-        shutil.rmtree(spack.dotkit_path, ignore_errors=False)
-        mkdirp(spack.dotkit_path)
-
     for spec in specs:
-        spack.hooks.dotkit.post_install(spec.package)
+        for mt in module_types:
+            mt(spec.package).write()
 
 
+def module(parser, args):
+    if args.module_command == 'refresh':
+        module_refresh()
 
-def dotkit(parser, args):
-    if args.refresh:
-        dotkit_refresh(parser, args)
-    else:
-        dotkit_find(parser, args)
+    elif args.module_command == 'find':
+        module_find(args.module_type, args.spec)
diff --git a/lib/spack/spack/cmd/tclmodule.py b/lib/spack/spack/cmd/tclmodule.py
deleted file mode 100644
index 270ee65b7b484a84b5cd5ace7db04b3154c23441..0000000000000000000000000000000000000000
--- a/lib/spack/spack/cmd/tclmodule.py
+++ /dev/null
@@ -1,99 +0,0 @@
-##############################################################################
-# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Written by David Beckingsale, david@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://scalability-llnl.github.io/spack
-# Please also see the LICENSE file for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License (as published by
-# the Free Software Foundation) version 2.1 dated February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-import sys
-import os
-import shutil
-import argparse
-
-import llnl.util.tty as tty
-from llnl.util.lang import partition_list
-from llnl.util.filesystem import mkdirp
-
-import spack.cmd
-import spack.hooks.tclmodule
-from spack.spec import Spec
-
-
-description ="Find modules for packages if they exist."
-
-def setup_parser(subparser):
-    subparser.add_argument(
-        '--refresh', action='store_true', help='Regenerate all modules')
-
-    subparser.add_argument(
-        'spec', nargs=argparse.REMAINDER, help='spec to find a module for.')
-
-
-def module_find(parser, args):
-    if not args.spec:
-        parser.parse_args(['tclmodule', '-h'])
-
-    spec = spack.cmd.parse_specs(args.spec)
-    if len(spec) > 1:
-        tty.die("You can only pass one spec.")
-    spec = spec[0]
-
-    if not spack.db.exists(spec.name):
-        tty.die("No such package: %s" % spec.name)
-
-    specs = [s for s in spack.db.installed_package_specs() if s.satisfies(spec)]
-
-    if len(specs) == 0:
-        tty.die("No installed packages match spec %s" % spec)
-
-    if len(specs) > 1:
-        tty.error("Multiple matches for spec %s.  Choose one:" % spec)
-        for s in specs:
-            sys.stderr.write(s.tree(color=True))
-        sys.exit(1)
-
-    match = specs[0]
-    if not os.path.isfile(spack.hooks.tclmodule.module_file(match.package)):
-        tty.die("No module is installed for package %s." % spec)
-
-    print match.format('$_$@$+$%@$=$#')
-
-
-def module_refresh(parser, args):
-    query_specs = spack.cmd.parse_specs(args.spec)
-
-    specs = spack.db.installed_package_specs()
-    if query_specs:
-        specs = [s for s in specs
-                 if any(s.satisfies(q) for q in query_specs)]
-    else:
-        shutil.rmtree(spack.tclmodule_path, ignore_errors=False)
-        mkdirp(spack.tclmodule_path)
-
-    for spec in specs:
-        spack.hooks.tclmodule.post_install(spec.package)
-
-
-
-def tclmodule(parser, args):
-    if args.refresh:
-        module_refresh(parser, args)
-    else:
-        module_find(parser, args)
diff --git a/lib/spack/spack/cmd/unload.py b/lib/spack/spack/cmd/unload.py
index df009c840b3ec2900ee0ed41e674dd707a90e45e..24e49b3f24aed0d40c8edb798fa3d981cca40e81 100644
--- a/lib/spack/spack/cmd/unload.py
+++ b/lib/spack/spack/cmd/unload.py
@@ -23,14 +23,16 @@
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 import argparse
-import spack.cmd.tclmodule
+import spack.modules
 
 description ="Remove package from environment using module."
 
 def setup_parser(subparser):
+    """Parser is only constructed so that this prints a nice help
+       message with -h. """
     subparser.add_argument(
-        'spec', nargs=argparse.REMAINDER, help='Spec of package to remove.')
+        'spec', nargs=argparse.REMAINDER, help='Spec of package to unload with modules.')
 
 
 def unload(parser, args):
-    spack.cmd.load.print_help()
+    spack.modules.print_help()
diff --git a/lib/spack/spack/cmd/unuse.py b/lib/spack/spack/cmd/unuse.py
index a31e16d11ad604e8748b858a41616a3a00ffe49d..7f0b384ea05bbb336b5a6289dbe79968bfec21ed 100644
--- a/lib/spack/spack/cmd/unuse.py
+++ b/lib/spack/spack/cmd/unuse.py
@@ -23,14 +23,16 @@
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 import argparse
-import spack.cmd.use
+import spack.modules
 
 description ="Remove package from environment using dotkit."
 
 def setup_parser(subparser):
+    """Parser is only constructed so that this prints a nice help
+       message with -h. """
     subparser.add_argument(
-        'spec', nargs=argparse.REMAINDER, help='Spec of package to remove.')
+        'spec', nargs=argparse.REMAINDER, help='Spec of package to unuse with dotkit.')
 
 
 def unuse(parser, args):
-    spack.cmd.use.print_help()
+    spack.modules.print_help()
diff --git a/lib/spack/spack/cmd/use.py b/lib/spack/spack/cmd/use.py
index 10a0644df8266ec75716a51978a634b50f9ccb6d..4990fea2f821441947872007ce463fd21cd1fa24 100644
--- a/lib/spack/spack/cmd/use.py
+++ b/lib/spack/spack/cmd/use.py
@@ -23,28 +23,16 @@
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 import argparse
-import llnl.util.tty as tty
-import spack
+import spack.modules
 
 description ="Add package to environment using dotkit."
 
 def setup_parser(subparser):
+    """Parser is only constructed so that this prints a nice help
+       message with -h. """
     subparser.add_argument(
-        'spec', nargs=argparse.REMAINDER, help='Spec of package to add.')
-
-
-def print_help():
-    tty.msg("Spack dotkit support is not initialized.",
-            "",
-            "To use dotkit with Spack, you must first run the command",
-            "below, which you can copy and paste:",
-            "",
-            "For bash:",
-            "    . %s/setup-env.bash" % spack.share_path,
-            "",
-            "ksh/csh/tcsh shells are currently unsupported",
-            "")
+        'spec', nargs=argparse.REMAINDER, help='Spec of package to use with dotkit.')
 
 
 def use(parser, args):
-    print_help()
+    spack.modules.print_help()
diff --git a/lib/spack/spack/hooks/dotkit.py b/lib/spack/spack/hooks/dotkit.py
index 10b773235342cef9026f0ba8a5380ff1ba75b0ee..0f46f6a2fcfcb35fad8d23dea714efe92c203373 100644
--- a/lib/spack/spack/hooks/dotkit.py
+++ b/lib/spack/spack/hooks/dotkit.py
@@ -22,62 +22,14 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import os
-import re
-import textwrap
-import shutil
-from contextlib import closing
-
-from llnl.util.filesystem import join_path, mkdirp
-
-import spack
-
-
-def dotkit_file(pkg):
-    dk_file_name = pkg.spec.format('$_$@$%@$+$=$#') + ".dk"
-    return join_path(spack.dotkit_path, dk_file_name)
+import spack.modules
 
 
 def post_install(pkg):
-    if not os.path.exists(spack.dotkit_path):
-        mkdirp(spack.dotkit_path)
-
-    alterations = []
-    for var, path in [
-        ('PATH', pkg.prefix.bin),
-        ('MANPATH', pkg.prefix.man),
-        ('MANPATH', pkg.prefix.share_man),
-        ('LD_LIBRARY_PATH', pkg.prefix.lib),
-        ('LD_LIBRARY_PATH', pkg.prefix.lib64)]:
-
-        if os.path.isdir(path):
-            alterations.append("dk_alter %s %s\n" % (var, path))
-
-    if not alterations:
-        return
-
-    alterations.append("dk_alter CMAKE_PREFIX_PATH %s\n" % pkg.prefix)
-
-    dk_file = dotkit_file(pkg)
-    with closing(open(dk_file, 'w')) as dk:
-        # Put everything in the spack category.
-        dk.write('#c spack\n')
-
-        dk.write('#d %s\n' % pkg.spec.format("$_ $@"))
-
-        # Recycle the description
-        if pkg.__doc__:
-            doc = re.sub(r'\s+', ' ', pkg.__doc__)
-            for line in textwrap.wrap(doc, 72):
-                dk.write("#h %s\n" % line)
-
-        # Write alterations
-        for alter in alterations:
-            dk.write(alter)
+    dk = spack.modules.Dotkit(pkg)
+    dk.write()
 
 
 def post_uninstall(pkg):
-    dk_file = dotkit_file(pkg)
-    if os.path.exists(dk_file):
-        shutil.rmtree(dk_file, ignore_errors=True)
-
+    dk = spack.modules.Dotkit(pkg)
+    dk.remove()
diff --git a/lib/spack/spack/hooks/tclmodule.py b/lib/spack/spack/hooks/tclmodule.py
index d9b4a43831986f10d10be32aa2d758e42bff3e03..d93da3177eaebb152fc30d0762de4dc2b210ec6b 100644
--- a/lib/spack/spack/hooks/tclmodule.py
+++ b/lib/spack/spack/hooks/tclmodule.py
@@ -22,65 +22,14 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import os
-import re
-import textwrap
-import shutil
-from contextlib import closing
-
-from llnl.util.filesystem import join_path, mkdirp
-
-import spack
-
-
-def module_file(pkg):
-    m_file_name = pkg.spec.format('$_$@$%@$+$=$#')
-    return join_path(spack.tclmodule_path, m_file_name)
+import spack.modules
 
 
 def post_install(pkg):
-    if not os.path.exists(spack.module_path):
-        mkdirp(spack.module_path)
-
-    alterations = []
-    for var, path in [
-        ('PATH', pkg.prefix.bin),
-        ('MANPATH', pkg.prefix.man),
-        ('MANPATH', pkg.prefix.share_man),
-        ('LD_LIBRARY_PATH', pkg.prefix.lib),
-        ('LD_LIBRARY_PATH', pkg.prefix.lib64)]:
-
-        if os.path.isdir(path):
-            alterations.append("prepend-path %s \"%s\"\n" % (var, path))
-
-    if not alterations:
-        return
-
-    alterations.append("prepend-path CMAKE_PREFIX_PATH \"%s\"\n" % pkg.prefix)
-
-    m_file = module_file(pkg)
-    with closing(open(m_file, 'w')) as m:
-        # Put everything in the spack category.
-        m.write('#%Module1.0\n')
-
-        m.write('module-whatis \"%s\"\n\n' % pkg.spec.format("$_ $@"))
-
-        # Recycle the description
-        if pkg.__doc__:
-            m.write('proc ModulesHelp { } {\n')
-            doc = re.sub(r'\s+', ' ', pkg.__doc__)
-            doc = re.sub(r'"', '\"', pkg.__doc__)
-            m.write("puts stderr \"%s\"\n" % doc)
-            m.write('}\n\n')
-
-
-        # Write alterations
-        for alter in alterations:
-            m.write(alter)
+    dk = spack.modules.TclModule(pkg)
+    dk.write()
 
 
 def post_uninstall(pkg):
-    m_file = module_file(pkg)
-    if os.path.exists(m_file):
-        shutil.rmtree(m_file, ignore_errors=True)
-
+    dk = spack.modules.TclModule(pkg)
+    dk.remove()
diff --git a/lib/spack/spack/modules.py b/lib/spack/spack/modules.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ac7e470cb5dc239467dd868dc57679ec50b162a
--- /dev/null
+++ b/lib/spack/spack/modules.py
@@ -0,0 +1,216 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+"""This module contains code for creating environment modules, which
+can include dotkits, tcl modules, lmod, and others.
+
+The various types of modules are installed by post-install hooks and
+removed after an uninstall by post-uninstall hooks.  This class
+consolidates the logic for creating an abstract description of the
+information that module systems need.  Currently that includes a
+number directories to be appended to paths in the user's environment:
+
+  * /bin directories to be appended to PATH
+  * /lib* directories for LD_LIBRARY_PATH
+  * /man* and /share/man* directories for LD_LIBRARY_PATH
+  * the package prefix for CMAKE_PREFIX_PATH
+
+This module also includes logic for coming up with unique names for
+the module files so that they can be found by the various
+shell-support files in $SPACK/share/spack/setup-env.*.
+
+Each hook in hooks/ implements the logic for writing its specific type
+of module file.
+"""
+__all__ = ['EnvModule', 'Dotkit', 'TclModule']
+
+import os
+import re
+import textwrap
+import shutil
+from contextlib import closing
+
+import llnl.util.tty as tty
+from llnl.util.filesystem import join_path, mkdirp
+
+import spack
+
+dotkit_path  = join_path(spack.share_path, "dotkit")
+tcl_mod_path = join_path(spack.share_path, "modules")
+
+def print_help():
+    """For use by commands to tell user how to activate shell support."""
+
+    tty.msg("Spack module/dotkit support is not initialized.",
+            "",
+            "To use dotkit or modules with Spack, you must first run",
+            "one of the commands below.  You can copy/paste them.",
+            "",
+            "For bash and zsh:",
+            "    . %s/setup-env.sh" % spack.share_path,
+            "",
+            "For csh and tcsh:",
+            "    source %s/setup-env.csh" % spack.share_path,
+            "")
+
+
+class EnvModule(object):
+    def __init__(self, pkg=None):
+        # category in the modules system
+        # TODO: come up with smarter category names.
+        self.category = "spack"
+
+        # Descriptions for the module system's UI
+        self.short_description = ""
+        self.long_description = ""
+
+        # dict pathname -> list of directories to be prepended to in
+        # the module file.
+        self._paths = None
+        self.pkg = pkg
+
+
+    @property
+    def paths(self):
+        if self._paths is None:
+            self._paths = {}
+
+            def add_path(self, path_name, directory):
+                path = self._paths.setdefault(path_name, [])
+                path.append(directory)
+
+            # Add paths if they exist.
+            for var, directory in [
+                    ('PATH', self.pkg.prefix.bin),
+                    ('MANPATH', self.pkg.prefix.man),
+                    ('MANPATH', self.pkg.prefix.share_man),
+                    ('LD_LIBRARY_PATH', self.pkg.prefix.lib),
+                    ('LD_LIBRARY_PATH', self.pkg.prefix.lib64)]:
+
+                if os.path.isdir(directory):
+                    add_path(var, directory)
+
+            # short description is just the package + version
+            # TODO: maybe packages can optionally provide it.
+            self.short_description = self.pkg.spec.format("$_ $@")
+
+            # long description is the docstring with reduced whitespace.
+            if self.pkg.__doc__:
+                self.long_description = re.sub(r'\s+', ' ', self.pkg.__doc__)
+
+        return self._paths
+
+
+    def write(self):
+        """Write out a module file for this object."""
+        module_dir = os.path.dirname(self.file_name)
+        if not os.path.exists():
+            mkdirp(module_dir)
+
+        # If there are no paths, no need for a dotkit.
+        if not self.paths:
+            return
+
+        with closing(open(self.file_name)) as f:
+            self._write(f)
+
+
+    def _write(self, stream):
+        """To be implemented by subclasses."""
+        raise NotImplementedError()
+
+
+    @property
+    def file_name(self):
+        """Subclasses should implement this to return the name of the file
+           where this module lives."""
+        return self.pkg.spec.format('$_$@$%@$+$=$#')
+
+
+    def remove(self):
+        mod_file = self.file_name
+        if os.path.exists(mod_file):
+            shutil.rmtree(mod_file, ignore_errors=True)
+
+
+class Dotkit(EnvModule):
+    @property
+    def file_name(self):
+        spec = self.pkg.spec
+        return join_path(dotkit_path, spec.architecture,
+                         spec.format('$_$@$%@$+$#.dk'))
+
+
+    def _write(self, dk_file):
+        # Category
+        if self.category:
+            dk_file.write('#c %s\n' % self.category)
+
+        # Short description
+        if self.short_description:
+            dk_file.write('#d %s\n' % self.short_description)
+
+        # Long description
+        if self.long_description:
+            for line in textwrap.wrap(self.long_description, 72):
+                dk_file.write("#h %s\n" % line)
+
+        # Path alterations
+        for var, dirs in self.paths.items():
+            for directory in dirs:
+                dk_file.write("dk_alter %s %s\n" % (var, directory))
+
+        # Let CMake find this package.
+        dk_file.write("dk_alter CMAKE_PREFIX_PATH %s\n" % pkg.prefix)
+
+
+class TclModule(EnvModule):
+    @property
+    def file_name(self):
+        spec = self.pkg.spec
+        return join_path(tcl_mod_path, spec.architecture,
+                         spec.format('$_$@$%@$+$#'))
+
+
+    def _write(self, m_file):
+        # TODO: cateogry?
+        m_file.write('#%Module1.0\n')
+
+        # Short description
+        if self.short_description:
+            m_file.write('module-whatis \"%s\"\n\n' % self.short_description)
+
+        # Long description
+        if self.long_description:
+            m_file.write('proc ModulesHelp { } {\n')
+            doc = re.sub(r'"', '\"', self.long_description)
+            m_file.write("puts stderr \"%s\"\n" % doc)
+            m_file.write('}\n\n')
+
+        # Path alterations
+        for var, dirs in self.paths.items():
+            for directory in dirs:
+                m_file.write("prepend-path %s \"%s\"\n" % (var, directory))
+
+        m_file.write("prepend-path CMAKE_PREFIX_PATH \"%s\"\n" % pkg.prefix)
diff --git a/share/spack/setup-env.bash b/share/spack/setup-env.sh
similarity index 56%
rename from share/spack/setup-env.bash
rename to share/spack/setup-env.sh
index c23a5acab437905c352319bbdf9041dddf9ab1a2..7cadc6f20220531fee7ef3a4cf4b6ac5b1e70cb6 100755
--- a/share/spack/setup-env.bash
+++ b/share/spack/setup-env.sh
@@ -23,17 +23,14 @@
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 
-#
 #
 # This file is part of Spack and sets up the spack environment for
-# bash shells.  This includes dotkit support as well as putting spack
-# in your path.  Source it like this:
-#
-#    . /path/to/spack/share/spack/setup-env.bash
+# bash and zsh.  This includes dotkit support, module support, and
+# it also puts spack in your path.  Source it like this:
 #
+#    . /path/to/spack/share/spack/setup-env.sh
 #
 
-
 ########################################################################
 # This is a wrapper around the spack command that forwards calls to
 # 'spack use' and 'spack unuse' to shell functions.  This in turn
@@ -59,56 +56,46 @@
 # spack dotfiles.
 ########################################################################
 function spack {
-    _spack_subcommand=$1; shift
-    _spack_spec="$@"
+    _sp_subcommand=$1; shift
+    _sp_spec="$@"
 
     # Filter out use and unuse.  For any other commands, just run the
     # command.
-    case $_spack_subcommand in
-        "use"|"unuse")
+    case $_sp_subcommand in
+        "use"|"unuse"|"load"|"unload")
             # Shift any other args for use off before parsing spec.
-            _spack_use_args=""
+            _sp_module_args=""
             if [[ "$1" =~ ^- ]]; then
-                _spack_use_args="$1"; shift
-                _spack_spec="$@"
+                _sp_module_args="$1"; shift
+                _sp_spec="$@"
             fi
 
-            # Here the user has run use or unuse with a spec.  Find a matching
-            # spec with a dotkit using spack dotkit, then use or unuse the
-            # result.  If spack dotkit comes back with an error, do nothing.
-            if _spack_full_spec=$(command spack dotkit $_spack_spec); then
-                $_spack_subcommand $_spack_use_args $_spack_full_spec
-            fi
-            return
-            ;;
-        "load"|"unload")
-            # Shift any other args for module off before parsing spec.
-            _spack_module_args=""
-            if [[ "$1" =~ ^- ]]; then
-                _spack_module_args="$1"; shift
-                _spack_spec="$@"
-            fi
+            # Translate the parameter into pieces of a command.
+            # _sp_modtype is an arg to spack module find, and
+            # _sp_sh_cmd is the equivalent shell command.
+            case $_sp_subcommand in
+                "use"|"unuse")
+                    _sp_modtype=dotkit
+                    _sp_sh_cmd=$_sp_subcommand
+                    ;;
+                "load"|"unload")
+                    _sp_modtype=tcl
+                    _sp_sh_cmd="module $_sp_subcommand"
+                    ;;
+            esac
 
             # Here the user has run use or unuse with a spec.  Find a matching
-            # spec with a dotkit using spack dotkit, then use or unuse the
-            # result.  If spack dotkit comes back with an error, do nothing.
-            if _spack_full_spec=$(command spack tclmodule $_spack_spec); then
-                $_spack_subcommand $_spack_module_args $_spack_full_spec
+            # spec using 'spack module find', then use the appropriate module
+            # tool's commands to add/remove the result from the environment.
+            # If spack module command comes back with an error, do nothing.
+            if _sp_full_spec=$(command spack module find $_sp_modtype $_sp_spec); then
+                $_sp_sh_cmd $_sp_module_args $_sp_full_spec
             fi
             return
             ;;
         *)
-            command spack $_spack_subcommand "$@"
-            return
-            ;;
+            command spack $_sp_subcommand $_sp_spec
     esac
-
-    # If no args or -h, just run that command as well.
-    if [ -z "$1" -o "$1" = "-h" ]; then
-        command spack $_spack_subcommand -h
-        return
-    fi
-
 }
 
 ########################################################################
@@ -119,31 +106,45 @@ function spack {
 function _spack_pathadd {
     # If no variable name is supplied, just append to PATH
     # otherwise append to that variable.
-    varname=PATH
-    path="$1"
+    _pa_varname=PATH
+    _pa_new_path="$1"
     if [ -n "$2" ]; then
-        varname="$1"
-        path="$2"
+        _pa_varname="$1"
+        _pa_new_path="$2"
     fi
 
     # Do the actual prepending here.
-    eval "oldvalue=\"\$$varname\""
-    if [ -d "$path" ] && [[ ":$oldvalue:" != *":$path:"* ]]; then
-        if [ -n "$oldvalue" ]; then
-            eval "export $varname=\"$path:$oldvalue\""
+    eval "_pa_oldvalue=\$${_pa_varname}"
+
+    if [ -d "$_pa_new_path" ] && [[ ":$_pa_oldvalue:" != *":$_pa_new_path:"* ]]; then
+        if [ -n "$_pa_oldvalue" ]; then
+            eval "export $_pa_varname=\"$_pa_new_path:$_pa_oldvalue\""
         else
-            export $varname="$path"
+            export $_pa_varname="$_pa_new_path"
         fi
     fi
 }
 
+#
+# Figure out where this file is.  Below code needs to be portable to
+# bash and zsh.
+#
+_sp_source_file="${BASH_SOURCE[0]}"  # Bash's location of last sourced file.
+if [ -z "$_sp_source_file" ]; then
+    _sp_source_file="$0:A"           # zsh way to do it
+    if [[ "$_sp_source_file" == *":A" ]]; then
+        # Not zsh either... bail out with plain old $0,
+        # which WILL NOT work if this is sourced indirectly.
+        _sp_source_file="$0"
+    fi
+fi
 
 #
-# Set up dotkit and path in the user environment
+# Set up modules and dotkit search paths in the user environment
 #
-_spack_share_dir="$(dirname ${BASH_SOURCE[0]})"
-_spack_prefix="$(dirname $(dirname $_spack_share_dir))"
+_sp_share_dir="$(dirname $_sp_source_file)"
+_sp_prefix="$(dirname $(dirname $_sp_share_dir))"
 
-_spack_pathadd DK_NODE "$_spack_share_dir/dotkit"
-_spack_pathadd MODULEPATH "$_spack_share_dir/modules"
-_spack_pathadd PATH    "$_spack_prefix/bin"
+_spack_pathadd DK_NODE    "$_sp_share_dir/dotkit"
+_spack_pathadd MODULEPATH "$_sp_share_dir/modules"
+_spack_pathadd PATH       "$_sp_prefix/bin"
diff --git a/share/spack/setup-env.zsh b/share/spack/setup-env.zsh
deleted file mode 100755
index 9aba92818d73b9fc8e15371c433b59718679711b..0000000000000000000000000000000000000000
--- a/share/spack/setup-env.zsh
+++ /dev/null
@@ -1,122 +0,0 @@
-##############################################################################
-# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Written by David Beckingsale, david@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://scalability-llnl.github.io/spack
-# Please also see the LICENSE file for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License (as published by
-# the Free Software Foundation) version 2.1 dated February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-
-#
-#
-# This file is part of Spack and sets up the spack environment for zsh shells.
-# This includes dotkit and module support as well as putting spack
-# in your path.  Source it like this:
-#
-#    source /path/to/spack/share/spack/setup-env.zsh
-#
-#
-
-
-########################################################################
-# This is a wrapper around the spack command that forwards calls to
-# 'spack use' and 'spack unuse' to shell functions.  This in turn
-# allows them to be used to invoke dotkit functions.
-#
-# 'spack use' is smarter than just 'use' because it converts its
-# arguments into a unique spack spec that is then passed to dotkit
-# commands.  This allows the user to use packages without knowing all
-# their installation details.
-#
-# e.g., rather than requring a full spec for libelf, the user can type:
-#
-#     spack use libelf
-#
-# This will first find the available libelf dotkits and use a
-# matching one.  If there are two versions of libelf, the user would
-# need to be more specific, e.g.:
-#
-#     spack use libelf@0.8.13
-#
-# This is very similar to how regular spack commands work and it
-# avoids the need to come up with a user-friendly naming scheme for
-# spack dotfiles.
-########################################################################
-function spack {
-    _spack_subcommand=${1}; shift
-    _spack_spec="$@"
-
-    # Filter out use and unuse.  For any other commands, just run the
-    # command.
-    case ${_spack_subcommand} in
-        "use"|"unuse")
-            # Shift any other args for use off before parsing spec.
-            _spack_use_args=""
-            if [[ "$1" =~ ^- ]]; then
-                _spack_use_args="$1"; shift
-                _spack_spec="$@"
-            fi
-
-            # Here the user has run use or unuse with a spec.  Find a matching
-            # spec with a dotkit using spack dotkit, then use or unuse the
-            # result.  If spack dotkit comes back with an error, do nothing.
-            if _spack_full_spec=$(command spack dotkit $_spack_spec); then
-                $_spack_subcommand $_spack_use_args $_spack_full_spec
-            fi
-            return
-            ;;
-        "load"|"unload")
-            # Shift any other args for module off before parsing spec.
-            _spack_module_args=""
-            if [[ "$1" =~ ^- ]]; then
-                _spack_module_args="$1"; shift
-                _spack_spec="$@"
-            fi
-
-            # Here the user has run use or unuse with a spec.  Find a matching
-            # spec with a dotkit using spack dotkit, then use or unuse the
-            # result.  If spack dotkit comes back with an error, do nothing.
-            if _spack_full_spec=$(command spack tclmodule ${_spack_spec}); then
-                module ${_spack_subcommand} ${_spack_module_args} ${_spack_full_spec}
-            fi
-            return
-            ;;
-        *)
-            command spack $_spack_subcommand "$@"
-            return
-            ;;
-    esac
-
-    # If no args or -h, just run that command as well.
-    if [ -z "$1" -o "$1" = "-h" ]; then
-        command spack $_spack_subcommand -h
-        return
-    fi
-
-}
-
-#
-# Set up dotkit and path in the user environment
-#
-_spack_share_dir="$(dirname $0:A)"
-_spack_prefix="$(dirname $(dirname ${_spack_share_dir}))"
-
-export DK_NODE="$_spack_share_dir/dotkit:$DK_NODE"
-export MODULEPATH="$_spack_share_dir/modules:$MODULEPATH"
-export PATH="$_spack_prefix/bin:$PATH"