packages feed

autoexporter 1.1.17 → 1.1.18

raw patch · 3 files changed

+134/−94 lines, 3 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Autoexporter: autoexport :: ExportScope -> String -> FilePath -> FilePath -> IO ()
- Autoexporter: defaultMain :: IO ()
- Autoexporter: findFiles :: ExportScope -> FilePath -> IO [FilePath]
- Autoexporter: findFilesDeep :: FilePath -> IO [FilePath]
- Autoexporter: isHaskellFile :: FilePath -> Bool
- Autoexporter: isModuleName :: String -> Bool
- Autoexporter: mainWithArgs :: [String] -> IO ()
- Autoexporter: makeModuleName :: FilePath -> String
- Autoexporter: makeOutput :: String -> FilePath -> [FilePath] -> String
- Autoexporter: parseModuleName :: String -> Maybe ModuleName
- Autoexporter: renderExport :: String -> String
- Autoexporter: renderImport :: String -> String
- Autoexporter: renderModule :: String -> [String] -> String
- Autoexporter: takeWhileEnd :: (a -> Bool) -> [a] -> [a]
- Autoexporter: unlines' :: [String] -> String
+ Autoexporter: autoexporter :: IO ()
+ Autoexporter: instance GHC.Classes.Eq Autoexporter.Depth
+ Autoexporter: instance GHC.Classes.Eq Autoexporter.InvalidArguments
+ Autoexporter: instance GHC.Classes.Eq Autoexporter.InvalidModuleName
+ Autoexporter: instance GHC.Exception.Type.Exception Autoexporter.InvalidArguments
+ Autoexporter: instance GHC.Exception.Type.Exception Autoexporter.InvalidModuleName
+ Autoexporter: instance GHC.Show.Show Autoexporter.Depth
+ Autoexporter: instance GHC.Show.Show Autoexporter.InvalidArguments
+ Autoexporter: instance GHC.Show.Show Autoexporter.InvalidModuleName

Files

autoexporter.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.2  name: autoexporter-version: 1.1.17+version: 1.1.18  synopsis: Automatically re-export modules. description: Autoexporter automatically re-exports modules.
src/exe/Main.hs view
@@ -1,4 +1,6 @@+module Main ( main ) where+ import qualified Autoexporter  main :: IO ()-main = Autoexporter.defaultMain+main = Autoexporter.autoexporter
src/lib/Autoexporter.hs view
@@ -5,118 +5,156 @@ -- -- 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+module Autoexporter ( autoexporter ) where +import qualified Control.Exception as Exception 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 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 -data ExportScope = ExportScopeShallow-                 | ExportScopeDeep -defaultMain :: IO ()-defaultMain = do-  args <- Environment.getArgs-  mainWithArgs args+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) -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)+  -- 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) -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+  -- 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 -findFiles :: ExportScope -> FilePath -> IO [FilePath]-findFiles exportScope dir =-  case exportScope of-    ExportScopeShallow ->-      fmap (filter isHaskellFile) (Directory.listDirectory dir)-    ExportScopeDeep ->-      findFilesDeep dir+  -- Finally we render the module and write it to the output file.+  let content = renderModule moduleName moduleNames+  writeFile output content -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+-- | 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) -takeWhileEnd :: (a -> Bool) -> [a] -> [a]-takeWhileEnd p xs = reverse (takeWhile p (reverse xs)) -isModuleName :: String -> Bool-isModuleName x = Maybe.isJust (parseModuleName x)+-- | 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) -parseModuleName :: String -> Maybe ModuleName.ModuleName-parseModuleName = Text.simpleParse+instance Exception.Exception InvalidArguments -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 +-- | 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 x = List.isSuffixOf ".hs" x || List.isSuffixOf ".lhs" x+isHaskellFile = flip elem haskellExtensions . FilePath.takeExtensions -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"+-- | These are the extensions that we consider to be Haskell source files.+haskellExtensions :: [String]+haskellExtensions = [".hs", ".lhs"] -renderExport :: String -> String-renderExport x = "  module " ++ x ++ "," -renderImport :: String -> String-renderImport x = "import " ++ x+-- | 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