summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.markdown7
-rw-r--r--LICENSE.markdown2
-rw-r--r--README.markdown13
-rw-r--r--Setup.hs4
-rw-r--r--autoexporter.cabal79
-rw-r--r--executables/Main.hs4
-rw-r--r--library/Autoexporter.hs122
-rw-r--r--package.yaml35
-rw-r--r--src/exe/Main.hs6
-rw-r--r--src/lib/Autoexporter.hs160
-rw-r--r--stack.yaml1
11 files changed, 204 insertions, 229 deletions
diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown
deleted file mode 100644
index 85cd3aa..0000000
--- a/CHANGELOG.markdown
+++ /dev/null
@@ -1,7 +0,0 @@
-# Change log
-
-Autoexporter uses [Semantic Versioning][].
-The change log is available through the [releases on GitHub][].
-
-[Semantic Versioning]: http://semver.org/spec/v2.0.0.html
-[releases on GitHub]: https://github.com/tfausak/autoexporter/releases
diff --git a/LICENSE.markdown b/LICENSE.markdown
index 3e8956e..f470793 100644
--- a/LICENSE.markdown
+++ b/LICENSE.markdown
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020 Taylor Fausak
+Copyright (c) 2021 Taylor Fausak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.markdown b/README.markdown
index f794ee5..c040c29 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,7 +1,8 @@
-# [Autoexporter][]
+# Autoexporter
-[![Version badge][]][version]
-[![Build badge][]][build]
+[![CI](https://github.com/tfausak/autoexporter/workflows/CI/badge.svg)](https://github.com/tfausak/autoexporter/actions)
+[![Hackage](https://img.shields.io/hackage/v/autoexporter)](https://hackage.haskell.org/package/autoexporter)
+[![Stackage](https://www.stackage.org/package/autoexporter/badge/nightly?label=stackage)](https://www.stackage.org/package/autoexporter)
Autoexporter automatically re-exports Haskell modules.
@@ -63,9 +64,3 @@ things to look out for:
```haskell
{-# OPTIONS_GHC -F -pgmF autoexporter -optF --deep #-}
```
-
-[Autoexporter]: https://github.com/tfausak/autoexporter
-[Version badge]: https://www.stackage.org/package/autoexporter/badge/nightly?label=version
-[version]: https://www.stackage.org/nightly/package/autoexporter
-[Build badge]: https://travis-ci.org/tfausak/autoexporter.svg?branch=master
-[build]: https://travis-ci.org/tfausak/autoexporter
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
index 4bebcfd..0000000
--- a/Setup.hs
+++ /dev/null
@@ -1,4 +0,0 @@
-import qualified Distribution.Simple as Cabal
-
-main :: IO ()
-main = Cabal.defaultMain
diff --git a/autoexporter.cabal b/autoexporter.cabal
index 9b4c7ab..d242770 100644
--- a/autoexporter.cabal
+++ b/autoexporter.cabal
@@ -1,58 +1,45 @@
-cabal-version: 1.12
+cabal-version: >= 1.10
--- This file has been generated from package.yaml by hpack version 0.33.0.
---
--- see: https://github.com/sol/hpack
---
--- hash: 984d7105fe5e1e9e1d0a652367e133918b821d074ea7d914f171334af3c402b4
+name: autoexporter
+version: 1.1.20
-name: autoexporter
-version: 1.1.16
-synopsis: Automatically re-export modules.
-description: Autoexporter automatically re-exports modules.
-category: Utility
-homepage: https://github.com/tfausak/autoexporter#readme
-bug-reports: https://github.com/tfausak/autoexporter/issues
-maintainer: Taylor Fausak
-license: MIT
-license-file: LICENSE.markdown
-build-type: Simple
-extra-source-files:
- CHANGELOG.markdown
- package.yaml
- README.markdown
- stack.yaml
+synopsis: Automatically re-export modules.
+description: Autoexporter automatically re-exports modules.
+
+build-type: Simple
+category: Utility
+extra-source-files: README.markdown
+license-file: LICENSE.markdown
+license: MIT
+maintainer: Taylor Fausak
source-repository head
- type: git
location: https://github.com/tfausak/autoexporter
+ type: git
library
- exposed-modules:
- Autoexporter
- other-modules:
- Paths_autoexporter
- hs-source-dirs:
- library
- ghc-options: -Weverything -Wno-implicit-prelude -Wno-safe -Wno-unsafe
build-depends:
- Cabal >=1.24.0 && <1.25 || >=2.0.1 && <2.5 || >=3.0.0 && <3.3
- , base >=4.9.0 && <4.15
- , directory >=1.2.6 && <1.4
- , filepath >=1.4.1 && <1.5
+ base >= 4.13.0 && < 4.16
+ , Cabal >= 3.0.1 && < 3.5
+ , directory >= 1.3.6 && < 1.4
+ , filepath >= 1.4.2 && < 1.5
default-language: Haskell2010
+ ghc-options:
+ -Weverything
+ -Wno-implicit-prelude
+ -Wno-missing-deriving-strategies
+ -Wno-unsafe
+ exposed-modules: Autoexporter
+ hs-source-dirs: src/lib
+
+ if impl(ghc >= 8.10)
+ ghc-options:
+ -Wno-missing-safe-haskell-mode
+ -Wno-prepositive-qualified-module
executable autoexporter
- main-is: Main.hs
- other-modules:
- Paths_autoexporter
- hs-source-dirs:
- executables
- ghc-options: -Weverything -Wno-implicit-prelude -Wno-safe -Wno-unsafe
- build-depends:
- Cabal >=1.24.0 && <1.25 || >=2.0.1 && <2.5 || >=3.0.0 && <3.3
- , autoexporter
- , base >=4.9.0 && <4.15
- , directory >=1.2.6 && <1.4
- , filepath >=1.4.1 && <1.5
+ build-depends: base, autoexporter
default-language: Haskell2010
+ ghc-options: -rtsopts -threaded
+ hs-source-dirs: src/exe
+ main-is: Main.hs
diff --git a/executables/Main.hs b/executables/Main.hs
deleted file mode 100644
index 70747e9..0000000
--- a/executables/Main.hs
+++ /dev/null
@@ -1,4 +0,0 @@
-import qualified Autoexporter
-
-main :: IO ()
-main = Autoexporter.defaultMain
diff --git a/library/Autoexporter.hs b/library/Autoexporter.hs
deleted file mode 100644
index 2c0c0c8..0000000
--- a/library/Autoexporter.hs
+++ /dev/null
@@ -1,122 +0,0 @@
--- | This package isn't really meant to be used as a library. It's typically
--- used as a GHC preprocessor, like so:
---
--- > {-# OPTIONS_GHC -F -pgmF autoexporter #-}
---
--- For more information, please see the README on GitHub:
--- <https://github.com/tfausak/autoexporter#readme>.
-module Autoexporter
- ( defaultMain
- , mainWithArgs
- , autoexport
- , findFiles
- , findFilesDeep
- , makeModuleName
- , takeWhileEnd
- , isModuleName
- , parseModuleName
- , makeOutput
- , isHaskellFile
- , renderModule
- , unlines'
- , renderExport
- , renderImport
- ) where
-
-import qualified Data.List as List
-import qualified Data.Maybe as Maybe
-import qualified Data.Traversable as Traversable
-import qualified Distribution.ModuleName as ModuleName
-import qualified Distribution.Text as Text
-import qualified System.Directory as Directory
-import qualified System.Environment as Environment
-import qualified System.FilePath as FilePath
-
-data ExportScope = ExportScopeShallow
- | ExportScopeDeep
-
-defaultMain :: IO ()
-defaultMain = do
- args <- Environment.getArgs
- mainWithArgs args
-
-mainWithArgs :: [String] -> IO ()
-mainWithArgs args =
- case args of
- [name, inputFile, outputFile, "--deep"] ->
- autoexport ExportScopeDeep name inputFile outputFile
- [name, inputFile, outputFile] ->
- autoexport ExportScopeShallow name inputFile outputFile
- _ -> fail ("unexpected arguments: " ++ show args)
-
-autoexport :: ExportScope -> String -> FilePath -> FilePath -> IO ()
-autoexport exportScope name inputFile outputFile = do
- let moduleName = makeModuleName name
- let directory = FilePath.dropExtension inputFile
- files <- findFiles exportScope directory
- let output = makeOutput moduleName directory files
- writeFile outputFile output
-
-findFiles :: ExportScope -> FilePath -> IO [FilePath]
-findFiles exportScope dir =
- case exportScope of
- ExportScopeShallow ->
- fmap (filter isHaskellFile) (Directory.listDirectory dir)
- ExportScopeDeep ->
- findFilesDeep dir
-
-findFilesDeep :: FilePath -> IO [FilePath]
-findFilesDeep dir = do
- rootItems <- Directory.listDirectory dir
- childItems <- Traversable.for rootItems $ \item -> do
- let path = dir FilePath.</> item
- dirExists <- Directory.doesDirectoryExist path
- if dirExists
- then fmap (fmap (item FilePath.</>)) (findFilesDeep path)
- else pure []
- pure $ mconcat (filter isHaskellFile rootItems : childItems)
-
-makeModuleName :: FilePath -> String
-makeModuleName name =
- let path = FilePath.dropExtension name
- parts = FilePath.splitDirectories path
- rest = takeWhileEnd isModuleName parts
- in List.intercalate "." rest
-
-takeWhileEnd :: (a -> Bool) -> [a] -> [a]
-takeWhileEnd p xs = reverse (takeWhile p (reverse xs))
-
-isModuleName :: String -> Bool
-isModuleName x = Maybe.isJust (parseModuleName x)
-
-parseModuleName :: String -> Maybe ModuleName.ModuleName
-parseModuleName = Text.simpleParse
-
-makeOutput :: String -> FilePath -> [FilePath] -> String
-makeOutput moduleName directory files =
- let haskellFiles = filter isHaskellFile files
- paths = map (directory FilePath.</>) haskellFiles
- modules = List.sort (map makeModuleName paths)
- in renderModule moduleName modules
-
-isHaskellFile :: FilePath -> Bool
-isHaskellFile x = List.isSuffixOf ".hs" x || List.isSuffixOf ".lhs" x
-
-renderModule :: String -> [String] -> String
-renderModule name modules =
- unlines'
- [ "{-# OPTIONS_GHC -fno-warn-dodgy-exports -fno-warn-unused-imports #-}"
- , unwords ["module", name, "("]
- , unlines' (map renderExport modules)
- , ") where"
- , unlines' (map renderImport modules)
- ]
-
-unlines' :: [String] -> String
-unlines' = List.intercalate "\n"
-
-renderExport :: String -> String
-renderExport x = " module " ++ x ++ ","
-
-renderImport :: String -> String
-renderImport x = "import " ++ x
diff --git a/package.yaml b/package.yaml
deleted file mode 100644
index 31e5c39..0000000
--- a/package.yaml
+++ /dev/null
@@ -1,35 +0,0 @@
-name: autoexporter
-version: 1.1.16
-
-category: Utility
-description: Autoexporter automatically re-exports modules.
-extra-source-files:
- - CHANGELOG.markdown
- - package.yaml
- - README.markdown
- - stack.yaml
-github: tfausak/autoexporter
-license-file: LICENSE.markdown
-license: MIT
-maintainer: Taylor Fausak
-synopsis: Automatically re-export modules.
-
-dependencies:
- base: '>= 4.9.0 && < 4.15'
- Cabal: '>= 1.24.0 && < 1.25 || >= 2.0.1 && < 2.5 || >= 3.0.0 && < 3.3'
- directory: '>= 1.2.6 && < 1.4'
- filepath: '>= 1.4.1 && < 1.5'
-ghc-options:
- - -Weverything
- - -Wno-implicit-prelude
- - -Wno-safe
- - -Wno-unsafe
-
-library:
- source-dirs: library
-
-executable:
- dependencies:
- autoexporter: -any
- main: Main.hs
- source-dirs: executables
diff --git a/src/exe/Main.hs b/src/exe/Main.hs
new file mode 100644
index 0000000..023b03f
--- /dev/null
+++ b/src/exe/Main.hs
@@ -0,0 +1,6 @@
+module Main ( main ) where
+
+import qualified Autoexporter
+
+main :: IO ()
+main = Autoexporter.autoexporter
diff --git a/src/lib/Autoexporter.hs b/src/lib/Autoexporter.hs
new file mode 100644
index 0000000..e4bf0db
--- /dev/null
+++ b/src/lib/Autoexporter.hs
@@ -0,0 +1,160 @@
+-- | This package isn't really meant to be used as a library. It's typically
+-- used as a GHC preprocessor, like so:
+--
+-- > {-# OPTIONS_GHC -F -pgmF autoexporter #-}
+--
+-- For more information, please see the README on GitHub:
+-- <https://github.com/tfausak/autoexporter#readme>.
+module Autoexporter ( autoexporter ) where
+
+import qualified Control.Exception as Exception
+import qualified Data.List as List
+import qualified Data.Maybe as Maybe
+import qualified Distribution.ModuleName as Cabal
+import qualified Distribution.Text as Cabal
+import qualified System.Directory as Directory
+import qualified System.Environment as Environment
+import qualified System.FilePath as FilePath
+
+
+autoexporter :: IO ()
+autoexporter = do
+ -- Start by getting the command line arguments. We expect three positional
+ -- arguments from GHC: the path to the original source file, the path to the
+ -- actual input file, and the path to the output file. The source and input
+ -- files could be different if another preprocessor is involved. Since we
+ -- don't consider the file's contents, we can ignore the input file.
+ --
+ -- After GHC's arguments, we have to check for anything passed in by the user
+ -- with @-optF@.
+ arguments <- Environment.getArgs
+ (input, output, depth) <- case arguments of
+ [input, _, output] -> pure (input, output, DepthShallow)
+ [input, _, output, "--deep"] -> pure (input, output, DepthDeep)
+ _ -> Exception.throwIO (InvalidArguments arguments)
+
+ -- Next we convert the original source file path into a module name. If we
+ -- aren't able to do this then something weird is going on and we should
+ -- crash.
+ moduleName <- case toModuleName input of
+ Just moduleName -> pure moduleName
+ Nothing -> Exception.throwIO (InvalidModuleName input)
+
+ -- Then we want to find all of the relevant modules to re-export. Note that
+ -- we simply ignore non-Haskell files and files that don't form valid module
+ -- names. Also we sort the module names so that the output is deterministic.
+ entries <- listDirectory depth (FilePath.dropExtension input)
+ let moduleNames = getModuleNames entries
+
+ -- Finally we render the module and write it to the output file.
+ let content = renderModule moduleName moduleNames
+ writeFile output content
+
+
+-- | This type describes how to search for modules to export. A shallow search
+-- only considers files in one directory. A deep search considers all files in
+-- the directory tree.
+data Depth
+ = DepthShallow
+ | DepthDeep
+ deriving (Eq, Show)
+
+
+-- | This exception type is thrown when we don't know how to interpret the
+-- arguments passed to the program.
+newtype InvalidArguments
+ = InvalidArguments [String]
+ deriving (Eq, Show)
+
+instance Exception.Exception InvalidArguments
+
+
+-- | This function attempts to convert an arbitrary file path into a valid
+-- Haskell module name. Any extensions are ignored.
+--
+-- >>> toModuleName "invalid/module.name"
+-- Nothing
+-- >>> toModuleName "valid/Module.name"
+-- Just (ModuleName ["Module"])
+-- >>> toModuleName "Qualified/Module.name"
+-- Just (ModuleName ["Qualified","Module"])
+toModuleName :: FilePath -> Maybe Cabal.ModuleName
+toModuleName
+ = Maybe.listToMaybe
+ . Maybe.mapMaybe Cabal.simpleParse
+ . fmap (List.intercalate ".")
+ . List.tails
+ . FilePath.splitDirectories
+ . FilePath.dropExtensions
+
+
+-- | This exception type is thrown when we can't create a valid module name
+-- from the source file path.
+newtype InvalidModuleName
+ = InvalidModuleName FilePath
+ deriving (Eq, Show)
+
+instance Exception.Exception InvalidModuleName
+
+
+-- | Lists all of the entries in the given directory. Note that unlike
+-- 'Directory.listDirectory' the results of calling this function will include
+-- the original directory name.
+listDirectory :: Depth -> FilePath -> IO [FilePath]
+listDirectory depth = case depth of
+ DepthShallow -> listDirectoryShallow
+ DepthDeep -> listDirectoryDeep
+
+
+listDirectoryShallow :: FilePath -> IO [FilePath]
+listDirectoryShallow directory = do
+ entries <- Directory.listDirectory directory
+ pure (fmap (FilePath.combine directory) entries)
+
+
+listDirectoryDeep :: FilePath -> IO [FilePath]
+listDirectoryDeep directory = do
+ entries <- listDirectoryShallow directory
+ let
+ listEntry entry = do
+ isDirectory <- Directory.doesDirectoryExist entry
+ if isDirectory
+ then listDirectoryDeep entry
+ else pure [entry]
+ fmap concat (mapM listEntry entries)
+
+
+-- | Given a list of file paths, returns a sorted list of module names from the
+-- entries that were Haskell files.
+getModuleNames :: [FilePath] -> [Cabal.ModuleName]
+getModuleNames = List.sort . Maybe.mapMaybe toModuleName . filter isHaskellFile
+
+
+-- | This predicate tells you if the given file path is a Haskell source file.
+isHaskellFile :: FilePath -> Bool
+isHaskellFile = flip elem haskellExtensions . FilePath.takeExtensions
+
+
+-- | These are the extensions that we consider to be Haskell source files.
+haskellExtensions :: [String]
+haskellExtensions = [".hs", ".lhs"]
+
+
+-- | Given a module name and a list of module names to re-export, renders a
+-- module with all the appropriate imports and exports.
+renderModule :: Cabal.ModuleName -> [Cabal.ModuleName] -> String
+renderModule moduleName moduleNames = unlines
+ [ "{-# OPTIONS_GHC -fno-warn-dodgy-exports -fno-warn-unused-imports #-}"
+ , "module " <> Cabal.display moduleName <> " ("
+ , List.intercalate "\n" (fmap renderExport moduleNames)
+ , ") where"
+ , List.intercalate "\n" (fmap renderImport moduleNames)
+ ]
+
+
+renderExport :: Cabal.ModuleName -> String
+renderExport moduleName = "module " <> Cabal.display moduleName <> ","
+
+
+renderImport :: Cabal.ModuleName -> String
+renderImport moduleName = "import " <> Cabal.display moduleName
diff --git a/stack.yaml b/stack.yaml
deleted file mode 100644
index 427d534..0000000
--- a/stack.yaml
+++ /dev/null
@@ -1 +0,0 @@
-resolver: lts-15.4