-- 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.6.1.0 -- | Re-exports of Haskell base and GHC system libraries. module Core.System.Base -- | Lift a computation from the IO monad. This allows us to run IO -- computations in any monadic stack, so long as it supports these kinds -- of operations (i.e. IO is the base monad for the stack). -- --
-- import Control.Monad.Trans.State -- from the "transformers" library -- -- printState :: Show s => StateT s IO () -- printState = do -- state <- get -- liftIO $ print state ---- -- Had we omitted liftIO, we would have ended up with -- this error: -- --
-- • Couldn't match type ‘IO’ with ‘StateT s IO’ -- Expected type: StateT s IO () -- Actual type: IO () ---- -- The important part here is the mismatch between StateT s IO -- () and IO (). -- -- Luckily, we know of a function that takes an IO a and -- returns an (m a): liftIO, enabling us to run -- the program and see the expected results: -- --
-- > evalStateT printState "hello" -- "hello" -- -- > evalStateT printState 3 -- 3 --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: -- --
-- 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
-- | Re-exports of dependencies from various external libraries.
module Core.System.External
-- | Re-exports of combinators for use when building Render
-- instances.
module Core.System.Pretty
-- | The abstract data type Doc ann represents pretty
-- documents that have been annotated with data of type ann.
--
-- More specifically, a value of type Doc represents a
-- non-empty set of possible layouts of a document. The layout functions
-- select one of these possibilities, taking into account things like the
-- width of the output document.
--
-- The annotation is an arbitrary piece of data associated with (part of)
-- a document. Annotations may be used by the rendering backends in order
-- to display output differently, such as
--
-- -- >>> putStrLn (show (vsep ["hello", "world"])) -- hello -- world --data Doc ann -- | Overloaded conversion to Doc. -- -- Laws: -- --
-- >>> pretty 1 <+> pretty "hello" <+> pretty 1.234 -- 1 hello 1.234 --pretty :: Pretty a => a -> Doc ann -- |
-- >>> dquote -- " --dquote :: Doc ann -- |
-- >>> squote -- ' --squote :: Doc ann -- |
-- >>> comma -- , --comma :: Doc ann -- | (punctuate p xs) appends p to all but the -- last document in xs. -- --
-- >>> let docs = punctuate comma (Util.words "lorem ipsum dolor sit amet") -- -- >>> putDocW 80 (hsep docs) -- lorem, ipsum, dolor, sit, amet ---- -- The separators are put at the end of the entries, which we can see if -- we position the result vertically: -- --
-- >>> putDocW 20 (vsep docs) -- lorem, -- ipsum, -- dolor, -- sit, -- amet ---- -- If you want put the commas in front of their elements instead of at -- the end, you should use tupled or, in general, -- encloseSep. punctuate :: Doc ann -> [Doc ann] -> [Doc ann] -- | (enclose l r x) encloses document x between -- documents l and r using <>. -- --
-- >>> enclose "A" "Z" "·" -- A·Z ---- --
-- enclose l r x = l <> x <> r --enclose :: Doc ann -> Doc ann -> Doc ann -> Doc ann -- |
-- >>> lbracket -- [ --lbracket :: Doc ann -- |
-- >>> rbracket -- ] --rbracket :: Doc ann -- | (x <+> y) concatenates document x and -- y with a space in between. -- --
-- >>> "hello" <+> "world" -- hello world ---- --
-- x <+> y = x <> space <> y --(<+>) :: Doc ann -> Doc ann -> Doc ann infixr 6 <+> -- |
-- >>> lbrace
-- {
--
lbrace :: Doc ann
-- | -- >>> rbrace -- } --rbrace :: Doc ann -- |
-- >>> lparen -- ( --lparen :: Doc ann -- |
-- >>> rparen -- ) --rparen :: Doc ann -- | The empty document behaves like (pretty ""), so it has -- a height of 1. This may lead to surprising behaviour if we expect it -- to bear no weight inside e.g. vcat, where we get an empty line -- of output from it (parens for visibility only): -- --
-- >>> vsep ["hello", parens emptyDoc, "world"] -- hello -- () -- world ---- -- Together with <>, emptyDoc forms the Monoid -- Doc. emptyDoc :: Doc ann -- | (sep xs) tries laying out the documents xs -- separated with spaces, and if this does not fit the page, -- separates them with newlines. This is what differentiates it from -- vsep, which always lays out its contents beneath each other. -- --
-- >>> let doc = "prefix" <+> sep ["text", "to", "lay", "out"] -- -- >>> putDocW 80 doc -- prefix text to lay out ---- -- With a narrower layout, the entries are separated by newlines: -- --
-- >>> putDocW 20 doc -- prefix text -- to -- lay -- out ---- --
-- sep = group . vsep --sep :: [Doc ann] -> Doc ann -- | (hsep xs) concatenates all documents xs -- horizontally with <+>, i.e. it puts a space -- between all entries. -- --
-- >>> let docs = Util.words "lorem ipsum dolor sit amet" ---- --
-- >>> hsep docs -- lorem ipsum dolor sit amet ---- -- hsep does not introduce line breaks on its own, even -- when the page is too narrow: -- --
-- >>> putDocW 5 (hsep docs) -- lorem ipsum dolor sit amet ---- -- For automatic line breaks, consider using fillSep instead. hsep :: [Doc ann] -> Doc ann -- | (vsep xs) concatenates all documents xs above -- each other. If a group undoes the line breaks inserted by -- vsep, the documents are separated with a space -- instead. -- -- Using vsep alone yields -- --
-- >>> "prefix" <+> vsep ["text", "to", "lay", "out"] -- prefix text -- to -- lay -- out ---- -- grouping a vsep separates the documents with a -- space if it fits the page (and does nothing otherwise). See -- the sep convenience function for this use case. -- -- The align function can be used to align the documents under -- their first element: -- --
-- >>> "prefix" <+> align (vsep ["text", "to", "lay", "out"]) -- prefix text -- to -- lay -- out ---- -- Since grouping a vsep is rather common, sep is a -- built-in for doing that. vsep :: [Doc ann] -> Doc ann -- | (fillCat xs) concatenates documents xs -- horizontally with <> as long as it fits the -- page, then inserts a line' and continues doing that -- for all documents in xs. This is similar to how an ordinary -- word processor lays out the text if you just keep typing after you hit -- the maximum line length. -- -- (line' means that if grouped, the documents are -- separated with nothing instead of newlines. See fillSep if you -- want a space instead.) -- -- Observe the difference between fillSep and fillCat. -- fillSep concatenates the entries spaced when -- grouped: -- --
-- >>> let docs = take 20 (cycle (["lorem", "ipsum", "dolor", "sit", "amet"]))
--
-- >>> putDocW 40 ("Grouped:" <+> group (fillSep docs))
-- Grouped: lorem ipsum dolor sit amet
-- lorem ipsum dolor sit amet lorem ipsum
-- dolor sit amet lorem ipsum dolor sit
-- amet
--
--
-- On the other hand, fillCat concatenates the entries directly
-- when grouped:
--
--
-- >>> putDocW 40 ("Grouped:" <+> group (fillCat docs))
-- Grouped: loremipsumdolorsitametlorem
-- ipsumdolorsitametloremipsumdolorsitamet
-- loremipsumdolorsitamet
--
fillCat :: [Doc ann] -> Doc ann
-- | (fillSep xs) concatenates the documents xs
-- horizontally with <+> as long as it fits the
-- page, then inserts a line and continues doing that for
-- all documents in xs. (line means that if
-- grouped, the documents are separated with a space
-- instead of newlines. Use fillCat if you do not want a
-- space.)
--
-- Let's print some words to fill the line:
--
--
-- >>> let docs = take 20 (cycle ["lorem", "ipsum", "dolor", "sit", "amet"])
--
-- >>> putDocW 80 ("Docs:" <+> fillSep docs)
-- Docs: lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor
-- sit amet lorem ipsum dolor sit amet
--
--
-- The same document, printed at a width of only 40, yields
--
--
-- >>> putDocW 40 ("Docs:" <+> fillSep docs)
-- Docs: lorem ipsum dolor sit amet lorem
-- ipsum dolor sit amet lorem ipsum dolor
-- sit amet lorem ipsum dolor sit amet
--
fillSep :: [Doc ann] -> Doc ann
-- | By default, (flatAlt x y) renders as x.
-- However when grouped, y will be preferred, with
-- x as the fallback for the case when y doesn't fit.
--
-- -- >>> let doc = flatAlt "a" "b" -- -- >>> putDoc doc -- a -- -- >>> putDoc (group doc) -- b -- -- >>> putDocW 0 (group doc) -- a ---- -- flatAlt is particularly useful for defining conditional -- separators such as -- --
-- softline = group (flatAlt hardline " ") ---- --
-- >>> let hello = "Hello" <> softline <> "world!" -- -- >>> putDocW 12 hello -- Hello world! -- -- >>> putDocW 11 hello -- Hello -- world! ---- --
-- >>> let open = flatAlt "" "{ "
--
-- >>> let close = flatAlt "" " }"
--
-- >>> let separator = flatAlt "" "; "
--
-- >>> let prettyDo xs = group ("do" <+> align (encloseSep open close separator xs))
--
-- >>> let statements = ["name:_ <- getArgs", "let greet = \"Hello, \" <> name", "putStrLn greet"]
--
--
-- This is put into a single line with {;} style if it fits:
--
--
-- >>> putDocW 80 (prettyDo statements)
-- do { name:_ <- getArgs; let greet = "Hello, " <> name; putStrLn greet }
--
--
-- When there is not enough space the statements are broken up into lines
-- nicely:
--
-- -- >>> putDocW 10 (prettyDo statements) -- do name:_ <- getArgs -- let greet = "Hello, " <> name -- putStrLn greet ---- --
-- >>> let ugly = group (flatAlt "even wider" "too wide") -- -- >>> putDocW 7 ugly -- even wider ---- -- Also note that group will flatten y: -- --
-- >>> putDoc (group (flatAlt "x" ("y" <> line <> "y")))
-- y y
--
--
-- This also means that an "unflattenable" y which contains a
-- hard linebreak will never be rendered:
--
--
-- >>> putDoc (group (flatAlt "x" ("y" <> hardline <> "y")))
-- x
--
flatAlt :: Doc ann -> Doc ann -> Doc ann
-- | (hcat xs) concatenates all documents xs
-- horizontally with <> (i.e. without any spacing).
--
-- It is provided only for consistency, since it is identical to
-- mconcat.
--
-- -- >>> let docs = Util.words "lorem ipsum dolor" -- -- >>> hcat docs -- loremipsumdolor --hcat :: [Doc ann] -> Doc ann -- | (vcat xs) vertically concatenates the documents -- xs. If it is grouped, the line breaks are removed. -- -- In other words vcat is like vsep, with -- newlines removed instead of replaced by spaces. -- --
-- >>> let docs = Util.words "lorem ipsum dolor" -- -- >>> vcat docs -- lorem -- ipsum -- dolor -- -- >>> group (vcat docs) -- loremipsumdolor ---- -- Since grouping a vcat is rather common, cat is a -- built-in shortcut for it. vcat :: [Doc ann] -> Doc ann -- | Add an annotation to a Doc. This annotation can then -- be used by the renderer to e.g. add color to certain parts of the -- output. For a full tutorial example on how to use it, see the -- Prettyprinter.Render.Tutorials.StackMachineTutorial or -- Prettyprinter.Render.Tutorials.TreeRenderingTutorial modules. -- -- This function is only relevant for custom formats with their own -- annotations, and not relevant for basic prettyprinting. The predefined -- renderers, e.g. Prettyprinter.Render.Text, should be enough for -- the most common needs. annotate :: ann -> Doc ann -> Doc ann -- | Remove all annotations. -- -- Although unAnnotate is idempotent with respect to rendering, -- --
-- unAnnotate . unAnnotate = unAnnotate ---- -- it should not be used without caution, for each invocation traverses -- the entire contained document. If possible, it is preferrable to -- unannotate after producing the layout by using unAnnotateS. unAnnotate :: Doc ann -> Doc xxx -- | The line document advances to the next line and -- indents to the current nesting level. -- --
-- >>> let doc = "lorem ipsum" <> line <> "dolor sit amet" -- -- >>> doc -- lorem ipsum -- dolor sit amet ---- -- line behaves like space if the line -- break is undone by group: -- --
-- >>> group doc -- lorem ipsum dolor sit amet --line :: Doc ann -- | line' is like line, but behaves like -- mempty if the line break is undone by group -- (instead of space). -- --
-- >>> let doc = "lorem ipsum" <> line' <> "dolor sit amet" -- -- >>> doc -- lorem ipsum -- dolor sit amet -- -- >>> group doc -- lorem ipsumdolor sit amet --line' :: Doc ann -- | softline behaves like space if the -- resulting output fits the page, otherwise like line. -- -- Here, we have enough space to put everything in one line: -- --
-- >>> let doc = "lorem ipsum" <> softline <> "dolor sit amet" -- -- >>> putDocW 80 doc -- lorem ipsum dolor sit amet ---- -- If we narrow the page to width 10, the layouter produces a line break: -- --
-- >>> putDocW 10 doc -- lorem ipsum -- dolor sit amet ---- --
-- softline = group line --softline :: Doc ann -- | softline' is like softline, but -- behaves like mempty if the resulting output does not -- fit on the page (instead of space). In other words, -- line is to line' how -- softline is to softline'. -- -- With enough space, we get direct concatenation: -- --
-- >>> let doc = "ThisWord" <> softline' <> "IsWayTooLong" -- -- >>> putDocW 80 doc -- ThisWordIsWayTooLong ---- -- If we narrow the page to width 10, the layouter produces a line break: -- --
-- >>> putDocW 10 doc -- ThisWord -- IsWayTooLong ---- --
-- softline' = group line' --softline' :: Doc ann -- | A hardline is always laid out as a line break, -- even when grouped or when there is plenty of space. Note that -- it might still be simply discarded if it is part of a flatAlt -- inside a group. -- --
-- >>> let doc = "lorem ipsum" <> hardline <> "dolor sit amet" -- -- >>> putDocW 1000 doc -- lorem ipsum -- dolor sit amet ---- --
-- >>> group doc -- lorem ipsum -- dolor sit amet --hardline :: Doc ann -- | (group x) tries laying out x into a single -- line by removing the contained line breaks; if this does not fit the -- page, or when a hardline within x prevents it from -- being flattened, x is laid out without any changes. -- -- The group function is key to layouts that adapt to available -- space nicely. -- -- See vcat, line, or flatAlt for examples that are -- related, or make good use of it. group :: Doc ann -> Doc ann -- | (hang i x) lays out the document x with a -- nesting level set to the current column plus i. -- Negative values are allowed, and decrease the nesting level -- accordingly. -- --
-- >>> let doc = reflow "Indenting these words with hang"
--
-- >>> putDocW 24 ("prefix" <+> hang 4 doc)
-- prefix Indenting these
-- words with
-- hang
--
--
-- This differs from nest, which is based on the current
-- nesting level plus i. When you're not sure, try the more
-- efficient nest first. In our example, this would yield
--
--
-- >>> let doc = reflow "Indenting these words with nest"
--
-- >>> putDocW 24 ("prefix" <+> nest 4 doc)
-- prefix Indenting these
-- words with nest
--
--
-- -- hang i doc = align (nest i doc) --hang :: Int -> Doc ann -> Doc ann -- | (indent i x) indents document x by i -- columns, starting from the current cursor position. -- --
-- >>> let doc = reflow "The indent function indents these words!"
--
-- >>> putDocW 24 ("prefix" <> indent 4 doc)
-- prefix The indent
-- function
-- indents these
-- words!
--
--
--
-- indent i d = hang i ({i spaces} <> d)
--
indent :: Int -> Doc ann -> Doc ann
-- | (nest i x) lays out the document x with the
-- current nesting level (indentation of the following lines) increased
-- by i. Negative values are allowed, and decrease the nesting
-- level accordingly.
--
-- -- >>> vsep [nest 4 (vsep ["lorem", "ipsum", "dolor"]), "sit", "amet"] -- lorem -- ipsum -- dolor -- sit -- amet ---- -- See also -- --
-- concatWith _ [] = mempty -- concatWith (**) [x,y,z] = x ** y ** z ---- -- Multiple convenience definitions based on concatWith are -- already predefined, for example: -- --
-- hsep = concatWith (<+>) -- fillSep = concatWith (\x y -> x <> softline <> y) ---- -- This is also useful to define customized joiners: -- --
-- >>> concatWith (surround dot) ["Prettyprinter", "Render", "Text"] -- Prettyprinter.Render.Text --concatWith :: Foldable t => (Doc ann -> Doc ann -> Doc ann) -> t (Doc ann) -> Doc ann -- | 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 -- | Digging metadata out of the description of your project, and other -- useful helpers. 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 (simpleConfig ...
--
--
-- 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
versionNumberFrom :: Version -> String
projectNameFrom :: Version -> String
projectSynopsisFrom :: Version -> String
-- | 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). This uses
-- the evil TemplateHaskell extension.
--
-- 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 (simpleConfig ...
--
--
-- (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
-- | Access the source location of the call site.
--
-- This is insanely cool, and does not require you to turn on the
-- CPP or TemplateHaskell language extensions!
-- Nevertheless we named it with underscores to compliment the symbols
-- that CPP gives you; the double underscore convention holds
-- across many languages and stands out as a very meta thing, even if it
-- is a proper Haskell value.
--
-- We have a Render instance that simply prints the filename and
-- line number. Doing:
--
-- -- main :: IO () -- main = execute $ do -- writeR __LOCATION__ ---- -- will give you: -- --
-- tests/Snipppet.hs:32 ---- -- This isn't the full stack trace, just information about the current -- line. If you want more comprehensive stack trace you need to add -- HasCallStack constraints everywhere, and then... __LOCATION__ :: HasCallStack => SrcLoc 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 instance Core.Text.Utilities.Render GHC.Stack.Types.SrcLoc -- | 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 simpleConfig or -- complexConfig, 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 -- simpleConfig. blankConfig :: 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 (simpleConfig -- [ 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 informational messages. 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. simpleConfig :: [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 (complexConfig -- [ 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. complexConfig :: [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 -> [String] -> Map LongName ParameterValue -> Parameters
[commandNameFrom] :: Parameters -> Maybe LongName
[parameterValuesFrom] :: Parameters -> Map LongName ParameterValue
[remainingArgumentsFrom] :: Parameters -> [String]
[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. -- -- Remaining is special; it indicates that you are expecting a -- variable number of additional, non-mandatory arguments. This is used -- for programs which take a list of files to process, for example. It'll -- show up in the help with the description you supply alongside. -- --
-- [ ... -- , Remaining "The files you wish to delete permanently." -- , ... -- ] ---- -- 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 Remaining :: 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.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.Classes.Eq Core.Program.Arguments.ParameterValue instance GHC.Show.Show Core.Program.Arguments.ParameterValue instance GHC.Show.Show Core.Program.Arguments.Options instance GHC.Classes.Eq Core.Program.Arguments.Parameters instance GHC.Show.Show Core.Program.Arguments.Parameters instance GHC.Classes.Eq Core.Program.Arguments.InvalidCommandLine instance GHC.Show.Show Core.Program.Arguments.InvalidCommandLine 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 Prettyprinter.Internal.Pretty Core.Program.Arguments.LongName instance Core.Text.Rope.Textual 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. -- --
-- $ kill -USR1 42069 -- $ --module Core.Program.Logging -- | The verbosity level of the output logging subsystem. You can override -- the level specified on the command-line by calling -- setVerbosityLevel from within the Program monad. data Verbosity Output :: Verbosity Verbose :: Verbosity Debug :: Verbosity Internal :: Verbosity -- | 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; also used as a -- heading for subsequent debugging messages. This: -- --
-- info "Starting..." ---- -- will result in -- --
-- 13:05:55Z (00.112) Starting... ---- -- appearing on stdout. 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). info :: Rope -> Program τ () -- | Emit a diagnostic message warning of an off-nominal condition. They -- are best used for unexpected conditions or places where defaults are -- being applied (potentially detrimentally). -- --
-- warn "You left the lights on again" ---- -- Warnings are worthy of note if you are looking into the behaviour of -- the system, and usually—but not always—indicate a problem. That -- problem may not need to be rectified, certainly not immediately. -- -- DO NOT PAGE OPERATIONS STAFF ON WARNINGS. -- -- For example, see Core.Program.Execute's trap_ function, -- a wrapper action which allows you to restart a loop when combined with -- forever. trap_ swollows exceptions but does not -- do so silently, instead using warn to log a warning -- as an information message. You don't need to do anything about the -- warning right away; after all the point is to allow your program to -- continue. If it is happening unexpectly or frequently, however, the -- issue bears investigation and the warning severity message will give -- you a starting point for diagnosis. warn :: Rope -> Program τ () -- | Report an anomoly or condition critical to the ongoing health of the -- program. -- --
-- critical "Unable to do hostname lookups" -- Yup, it was DNS. It's always DNS. ---- -- The term "critical" generally means the program is now in an -- unexpected or invalid state, that further processing is incorrect, and -- that the program is likely about to crash. The key is to get the -- message out to the informational channel as quickly as possible before -- it does. -- -- For example, an uncaught exception bubbling to the top the -- Program monad will be logged as a critical severity -- message and forceibly output to the console before the program exits. -- Your program is crashing, but at least you have a chance to find about -- why before it does. -- -- You're not going to page your operations staff on these either, but if -- they're happening in a production service and it's getting restarted a -- lot as a result you're probably going to be hearing about it. critical :: Rope -> Program τ () -- | Output a debugging message formed from a label and a value. This is -- like info 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 (03.141) programName = hello ---- -- appearing on stdout, assuming these actions executed about -- three seconds after program start. 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 τ () -- | Utility functions for running Program actions concurrently. -- -- 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). Haskell threads 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 module provides wrappers around some of these primatives so you -- can use them easily from the Program monad. -- -- Note that when you fire off a new thread the top-level application -- state is shared; it's the same τ inherited from the -- parent Program. module Core.Program.Threads -- | Create a scope to enclose any subsequently spawned threads as a single -- group. Ordinarily threads launched in Haskell are completely -- indepedent. Creating a scope allows you to operate on a set of threads -- as a single group with bi-directional exception passing. This is the -- basis of an approach called structured concurrency. -- -- When the execution flow exits the scope, any threads that were spawned -- within it that are still running will be killed. -- -- If any of the child threads within the scope throws an exception, the -- other remaining threads will be killed and then the original exception -- will be propegated to this parent thread and re-thrown. createScope :: Program τ α -> Program τ α -- | Fork a thread. The child thread will run in the same Context as -- the calling Program, including sharing the user-defined -- application state value. -- -- If you want to find out what the result of a thread was use -- waitThread on the Thread object returned from this -- function. If you don't need the result, use forkThread_ -- instead. -- -- Threads that are launched off as children are on their own! If the -- code in the child thread throws an exception that is not caught -- within that thread, the exception will kill the thread. Threads dying -- without telling anyone is a bit of an anti-pattern, so this library -- logs a warning-level log message if this happens. -- -- (this wraps base's forkIO) forkThread :: Program τ α -> Program τ (Thread α) -- | Fork a thread with forkThread but do not wait for a result. -- This is on the assumption that the sub program will either be a -- side-effect and over quickly, or long-running daemon thread -- (presumably containing a forever loop in it), never returning. forkThread_ :: Program τ α -> Program τ () -- | Wait for the completion of a thread, returning the result. This is a -- blocking operation. -- -- If the thread you are waiting on throws an exception it will be -- rethrown by waitThread. -- -- If the current thread making this call is cancelled (as a result of -- being on the losing side of concurrentThreads or -- raceThreads for example, or due to the current scope exiting), -- then the thread you are waiting on will be cancelled too. This is -- necessary to ensure that child threads are not leaked if you nest -- forkThreads. waitThread :: Thread α -> Program τ α -- | Wait for the completion of a thread, discarding its result. This is -- particularly useful at the end of a do-block if you're waiting on a -- worker thread to finish but don't need its return value, if any; -- otherwise you have to explicily deal with the unused return value: -- --
-- _ <- waitThread t1 -- return () ---- -- which is a bit tedious. Instead, you can just use this convenience -- function: -- --
-- waitThread_ t1 ---- -- The trailing underscore in the name of this function follows the same -- convetion as found in Control.Monad, which has mapM_ -- which does the same as mapM but which likewise discards the -- return value. waitThread_ :: Thread α -> Program τ () -- | Wait for a thread to complete, returning the result if the computation -- was successful or the exception if one was thrown by the child thread. -- -- This basically is convenience for calling waitThread and -- putting catch around it, but as with all the other -- wait* functions this ensures that if the thread waiting is -- killed the cancellation is propagated to the thread being watched as -- well. waitThread' :: Thread α -> Program τ (Either SomeException α) -- | Wait for many threads to complete. This function is intended for the -- scenario where you fire off a number of worker threads with -- forkThread but rather than leaving them to run independantly, -- you need to wait for them all to complete. -- -- The results of the threads that complete successfully will be returned -- as Right values. Should any of the threads being waited upon -- throw an exception, those exceptions will be returned as Left -- values. -- -- If you don't need to analyse the failures individually, then you can -- just collect the successes using Data.Either's rights: -- --
-- responses <- waitThreads' -- -- info "Aggregating results..." -- combineResults (rights responses) ---- -- Likewise, if you do want to do something with all the failures, -- you might find lefts useful: -- --
-- mapM_ (warn . intoRope . displayException) (lefts responses) ---- -- If the thread calling waitThreads' is cancelled, then all the -- threads being waited upon will also be cancelled. This often occurs -- within a timeout or similar control measure implemented using -- raceThreads_. Should the thread that spawned all the workers -- and is waiting for their results be told to cancel because it lost the -- "race", the child threads need to be told in turn to cancel so as to -- avoid those threads being leaked and continuing to run as zombies. -- This function takes care of that. -- -- (this extends waitThread' to work across a list of Threads, -- taking care to ensure the cancellation behaviour described throughout -- this module) waitThreads' :: [Thread α] -> Program τ [Either SomeException α] -- | Cancel a thread. -- -- (this wraps base's killThread. The underlying mechanism -- used is to throw the ThreadKilled exception to the other -- thread. That exception is asynchronous, so will not be trapped by a -- catch block and will indeed cause the thread receiving the -- exception to come to an end) cancelThread :: Thread α -> Program τ () -- | Fork two threads and wait for both to finish. The return value is the -- pair of each action's return types. -- -- This is the same as calling forkThread and waitThread -- twice, except that if either sub-program fails with an exception the -- other program which is still running will be cancelled and the -- original exception is then re-thrown. -- --
-- (a,b) <- concurrentThreads one two -- -- -- continue, doing something with both results. ---- -- For a variant that ingores the return values and just waits for both -- see concurrentThreads_ below. concurrentThreads :: Program τ α -> Program τ β -> Program τ (α, β) -- | Fork two threads and wait for both to finish. -- -- This is the same as calling forkThread and waitThread_ -- twice, except that if either sub-program fails with an exception the -- other program which is still running will be cancelled and the -- original exception is then re-thrown. concurrentThreads_ :: Program τ α -> Program τ β -> Program τ () -- | Fork two threads and race them against each other. This blocks until -- one or the other of the threads finishes. The return value will be -- Left α if the first program (one) completes -- first, and Right β if it is the second program -- (two) which finishes first. The sub program which is still -- running will be cancelled with an exception. -- --
-- result <- raceThreads one two -- case result of -- Left a -> do -- -- one finished first -- Right b -> do -- -- two finished first ---- -- For a variant that ingores the return value and just races the threads -- see raceThreads_ below. raceThreads :: Program τ α -> Program τ β -> Program τ (Either α β) -- | Fork two threads and race them against each other. When one action -- completes the other will be cancelled with an exception. This is -- useful for enforcing timeouts: -- --
-- raceThreads_ -- (sleepThread 300) -- (do -- -- We expect this to complete within 5 minutes. -- performAction -- ) --raceThreads_ :: Program τ α -> Program τ β -> Program τ () -- | A thread for concurrent computation. -- -- (this wraps base's ThreadId along with a holder for the -- result of the thread) data Thread α -- | Facilities for handling exceptions. -- -- The Haskell language itself doesn't treat exceptions specially, but -- the Haskell runtime does. Any I/O action can result in -- exceptions being thrown and frequently do. Developers can define -- exceptions too, and use them to signal anomolies. -- -- In order to catch an exception you need to know the type of -- that exception. The way this is typically done is with the -- ScopedTypeVariables extension turned on and then adding a -- type annotation around the e variable in the lambda passed to -- catch. -- --
-- catch -- (do -- performSong "This is my party and I'll cry if I want to" -- ) -- (\(e :: FirstWorldProblem) -> do -- critical "Someone is crying" -- debug "e" (displayException e) -- terminate 1 -- ) ---- -- which would work on the assumption that somewhere you have defined: -- --
-- data FirstWorldProblem -- = PersonCrying -- | MyToastIsBurnt -- | SomeoneWrongOnInternet Rope -- deriving Show -- -- instance Exception FirstWorldProblem ---- -- and that the performSong function at some point does -- something like: -- --
-- performSong :: Lyrics -> Program None () -- performSong lyrics = do -- ... -- throw PersonCrying ---- -- Keep in mind that exceptions are really for signalling failure and -- aren't generally that recoverable. Their utility is that they unwind -- the call stack from the point that failure occurs and get you back to -- somewhere you can handle it, but in Haskell "handling it" really just -- means that you log the problem and either go on to processing the next -- request or then outright terminate the program. -- -- Thus a good pattern for using exceptions effectively is to use small -- blocks of pure code which can fail in the type Either -- Rope a, then pattern matching on what you get back: if -- you get a Left back then you throw an exception, -- otherwise you return the value with pure and continue: -- --
-- result <- case calculateInterestingThing inputs of -- Left problem -> throw (SomeoneWrongOnInternet problem) -- Right value -> pure value -- ... ---- -- this works rather nicely especially when you're doing lots of parsing; -- small things that can fail but it's all pure code. In conjunction with -- the Either monad you can quickly work through getting the -- values you need knowing it will fail fast if something goes wrong and -- you can get an appropriate error message back to the surface (in our -- case the Program τ monad) and you can throw -- from there. module Core.Program.Exceptions -- | Catch an exception. -- -- Some care must be taken. Remember that even though it is constrained -- by the Exception typeclass, ε does not stand -- for "any" exception type; is has a concrete type when it gets to being -- used in your code. Things are fairly straight-forward if you know -- exactly the exception you are looking for: -- --
-- catch -- action -- (\(e :: FirstWorldProblem) -> do -- ... -- ) ---- -- but more awkward when you don't. -- -- If you just need to catch all exceptions, the pattern for that is as -- follows: -- --
-- catch -- action -- (\(e :: SomeException) -> do -- ... -- ) ---- -- The SomeException type is the root type of all exceptions; or -- rather, all types that have an instance of Exception can be -- converted into this root type. Thus you can catch all -- synchronous exceptions but you can't tell which type of exception it -- was originally; you rely on the Show instance (which is the -- default that displayException falls back to) to display a -- message which will hopefully be of enough utility to figure out what -- the problem is. In fairness it usually is. (This all seems a bit of a -- deficiency in the underlying exception machinery but it's what we -- have) -- -- This catch function will not catch asynchonous -- exceptions. If you need to do that, see the more comprehensive -- exception handling facilities offered by safe-exceptions, which -- in turn builds on exceptions and base). Note that -- Program implements MonadCatch so you can use the full -- power available there if required. catch :: Exception ε => Program τ α -> (ε -> Program τ α) -> Program τ α -- | Catch an exception. Instead of handling an exception in a supplied -- function, however, return from executing the sub-program with the -- outcome in an Either, with the exception being on the -- Left side if one is thrown. If the sub-program completes -- normally its result is in the Right side. -- -- (this is a wrapper around calling safe-exceptions's try -- function, which in turn wraps exceptions's try, -- which...) try :: Exception ε => Program τ α -> Program τ (Either ε α) -- | Throw an exception. -- -- This will be thrown as a normal synchronous exception that can be -- caught with catch or try above. -- -- Don't try and use this from pure code! A common temptation is to be in -- the middle of a computation, hit a problem, and think "oh, that's bad. -- I guess I'll throw an exception!". You can't. Surface the -- problem back to the I/O level code that Program τ -- monad provides, and then you can throw an exception if -- appropriate. -- -- When you do throw an exception, we recommend you go to some -- trouble to make sure that the string or otherwise descriptive message -- is unique in your codebase. If you do so then when the problem arises -- you will be able to quickly search for that string and find the place -- where the exception arose from, even without the benefit of stack -- traces. For example, -- --
-- throw (SomeoneWrongOnInternet "Ashley thinks there are more than three Star Wars movies") ---- -- which will get you a nice crash message as your world falls down -- around you: -- --
-- 22:54:39Z (00.002) SomeoneWrongOnInternet "Ashley thinks there are more than three Star Wars movies" -- $ ---- -- but if you're in a hurry and don't want to define a local exception -- type to use, -- --
-- throw Boom ---- -- will work. -- -- (experienced users will note that Program implements -- MonadThrow and as such this is just a wrapper around calling -- safe-exceptions's throw function) throw :: Exception ε => ε -> Program τ α -- | Acquire a resource, use it, then release it back. -- -- The bracket pattern is common in Haskell for getting a resource -- ρ needed for a computation, preforming that computation, then -- returning the resource back to the system. Common examples are when -- making database connections and doing file or network operations, -- where you need to make sure you "close" the connection afterward -- before continuing the program so that scare resources like file -- handles are released. -- -- Typically you have an open and close action that return and take a -- resource respectively, so you can use those directly, and use a lambda -- in the third action to actally get at the resource and do something -- with it when you need it: -- --
-- bracket -- (openConnection) -- (closeConnection) -- (c -> do -- this -- thatAndNow -- theOtherThingThatNeeds c -- ) ---- -- Note that bracket does not catch the exception if one is -- thrown! The finalizer will run, but then the exception will continue -- to propogate its way out of your program's call stack. Note also that -- the result of the cleanup action γ is ignored (it can be -- () or anythign else; it will be discarded). bracket :: Program τ ρ -> (ρ -> Program τ γ) -> (ρ -> Program τ α) -> Program τ α -- | Run an action and then, run a finalizer afterwards. The second action -- will run whether or not an exception was raised by the first one. This -- is like bracket above, but can be used when you know you have -- cleanup steps to take after your computation which do have to -- be run even if (especially if!) an exception is thrown but that that -- cleanup doesn't depend on the result of that computation or the -- resources used to do it. The return value γ of the subsequent -- action is ignored. finally :: Program τ α -> Program τ γ -> Program τ α -- | Run an action and then, if an exception was raised (and only if an -- exception was raised), run the second action. The return value -- γ of the subsequent action is is ignored. onException :: Program τ α -> Program τ γ -> Program τ α -- | A utility exception for those occasions when you just need to go -- "boom". -- --
-- case containsKey "James Bond" agents of -- False -> do -- evilPlan -- True -> do -- write "No Mr Bond, I expect you to die!" -- throw Boom --data Boom Boom :: Boom instance GHC.Show.Show Core.Program.Exceptions.Boom instance GHC.Exception.Type.Exception Core.Program.Exceptions.Boom -- | 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. -- -- Encoding is set to UTF-8, working around confusing bugs that sometimes -- occur when applications are running in Docker containers. -- -- 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. One way of dealing -- with this is to put your top-level Program actions in a -- separate modules so you can refer to them from test suites and example -- snippets. -- -- Interoperating with the rest of the Haskell ecosystem -- -- The Program monad is a wrapper over IO; at any point -- when you need to move to another package's entry point, just use -- liftIO. It's re-exported by Core.System.Base for your -- convenience. Later, you might be interested in unlifting back to -- Program; see Core.Program.Unlift. 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. -- This function does not return. 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 queryOptionFlag, -- queryOptionValue, and queryArgument functions below. getCommandLine :: Program τ Parameters -- | Retreive the sub-command mode selected by the user. This assumes your -- program was set up to take sub-commands via complexConfig. -- --
-- mode <- queryCommandName --queryCommandName :: Program τ Rope -- | Returns True if the option is present, and False if -- it is not. -- --
-- program = do -- overwrite <- queryOptionFlag "overwrite" -- ... --queryOptionFlag :: LongName -> Program τ Bool -- | Look to see if the user supplied a valued option and if so, what its -- value was. Use of the LambdaCase extension makes accessing -- the option (and specifying a default if it is absent) reasonably nice: -- --
-- program = do -- region <- queryOptionValue "region" >>= \case -- Nothing -> pure "us-west-2" -- Oregon, not a bad default -- Just value -> pure value ---- -- If you require something other than the text value as entered by the -- user you'll need to do something to parse the returned value and -- convert it to an appropriate type See queryOptionValue' for an -- alternative that does this automatically in many common cases, i.e. -- for options that take numberic values. queryOptionValue :: LongName -> Program τ (Maybe Rope) -- | Look to see if the user supplied a valued option and if so, what its -- value was. This covers the common case of wanting to read a numeric -- argument from an option: -- --
-- program = do -- count <- queryOptionValue' "count" >>= \case -- Nothing -> pure (0 :: Int) -- Just value -> pure value -- ... ---- -- The return type of this function has the same semantics as -- queryOptionValue: if the option is absent you get -- Nothing back (and in the example above we specify a default in -- that case) and Just if a value is present. Unlike the original -- function, however, here we assume success in reading the value! If the -- value is unable to be parsed into the nominated Haskell type using -- parseExternal then an exception with an appropriate error -- message will be thrown—which is what you want if the user specifies -- something that can't be parsed. -- -- Note that the return type is polymorphic so you'll need to ensure the -- concrete type you actually want is specified either via type inference -- or by adding a type annotation somewhere. queryOptionValue' :: Externalize ξ => LongName -> Program τ (Maybe ξ) -- | Arguments are mandatory, so by the time your program is running a -- value has already been identified. This retreives the value for that -- parameter. -- --
-- program = do -- file <- queryArgument "filename" -- ... --queryArgument :: LongName -> Program τ Rope -- | In other applications, you want to gather up the remaining arguments -- on the command-line. You need to have specified Remaining in -- the configuration. -- --
-- program = do -- files <- queryRemaining -- ... --queryRemaining :: Program τ [Rope] -- | Look to see if the user supplied the named environment variable and if -- so, return what its value was. queryEnvironmentValue :: LongName -> Program τ (Maybe Rope) -- | 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 info 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 τ () -- | Retreive the current terminal's width, in characters. -- -- If you are outputting an object with a Render instance then you -- may not need this; you can instead use writeR which is aware of -- the width of your terminal and will reflow (in as much as the -- underlying type's Render instance lets it). getConsoleWidth :: Program τ Int -- | Get the user supplied application state as originally supplied to -- configure and modified subsequntly by replacement with -- setApplicationState. -- --
-- settings <- getApplicationState --getApplicationState :: Program τ τ -- | Update the user supplied top-level application state. -- --
-- let settings' = settings { answer = 42 }
-- setApplicationState settings'
--
setApplicationState :: τ -> Program τ ()
-- | Write the supplied Bytes to the given Handle. Note
-- that in contrast to write we don't output a trailing newline.
--
-- -- outputEntire h b ---- -- Do not use this to output to stdout as that would -- bypass the mechanism used by the write*, info, 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). outputEntire :: Handle -> Bytes -> Program τ () -- | Read the (entire) contents of the specified Handle. inputEntire :: Handle -> Program τ Bytes -- | Execute an external child process and wait for its output and result. -- The command is specified first and and subsequent arguments as -- elements of the list. This helper then logs the command being executed -- to the debug output, which can be useful when you're trying to find -- out what exactly what program is being invoked. -- -- Keep in mind that this isn't invoking a shell; arguments and their -- values have to be enumerated separately: -- --
-- execProcess ["/usr/bin/ssh", "-l", "admin", "203.0.113.42", "\'remote command here\'"] ---- -- having to write out the individual options and arguments and deal with -- escaping is a bit of an annoyance but that's execvp(3) for you. -- -- The return tuple is the exit code from the child process, its entire -- stdout and its entire stderr, if any. Note that this -- is not a streaming interface, 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) execProcess :: [Rope] -> Program τ (ExitCode, Rope, Rope) -- | Pause the current thread for the given number of seconds. For example, -- to delay a second and a half, do: -- --
-- sleepThread 1.5 ---- -- (this wraps base's threadDelay) sleepThread :: Rational -> Program τ () -- | Reset the start time (used to calculate durations shown in event- and -- debug-level logging) held in the Context to zero. This is -- useful if you want to see the elapsed time taken by a specific worker -- rather than seeing log entries relative to the program start time -- which is the default. -- -- If you want to start time held on your main program thread to maintain -- a count of the total elapsed program time, then fork a new thread for -- your worker and reset the timer there. -- --
-- forkThread $ do -- resetTimer -- ... ---- -- then times output in the log messages will be relative to that call to -- resetTimer, not the program start. resetTimer :: Program τ () -- | Trap any exceptions coming out of the given Program action, and -- discard them. The one and only time you want this is inside an endless -- loop: -- --
-- forever $ do -- trap_ -- ( bracket -- obtainResource -- releaseResource -- useResource -- ) ---- -- This function really will swollow expcetions, which means that you'd -- better have handled any synchronous checked errors already with a -- catch and/or have released resources with bracket or -- finally as shown above. -- -- An info level message will be sent to the log channel indicating that -- an uncaught exception was trapped along with a debug level message -- showing the exception text, if any. trap_ :: Program τ α -> Program τ () -- | Catch an exception. -- -- Some care must be taken. Remember that even though it is constrained -- by the Exception typeclass, ε does not stand -- for "any" exception type; is has a concrete type when it gets to being -- used in your code. Things are fairly straight-forward if you know -- exactly the exception you are looking for: -- --
-- catch -- action -- (\(e :: FirstWorldProblem) -> do -- ... -- ) ---- -- but more awkward when you don't. -- -- If you just need to catch all exceptions, the pattern for that is as -- follows: -- --
-- catch -- action -- (\(e :: SomeException) -> do -- ... -- ) ---- -- The SomeException type is the root type of all exceptions; or -- rather, all types that have an instance of Exception can be -- converted into this root type. Thus you can catch all -- synchronous exceptions but you can't tell which type of exception it -- was originally; you rely on the Show instance (which is the -- default that displayException falls back to) to display a -- message which will hopefully be of enough utility to figure out what -- the problem is. In fairness it usually is. (This all seems a bit of a -- deficiency in the underlying exception machinery but it's what we -- have) -- -- This catch function will not catch asynchonous -- exceptions. If you need to do that, see the more comprehensive -- exception handling facilities offered by safe-exceptions, which -- in turn builds on exceptions and base). Note that -- Program implements MonadCatch so you can use the full -- power available there if required. catch :: Exception ε => Program τ α -> (ε -> Program τ α) -> Program τ α -- | Throw an exception. -- -- This will be thrown as a normal synchronous exception that can be -- caught with catch or try above. -- -- Don't try and use this from pure code! A common temptation is to be in -- the middle of a computation, hit a problem, and think "oh, that's bad. -- I guess I'll throw an exception!". You can't. Surface the -- problem back to the I/O level code that Program τ -- monad provides, and then you can throw an exception if -- appropriate. -- -- When you do throw an exception, we recommend you go to some -- trouble to make sure that the string or otherwise descriptive message -- is unique in your codebase. If you do so then when the problem arises -- you will be able to quickly search for that string and find the place -- where the exception arose from, even without the benefit of stack -- traces. For example, -- --
-- throw (SomeoneWrongOnInternet "Ashley thinks there are more than three Star Wars movies") ---- -- which will get you a nice crash message as your world falls down -- around you: -- --
-- 22:54:39Z (00.002) SomeoneWrongOnInternet "Ashley thinks there are more than three Star Wars movies" -- $ ---- -- but if you're in a hurry and don't want to define a local exception -- type to use, -- --
-- throw Boom ---- -- will work. -- -- (experienced users will note that Program implements -- MonadThrow and as such this is just a wrapper around calling -- safe-exceptions's throw function) throw :: Exception ε => ε -> Program τ α -- | Catch an exception. Instead of handling an exception in a supplied -- function, however, return from executing the sub-program with the -- outcome in an Either, with the exception being on the -- Left side if one is thrown. If the sub-program completes -- normally its result is in the Right side. -- -- (this is a wrapper around calling safe-exceptions's try -- function, which in turn wraps exceptions's try, -- which...) try :: Exception ε => Program τ α -> Program τ (Either ε α) -- | 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 τ α -- | A utility exception for those occasions when you just need to go -- "boom". -- --
-- case containsKey "James Bond" agents of -- False -> do -- evilPlan -- True -> do -- write "No Mr Bond, I expect you to die!" -- throw Boom --data Boom Boom :: Boom instance GHC.Show.Show Core.Program.Execute.ProcessProblem instance GHC.Show.Show Core.Program.Execute.QueryParameterError instance GHC.Exception.Type.Exception Core.Program.Execute.QueryParameterError instance GHC.Exception.Type.Exception Core.Program.Execute.ProcessProblem -- | 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 what 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! -- info "Starting compile..." -- info "Nah. Changed our minds" -- info "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 α -- | Helpers for watching files for changes and taking action in the event -- of a change. module Core.Program.Notify -- | Watch for changes to a given list of files. -- -- Before continuing we insert a 100ms pause to allow whatever the editor -- was to finish its write and switcheroo sequence. waitForChange :: [FilePath] -> Program τ () -- | 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 -- | Carrier for spans and events while their data is being accumulated, -- and later sent down the telemetry channel. There is one of these in -- the Program monad's Context. data Datum Datum :: Maybe Span -> Rope -> Maybe Rope -> Time -> Maybe Trace -> Maybe Span -> Maybe Int64 -> Map JsonKey JsonValue -> Datum [$sel:spanIdentifierFrom:Datum] :: Datum -> Maybe Span [$sel:spanNameFrom:Datum] :: Datum -> Rope [$sel:serviceNameFrom:Datum] :: Datum -> Maybe Rope [$sel:spanTimeFrom:Datum] :: Datum -> Time [$sel:traceIdentifierFrom:Datum] :: Datum -> Maybe Trace [$sel:parentIdentifierFrom:Datum] :: Datum -> Maybe Span [$sel:durationFrom:Datum] :: Datum -> Maybe Int64 [$sel:attachedMetadataFrom:Datum] :: Datum -> Map JsonKey JsonValue emptyDatum :: Datum -- | Unique identifier for a trace. If your program is the top of an -- service stack then you can use beginTrace to generate a new -- idenfifier for this request or iteration. More commonly, however, you -- will inherit the trace identifier from the application or service -- which invokes this program or request handler, and you can specify it -- by using usingTrace. newtype Trace Trace :: Rope -> Trace unTrace :: Trace -> Rope -- | Unique identifier for a span. This will be generated by -- encloseSpan but for the case where you are continuing an -- inherited trace and passed the identifier of the parent span you can -- specify it using this constructor. newtype Span Span :: Rope -> Span unSpan :: Span -> Rope -- | 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 τ Context :: MVar Rope -> Int -> Bool -> Version -> Config -> [Exporter] -> Parameters -> MVar ExitCode -> MVar Time -> MVar Verbosity -> TQueue (Maybe Rope) -> TQueue (Maybe Datum) -> Maybe Forwarder -> TVar (Set ThreadId) -> MVar Datum -> MVar τ -> Context τ [$sel:programNameFrom:Context] :: Context τ -> MVar Rope [$sel:terminalWidthFrom:Context] :: Context τ -> Int [$sel:terminalColouredFrom:Context] :: Context τ -> Bool [$sel:versionFrom:Context] :: Context τ -> Version [$sel:initialConfigFrom:Context] :: Context τ -> Config [$sel:initialExportersFrom:Context] :: Context τ -> [Exporter] [$sel:commandLineFrom:Context] :: Context τ -> Parameters [$sel:exitSemaphoreFrom:Context] :: Context τ -> MVar ExitCode [$sel:startTimeFrom:Context] :: Context τ -> MVar Time [$sel:verbosityLevelFrom:Context] :: Context τ -> MVar Verbosity [$sel:outputChannelFrom:Context] :: Context τ -> TQueue (Maybe Rope) [$sel:telemetryChannelFrom:Context] :: Context τ -> TQueue (Maybe Datum) [$sel:telemetryForwarderFrom:Context] :: Context τ -> Maybe Forwarder [$sel:currentScopeFrom:Context] :: Context τ -> TVar (Set ThreadId) [$sel:currentDatumFrom:Context] :: Context τ -> MVar Datum [$sel:applicationDataFrom:Context] :: Context τ -> MVar τ -- | Process the command line options and arguments. If an invalid option -- is encountered or a [mandatory] argument is missing, then the program -- will terminate here. handleCommandLine :: Context τ -> IO (Context τ) handleVerbosityLevel :: Context τ -> IO (MVar Verbosity) handleTelemetryChoice :: Context τ -> IO (Context τ) data Exporter Exporter :: Rope -> (Config -> Config) -> (forall τ. Context τ -> IO Forwarder) -> Exporter [$sel:codenameFrom:Exporter] :: Exporter -> Rope [$sel:setupConfigFrom:Exporter] :: Exporter -> Config -> Config [$sel:setupActionFrom:Exporter] :: Exporter -> forall τ. Context τ -> IO Forwarder -- | Implementation of a forwarder for structured logging of the telemetry -- channel. data Forwarder Forwarder :: ([Datum] -> IO ()) -> Forwarder [$sel:telemetryHandlerFrom:Forwarder] :: Forwarder -> [Datum] -> IO () -- | 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 isNone :: None -> Bool -- | 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 τ) -- | The verbosity level of the output logging subsystem. You can override -- the level specified on the command-line by calling -- setVerbosityLevel from within the Program monad. data Verbosity Output :: Verbosity Verbose :: Verbosity Debug :: Verbosity Internal :: Verbosity -- | 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. One way of dealing -- with this is to put your top-level Program actions in a -- separate modules so you can refer to them from test suites and example -- snippets. -- -- Interoperating with the rest of the Haskell ecosystem -- -- The Program monad is a wrapper over IO; at any point -- when you need to move to another package's entry point, just use -- liftIO. It's re-exported by Core.System.Base for your -- convenience. Later, you might be interested in unlifting back to -- Program; see Core.Program.Unlift. newtype Program τ α Program :: ReaderT (Context τ) IO α -> Program τ α unProgram :: Program τ α -> ReaderT (Context τ) IO α -- | 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 τ) -- | Map a function over the underlying user-data inside the -- Context, changing it from typeτ1 to τ2. fmapContext :: (τ1 -> τ2) -> Context τ1 -> IO (Context τ2) -- | Run a subprogram from within a lifted IO block. subProgram :: Context τ -> Program τ α -> IO α