glualint-1.27.0: app/GLuaFixer/Effects/Run.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module GLuaFixer.Effects.Run where
import Control.Monad (when)
import qualified Data.Aeson as JSON
import qualified Data.ByteString.Lazy.Char8 as BL8
import Effectful (Eff, (:>))
import qualified Effectful.Environment as Eff
import Effectful.Error.Static (runErrorNoCallStack)
import GLua.AG.AST (AST)
import GLua.AG.Token (MToken)
import GLua.ASTInstances ()
import qualified GLua.Parser as Parser
import GLuaFixer.Cli (Command (..), Options (..), OverriddenSettings, SettingsPath)
import GLuaFixer.Effects.AnalyseGlobals (analyseFile, execAnalysis, reportAnalysis)
import GLuaFixer.Effects.Cli (Cli, CliParseResult (..), parseCliOptions)
import GLuaFixer.Effects.Files (Files, IgnoreFiles (..), findLuaFiles, getCurrentDirectory, isDirectory, readFile, readStdIn, traceFilesIfEnabled, writeFile)
import GLuaFixer.Effects.Interruptible (Interruptible, interruptibleFoldMStrict)
import GLuaFixer.Effects.Logging (Logging, emitLintMessage, getLogFormat, putStrLnStdError, putStrLnStdOut, putStrStdOut)
import GLuaFixer.Effects.Settings (Settings, SettingsError (CouldNotParseSettings), getSettingsForFile, runSettings, traceSettingsIfEnabled)
import qualified GLuaFixer.Interface as Interface
import GLuaFixer.LintMessage (sortLintMessages)
import GLuaFixer.LintSettings (
LintSettings (..),
StdInOrFiles (..),
)
import GLuaFixer.Version (version)
import System.Exit (ExitCode (..))
import Prelude hiding (lex, readFile, writeFile)
-- | Top level run function
run
:: ( Interruptible :> es
, Files :> es
, Logging :> es
, Eff.Environment :> es
, Cli :> es
)
=> Eff es ExitCode
run = do
pareResult <- parseCliOptions
case pareResult of
PrintHelpText exitCode helpText -> do
putStrLnStdError helpText
pure exitCode
ParseSuccessful options -> do
result <-
runErrorNoCallStack @SettingsError $
runSettings $
runOptions options
case result of
Left (CouldNotParseSettings err) -> do
putStrLnStdError $ "Could not parse settings file: " <> err
pure $ ExitFailure 1
Right exitCode -> pure exitCode
-- | Run the given options
runOptions
:: ( Interruptible :> es
, Files :> es
, Settings :> es
, Logging :> es
, Eff.Environment :> es
, Cli :> es
)
=> Options
-> Eff es ExitCode
runOptions options =
traceFilesIfEnabled options.optsDebug $
traceSettingsIfEnabled options.optsDebug $ do
when (options.optsDebug) $
putStrLnStdError $
show options
case (options.optsCommand, options.optsFiles) of
(Lint, UseStdIn) -> do
(lintSettings, contents) <- getStdIn options.optsConfigFile options.optsOverridden
lint lintSettings "stdin" contents
(Lint, UseFiles files) ->
foldLuaFiles
options.optsConfigFile
options.optsOverridden
ExitSuccess
files
$ \exitCode lintSettings filepath contents ->
worstExitCode exitCode <$> lint lintSettings filepath contents
(PrettyPrint, UseStdIn) -> do
(lintSettings, contents) <- getStdIn options.optsConfigFile options.optsOverridden
case prettyprint lintSettings "stdin" contents of
Nothing -> pure $ ExitFailure 1
Just prettyprinted -> do
putStrStdOut prettyprinted
pure ExitSuccess
(PrettyPrint, UseFiles files) ->
foldLuaFiles
options.optsConfigFile
options.optsOverridden
ExitSuccess
files
$ \exitCode lintSettings filepath contents -> do
putStrLnStdOut $ "Pretty printing " <> filepath
case prettyprint lintSettings filepath contents of
Nothing -> pure $ ExitFailure 1
Just prettyprinted -> do
writeFile filepath prettyprinted
pure exitCode
(AnalyseGlobals, UseStdIn) -> do
(lintSettings, contents) <- getStdIn options.optsConfigFile options.optsOverridden
withParsed lintSettings "stdin" contents (ExitFailure 1) $ \ast -> do
analysis <- execAnalysis $ analyseFile lintSettings "stdin" ast
reportAnalysis analysis
pure ExitSuccess
(AnalyseGlobals, UseFiles files) -> do
analysis <- execAnalysis
$ foldLuaFiles
options.optsConfigFile
options.optsOverridden
()
files
$ \() lintSettings filepath contents -> do
withParsed lintSettings filepath contents () $ \ast ->
analyseFile lintSettings filepath ast
reportAnalysis analysis
pure ExitSuccess
(DumpLexicon, UseStdIn) -> do
(lintSettings, contents) <- getStdIn options.optsConfigFile options.optsOverridden
mbTokens <- getLexicon lintSettings "stdin" contents
case mbTokens of
Nothing -> pure $ ExitFailure 1
tokens -> do
putStrLnStdOut $ show tokens
pure ExitSuccess
(DumpLexicon, UseFiles files) ->
foldLuaFiles
options.optsConfigFile
options.optsOverridden
ExitSuccess
files
$ \exitCode lintSettings filepath contents -> do
mbTokens <- getLexicon lintSettings filepath contents
case mbTokens of
Nothing -> pure $ ExitFailure 1
Just tokens -> do
putStrLnStdOut $ show tokens
pure exitCode
(DumpAst, UseStdIn) -> do
(lintSettings, contents) <- getStdIn options.optsConfigFile options.optsOverridden
withParsed lintSettings "stdin" contents (ExitFailure 1) $ \ast -> do
putStrLnStdOut $ BL8.unpack $ JSON.encode ast
pure ExitSuccess
(DumpAst, UseFiles files) ->
foldLuaFiles
options.optsConfigFile
options.optsOverridden
ExitSuccess
files
$ \exitCode lintSettings filepath contents ->
withParsed lintSettings filepath contents (ExitFailure 1) $ \ast -> do
putStrLnStdOut $ BL8.unpack $ JSON.encode ast
pure exitCode
(Test, UseStdIn) -> do
(lintSettings, contents) <- getStdIn options.optsConfigFile options.optsOverridden
test ExitSuccess lintSettings "stdin" contents
(Test, UseFiles files) -> do
foldLuaFiles
options.optsConfigFile
options.optsOverridden
ExitSuccess
files
$ \exitCode lintSettings filepath contents ->
test exitCode lintSettings filepath contents
(PrintVersion, _) -> do
putStrLnStdOut version
pure ExitSuccess
-- | Retrieves the contents of stdin and the settings that apply.
getStdIn
:: (Files :> es, Settings :> es)
=> Maybe SettingsPath
-> OverriddenSettings
-> Eff es (LintSettings, String)
getStdIn mbSettingsPath overriddenSettings = do
cwd <- getCurrentDirectory
settings <- getSettingsForFile mbSettingsPath overriddenSettings cwd
code <- readStdIn
pure (settings, code)
-- | Fold over all the Lua files. Recurses into directories, retrieves settings and passes file
-- contents to the fold function.
foldLuaFiles
:: forall es a
. (Files :> es, Interruptible :> es, Settings :> es)
=> Maybe SettingsPath
-> OverriddenSettings
-> a
-> [FilePath]
-> (a -> LintSettings -> FilePath -> String -> Eff es a)
-> Eff es a
foldLuaFiles mbSettingsPath overriddenSettings initial files f =
interruptibleFoldMStrict go initial files
where
go :: a -> FilePath -> Eff es a
go acc file = do
isDir <- isDirectory file
lintSettings <- getSettingsForFile mbSettingsPath overriddenSettings file
if isDir
then do
let
ignoreFiles = IgnoreFiles $ lintSettings.lint_ignoreFiles
recurseFiles <- findLuaFiles ignoreFiles file
interruptibleFoldMStrict go acc recurseFiles
else do
contents <- readFile file
f acc lintSettings file contents
-- | Lint a file
lint
:: (Logging :> es, Eff.Environment :> es)
=> LintSettings
-> FilePath
-> String
-> Eff es ExitCode
lint lintSettings filepath contents = do
logFormat <- getLogFormat lintSettings.log_format
let
sourceLint = Interface.sourceLint lintSettings filepath contents
case Interface.lex lintSettings filepath contents of
Left msgs -> do
mapM_ (emitLintMessage logFormat) msgs
pure $ ExitFailure 1
Right tokens -> do
let
!lextLint = Interface.lexiconLint filepath lintSettings tokens
case Interface.parse lintSettings filepath tokens of
Left msgs -> do
mapM_ (emitLintMessage logFormat) msgs
pure $ ExitFailure 1
Right ast -> do
let
astLint = Interface.astLint filepath lintSettings ast
msgs = sortLintMessages $ sourceLint ++ lextLint ++ astLint
mapM_ (emitLintMessage logFormat) msgs
pure $ if null msgs then ExitSuccess else ExitFailure 1
-- | Pretty print a file
prettyprint
:: LintSettings
-> FilePath
-> String
-> Maybe String
prettyprint lintSettings filepath contents =
case eAst of
Left _errors -> Nothing
Right ast -> Just $ Interface.prettyprint lintSettings ast
where
eAst = Interface.lex lintSettings filepath contents >>= Interface.parse lintSettings filepath
-- | Test glualint itself against a file. TODO: Refactor this into a nicer command
test
:: (Logging :> es, Eff.Environment :> es)
=> ExitCode
-> LintSettings
-> FilePath
-> String
-> Eff es ExitCode
test exitCode lintSettings filepath contents = do
putStrLnStdOut $ "Testing " <> filepath
logFormat <- getLogFormat lintSettings.log_format
case Interface.lex lintSettings filepath contents of
Left msgs -> do
mapM_ (emitLintMessage logFormat) msgs
pure $ ExitFailure 1
Right tokens ->
case Interface.parse lintSettings filepath tokens of
Left msgs -> do
putStrLnStdOut $
"Errors when trying to parse '" ++ filepath
mapM_ (emitLintMessage logFormat) msgs
pure $ ExitFailure 1
Right ast -> do
let
prettyprinted = Interface.prettyprint lintSettings ast
case Parser.parseGLuaFromString prettyprinted of
Left err -> do
putStrLnStdOut $
"Errors when trying to parse '" ++ filepath ++ "' after pretty print!"
putStrLnStdOut $ show err
pure $ ExitFailure 1
Right _ast -> pure exitCode
-- | Function to easily parse a file's contents into an AST. This will log any parse failures and
-- give an AST if it can.
withParsed
:: (Logging :> es, Eff.Environment :> es)
=> LintSettings
-> FilePath
-> String
-> a
-> (AST -> Eff es a)
-> Eff es a
withParsed lintSettings filepath contents defaultValue f = do
lexicon <- getLexicon lintSettings filepath contents
case lexicon of
Nothing -> pure defaultValue
Just tokens -> do
logFormat <- getLogFormat lintSettings.log_format
case Interface.parse lintSettings filepath tokens of
Left msgs -> do
mapM_ (emitLintMessage logFormat) msgs
pure defaultValue
Right ast -> f ast
-- | Function to parse a file's contents into MTokens. This will log any parse failures and give
-- MTokens if it can.
getLexicon :: Logging :> es => LintSettings -> FilePath -> String -> Eff es (Maybe [MToken])
getLexicon lintSettings filepath contents = do
logFormat <- getLogFormat lintSettings.log_format
case Interface.lex lintSettings filepath contents of
Left msgs -> do
mapM_ (emitLintMessage logFormat) msgs
pure Nothing
Right tokens -> do
pure $ Just tokens
-- | Takes the worst of the two exit codes.
worstExitCode :: ExitCode -> ExitCode -> ExitCode
worstExitCode e1 e2 = case (e1, e2) of
(ExitSuccess, ExitSuccess) -> ExitSuccess
(ExitFailure code, _) -> ExitFailure code
(_, ExitFailure code) -> ExitFailure code