commander-cli 0.6.2.0 → 0.7.0.0
raw patch · 4 files changed
+169/−132 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Options.Commander: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Options.Commander.CommanderT state m)
- Options.Commander: instance Control.Monad.Trans.Class.MonadTrans (Options.Commander.CommanderT state)
- Options.Commander: instance GHC.Base.Functor m => GHC.Base.Functor (Options.Commander.CommanderT state m)
- Options.Commander: instance GHC.Base.Monad m => GHC.Base.Alternative (Options.Commander.CommanderT state m)
- Options.Commander: instance GHC.Base.Monad m => GHC.Base.Applicative (Options.Commander.CommanderT state m)
- Options.Commander: instance GHC.Base.Monad m => GHC.Base.Monad (Options.Commander.CommanderT state m)
- Options.Commander: instance Options.Commander.Unrender a => Options.Commander.Unrender (GHC.Maybe.Maybe a)
+ Control.Monad.Commander: Action :: (state -> m (CommanderT state m a, state)) -> CommanderT state m a
+ Control.Monad.Commander: Defeat :: CommanderT state m a
+ Control.Monad.Commander: Victory :: a -> CommanderT state m a
+ Control.Monad.Commander: data CommanderT state m a
+ Control.Monad.Commander: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Control.Monad.Commander.CommanderT state m)
+ Control.Monad.Commander: instance Control.Monad.Trans.Class.MonadTrans (Control.Monad.Commander.CommanderT state)
+ Control.Monad.Commander: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Monad.Commander.CommanderT state m)
+ Control.Monad.Commander: instance GHC.Base.Monad m => GHC.Base.Alternative (Control.Monad.Commander.CommanderT state m)
+ Control.Monad.Commander: instance GHC.Base.Monad m => GHC.Base.Applicative (Control.Monad.Commander.CommanderT state m)
+ Control.Monad.Commander: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Monad.Commander.CommanderT state m)
+ Control.Monad.Commander: runCommanderT :: Monad m => CommanderT state m a -> state -> m (Maybe a)
Files
- Test.hs +22/−1
- commander-cli.cabal +2/−2
- src/Control/Monad/Commander.hs +141/−0
- src/Options/Commander.hs +4/−129
Test.hs view
@@ -29,7 +29,7 @@ >> envTest >> envOptTest >> envOptDefTest-+ >> eitherSwitchTest rawProg :: ProgramT Raw IO Bool rawProg = raw (pure True) @@ -128,3 +128,24 @@ testMaybeBool =<< fmap not <$> test (envOptDefProg "POP" (== ("POOP" :: String))) (State mempty mempty mempty) setEnv "CORPUS" "POOP" testMaybeBool =<< test (envOptDefProg "POP" (== ("POOP" :: String))) (State mempty mempty mempty)++eitherSwitchProg :: (x -> Bool) -> (y -> Bool) -> ProgramT (Arg "xy" (Either x y) & Raw) IO (Either Bool Bool)+eitherSwitchProg xpred ypred = arg \case { Left x -> raw (pure (Left $ xpred x)); Right y -> raw (pure (Right $ ypred y)) }++eitherSwitchTest :: IO ()+eitherSwitchTest = do+ let example = eitherSwitchProg (== (10 :: Int)) (== ("Hello" :: String))+ let ploof a c = runCommanderT (run example) (State a mempty mempty) >>= c+ ploof ["10"] \case+ Just (Left True) -> pure ()+ _ -> exitFailure+ ploof ["Hello"] \case+ Just (Right True) -> pure ()+ _ -> exitFailure+ ploof ["Poop"] \case+ Just (Right False) -> pure ()+ _ -> exitFailure+ ploof [] \case+ Nothing -> pure ()+ _ -> exitFailure+
commander-cli.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.4 name: commander-cli-version: 0.6.2.0+version: 0.7.0.0 synopsis: A command line argument/option parser library built around a monadic metaphor description: A command line argument/option parser library built around a monadic metaphor. homepage: https://github.com/SamuelSchlesinger/commander-cli@@ -15,7 +15,7 @@ extra-source-files: CHANGELOG.md, README.md library- exposed-modules: Options.Commander+ exposed-modules: Options.Commander, Control.Monad.Commander other-extensions: ViewPatterns, DerivingVia, StandaloneDeriving,
+ src/Control/Monad/Commander.hs view
@@ -0,0 +1,141 @@+{-# LANGUAGE DeriveFunctor #-}+{- |+Module: Control.Monad.Commander+Description: A monad for stateful, backtracking computations+Copyright: (c) Samuel Schlesinger 2020+License: MIT+Maintainer: sgschlesinger@gmail.com+Stability: experimental+Portability: POSIX, Windows+-}+module Control.Monad.Commander (+ -- ** The CommanderT Monad+ {- |+ The 'CommanderT' monad is how your CLI programs are interpreted by 'run'.+ It has the ability to backtrack and it maintains some state.+ -}+ CommanderT(Action, Defeat, Victory), runCommanderT,+) where++import Control.Monad (ap)+import Control.Monad.Trans (MonadTrans, lift, liftIO, MonadIO)+import Control.Applicative (Alternative(empty, (<|>)))++-- | A 'CommanderT' action is a metaphor for a military commander. At each+-- step, we have a new 'Action' to take, or we could have experienced+-- 'Defeat', or we can see 'Victory'. While a real life commander+-- worries about moving his troops around in order to achieve a victory in+-- battle, a 'CommanderT' worries about iteratively transforming a state +-- to find some value. We will deal with the subset of these actions where+-- every function must decrease the size of the state, as those are the+-- actions for which this is a monad.+data CommanderT state m a+ = Action (state -> m (CommanderT state m a, state))+ | Defeat+ | Victory a+ deriving Functor++-- | We can run a 'CommanderT' action on a state and see if it has+-- a successful campaign.+runCommanderT :: Monad m + => CommanderT state m a + -> state + -> m (Maybe a)+runCommanderT (Action action) state = do+ (action', state') <- action state+ m <- runCommanderT action' state'+ return m+runCommanderT Defeat _ = return Nothing+runCommanderT (Victory a) _ = return (Just a)++instance (Monad m) => Applicative (CommanderT state m) where+ (<*>) = ap+ pure = Victory++instance MonadTrans (CommanderT state) where+ lift ma = Action $ \state -> do+ a <- ma+ return (pure a, state)++instance MonadIO m => MonadIO (CommanderT state m) where+ liftIO ma = Action $ \state -> do+ a <- liftIO ma+ return (pure a, state)++-- Return laws:+-- Goal: return a >>= k = k a+-- Proof: return a >>= k +-- = Victory a >>= k +-- = k a +-- = k a+-- Goal: m >>= return = m+-- Proof:+-- Case 1: Defeat >>= return = Defeat+-- Case 2: Victory a >>= return +-- = Victory a+-- Case 3: Action action >>= return+-- = Action $ \state -> do+-- (action', state') <- action state+-- return (action' >>= return, state')+--+-- Case 3 serves as an inductive proof only if action' is a strictly smaller action+-- than action!+--+-- Bind laws:+-- Goal: m >>= (\x -> k x >>= h) = (m >>= k) >>= h+-- Proof: +-- Case 1: Defeat >>= _ = Defeat+-- Case 2: Victory a >>= (\x -> k x >>= f)+-- = k a >>= f+-- = (Victory a >>= k) >>= f+-- Case 3: Action action >>= (\x -> k x >>= h)+-- = Action $ \state -> do+-- (action', state') <- action state+-- return (action' >>= (\x -> k x >>= h), state')+-- = Action $ \state -> do+-- (action', state') <- action state+-- return ((action' >>= k) >>= h, state') -- by IH+-- On the other hand,+-- (Action action >>= k) >>= h+-- = Action (\state -> do+-- (action', state') <- action state+-- return (action' >>= k, state') >>= h+-- = Action $ \state -> do+-- (action', state') <- action state+-- return ((action' >>= k) >>= h, state')+-- +-- This completes our proof for the case when these are finite.+-- Basically, we require that the stream an action produces is strictly+-- smaller than any other streams, for all state inputs. The ways that we+-- use this monad transformer satisify this constraint. If this+-- constraint is not met, many of our functions will return bottom.+--+-- We can certainly have functions that operate on these things and+-- change them safely, without violating this constraint. All of the+-- functions that we define on CommanderT programs preserve this+-- property.+--+-- An example of a violating term might be:+--+-- violator :: CommanderT state m+-- violator = Action (\state -> return (violator, state))+--+-- The principled way to include this type would be to parameterize it by+-- a natural number and have that natural number decrease over time, but+-- to enforce that in Haskell we couldn't have the monad instance+-- anyways. This is the way to go for now, despite the type violating the+-- monad laws potentially for infinite inputs. +instance Monad m => Monad (CommanderT state m) where+ Defeat >>= _ = Defeat+ Victory a >>= f = f a+ Action action >>= f = Action $ \state -> do+ (action', state') <- action state+ return (action' >>= f, state')++instance Monad m => Alternative (CommanderT state m) where+ empty = Defeat + Defeat <|> a = a + v@(Victory _) <|> _ = v+ Action action <|> p = Action $ \state -> do+ (action', state') <- action state + return (action' <|> p, state')
src/Options/Commander.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE BlockArguments #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE DeriveGeneric #-}@@ -14,7 +15,6 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE RankNTypes #-}-{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE GADTs #-}@@ -146,6 +146,7 @@ import qualified Data.ByteString as SBS import qualified Data.ByteString.Char8 as BS8 import qualified Data.ByteString.Lazy as LBS+import Control.Monad.Commander -- | A class for interpreting command line arguments into Haskell types. class Typeable t => Unrender t where@@ -170,17 +171,10 @@ instance Unrender () where unrender = unrenderSmall -instance Unrender a => Unrender (Maybe a) where- unrender x = justCase x <|> nothingCase x where- justCase x' = do- x'' <- stripPrefix "Just " x'- return (unrender x'')- nothingCase x' = if x' == "Nothing" then return Nothing else Nothing- instance (Unrender a, Unrender b) => Unrender (Either a b) where unrender x = leftCase x <|> rightCase x where- leftCase = fmap Left . unrender <=< stripPrefix "Left "- rightCase = fmap Right . unrender <=< stripPrefix "Right "+ leftCase = fmap Left . unrender+ rightCase = fmap Right . unrender instance Unrender Bool where unrender = unrenderSmall@@ -258,125 +252,6 @@ -- options or arguments fails, runs the second, otherwise failing. data a + b infixr 2 +---- | A 'CommanderT' action is a metaphor for a military commander. At each--- step, we have a new 'Action' to take, or we could have experienced--- 'Defeat', or we can see 'Victory'. While a real life commander--- worries about moving his troops around in order to achieve a victory in--- battle, a 'CommanderT' worries about iteratively transforming a state --- to find some value. We will deal with the subset of these actions where--- every function must decrease the size of the state, as those are the--- actions for which this is a monad.-data CommanderT state m a- = Action (state -> m (CommanderT state m a, state))- | Defeat- | Victory a- deriving Functor---- | We can run a 'CommanderT' action on a state and see if it has--- a successful campaign.-runCommanderT :: Monad m - => CommanderT state m a - -> state - -> m (Maybe a)-runCommanderT (Action action) state = do- (action', state') <- action state- m <- runCommanderT action' state'- return m-runCommanderT Defeat _ = return Nothing-runCommanderT (Victory a) _ = return (Just a)--instance (Monad m) => Applicative (CommanderT state m) where- (<*>) = ap- pure = Victory--instance MonadTrans (CommanderT state) where- lift ma = Action $ \state -> do- a <- ma- return (pure a, state)--instance MonadIO m => MonadIO (CommanderT state m) where- liftIO ma = Action $ \state -> do- a <- liftIO ma- return (pure a, state)---- Return laws:--- Goal: return a >>= k = k a--- Proof: return a >>= k --- = Victory a >>= k --- = k a --- = k a--- Goal: m >>= return = m--- Proof:--- Case 1: Defeat >>= return = Defeat--- Case 2: Victory a >>= return --- = Victory a--- Case 3: Action action >>= return--- = Action $ \state -> do--- (action', state') <- action state--- return (action' >>= return, state')------ Case 3 serves as an inductive proof only if action' is a strictly smaller action--- than action!------ Bind laws:--- Goal: m >>= (\x -> k x >>= h) = (m >>= k) >>= h--- Proof: --- Case 1: Defeat >>= _ = Defeat--- Case 2: Victory a >>= (\x -> k x >>= f)--- = k a >>= f--- = (Victory a >>= k) >>= f--- Case 3: Action action >>= (\x -> k x >>= h)--- = Action $ \state -> do--- (action', state') <- action state--- return (action' >>= (\x -> k x >>= h), state')--- = Action $ \state -> do--- (action', state') <- action state--- return ((action' >>= k) >>= h, state') -- by IH--- On the other hand,--- (Action action >>= k) >>= h--- = Action (\state -> do--- (action', state') <- action state--- return (action' >>= k, state') >>= h--- = Action $ \state -> do--- (action', state') <- action state--- return ((action' >>= k) >>= h, state')--- --- This completes our proof for the case when these are finite.--- Basically, we require that the stream an action produces is strictly--- smaller than any other streams, for all state inputs. The ways that we--- use this monad transformer satisify this constraint. If this--- constraint is not met, many of our functions will return bottom.------ We can certainly have functions that operate on these things and--- change them safely, without violating this constraint. All of the--- functions that we define on CommanderT programs preserve this--- property.------ An example of a violating term might be:------ violator :: CommanderT state m--- violator = Action (\state -> return (violator, state))------ The principled way to include this type would be to parameterize it by--- a natural number and have that natural number decrease over time, but--- to enforce that in Haskell we couldn't have the monad instance--- anyways. This is the way to go for now, despite the type violating the--- monad laws potentially for infinite inputs. -instance Monad m => Monad (CommanderT state m) where- Defeat >>= _ = Defeat- Victory a >>= f = f a- Action action >>= f = Action $ \state -> do- (action', state') <- action state- return (action' >>= f, state')--instance Monad m => Alternative (CommanderT state m) where- empty = Defeat - Defeat <|> a = a - v@(Victory _) <|> _ = v- Action action <|> p = Action $ \state -> do- (action', state') <- action state - return (action' <|> p, state') -- | This is the 'State' that the 'CommanderT' library uses for its role in -- this library. It is not inlined, because that does nothing but obfuscate