packages feed

cli 0.1.2 → 0.2.0

raw patch · 9 files changed

+361/−221 lines, 9 filesdep +basementdep +foundationdep −QuickCheckdep −directorydep −mtldep ~basenew-component:exe:examplePVP ok

version bump matches the API change (PVP)

Dependencies added: basement, foundation

Dependencies removed: QuickCheck, directory, mtl, tasty, tasty-quickcheck, terminfo, transformers

Dependency ranges changed: base

API changes (from Hackage documentation)

- Console.Display: Black :: Color
- Console.Display: Blue :: Color
- Console.Display: ColorNumber :: Int -> Color
- Console.Display: Cyan :: Color
- Console.Display: Green :: Color
- Console.Display: Magenta :: Color
- Console.Display: Red :: Color
- Console.Display: White :: Color
- Console.Display: Yellow :: Color
- Console.Display: data Color :: *
+ Console.Display: CenterT :: CountOf Char -> String -> OutputElem
+ Console.Display: JustifiedT :: CountOf Char -> String -> OutputElem
+ Console.Display: JustifyCenter :: Justify
+ Console.Display: JustifyJustified :: Justify
+ Console.Display: type ColorComponent = Zn64 8
+ Console.Options: type ValueParser a = String -> Either String a
- Console.Display: Bg :: Color -> OutputElem
+ Console.Display: Bg :: ColorComponent -> OutputElem
- Console.Display: Fg :: Color -> OutputElem
+ Console.Display: Fg :: ColorComponent -> OutputElem
- Console.Display: LeftT :: Int -> String -> OutputElem
+ Console.Display: LeftT :: CountOf Char -> String -> OutputElem
- Console.Display: RightT :: Int -> String -> OutputElem
+ Console.Display: RightT :: CountOf Char -> String -> OutputElem
- Console.Display: columnNew :: Int -> String -> Column
+ Console.Display: columnNew :: CountOf Char -> String -> Column
- Console.Display: displayLn :: TerminalDisplay -> Color -> String -> IO ()
+ Console.Display: displayLn :: TerminalDisplay -> ColorComponent -> String -> IO ()
- Console.Display: displayTextColor :: TerminalDisplay -> Color -> String -> IO ()
+ Console.Display: displayTextColor :: TerminalDisplay -> ColorComponent -> String -> IO ()
- Console.Display: justify :: Justify -> Int -> String -> String
+ Console.Display: justify :: Justify -> CountOf Char -> String -> String
- Console.Display: termText :: String -> TermOutput
+ Console.Display: termText :: String -> String
- Console.Options: FlagOptional :: a -> (ValueParser a) -> FlagParser a
+ Console.Options: FlagOptional :: a -> ValueParser a -> FlagParser a
- Console.Options: FlagRequired :: (ValueParser a) -> FlagParser a
+ Console.Options: FlagRequired :: ValueParser a -> FlagParser a
- Console.Options: OptionSuccess :: Params -> (Action r) -> OptionRes r
+ Console.Options: OptionSuccess :: Params -> Action r -> OptionRes r
- Console.Options: getParams :: Param p => Params -> (forall a. p a -> Ret p a)
+ Console.Options: getParams :: Param p => Params -> forall a. p a -> Ret p a

Files

