diff --git a/completions/nom.fish b/completions/nom.fish index a668434..797402f 100644 --- a/completions/nom.fish +++ b/completions/nom.fish @@ -1 +1,7 @@ -complete --command nom --wraps nix +complete --command nom-build --wraps nix-build +complete --command nom-shell --wraps nix-shell + +complete --command nom --condition "__fish_seen_subcommand_from build" --wraps "nix build" +complete --command nom --condition "__fish_seen_subcommand_from develop" --wraps "nix develop" +complete --command nom --condition "__fish_seen_subcommand_from shell" --wraps "nix shell" +complete --command nom --condition "__fish_seen_subcommand_from copy" --wraps "nix copy" diff --git a/completions/nom-build.zsh b/completions/zsh/_nom-build.zsh similarity index 100% rename from completions/nom-build.zsh rename to completions/zsh/_nom-build.zsh diff --git a/completions/nom-shell.zsh b/completions/zsh/_nom-shell.zsh similarity index 100% rename from completions/nom-shell.zsh rename to completions/zsh/_nom-shell.zsh diff --git a/completions/nom.zsh b/completions/zsh/nom.zsh similarity index 100% rename from completions/nom.zsh rename to completions/zsh/nom.zsh diff --git a/default.nix b/default.nix index 061daf4..3846a24 100644 --- a/default.nix +++ b/default.nix @@ -1,8 +1,9 @@ { mkDerivation, ansi-terminal, async, attoparsec, base, bytestring , cassava, containers, directory, extra, filelock, filepath -, hermes-json, HUnit, lib, nix-derivation, optics, random, relude -, safe, safe-exceptions, stm, streamly-core, strict, terminal-size -, text, time, transformers, typed-process, unix, word8 +, hermes-json, HUnit, lib, nix-derivation, optics +, optparse-applicative, random, relude, safe, safe-exceptions, stm +, streamly-core, strict, terminal-size, text, time, transformers +, typed-process, unix, word8 }: mkDerivation { pname = "nix-output-monitor"; @@ -19,8 +20,9 @@ mkDerivation { executableHaskellDepends = [ ansi-terminal async attoparsec base bytestring cassava containers directory extra filelock filepath hermes-json nix-derivation optics - relude safe safe-exceptions stm streamly-core strict terminal-size - text time transformers typed-process unix word8 + optparse-applicative relude safe safe-exceptions stm streamly-core + strict terminal-size text time transformers typed-process unix + word8 ]; testHaskellDepends = [ ansi-terminal async attoparsec base bytestring cassava containers diff --git a/exe/Main.hs b/exe/Main.hs index 579a237..4e81eb7 100644 --- a/exe/Main.hs +++ b/exe/Main.hs @@ -23,6 +23,8 @@ import NOM.Update (detectLocalFinishedBuilds, maintainState) import NOM.Update.Monad (UpdateMonad) import Optics ((%), (%~), (.~), (^.)) import Optics.TH (makeFieldLabelsNoPrefix) +import Options.Applicative +import Options.Applicative.Help import Paths_nix_output_monitor (version) import Relude import System.Console.ANSI qualified as Terminal @@ -40,6 +42,16 @@ data ProcessState a = MkProcessState , printFunction :: Maybe (Window Int) -> (ZonedTime, Double) -> Text } +data SubCommand = CmdBuild | CmdShell | CmdDevelop | CmdCopy + deriving stock (Show) + +data Options = Options + { version :: Bool + , json :: Bool + , subCommand :: Maybe SubCommand -- This is always Nothing + } + deriving stock (Show) + makeFieldLabelsNoPrefix ''ProcessState outputHandle :: Handle @@ -55,15 +67,62 @@ defaultConfig = replaceCommandWithExit :: [String] -> [String] replaceCommandWithExit = (<> ["--command", "sh", "-c", "exit"]) . takeWhile (\x -> x /= "--command" && x /= "-c") -knownSubCommands :: [String] -knownSubCommands = ["build", "copy", "shell", "develop"] - -knownFlags :: [String] -knownFlags = ["--version", "-h", "--help", "--json"] - withJSON :: [String] -> [String] withJSON x = "-v" : "--log-format" : "internal-json" : x +parseOptions :: ParserInfo Options +parseOptions = + info + (helper <*> parseOptionsNoInfo) + ( progDesc "Outputs nix build info in a pretty way" + <> footerDoc (Just usageDoc) + ) + where + usageDoc :: Doc + usageDoc = + vsep + [ "Wrappers (when invoked as nom-build, nom-shell, etc.):" + , indent 2 + $ vsep + [ "nom-build " + , "nom-shell " + ] + , mempty + , "Direct piping:" + , indent 2 + $ vsep + [ "via json parsing:" + , indent 2 + $ vsep + [ "nix build --log-format internal-json -v |& nom --json" + , "nix-build --log-format internal-json -v |& nom --json" + ] + , mempty + , "via human-readable log parsing:" + , indent 2 "nix-build |& nom" + , mempty + , "Don't forget to redirect stderr, too. That's what the `&` does." + ] + , mempty + , "Please see the readme for more details:" + , "https://code.maralorn.de/maralorn/nix-output-monitor#readme" + ] + + parseSubCommand :: Parser SubCommand + parseSubCommand = + subparser + ( command "build" (info (pure CmdBuild) $ progDesc "Wrapper over `nix build`") + <> command "shell" (info (pure CmdShell) $ progDesc "Wrapper over `nix shell`") + <> command "develop" (info (pure CmdDevelop) $ progDesc "Wrapper over `nix develop`") + <> command "copy" (info (pure CmdDevelop) $ progDesc "Wrapper over `nix copy`") + ) + parseOptionsNoInfo :: Parser Options + parseOptionsNoInfo = + Options + <$> switch (long "version" <> help "Show version") + <*> switch (long "json" <> help "Parse input as nix internal-json") + <*> optional parseSubCommand + main :: IO Void main = do installSignalHandlers @@ -71,15 +130,13 @@ main = do prog_name <- Environment.getProgName args <- Environment.getArgs - lookupEnv "NIX_GET_COMPLETIONS" >>= \case - Just _ -> printNixCompletion prog_name args - Nothing -> runApp prog_name args + runApp prog_name args runApp :: String -> [String] -> IO Void runApp = \cases - _ ["--version"] -> do - hPutStrLn stderr ("nix-output-monitor " <> fromString (showVersion version)) - exitWith =<< runProcess (proc "nix" ["--version"]) + -- _ ["--version"] -> do + -- hPutStrLn stderr ("nix-output-monitor " <> fromString (showVersion version)) + -- exitWith =<< runProcess (proc "nix" ["--version"]) "nom-build" args -> exitWith =<< runMonitoredCommand defaultConfig (proc "nix-build" (withJSON args)) "nom-shell" args -> do exitOnFailure =<< runMonitoredCommand defaultConfig{silent = True} (proc "nix-shell" (withJSON args <> ["--run", "exit"])) @@ -92,37 +149,20 @@ runApp = \cases "nom" ("develop" : args) -> do exitOnFailure =<< runMonitoredCommand defaultConfig{silent = True} (proc "nix" ("develop" : withJSON (replaceCommandWithExit args))) exitWith =<< runProcess (proc "nix" ("develop" : args)) - "nom" [] -> do - finalState <- monitorHandle OldStyleInput defaultConfig{piping = True} stdin - if CMap.size finalState.fullSummary.failedBuilds + length finalState.nixErrors == 0 - then exitSuccess - else exitFailure - "nom" ["--json"] -> do - finalState <- monitorHandle NixJSONMessage defaultConfig{piping = True} stdin - if CMap.size finalState.fullSummary.failedBuilds + length finalState.nixErrors == 0 - then exitSuccess - else exitFailure - _ xs -> do - hPutStrLn stderr helpText - -- It's not a mistake if the user requests the help text, otherwise tell - -- them off with a non-zero exit code. - if any (liftA2 (||) (== "-h") (== "--help")) xs then exitSuccess else exitFailure - -printNixCompletion :: String -> [String] -> IO Void -printNixCompletion = \cases - "nom" [input] -> do - putStrLn "normal" - mapM_ putStrLn $ findMatches input (knownSubCommands <> knownFlags) - exitSuccess - "nom" args@(sub_cmd : _) - | sub_cmd `elem` knownSubCommands -> - exitWith =<< Process.runProcess (Process.proc "nix" args) - prog args -> do - putTextLn $ "No completion support for " <> unwords (toText <$> prog : args) - exitFailure - -findMatches :: String -> [String] -> [String] -findMatches input = filter (input `isPrefixOf`) + _ _ -> do + parsedArgs <- execParser parseOptions + case parsedArgs of + Options{version = True} -> do + hPutStrLn stderr ("nix-output-monitor " <> fromString (showVersion version)) + exitWith =<< runProcess (proc "nix" ["--version"]) + _ -> do + finalState <- + (bool (monitorHandle OldStyleInput) (monitorHandle NixJSONMessage) parsedArgs.json) + defaultConfig{piping = True} + stdin + if CMap.size finalState.fullSummary.failedBuilds + length finalState.nixErrors == 0 + then exitSuccess + else exitFailure installSignalHandlers :: IO () installSignalHandlers = do @@ -217,35 +257,3 @@ finalizer config = do { updaterState = nomState @a .~ newState $ old_state.updaterState , printFunction = stateToText config newState } - -helpText :: Text -helpText = - unlines - [ "nix-output-monitor usages:" - , " Wrappers:" - , " nom build " - , " nom shell " - , " nom develop " - , " nom copy " - , "" - , " nom-build " - , " nom-shell " - , "" - , " Direct piping:" - , " via json parsing:" - , " nix build --log-format internal-json -v |& nom --json" - , " nix-build --log-format internal-json -v |& nom --json" - , "" - , " via human-readable log parsing:" - , " nix-build |& nom" - , "" - , " Don't forget to redirect stderr, too. That's what the & does." - , "" - , "Flags:" - , " --version Show version." - , " -h, --help Show this help." - , " --json Parse input as nix internal-json" - , "" - , "Please see the readme for more details:" - , "https://code.maralorn.de/maralorn/nix-output-monitor#readme" - ] diff --git a/flake.nix b/flake.nix index 9c971b5..1ed7d90 100644 --- a/flake.nix +++ b/flake.nix @@ -47,19 +47,35 @@ (haskellPackages.callPackage self) haskellPackages.buildFromCabalSdist hlib.justStaticExecutables - (hlib.appendConfigureFlag "--ghc-option=-Werror --ghc-option=-Wno-error=unrecognised-warning-flags") + (hlib.appendConfigureFlag "--ghc-option=-Wno-error=unrecognised-warning-flags") (hlib.overrideCabal ( { src = cleanSelf; doCheck = false; - buildTools = [ pkgs.installShellFiles ]; - postInstall = '' - ln -s nom "$out/bin/nom-build" - ln -s nom "$out/bin/nom-shell" - chmod a+x $out/bin/nom-shell - installShellCompletion completions/* - ''; + postInstall = + # bash + '' + ln -s nom "$out/bin/nom-build" + ln -s nom "$out/bin/nom-shell" + + bashCompDir="''${!outputBin}/share/bash-completion/completions" + zshCompDir="''${!outputBin}/share/zsh/vendor-completions" + fishCompDir="''${!outputBin}/share/fish/vendor_completions.d" + + mkdir -p "$bashCompDir" "$zshCompDir" "$fishCompDir" + + # Optparse applicative builtin completions + "''${!outputBin}/bin/nom" --bash-completion-script "''${!outputBin}/bin/nom" >"$bashCompDir/nom" + "''${!outputBin}/bin/nom" --zsh-completion-script "''${!outputBin}/bin/nom" >"$zshCompDir/_nom" + "''${!outputBin}/bin/nom" --fish-completion-script "''${!outputBin}/bin/nom" >"$fishCompDir/nom.fish" + + # Nix 2 wrappers (nix-*) without fish + cp completions/zsh/_nom-*.zsh "$zshCompDir" + + # Both Nix 2 and Nix 3 wrappers for fish + cat completions/nom.fish >> "$fishCompDir/nom.fish" + ''; } // lib.optionalAttrs (system == "x86_64-linux") { doCheck = true; diff --git a/hls.json b/hls.json new file mode 100644 index 0000000..806073d --- /dev/null +++ b/hls.json @@ -0,0 +1,8 @@ +{ + "plugin": { + "hlint": { + "codeActionsOn": false, + "diagnosticsOn": false + } + } +} diff --git a/nix-output-monitor.cabal b/nix-output-monitor.cabal index 08e9c72..3a6b921 100644 --- a/nix-output-monitor.cabal +++ b/nix-output-monitor.cabal @@ -15,8 +15,6 @@ author: maralorn maintainer: maralorn build-type: Simple extra-source-files: - completions/completion.bash - completions/completion.zsh test/golden/fail/stderr test/golden/fail/stderr.json test/golden/fail/stdout @@ -151,6 +149,7 @@ executable nom other-modules: Paths_nix_output_monitor build-depends: nix-output-monitor, + optparse-applicative, typed-process, unix,