diff --git a/AUTHORS b/AUTHORS
new file mode 100644
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,1 @@
+fr33domlover <fr33domlover@riseup.net>
diff --git a/COPYING b/COPYING
new file mode 100644
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+    INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+    HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+  i. the right to reproduce, adapt, distribute, perform, display,
+     communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+     likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+     subject to the limitations in paragraph 4(a), below;
+  v. rights protecting the extraction, dissemination, use and reuse of data
+     in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+     European Parliament and of the Council of 11 March 1996 on the legal
+     protection of databases, and under any national implementation
+     thereof, including any amended or successor version of such
+     directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+     world based on applicable law or treaty, and any national
+     implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+    surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+    warranties of any kind concerning the Work, express, implied,
+    statutory or otherwise, including without limitation warranties of
+    title, merchantability, fitness for a particular purpose, non
+    infringement, or the absence of latent or other defects, accuracy, or
+    the present or absence of errors, whether or not discoverable, all to
+    the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+    that may apply to the Work or any use thereof, including without
+    limitation any person's Copyright and Related Rights in the Work.
+    Further, Affirmer disclaims responsibility for obtaining any necessary
+    consents, permissions or other rights required for any use of the
+    Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+    party to this document and has no duty or obligation with respect to
+    this CC0 or use of the Work.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,17 @@
+The changes are recorded by the version control system, Darcs. To see a log
+quickly from the terminal, run:
+
+  $ darcs changes --repo http://darcs.rel4tion.org/repos/settings
+
+There is also a web interface at <http://darcs.rel4tion.org> which, among other
+things, can display the history log.
+
+To see the log in a local clone, first get a copy of the repository if you
+haven't yet:
+
+  $ darcs get http://darcs.rel4tion.org/repos/settings
+
+Then move into the newly created directory and run darcs:
+
+  $ cd settings
+  $ darcs changes
diff --git a/INSTALL b/INSTALL
new file mode 100644
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,13 @@
+Install from Hackage:
+
+    $ cabal install settings
+
+Install from unpacked release tarball or source repo:
+
+    $ cd settings
+    $ cabal install
+
+Just play with it without installing:
+
+    $ cabal build
+    $ cabal repl
diff --git a/NEWS b/NEWS
new file mode 100644
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,23 @@
+This file lists the user-visible interesting changes between releases. For a
+full list of changes to the source, see the ChangeLog.
+
+
+
+settings 0.1.0.0 -- 2015-09-10
+==============================
+
+General, build and documentation changes:
+
+* (This is the first release, so everything is new)
+
+New APIs, features and enhancements:
+
+* (This is the first release, so everything is a new feature)
+
+Bug fixes:
+
+* (This is just the first release)
+
+Dependency changes:
+
+* (This is the first release)
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,14 @@
+See the .cabal file for more info and link to project website the version
+control.
+
+The official download location is Hackage:
+
+<http://hackage.haskell.org/package/settings>
+
+This library is free software, and is committed to software freedom. It is
+released to the public domain using the CC0 Public Domain Dedication. For the
+boring "legal" details see the file 'COPYING'.
+
+See the file 'INSTALL' for hints on installation. The file 'ChangeLog' explains
+how to see the history log of the changes done in the code. 'NEWS' provides a
+friendly overview of the changes for each release.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/settings.cabal b/settings.cabal
new file mode 100644
--- /dev/null
+++ b/settings.cabal
@@ -0,0 +1,51 @@
+name:                settings
+version:             0.1.0.0
+synopsis:            Runtime-editable program settings.
+description:
+  This library aims to be a tool for constructing a settings management UI on
+  top of the relevant part of your program state. The settings tree structure
+  definition is separate from the actual setting values, so your program logic
+  code doesn't need to be changed. It can access the settings as regular
+  Haskell values.
+  .
+  The settings are presented to the user as a tree hierarchy. The idea has some
+  similarity to @git config@ and <http://weechat.org Weechat>'s settings
+  system, but isn't identical.
+  .
+  NOTE: The API is probably not documented well enough. Sorry for that. I'm
+  releasing like this because I want to release packages which depend on this
+  library. Full docs will be written.
+homepage:            http://rel4tion.org/projects/settings/
+bug-reports:         http://rel4tion.org/projects/settings/tickets/
+license:             PublicDomain
+license-file:        COPYING
+author:              fr33domlover
+maintainer:          fr33domlover@riseup.net
+copyright:           ♡ Copying is an act of love. Please copy, reuse and share.
+category:            Data, User Interfaces
+build-type:          Simple
+extra-source-files:  AUTHORS ChangeLog COPYING INSTALL NEWS README.md
+cabal-version:       >=1.10
+
+source-repository head
+  type:                darcs
+  location:            http://darcs.rel4tion.org/repos/settings/
+
+library
+  exposed-modules:     Data.Settings
+                     , Data.Settings.Interface
+                     , Data.Settings.Option
+                     , Data.Settings.Persist
+                     , Data.Settings.Section
+                     , Data.Settings.Route
+                     , Data.Settings.Types
+  other-modules:       Control.Debounce
+  -- other-extensions:    
+  build-depends:       aeson
+                     , aeson-pretty         >=0.7
+                     , base                 >=4.7 && <5
+                     , bytestring
+                     , time-units           >=1
+                     , unordered-containers >=0.2.5
+  hs-source-dirs:      src
+  default-language:    Haskell2010
diff --git a/src/Control/Debounce.hs b/src/Control/Debounce.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Debounce.hs
@@ -0,0 +1,61 @@
+{- This file is part of settings.
+ -
+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.
+ -
+ - ♡ Copying is an act of love. Please copy, reuse and share.
+ -
+ - The author(s) have dedicated all copyright and related and neighboring
+ - rights to this software to the public domain worldwide. This software is
+ - distributed without any warranty.
+ -
+ - You should have received a copy of the CC0 Public Domain Dedication along
+ - with this software. If not, see
+ - <http://creativecommons.org/publicdomain/zero/1.0/>.
+ -}
+
+-- | This is similar to the same module from "auto-update" package, except here
+-- the caller can pass a parameter to the debounced action. Also, the returned
+-- action comes in 2 versions.
+--
+-- The first is non-blocking at the cost of a small chance a parameter isn't
+-- passed and is instead discarded. This can happen if the action is called
+-- from different threads simultanously. One empties the 'MVar', and the other
+-- happens to fill it first, and then the parameter the former thread passed is
+-- discarded. If you run the action from a single thread, there is no problem,
+-- or if missing at a hopefully small chance isn't a problem.
+--
+-- The second is blocking, but only in the small chance described above.
+-- Otherwise it doesn't block in practice.
+--
+-- Also, exceptions aren't handled. This includes async exceptions and any
+-- exceptions thrown by the given action.
+module Control.Debounce
+    ( mkDebounce
+    )
+where
+
+import Control.Monad (forever, void)
+import Control.Concurrent (forkIO, threadDelay)
+import Control.Concurrent.MVar
+import Data.Time.Units
+
+mkDebounce :: TimeUnit t
+           => t               -- ^ Time delay between calls to the action
+           -> (a -> IO ())    -- ^ Action to perform
+           -> IO ( a -> IO () --   Never-blocking version
+                 , a -> IO () --   Possibly-blocking version
+                 )
+mkDebounce interval action = do
+    paramVar <- newEmptyMVar
+    let run = forkIO $ forever $ do
+            param <- takeMVar paramVar
+            action param
+            threadDelay $ fromInteger $ toMicroseconds interval
+        actNB param = do
+            tryTakeMVar paramVar
+            void $ tryPutMVar paramVar param
+        actPB param = do
+            tryTakeMVar paramVar
+            putMVar paramVar param
+    run
+    return (actNB, actPB)
diff --git a/src/Data/Settings.hs b/src/Data/Settings.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Settings.hs
@@ -0,0 +1,19 @@
+{- This file is part of settings.
+ -
+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.
+ -
+ - ♡ Copying is an act of love. Please copy, reuse and share.
+ -
+ - The author(s) have dedicated all copyright and related and neighboring
+ - rights to this software to the public domain worldwide. This software is
+ - distributed without any warranty.
+ -
+ - You should have received a copy of the CC0 Public Domain Dedication along
+ - with this software. If not, see
+ - <http://creativecommons.org/publicdomain/zero/1.0/>.
+ -}
+
+module Data.Settings
+    (
+    )
+where
diff --git a/src/Data/Settings/Interface.hs b/src/Data/Settings/Interface.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Settings/Interface.hs
@@ -0,0 +1,143 @@
+{- This file is part of settings.
+ -
+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.
+ -
+ - ♡ Copying is an act of love. Please copy, reuse and share.
+ -
+ - The author(s) have dedicated all copyright and related and neighboring
+ - rights to this software to the public domain worldwide. This software is
+ - distributed without any warranty.
+ -
+ - You should have received a copy of the CC0 Public Domain Dedication along
+ - with this software. If not, see
+ - <http://creativecommons.org/publicdomain/zero/1.0/>.
+ -}
+
+module Data.Settings.Interface
+    ( -- * By Route
+      queryR
+    , querySectionR
+    , queryOptionR
+    , updateOptionR
+    , resetOptionR
+      -- * By Path
+    , query
+    , querySection
+    , queryOption
+    , updateOption
+    , resetOption
+    )
+where
+
+import Control.Monad (liftM)
+import Data.HashMap.Lazy (keys)
+import Data.Settings.Route
+import Data.Settings.Section
+import Data.Settings.Types
+import Prelude hiding (lookup)
+
+-------------------------------------------------------------------------------
+-- By Route
+-------------------------------------------------------------------------------
+
+-- | TODO
+queryR :: MonadSettings m s
+       => OptRoute
+       -> m (Either SettingsError (Either ([SecName], [OptName]) String))
+queryR route = do
+    t <- getSTree
+    case lookup route t of
+        Just (Left sec)  ->
+            return $ Right $ Left (keys $ secSubs sec, keys $ secOpts sec)
+        Just (Right opt) -> liftM (Right . Right) $ optGet opt
+        Nothing          -> return . Left $ NoSuchNode route
+
+-- | TODO
+querySectionR :: MonadSettings m s
+              => OptRoute
+              -> m (Either SettingsError ([SecName], [OptName]))
+querySectionR route = do
+    result <- queryR route
+    return $ case result of
+        Left err              -> Left err
+        Right (Left secsOpts) -> Right secsOpts
+        Right (Right _val)    -> Left $ NoSuchSection route
+
+-- | TODO
+queryOptionR :: MonadSettings m s
+             => OptRoute
+             -> m (Either SettingsError String)
+queryOptionR route = do
+    result <- queryR route
+    return $ case result of
+        Left err               -> Left err
+        Right (Left _secsOpts) -> Left $ NoSuchOption route
+        Right (Right val)      -> Right val
+
+-- | TODO
+updateOptionR :: MonadSettings m s
+              => OptRoute
+              -> String
+              -> m (Maybe SettingsError)
+updateOptionR route val = do
+    t <- getSTree
+    case lookupOpt route t of
+        Just opt -> optSet opt val
+        Nothing  -> return $ Just $ NoSuchOption route
+
+-- | TODO
+resetOptionR :: MonadSettings m s => OptRoute -> m (Maybe SettingsError)
+resetOptionR route = do
+    t <- getSTree
+    case lookupOpt route t of
+        Just opt -> optReset opt >> return Nothing
+        Nothing  -> return $ Just $ NoSuchOption route
+
+-------------------------------------------------------------------------------
+-- By Path
+-------------------------------------------------------------------------------
+
+byPathMaybe :: Monad m
+            => (OptRoute -> m (Maybe SettingsError))
+            -> OptPath
+            -> m (Maybe SettingsError)
+byPathMaybe f path =
+    case parseRoute path of
+        Just route -> f route
+        Nothing    -> return $ Just $ InvalidPath path
+
+byPathEither :: Monad m
+             => (OptRoute -> m (Either SettingsError a))
+             -> OptPath
+             -> m (Either SettingsError a)
+byPathEither f path =
+    case parseRoute path of
+        Just route -> f route
+        Nothing    -> return $ Left $ InvalidPath path
+
+-- | TODO
+query :: MonadSettings m s
+      => OptPath
+      -> m (Either SettingsError (Either ([SecName], [OptName]) String))
+query = byPathEither queryR
+
+-- | TODO
+querySection :: MonadSettings m s
+             => OptPath
+             -> m (Either SettingsError ([SecName], [OptName]))
+querySection = byPathEither querySectionR
+
+-- | TODO
+queryOption :: MonadSettings m s => OptPath -> m (Either SettingsError String)
+queryOption = byPathEither queryOptionR
+
+-- | TODO
+updateOption :: MonadSettings m s
+             => OptPath
+             -> String
+             -> m (Maybe SettingsError)
+updateOption path val = byPathMaybe (flip updateOptionR val) path
+
+-- | TODO
+resetOption :: MonadSettings m s => OptPath -> m (Maybe SettingsError)
+resetOption = byPathMaybe resetOptionR
diff --git a/src/Data/Settings/Option.hs b/src/Data/Settings/Option.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Settings/Option.hs
@@ -0,0 +1,61 @@
+{- This file is part of settings.
+ -
+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.
+ -
+ - ♡ Copying is an act of love. Please copy, reuse and share.
+ -
+ - The author(s) have dedicated all copyright and related and neighboring
+ - rights to this software to the public domain worldwide. This software is
+ - distributed without any warranty.
+ -
+ - You should have received a copy of the CC0 Public Domain Dedication along
+ - with this software. If not, see
+ - <http://creativecommons.org/publicdomain/zero/1.0/>.
+ -}
+
+module Data.Settings.Option
+    ( mkOptionV
+    , mkOptionS
+    )
+where
+
+import Control.Monad (liftM)
+import Data.Bool (bool)
+import Data.Settings.Types
+
+mkOptionV :: (Monad m, OptionValue v)
+          => m v
+          -> (v -> m Bool)
+          -> m ()
+          -> Option m
+mkOptionV get set reset = Option
+    { optGet   = liftM (showOption) get
+    , optSet   = \ s -> case readOption s of
+        Nothing -> return $ Just $ InvalidValueForType s
+        Just v  -> liftM (bool (Just $ InvalidValue s) Nothing) $ set v
+    , optReset = reset
+    }
+
+mkOptionS :: (MonadSettings m s, OptionValue v)
+          => (s -> v)
+          -> (v -> s -> Maybe s)
+          -> (s -> (Maybe v, s))
+          -> (v -> m ()) -- callback
+          -> Option m
+mkOptionS get set reset cb =
+    mkOptionV
+        (liftM get getSettings)
+        (\ v -> do
+            s <- getSettings
+            case set v s of
+                Just s' -> putSettings s' >> cb v >> return True
+                Nothing -> return False
+        )
+        ( do
+            s <- getSettings
+            let (mv, s') = reset s
+            putSettings s'
+            case mv of
+                Just v  -> cb v
+                Nothing -> return ()
+        )
diff --git a/src/Data/Settings/Persist.hs b/src/Data/Settings/Persist.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Settings/Persist.hs
@@ -0,0 +1,57 @@
+{- This file is part of settings.
+ -
+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.
+ -
+ - ♡ Copying is an act of love. Please copy, reuse and share.
+ -
+ - The author(s) have dedicated all copyright and related and neighboring
+ - rights to this software to the public domain worldwide. This software is
+ - distributed without any warranty.
+ -
+ - You should have received a copy of the CC0 Public Domain Dedication along
+ - with this software. If not, see
+ - <http://creativecommons.org/publicdomain/zero/1.0/>.
+ -}
+
+module Data.Settings.Persist
+    ( mkSaveSettings
+    , loadSettings
+    )
+where
+
+import Control.Debounce (mkDebounce)
+import Control.Monad (liftM)
+import qualified Data.ByteString.Lazy as B
+import Data.Aeson (FromJSON, ToJSON, eitherDecode)
+import Data.Aeson.Encode.Pretty (encodePretty)
+import Data.Time.Units (TimeUnit)
+import System.IO.Error (tryIOError)
+
+saveAction :: ToJSON s => FilePath -> s -> IO ()
+saveAction file s = B.writeFile file $ encodePretty s
+
+-- | Prepare a save action which writes settings into a JSON file. This action
+-- defers the work to a separate dedicated thread, and ensures the file isn't
+-- saved more than once within the given time interval.
+--
+-- The action is non-blocking but there is a chance a save is missed if saves
+-- are triggered 
+--
+-- You can call the returned action from your UI thread as a reaction to a
+-- settings change, without worrying about delay or IO load.
+mkSaveSettings :: (TimeUnit t, ToJSON s) => t -> FilePath -> IO (s -> IO ())
+mkSaveSettings interval file =
+    liftM fst $ mkDebounce interval (saveAction file)
+
+-- | Try to load settings from a file.
+--
+-- If an error occurs, 'Left' a pair is returned. The boolean indicates whether
+-- reading the file failed ('False') or parsing the content failed ('True').
+-- The string is an error description.
+--
+-- If the operation succeeds, 'Right' the loaded settings are returned.
+loadSettings :: FromJSON s => FilePath -> IO (Either (Bool, String) s)
+loadSettings file = do
+    errOrStr <- tryIOError (B.readFile file)
+    let pairOrStr = either (Left . (,) False . show) Right errOrStr
+    return $ pairOrStr >>= either (Left . (,) True) Right . eitherDecode
diff --git a/src/Data/Settings/Route.hs b/src/Data/Settings/Route.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Settings/Route.hs
@@ -0,0 +1,41 @@
+{- This file is part of settings.
+ -
+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.
+ -
+ - ♡ Copying is an act of love. Please copy, reuse and share.
+ -
+ - The author(s) have dedicated all copyright and related and neighboring
+ - rights to this software to the public domain worldwide. This software is
+ - distributed without any warranty.
+ -
+ - You should have received a copy of the CC0 Public Domain Dedication along
+ - with this software. If not, see
+ - <http://creativecommons.org/publicdomain/zero/1.0/>.
+ -}
+
+module Data.Settings.Route
+    ( parseRoute
+    , showRoute
+    )
+where
+
+import Data.List (intercalate)
+import Data.Settings.Types
+
+-- | Split a path string into its components, if it's a valid path
+-- syntactically.
+parseRoute :: OptPath -> Maybe OptRoute
+parseRoute "" = Just []
+parseRoute s  =
+    case break (== '.') s of
+        ("", _)     -> Nothing
+        (p, "")     -> Just [p]
+        (p, (c:cs)) ->
+            case parseRoute cs of
+                Nothing -> Nothing
+                Just ps -> Just $ p : ps
+
+-- | Create a string representation of a path, with the parts separated by
+-- periods.
+showRoute :: OptRoute -> OptPath
+showRoute = intercalate "."
diff --git a/src/Data/Settings/Section.hs b/src/Data/Settings/Section.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Settings/Section.hs
@@ -0,0 +1,161 @@
+{- This file is part of settings.
+ -
+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.
+ -
+ - ♡ Copying is an act of love. Please copy, reuse and share.
+ -
+ - The author(s) have dedicated all copyright and related and neighboring
+ - rights to this software to the public domain worldwide. This software is
+ - distributed without any warranty.
+ -
+ - You should have received a copy of the CC0 Public Domain Dedication along
+ - with this software. If not, see
+ - <http://creativecommons.org/publicdomain/zero/1.0/>.
+ -}
+
+-- | This module provides functions work working with the 'Section' type, i.e.
+-- option trees. The style is similar to the APIs for @HashMap@s and @Map@s.
+-- You can use these functions to construct a custom settings tree UI. Before
+-- you do that, try the "Data.Settings.Interface" module, which may already
+-- offer what you need.
+module Data.Settings.Section
+    ( -- * Construction
+      empty
+    , singleton
+      -- * Observation
+    , hasOpts
+    , hasSubs
+    , null
+    , member
+    , memberOpt
+    , memberSub
+    , lookup
+    , lookupOpt
+    , lookupSub
+      -- * Modification
+    , insert
+    , deleteOpt
+    , deleteSub
+    , delete
+    )
+where
+
+import Data.Either (isRight)
+import qualified Data.HashMap.Lazy as M
+import Data.Settings.Types
+import Prelude hiding (lookup, null)
+
+-- | Construct an empty section, no options and no subsections.
+empty :: Section m
+empty = Section M.empty M.empty
+
+-- | Construct a section with a single option.
+singleton :: SecName -> Option m -> Section m
+singleton name opt = Section (M.singleton name opt) M.empty
+
+-- | Return 'True' if this section contains any options, 'False' otherwise.
+hasOpts :: Section m -> Bool
+hasOpts = not . M.null . secOpts
+
+-- | Return 'True' if this section contains any subsections, 'False' otherwise.
+hasSubs :: Section m -> Bool
+hasSubs = not . M.null . secSubs
+
+-- | Return 'True' if this section is empty (no options, no subsections),
+-- 'False' otherwise.
+null :: Section m -> Bool
+null s = not $ hasOpts s || hasSubs s
+
+-- | Return 'True' if an option or a subsection is present at the specified
+-- path, 'False' otherwise.
+member :: OptRoute -> Section m -> (Bool, Bool)
+member route sec =
+    case lookup route sec of
+        Nothing -> (False, False)
+        Just e  -> (True, isRight e)
+
+-- | Return 'True' if an option is present at the specified path, 'False'
+-- otherwise.
+memberOpt :: OptRoute -> Section m -> Bool
+memberOpt route sec =
+    let (m, o) = member route sec
+    in  m && o
+
+-- | Return 'True' if a subsection is present at the specified path, 'False'
+-- otherwise.
+memberSub :: OptRoute -> Section m -> Bool
+memberSub route sec =
+    let (m, o) = member route sec
+    in  m && not o
+
+-- | Return 'Just' the section or option at the specified path, or 'Nothing' if
+-- this section contains no such path.
+lookup :: OptRoute -> Section m -> Maybe (Either (Section m) (Option m))
+lookup []     sec = Just $ Left sec
+lookup [name] sec =
+    case (M.lookup name $ secOpts sec, M.lookup name $ secSubs sec) of
+        (Just o,  _)       -> Just $ Right o
+        (Nothing, Just s)  -> Just $ Left s
+        (Nothing, Nothing) -> Nothing
+lookup (n:ns) sec =
+    case M.lookup n $ secSubs sec of
+        Just s  -> lookup ns s
+        Nothing -> Nothing
+
+-- | Return 'Just' the option at the specified path, or 'Nothing' if this
+-- section doesn't contain an option at this path.
+lookupOpt :: [String] -> Section m -> Maybe (Option m)
+lookupOpt path sec =
+    case lookup path sec of
+        Just (Right o) -> Just o
+        _              -> Nothing
+
+-- | Return 'Just' the section at the specified path, or 'Nothing' if this
+-- section doesn't contain a subsection at this path.
+lookupSub :: [String] -> Section m -> Maybe (Section m)
+lookupSub path sec =
+    case lookup path sec of
+        Just (Left s) -> Just s
+        _             -> Nothing
+
+-- | Add the specified option at the specified path under this section. If the
+-- section previously contained an option for this path, the old value is
+-- replaced.
+insert :: [String] -> Option m -> Section m -> Section m
+insert path opt s@(Section opts subs) =
+    case path of
+        []     -> s
+        [name] -> Section (M.insert name opt opts) subs
+        (n:ns) ->
+                -- Find the section or make a new one
+            let sub   = M.lookupDefault empty n subs
+                -- Insert the option there, applying recursively
+                sub'  = insert ns opt sub
+                -- Put the result back into the subsection map
+                subs' = M.insert n sub' subs
+            in  Section opts subs'
+
+deleteImpl :: Bool -> Bool -> [String] -> Section m -> Section m
+deleteImpl delO delS path sec = d path sec
+    where
+    d path sec@(Section opts subs) =
+        case path of
+            []     -> sec
+            [name] -> Section
+                        (if delO then M.delete name opts else opts)
+                        (if delS then M.delete name subs else subs)
+            (n:ns) -> Section opts $ M.adjust (d ns) n subs
+
+-- | Remove the option at the specified path, if present. If there is a section
+-- under this path, it won't be removed.
+deleteOpt :: [String] -> Section m -> Section m
+deleteOpt = deleteImpl True False
+
+-- | Remove the section at the specified path, if present. If there is an
+-- option under this path, it won't be removed.
+deleteSub :: [String] -> Section m -> Section m
+deleteSub = deleteImpl False True
+
+-- | Remove the option or section at the specified path, if present.
+delete :: [String] -> Section m -> Section m
+delete = deleteImpl True True
diff --git a/src/Data/Settings/Types.hs b/src/Data/Settings/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Settings/Types.hs
@@ -0,0 +1,69 @@
+{- This file is part of settings.
+ -
+ - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.
+ -
+ - ♡ Copying is an act of love. Please copy, reuse and share.
+ -
+ - The author(s) have dedicated all copyright and related and neighboring
+ - rights to this software to the public domain worldwide. This software is
+ - distributed without any warranty.
+ -
+ - You should have received a copy of the CC0 Public Domain Dedication along
+ - with this software. If not, see
+ - <http://creativecommons.org/publicdomain/zero/1.0/>.
+ -}
+
+{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
+
+module Data.Settings.Types
+    ( Option (..)
+    , Section (..)
+    , OptName
+    , SecName
+    , OptPath
+    , OptRoute
+    , SettingsError (..)
+    , OptionValue (..)
+    , MonadSettings (..)
+    )
+where
+
+import qualified Data.HashMap.Lazy as M
+
+data Option m = Option
+    { optGet   :: m String
+    , optSet   :: String -> m (Maybe SettingsError)
+    , optReset :: m ()
+    }
+
+data Section m = Section
+    { secOpts :: M.HashMap String (Option m)
+    , secSubs :: M.HashMap String (Section m)
+    }
+
+type OptName = String
+
+type SecName = String
+
+type OptPath = String
+
+type OptRoute = [String]
+
+data SettingsError
+    = InvalidPath OptPath
+    | NoSuchOption OptRoute
+    | NoSuchSection OptRoute
+    | NoSuchNode OptRoute
+    | InvalidValueForType String -- the value as a string
+    | InvalidValue String        -- error description
+
+class OptionValue v where
+    readOption :: String -> Maybe v
+    showOption :: v -> String
+    typeName   :: v -> String
+
+class Monad m => MonadSettings m s | m -> s where
+    getSettings    :: m s
+    putSettings    :: s -> m ()
+    modifySettings :: (s -> s) -> m ()
+    getSTree       :: m (Section m)