Console/Display.hs view
@@ -7,6 +7,9 @@ -- -- Displaying utilities --++{-# LANGUAGE NoImplicitPrelude #-}+ module Console.Display     ( TerminalDisplay     -- * Basic@@ -23,7 +26,7 @@     , summary     , summarySet     -- * Attributes-    , Color(..)+    , ColorComponent     , OutputElem(..)     , termText     , justify@@ -37,66 +40,90 @@     , tableAppend     ) where +import Basement.Terminal+import Basement.Terminal.ANSI+import Basement.Types.OffsetSize++import Foundation+import Foundation.Numerical+import Foundation.IO+import Foundation.IO.Terminal+import Foundation.String+import Foundation.Collection++import           System.IO (Handle, hSetBuffering, BufferMode(NoBuffering))+ import           Control.Applicative-import           Control.Monad+import           Control.Monad (when) import           Control.Concurrent.MVar-import           System.Console.Terminfo-import           System.IO-import           Data.List  -- | Element to output text and attributes to the display data OutputElem =-      Bg Color-    | Fg Color+      Bg ColorComponent+    | Fg ColorComponent     | T  String-    | LeftT Int String-    | RightT Int String+    | LeftT (CountOf Char) String      -- ^ Left-aligned text+    | RightT (CountOf Char) String     -- ^ Right-aligned text+    | CenterT (CountOf Char) String    -- ^ Centered text+    | JustifiedT (CountOf Char) String -- ^ Justified text     | NA     deriving (Show,Eq)  -- | Terminal display state-data TerminalDisplay = TerminalDisplay (MVar Bool) Terminal+data TerminalDisplay = TerminalDisplay (MVar Bool) Handle +hPutStr :: Handle -> String -> IO ()+hPutStr h = hPut h . toBytes UTF8++termText :: String -> String+termText = id+ -- | Create a new display displayInit :: IO TerminalDisplay displayInit = do+    initialize     hSetBuffering stdout NoBuffering     cf <- newMVar False-    TerminalDisplay cf <$> setupTermFromEnv+    pure $ TerminalDisplay cf stdout  -- | Display display :: TerminalDisplay -> [OutputElem] -> IO () display tdisp@(TerminalDisplay clearFirst term) oelems = do     cf <- modifyMVar clearFirst $ \cf -> return (False, cf)-    when cf $ runTermOutput term (maybe mempty id clearLineFirst)-    runTermOutput term $ renderOutput tdisp oelems+    when cf $ hPutStr term eraseLineFromCursor+    hPutStr term $ renderOutput tdisp oelems   where-        clearLineFirst = getCapability term clearEOL+        clearLineFirst = eraseLineFromCursor -renderOutput :: TerminalDisplay -> [OutputElem] -> TermOutput-renderOutput (TerminalDisplay _ term) to = mconcat $ map toTermOutput to+renderOutput :: TerminalDisplay -> [OutputElem] -> String+renderOutput (TerminalDisplay _ term) to = mconcat $ fmap toString to   where-        wF = maybe (const mempty) id $ getCapability term setForegroundColor-        wB = maybe (const mempty) id $ getCapability term setBackgroundColor-        rD = maybe mempty         id $ getCapability term restoreDefaultColors+        wF = flip sgrForeground False+        wB = flip sgrBackground False+        rD = sgrReset -        toTermOutput (Fg c) = wF c-        toTermOutput (Bg c) = wB c-        toTermOutput (T t)  = termText t-        toTermOutput (LeftT sz t)  = termText (t ++ replicate (sz - length t) ' ')-        toTermOutput (RightT sz t)  = termText (replicate (sz - length t) ' ' ++ t)-        toTermOutput NA     = rD+        toString (Fg c) = wF c+        toString (Bg c) = wB c+        toString (T t)  = t+        toString (LeftT size t)      = justify JustifyLeft      size t+        toString (RightT size t)     = justify JustifyRight     size t+        toString (CenterT size t)    = justify JustifyCenter    size t+        toString (JustifiedT size t) = justify JustifyJustified size t+        toString NA     = rD  -- | A simple utility that display a @msg@ in @color@-displayTextColor :: TerminalDisplay -> Color -> String -> IO ()-displayTextColor term color msg = do+displayTextColor :: TerminalDisplay -> ColorComponent -> String -> IO ()+displayTextColor term color msg =     display term [Fg color, T msg]  -- | A simple utility that display a @msg@ in @color@ and newline at the end.-displayLn :: TerminalDisplay -> Color -> String -> IO ()-displayLn disp color msg = displayTextColor disp color (msg ++ "\n")+displayLn :: TerminalDisplay -> ColorComponent -> String -> IO ()+displayLn disp color msg = displayTextColor disp color (msg <> "\n") --- | Progress bar widget+white :: ColorComponent+white = 7++-- | A progress bar widget created and used within the `progress` function. data ProgressBar = ProgressBar TerminalDisplay ProgressBackend (MVar ProgressState)  type ProgressBackend = String -> IO ()@@ -120,31 +147,61 @@     , pgCurrent = 0     } --- | Create a new progress bar context-progress :: TerminalDisplay-         -> Int-         -> (ProgressBar -> IO a)-         -> IO a+-- | Create a new progress bar context from a terminal display, a number of+-- items, and a progress update function.+--+-- The progress bar update function should perform the desired actions on each+-- of the items, and call `progressTick` whenever an item is fully processed.+--+-- Each time the given update function calls progressTick the progress bar+-- fills by one item until the given number of items is matched. Once the bar+-- is filled it will not fill any further even if progressTick is called+-- again.+--+-- The progress bar will disappear when the given update function completes+-- running, even if the progress bar is not yet entirely filled.+--+-- For example, the following function will create a progress bar of 100+-- items, and complete one of the items every tenth of a second. Once all of+-- the items are completed the progress bar is removed and a completion String+-- is returned.+--+-- @+-- runProgressBar :: IO String+-- runProgressBar = do+--     terminalDisplay <- displayInit+--     progress terminalDisplay 100 (progressFunction 100)+--   where+--     progressFunction :: Int -> ProgressBar -> IO String+--     progressFunction 0 _   = return "Completed!"+--     progressFunction n bar = do+--       threadDelay 100000+--       progressTick bar+--       progressFunction (n - 1) bar+-- @+progress :: TerminalDisplay       -- ^ The terminal display to display the progress bar on.+         -> Int                   -- ^ The number of items the progress bar represents.+         -> (ProgressBar -> IO a) -- ^ The progression bar update function.+         -> IO a                  -- ^ The results of the progress bar update function. progress tdisp@(TerminalDisplay cf term) numberItems f = do-    let b = backend (getCapability term cursorDown)-                    (getCapability term carriageReturn)-                    (getCapability term clearEOL)+    let b = backend (Just cursorDown)+                    (Just "\r")+                    (Just eraseLineFromCursor)     pbar <- ProgressBar tdisp b <$> newMVar (initProgressState numberItems)      progressStart pbar     a <- f pbar-    displayLn tdisp White ""+    displayLn tdisp white ""     return a   where-    backend :: Maybe (Int -> TermOutput)-            -> Maybe TermOutput-            -> Maybe TermOutput+    backend :: Maybe (Word64 -> String)+            -> Maybe String+            -> Maybe String             -> ProgressBackend     backend _ (Just goHome) (Just clearEol) = \msg -> do-        runTermOutput term $ mconcat [clearEol, termText msg, goHome]+        hPutStr term $ mconcat [clearEol, msg, goHome]         modifyMVar_ cf $ return . const True-    backend _ _ _ = \msg ->-        displayLn tdisp White msg+    backend _ _ _ = displayLn tdisp white  showBar :: ProgressBar -> IO () showBar (ProgressBar _ backend pgsVar) = do@@ -154,26 +211,26 @@   where     getBar (ProgressState lhs rhs maxItems current) =             lhs `sep` bar `sep`-            (show current ++ "/" ++ show maxItems) `sep`+            (show current <> "/" <> show maxItems) `sep`             rhs       where         sep s1 s2             | null s1   = s2             | null s2   = s1-            | otherwise = s1 ++ " " ++ s2+            | otherwise = s1 <> " " <> s2          bar-            | maxItems == current = "[" ++ replicate szMax fillingChar ++ "]"-            | otherwise           = "[" ++ replicate filled fillingChar ++ ">" ++ replicate (unfilled-1) ' ' ++ "]"+            | maxItems == current = "[" <> replicate (CountOf szMax) fillingChar <> "]"+            | otherwise           = "[" <> replicate (CountOf filled) fillingChar <> ">" <> replicate unfilled' ' ' <> "]"          fillingChar = '=' -        unfilled, filled :: Int-        unfilled   = szMax - filled-        filled     = floor numberChar+        unfilled   = CountOf $ szMax - filled+        unfilled'  = CountOf $ szMax - filled - 1+        filled     = roundDown numberChar          numberChar = fromIntegral szMax / currentProgress-        szMax      = 40+        szMax      = 40 :: Int          currentProgress :: Double         currentProgress = fromIntegral maxItems / fromIntegral current@@ -184,7 +241,18 @@     showBar pbar     return () --- | Tick an element on the progress bar+-- | Ticks an element on the given progress bar. Should be used within a+-- progress bar update function that is passed into `progress`.+--+-- @+-- progressFunction :: ProgressBar -> IO String+-- progressFunction bar = do+--   threadDelay 100000+--   progressTick bar+--   threadDelay 200000+--   progressTick bar+--   return "Completed!"+-- @ progressTick :: ProgressBar -> IO () progressTick pbar@(ProgressBar _ _ st) = do     modifyMVar_ st $ \pgs -> return $ pgs { pgCurrent = min (pgMax pgs) (pgCurrent pgs + 1) }@@ -194,40 +262,104 @@ -- | Create a summary summary :: TerminalDisplay -> IO Summary summary tdisp@(TerminalDisplay cf term) = do-    let b = backend (getCapability term cursorDown)-                    (getCapability term carriageReturn)-                    (getCapability term clearEOL)+    let b = backend (Just cursorDown)+                    (Just "\r")+                    (Just eraseLineFromCursor)     return $ Summary b   where-    backend :: Maybe (Int -> TermOutput)-            -> Maybe TermOutput-            -> Maybe TermOutput+    backend :: Maybe (Word64 -> String)+            -> Maybe String+            -> Maybe String             -> SummaryBackend     backend _ (Just goHome) (Just clearEol) = \msg -> do-        runTermOutput term $ mconcat [clearEol, renderOutput tdisp msg, goHome]+        hPutStr term $ mconcat [clearEol, renderOutput tdisp msg, goHome]         modifyMVar_ cf $ return . const True     backend _ _ _ = \msg ->-        runTermOutput term $ mconcat [renderOutput tdisp msg]+        hPutStr term $ mconcat [renderOutput tdisp msg]  -- | Set the summary summarySet :: Summary -> [OutputElem] -> IO ()-summarySet (Summary backend) output = do-    backend output+summarySet (Summary backend) = backend --- | Justify position-data Justify = JustifyLeft | JustifyRight+-- | A justification of text.+data Justify = JustifyLeft       -- ^ Left align text+             | JustifyRight      -- ^ Right align text+             | JustifyCenter     -- ^ Center text+             | JustifyJustified  -- ^ Text fills the whole line width --- | box a string to a specific size, choosing the justification-justify :: Justify -> Int -> String -> String-justify dir sz s-    | sz <= szS = s-    | otherwise =-        let pad = replicate (sz - szS) ' '-         in case dir of-                JustifyLeft  -> pad ++ s-                JustifyRight -> s ++ pad+-- | Boxes a string to a given size using the given justification.+--+-- If the size of the given string is greater than or equal to the given boxing+-- size, then the original string is returned.+--+-- ==== __Examples__+--+-- Basic usage:+--+-- >>> justify JustifyRight 35 "Lorem ipsum dolor sit amet"+-- "Lorem ipsum dolor sit amet         "+--+-- >>> justify JustifyLeft 35 "Lorem ipsum dolor sit amet"+-- "         Lorem ipsum dolor sit amet"+--+-- >>> justify JustifyCenter 35 "Lorem ipsum dolor sit amet"+-- "    Lorem ipsum dolor sit amet     "+--+-- >>> justify JustifyJustified 35 "Lorem ipsum dolor sit amet"+-- "Lorem    ipsum   dolor   sit   amet"+--+-- Apply a justified justification to a one word string, resulting in a string+-- of the given length with the word at the left followed by the remaining+-- space.+--+-- >>> justify JustifyJustified 10 "Hello."+-- "Hello.    "+--+-- Attempt to box a string that is larger than the given box, yielding the+-- original string.+--+-- >>> justify JustifyRight 5 "Hello, World!"+-- "Hello, World!"+justify :: Justify -> CountOf Char -> String -> String+justify justification size string+    | size <= stringSize = string+    | otherwise = case justification of+        JustifyLeft      -> padding <> string+        JustifyRight     -> string <> padding+        JustifyCenter    -> if even sizeDifference+          then halfPadding <> string <>       halfPadding+          else halfPadding <> string <> ' ' `cons` halfPadding+        JustifyJustified -> justifyJustified size string   where-    szS = length s+    padding        = replicate sizeDifference ' '+    halfPadding    = replicate (toCount $ fromCount sizeDifference `div` 2) ' '+    sizeDifference = fromMaybe 0 $ size - stringSize+    stringSize     = length string+    even (CountOf n) = (n `mod` 2) == 0++-- | Justifies the given string so that it takes up the space of the entire+-- given box size.+justifyJustified :: CountOf Char -> String -> String+justifyJustified size string+    | numberOfWords == 1 = string <> replicate (toCount lengthDifference) ' '+    | otherwise          = justifiedString+  where+    justifiedString  = intercalate spaces $ insertExcessSpaces (toCount excessChars) stringWords+    spaces           = replicate (toCount spacing) ' '+    excessChars      = lengthDifference `mod` (numberOfWords - 1)+    spacing          = lengthDifference `div` (numberOfWords - 1)+    lengthDifference = fromCount size - wordsLength+    wordsLength      = foldl' (+) 0 $ fmap (fromCount . length) stringWords+    numberOfWords    = fromCount (length stringWords)+    stringWords      = words string++-- | Inserts excess spaces between words for the process of justifying a+-- string.+insertExcessSpaces :: CountOf Char -> [String] -> [String]+insertExcessSpaces _ []     = []+insertExcessSpaces 0 w      = w+insertExcessSpaces n (x:xs) = (x <> " ") : insertExcessSpaces (fromMaybe 0 $ n - 1) xs+ {- data Attr = Attr     { attrHasSize    :: Maybe Int@@ -281,14 +413,14 @@  -- | Column for a table data Column = Column-    { columnSize    :: Int     -- ^ Size of the column+    { columnSize    :: CountOf Char     -- ^ Size of the column     , columnName    :: String  -- ^ Name of the column. used in 'tableHeaders'     , columnJustify :: Justify -- ^ The justification to use for the cell of this column     , columnWrap    :: Bool    -- ^ if needed to wrap, whether text disappear or use multi lines row (unimplemented)     }  -- | Create a new column setting the right default parameters-columnNew :: Int -> String -> Column+columnNew :: CountOf Char -> String -> Column columnNew n name = Column     { columnSize    = n     , columnName    = name@@ -309,7 +441,7 @@ -- | Show the table headers tableHeaders :: TerminalDisplay -> Table -> IO () tableHeaders td t =-    tableAppend td t $ map columnName $ tColumns t+    tableAppend td t $ columnName <$> tColumns t  -- | Append a row to the table. --@@ -318,15 +450,19 @@ -- elements are dropped. tableAppend :: TerminalDisplay -> Table -> [String] -> IO () tableAppend td (Table cols rowSep) l = do-    let disp = case compare (length l) (length cols) of-                        LT -> zip cols (l ++ replicate (length cols - length l) "")-                        _  -> zip cols l-    mapM_ printColRow $ intersperse Nothing $ map Just disp+    let disp = case compare numelems numcols of+                        LT -> zip cols (l <> replicate (fromMaybe 0 $ numcols - numelems) "")+                        _  -> zip cols l :: [(Column, String)]+    mapM_ printColRow $ intersperse Nothing $ fmap Just disp   where+    numelems = length l+    numcols  = sizeCast Proxy (length cols) :: CountOf String     printColRow Nothing =-        display td [T $ rowSep]+        display td [T rowSep]     printColRow (Just (c, fieldElement)) = do         let oe = case columnJustify c of-                   JustifyLeft  -> RightT (columnSize c) fieldElement-                   JustifyRight -> LeftT (columnSize c) fieldElement+                   JustifyLeft      -> RightT     (columnSize c) fieldElement+                   JustifyRight     -> LeftT      (columnSize c) fieldElement+                   JustifyCenter    -> CenterT    (columnSize c) fieldElement+                   JustifyJustified -> JustifiedT (columnSize c) fieldElement         display td [oe,T "\n"]
Console/Options.hs view
@@ -36,7 +36,6 @@ -- >        putStrLn $ "using flag A : " ++ show (toParam flagA) -- >        putStrLn $ "args: " ++ show (toParam allArgs) ---{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE Rank2Types #-}@@ -66,6 +65,7 @@     , description     , Action     -- * Arguments+    , ValueParser     , FlagParser(..)     , Flag     , FlagLevel@@ -78,7 +78,9 @@     , getParams     ) where -import           Console.Options.Flags hiding (Flag, flagArg)+import Foundation (toList, toCount, fromList)++import           Console.Options.Flags hiding (Flag) import qualified Console.Options.Flags as F import           Console.Options.Nid import           Console.Options.Utils@@ -89,10 +91,9 @@ import           Control.Applicative import           Control.Monad import           Control.Monad.IO.Class-import           Control.Monad.State-import           Control.Monad.Writer  import           Data.List+import           Data.Maybe (fromMaybe) import           Data.Version import           Data.Functor.Identity @@ -151,7 +152,8 @@ -- | same as 'defaultMain', but with the argument defaultMainWith :: OptionDesc (IO ()) () -> [String] -> IO () defaultMainWith dsl args = do-    let (programDesc, res) = parseOptions dsl args+    progrName <- getProgName+    let (programDesc, res) = parseOptions (programName progrName >> dsl) args      in case res of         OptionError s          -> putStrLn s >> exitFailure         OptionHelp             -> help (stMeta programDesc) (stCT programDesc) >> exitSuccess@@ -170,8 +172,8 @@ --helpSubcommand :: [String] -> IO ()  help :: ProgramMeta -> Command (IO ()) -> IO ()-help pmeta (Command hier _ commandOpts _) = mapM_ putStrLn . lines $ snd $ runWriter $ do-    tell (maybe "<program>" id (programMetaName pmeta) ++ " version " ++ maybe "<undefined>" id (programMetaVersion pmeta) ++ "\n")+help pmeta (Command hier _ commandOpts _) = do+    tell (fromMaybe "<program>" (programMetaName pmeta) ++ " version " ++ fromMaybe "<undefined>" (programMetaVersion pmeta) ++ "\n")     tell "\n"     maybe (return ()) (\d -> tell d >> tell "\n\n") (programMetaDescription pmeta)     tell "Options:\n"@@ -182,20 +184,25 @@             tell "\n"             tell "Commands:\n"             let cmdLength = maximum (map (length . fst) subs) + 2-            mapM_ (\(n, c) -> tell $ indent 2 (justify JustifyRight cmdLength n ++ getCommandDescription c ++ "\n")) subs+            forM_ subs $ \(n, c) -> tell $ indent 2 (toList (justify JustifyRight (toCount cmdLength) (fromList n)) ++ getCommandDescription c ++ "\n")             tell "\n"-            mapM_ (printSub 0) subs+            mapM_ (printSub 2) subs         CommandLeaf _    ->             return ()   where+    tell = putStr     printSub iLevel (name, cmdOpt) = do-        tell ("\n" ++ name ++ " options:\n\n")+        tell $ "\nCommand `" ++ name ++ "':\n\n"+        tell $ indent iLevel "Options:\n\n"         mapM_ (tell . printOpt iLevel) (getCommandOptions cmdOpt)         case getCommandHier cmdOpt of-            CommandTree _ -> do-                return ()-            CommandLeaf _ -> do-                return ()+            CommandTree subs -> do+                tell $ indent iLevel "Commands:\n"+                let cmdLength = maximum (map (length . fst) subs) + 2 + iLevel+                forM_ subs $ \(n, c) -> tell $ indent (iLevel + 2) (toList (justify JustifyRight (toCount cmdLength) (fromList n)) ++ getCommandDescription c ++ "\n")+                tell "\n"+                mapM_ (printSub (iLevel + 2)) subs+            CommandLeaf _ -> pure ()         --tell . indent 2 ""      printOpt iLevel fd =@@ -283,6 +290,8 @@ indent n s = replicate n ' ' ++ s  -- | Set the program name+--+-- default is the result of base's `getProgName` programName :: String -> OptionDesc r () programName s = modify $ \st -> st { stMeta = (stMeta st) { programMetaName = Just s } } 
Console/Options/Flags.hs view
@@ -26,6 +26,8 @@ import Data.List import Data.Monoid +import Basement.Compat.Semigroup+ -- | Result of validation of flag value. data FlagArgValidation = FlagArgValid          -- ^ Validation success                        | FlagArgInvalid String -- ^ Validation failed with reason@@ -73,6 +75,11 @@     flat ff  (FlagDescription f) = ff { flagDescription = Just f }     flat acc (FlagMany l)        = foldl' flat acc l +instance Semigroup FlagFrag where+    (<>) (FlagMany l1) (FlagMany l2) = FlagMany (l1 ++ l2)+    (<>) (FlagMany l1) o             = FlagMany (l1 ++ [o])+    (<>) o             (FlagMany l2) = FlagMany (o : l2)+    (<>) o1            o2            = FlagMany [o1,o2] instance Monoid FlagFrag where     mempty                              = FlagMany []     mappend (FlagMany l1) (FlagMany l2) = FlagMany (l1 ++ l2)
Console/Options/Monad.hs view
@@ -14,16 +14,18 @@     , gatherDesc     , getNextID     , getNextIndex+    , modify     ) where  import           Control.Applicative import           Console.Options.Nid import           Console.Options.Types import           Console.Options.Utils-import           Control.Monad.State-import           Control.Monad.Identity import           System.Exit +import Foundation.Monad+import Foundation.Monad.State+ -- | Ongoing State of the program description, as filled by monadic action data ProgramDesc r = ProgramDesc     { stMeta        :: ProgramMeta@@ -45,11 +47,14 @@  -- | Option description Monad newtype OptionDesc r a = OptionDesc { runOptionDesc :: StateT (ProgramDesc r) Identity a }-    deriving (Functor,Applicative,Monad,MonadState (ProgramDesc r))+    deriving (Functor,Applicative,Monad)+instance MonadState (OptionDesc r) where+    type State (OptionDesc r) = ProgramDesc r+    withState f = OptionDesc $ withState f  -- | Run option description gatherDesc :: OptionDesc r a -> ProgramDesc r-gatherDesc dsl = runIdentity $ execStateT (runOptionDesc dsl) initialProgramDesc+gatherDesc dsl = snd $ runIdentity $ runStateT (runOptionDesc dsl) initialProgramDesc  initialProgramDesc :: ProgramDesc r initialProgramDesc = ProgramDesc { stMeta        = programMetaDefault@@ -65,12 +70,15 @@ getNextID :: OptionDesc r Nid getNextID = do     (nid, nidGen) <- nidNext . stNextID <$> get-    modify $ \st -> st { stNextID = nidGen }+    withState $ \st -> ((), st { stNextID = nidGen })     return nid +modify :: (ProgramDesc r -> ProgramDesc r) -> OptionDesc r ()+modify f = withState $ \st -> ((), f st)+ -- | Return the next unique position argument ID getNextIndex :: OptionDesc r UnnamedIndex getNextIndex = do     idx <- stNextIndex <$> get-    modify $ \st -> st { stNextIndex = idx + 1 }+    withState $ \st -> ((), st { stNextIndex = idx + 1 })     return idx
Console/Options/Types.hs view
@@ -29,6 +29,7 @@  import           Console.Options.Flags (FlagDesc) import           Console.Options.Nid+import           Data.Maybe  -- | A unnamed argument data Argument =@@ -116,7 +117,7 @@ instance Param Flag where     type Ret Flag a = Bool     getParams (Params flagArgs _ _) (Flag nid) =-        maybe False (const True) $ lookup nid flagArgs+        isJust $ lookup nid flagArgs instance Param FlagLevel where     type Ret FlagLevel a = Int     getParams (Params flagArgs _ _) (FlagLevel nid) =
README.md view
@@ -46,3 +46,8 @@         putStrLn $ "using flag A : " ++ show (toParam flagA)         putStrLn $ "args: " ++ show (toParam allArgs) ```++License+-------++The source code of cli is available under the [BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause), see `LICENSE` for more information.
cli.cabal view
@@ -1,63 +1,57 @@-Name:                cli-Version:             0.1.2-Synopsis:            Command Line Interface-Description: -    All you need for interacting with users at the Console level-    .-    * Display routines, formatting, progress bars-    .-    * Options parsing-    .-License:             BSD3-License-file:        LICENSE-Copyright:           Vincent Hanquez <vincent@snarc.org>, Nicolas Di Prima <nicolas@di-prima.fr>-Author:              Vincent Hanquez <vincent@snarc.org>, Nicolas Di Prima <nicolas@di-prima.fr>-Maintainer:          vincent@snarc.org-Category:            cli-Stability:           experimental-Build-Type:          Simple-Homepage:            https://github.com/vincenthz/hs-cli-Bug-Reports:         https://github.com/vincenthz/hs-cli/issues-Cabal-Version:       >=1.10-extra-source-files:  README.md--source-repository head-  type: git-  location: https://github.com/vincenthz/hs-cli+cabal-version: 1.12 -Library-  Exposed-modules:   Console.Display-                   , Console.Options-  Other-modules:     Console.Options.Nid-                   , Console.Options.Flags-                   , Console.Options.Monad-                   , Console.Options.Types-                   , Console.Options.Utils-  Build-depends:     base >= 4 && < 5-                   , transformers-                   , mtl-                   , terminfo-  ghc-options:       -Wall -fwarn-tabs -fno-warn-unused-imports-  default-language:  Haskell2010+-- This file has been generated from package.yaml by hpack version 0.31.1.+--+-- see: https://github.com/sol/hpack+--+-- hash: bf0a618f8bd90a826dbcb6f5911cf0add6644863cb2148d8ecc6ae2e07938567 +name:           cli+version:        0.2.0+synopsis:       CLI+description:    please see README.md+category:       cli+homepage:       https://github.com/vincenthz/hs-cli#readme+author:         Vincent Hanquez <vincent@snarc.org>, Nicolas Di Prima <nicolas@di-prima.fr>+maintainer:     vincent@snarc.org+copyright:      Vincent Hanquez <vincent@snarc.org>, Nicolas Di Prima <nicolas@di-prima.fr>+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    LICENSE ---Executable           cli---  Main-Is:           cli.hs--- mtl ghc-options:       -Wall -fno-warn-missing-signatures---  Hs-Source-Dirs:    .---  Build-depends:     base >= 4 && < 5---  default-language:  Haskell2010+library+  exposed-modules:+      Console.Display+      Console.Options+  other-modules:+      Console.Options.Nid+      Console.Options.Flags+      Console.Options.Monad+      Console.Options.Types+      Console.Options.Utils+  hs-source-dirs:+      ./.+  default-extensions: TypeFamilies DataKinds OverloadedStrings+  build-depends:+      base >0 && <10+    , basement+    , foundation+  default-language: Haskell2010 -Test-Suite test-cli-  type:              exitcode-stdio-1.0-  hs-source-dirs:    tests-  Main-is:           Cli.hs-  default-language:  Haskell2010-  Build-Depends:     base >= 3 && < 5-                   , directory-                   , transformers-                   , tasty-                   , tasty-quickcheck-                   , QuickCheck-                   , cli-  ghc-options:       -Wall -fno-warn-orphans -fno-warn-missing-signatures -fwarn-tabs -fno-warn-unused-imports+executable example+  main-is: Simple.hs+  other-modules:+      Paths_cli+  hs-source-dirs:+      examples/+  default-extensions: TypeFamilies DataKinds OverloadedStrings+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >0 && <10+    , basement+    , cli+    , foundation+  default-language: Haskell2010
+ examples/Simple.hs view
@@ -0,0 +1,35 @@+module Main where++import Console.Options+import Data.Char (isDigit)+import Data.Monoid++data MyADT = A | B | C+    deriving (Show,Eq)++main = defaultMain $ do++    programName "my-simple-program"+    programDescription "an optional description of your program that can be found in the help"++    -- a simple boolean flag -v or --verbose+    showFlag <- flag (FlagShort 'v' <> FlagLong "verbose" <> FlagDescription "activate verbose mode")++    -- a flag '-n' or '--name' requiring a string as parameter+    nameFlag <- flagParam (FlagShort 'n' <> FlagLong "name" <> FlagDescription "name of something")+                          (FlagRequired $ \s -> Right s)++    -- a flag 'i' or '--int' requiring an integer as parameter+    valueFlag <- flagParam (FlagShort 'i' <> FlagLong "int" <> FlagDescription "an integer value")+                           (FlagRequired $ \s -> if all isDigit s then Right (read s :: Int) else Left "invalid integer")++    -- a flag with an optional parameter, defaulting to some value+    xyzFlag <- flagParam (FlagShort 'x' <> FlagLong "xyz" <> FlagDescription "some ADT option")+                         (FlagOptional B (\s -> if s == "A" then Right A else if s == "B" then Right B else if s == "C" then Right C else Left "invalid value"))++    +    action $ \toParam -> do+        putStrLn $ show $ toParam showFlag+        putStrLn $ show $ toParam nameFlag+        putStrLn $ show $ toParam valueFlag+        putStrLn $ show $ toParam xyzFlag
− tests/Cli.hs
@@ -1,55 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-module Main (main) where--import           Control.Applicative-import           Control.Monad--import           Test.Tasty-import           Test.Tasty.QuickCheck-import           Test.QuickCheck.Monadic--import           System.Directory-import           Data.List-import           Data.Monoid-import           Data.Functor.Identity--import           Console.Options hiding (defaultMain)--flagA = flagParam (FlagShort 'a' <> FlagLong "aaa") (FlagRequired Right)-flagB = flagParam (FlagShort 'b' <> FlagLong "bbb") (FlagRequired Right)--commandFoo = command "foo" $ do-    action $ \toParam -> return True----commandBar :: Monad m => OptionDesc (m Bool) ()-commandBar :: OptionDesc (Identity Bool) ()-commandBar = command "bar" $ do-    a  <- flagA-    a2 <- argument "arg1" Right-    action $ \toParam -> do-        return True--testParseHelp name f = testProperty name $ runIdentity $ do-    case snd f of-        OptionHelp -> return True-        _          -> return False--testParseSuccess name values f =-    testProperty name $-        let (_,r) = f-         in case r of-                OptionSuccess p act -> runIdentity $ return (sort values == sort (paramsFlags p))-                OptionHelp          -> error $ "got help expected success"-                OptionError s       -> error $ "got error " ++ show s ++ " expected success"-                OptionInvalid s     -> error $ "got invalid " ++ show s ++ " expected success"--main = defaultMain $ testGroup "options"-    [ testGroup "help"-        [ testParseHelp "1" $ parseOptions (commandBar) ["options", "argument", "--help", "a"]-        , testParseHelp "2" $ parseOptions (commandBar) ["options", "argument", "-h", "a"]-        ]-    , testGroup "success"-        [ testParseSuccess "1" [] $ parseOptions (commandBar >> commandFoo) ["bar", "arg1"]-        , testParseSuccess "2" [] $ parseOptions (commandBar >> commandFoo) ["foo", "a"]-        ]-    ]