diff options
-rw-r--r-- | app/Indexer.hs | 376 |
1 files changed, 204 insertions, 172 deletions
diff --git a/app/Indexer.hs b/app/Indexer.hs index 88aa7ad..3b357a3 100644 --- a/app/Indexer.hs +++ b/app/Indexer.hs @@ -4,70 +4,79 @@ module Main where -import Codec.Compression.GZip(compress) -import Control.Exception (SomeException, handle) -import Control.Monad (when) -import Control.Monad.Logger (LogLevel(..), runLoggingT) -import qualified Data.Aeson as A -import qualified Data.ByteString as BS -import qualified Data.ByteString.Lazy as BSL -import qualified Data.ByteString.Lazy.Char8 as BSC -import qualified Data.HashMap.Strict as HM -import Data.Maybe (fromMaybe) -import Data.Semigroup ((<>)) -import qualified Data.Serialize as S -import qualified Data.Text as T -import qualified Data.Text.Encoding as TE -import Data.Time (getZonedTime) -import Data.Version (showVersion) -import HaskellCodeExplorer.PackageInfo (createPackageInfo, ghcVersion) -import qualified HaskellCodeExplorer.Types as HCE -import Network.URI.Encode (encode) -import Options.Applicative - ( Parser - , (<|>) - , execParser - , flag - , fullDesc - , help - , helper - , info - , long - , many - , metavar - , optional - , progDesc - , short - , showDefault - , strOption - , value - ) -import Paths_haskell_code_explorer as HSE (version) -import System.Directory (createDirectoryIfMissing) -import System.Exit (ExitCode(..), exitWith) -import System.FilePath ((</>)) -import System.Log.FastLogger - ( LoggerSet - , ToLogStr(..) - , defaultBufSize - , fromLogStr - , newFileLoggerSet - , newStdoutLoggerSet - , pushLogStrLn - , rmLoggerSet - ) +import Codec.Compression.GZip ( compress ) +import Control.Exception ( SomeException + , handle + ) +import Control.Monad ( when ) +import Control.Monad.Logger ( LogLevel(..) + , runLoggingT + ) +import qualified Data.Aeson as A +import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as BSL +import qualified Data.ByteString.Lazy.Char8 as BSC +import qualified Data.HashMap.Strict as HM +import Data.Maybe ( fromMaybe ) +import Data.Semigroup ( (<>) ) +import qualified Data.Serialize as S +import qualified Data.Text as T +import qualified Data.Text.Encoding as TE +import Data.Time ( getZonedTime ) +import Data.Version ( showVersion ) +import HaskellCodeExplorer.PackageInfo + ( createPackageInfo + , ghcVersion + ) +import qualified HaskellCodeExplorer.Types as HCE +import Network.URI.Encode ( encode ) +import Options.Applicative ( (<|>) + , Parser + , execParser + , flag + , fullDesc + , help + , helper + , info + , long + , many + , metavar + , optional + , progDesc + , short + , showDefault + , strOption + , value + ) +import Paths_haskell_code_explorer as HSE + ( version ) +import System.Directory ( createDirectoryIfMissing ) +import System.Exit ( ExitCode(..) + , exitWith + ) +import System.FilePath ( (</>) ) +import System.Log.FastLogger ( LoggerSet + , ToLogStr(..) + , defaultBufSize + , fromLogStr + , newFileLoggerSet + , newStdoutLoggerSet + , pushLogStrLn + , rmLoggerSet + ) data IndexerConfig = IndexerConfig - { configPackageDirectoryPath :: FilePath + { configPackageDirectoryPath :: FilePath , configPackageDistDirRelativePath :: Maybe FilePath - , configOutputDirectoryName :: Maybe String - , configLog :: !HCE.Log - , configMinLogLevel :: !LogLevel - , configSourceCodePreprocessing :: !HCE.SourceCodePreprocessing - , configCompression :: !Compression - , configGhcOptions :: [String] - , configIgnoreDirectories :: [String] - } deriving (Show, Eq) + , configOutputDirectoryName :: Maybe String + , configLog :: !HCE.Log + , configMinLogLevel :: !LogLevel + , configSourceCodePreprocessing :: !HCE.SourceCodePreprocessing + , configCompression :: !Compression + , configGhcOptions :: [String] + , configIgnoreDirectories :: [String] + } + deriving (Show, Eq) data Compression = Gzip @@ -76,126 +85,149 @@ data Compression versionInfo :: String versionInfo = - "haskell-code-indexer version " ++ - showVersion version ++ ", GHC version " ++ showVersion ghcVersion + "haskell-code-indexer version " + ++ showVersion version + ++ ", GHC version " + ++ showVersion ghcVersion main :: IO () main = do - let description = - "haskell-code-indexer collects and saves information about the source code of a Cabal package. " ++ - versionInfo - config <- - execParser $ - info (helper <*> configParser) (fullDesc <> progDesc description) - loggerSet <- - case configLog config of - HCE.ToFile logfile -> newFileLoggerSet defaultBufSize logfile - HCE.StdOut -> newStdoutLoggerSet defaultBufSize + let + description = + "haskell-code-indexer collects and saves information about the source code of a Cabal package. " + ++ versionInfo + config <- execParser + $ info (helper <*> configParser) (fullDesc <> progDesc description) + loggerSet <- case configLog config of + HCE.ToFile logfile -> newFileLoggerSet defaultBufSize logfile + HCE.StdOut -> newStdoutLoggerSet defaultBufSize let minLogLevel = configMinLogLevel config logger loggerSet minLogLevel LevelInfo versionInfo logger loggerSet minLogLevel LevelDebug $ show config handle - (\(e :: SomeException) -> do - logger loggerSet minLogLevel LevelError (show e) - rmLoggerSet loggerSet - exitWith (ExitFailure 1)) $ do - packageInfo <- - runLoggingT - (createPackageInfo - (configPackageDirectoryPath config) - (configPackageDistDirRelativePath config) - (configSourceCodePreprocessing config) - (configGhcOptions config) - (configIgnoreDirectories config)) - (\_loc _source level msg -> logger loggerSet minLogLevel level msg) - let outputDir = - configPackageDirectoryPath config </> - fromMaybe - HCE.defaultOutputDirectoryName - (configOutputDirectoryName config) - createDirectoryIfMissing False outputDir - logger loggerSet minLogLevel LevelDebug $ "Output directory : " ++ outputDir - BS.writeFile - (outputDir </> HCE.packageInfoBinaryFileName) - (S.encode $ HCE.toCompactPackageInfo packageInfo) - mapM_ - (\(HCE.HaskellModulePath path, modInfo) -> - let (compressFunction, compressExtension) = - case configCompression config of - Gzip -> (compress, ".gz") - NoCompression -> (id, "") - filePath = - outputDir </> - (encode (T.unpack path) ++ ".json" ++ compressExtension) - in BSL.writeFile filePath . compressFunction . A.encode $ modInfo) . - HM.toList $ - HCE.moduleMap (packageInfo :: HCE.PackageInfo HCE.ModuleInfo) - BSL.writeFile - (outputDir </> HCE.packageInfoJsonFileName) - (A.encode packageInfo) - BSL.writeFile (outputDir </> "version.txt") (BSC.pack $ showVersion version) - logger loggerSet minLogLevel LevelInfo ("Finished" :: T.Text) - rmLoggerSet loggerSet + (\(e :: SomeException) -> do + logger loggerSet minLogLevel LevelError (show e) + rmLoggerSet loggerSet + exitWith (ExitFailure 1) + ) + $ do + packageInfo <- runLoggingT + (createPackageInfo (configPackageDirectoryPath config) + (configPackageDistDirRelativePath config) + (configSourceCodePreprocessing config) + (configGhcOptions config) + (configIgnoreDirectories config) + ) + (\_loc _source level msg -> logger loggerSet minLogLevel level msg) + let outputDir = configPackageDirectoryPath config </> fromMaybe + HCE.defaultOutputDirectoryName + (configOutputDirectoryName config) + createDirectoryIfMissing False outputDir + logger loggerSet minLogLevel LevelDebug + $ "Output directory : " + ++ outputDir + BS.writeFile (outputDir </> HCE.packageInfoBinaryFileName) + (S.encode $ HCE.toCompactPackageInfo packageInfo) + mapM_ + (\(HCE.HaskellModulePath path, modInfo) -> + let (compressFunction, compressExtension) = + case configCompression config of + Gzip -> (compress, ".gz") + NoCompression -> (id, "") + filePath = + outputDir + </> ( encode (T.unpack path) + ++ ".json" + ++ compressExtension + ) + in BSL.writeFile filePath . compressFunction . A.encode $ modInfo + ) + . HM.toList + $ HCE.moduleMap (packageInfo :: HCE.PackageInfo HCE.ModuleInfo) + BSL.writeFile (outputDir </> HCE.packageInfoJsonFileName) + (A.encode packageInfo) + BSL.writeFile (outputDir </> "version.txt") + (BSC.pack $ showVersion version) + logger loggerSet minLogLevel LevelInfo ("Finished" :: T.Text) + rmLoggerSet loggerSet configParser :: Parser IndexerConfig configParser = - IndexerConfig <$> - strOption - (long "package" <> short 'p' <> metavar "PATH" <> value "." <> showDefault <> - help "Path to a Cabal package") <*> - optional - (strOption - (long "dist" <> metavar "RELATIVE_PATH" <> - help "Relative path to a dist directory")) <*> - optional - (strOption - (long "output" <> metavar "DIRECTORY_NAME" <> - help "Output directory (default is '.haskell-code-explorer')")) <*> - (pure HCE.StdOut <|> - (HCE.ToFile <$> - strOption - (long "logfile" <> metavar "PATH" <> - help "Path to a log file (by default log is written to stdout)"))) <*> - flag - LevelInfo - LevelDebug - (long "verbose" <> short 'v' <> help "Write debug information to a log") <*> - flag - HCE.AfterPreprocessing - HCE.BeforePreprocessing - (long "before-preprocessing" <> - help - "Index source code before preprocessor pass (by default source code after preprocessing is indexed)") <*> - flag - Gzip - NoCompression - (long "no-compression" <> - help - "Do not compress json files (by default json files are compressed using gzip)") <*> - many - (strOption - (long "ghc" <> metavar "OPTIONS" <> help "Command-line options for GHC")) <*> - many - (strOption - (long "ignore" <> metavar "DIRECTORY_NAME" <> - help "Directories to ignore (e.g. node_modules)")) + IndexerConfig + <$> strOption + ( long "package" + <> short 'p' + <> metavar "PATH" + <> value "." + <> showDefault + <> help "Path to a Cabal package" + ) + <*> optional + (strOption + (long "dist" <> metavar "RELATIVE_PATH" <> help + "Relative path to a dist directory" + ) + ) + <*> optional + (strOption + (long "output" <> metavar "DIRECTORY_NAME" <> help + "Output directory (default is '.haskell-code-explorer')" + ) + ) + <*> ( pure HCE.StdOut + <|> (HCE.ToFile <$> strOption + (long "logfile" <> metavar "PATH" <> help + "Path to a log file (by default log is written to stdout)" + ) + ) + ) + <*> flag + LevelInfo + LevelDebug + (long "verbose" <> short 'v' <> help + "Write debug information to a log" + ) + <*> flag + HCE.AfterPreprocessing + HCE.BeforePreprocessing + ( long "before-preprocessing" + <> help + "Index source code before preprocessor pass (by default source code after preprocessing is indexed)" + ) + <*> flag + Gzip + NoCompression + ( long "no-compression" + <> help + "Do not compress json files (by default json files are compressed using gzip)" + ) + <*> many + (strOption + (long "ghc" <> metavar "OPTIONS" <> help + "Command-line options for GHC" + ) + ) + <*> many + (strOption + (long "ignore" <> metavar "DIRECTORY_NAME" <> help + "Directories to ignore (e.g. node_modules)" + ) + ) logger :: ToLogStr msg => LoggerSet -> LogLevel -> LogLevel -> msg -> IO () -logger loggerSet minLogLevel logLevel msg = - when (logLevel >= minLogLevel) $ do - time <- getZonedTime - let showLogLevel :: LogLevel -> T.Text - showLogLevel LevelDebug = "[debug]" - showLogLevel LevelInfo = "[info]" - showLogLevel LevelWarn = "[warn]" - showLogLevel LevelError = "[error]" - showLogLevel (LevelOther t) = T.concat ["[",t,"]"] - text = - T.concat - [ T.pack $ show time - , " : " - , showLogLevel logLevel - , " " - , TE.decodeUtf8 . fromLogStr . toLogStr $ msg - ] - pushLogStrLn loggerSet $ toLogStr text +logger loggerSet minLogLevel logLevel msg = when (logLevel >= minLogLevel) $ do + time <- getZonedTime + let showLogLevel :: LogLevel -> T.Text + showLogLevel LevelDebug = "[debug]" + showLogLevel LevelInfo = "[info]" + showLogLevel LevelWarn = "[warn]" + showLogLevel LevelError = "[error]" + showLogLevel (LevelOther t) = T.concat ["[", t, "]"] + text = T.concat + [ T.pack $ show time + , " : " + , showLogLevel logLevel + , " " + , TE.decodeUtf8 . fromLogStr . toLogStr $ msg + ] + pushLogStrLn loggerSet $ toLogStr text |