diff --git a/PKG-INFO b/PKG-INFO
index b62b00d577fef913e5de77ce8c93c55132d67f4d..b22a155ae9c02ddadeddfd8382a81bd029c2ef42 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,11 +1,200 @@
-Metadata-Version: 1.0
+Metadata-Version: 2.1
 Name: scruffington
-Version: 0.3.3
+Version: 0.3.8.2
 Summary: The janitor
 Home-page: https://github.com/snare/scruffy
 Author: snare
 Author-email: snare@ho.ax
 License: MIT
-Description: UNKNOWN
+Description: Scruffy
+        =======
+        
+        .. image:: https://img.shields.io/travis/snare/scruffy.svg
+            :target: https://travis-ci.org/snare/scruffy
+        
+        .. image:: https://img.shields.io/pypi/format/scruffington.svg
+            :target: https://pypi.python.org/pypi/scruffington
+        
+        .. image:: https://readthedocs.org/projects/scruffy/badge/?version=latest
+            :target: http://scruffy.readthedocs.org/en/latest/
+        
+        
+        *Scruffy. The Janitor.*
+        
+        Scruffy is a framework for taking care of a bunch of boilerplate in Python apps. It handles the loading of configuration files, the loading and management of plugins, and the management of other filesystem resources such as temporary files and directories, log files, etc.
+        
+        A typical use case for Scruffy is a command-line Python tool with some or all of the following requirements:
+        
+        * Read a set of configuration defaults
+        * Read a local configuration file and apply it on top of the defaults
+        * Allow overriding some configuration options with command line flags or at runtime
+        * Load a core set of Python-based plugins
+        * Load a set of user-defined Python-based plugins
+        * Generate log files whose name, location and other logging settings are based on configuration
+        * Store application state between runs in a file or database
+        
+        Scruffy is used by Voltron_ and Calculon_
+        
+        .. _Voltron: https://github.com/snare/voltron
+        .. _Calculon: https://github.com/snare/calculon
+        
+        Installation
+        ------------
+        
+        A standard python setup script is included.
+        
+            $ python setup.py install
+        
+        This will install the Scruffy package wherever that happens on your system.
+        
+        Alternately, Scruffy can be installed with `pip` from PyPi (where it's called `scruffington`, because I didn't check for a conflict before I named it).
+        
+            $ pip install scruffington
+        
+        Documentation
+        -------------
+        
+        Full documentation is hosted at readthedocs_
+        
+        .. _readthedocs: http://scruffy.readthedocs.io/
+        
+        Quick start
+        -----------
+        
+        Config
+        ~~~~~~
+        
+        Load a user config file, and apply it on top of a set of defaults loaded from inside the Python package we're currently running from.
+        
+        *thingy.yaml*:
+        
+        .. code:: yaml
+        
+            some_property:  1
+            other_property: a thing
+        
+        *thingy.py*:
+        
+        .. code:: python
+        
+            from scruffy import ConfigFile
+        
+            c = ConfigFile('thingy.yaml', load=True,
+                defaults=File('defaults.yaml', parent=PackageDirectory())
+            )
+        
+            print("c.some_property == {c.some_property}".format(c=c))
+            print("c.other_property == {c.other_property}".format(c=c))
+        
+        Run it:
+        
+        ::
+        
+            $ python thingy.py
+            c.some_property == 1
+            c.other_property == a thing
+        
+        Plugins
+        ~~~~~~~
+        
+        Load some plugins.
+        
+        *~/.thingy/plugins/example.py*:
+        
+        .. code:: python
+        
+            from scruffy import Plugin
+        
+            class ExamplePlugin(Plugin):
+                def do_a_thing(self):
+                    print('{}.{} is doing a thing'.format(__name__, self.__class__.__name__))
+        
+        *thingy.py*:
+        
+        .. code:: python
+        
+            from scruffy import PluginDirectory, PluginRegistry
+        
+            pd = PluginDirectory('~/.thingy/plugins')
+            pd.load()
+        
+            for p in PluginRegistry.plugins:
+                print("Initialising plugin {}".format(p))
+                p().do_a_thing()
+        
+        Run it:
+        
+        ::
+        
+            $ python thingy.py
+            Initialising plugin <class 'example.ExamplePlugin'>
+            example.ExamplePlugin is doing a thing
+        
+        Logging
+        ~~~~~~~
+        
+        Scruffy's `LogFile` class will do some configuration of Python's `logging` module.
+        
+        *log.py*:
+        
+        .. code:: python
+        
+            import logging
+            from scruffy import LogFile
+        
+            log = logging.getLogger('main')
+            log.setLevel(logging.INFO)
+            LogFile('/tmp/thingy.log', logger='main').configure()
+        
+            log.info('Hello from log.py')
+        
+        */tmp/thingy.log*:
+        
+        ::
+        
+            Hello from log.py
+        
+        Environment
+        ~~~~~~~~~~~
+        
+        Scruffy's `Environment` class ties all the other stuff together. The other classes can be instantiated as named children of an `Environment`, which will load any `Config` objects, apply the configs to the other objects, and then prepare the other objects.
+        
+        *~/.thingy/config*:
+        
+        .. code:: yaml
+        
+            log_dir:    /tmp/logs
+            log_file:   thingy.log
+        
+        *env.py*:
+        
+        .. code:: python
+        
+            from scruffy import *
+        
+            e = Environment(
+                main_dir=Directory('~/.thingy', create=True,
+                    config=ConfigFile('config', defaults=File('defaults.yaml', parent=PackageDirectory())),
+                    lock=LockFile('lock')
+                    user_plugins=PluginDirectory('plugins')
+                ),
+                log_dir=Directory('{config:log_dir}', create=True
+                    LogFile('{config:log_file}', logger='main')
+                ),
+                pkg_plugins=PluginDirectory('plugins', parent=PackageDirectory())
+            )
+        
+        License
+        -------
+        
+        See LICENSE file. If you use this and don't hate it, buy me a beer at a conference some time.
+        
+        Credits
+        -------
+        
+        Props to richo_. Flat duck pride.
+        
+        .. _richo: http://github.com/richo
 Keywords: scruffy
 Platform: UNKNOWN
