-- 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.3.1.0 -- | Re-exports of Haskell base and GHC system libraries. module Core.System.Base -- | Lift a computation from the IO monad. liftIO :: MonadIO m => IO a -> m a -- | Monads in which IO computations may be embedded. Any monad -- built by applying a sequence of monad transformers to the IO -- monad will be an instance of this class. -- -- Instances should satisfy the following laws, which state that -- liftIO is a transformer of monads: -- --
class Monad m => MonadIO (m :: Type -> Type) -- | Haskell defines operations to read and write characters from and to -- files, represented by values of type Handle. Each value of -- this type is a handle: a record used by the Haskell run-time -- system to manage I/O with file system objects. A handle has at -- least the following properties: -- ---- test :: IORef [a] -- test = unsafePerformIO $ newIORef [] -- -- main = do -- writeIORef test [42] -- bang <- readIORef test -- print (bang :: [Char]) ---- -- This program will core dump. This problem with polymorphic references -- is well known in the ML community, and does not arise with normal -- monadic use of references. There is no easy way to make it impossible -- once you use unsafePerformIO. Indeed, it is possible to write -- coerce :: a -> b with the help of unsafePerformIO. -- So be careful! unsafePerformIO :: IO a -> a -- | Any type that you wish to throw or catch as an exception must be an -- instance of the Exception class. The simplest case is a new -- exception type directly below the root: -- --
-- data MyException = ThisException | ThatException -- deriving Show -- -- instance Exception MyException ---- -- The default method definitions in the Exception class do what -- we need in this case. You can now throw and catch -- ThisException and ThatException as exceptions: -- --
-- *Main> throw ThisException `catch` \e -> putStrLn ("Caught " ++ show (e :: MyException))
-- Caught ThisException
--
--
-- In more complicated examples, you may wish to define a whole hierarchy
-- of exceptions:
--
-- -- --------------------------------------------------------------------- -- -- Make the root exception type for all the exceptions in a compiler -- -- data SomeCompilerException = forall e . Exception e => SomeCompilerException e -- -- instance Show SomeCompilerException where -- show (SomeCompilerException e) = show e -- -- instance Exception SomeCompilerException -- -- compilerExceptionToException :: Exception e => e -> SomeException -- compilerExceptionToException = toException . SomeCompilerException -- -- compilerExceptionFromException :: Exception e => SomeException -> Maybe e -- compilerExceptionFromException x = do -- SomeCompilerException a <- fromException x -- cast a -- -- --------------------------------------------------------------------- -- -- Make a subhierarchy for exceptions in the frontend of the compiler -- -- data SomeFrontendException = forall e . Exception e => SomeFrontendException e -- -- instance Show SomeFrontendException where -- show (SomeFrontendException e) = show e -- -- instance Exception SomeFrontendException where -- toException = compilerExceptionToException -- fromException = compilerExceptionFromException -- -- frontendExceptionToException :: Exception e => e -> SomeException -- frontendExceptionToException = toException . SomeFrontendException -- -- frontendExceptionFromException :: Exception e => SomeException -> Maybe e -- frontendExceptionFromException x = do -- SomeFrontendException a <- fromException x -- cast a -- -- --------------------------------------------------------------------- -- -- Make an exception type for a particular frontend compiler exception -- -- data MismatchedParentheses = MismatchedParentheses -- deriving Show -- -- instance Exception MismatchedParentheses where -- toException = frontendExceptionToException -- fromException = frontendExceptionFromException ---- -- We can now catch a MismatchedParentheses exception as -- MismatchedParentheses, SomeFrontendException or -- SomeCompilerException, but not other types, e.g. -- IOException: -- --
-- *Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: MismatchedParentheses))
-- Caught MismatchedParentheses
-- *Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: SomeFrontendException))
-- Caught MismatchedParentheses
-- *Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: SomeCompilerException))
-- Caught MismatchedParentheses
-- *Main> throw MismatchedParentheses `catch` \e -> putStrLn ("Caught " ++ show (e :: IOException))
-- *** Exception: MismatchedParentheses
--
class (Typeable e, Show e) => Exception e
toException :: Exception e => e -> SomeException
fromException :: Exception e => SomeException -> Maybe e
-- | Render this exception value in a human-friendly manner.
--
-- Default implementation: show.
displayException :: Exception e => e -> String
-- | The SomeException type is the root of the exception type
-- hierarchy. When an exception of type e is thrown, behind the
-- scenes it is encapsulated in a SomeException.
data SomeException
-- | Synchronously throw the given exception
throw :: (MonadThrow m, Exception e) => e -> m a
-- | Generate a pure value which, when forced, will synchronously throw the
-- given exception
--
-- Generally it's better to avoid using this function and instead use
-- throw, see
-- https://github.com/fpco/safe-exceptions#quickstart
impureThrow :: Exception e => e -> a
-- | Async safe version of bracket
bracket :: MonadMask m => m a -> (a -> m b) -> (a -> m c) -> m c
-- | Same as upstream catch, but will not catch asynchronous
-- exceptions
catch :: (MonadCatch m, Exception e) => m a -> (e -> m a) -> m a
-- | Async safe version of finally
finally :: MonadMask m => m a -> m b -> m a
-- | Re-exports of dependencies from various external libraries.
module Core.System.External
-- | Number of nanoseconds since the Unix epoch.
--
-- The Show instance displays the TimeStamp as seconds with the
-- nanosecond precision expressed as a decimal amount after the interger,
-- ie:
--
-- -- >>> t <- getCurrentTimeNanoseconds -- -- >>> show t -- 2014-07-31T23:09:35.274387031Z ---- -- However this doesn't change the fact the underlying representation -- counts nanoseconds since epoch: -- --
-- >>> show $ unTimeStamp t -- 1406848175274387031 ---- -- There is a Read instance that is reasonably accommodating. -- --
-- >>> read "2014-07-31T13:05:04.942089001Z" :: TimeStamp -- 2014-07-31T13:05:04.942089001Z ---- --
-- >>> read "1406811904.942089001" :: TimeStamp -- 2014-07-31T13:05:04.942089001Z ---- --
-- >>> read "1406811904" :: TimeStamp -- 2014-07-31T13:05:04.000000000Z ---- -- In case you're wondering, the valid range of nanoseconds that fits -- into the underlying Int64 is: -- --
-- >>> show $ minBound :: TimeStamp -- 1677-09-21T00:12:43.145224192Z ---- --
-- >>> show $ maxBound :: TimeStamp -- 2262-04-11T23:47:16.854775807Z ---- -- so in a quarter millenium's time, yes, you'll have the Y2262 Problem. -- Haskell code from today will, of course, still be running, so in the -- mid Twenty-Third century you will need to replace this implementation -- with something else. newtype TimeStamp TimeStamp :: Int64 -> TimeStamp [unTimeStamp] :: TimeStamp -> Int64 -- | Get the current system time, expressed as a TimeStamp (which is -- to say, number of nanoseconds since the Unix epoch). getCurrentTimeNanoseconds :: IO TimeStamp -- | 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 -- | Dig metadata out of the description of your project. -- -- This uses the evil Template Haskell to run code at compile time -- that parses the .cabal file for your Haskell project and -- extracts various meaningful fields. module Core.Program.Metadata -- | Information about the version number of this piece of software and -- other related metadata related to the project it was built from. This -- is supplied to your program when you call configure. This value -- is used if the user requests it by specifying the --version -- option on the command-line. -- -- Simply providing an overloaded string literal such as version -- "1.0" will give you a Version with that value: -- --
-- {-# LANGUAGE OverloadedStrings #-}
--
-- main :: IO ()
-- main = do
-- context <- configure "1.0" None (simple ...
--
--
-- For more complex usage you can populate a Version object using
-- the fromPackage splice below. You can then call various
-- accessors like versionNumberFrom to access individual fields.
data Version
-- | This is a splice which includes key built-time metadata, including the
-- number from the version field from your project's .cabal file
-- (as written by hand or generated from package.yaml).
--
-- While we generally discourage the use of Template Haskell by beginners
-- (there are more important things to learn first) it is a way to
-- execute code at compile time and that is what what we need in order to
-- have the version number extracted from the .cabal file rather
-- than requiring the user to specify (and synchronize) it in multiple
-- places.
--
-- To use this, enable the Template Haskell language extension in your
-- Main.hs file. Then use the special $( ... ) "insert
-- splice here" syntax that extension provides to get a Version
-- object with the desired metadata about your project:
--
--
-- {-# LANGUAGE TemplateHaskell #-}
--
-- version :: Version
-- version = $(fromPackage)
--
-- main :: IO ()
-- main = do
-- context <- configure version None (simple ...
--
--
-- (Using Template Haskell slows down compilation of this file, but the
-- upside of this technique is that it avoids linking the Haskell build
-- machinery into your executable, saving you about 10 MB in the size of
-- the resultant binary)
fromPackage :: Q Exp
versionNumberFrom :: Version -> String
projectNameFrom :: Version -> String
projectSynopsisFrom :: Version -> String
instance Language.Haskell.TH.Syntax.Lift Core.Program.Metadata.Version
instance GHC.Show.Show Core.Program.Metadata.Version
instance Data.String.IsString Core.Program.Metadata.Version
-- | Invoking a command-line program (be it tool or daemon) consists of
-- listing the name of its binary, optionally supplying various options
-- to adjust the behaviour of the program, and then supplying mandatory
-- arguments, if any are specified.
--
-- On startup, we parse any arguments passed in from the shell into
-- name,value pairs and incorporated into the resultant
-- configuration stored in the program's Context.
--
-- Additionally, this module allows you to specify environment variables
-- that, if present, will be incorporated into the stored configuration.
module Core.Program.Arguments
-- | The setup for parsing the command-line arguments of your program. You
-- build a Config with simple or complex, and
-- pass it to configure.
data Config
-- | A completely empty configuration, without the default debugging and
-- logging options. Your program won't process any command-line options
-- or arguments, which would be weird in most cases. Prefer
-- simple.
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 (complex -- [ Global -- [ Option "station-name" Nothing (Value "NAME") [quote| -- Specify an alternate radio station to connect to when performing -- actions. The default is "BBC Radio 1". -- |] -- , Variable "PLAYER_FORCE_HEADPHONES" [quote| -- If set to 1, override the audio subsystem to force output -- to go to the user's headphone jack. -- |] -- ] -- , Command "play" "Play the music." -- [ Option "repeat" Nothing Empty [quote| -- Request that they play the same song over and over and over -- again, simulating the effect of listening to a Top 40 radio -- station. -- |] -- ] -- , Command "rate" "Vote on whether you like the song or not." -- [ Option "academic" Nothing Empty [quote| -- The rating you wish to apply, from A+ to F. This is the -- default, so there is no reason whatsoever to specify this. -- But some people are obsessive, compulsive, and have time on -- their hands. -- |] -- , Option "numeric" Nothing Empty [quote| -- Specify a score as a number from 0 to 100 instead of an -- academic style letter grade. Note that negative values are -- not valid scores, despite how vicerally satisfying that -- would be for music produced in the 1970s. -- |] -- , Option "unicode" (Just 'c') Empty [quote| -- Instead of a score, indicate your rating with a single -- character. This allows you to use emoji, so that you can -- rate a piece '💩', as so many songs deserve. -- |] -- , Argument "score" [quote| -- The rating you wish to apply. -- |] -- ] -- ]) -- -- executeWith context program ---- -- is a program with one global option (in addition to the default ones) -- [and an environment variable] and two commands: play, with -- one option; and rate, with two options and a required -- argument. It also is set up to carry its top-level application state -- around in a type called MusicAppStatus (implementing -- Monoid and so initialized here with mempty. This is a -- good pattern to use given we are so early in the program's lifetime). -- -- The resultant program could be invoked as in these examples: -- --
-- $ ./player --station-name="KBBL-FM 102.5" play -- $ ---- --
-- $ ./player -v rate --numeric 76 -- $ ---- -- For information on how to use the multi-line string literals shown -- here, see quote in Core.Text.Utilities. 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 -> Map LongName ParameterValue -> Parameters
[commandNameFrom] :: Parameters -> Maybe LongName
[parameterValuesFrom] :: Parameters -> Map LongName ParameterValue
[environmentValuesFrom] :: Parameters -> Map LongName ParameterValue
-- | Individual parameters read in off the command-line can either have a
-- value (in the case of arguments and options taking a value) or be
-- empty (in the case of options that are just flags).
data ParameterValue
Value :: String -> ParameterValue
Empty :: ParameterValue
-- | The name of an option, command, or agument (omitting the "--"
-- prefix in the case of options). This identifier will be used to
-- generate usage text in response to --help and by you later
-- when retreiving the values of the supplied parameters after the
-- program has initialized.
--
-- Turn on OverloadedStrings when specifying
-- configurations, obviously.
newtype LongName
LongName :: String -> LongName
-- | Single letter "short" options (omitting the "-" prefix,
-- obviously).
type ShortName = Char
-- | The description of an option, command, or environment variable (for
-- use when rendering usage information in response to --help on
-- the command-line).
type Description = Rope
-- | Declaration of an optional switch or mandatory argument expected by a
-- program.
--
-- Option takes a long name for the option, a short single
-- character abbreviation if offered for convenience, whether or not the
-- option takes a value (and what label to show in help output) and a
-- description for use when displaying usage via --help.
--
-- Argument indicates a mandatory argument and takes the long name
-- used to identify the parsed value from the command-line, and likewise
-- a description for --help output.
--
-- By convention option and argument names are both lower case. If
-- the identifier is two or more words they are joined with a hyphen.
-- Examples:
--
-- -- [ Option "quiet" (Just 'q') Empty "Keep the noise to a minimum." -- , Option "dry-run" Nothing (Value "TIME") "Run a simulation of what would happen at the specified time." -- , Argument "username" "The user to delete from the system." -- ] ---- -- By convention a description is one or more complete sentences -- each of which ends with a full stop. For options that take values, use -- upper case when specifying the label to be used in help output. -- -- Variable declares an environment variable that, if -- present, will be read by the program and stored in its runtime -- context. By convention these are upper case. If the identifier -- is two or more words they are joined with an underscore: -- --
-- [ ... -- , Variable "CRAZY_MODE" "Specify how many crazies to activate." -- , ... -- ] --data Options Option :: LongName -> Maybe ShortName -> ParameterValue -> Description -> Options Argument :: LongName -> Description -> Options Variable :: LongName -> Description -> Options -- | Description of the command-line structure of a program which has -- "commands" (sometimes referred to as "subcommands") representing -- different modes of operation. This is familiar from tools like -- git and docker. data Commands Global :: [Options] -> Commands Command :: LongName -> Description -> [Options] -> Commands -- | Given a program configuration schema and the command-line arguments, -- process them into key/value pairs in a Parameters object. -- -- This results in InvalidCommandLine on the left side if one of -- the passed in options is unrecognized or if there is some other -- problem handling options or arguments (because at that point, we want -- to rabbit right back to the top and bail out; there's no recovering). -- -- This isn't something you'll ever need to call directly; it's exposed -- for testing convenience. This function is invoked when you call -- configure or execute (which calls configure -- with a default Config when initializing). parseCommandLine :: Config -> [String] -> Either InvalidCommandLine Parameters -- | Different ways parsing a simple or complex command-line can fail. data InvalidCommandLine -- | Something was wrong with the way the user specified [usually a short] -- option. InvalidOption :: String -> InvalidCommandLine -- | User specified an option that doesn't match any in the supplied -- configuration. UnknownOption :: String -> InvalidCommandLine -- | Arguments are mandatory, and this one is missing. MissingArgument :: LongName -> InvalidCommandLine -- | Arguments are present we weren't expecting. UnexpectedArguments :: [String] -> InvalidCommandLine -- | In a complex configuration, user specified a command that doesn't -- match any in the configuration. UnknownCommand :: String -> InvalidCommandLine -- | In a complex configuration, user didn't specify a command. NoCommandFound :: InvalidCommandLine -- | In a complex configuration, usage information was requested with -- --help, either globally or for the supplied command. HelpRequest :: Maybe LongName -> InvalidCommandLine -- | Display of the program version requested with --version. VersionRequest :: InvalidCommandLine instance GHC.Classes.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.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 -- | 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 event above but for the (rather common) case of needing -- to inspect or record the value of a variable when debugging code. -- This: -- --
-- setProgramName "hello" -- name <- getProgramName -- debug "programName" name ---- -- will result in -- --
-- 13:05:58Z (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 τ () -- | 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. terminate :: Int -> Program τ α -- | Retrieve the values of parameters parsed from options and arguments -- supplied by the user on the command-line. -- -- The command-line parameters are returned in a Map, mapping from -- from the option or argument name to the supplied value. You can query -- this map directly: -- --
-- program = do -- params <- getCommandLine -- let result = lookupKeyValue "silence" (paramterValuesFrom params) -- case result of -- Nothing -> return () -- Just quiet = case quiet of -- Value _ -> throw NotQuiteRight -- complain that flag doesn't take value -- Empty -> write "You should be quiet now" -- much better -- ... ---- -- which is pattern matching to answer "was this option specified by the -- user?" or "what was the value of this [mandatory] argument?", and then -- "if so, did the parameter have a value?" -- -- This is available should you need to differentiate between a -- Value and an Empty ParameterValue, but for -- many cases as a convenience you can use the lookupOptionFlag, -- lookupOptionValue, and lookupArgument functions below -- (which are just wrappers around a code block like the example shown -- here). getCommandLine :: Program τ Parameters -- | Returns Just True if the option is present, and -- Nothing if it is not. lookupOptionFlag :: LongName -> Parameters -> Maybe Bool -- | Look to see if the user supplied a valued option and if so, what its -- value was. lookupOptionValue :: LongName -> Parameters -> Maybe String -- | Arguments are mandatory, so by the time your program is running a -- value has already been identified. This returns the value for that -- parameter. lookupArgument :: LongName -> Parameters -> Maybe String -- | Look to see if the user supplied the named environment variable and if -- so, return what its value was. lookupEnvironmentValue :: LongName -> Parameters -> Maybe String -- | Get the program name as invoked from the command-line (or as -- overridden by setProgramName). getProgramName :: Program τ Rope -- | Override the program name used for logging, etc. At least, that was -- the idea. Nothing makes use of this at the moment. :/ setProgramName :: Rope -> Program τ () -- | Change the verbosity level of the program's logging output. This -- changes whether event and the debug family of -- functions emit to the logging stream; they do not affect -- writeing to the terminal on the standard output stream. setVerbosityLevel :: Verbosity -> Program τ () -- | 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 wrteR 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. -- --
-- state <- getApplicationState --getApplicationState :: Program τ τ -- | Update the user supplied top-level application state. -- --
-- let state' = state { answer = 42 }
-- setApplicationState state'
--
setApplicationState :: τ -> Program τ ()
-- | Write the supplied Bytes to the given Handle. Note
-- that in contrast to write we don't output a trailing newline.
--
-- -- output h b ---- -- Do not use this to output to stdout as that would -- bypass the mechanism used by the write*, event, and -- debug* functions to sequence output correctly. If you wish to -- write to the terminal use: -- --
-- write (intoRope b) ---- -- (which is not unsafe, but will lead to unexpected results if -- the binary blob you pass in is other than UTF-8 text). 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) -- | A thread for concurrent computation. Haskell uses green threads: small -- lines of work that are scheduled down onto actual execution contexts, -- set by default by this library to be one per core. They are incredibly -- lightweight, and you are encouraged to use them freely. Haskell -- provides a rich ecosystem of tools to do work concurrently and to -- communicate safely between threads -- -- (this wraps async's Async) data Thread α -- | Fork a thread. The child thread will run in the same Context -- as the calling Program, including sharing the user-defined -- application state type. -- -- (this wraps async's async which in turn wraps -- base's forkIO) forkThread :: Program τ α -> Program τ (Thread α) -- | 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 τ () -- | Wait for the completion of a thread, returning the result. This is a -- blocking operation. -- -- (this wraps async's wait) 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 τ () -- | 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 τ () -- | Internal context for a running program. You access this via actions in -- the Program monad. The principal item here is the user-supplied -- top-level application data of type τ which can be retrieved -- with getApplicationState and updated with -- setApplicationState. data Context τ -- | A Program with no user-supplied state to be threaded throughout -- the computation. -- -- The Core.Program.Execute framework makes your top-level -- application state available at the outer level of your process. While -- this is a feature that most substantial programs rely on, it is -- not needed for many simple tasks or when first starting out -- what will become a larger project. -- -- This is effectively the unit type, but this alias is here to clearly -- signal a user-data type is not a part of the program semantics. data None None :: None -- | Illegal internal state resulting from what should be unreachable code -- or otherwise a programmer error. invalid :: Program τ α -- | The Program monad is an instance of MonadIO, which makes -- sense; it's just a wrapper around doing IO and you call it -- using execute from the top-level main action that is -- the entrypoint to any program. So when you need to actually do some -- I/O or interact with other major libraries in the Haskell ecosystem, -- you need to get back to IO and you use liftIO to do it: -- --
-- main :: IO () -- main = execute $ do -- -- now in the Program monad -- write "Hello there" -- -- liftIO $ do -- -- now something in IO -- source <- readFile "hello.c" -- compileSourceCode source -- -- -- back in Program monad -- write "Finished" ---- -- and this is a perfectly reasonable pattern. -- -- Sometimes, however, you want to get to the Program monad from -- there, and that's tricky; you can't just execute a new -- program (and don't try: we've already initialized output and logging -- channels, signal handlers, your application context, etc). -- --
-- main :: IO () -- main = execute $ do -- -- now in the Program monad -- write "Hello there" -- -- liftIO $ do -- -- now something in IO -- source <- readFile "hello.c" -- -- log that we're starting compile ... FIXME how??? -- result <- compileSourceCode source -- case result of -- Right object -> linkObjectCode object -- Left err -> -- debug the error ... FIXME how??? -- -- -- back in Program monad -- write "Finished" ---- -- We have a problem, because 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! -- event "Starting compile..." -- event "Nah. Changed our minds" -- event "Ok, fine, compile the thing" -- -- -- more IO -- result <- compileSourceCode source -- case result of -- Right object -> linkObjectCode object -- Left err -> runProgram (debugS err) -- -- -- back in Program monad -- write "Finished" ---- -- Sometimes Haskell type inference can give you trouble because it tends -- to assume you mean what you say with the last statement of do-notation -- block. If you've got the type wrong you'll get an error, but in an odd -- place, probably at the top where you have the lambda. This can be -- confusing. If you're having trouble with the types try putting -- return () at the end of your subprogram. module Core.Program.Unlift -- | This gives you a function that you can use within your lifted -- IO actions to return to the Program monad. -- -- The type signature of this function is a bit involved, but the example -- below shows that the lambda gives you a function as its -- argument (we recommend you name it runProgram for -- consistency) which gives you a way to run a subprogram, be that a -- single action like writing to terminal or logging, or a larger action -- in a do-notation block: -- --
-- main :: IO () -- main = execute $ do -- withContext $ \runProgram -> do -- -- in IO monad, lifted -- -- (just as if you had used liftIO) -- -- ... -- -- runProgram $ do -- -- now unlifted, back to Program monad -- -- ... ---- -- Think of this as liftIO with an escape hatch. -- -- This function is named withContext because it is a convenience -- around the following pattern: -- --
-- context <- getContext -- liftIO $ do -- ... -- subProgram context $ do -- -- now in Program monad -- ... --withContext :: ((forall β. Program τ β -> IO β) -> IO α) -> Program τ α -- | Get the internal Context of the running Program. -- There is ordinarily no reason to use this; to access your top-level -- application data τ within the Context use -- getApplicationState. getContext :: Program τ (Context τ) -- | Run a subprogram from within a lifted IO block. subProgram :: Context τ -> Program τ α -> IO α -- | 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 -> TimeStamp -> 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 -> TimeStamp [$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 -> Version -> Config -> [Exporter] -> Parameters -> MVar ExitCode -> MVar TimeStamp -> MVar Verbosity -> TQueue (Maybe Rope) -> TQueue (Maybe Datum) -> Forwarder -> MVar Datum -> MVar τ -> Context τ [$sel:programNameFrom:Context] :: Context τ -> MVar Rope [$sel:terminalWidthFrom:Context] :: Context τ -> Int [$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 TimeStamp [$sel:verbosityLevelFrom:Context] :: Context τ -> MVar Verbosity [$sel:outputChannelFrom:Context] :: Context τ -> TQueue (Maybe Rope) [$sel:telemetryChannelFrom:Context] :: Context τ -> TQueue (Maybe Datum) [$sel:telemetryForwarderFrom:Context] :: Context τ -> Forwarder [$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 () emptyForwarder :: Forwarder -- | 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 -- | 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 α