diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,7 @@
+# Changelog for glob-imports
+
+## Unreleased changes
+
+## 0.1.0.0
+
+- Initial Release
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Matt Parsons (c) 2021
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Matt Parsons nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,90 @@
+# glob-imports
+
+![build status](https://github.com/parsonsmatt/glob-imports/actions/workflows/haskell.yml/badge.svg)
+
+A Haskell source  preprocessor which generalizes `persistent-discover` and
+`hspec-discover`, allowing you to metaprogram with source files.
+
+A big limitation of `TemplateHaskell` is that it cannot modify the import list
+of a file. The only way to metaprogram with the imports is to use a source
+preprocessor like this.
+
+## As an executable
+
+Let's say you've got a ton of `persistent` database models, all defined in a
+module hierarchy like this:
+
+```
+src/
+  Models/
+    Foo.hs
+    Bar.hs
+    Baz.hs
+    Blargh.hs
+    What.hs
+    OhNo.hs
+```
+
+If you're using `persistent` to automatically generate migrations, you'll want
+to have all the `[EntityDef]` in scope from each module. You can do that by
+importing each module that defines models and calling `$(discoverEntities)`,
+introduced in `persistent-2.13`.
+
+But you may forget to import a module.
+
+This utility splices in a qualified import statement for *all* the modules defined in the
+current directory and sub-directories, along with a list of imported modules. From this, you can use
+[`discover-instances`](https://hackage.haskell.org/package/discover-instances)
+or other `TemplateHaskell` code to work with these.
+
+To use it, place the following command in a file, located in the same directory
+that your Haskell modules are in:
+
+```haskell
+-- src/Models/All.hs
+
+{-# OPTIONS_GHC -F -pgmF glob-imports #-}
+
+module Models.All where
+
+import Database.Persist.Sql
+-- GLOB_IMPORTS_SPLICE
+
+allEntities :: [EntityDef]
+allEntities = $(discoverEntities)
+```
+
+The preprocessor will glob files in the current directory, and import all of
+them qualified. The result would look like this:
+
+```haskell
+-- src/Models/All.hs
+
+-- post-processed
+
+module Models.All where
+
+import Database.Persist.Sql
+import qualified Models.Foo
+import qualified Models.Bar
+import qualified Models.Baz
+import qualified Models.Blargh
+import qualified Models.What
+import qualified Models.OhNo
+
+_importedModules :: [String]
+_importedModules =
+    [ "Models.Foo"
+    , "Models.Bar"
+    , "Models.Baz"
+    , "Models.Blargh"
+    , "Models.What"
+    , "Models.OhNo"
+    ]
+
+allEntities :: [EntityDef]
+allEntities = $(discoverEntities)
+```
+
+You'll need to add `glob-imports` to the `build-tool-depends` on your cabal file
+for this to work.
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/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,20 @@
+module Main where
+
+import GlobImports.Exe
+import System.IO (readFile')
+import System.Environment (getArgs)
+
+main :: IO ()
+main = do
+    args <- getArgs
+    case args of
+        src : fileInput : dest : _ -> do
+            contents <- readFile' fileInput
+            spliceImports (Source src) (SourceContents contents) (Destination dest)
+        _ ->
+            fail . mconcat $
+               [ "glob-imports: expected to be called with three arguments as an -F -pgmF preprocessor. got:"
+               , show args
+               ]
+
+
diff --git a/glob-imports.cabal b/glob-imports.cabal
new file mode 100644
--- /dev/null
+++ b/glob-imports.cabal
@@ -0,0 +1,140 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.35.4.
+--
+-- see: https://github.com/sol/hpack
+
+name:           glob-imports
+version:        0.0.1.0
+synopsis:       Import modules for metaprogramming
+description:    This package provides an executable for importing modules in a directory and splicing those in. Please see the README on GitHub at <https://github.com/parsonsmatt/glob-imports#readme>
+category:       Metaprogramming
+homepage:       https://github.com/parsonsmatt/glob-imports#readme
+bug-reports:    https://github.com/parsonsmatt/glob-imports/issues
+author:         Matt Parsons
+maintainer:     parsonsmatt@gmail.com
+copyright:      Matt Parsons
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/parsonsmatt/glob-imports
+
+library
+  exposed-modules:
+      GlobImports.Exe
+  other-modules:
+      Paths_glob_imports
+  hs-source-dirs:
+      ./src
+  default-extensions:
+      BlockArguments
+      DataKinds
+      DerivingStrategies
+      DerivingStrategies
+      FlexibleInstances
+      FlexibleInstances
+      GADTs
+      GeneralizedNewtypeDeriving
+      MultiParamTypeClasses
+      OverloadedStrings
+      StandaloneDeriving
+      TypeFamilies
+      TypeFamilies
+      TypeOperators
+  ghc-options: -Wall
+  build-depends:
+      base >=4.12 && <5
+    , directory
+    , discover-instances
+    , dlist
+    , file-embed
+    , filepath
+    , mtl
+    , some-dict-of
+    , template-haskell >=2.16.0.0
+    , text
+  default-language: Haskell2010
+
+executable glob-imports
+  main-is: Main.hs
+  other-modules:
+      Paths_glob_imports
+  hs-source-dirs:
+      app
+  default-extensions:
+      BlockArguments
+      DataKinds
+      DerivingStrategies
+      DerivingStrategies
+      FlexibleInstances
+      FlexibleInstances
+      GADTs
+      GeneralizedNewtypeDeriving
+      MultiParamTypeClasses
+      OverloadedStrings
+      StandaloneDeriving
+      TypeFamilies
+      TypeFamilies
+      TypeOperators
+  ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.12 && <5
+    , directory
+    , discover-instances
+    , dlist
+    , file-embed
+    , filepath
+    , glob-imports
+    , mtl
+    , some-dict-of
+    , template-haskell >=2.16.0.0
+    , text
+  default-language: Haskell2010
+
+test-suite glob-imports-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      GlobImports.ExeSpec
+      Paths_glob_imports
+  hs-source-dirs:
+      test
+  default-extensions:
+      BlockArguments
+      DataKinds
+      DerivingStrategies
+      DerivingStrategies
+      FlexibleInstances
+      FlexibleInstances
+      GADTs
+      GeneralizedNewtypeDeriving
+      MultiParamTypeClasses
+      OverloadedStrings
+      StandaloneDeriving
+      TypeFamilies
+      TypeFamilies
+      TypeOperators
+  ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
+  build-tool-depends:
+      hspec-discover:hspec-discover
+  build-depends:
+      base >=4.12 && <5
+    , directory
+    , discover-instances
+    , dlist
+    , file-embed
+    , filepath
+    , glob-imports
+    , hspec
+    , hspec-discover
+    , mtl
+    , some-dict-of
+    , template-haskell >=2.16.0.0
+    , text
+  default-language: Haskell2010
diff --git a/src/GlobImports/Exe.hs b/src/GlobImports/Exe.hs
new file mode 100644
--- /dev/null
+++ b/src/GlobImports/Exe.hs
@@ -0,0 +1,312 @@
+-- | This preprocessor splices in imports to the file you define it in. Haskell
+-- source files are discovered according to a glob relative to the file the code
+-- is defined in. This utility is useful when metaprogramming with a group of
+-- related files, where you want to use @TemplateHaskell@ or similar
+--
+-- By default, the glob is for all modules in the directory containing the
+-- source file. Imports are qualified with the full module name to avoid
+-- potential import conflicts. The pre-processor will splice in a top-level
+-- value @_importedModules :: [String]@ which contains the fully qualified
+-- names of the modules that were imported.
+--
+-- You may want to disable warnings for redundant imports, if you are only using
+-- type class information. A future option to the library may only do empty
+-- import lists, to only get access to type class instances.
+--
+-- As an example, consider the
+-- <https://hackage.haskell.org/package/persistent-discover
+-- @persistent-discover@> utility, which is inspired by @hspec-discover@. That
+-- utility will perform the following transformation:
+--
+-- @
+-- -- src/PersistentModels/All.hs
+--
+-- {-# OPTIONS_GHC -F -pgmF persistent-discover #-}
+-- @
+--
+-- Then it will translate to:
+--
+-- @
+-- -- src/PersistentModels/All.hs
+--
+-- module PersistentModels.All where
+--
+-- import PersistentModels.Foo ()
+-- import PersistentModels.Bar ()
+-- import PersistentModels.Baz ()
+--
+-- allEntityDefs :: [EntityDef]
+-- allEntityDefs = $(discoverEntities)
+-- @
+--
+-- With this package, we can generalize the overall pattern. The new source
+-- module will look like this:
+--
+-- @
+-- -- src/PersistentModels/All.hs
+--
+-- {-# OPTIONS_GHC -F -pgmF glob-imports #-}
+--
+-- module PersistentModels.All where
+--
+-- import Database.Persist.Sql
+-- {- GLOB_IMPORTS_SPLICE -}
+--
+-- allEntityDefs :: [EntityDef]
+-- allEntityDefs = $(discoverEntities)
+-- @
+--
+-- This preprocessor will convert this into this form:
+--
+-- @
+-- -- src/PersistentModels/All.hs
+--
+-- module PersistentModels.All where
+--
+-- import Database.Persist.Sql
+-- import qualified PersistentModels.Foo
+-- import qualified PersistentModels.Bar
+-- import qualified PersistentModels.Baz
+--
+-- allEntityDefs :: [EntityDef]
+-- allEntityDefs = $(discoverEntities)
+-- @
+--
+-- Note how the only difference is that imports have been spliced in. This
+-- allows you to more flexibly customize how the code works.
+--
+-- @since 0.1.0.0
+module GlobImports.Exe where
+
+import System.FilePath
+import Control.Monad (guard, filterM)
+import Control.Monad.State
+import Data.String
+import Data.DList (DList(..))
+import qualified Data.DList as DList
+import Data.Foldable (for_)
+import System.Directory
+import Data.List
+import Data.Char
+import Control.Applicative
+import Data.Maybe
+
+-- | The source file location. This is the first argument passed to the
+-- preprocessor.
+newtype Source = Source { unSource :: FilePath }
+
+-- | The source file contents. This is the 'String' contained in the file of the
+-- second argument passed to the preprocessor.
+newtype SourceContents = SourceContents { unSourceContents :: String }
+
+-- | The destination file path to write the final source to. This is the third
+-- argument passed to the preprocessor.
+newtype Destination = Destination { unDestination :: FilePath }
+
+data AllModelsFile = AllModelsFile
+    { amfModuleBase :: Module
+    , amfModuleImports :: [Module]
+    }
+
+-- |
+--
+-- @since 0.1.0.0
+spliceImports
+    :: Source
+    -> SourceContents
+    -> Destination
+    -> IO ()
+spliceImports (Source src) (SourceContents srcContents) (Destination dest) = do
+    let (dir, file) = splitFileName src
+    files <- filter (/= file) <$> getFilesRecursive dir
+    let
+        input =
+            AllModelsFile
+                { amfModuleBase =
+                    fromJust $ pathToModule src
+                , amfModuleImports =
+                    mapMaybe pathToModule (fmap (dir </>) files)
+                }
+        output =
+            renderFile input srcContents
+
+    writeFile dest output
+
+-- | Returns a list of relative paths to all files in the given directory.
+getFilesRecursive
+    :: FilePath
+    -- ^ The directory to search.
+    -> IO [FilePath]
+getFilesRecursive baseDir = sort <$> go []
+  where
+    go :: FilePath -> IO [FilePath]
+    go dir = do
+      c <- map (dir </>) . filter (`notElem` [".", ".."]) <$> getDirectoryContents (baseDir </> dir)
+      dirs <- filterM (doesDirectoryExist . (baseDir </>)) c >>= mapM go
+      files <- filterM (doesFileExist . (baseDir </>)) c
+      return (files ++ concat dirs)
+
+renderFile
+    :: AllModelsFile
+    -> String
+    -> String
+renderFile amf originalContents =
+    concatMap unlines
+        [ modulePrior
+        , newImportLines
+        , newModuleRest
+        ]
+  where
+    originalLines =
+        lines originalContents
+
+    (modulePrior, moduleRest) =
+        case break ("GLOB_IMPORTS_SPLICE" `isInfixOf`) originalLines of
+            (_, []) ->
+                error $ unlines
+                    [ "While processing the module, I was unable to find a comment with GLOB_IMPORTS_SPLICE."
+                    , "I need this to know where to splice imports into the file. Please add a comment like "
+                    , "this to the source file in the import section: "
+                    , ""
+                    , "-- GLOB_IMPORTS_SPLICE"
+                    ]
+            (prior, (_globImportLine : rest)) ->
+                (prior, rest)
+
+    newModuleRest =
+        let
+            (remainingModule, lastImportLine) =
+                break ("import" `isPrefixOf`) (reverse moduleRest)
+            quoteModuleName mod' =
+                "\"" <> moduleName mod' <> "\""
+            mkFirstModuleLine mod' =
+                "  [ " <> quoteModuleName mod'
+            mkRestModuleLine mod' =
+                "  , " <> quoteModuleName mod'
+            newLines =
+                reverse case amfModuleImports amf of
+                    [] ->
+                        []
+                    (firstModule : restModules) ->
+                        [ "_importedModules :: [String]"
+                        , "_importedModules ="
+                        , mkFirstModuleLine firstModule
+                        ] <> map mkRestModuleLine restModules
+                          <> [ "  ]"]
+         in
+            reverse (concat [remainingModule, newLines, lastImportLine])
+
+    newImportLines =
+        map (\mod' -> "import qualified " <> moduleName mod') (amfModuleImports amf)
+
+
+
+
+
+
+
+--     render do
+--     let
+--         modName =
+--             moduleName $ amfModuleBase amf
+--     renderLine do
+--         "{-# LINE 1 "
+--         fromString $ show modName
+--         " #-}"
+--     "{-# LANGUAGE TemplateHaskell #-}"
+--     ""
+--     renderLine do
+--         "module "
+--         fromString $ modName
+--         " where"
+--     ""
+--     for_ (amfModuleImports amf) \mod' ->
+--         renderLine do
+--             "import "
+--             fromString $ moduleName mod'
+--             " ()"
+--     ""
+--     "import Database.Persist.TH (discoverEntities)"
+--     "import Database.Persist.Types (EntityDef)"
+--     ""
+--     "-- | All of the entity definitions, as discovered by the @glob-imports@ utility."
+--     "allEntityDefs :: [EntityDef]"
+--     "allEntityDefs = $(discoverEntities)"
+--
+-- -- -- | Derive module name from specified path.
+-- -- pathToModule :: FilePath -> Module
+-- -- pathToModule f =
+-- --     Module
+-- --         { moduleName =
+-- --             intercalate "." $ mapMaybe go $ splitDirectories f
+-- --         , modulePath =
+-- --             f
+-- --         }
+-- --   where
+-- --     go :: String -> Maybe String
+-- --     go (c:cs) =
+-- --         Just (toUpper c : cs)
+-- --     fileName = last $ splitDirectories f
+-- --     m:ms = takeWhile (/='.') fileName
+
+-- |
+data Module = Module
+    { moduleName :: String
+    , modulePath :: FilePath
+    }
+    deriving (Eq, Show)
+
+mkModulePieces
+    :: FilePath
+    -> [String]
+mkModulePieces fp = do
+    let
+        extension =
+            takeExtension fp
+    guard (extension == ".hs" || extension == ".lhs")
+    reverse
+        . takeWhile (not . isLowerFirst)
+        . reverse
+        . filter noDots
+        . splitDirectories
+        . dropExtension
+        $ fp
+  where
+    noDots x =
+        "." /= x && ".." /= x
+
+isLowerFirst :: String -> Bool
+isLowerFirst [] = True
+isLowerFirst (c:_) = isLower c
+
+pathToModule
+    :: FilePath
+    -> Maybe Module
+pathToModule file = do
+    case mkModulePieces file of
+        [] ->
+            empty
+        x : xs ->  do
+            guard $ all isValidModuleName (x : xs)
+            pure Module
+                { moduleName = intercalate "." (x:xs)
+                , modulePath = file
+                }
+
+-- | Returns True if the given string is a valid task module name.
+-- See `Cabal.Distribution.ModuleName` (http://git.io/bj34)
+isValidModuleName :: String -> Bool
+isValidModuleName [] = False
+isValidModuleName (c:cs) = isUpper c && all isValidModuleChar cs
+
+-- | Returns True if the given Char is a valid taks module character.
+isValidModuleChar :: Char -> Bool
+isValidModuleChar c = isAlphaNum c || c == '_' || c == '\''
+
+-- | Convert a String in camel case to snake case.
+casify :: String -> String
+casify str = intercalate "_" $ groupBy (\a b -> isUpper a && isLower b) str
+
+stripSuffix :: Eq a => [a] -> [a] -> Maybe [a]
+stripSuffix suffix str =
+    reverse <$> stripPrefix (reverse suffix) (reverse str)
diff --git a/test/GlobImports/ExeSpec.hs b/test/GlobImports/ExeSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/GlobImports/ExeSpec.hs
@@ -0,0 +1,122 @@
+module GlobImports.ExeSpec where
+
+import Test.Hspec
+import GlobImports.Exe
+import System.FilePath
+
+spec :: Spec
+spec = do
+    describe "renderFile" do
+        let allModelsFile =
+                AllModelsFile
+                    { amfModuleBase =
+                        Module
+                            { moduleName = "Foo.Bar"
+                            , modulePath = "src/Foo/Bar.hs"
+                            }
+                    , amfModuleImports =
+                        [ Module
+                            { moduleName = "Foo.Bar.Quux"
+                            , modulePath = "src/Foo/Bar/Quux.hs"
+                            }
+                        ]
+                    }
+        it "errors without a GLOB_IMPORTS_SPLICE marker" do
+            (pure $! length (renderFile allModelsFile (unlines
+                [ "module Foo.Bar where"
+                , ""
+                , "import Blah"
+                ])))
+                `shouldThrow` anyErrorCall
+        it "works with a GLOB_IMPORTS_SPLICE marker" do
+            renderFile allModelsFile
+                (unlines
+                    [ "module Foo.Bar where"
+                    , ""
+                    , "import Blah"
+                    , "-- GLOB_IMPORTS_SPLICE"
+                    ])
+                `shouldBe`
+                    unlines
+                        [ "module Foo.Bar where"
+                        , ""
+                        , "import Blah"
+                        , "import qualified Foo.Bar.Quux"
+                        , "_importedModules :: [String]"
+                        , "_importedModules ="
+                        , "  [ \"Foo.Bar.Quux\""
+                        , "  ]"
+                        ]
+        it "can handle imports after glob import splice" do
+            renderFile allModelsFile
+                (unlines
+                    [ "module Foo.Bar where"
+                    , ""
+                    , "import Blah"
+                    , "-- GLOB_IMPORTS_SPLICE"
+                    , "import Boo"
+                    , ""
+                    , "baz :: Int"
+                    , "baz = 3"
+                    ])
+                `shouldBe`
+                    unlines
+                        [ "module Foo.Bar where"
+                        , ""
+                        , "import Blah"
+                        , "import qualified Foo.Bar.Quux"
+                        , "import Boo"
+                        , "_importedModules :: [String]"
+                        , "_importedModules ="
+                        , "  [ \"Foo.Bar.Quux\""
+                        , "  ]"
+                        , ""
+                        , "baz :: Int"
+                        , "baz = 3"
+                        ]
+
+    describe "mkModulePieces" do
+        let
+            exPath =
+                "src/Foo/Bar.hs"
+        it "works" do
+            mkModulePieces exPath
+                `shouldBe`
+                    ["Foo", "Bar"]
+
+        it "does not eat non-hs files" do
+            let
+                mdFile =
+                    "src/Foo/README.md"
+            mkModulePieces mdFile
+                `shouldBe`
+                    []
+
+    describe "pathToModule" do
+        let
+            exPath =
+                "./src/Foo/Bar.hs"
+            (dir, file) =
+                splitFileName exPath
+        it "splits how i expect" $ do
+            dir `shouldBe` "./src/Foo/"
+            file `shouldBe` "Bar.hs"
+        it "works" do
+            pathToModule exPath
+                `shouldBe`
+                    Just Module
+                        { moduleName =
+                            "Foo.Bar"
+                        , modulePath =
+                            exPath
+                        }
+        it "pathToModule absolute path" do
+            let absPath = "/Users/user/Code/project/src/Foo/Bar.hs"
+            pathToModule absPath
+                `shouldBe`
+                    Just Module
+                        { moduleName =
+                            "Foo.Bar"
+                        , modulePath =
+                            absPath
+                        }
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+