+Description-Content-Type: text/x-rst
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000000000000000000000000000000000000..1a866667c8897c73fce9c2c32bb5e351f53434c1
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,189 @@
+Scruffy
+=======
+
+.. image:: https://img.shields.io/travis/snare/scruffy.svg
+    :target: https://travis-ci.org/snare/scruffy
+
+.. image:: https://img.shields.io/pypi/format/scruffington.svg
+    :target: https://pypi.python.org/pypi/scruffington
+
+.. image:: https://readthedocs.org/projects/scruffy/badge/?version=latest
+    :target: http://scruffy.readthedocs.org/en/latest/
+
+
+*Scruffy. The Janitor.*
+
+Scruffy is a framework for taking care of a bunch of boilerplate in Python apps. It handles the loading of configuration files, the loading and management of plugins, and the management of other filesystem resources such as temporary files and directories, log files, etc.
+
+A typical use case for Scruffy is a command-line Python tool with some or all of the following requirements:
+
+* Read a set of configuration defaults
+* Read a local configuration file and apply it on top of the defaults
+* Allow overriding some configuration options with command line flags or at runtime
+* Load a core set of Python-based plugins
+* Load a set of user-defined Python-based plugins
+* Generate log files whose name, location and other logging settings are based on configuration
+* Store application state between runs in a file or database
+
+Scruffy is used by Voltron_ and Calculon_
+
+.. _Voltron: https://github.com/snare/voltron
+.. _Calculon: https://github.com/snare/calculon
+
+Installation
+------------
+
+A standard python setup script is included.
+
+    $ python setup.py install
+
+This will install the Scruffy package wherever that happens on your system.
+
+Alternately, Scruffy can be installed with `pip` from PyPi (where it's called `scruffington`, because I didn't check for a conflict before I named it).
+
+    $ pip install scruffington
+
+Documentation
+-------------
+
+Full documentation is hosted at readthedocs_
+
+.. _readthedocs: http://scruffy.readthedocs.io/
+
+Quick start
+-----------
+
+Config
+~~~~~~
+
+Load a user config file, and apply it on top of a set of defaults loaded from inside the Python package we're currently running from.
+
+*thingy.yaml*:
+
+.. code:: yaml
+
+    some_property:  1
+    other_property: a thing
+
+*thingy.py*:
+
+.. code:: python
+
+    from scruffy import ConfigFile
+
+    c = ConfigFile('thingy.yaml', load=True,
+        defaults=File('defaults.yaml', parent=PackageDirectory())
+    )
+
+    print("c.some_property == {c.some_property}".format(c=c))
+    print("c.other_property == {c.other_property}".format(c=c))
+
+Run it:
+
+::
+
+    $ python thingy.py
+    c.some_property == 1
+    c.other_property == a thing
+
+Plugins
+~~~~~~~
+
+Load some plugins.
+
+*~/.thingy/plugins/example.py*:
+
+.. code:: python
+
+    from scruffy import Plugin
+
+    class ExamplePlugin(Plugin):
+        def do_a_thing(self):
+            print('{}.{} is doing a thing'.format(__name__, self.__class__.__name__))
+
+*thingy.py*:
+
+.. code:: python
+
+    from scruffy import PluginDirectory, PluginRegistry
+
+    pd = PluginDirectory('~/.thingy/plugins')
+    pd.load()
+
+    for p in PluginRegistry.plugins:
+        print("Initialising plugin {}".format(p))
+        p().do_a_thing()
+
+Run it:
+
+::
+
+    $ python thingy.py
+    Initialising plugin <class 'example.ExamplePlugin'>
+    example.ExamplePlugin is doing a thing
+
+Logging
+~~~~~~~
+
+Scruffy's `LogFile` class will do some configuration of Python's `logging` module.
+
+*log.py*:
+
+.. code:: python
+
+    import logging
+    from scruffy import LogFile
+
+    log = logging.getLogger('main')
+    log.setLevel(logging.INFO)
+    LogFile('/tmp/thingy.log', logger='main').configure()
+
+    log.info('Hello from log.py')
+
+*/tmp/thingy.log*:
+
+::
+
+    Hello from log.py
+
+Environment
+~~~~~~~~~~~
+
+Scruffy's `Environment` class ties all the other stuff together. The other classes can be instantiated as named children of an `Environment`, which will load any `Config` objects, apply the configs to the other objects, and then prepare the other objects.
+
+*~/.thingy/config*:
+
+.. code:: yaml
+
+    log_dir:    /tmp/logs
+    log_file:   thingy.log
+
+*env.py*:
+
+.. code:: python
+
+    from scruffy import *
+
+    e = Environment(
+        main_dir=Directory('~/.thingy', create=True,
+            config=ConfigFile('config', defaults=File('defaults.yaml', parent=PackageDirectory())),
+            lock=LockFile('lock')
+            user_plugins=PluginDirectory('plugins')
+        ),
+        log_dir=Directory('{config:log_dir}', create=True
+            LogFile('{config:log_file}', logger='main')
+        ),
+        pkg_plugins=PluginDirectory('plugins', parent=PackageDirectory())
+    )
+
+License
+-------
+
+See LICENSE file. If you use this and don't hate it, buy me a beer at a conference some time.
+
+Credits
+-------
+
+Props to richo_. Flat duck pride.
+
+.. _richo: http://github.com/richo
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
index d9b757c06e7970206bb39559ac861b0eed288a49..08ca1ed387f1d2fa73ce8d540b9ec992af73b76f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,14 @@
+python-scruffy (0.3.8.2-1) UNRELEASED; urgency=medium
+
+  * New upstream version 0.3.8.2.
+  * Patch imp to importlib for Python 3.12 compatibility. (Closes: #1058129)
+  * d/watch: Bump to version 4.
+  * Bump standards version to 4.6.2
+    - Update d/copyright to include upstream copyright notice.
+    - d/control: Add Rules-Requires-Root field.
+
+ -- Dale Richards <dale@dalerichards.net>  Mon, 25 Dec 2023 22:16:27 +0000
+
 python-scruffy (0.3.3-3) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/debian/control b/debian/control
index 8f9e980d6f8ce56fef9f4592efd931535c149444..5500c41f601ac8e5d6b3da5ef60f6dafee8ec301 100644
--- a/debian/control
+++ b/debian/control
@@ -9,7 +9,8 @@ Build-Depends: debhelper-compat (= 13),
                python3-setuptools,
                python3-six,
                python3-yaml,
-Standards-Version: 3.9.8
+Standards-Version: 4.6.2
+Rules-Requires-Root: no
 Homepage: https://github.com/snare/scruffy
 Vcs-Git: https://salsa.debian.org/python-team/packages/python-scruffy.git
 Vcs-Browser: https://salsa.debian.org/python-team/packages/python-scruffy
diff --git a/debian/copyright b/debian/copyright
index 9db7b09736eb5a0c483ccd520bdb5f4fbd0f1f88..3047bc72223393fbbe8f1aea7aaedea45f52af67 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -3,11 +3,12 @@ Upstream-Name: scruffy
 Source: <https://github.com/snare/scruffy>
 
 Files: *
-Copyright: snare <snare@ho.ax>
+Copyright: 2016 Loukas K <snare@ho.ax>
 License: Expat
 
 Files: debian/*
 Copyright: 2016 ChangZhuo Chen (陳昌倬) <czchen@debian.org>
+ 2023 Dale Richards <dale@dalerichards.net>
 License: Expat
 
 License: Expat
diff --git a/debian/patches/0001-Migrate-from-imp-to-importlib.patch b/debian/patches/0001-Migrate-from-imp-to-importlib.patch
new file mode 100644
index 0000000000000000000000000000000000000000..9e9188f99e372e3a6bb8e71a88ac355a4b1fdfaa
--- /dev/null
+++ b/debian/patches/0001-Migrate-from-imp-to-importlib.patch
@@ -0,0 +1,32 @@
+From: Dale Richards <dale@dalerichards.net>
+Date: Mon, 25 Dec 2023 21:41:37 +0000
+Subject: Migrate from imp to importlib
+Forwarded: https://github.com/snare/scruffy/pull/31
+
+---
+ scruffy/plugin.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/scruffy/plugin.py
++++ b/scruffy/plugin.py
+@@ -5,7 +5,7 @@
+ Classes for representing and loading plugins.
+ """
+ import os
+-import imp
++import importlib
+ import six
+ 
+ 
+@@ -56,9 +56,9 @@
+             # if it's a file, load it
+             modname, ext = os.path.splitext(filename)
+             if os.path.isfile(filepath) and ext == '.py':
+-                file, path, descr = imp.find_module(modname, [directory])
++                file, path, descr = importlib.find_spec(modname, [directory])
+                 if file:
+-                    mod = imp.load_module(modname, file, path, descr)
++                    mod = importlib.load_module(modname, file, path, descr)
+ 
+             # if it's a directory, recurse into it
+             if os.path.isdir(filepath):
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000000000000000000000000000000000000..953f293cdaf2660aab901bd46644e8eb24714e67
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1 @@
+0001-Migrate-from-imp-to-importlib.patch
diff --git a/debian/watch b/debian/watch
index a5e59937cefedbf0d8db4fd385cf2be62f086bf0..2a6a1e040b91e68609521d4bc27c437a28d0c593 100644
--- a/debian/watch
+++ b/debian/watch
@@ -1,3 +1,3 @@
-version=3
+version=4
 opts=uversionmangle=s/(rc|a|b|c)/~$1/ \
 https://pypi.debian.net/scruffington/scruffington-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))
diff --git a/scruffington.egg-info/PKG-INFO b/scruffington.egg-info/PKG-INFO
index b62b00d577fef913e5de77ce8c93c55132d67f4d..b22a155ae9c02ddadeddfd8382a81bd029c2ef42 100644
--- a/scruffington.egg-info/PKG-INFO
+++ b/scruffington.egg-info/PKG-INFO
@@ -1,11 +1,200 @@
-Metadata-Version: 1.0
+Metadata-Version: 2.1
 Name: scruffington
-Version: 0.3.3
+Version: 0.3.8.2
 Summary: The janitor
 Home-page: https://github.com/snare/scruffy
 Author: snare
 Author-email: snare@ho.ax
 License: MIT
-Description: UNKNOWN
+Description: Scruffy
+        =======
+        
+        .. image:: https://img.shields.io/travis/snare/scruffy.svg
+            :target: https://travis-ci.org/snare/scruffy
+        
+        .. image:: https://img.shields.io/pypi/format/scruffington.svg
+            :target: https://pypi.python.org/pypi/scruffington
+        
+        .. image:: https://readthedocs.org/projects/scruffy/badge/?version=latest
+            :target: http://scruffy.readthedocs.org/en/latest/
+        
+        
+        *Scruffy. The Janitor.*
+        
+        Scruffy is a framework for taking care of a bunch of boilerplate in Python apps. It handles the loading of configuration files, the loading and management of plugins, and the management of other filesystem resources such as temporary files and directories, log files, etc.
+        
+        A typical use case for Scruffy is a command-line Python tool with some or all of the following requirements:
+        
+        * Read a set of configuration defaults
+        * Read a local configuration file and apply it on top of the defaults
+        * Allow overriding some configuration options with command line flags or at runtime
+        * Load a core set of Python-based plugins
+        * Load a set of user-defined Python-based plugins
+        * Generate log files whose name, location and other logging settings are based on configuration
+        * Store application state between runs in a file or database
+        
+        Scruffy is used by Voltron_ and Calculon_
+        
+        .. _Voltron: https://github.com/snare/voltron
+        .. _Calculon: https://github.com/snare/calculon
+        
+        Installation
+        ------------
+        
+        A standard python setup script is included.
+        
+            $ python setup.py install
+        
+        This will install the Scruffy package wherever that happens on your system.
+        
+        Alternately, Scruffy can be installed with `pip` from PyPi (where it's called `scruffington`, because I didn't check for a conflict before I named it).
+        
+            $ pip install scruffington
+        
+        Documentation
+        -------------
+        
+        Full documentation is hosted at readthedocs_
+        
+        .. _readthedocs: http://scruffy.readthedocs.io/
+        
+        Quick start
+        -----------
+        
+        Config
+        ~~~~~~
+        
+        Load a user config file, and apply it on top of a set of defaults loaded from inside the Python package we're currently running from.
+        
+        *thingy.yaml*:
+        
+        .. code:: yaml
+        
+            some_property:  1
+            other_property: a thing
+        
+        *thingy.py*:
+        
+        .. code:: python
+        
+            from scruffy import ConfigFile
+        
+            c = ConfigFile('thingy.yaml', load=True,
+                defaults=File('defaults.yaml', parent=PackageDirectory())
+            )
+        
+            print("c.some_property == {c.some_property}".format(c=c))
+            print("c.other_property == {c.other_property}".format(c=c))
+        
+        Run it:
+        
+        ::
+        
+            $ python thingy.py
+            c.some_property == 1
+            c.other_property == a thing
+        
+        Plugins
+        ~~~~~~~
+        
+        Load some plugins.
+        
+        *~/.thingy/plugins/example.py*:
+        
+        .. code:: python
+        
+            from scruffy import Plugin
+        
+            class ExamplePlugin(Plugin):
+                def do_a_thing(self):
+                    print('{}.{} is doing a thing'.format(__name__, self.__class__.__name__))
+        
+        *thingy.py*:
+        
+        .. code:: python
+        
+            from scruffy import PluginDirectory, PluginRegistry
+        
+            pd = PluginDirectory('~/.thingy/plugins')
+            pd.load()
+        
+            for p in PluginRegistry.plugins:
+                print("Initialising plugin {}".format(p))
+                p().do_a_thing()
+        
+        Run it:
+        
+        ::
+        
+            $ python thingy.py
+            Initialising plugin <class 'example.ExamplePlugin'>
+            example.ExamplePlugin is doing a thing
+        
+        Logging
+        ~~~~~~~
+        
+        Scruffy's `LogFile` class will do some configuration of Python's `logging` module.
+        
+        *log.py*:
+        
+        .. code:: python
+        
+            import logging
+            from scruffy import LogFile
+        
+            log = logging.getLogger('main')
+            log.setLevel(logging.INFO)
+            LogFile('/tmp/thingy.log', logger='main').configure()
+        
+            log.info('Hello from log.py')
+        
+        */tmp/thingy.log*:
+        
+        ::
+        
+            Hello from log.py
+        
+        Environment
+        ~~~~~~~~~~~
+        
+        Scruffy's `Environment` class ties all the other stuff together. The other classes can be instantiated as named children of an `Environment`, which will load any `Config` objects, apply the configs to the other objects, and then prepare the other objects.
+        
+        *~/.thingy/config*:
+        
+        .. code:: yaml
+        
+            log_dir:    /tmp/logs
+            log_file:   thingy.log
+        
+        *env.py*:
+        
+        .. code:: python
+        
+            from scruffy import *
+        
+            e = Environment(
+                main_dir=Directory('~/.thingy', create=True,
+                    config=ConfigFile('config', defaults=File('defaults.yaml', parent=PackageDirectory())),
+                    lock=LockFile('lock')
+                    user_plugins=PluginDirectory('plugins')
+                ),
+                log_dir=Directory('{config:log_dir}', create=True
+                    LogFile('{config:log_file}', logger='main')
+                ),
+                pkg_plugins=PluginDirectory('plugins', parent=PackageDirectory())
+            )
+        
+        License
+        -------
+        
+        See LICENSE file. If you use this and don't hate it, buy me a beer at a conference some time.
+        
+        Credits
+        -------
+        
+        Props to richo_. Flat duck pride.
+        
+        .. _richo: http://github.com/richo
 Keywords: scruffy
 Platform: UNKNOWN
+Description-Content-Type: text/x-rst
diff --git a/scruffington.egg-info/SOURCES.txt b/scruffington.egg-info/SOURCES.txt
index b147ef573d17ea056417a9ecd07a426483049729..fb4f8be83949d6f7dad56a8754569015417ad5eb 100644
--- a/scruffington.egg-info/SOURCES.txt
+++ b/scruffington.egg-info/SOURCES.txt
@@ -1,3 +1,4 @@
+README.rst
 setup.cfg
 setup.py
 scruffington.egg-info/PKG-INFO
diff --git a/scruffington.egg-info/requires.txt b/scruffington.egg-info/requires.txt
index 4f1e759df5c275c61ba0b359fab468e2c0f02e48..2cd03d50af1ed3966bd7c277f82c09b0696be8e0 100644
--- a/scruffington.egg-info/requires.txt
+++ b/scruffington.egg-info/requires.txt
@@ -1,2 +1,2 @@
 pyyaml
-six
\ No newline at end of file
+six
diff --git a/scruffy/config.py b/scruffy/config.py
index 1177430e0864f7ab50d8f77862c5448fe27eaa1c..9ba455269384165dacb7751eb83ba925df34d13c 100644
--- a/scruffy/config.py
+++ b/scruffy/config.py
@@ -1,9 +1,18 @@
+"""
+Config
+------
+
+Classes for loading and accessing configuration data.
+"""
+from six import string_types
+
 import copy
 import os
 import ast
 import yaml
 import re
 
+from six import string_types
 from .file import File
 
 
@@ -13,15 +22,15 @@ class ConfigNode(object):
 
     Can be accessed as a dictionary, like this:
 
-        config['top-level-section']['second-level-property']
+        >>> config['top-level-section']['second-level-property']
 
     Or as a dictionary with a key path, like this:
 
-        config['top_level_section.second_level_property']
+        >>> config['top_level_section.second_level_property']
 
     Or as an object, like this:
 
-        config.top_level_section.second_level_property
+        >>> config.top_level_section.second_level_property
     """
     def __init__(self, data={}, defaults={}, root=None, path=None):
         super(ConfigNode, self).__init__()
@@ -72,9 +81,6 @@ class ConfigNode(object):
     def __le__(self, other):
         return self._get_value() <= other
 
-    def __le__(self, other):
-        return self._get_value() <= other
-
     def __eq__(self, other):
         return self._get_value() == other
 
@@ -90,6 +96,12 @@ class ConfigNode(object):
     def __contains__(self, key):
         return key in self._get_value()
 
+    def __nonzero__(self):
+        return self._get_value() != None
+
+    def __bool__(self):
+        return self._get_value() != None
+
     def items(self):
         return self._get_value().items()
 
@@ -133,7 +145,7 @@ class ConfigNode(object):
         the item referred to by the key path.
         """
         # Split up the key path
-        if type(self._path) == str:
+        if isinstance(self._path, string_types):
             key_path = self._path.split('.')
         else:
             key_path = [self._path]
@@ -195,22 +207,24 @@ class ConfigNode(object):
 
         `options` is a dict of keypath/value pairs like this (similar to
         CherryPy's config mechanism:
-        {
-            'server.port': 8080,
-            'server.host': 'localhost',
-            'admin.email': 'admin@lol'
-        }
+
+            >>> c.update(options={
+            ...     'server.port': 8080,
+            ...     'server.host': 'localhost',
+            ...     'admin.email': 'admin@lol'
+            ... })
 
         `data` is a dict of actual config data, like this:
-        {
-            'server': {
-                'port': 8080,
-                'host': 'localhost'
-            },
-            'admin': {
-                'email': 'admin@lol'
-            }
-        }
+
+            >>> c.update(data={
+            ...     'server': {
+            ...         'port': 8080,
+            ...         'host': 'localhost'
+            ...     },
+            ...     'admin': {
+            ...         'email': 'admin@lol'
+            ...     }
+            ... })
         """
         # Handle an update with a set of options like CherryPy does
         for key in options:
@@ -244,17 +258,17 @@ class ConfigEnv(ConfigNode):
     """
     Config based on based on environment variables.
     """
-    def __init__(self, *args, **kwargs):
+    def __init__(self, prefix='SCRUFFY', *args, **kwargs):
         super(ConfigEnv, self).__init__(*args, **kwargs)
 
-        # build options dictionary from environment variables starting with __SC_
+        # build options dictionary from environment variables starting with the prefix
         options = {}
-        for key in filter(lambda x: x.startswith('__SC_'), os.environ):
+        for key in [v for v in os.environ if v.startswith('__SC_') or v.startswith(prefix + '_')]:
             try:
                 val = ast.literal_eval(os.environ[key])
             except:
                 val = os.environ[key]
-            options[key.replace('__SC_', '').lower()] = val
+            options[key.replace('__SC_', '').replace(prefix + '_', '').lower()] = val
 
         # update config with the values we've found
         self.update(options=options)
@@ -264,10 +278,11 @@ class ConfigFile(Config, File):
     """
     Config based on a loaded YAML or JSON file.
     """
-    def __init__(self, path=None, defaults=None, load=False, apply_env=False, *args, **kwargs):
+    def __init__(self, path=None, defaults=None, load=False, apply_env=False, env_prefix='SCRUFFY', *args, **kwargs):
         self._loaded = False
         self._defaults_file = defaults
         self._apply_env = apply_env
+        self._env_prefix = env_prefix
         Config.__init__(self)
         File.__init__(self, path=path, *args, **kwargs)
 
@@ -280,7 +295,7 @@ class ConfigFile(Config, File):
         """
         if reload or not self._loaded:
             # load defaults
-            if self._defaults_file and type(self._defaults_file) == str:
+            if self._defaults_file and isinstance(self._defaults_file, string_types):
                 self._defaults_file = File(self._defaults_file, parent=self._parent)
             defaults = {}
             if self._defaults_file:
@@ -298,7 +313,7 @@ class ConfigFile(Config, File):
 
             # if specified, apply environment variables
             if self._apply_env:
-                self.update(ConfigEnv())
+                self.update(ConfigEnv(self._env_prefix))
 
             self._loaded = True
 
@@ -308,7 +323,7 @@ class ConfigFile(Config, File):
         """
         Save the config back to the config file.
         """
-        self.write(yaml.safe_dump(self._data))
+        self.write(yaml.safe_dump(self._data, default_flow_style=False))
 
     def prepare(self):
         """
@@ -328,7 +343,7 @@ class ConfigApplicator(object):
         """
         Apply the config to an object.
         """
-        if type(obj) == str:
+        if isinstance(obj, string_types):
             return self.apply_to_str(obj)
 
     def apply_to_str(self, obj):
@@ -370,20 +385,23 @@ def update_dict(target, source):
     dictionary.
 
     For example:
-    target before = {
-        'thing': 123,
-        'thang': {
-            'a': 1,
-            'b': 2
-        }
-    }
-    source = {
-        'thang': {
-            'a': 666,
-            'c': 777
-        }
-    }
-    target after = {
+
+    >>> target = {
+    ...     'thing': 123,
+    ...     'thang': {
+    ...         'a': 1,
+    ...         'b': 2
+    ...     }
+    ... }
+    >>> source = {
+    ...     'thang': {
+    ...         'a': 666,
+    ...         'c': 777
+    ...     }
+    ... }
+    >>> update_dict(target, source)
+    >>> target
+    {
         'thing': 123,
         'thang': {
             'a': 666,
diff --git a/scruffy/env.py b/scruffy/env.py
index 826493b44e233c011714ef1f834741307797d43a..ae086930f474fbeed9eb634eb920580de3fa9758 100644
--- a/scruffy/env.py
+++ b/scruffy/env.py
@@ -1,3 +1,10 @@
+"""
+Environment
+-----------
+
+Classes for representing the encompassing environment in which your application
+runs.
+"""
 import os
 import yaml
 import itertools
@@ -5,9 +12,11 @@ import errno
 import logging
 import logging.config
 
+from six import string_types
+
 from .file import Directory
 from .plugin import PluginManager
-from .config import ConfigNode, Config, ConfigEnv, ConfigApplicator
+from .config import ConfigNode, Config, ConfigEnv, ConfigApplicator, ConfigFile
 
 
 class Environment(object):
@@ -61,7 +70,7 @@ class Environment(object):
 
         # first see if we got a kwarg named 'config', as this guy is special
         if 'config' in children:
-            if type(children['config']) == str:
+            if isinstance(children['config'], string_types):
                 children['config'] = ConfigFile(children['config'])
             elif isinstance(children['config'], Config):
                 children['config'] = children['config']
@@ -96,7 +105,7 @@ class Environment(object):
         Add objects to the environment.
         """
         for key in kwargs:
-            if type(kwargs[key]) == str:
+            if isinstance(kwargs[key], string_types):
                 self._children[key] = Directory(kwargs[key])
             else:
                 self._children[key] = kwargs[key]
diff --git a/scruffy/file.py b/scruffy/file.py
index cd913f7fc1dd85a1a308879ca5d825330a5012a1..2db91e1db76b8cbb2c44127b6a67810b5aca3b04 100644
--- a/scruffy/file.py
+++ b/scruffy/file.py
@@ -1,4 +1,12 @@
+"""
+File
+----
+
+Classes for representing and performing operations on files and directories.
+"""
+from __future__ import unicode_literals
 import os
+from six import string_types
 import yaml
 import copy
 import logging
@@ -38,9 +46,9 @@ class File(object):
 
     def apply_config(self, applicator):
         """
-        Replace any config tokens with values from the config.
+        Replace any config tokens in the file's path with values from the config.
         """
-        if type(self._fpath) == str:
+        if isinstance(self._fpath, string_types):
             self._fpath = applicator.apply(self._fpath)
 
     def create(self):
@@ -51,7 +59,7 @@ class File(object):
 
     def remove(self):
         """
-        Remove the file.
+        Remove the file if it exists.
         """
         if self.exists:
             os.unlink(self.path)
@@ -166,7 +174,7 @@ class LogFile(File):
 
         # if we got a string for the formatter, assume it's the name of a
         # formatter in the environment's config
-        if type(self._formatter) == str:
+        if isinstance(self._format, string_types):
             if self._env and self._env.config.logging.dict_config.formatters[self._formatter]:
                 d = self._env.config.logging.dict_config.formatters[self._formatter].to_dict()
                 handler.setFormatter(logging.Formatter(**d))
@@ -237,14 +245,14 @@ class Directory(object):
     A Scruffy Environment usually encompasses a number of these. For example,
     the main Directory object may represent `~/.myproject`.
 
-    d = Directory({
-        path='~/.myproject',
-        create=True,
-        cleanup=False,
-        children=[
-        ...
-        ]
-    })
+    >>> d = Directory({
+    ...     path='~/.myproject',
+    ...     create=True,
+    ...     cleanup=False,
+    ...     children=[
+    ...     ...
+    ...     ]
+    ... })
 
     `path` can be either a string representing the path to the directory, or
     a nested Directory object. If a Directory object is passed as the `path`
@@ -261,7 +269,7 @@ class Directory(object):
         self._env = None
         self._parent = parent
 
-        if self._path and type(self._path) == str:
+        if self._path and isinstance(self._path, string_types):
             self._path = os.path.expanduser(self._path)
 
         self.add(**kwargs)
@@ -283,7 +291,7 @@ class Directory(object):
         """
         Replace any config tokens with values from the config.
         """
-        if type(self._path) == str:
+        if isinstance(self._path, string_types):
             self._path = applicator.apply(self._path)
 
         for key in self._children:
@@ -390,7 +398,7 @@ class Directory(object):
         Add objects to the directory.
         """
         for key in kwargs:
-            if isinstance(kwargs[key], str):
+            if isinstance(kwargs[key], string_types):
                 self._children[key] = File(kwargs[key])
             else:
                 self._children[key] = kwargs[key]
@@ -403,7 +411,7 @@ class Directory(object):
                 self._children[arg.name] = arg
                 self._children[arg.name]._parent = self
                 self._children[arg.name]._env = self._env
-            elif isinstance(arg, str):
+            elif isinstance(arg, string_types):
                 f = File(arg)
                 added.append(f)
                 self._children[arg] = f
diff --git a/scruffy/plugin.py b/scruffy/plugin.py
index 267b2d21f66e5721a5174d175389aa9c2c525ed3..9c6853b6fd4d2a4eb94e974e54d9f2551ca51b36 100644
--- a/scruffy/plugin.py
+++ b/scruffy/plugin.py
@@ -1,3 +1,9 @@
+"""
+Plugin
+------
+
+Classes for representing and loading plugins.
+"""
 import os
 import imp
 import six
diff --git a/scruffy/state.py b/scruffy/state.py
index f73881a3d64019253403a547024b053bf5ae2d6b..3931d4735f02e646d5d5773a1031c1712ce31cac 100644
--- a/scruffy/state.py
+++ b/scruffy/state.py
@@ -1,6 +1,14 @@
+"""
+State
+-----
+
+Classes for storing a program's state.
+"""
 import os
+import atexit
 import yaml
 
+
 try:
     from sqlalchemy import create_engine, Column, Integer, String
     from sqlalchemy.ext.declarative import declarative_base
@@ -28,6 +36,7 @@ class State(object):
         self.path = path
         self.d = {}
         self.load()
+        atexit.register(self._exit_handler)
 
     def __enter__(self):
         self.load()
@@ -45,6 +54,9 @@ class State(object):
     def __setitem__(self, key, value):
         self.d[key] = value
 
+    def _exit_handler(self):
+        self.save()
+
     def save(self):
         """
         Save the state to a file.
@@ -58,7 +70,11 @@ class State(object):
         """
         if os.path.exists(self.path):
             with open(self.path, 'r') as f:
-                self.d = yaml.safe_load(f.read().replace('\t', ' '*4))
+                d = yaml.safe_load(f.read().replace('\t', ' '*4))
+                # don't clobber self.d if we successfully opened the state file
+                # but it was empty
+                if d:
+                    self.d = d
 
     def cleanup(self):
         """
diff --git a/setup.cfg b/setup.cfg
index 6f08d0e3e7d4475804244ad049d476c45fa48356..adf5ed72aa402bde56b499833d162862dae66dc4 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,5 +4,4 @@ universal = 1
 [egg_info]
 tag_build = 
 tag_date = 0
-tag_svn_revision = 0
 
diff --git a/setup.py b/setup.py
index aef1603b3e36692ee8cd7f3e7ef91802c69f06da..7c63b7ef62cebe4abbd05d11b40f68d689c9a413 100755
--- a/setup.py
+++ b/setup.py
@@ -1,10 +1,15 @@
 from setuptools import setup
 
+with open("README.rst", "r") as fp:
+    long_description = fp.read()
+
 setup(
     name="scruffington",
-    version="0.3.3",
+    version="0.3.8.2",
     author="snare",
     author_email="snare@ho.ax",
+    long_description=long_description,
+    long_description_content_type="text/x-rst",
     description=("The janitor"),
     license="MIT",
     keywords="scruffy",