-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Opinionated Haskell Interoperability -- -- A library to help build command-line programs, both tools and -- longer-running daemons. -- -- A description of this package, a list of features, and some background -- to its design is contained in the README on GitHub. -- -- See Core.Program.Execute to get started. @package core-program @version 0.2.0.0 -- | Re-exports of Haskell base and GHC system libraries. module Core.System.Base -- | Lift a computation from the IO monad. liftIO :: MonadIO m => IO a -> m a -- | Monads in which IO computations may be embedded. Any monad -- built by applying a sequence of monad transformers to the IO -- monad will be an instance of this class. -- -- Instances should satisfy the following laws, which state that -- liftIO is a transformer of monads: -- -- class Monad m => MonadIO (m :: Type -> Type) -- | Haskell defines operations to read and write characters from and to -- files, represented by values of type Handle. Each value of -- this type is a handle: a record used by the Haskell run-time -- system to manage I/O with file system objects. A handle has at -- least the following properties: -- -- -- -- Most handles will also have a current I/O position indicating where -- the next input or output operation will occur. A handle is -- readable if it manages only input or both input and output; -- likewise, it is writable if it manages only output or both -- input and output. A handle is open when first allocated. Once -- it is closed it can no longer be used for either input or output, -- though an implementation cannot re-use its storage while references -- remain to it. Handles are in the Show and Eq classes. -- The string produced by showing a handle is system dependent; it should -- include enough information to identify the handle for debugging. A -- handle is equal according to == only to itself; no attempt is -- made to compare the internal state of different handles for equality. data Handle -- | See openFile data IOMode ReadMode :: IOMode WriteMode :: IOMode AppendMode :: IOMode ReadWriteMode :: IOMode -- | withFile name mode act opens a file using -- openFile and passes the resulting handle to the computation -- act. The handle will be closed on exit from withFile, -- whether by normal termination or by raising an exception. If closing -- the handle raises an exception, then this exception will be raised by -- withFile rather than any exception raised by act. withFile :: () => FilePath -> IOMode -> (Handle -> IO r) -> IO r -- | A handle managing input from the Haskell program's standard input -- channel. stdin :: Handle -- | A handle managing output to the Haskell program's standard output -- channel. stdout :: Handle -- | A handle managing output to the Haskell program's standard error -- channel. stderr :: Handle -- | The action hFlush hdl causes any items buffered for -- output in handle hdl to be sent immediately to the operating -- system. -- -- This operation may fail with: -- -- hFlush :: Handle -> IO () -- | This is the "back door" into the IO monad, allowing IO -- computation to be performed at any time. For this to be safe, the -- IO computation should be free of side effects and independent -- of its environment. -- -- If the I/O computation wrapped in unsafePerformIO performs side -- effects, then the relative order in which those side effects take -- place (relative to the main I/O trunk, or other calls to -- unsafePerformIO) is indeterminate. Furthermore, when using -- unsafePerformIO to cause side-effects, you should take the -- following precautions to ensure the side effects are performed as many -- times as you expect them to be. Note that these precautions are -- necessary for GHC, but may not be sufficient, and other compilers may -- require different precautions: -- -- -- -- It is less well known that unsafePerformIO is not type safe. -- For example: -- --
--   test :: IORef [a]
--   test = unsafePerformIO $ newIORef []
--   
--   main = do
--           writeIORef test [42]
--           bang <- readIORef test
--           print (bang :: [Char])
--   
-- -- This program will core dump. This problem with polymorphic references -- is well known in the ML community, and does not arise with normal -- monadic use of references. There is no easy way to make it impossible -- once you use unsafePerformIO. Indeed, it is possible to write -- coerce :: a -> b with the help of unsafePerformIO. -- So be careful! unsafePerformIO :: () => IO a -> a -- | Any type that you wish to throw or catch as an exception must be an -- instance of the Exception class. The simplest case is a new -- exception type directly below the root: -- --
--   data MyException = ThisException | ThatException
--       deriving Show
--   
--   instance Exception MyException
--   
-- -- The default method definitions in the Exception class do what -- we need in this case. You can now throw and catch -- ThisException and ThatException as exceptions: -- --
--   *Main> throw ThisException `catch` \e -> putStrLn ("Caught " ++ show (e :: MyException))
--   Caught ThisException
--   
-- -- In more complicated examples, you may wish to define a whole hierarchy -- of exceptions: -- --
--   ---------------------------------------------------------------------
--   -- Make the root exception type for all the exceptions in a compiler
--   
--   data SomeCompilerException = forall e . Exception e => SomeCompilerException e
--   
--   instance Show SomeCompilerException where
--       show (SomeCompilerException e) = show e
--   
--   instance Exception SomeCompilerException
--   
--   compilerExceptionToException :: Exception e => e -> SomeException
--   compilerExceptionToException = toException . SomeCompilerException
--   
--   compilerExceptionFromException :: Exception e => SomeException -> Maybe e
--   compilerExceptionFromException x = do
--       SomeCompilerException a <- fromException x
--       cast a
--   
--   ---------------------------------------------------------------------
--   -- Make a subhierarchy for exceptions in the frontend of the compiler
--   
--   data SomeFrontendException = forall e . Exception e => SomeFrontendException e
--   
--   instance Show SomeFrontendException where
--       show (SomeFrontendException e) = show e
--   
--   instance Exception SomeFrontendException where
--       toException = compilerExceptionToException
--       fromException = compilerExceptionFromException
--   
--   frontendExceptionToException :: Exception e => e -> SomeException
--   frontendExceptionToException = toException . SomeFrontendException
--   
--   frontendExceptionFromException :: Exception e => SomeException -> Maybe e
--   frontendExceptionFromException x = do
--       SomeFrontendException a <- fromException x
--       cast a
--   
--   ---------------------------------------------------------------------
--   -- Make an exception type for a particular frontend compiler exception
--   
--   data MismatchedParentheses = MismatchedParentheses
--       deriving Show
--   
--   instance Exception MismatchedParentheses where
--       toException   = frontendExceptionToException
--       fromException = frontendExceptionFromException
--   
-- -- We can now catch a MismatchedParentheses exception as -- MismatchedParentheses, SomeFrontendException or -- SomeCompilerException, but not other types, e.g. -- IOException: -- --
--   *Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: MismatchedParentheses))
--   Caught MismatchedParentheses
--   *Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: SomeFrontendException))
--   Caught MismatchedParentheses
--   *Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: SomeCompilerException))
--   Caught MismatchedParentheses
--   *Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: IOException))
--   *** Exception: MismatchedParentheses
--   
class (Typeable e, Show e) => Exception e toException :: Exception e => e -> SomeException fromException :: Exception e => SomeException -> Maybe e -- | Render this exception value in a human-friendly manner. -- -- Default implementation: show. displayException :: Exception e => e -> String -- | The SomeException type is the root of the exception type -- hierarchy. When an exception of type e is thrown, behind the -- scenes it is encapsulated in a SomeException. data SomeException -- | Synchronously throw the given exception throw :: (MonadThrow m, Exception e) => e -> m a -- | Generate a pure value which, when forced, will synchronously throw the -- given exception -- -- Generally it's better to avoid using this function and instead use -- throw, see -- https://github.com/fpco/safe-exceptions#quickstart impureThrow :: Exception e => e -> a -- | Async safe version of bracket bracket :: MonadMask m => m a -> (a -> m b) -> (a -> m c) -> m c -- | Same as upstream catch, but will not catch asynchronous -- exceptions catch :: (MonadCatch m, Exception e) => m a -> (e -> m a) -> m a -- | Async safe version of finally finally :: MonadMask m => m a -> m b -> m a -- | Re-exports of dependencies from various external libraries. module Core.System.External -- | Number of nanoseconds since the Unix epoch. -- -- The Show instance displays the TimeStamp as seconds with the -- nanosecond precision expressed as a decimal amount after the interger, -- ie: -- --
--   >>> t <- getCurrentTimeNanoseconds
--   
--   >>> show t
--   2014-07-31T23:09:35.274387031Z
--   
-- -- However this doesn't change the fact the underlying representation -- counts nanoseconds since epoch: -- --
--   >>> show $ unTimeStamp t
--   1406848175274387031
--   
-- -- There is a Read instance that is reasonably accommodating. -- --
--   >>> read "2014-07-31T13:05:04.942089001Z" :: TimeStamp
--   2014-07-31T13:05:04.942089001Z
--   
-- --
--   >>> read "1406811904.942089001" :: TimeStamp
--   2014-07-31T13:05:04.942089001Z
--   
-- --
--   >>> read "1406811904" :: TimeStamp
--   2014-07-31T13:05:04.000000000Z
--   
-- -- In case you're wondering, the valid range of nanoseconds that fits -- into the underlying Int64 is: -- --
--   >>> show $ minBound :: TimeStamp
--   1677-09-21T00:12:43.145224192Z
--   
-- --
--   >>> show $ maxBound :: TimeStamp
--   2262-04-11T23:47:16.854775807Z
--   
-- -- so in a quarter millenium's time, yes, you'll have the Y2262 Problem. -- Haskell code from today will, of course, still be running, so in the -- mid Twenty-Third century you will need to replace this implementation -- with something else. newtype TimeStamp TimeStamp :: Int64 -> TimeStamp [unTimeStamp] :: TimeStamp -> Int64 -- | Get the current system time, expressed as a TimeStamp (which is -- to say, number of nanoseconds since the Unix epoch). getCurrentTimeNanoseconds :: IO TimeStamp -- | Common elements from the rest of the Haskell ecosystem. This is mostly -- about re-exports. There are numerous types and functions that are more -- or less assumed to be in scope when you're doing much of anything in -- Haskell; this module is a convenience to pull in the ones we rely on -- for the rest of this library. -- -- You can just import this directly: -- --
--   import Core.System
--   
-- -- as there's no particular benefit to cherry-picking the various -- sub-modules. module Core.System -- | Dig metadata out of the description of your project. -- -- This uses the evil Template Haskell to run code at compile time -- that parses the .cabal file for your Haskell project and -- extracts various meaningful fields. module Core.Program.Metadata -- | Information about the version number of this piece of software and -- other related metadata related to the project it was built from. This -- is supplied to your program when you call configure. This value -- is used if the user requests it by specifying the --version -- option on the command-line. -- -- Simply providing an overloaded string literal such as version -- "1.0" will give you a Version with that value: -- --
--   {-# LANGUAGE OverloadedStrings #-}
--   
--   main :: IO ()
--   main = do
--       context <- configure "1.0" None (simple ...
--   
-- -- For more complex usage you can populate a Version object using -- the fromPackage splice below. You can then call various -- accessors like versionNumberFrom to access individual fields. data Version -- | This is a splice which includes key built-time metadata, including the -- number from the version field from your project's .cabal file -- (as written by hand or generated from package.yaml). -- -- While we generally discourage the use of Template Haskell by beginners -- (there are more important things to learn first) it is a way to -- execute code at compile time and that is what what we need in order to -- have the version number extracted from the .cabal file rather -- than requiring the user to specify (and synchronize) it in multiple -- places. -- -- To use this, enable the Template Haskell language extension in your -- Main.hs file. Then use the special $( ... ) "insert -- splice here" syntax that extension provides to get a Version -- object with the desired metadata about your project: -- --
--   {-# LANGUAGE TemplateHaskell #-}
--   
--   version :: Version
--   version = $(fromPackage)
--   
--   main :: IO ()
--   main = do
--       context <- configure version None (simple ...
--   
-- -- (Using Template Haskell slows down compilation of this file, but the -- upside of this technique is that it avoids linking the Haskell build -- machinery into your executable, saving you about 10 MB in the size of -- the resultant binary) fromPackage :: Q Exp versionNumberFrom :: Version -> String projectNameFrom :: Version -> String projectSynopsisFrom :: Version -> String instance Language.Haskell.TH.Syntax.Lift Core.Program.Metadata.Version instance GHC.Show.Show Core.Program.Metadata.Version instance Data.String.IsString Core.Program.Metadata.Version -- | Invoking a command-line program (be it tool or daemon) consists of -- listing the name of its binary, optionally supplying various options -- to adjust the behaviour of the program, and then supplying mandatory -- arguments, if any are specified. -- -- On startup, we parse any arguments passed in from the shell into -- name,value pairs and incorporated into the resultant -- configuration stored in the program's Context. -- -- Additionally, this module allows you to specify environment variables -- that, if present, will be incorporated into the stored configuration. module Core.Program.Arguments -- | The setup for parsing the command-line arguments of your program. You -- build a Config with simple or complex, and pass -- it to configure. data Config -- | A completely empty configuration, without the default debugging and -- logging options. Your program won't process any command-line options -- or arguments, which would be weird in most cases. Prefer -- simple. blank :: Config -- | Declare a simple (as in normal) configuration for a program with any -- number of optional parameters and mandatory arguments. For example: -- --
--   main :: IO ()
--   main = do
--       context <- configure "1.0" None (simple
--           [ Option "host" (Just 'h') Empty [quote|
--               Specify an alternate host to connect to when performing the
--               frobnication. The default is "localhost".
--             |]
--           , Option "port" (Just 'p') Empty [quote|
--               Specify an alternate port to connect to when frobnicating.
--             |]
--           , Option "dry-run" Nothing (Value "TIME") [quote|
--               Perform a trial run at the specified time but don't actually
--               do anything.
--             |]
--           , Option "quiet" (Just 'q') Empty [quote|
--               Supress normal output.
--             |]
--           , Argument "filename" [quote|
--               The file you want to frobnicate.
--             |]
--           ])
--   
--       executeWith context program
--   
-- -- which, if you build that into an executable called snippet -- and invoke it with --help, would result in: -- --
--   $ ./snippet --help
--   Usage:
--   
--       snippet [OPTIONS] filename
--   
--   Available options:
--   
--     -h, --host     Specify an alternate host to connect to when performing the
--                    frobnication. The default is "localhost".
--     -p, --port     Specify an alternate port to connect to when frobnicating.
--         --dry-run=TIME
--                    Perform a trial run at the specified time but don't
--                    actually do anything.
--     -q, --quiet    Supress normal output.
--     -v, --verbose  Turn on event tracing. By default the logging stream will go
--                    to standard output on your terminal.
--         --debug    Turn on debug level logging. Implies --verbose.
--   
--   Required arguments:
--   
--     filename       The file you want to frobnicate.
--   $ |
--   
-- -- For information on how to use the multi-line string literals shown -- here, see quote in Core.Text.Utilities. simple :: [Options] -> Config -- | 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 that will be honoured by the program. Each -- command can have a list of local options and arguments as needed. For -- example: -- --
--   program :: Program MusicAppStatus ()
--   program = ...
--   
--   main :: IO ()
--   main = do
--       context <- configure (fromPackage version) mempty (complex
--           [ Global
--               [ Option "station-name" Nothing (Value "NAME") [quote|
--                   Specify an alternate radio station to connect to when performing
--                   actions. The default is "BBC Radio 1".
--                 |]
--               , Variable "PLAYER_FORCE_HEADPHONES" [quote|
--                   If set to 1, override the audio subsystem to force output
--                   to go to the user's headphone jack.
--                 |]
--               ]
--           , Command "play" "Play the music."
--               [ Option "repeat" Nothing Empty [quote|
--                   Request that they play the same song over and over and over
--                   again, simulating the effect of listening to a Top 40 radio
--                   station.
--                 |]
--               ]
--           , Command "rate" "Vote on whether you like the song or not."
--               [ Option "academic" Nothing Empty [quote|
--                   The rating you wish to apply, from A+ to F. This is the
--                   default, so there is no reason whatsoever to specify this.
--                   But some people are obsessive, compulsive, and have time on
--                   their hands.
--                 |]
--               , Option "numeric" Nothing Empty [quote|
--                   Specify a score as a number from 0 to 100 instead of an
--                   academic style letter grade. Note that negative values are
--                   not valid scores, despite how vicerally satisfying that
--                   would be for music produced in the 1970s.
--                 |]
--               , Option "unicode" (Just 'c') Empty [quote|
--                   Instead of a score, indicate your rating with a single
--                   character.  This allows you to use emoji, so that you can
--                   rate a piece '💩', as so many songs deserve.
--                 |]
--               , Argument "score" [quote|
--                   The rating you wish to apply.
--                 |]
--               ]
--           ])
--   
--       executeWith context program
--   
-- -- is a program with one global option (in addition to the default ones) -- [and an environment variable] and two commands: play, with -- one option; and rate, with two options and a required -- argument. It also is set up to carry its top-level application state -- around in a type called MusicAppStatus (implementing -- Monoid and so initialized here with mempty. This is a -- good pattern to use given we are so early in the program's lifetime). -- -- The resultant program could be invoked as in these examples: -- --
--   $ ./player --station-name="KBBL-FM 102.5" play
--   $
--   
-- --
--   $ ./player -v rate --numeric 76
--   $
--   
-- -- For information on how to use the multi-line string literals shown -- here, see quote in Core.Text.Utilities. complex :: [Commands] -> Config -- | Result of having processed the command-line and the environment. You -- get at the parsed command-line options and arguments by calling -- getCommandLine within a Program block. -- -- Each option and mandatory argument parsed from the command-line is -- either standalone (in the case of switches and flags, such as -- --quiet) or has an associated value. In the case of options -- the key is the name of the option, and for arguments it is the -- implicit name specified when setting up the program. For example, in: -- --
--   $ ./submit --username=gbmh GraceHopper_Resume.pdf
--   
-- -- the option has parameter name "username" and value -- "gmbh"; the argument has parameter name "filename" (assuming -- that is what was declared in the Argument entry) and a value -- being the Admiral's CV. This would be returned as: -- --
--   Parameters Nothing [("username","gbmh"), ("filename","GraceHopper_Resume.pdf")] []
--   
-- -- The case of a complex command such as git or stack, you -- get the specific mode chosen by the user returned in the first -- position: -- --
--   $ missiles launch --all
--   
-- -- would be parsed as: -- --
--   Parameters (Just "launch") [("all",Empty)] []
--   
data Parameters Parameters :: Maybe LongName -> Map LongName ParameterValue -> Map LongName ParameterValue -> Parameters [commandNameFrom] :: Parameters -> Maybe LongName [parameterValuesFrom] :: Parameters -> Map LongName ParameterValue [environmentValuesFrom] :: Parameters -> Map LongName ParameterValue -- | Individual parameters read in off the command-line can either have a -- value (in the case of arguments and options taking a value) or be -- empty (in the case of options that are just flags). data ParameterValue Value :: String -> ParameterValue Empty :: ParameterValue -- | The name of an option, command, or agument (omitting the "--" -- prefix in the case of options). This identifier will be used to -- generate usage text in response to --help and by you later -- when retreiving the values of the supplied parameters after the -- program has initialized. -- -- Turn on OverloadedStrings when specifying -- configurations, obviously. newtype LongName LongName :: String -> LongName -- | Single letter "short" options (omitting the "-" prefix, -- obviously). type ShortName = Char -- | The description of an option, command, or environment variable (for -- use when rendering usage information in response to --help on -- the command-line). type Description = Rope -- | Declaration of an optional switch or mandatory argument expected by a -- program. -- -- Option takes a long name for the option, a short single -- character abbreviation if offered for convenience, whether or not the -- option takes a value (and what label to show in help output) and a -- description for use when displaying usage via --help. -- -- Argument indicates a mandatory argument and takes the long name -- used to identify the parsed value from the command-line, and likewise -- a description for --help output. -- -- By convention option and argument names are both lower case. If -- the identifier is two or more words they are joined with a hyphen. -- Examples: -- --
--   [ Option "quiet" (Just 'q') Empty "Keep the noise to a minimum."
--   , Option "dry-run" Nothing (Value "TIME") "Run a simulation of what would happen at the specified time."
--   , Argument "username" "The user to delete from the system."
--   ]
--   
-- -- By convention a description is one or more complete sentences -- each of which ends with a full stop. For options that take values, use -- upper case when specifying the label to be used in help output. -- -- Variable declares an environment variable that, if -- present, will be read by the program and stored in its runtime -- context. By convention these are upper case. If the identifier -- is two or more words they are joined with an underscore: -- --
--   [ ...
--   , Variable "CRAZY_MODE" "Specify how many crazies to activate."
--   , ...
--   ]
--   
data Options Option :: LongName -> Maybe ShortName -> ParameterValue -> Description -> Options Argument :: LongName -> Description -> Options Variable :: LongName -> Description -> Options -- | 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 and docker. data Commands Global :: [Options] -> Commands Command :: LongName -> Description -> [Options] -> Commands -- | Given a program configuration schema and the command-line arguments, -- process them into key/value pairs in a Parameters object. -- -- This results in InvalidCommandLine on the left side if one of -- the passed in options is unrecognized or if there is some other -- problem handling options or arguments (because at that point, we want -- to rabbit right back to the top and bail out; there's no recovering). -- -- This isn't something you'll ever need to call directly; it's exposed -- for testing convenience. This function is invoked when you call -- configure or execute (which calls configure -- with a default Config when initializing). parseCommandLine :: Config -> [String] -> Either InvalidCommandLine Parameters -- | Different ways parsing a simple or complex command-line can fail. data InvalidCommandLine -- | Something was wrong with the way the user specified [usually a short] -- option. InvalidOption :: String -> InvalidCommandLine -- | User specified an option that doesn't match any in the supplied -- configuration. UnknownOption :: String -> InvalidCommandLine -- | Arguments are mandatory, and this one is missing. MissingArgument :: LongName -> InvalidCommandLine -- | Arguments are present we weren't expecting. UnexpectedArguments :: [String] -> InvalidCommandLine -- | In a complex configuration, user specified a command that doesn't -- match any in the configuration. UnknownCommand :: String -> InvalidCommandLine -- | In a complex configuration, user didn't specify a command. NoCommandFound :: InvalidCommandLine -- | In a complex configuration, usage information was requested with -- --help, either globally or for the supplied command. HelpRequest :: Maybe LongName -> InvalidCommandLine -- | Display of the program version requested with --version. VersionRequest :: InvalidCommandLine instance GHC.Classes.Eq Core.Program.Arguments.InvalidCommandLine instance GHC.Show.Show Core.Program.Arguments.InvalidCommandLine instance GHC.Classes.Eq Core.Program.Arguments.Parameters instance GHC.Show.Show Core.Program.Arguments.Parameters instance GHC.Classes.Eq Core.Program.Arguments.ParameterValue instance GHC.Show.Show Core.Program.Arguments.ParameterValue instance GHC.Classes.Ord Core.Program.Arguments.LongName instance Data.Hashable.Class.Hashable Core.Program.Arguments.LongName instance GHC.Classes.Eq Core.Program.Arguments.LongName instance Data.String.IsString Core.Program.Arguments.LongName instance GHC.Show.Show Core.Program.Arguments.LongName instance GHC.Exception.Type.Exception Core.Program.Arguments.InvalidCommandLine instance Data.String.IsString Core.Program.Arguments.ParameterValue instance Core.Data.Structures.Key Core.Program.Arguments.LongName instance Data.Text.Prettyprint.Doc.Internal.Pretty Core.Program.Arguments.LongName -- | Output and Logging from your program. -- -- Broadly speaking, there are two kinds of program: console tools -- invoked for a single purpose, and long-running daemons that -- effectively run forever. -- -- Tools tend to be run to either have an effect (in which case they tend -- not to a say much of anything) or to report a result. This tends to be -- written to "standard output"—traditionally abbreviated in code as -- stdout—which is usually printed to your terminal. -- -- Daemons, on the other hand, don't write their output to file -- descriptor 1; rather they tend to respond to requests by writing to -- files, replying over network sockets, or sending up smoke signals -- (ECPUTOOHOT, in case you're curious). What daemons do -- output, however, is log messages. -- -- While there are many sophisticated logging services around that you -- can interact with directly, from the point of view of an individual -- program these tend to have faded away and have become more an -- aspect of the Infrastructure- or Platform-as-a-Service you're running -- on. Over the past few years containerization mechanisms like -- docker, then more recently container orchestration layers like -- kubernetes, have generally simply captured programs' standard -- output as if it were the program's log output and then sent -- that down external logging channels to whatever log analysis system is -- available. Even programs running locally under systemd or -- similar tend to follow the same pattern; services write to -- stdout and that output, as "logs", ends up being fed to the -- system journal. -- -- So with that in mind, in your program you will either be outputting -- results to stdout or not writing there at all, and you will -- either be describing extensively what your application is up to, or -- not at all. -- -- There is also a "standard error" file descriptor available. We -- recommend not using it. At best it is unclear what is written to -- stderr and what isn't; at worse it is lost as many -- environments in the wild discard stderr entirely. To avoid -- this most of the time people just combine them in the invoking shell -- with 2>&1, which inevitably results in stderr -- text appearing in the middle of normal stdout lines -- corrupting them. -- -- The original idea of standard error was to provde a way to report -- adverse conditions without interrupting normal text output, but as we -- have just observed if it happens without context or out of order there -- isn't much point. Instead this library offers a mechanism which caters -- for the different kinds of output in a unified, safe manner. -- --

Three kinds of output/logging messages

-- -- Standard output -- -- Your program's normal output to the terminal. This library provides -- the write (and writeS and writeR) functions to -- send output to stdout. -- -- Events -- -- When running a tool, you sometimes need to know what it is -- doing as it is carrying out its steps. The event function -- allows you to emit descriptive messages to the log channel tracing the -- activities of your program. -- -- Ideally you would never need to turn this on in a command-line tool, -- but sometimes a user or operations engineer needs to see what an -- application is up to. These should be human readable status messages -- to convey a sense of progress. -- -- In the case of long-running daemons, event can be used to -- describe high-level lifecycle events, to document individual requests, -- or even describing individual transitions in a request handler's state -- machine, all depending on the nature of your program. -- -- Debugging -- -- Programmers, on the other hand, often need to see the internal state -- of the program when debugging. -- -- You almost always you want to know the value of some variable or -- parameter, so the debug (and debugS and debugR) -- utility functions here send messages to the log channel prefixed with -- a label that is, by convention, the name of the value you are -- examining. -- -- The important distinction here is that such internal values are almost -- never useful for someone other than the person or team who wrote the -- code emitting it. Operations engineers might be asked by developers to -- turn on --debuging and report back the results; but a user of -- your program is not going to do that in and of themselves to solve a -- problem. -- --

Single output channel

-- -- It is the easy to make the mistake of having multiple subsystems -- attempting to write to stdout and these outputs corrupting -- each other, especially in a multithreaded language like Haskell. The -- output actions described here send all output to terminal down a -- single thread-safe channel. Output will be written in the order it was -- executed, and (so long as you don't use the stdout Handle -- directly yourself) your terminal output will be sound. -- -- Passing --verbose on the command-line of your program will -- cause event to write its tracing messages to the terminal. This -- shares the same output channel as the write* functions -- and will not cause corruption of your program's normal output. -- -- Passing --debug on the command-line of your program will -- cause the debug* actions to write their debug-level -- messages to the terminal. This shares the same output channel as above -- and again will not cause corruption of your program's normal output. -- --

Logging channel

-- -- Event and debug messages are internally also sent to a "logging -- channel", as distinct from the "output" one. This would allow -- us to send them directly to a file, syslog, or network logging -- service, but this is as-yet unimplemented. module Core.Program.Logging -- | Write the supplied text to stdout. -- -- This is for normal program output. -- --
--   write "Beginning now"
--   
write :: Rope -> Program τ () -- | Call show on the supplied argument and write the resultant text -- to stdout. -- -- (This is the equivalent of print from base) writeS :: Show α => α -> Program τ () -- | Pretty print the supplied argument and write the resultant text to -- stdout. This will pass the detected terminal width to the -- render function, resulting in appopriate line wrapping when -- rendering your value. writeR :: Render α => α -> Program τ () -- | Note a significant event, state transition, status, or debugging -- message. This: -- --
--   event "Starting..."
--   
-- -- will result in -- --
--   13:05:55Z (0000.001) Starting...
--   
-- -- appearing on stdout and the message being sent down the logging -- channel. The output string is current time in UTC, and time elapsed -- since startup shown to the nearest millisecond (our timestamps are to -- nanosecond precision, but you don't need that kind of resolution in in -- ordinary debugging). -- -- Messages sent to syslog will be logged at Info level -- severity. event :: Rope -> Program Ï„ () -- | Output a debugging message formed from a label and a value. This is -- like event above but for the (rather common) case of needing to -- inspect or record the value of a variable when debugging code. This: -- --
--   setProgramName "hello"
--   name <- getProgramName
--   debug "programName" name
--   
-- -- will result in -- --
--   13:05:58Z (0003.141) programName = hello
--   
-- -- appearing on stdout and the message being sent down the logging -- channel, assuming these actions executed about three seconds after -- program start. -- -- Messages sent to syslog will be logged at Debug level -- severity. debug :: Rope -> Rope -> Program τ () -- | Convenience for the common case of needing to inspect the value of a -- general variable which has a Show instance debugS :: Show α => Rope -> α -> Program τ () -- | Convenience for the common case of needing to inspect the value of a -- general variable for which there is a Render instance and so -- can pretty print the supplied argument to the log. This will pass the -- detected terminal width to the render function, resulting in -- appopriate line wrapping when rendering your value (if logging to -- something other than console the default width of 80 will be -- applied). debugR :: Render α => Rope -> α -> Program τ () -- | Embelish a Haskell command-line program with useful behaviours. -- -- Runtime -- -- Sets number of capabilities (heavy-weight operating system threads -- used by the GHC runtime to run Haskell green threads) to the number of -- CPU cores available (for some reason the default is 1 capability only, -- which is a bit silly on a multicore system). -- -- Install signal handlers to properly terminate the program performing -- cleanup as necessary. -- -- Logging and output -- -- The Program monad provides functions for both normal output and -- debug logging. A common annoyance when building command line tools and -- daemons is getting program output to stdout and debug -- messages interleaved, made even worse when error messages written to -- stderr land in the same console. To avoid this, when all -- output is sent through a single channel. This includes both normal -- output and log messages. -- -- Exceptions -- -- Ideally your code should handle (and not leak) exceptions, as is good -- practice anywhere in the Haskell ecosystem. As a measure of last -- resort however, if an exception is thrown (and not caught) by your -- program it will be caught at the outer execute entrypoint, -- logged for debugging, and then your program will exit. -- -- Customizing the execution context -- -- The execute function will run your Program in a basic -- Context initialized with appropriate defaults. Most settings -- can be changed at runtime, but to specify the allowed command-line -- options and expected arguments you can initialize your program using -- configure and then run with executeWith. module Core.Program.Execute -- | The type of a top-level program. -- -- You would use this by writing: -- --
--   module Main where
--   
--   import Core.Program
--   
--   main :: IO ()
--   main = execute program
--   
-- -- and defining a program that is the top level of your application: -- --
--   program :: Program None ()
--   
-- -- Such actions are combinable; you can sequence them (using bind in -- do-notation) or run them in parallel, but basically you should need -- one such object at the top of your application. -- -- Type variables -- -- A Program has a user-supplied application state and a return -- type. -- -- The first type variable, τ, is your application's state. This -- is an object that will be threaded through the computation and made -- available to your code in the Program monad. While this is a -- common requirement of the outer code layer in large programs, it is -- often not necessary in small programs or when starting new -- projects. You can mark that there is no top-level application state -- required using None and easily change it later if your needs -- evolve. -- -- The return type, α, is usually unit as this effectively being -- called directly from main and Haskell programs have type -- IO (). That is, they don't return anything; I/O having -- already happened as side effects. -- -- Programs in separate modules -- -- One of the quirks of Haskell is that it is difficult to refer to code -- in the Main module when you've got a number of programs kicking around -- in a project each with a main function. So you're best off -- putting your top-level Program actions in a separate modules so -- you can refer to them from test suites and example snippets. data Program τ α -- | Initialize the programs's execution context. This takes care of -- various administrative actions, including setting up output channels, -- parsing command-line arguments (according to the supplied -- configuration), and putting in place various semaphores for internal -- program communication. See Core.Program.Arguments for details. -- -- This is also where you specify the initial {blank, empty, default) -- value for the top-level user-defined application state, if you have -- one. Specify None if you aren't using this feature. configure :: Version -> τ -> Config -> IO (Context τ) -- | Embelish a program with useful behaviours. See module header -- Core.Program.Execute for a detailed description. Internally -- this function calls configure with an appropriate default when -- initializing. execute :: Program None α -> IO () -- | Embelish a program with useful behaviours, supplying a configuration -- for command-line options & argument parsing and an initial value -- for the top-level application state, if appropriate. executeWith :: Context τ -> Program τ α -> IO () -- | Safely exit the program with the supplied exit code. Current output -- and debug queues will be flushed, and then the process will terminate. terminate :: Int -> Program τ α -- | Retrieve the values of parameters parsed from options and arguments -- supplied by the user on the command-line. -- -- The command-line parameters are returned in a Map, mapping from -- from the option or argument name to the supplied value. You can query -- this map directly: -- --
--   program = do
--       params <- getCommandLine
--       let result = lookupKeyValue "silence" (paramterValuesFrom params)
--       case result of
--           Nothing -> return ()
--           Just quiet = case quiet of
--               Value _ -> throw NotQuiteRight               -- complain that flag doesn't take value
--               Empty   -> write "You should be quiet now"   -- much better
--       ...
--   
-- -- which is pattern matching to answer "was this option specified by the -- user?" or "what was the value of this [mandatory] argument?", and then -- "if so, did the parameter have a value?" -- -- This is available should you need to differentiate between a -- Value and an Empty ParameterValue, but for -- many cases as a convenience you can use the lookupOptionFlag, -- lookupOptionValue, and lookupArgument functions below -- (which are just wrappers around a code block like the example shown -- here). getCommandLine :: Program Ï„ Parameters -- | Returns Just True if the option is present, and -- Nothing if it is not. lookupOptionFlag :: LongName -> Parameters -> Maybe Bool -- | Look to see if the user supplied a valued option and if so, what its -- value was. lookupOptionValue :: LongName -> Parameters -> Maybe String -- | Arguments are mandatory, so by the time your program is running a -- value has already been identified. This returns the value for that -- parameter. lookupArgument :: LongName -> Parameters -> Maybe String -- | Get the program name as invoked from the command-line (or as -- overridden by setProgramName). getProgramName :: Program Ï„ Rope -- | Override the program name used for logging, etc. At least, that was -- the idea. Nothing makes use of this at the moment. :/ setProgramName :: Rope -> Program Ï„ () -- | Change the verbosity level of the program's logging output. This -- changes whether event and the debug family of functions -- emit to the logging stream; they do not affect writeing -- to the terminal on the standard output stream. setVerbosityLevel :: Verbosity -> Program Ï„ () -- | Get the user supplied application state as originally supplied to -- configure and modified subsequntly by replacement with -- setApplicationState. -- --
--   state <- getApplicationState
--   
getApplicationState :: Program Ï„ Ï„ -- | Update the user supplied top-level application state. -- --
--   let state' = state { answer = 42 }
--   setApplicationState state'
--   
setApplicationState :: Ï„ -> Program Ï„ () -- | Alias for getApplicationState. retrieve :: Program Ï„ Ï„ -- | Alias for setApplicationState. update :: Ï„ -> Program Ï„ () -- | Write the supplied Bytes to the given Handle. Note -- that in contrast to write we don't output a trailing newline. -- --
--   output h b
--   
-- -- Do not use this to output to stdout as that would -- bypass the mechanism used by the write*, event, and -- debug* functions to sequence output correctly. If you wish to -- write to the terminal use: -- --
--   write (intoRope b)
--   
-- -- (which is not unsafe, but will lead to unexpected results if -- the binary blob you pass in is other than UTF-8 text). output :: Handle -> Bytes -> Program τ () -- | Read the (entire) contents of the specified Handle. input :: Handle -> Program τ Bytes -- | A thread for concurrent computation. Haskell uses green threads: small -- lines of work that are scheduled down onto actual execution contexts, -- set by default by this library to be one per core. They are incredibly -- lightweight, and you are encouraged to use them freely. Haskell -- provides a rich ecosystem of tools to do work concurrently and to -- communicate safely between threads -- -- (this wraps async's Async) data Thread α -- | Fork a thread. The child thread will run in the same Context -- as the calling Program, including sharing the user-defined -- application state type. -- -- (this wraps async's async which in turn wraps -- base's forkIO) fork :: Program τ α -> Program τ (Thread α) -- | Pause the current thread for the given number of seconds. For example, -- to delay a second and a half, do: -- --
--   sleep 1.5
--   
-- -- (this wraps base's threadDelay) sleep :: Rational -> Program τ () -- | Internal context for a running program. You access this via actions in -- the Program monad. The principal item here is the user-supplied -- top-level application data of type τ which can be retrieved -- with getApplicationState and updated with -- setApplicationState. data Context τ -- | A Program with no user-supplied state to be threaded throughout -- the computation. -- -- The Core.Program.Execute framework makes your top-level -- application state available at the outer level of your process. While -- this is a feature that most substantial programs rely on, it is -- not needed for many simple tasks or when first starting out -- what will become a larger project. -- -- This is effectively the unit type, but this alias is here to clearly -- signal a user-data type is not a part of the program semantics. data None None :: None -- | Illegal internal state resulting from what should be unreachable code -- or otherwise a programmer error. invalid :: Program τ α -- | The Program monad is an instance of MonadIO, which makes -- sense; it's just a wrapper around doing IO and you call it -- using execute from the top-level main action that is -- the entrypoint to any program. So when you need to actually do some -- I/O or interact with other major libraries in the Haskell ecosystem, -- you need to get back to IO and you use liftIO to do it: -- --
--   main :: IO ()
--   main = execute $ do
--       -- now in the Program monad
--       write "Hello there"
--   
--       liftIO $ do
--           -- now something in IO
--           source <- readFile "hello.c"
--           compileSourceCode source
--   
--       -- back in Program monad
--       write "Finished"
--   
-- -- and this is a perfectly reasonable pattern. -- -- Sometimes, however, you want to get to the Program monad from -- there, and that's tricky; you can't just execute a new -- program (and don't try: we've already initialized output and logging -- channels, signal handlers, your application context, etc). -- --
--   main :: IO ()
--   main = execute $ do
--       -- now in the Program monad
--       write "Hello there"
--   
--       liftIO $ do
--           -- now something in IO
--           source <- readFile "hello.c"
--           -- log that we're starting compile      ... FIXME how???
--           result <- compileSourceCode source
--           case result of
--               Right object -> linkObjectCode object
--               Left err     -> -- debug the error  ... FIXME how???
--   
--       -- back in Program monad
--       write "Finished"
--   
-- -- We have a problem, because we'd like to do is use, say, debug -- to log the compiler error, but we have no way to unlift back out of -- IO to get to the Program monad. -- -- To workaround this, we offer withContext. It gives you a -- function that you can then use within your lifted IO to run a -- (sub)Program action: -- --
--   main :: IO ()
--   main = execute $ do
--       -- now in the Program monad
--       write "Hello there"
--   
--       withContext $ \runProgram -> do
--           -- now lifted to IO
--           source <- readFile "hello.c"
--   
--           runProgram $ do
--               -- now "unlifted" back to Program monad!
--               event "Starting compile..."
--               event "Nah. Changed our minds"
--               event "Ok, fine, compile the thing"
--   
--           -- more IO
--           result <- compileSourceCode source
--           case result of
--               Right object -> linkObjectCode object
--               Left err     -> runProgram (debugS err)
--   
--       -- back in Program monad
--       write "Finished"
--   
-- -- Sometimes Haskell type inference can give you trouble because it tends -- to assume you mean what you say with the last statement of do-notation -- block. If you've got the type wrong you'll get an error, but in an odd -- place, probably at the top where you have the lambda. This can be -- confusing. If you're having trouble with the types try putting -- return () at the end of your subprogram. module Core.Program.Unlift -- | This gives you a function that you can use within your lifted -- IO actions to return to the Program monad. -- -- The type signature of this function is a bit involved, but the example -- below shows that the lambda gives you a function as its -- argument (we recommend you name it runProgram for -- consistency) which gives you a way to run a subprogram, be that a -- single action like writing to terminal or logging, or a larger action -- in a do-notation block: -- --
--   main :: IO ()
--   main = execute $ do
--       withContext $ \runProgram -> do
--           -- in IO monad, lifted
--           -- (just as if you had used liftIO)
--   
--           ...
--   
--           runProgram $ do
--               -- now unlifted, back to Program monad
--   
--           ...
--   
-- -- Think of this as liftIO with an escape hatch. -- -- This function is named withContext because it is a convenience -- around the following pattern: -- --
--   context <- getContext
--   liftIO $ do
--       ...
--       subProgram context $ do
--           -- now in Program monad
--       ...
--   
withContext :: ((forall β. Program τ β -> IO β) -> IO α) -> Program τ α -- | Get the internal Context of the running Program. -- There is ordinarily no reason to use this; to access your top-level -- application data τ within the Context use -- getApplicationState. getContext :: Program τ (Context τ) -- | Run a subprogram from within a lifted IO block. subProgram :: Context τ -> Program τ α -> IO α -- | Support for building command-line programs, ranging from simple tools -- to long-running daemons. -- -- This is intended to be used directly: -- --
--   import Core.Program
--   
-- -- the submodules are mostly there to group documentation. module Core.Program