diff options
author | fozworth <> | 2021-02-06 17:50:00 (GMT) |
---|---|---|
committer | hdiff <hdiff@hdiff.luite.com> | 2021-02-06 17:50:00 (GMT) |
commit | c4f247c32c53cf3b6950e82cc5ab3e1ea72c1f90 (patch) | |
tree | d3aa72da4e5c54c53163e66ef12591aad57fba8a | |
parent | 33f64d3353584bace8ec6e437c70f24b19459aea (diff) |
-rw-r--r-- | CHANGELOG.markdown | 7 | ||||
-rw-r--r-- | LICENSE.markdown | 2 | ||||
-rw-r--r-- | README.markdown | 13 | ||||
-rw-r--r-- | Setup.hs | 4 | ||||
-rw-r--r-- | autoexporter.cabal | 79 | ||||
-rw-r--r-- | executables/Main.hs | 4 | ||||
-rw-r--r-- | library/Autoexporter.hs | 122 | ||||
-rw-r--r-- | package.yaml | 35 | ||||
-rw-r--r-- | src/exe/Main.hs | 6 | ||||
-rw-r--r-- | src/lib/Autoexporter.hs | 160 | ||||
-rw-r--r-- | stack.yaml | 1 |
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] +[](https://github.com/tfausak/autoexporter/actions) +[](https://hackage.haskell.org/package/autoexporter) +[](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 |