autoexporter 1.1.20 → 2.0.0.1
raw patch · 6 files changed
+207/−178 lines, 6 filesdep ~Cabaldep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: Cabal, base
API changes (from Hackage documentation)
Files
- LICENSE.markdown +1/−1
- autoexporter.cabal +38/−11
- source/executable/Main.hs +8/−0
- source/library/Autoexporter.hs +160/−0
- src/exe/Main.hs +0/−6
- src/lib/Autoexporter.hs +0/−160
LICENSE.markdown view
@@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Taylor Fausak+Copyright (c) 2022 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
autoexporter.cabal view
@@ -1,7 +1,7 @@-cabal-version: >= 1.10+cabal-version: 2.2 name: autoexporter-version: 1.1.20+version: 2.0.0.1 synopsis: Automatically re-export modules. description: Autoexporter automatically re-exports modules.@@ -17,10 +17,15 @@ location: https://github.com/tfausak/autoexporter type: git -library+flag pedantic+ default: False+ description: Enables @-Werror@, which turns warnings into errors.+ manual: True++common library build-depends:- base >= 4.13.0 && < 4.16- , Cabal >= 3.0.1 && < 3.5+ , base >= 4.13.0 && < 4.17+ , Cabal >= 3.0.1 && < 3.7 , directory >= 1.3.6 && < 1.4 , filepath >= 1.4.2 && < 1.5 default-language: Haskell2010@@ -28,18 +33,40 @@ -Weverything -Wno-implicit-prelude -Wno-missing-deriving-strategies+ -Wno-missing-exported-signatures -Wno-unsafe- exposed-modules: Autoexporter- hs-source-dirs: src/lib+ -Wno-all-missed-specialisations+ -Wno-missed-specialisations + if flag(pedantic)+ ghc-options: -Werror+ if impl(ghc >= 8.10) ghc-options: -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module + if impl(ghc >= 9.2)+ ghc-options:+ -Wno-missing-kind-signatures++common executable+ import: library++ build-depends: autoexporter+ ghc-options:+ -rtsopts+ -threaded+ -Wno-unused-packages++library+ import: library++ exposed-modules: Autoexporter+ hs-source-dirs: source/library+ executable autoexporter- build-depends: base, autoexporter- default-language: Haskell2010- ghc-options: -rtsopts -threaded- hs-source-dirs: src/exe+ import: executable++ hs-source-dirs: source/executable main-is: Main.hs
+ source/executable/Main.hs view
@@ -0,0 +1,8 @@+module Main+ ( main+ ) where++import qualified Autoexporter++main :: IO ()+main = Autoexporter.autoexporter
+ source/library/Autoexporter.hs view
@@ -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 . 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
− src/exe/Main.hs
@@ -1,6 +0,0 @@-module Main ( main ) where--import qualified Autoexporter--main :: IO ()-main = Autoexporter.autoexporter
− src/lib/Autoexporter.hs
@@ -1,160 +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 ( 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