packages feed

core-program 0.6.3.0 → 0.6.5.0

raw patch · 4 files changed

+194/−65 lines, 4 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Core.Program.Execute: execProcess :: [Rope] -> Program τ (ExitCode, Rope, Rope)
+ Core.Program.Arguments: complexConfig' :: Description -> [Commands] -> Config
+ Core.Program.Arguments: simpleConfig' :: Description -> [Options] -> Config
+ Core.Program.Execute: execProcess_ :: [Rope] -> Program τ ()
+ Core.Program.Execute: readProcess :: [Rope] -> Program τ (ExitCode, Rope, Rope)

Files

core-program.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           core-program-version:        0.6.3.0+version:        0.6.5.0 synopsis:       Opinionated Haskell Interoperability description:    A library to help build command-line programs, both tools and                 longer-running daemons.
lib/Core/Program/Arguments.hs view
@@ -25,7 +25,9 @@       Config     , blankConfig     , simpleConfig+    , simpleConfig'     , complexConfig+    , complexConfig'     , baselineOptions     , Parameters (..)     , ParameterValue (..)@@ -115,8 +117,8 @@ -} data Config     = Blank-    | Simple [Options]-    | Complex [Commands]+    | Simple Description [Options]+    | Complex Description [Commands]  -- -- Those constructors are not exposed [and functions wrapping them are] partly@@ -200,9 +202,29 @@ @since 0.2.9 -} simpleConfig :: [Options] -> Config-simpleConfig options = Simple (options ++ baselineOptions)+simpleConfig options = Simple emptyRope (options ++ baselineOptions)  {- |+Declare a simple configuration as with 'simpleConfig', along with a+descriptive precis that will be printed at the top of the @--help@ output.+++@+\$ __./snippet --help__+A small but very useful program.++Usage:++    snippet [OPTIONS] <filename>+...+@++@since 0.6.5+-}+simpleConfig' :: Description -> [Options] -> Config+simpleConfig' description options = Simple description (options ++ baselineOptions)++{- | Declare a complex configuration (implying a larger tool with various "[sub]commands" or "modes"} for a program. You can specify global options applicable to all commands, a list of commands, and environment variables@@ -286,9 +308,22 @@ @since 0.2.9 -} complexConfig :: [Commands] -> Config-complexConfig commands = Complex (commands ++ [Global baselineOptions])+complexConfig commands = Complex emptyRope (commands ++ [Global baselineOptions])  {- |+Declare a complex configuration as with 'complexConfig', along with a+descriptive precis that will be printed at the top of the @--help@ output when+requesting help for the program as a whole.++If help is requested for one of the sub commands, the description from the+'Command' constructor will be used at the top of the output.++@since 0.6.5+-}+complexConfig' :: Description -> [Commands] -> Config+complexConfig' precis commands = Complex precis (commands ++ [Global baselineOptions])++{- | Description of the command-line structure of a program which has \"commands\" (sometimes referred to as \"subcommands\") representing different modes of operation. This is familiar from tools like /git/@@ -332,7 +367,7 @@  @         [ ...-        , 'Remaining' "The files you wish to delete permanently."+        , 'Remaining' \"The files you wish to delete permanently.\"         , ...         ] @@@ -344,10 +379,20 @@  @         [ ...-        , 'Variable' \"CRAZY_MODE\" "Specify how many crazies to activate."+        , 'Variable' \"CRAZY_MODE\" \"Specify how many crazies to activate.\"         , ...         ] @++Finally, there is a 'Descriptive' constructor which allows you to put a piece+of header text as a descriptive summary in help output before it starts+enumerating the options and arguments.++@+        [ 'Descriptive' \"A program to evaluate just how crazy you are.\"+        , ...+        ]+@ -} data Options     = Option LongName (Maybe ShortName) ParameterValue Description@@ -360,8 +405,8 @@ appendOption option config =     case config of         Blank -> Blank-        Simple options -> Simple (options ++ [option])-        Complex commands -> Complex (commands ++ [Global [option]])+        Simple precis options -> Simple precis (options ++ [option])+        Complex precis commands -> Complex precis (commands ++ [Global [option]])  {- | Individual parameters read in off the command-line can either have a value@@ -550,11 +595,11 @@ parseCommandLine :: Config -> [String] -> Either InvalidCommandLine Parameters parseCommandLine config argv = case config of     Blank -> return (Parameters Nothing emptyMap [] emptyMap)-    Simple options -> do+    Simple _ options -> do         (params, remainder) <- extractor Nothing options argv         checkRemainder options remainder         return (Parameters Nothing params remainder emptyMap)-    Complex commands ->+    Complex _ commands ->         let globalOptions = extractGlobalOptions commands             modes = extractValidModes commands         in  do@@ -655,13 +700,13 @@                 Right (list, remainder) -> Right (((name, Value arg) : list), remainder)  parseIndicatedCommand-    :: Map LongName [Options]+    :: Map LongName (Description, [Options])     -> String     -> Either InvalidCommandLine (LongName, [Options]) parseIndicatedCommand modes first =     let candidate = LongName first     in  case lookupKeyValue candidate modes of-            Just options -> Right (candidate, options)+            Just (_, options) -> Right (candidate, options)             Nothing -> Left (UnknownCommand first)  --@@ -702,12 +747,12 @@     j (Global options) valids = options ++ valids     j _ valids = valids -extractValidModes :: [Commands] -> Map LongName [Options]+extractValidModes :: [Commands] -> Map LongName (Description, [Options]) extractValidModes commands =     List.foldl' k emptyMap commands   where-    k :: Map LongName [Options] -> Commands -> Map LongName [Options]-    k modes (Command longname _ options) = insertKeyValue longname options modes+    k :: Map LongName (Description, [Options]) -> Commands -> Map LongName (Description, [Options])+    k modes (Command longname description options) = insertKeyValue longname (description, options) modes     k modes _ = modes  {-@@ -740,8 +785,8 @@ extractValidEnvironments :: Maybe LongName -> Config -> Set LongName extractValidEnvironments mode config = case config of     Blank -> emptySet-    Simple options -> extractVariableNames options-    Complex commands ->+    Simple _ options -> extractVariableNames options+    Complex _ commands ->         let globals = extractGlobalOptions commands             variables1 = extractVariableNames globals @@ -775,9 +820,10 @@ buildUsage :: Config -> Maybe LongName -> Doc ann buildUsage config mode = case config of     Blank -> emptyDoc-    Simple options ->+    Simple precis options ->         let (o, a, v) = partitionParameters options-        in  "Usage:"+        in  formatPrecis precis+                <> "Usage:"                 <> hardline                 <> hardline                 <> indent@@ -799,36 +845,18 @@                 <> formatParameters a                 <> variablesHeading v                 <> formatParameters v-    Complex commands ->+    Complex precis commands ->         let globalOptions = extractGlobalOptions commands             modes = extractValidModes commands              (oG, _, vG) = partitionParameters globalOptions-        in  "Usage:" <> hardline <> hardline <> case mode of+        in  case mode of                 Nothing ->-                    indent-                        2-                        ( nest-                            4-                            ( fillCat-                                [ pretty programName-                                , globalSummary oG-                                , commandSummary modes-                                ]-                            )-                        )+                    formatPrecis precis+                        <> "Usage:"                         <> hardline-                        <> globalHeading oG-                        <> formatParameters oG-                        <> commandHeading modes-                        <> formatCommands commands-                        <> variablesHeading vG-                        <> formatParameters vG-                Just longname ->-                    let (oL, aL, vL) = case lookupKeyValue longname modes of-                            Just localOptions -> partitionParameters localOptions-                            Nothing -> error "Illegal State"-                    in  indent+                        <> hardline+                        <> indent                             2                             ( nest                                 4@@ -836,13 +864,39 @@                                     [ pretty programName                                     , globalSummary oG                                     , commandSummary modes-                                    , localSummary oL-                                    , argumentsSummary aL-                                    , remainingSummary aL                                     ]                                 )                             )+                        <> hardline+                        <> globalHeading oG+                        <> formatParameters oG+                        <> commandHeading modes+                        <> formatCommands commands+                        <> variablesHeading vG+                        <> formatParameters vG+                Just longname ->+                    let (dL, (oL, aL, vL)) = case lookupKeyValue longname modes of+                            Just (description, localOptions) -> (description, partitionParameters localOptions)+                            Nothing -> error "Illegal State"+                    in  formatPrecis dL+                            <> "Usage:"                             <> hardline+                            <> hardline+                            <> indent+                                2+                                ( nest+                                    4+                                    ( fillCat+                                        [ pretty programName+                                        , globalSummary oG+                                        , commandSummary modes+                                        , localSummary oL+                                        , argumentsSummary aL+                                        , remainingSummary aL+                                        ]+                                    )+                                )+                            <> hardline                             <> localHeading oL                             <> formatParameters oL                             <> argumentsHeading aL@@ -850,6 +904,11 @@                             <> variablesHeading vL                             <> formatParameters vL   where+    formatPrecis :: Description -> Doc ann+    formatPrecis precis = case widthRope precis of+        0 -> emptyDoc+        _ -> reflow (fromRope precis) <> hardline <> hardline+     partitionParameters :: [Options] -> ([Options], [Options], [Options])     partitionParameters options = List.foldl' f ([], [], []) options 
lib/Core/Program/Context.hs view
@@ -175,7 +175,9 @@     , exitSemaphoreFrom :: MVar ExitCode     , startTimeFrom :: MVar Time     , verbosityLevelFrom :: MVar Verbosity+    , outputSemaphoreFrom :: MVar ()     , outputChannelFrom :: TQueue (Maybe Rope) -- communication channels+    , telemetrySemaphoreFrom :: MVar ()     , telemetryChannelFrom :: TQueue (Maybe Datum) -- machinery for telemetry     , telemetryForwarderFrom :: Maybe Forwarder     , currentScopeFrom :: TVar (Set ThreadId)@@ -370,6 +372,8 @@     columns <- getConsoleWidth     coloured <- getConsoleColoured     level <- newEmptyMVar+    vo <- newEmptyMVar+    vl <- newEmptyMVar     out <- newTQueueIO     tel <- newTQueueIO @@ -389,7 +393,9 @@             , exitSemaphoreFrom = q             , startTimeFrom = i             , verbosityLevelFrom = level -- will be filled in handleVerbosityLevel+            , outputSemaphoreFrom = vo             , outputChannelFrom = out+            , telemetrySemaphoreFrom = vl             , telemetryChannelFrom = tel             , telemetryForwarderFrom = Nothing             , currentScopeFrom = scope
lib/Core/Program/Execute.hs view
@@ -82,7 +82,6 @@       -- * Useful actions     , outputEntire     , inputEntire-    , execProcess     , sleepThread     , resetTimer     , trap_@@ -92,6 +91,10 @@     , throw     , try +      -- * Running processes+    , readProcess+    , execProcess_+       -- * Internals     , Context     , None (..)@@ -104,6 +107,7 @@     , lookupOptionValue     , lookupArgument     , lookupEnvironmentValue+    , execProcess     ) where @@ -117,7 +121,6 @@ import Control.Concurrent.MVar     ( MVar     , modifyMVar_-    , newEmptyMVar     , newMVar     , putMVar     , readMVar@@ -176,8 +179,8 @@     ) import System.Exit (ExitCode (..)) import System.Posix.Internals (hostIsThreaded)-import System.Posix.Process qualified as Posix (exitImmediately)-import System.Process.Typed (nullStream, proc, readProcess, setStdin)+import System.Posix.Process qualified as Posix (executeFile, exitImmediately)+import System.Process.Typed qualified as Typed (nullStream, proc, readProcess, setStdin) import Prelude hiding (log)  {- |@@ -253,23 +256,23 @@     level <- handleVerbosityLevel context      let quit = exitSemaphoreFrom context-        out = outputChannelFrom context-        tel = telemetryChannelFrom context-        forwarder = telemetryForwarderFrom context+    let vo = outputSemaphoreFrom context+    let out = outputChannelFrom context+    let vl = telemetrySemaphoreFrom context+    let tel = telemetryChannelFrom context+    let forwarder = telemetryForwarderFrom context      -- set up signal handlers     _ <- forkIO $ do         setupSignalHandlers quit level      -- set up standard output-    vo <- newEmptyMVar     _ <-         forkFinally             (processStandardOutput out)             (\_ -> putMVar vo ())      -- set up debug logger-    vl <- newEmptyMVar     _ <-         forkFinally             (processTelemetryMessages forwarder level out tel)@@ -612,7 +615,7 @@             Settings                 { ...                 }-    +     'changeProgram' settings program2  program2 :: 'Program' Settings ()@@ -680,7 +683,7 @@ to be enumerated separately:  @-    'execProcess' [\"\/usr\/bin\/ssh\", \"-l\", \"admin\", \"203.0.113.42\", \"\\\'remote command here\\\'\"]+    'readProcess' [\"\/usr\/bin\/ssh\", \"-l\", \"admin\", \"203.0.113.42\", \"\\\'remote command here\\\'\"] @  having to write out the individual options and arguments and deal with@@ -691,15 +694,17 @@ so if you're doing something that returns huge amounts of output you'll want to use something like __io-streams__ instead. -(this wraps __typed-process__'s 'readProcess')+(this wraps __typed-process__'s 'System.Process.Typed.readProcess')++@since 0.6.4 -}-execProcess :: [Rope] -> Program τ (ExitCode, Rope, Rope)-execProcess [] = error "No command provided"-execProcess (cmd : args) =+readProcess :: [Rope] -> Program τ (ExitCode, Rope, Rope)+readProcess [] = error "No command provided"+readProcess (cmd : args) =     let cmd' = fromRope cmd         args' = fmap fromRope args-        task = proc cmd' args'-        task1 = setStdin nullStream task+        task = Typed.proc cmd' args'+        task1 = Typed.setStdin Typed.nullStream task         command = mconcat (List.intersperse (singletonRope ' ') (cmd : args))     in  do             debug "command" command@@ -711,9 +716,68 @@                     Safe.throw (CommandNotFound cmd)                 Just _ -> do                     (exit, out, err) <- liftIO $ do-                        readProcess task1+                        Typed.readProcess task1                      pure (exit, intoRope out, intoRope err)++execProcess :: [Rope] -> Program τ (ExitCode, Rope, Rope)+execProcess = readProcess+{-# DEPRECATED execProcess "Use readProcess intead" #-}++{- |+Execute a new external binary, replacing this Haskell program in memory and+running the new binary in this program's place. The PID of the process does+not change.++This function does not return.++As with 'readProcess' above, each of the arguments to the new process+must be supplied as individual values in the list. The first argument is the+name of the binary to be executed. The @PATH@ will be searched for the binary+if an absolute path is not given; an exception will be thrown if it is not+found.++(this wraps __unix__'s 'executeFile' machinery, which results in an+/execvp(3)/ standard library function call)++@since 0.6.4+-}+execProcess_ :: [Rope] -> Program τ ()+execProcess_ [] = error "No command provided"+execProcess_ (cmd : args) = do+    context <- ask+    let cmd' = fromRope cmd+    let args' = fmap fromRope args+    let command = mconcat (List.intersperse (singletonRope ' ') (cmd : args))+    let vo = outputSemaphoreFrom context+    let out = outputChannelFrom context+    let vl = telemetrySemaphoreFrom context+    let tel = telemetryChannelFrom context++    debug "command" command++    probe <- liftIO $ do+        findExecutable cmd'+    case probe of+        Nothing -> do+            Safe.throw (CommandNotFound cmd)+        Just _ -> do+            liftIO $ do+                atomically $ do+                    writeTQueue tel Nothing+                    writeTQueue out Nothing++                _ <- forkIO $ do+                    threadDelay 10000000+                    putStrLn "error: Timeout"+                    Safe.throw (ExitFailure 97)++                readMVar vl+                readMVar vo++                -- does not return+                _ <- Posix.executeFile cmd' True args' Nothing+                pure ()  {- | Reset the start time (used to calculate durations shown in event- and