@@ -4,14 +4,20 @@ import Prelude
44
55import Data.Array as Array
66import Data.Either (Either(..))
7+import Data.Enum (fromEnum)
78import Data.Foldable (for_)
89import Data.Maybe (Maybe(..))
910import Data.Newtype (unwrap)
1011import Data.String.CodeUnits as String
12+import Data.Tuple.Nested ((/\))
1113import Effect (Effect)
1214import Effect.Console (log)
1315import Node.FS.Simple as FS
1416import Node.CLI as Process
17+import Options.Applicative (Parser, argument, execParserPure, fullDesc, header, help, helper, info, long, many, metavar, progDesc, str, switch, (<**>))
18+import Options.Applicative.Builder (defaultPrefs)
19+import Options.Applicative.Types (CompletionResult(..), ParserFailure(ParserFailure), ParserResult(..))
20+import Options.Applicative.Help.Types (ParserHelp(ParserHelp), renderHelp)
1521import Purslint.Config (defaultConfig, filterRules, parseConfig)
1622import Purslint.Fix (applyAllFixes)
1723import Purslint.Rule (Rule)
@@ -99,6 +105,13 @@ import Purslint.Rules.RedundantParens (redundantParensRule)
99105import Purslint.Runner (runRules)
100106import Purslint.Types (LintResult(..), LintWarning(..), Severity(..), SourceCode(..), Suggestion(..))
101107
108+type CliOptions =
109+ { fix :: Boolean
110+ , listRules :: Boolean
111+ , initConfig :: Boolean
112+ , paths :: Array String
113+ }
114+
102115-- | All available rules (81 total)
103116allRules :: Array Rule
104117allRules =
@@ -337,38 +350,70 @@ initConfig = do
337350 FS.writeTextFile ".purslintrc" sampleConfig
338351 log "Created .purslintrc"
339352
353+parseFlags :: Array String -> Effect (Maybe CliOptions)
354+parseFlags args = case parseFlags' (Array.drop 2 args) of
355+ Success cli -> pure (Just cli)
356+ Failure (ParserFailure pf) -> case pf programName of
357+ ph@(ParserHelp _) /\ exitCode /\ i /\ _unit -> do
358+ log $ renderHelp i ph
359+ Process.exit (fromEnum exitCode)
360+ pure Nothing
361+ CompletionInvoked (CompletionResult { execCompletion }) -> do
362+ executedCompletion <- execCompletion programName
363+ log executedCompletion
364+ Process.exit 0
365+ pure Nothing
366+ where
367+ programName = "purslint"
368+
369+parseFlags' :: Array String -> ParserResult CliOptions
370+parseFlags' = execParserPure defaultPrefs opts
371+ where
372+ opts =
373+ info (flagsParser <**> helper) $
374+ fullDesc
375+ <> progDesc "Lint PureScript source files"
376+ <> header "purslint - a linter for PureScript"
377+
378+flagsParser :: Parser CliOptions
379+flagsParser = ado
380+ fix <- switch $ long "fix" <> help "Auto-fix issues (where possible)"
381+ listRules <- switch $ long "list" <> help "List all available rules"
382+ initConfig <- switch $ long "init" <> help "Create a sample .purslintrc"
383+ pathsList <- many $ argument str $ metavar "PATH..."
384+ let paths = Array.fromFoldable pathsList
385+ in { fix, listRules, initConfig, paths }
386+
340387main :: Effect Unit
341388main = do
342389 args <- Process.argv
343− -- args[0] is node, args[1] is the script, args[2..] are the actual arguments
344− let userArgs = Array.drop 1 args
345− case userArgs of
346− [] -> showUsage
347− ["--help"] -> showUsage
348− ["-h"] -> showUsage
349− ["--list"] -> listRules
350− ["--init"] -> initConfig
351− _ -> do
352− -- Check for --fix flag
353− let hasFix = Array.elem "--fix" userArgs
354− let paths = Array.filter (\a -> a /= "--fix") userArgs
355−
356− -- Load config and filter rules
357− config <- loadConfig
358− let enabledRules = filterRules allRules config
359−
360− if hasFix then do
361− -- Fix mode
362− _ <- Array.foldM (\acc path -> do
363− count <- fixPath enabledRules path
364− pure (acc + count)) 0 paths
365− pure unit
390+ maybeCli <- parseFlags args
391+ case maybeCli of
392+ Nothing -> pure unit
393+ Just cli ->
394+ if cli.listRules then
395+ listRules
396+ else if cli.initConfig then
397+ initConfig
398+ else if Array.null cli.paths then
399+ showUsage
366400 else do
367− -- Lint mode
368− totalWarnings <- Array.foldM (\acc path -> do
369− count <- lintPathWithConfig enabledRules path
370− pure (acc + count)) 0 paths
371− when (totalWarnings > 0) do
372− log ""
373− log $ "Total: " <> show totalWarnings <> " warning" <> if totalWarnings == 1 then "" else "s"
374− Process.exit 1
401+ -- Load config and filter rules
402+ config <- loadConfig
403+ let enabledRules = filterRules allRules config
404+
405+ if cli.fix then do
406+ -- Fix mode
407+ _ <- Array.foldM (\acc path -> do
408+ count <- fixPath enabledRules path
409+ pure (acc + count)) 0 cli.paths
410+ pure unit
411+ else do
412+ -- Lint mode
413+ totalWarnings <- Array.foldM (\acc path -> do
414+ count <- lintPathWithConfig enabledRules path
415+ pure (acc + count)) 0 cli.paths
416+ when (totalWarnings > 0) do
417+ log ""
418+ log $ "Total: " <> show totalWarnings <> " warning" <> if totalWarnings == 1 then "" else "s"
419+ Process.exit 1