packages feed

optparse-declarative 0.3.1 → 0.4.0

raw patch · 7 files changed

+223/−51 lines, 7 filessetup-changedPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Options.Declarative: instance Options.Declarative.ArgRead a => Options.Declarative.ArgRead [a]
- Options.Declarative: argRead :: (ArgRead a, Read a) => Maybe String -> Maybe a
+ Options.Declarative: argRead :: (ArgRead a, Read a) => [String] -> Maybe a

Files

ChangeLog.md view
@@ -1,3 +1,13 @@+# 0.4.0++## Breaking changes++- Change in behavior when the same option is specified multiple times [#8](https://github.com/tanakh/optparse-declarative/pull/8)++## Other changes++- Support for list types [#8](https://github.com/tanakh/optparse-declarative/pull/8)+ # 0.3.1  - Allow False as a default value for Bool arguments [#2](https://github.com/tanakh/optparse-declarative/pull/2)
README.md view
@@ -2,7 +2,7 @@  [![Hackage](https://matrix.hackage.haskell.org/api/v2/packages/optparse-declarative/badge)](http://hackage.haskell.org/package/optparse-declarative) [![GitHub Actions: test](https://github.com/tanakh/optparse-declarative/workflows/test/badge.svg)](https://github.com/tanakh/optparse-declarative/actions?query=workflow%3Atest) [![Join the chat at https://gitter.im/optparse-declarative/community](https://badges.gitter.im/optparse-declarative/community.svg)](https://gitter.im/optparse-declarative/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -`optparse-declarative` is a declarative and easy to use command-line option parser.+`optparse-declarative` is a declarative and easy-to-use command-line option parser.  # Install @@ -14,15 +14,17 @@  ## Writing a simple command -First, you need to enable `DataKinds` extension and import `Options.Declarative` module.+First, you need to enable `DataKinds` extension. Then import `Options.Declarative` module.  ```hs {-# LANGUAGE DataKinds #-} import           Options.Declarative ``` -Then, define the command line option as a **type of the function**.-For example, this is a simple greeting program:+Next, define command line options as a **type of the function**.+For example, this is a simple greeting program with `-g` option that+takes a message of type `String` and an unnamed command-line argument+that specifies a name:  ```hs greet :: Flag "g" '["greet"] "STRING" "greeting message" (Def "Hello" String)@@ -32,26 +34,47 @@     liftIO $ putStrLn $ get msg ++ ", " ++ get name ++ "!" ``` -There are two type of options, `Flag` and `Arg`.-`Flag` is named argument and `Arg` is unnamed argument.-Last argument of both options is value type.-If you need to specify default value, use the modifiers such as `Def`.+There are two types of options, `Flag` and `Arg`.+`Flag` represents a named argument (e.g., `--greet "Hola"`), and `Arg` an unnamed argument (e.g., `John` of `greet --greet Hola John`).+The last argument of `Flag` and `Arg` is the type of the value of the+argument; in this example, they are both `String`.+You can specify any type for the value as long as the type is an+instance of `ArgRead` typeclass, in which the conversion function+from `String` to the specified type is defined.+`Options.Declarative` provides following instances of `ArgRead`+typeclass. -In above, variable `msg` has a very complex type (`Flag "g" '["greet"] "STRING" "greeting message" (Def "Hello" String)`).-In order to get the value of usual type (in this case, that is `String`),+- Int+- Integer+- Bool+- Double+- String+- (ArgRead a) => Maybe a++Users can add a new instance of `ArgRead` to support any user-defined type.+Please see Section "How to add a new instance of `ArgRead`" for details.++If you wish to specify a default value for allowing users to omit a+value, use the modifier `Def` with the default value as the second type argument (and the third type argument is the type of the value).+You need to specify the default value in `String` instead of the final+value of the target type; the string will be converted to the final+value via `ArgRead` typeclass.++In the example above, the variable `msg` has a very complex type (`Flag "g" '["greet"] "STRING" "greeting message" (Def "Hello" String)`).+In order to get the value of the target type (in this case, that is `String`), you can use `get` function.  The whole type of command is `Cmd`. `Cmd` is an instance of `MonadIO` and it has some extra information. -After defining a command, you just invoke it by `run_`.+Finally, you can run the whole program by `run_`.  ```hs main :: IO () main = run_ greet ``` -You can execute this program like this:+Here is an example session with the program shown above.  ```bash $ ghc simple.hs@@ -73,14 +96,44 @@ Goodbye, World! ``` -## Writing multiple sub-commands+Note that only the final option is used when multiple options of the+same name are given. This behavior emulates the behavior of a naive+program that uses GNU Getopt. -You can write (nested) sub-commands.+```bash+$ ./simple --greet=Hello --greet=Goodbye World+Goodbye, World!+``` -Just groupe subcommands by `Group`, you got sub-command parser.+There is another way of interpreting multiple options of the same name.+Suppose if you need to get multiple values from the same option.+Say, you wish to get `["Hello", "Goodbye"]` from the command-line+option `--greet=Hello --greeet=Goodbye`. Then, you can use+the type `[]` to indicate that it accepts multiple values.+The first line of the function `greet` in the example above+would be changed as this: -This is the example:+```hs+greet :: Flag "g" '["greet"] "STRING" "greeting message" [String]+``` +The value returned by `get` will be a value of type `[String]`.+See the complete working example at `example/list.hs` for details.+++## Writing multiple subcommands++You can write (nested) subcommands.+You don't know what subcommands are? Imagine `git` command.+`git` has subcommands such as `git add`, `git commit`, `git log`, etc.+`git` has nested subcommands such as `git remote add`, `git remote rm`,+etc.+`optparse-declarative` provides an easy way to provide such possibly+nested subcommands.++Just group subcommands by `Group`, then you get a subcommand parser.+Here is an example with two subcommands `greet` and `connect`:+ ```hs {-# LANGUAGE DataKinds #-} @@ -110,7 +163,7 @@     liftIO $ putStrLn $ "connect to " ++ addr ``` -And this is the output:+This is a sample session for the program above:  ```bash $ ./subcmd --help@@ -126,4 +179,78 @@ connect to localhost:1234 ``` +If you wish to specify the program name or the version number,+use `run` instead of `run_`. The first argument of `run` is+a program name (of type `String`). The second argument is+a version number (of type `Maybe String`).++```hs+main :: IO ()+main = run "program_name" (Just "1.3.2") $+    Group "Test program for library"+    [ subCmd "greet"   greet+    , subCmd "connect" connect+    ]+```+ For more examples, please see `example` directory.+++## Default options+`optparse-declarative` provides a few default options.+For example, `--help` is defined automatically so users do not have to+write it by their own. If run with `run` and the version number,+`--version` is defined automatically. Also, `--verbosity` option (`-v`+for short) is defined by default.+`getVerbosity` returns the verbosity level in `Int`.+`-v` gives 1, `-vv` gives 2, `-vvv` gives 3.+Alternatively, `--verbose=3` would yield 3.+++## How to add a new instance of `ArgRead`+Users need to create an instance of `ArgRead` for supporting a new type+for the command line argument. Here is the definition of class+`ArgRead`.++```hs+class ArgRead a where+    -- | Type of the argument+    type Unwrap a :: *+    type Unwrap a = a++    -- | Get the argument's value+    unwrap :: a -> Unwrap a+    default unwrap :: a ~ Unwrap a => a -> Unwrap a+    unwrap = id++    -- | Argument parser+    argRead :: [String] -> Maybe a+    default argRead :: Read a => [String] -> Maybe a+    argRead ss = getLast $ mconcat $ Last . readMaybe <$> ss++    -- | Indicate this argument is mandatory+    needArg :: Proxy a -> Bool+    needArg _ = True+```++Suppose you are adding a support for your type `T`.+We explain which function to define explicitly, depending on the+property of `T`.++If `T` is the type of the final value you take out of a command line,+you do not have to define `Unwrap`. If `T` is a wrapper like `Def`,+define `type Unwrap T = <unwrapped type>`. For `Def x y`,+`type Unwrap (Def x y) = y`. If you defined `Unwrap`, define `unwrap`+that takes an actual value out of the wrapped value.++`argRead` is the main function that converts String into a value.+If the type is an instance of `Read` and you are satisfied with+how `read` converts a `String` into value, there is no need to+define your own `argRead`. Otherwise, you define a function that+converts a `String` into a value of the target type. When parsing+is successful, return `Just`. When it fails, return `Nothing`.+If the input is `[]`, it indicates the option does not have an+argument; otherwise the input is a list of a single `String`.+Last but not least, define `needArg _ = False` when the option+allows us to omit the associated value; consider a boolean+option like `--help`.
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
+ example/bool.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE DataKinds #-}++import           Control.Monad.Trans+import           Options.Declarative++main' :: Flag "b" '["bool"] "STRING" "boolean flag" Bool+      -> Cmd "Simple greeting example" ()+main' b =+    liftIO $ putStrLn $ if get b then "Flag is True" else "Flag is False"++main :: IO ()+main = run_ main'
+ example/list.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE DataKinds #-}++import           Control.Monad+import           Control.Monad.Trans+import           Options.Declarative++greet :: Flag "n" '["name"] "STRING" "name" [String]+      -> Cmd "Count the number of people" ()+greet name =+    let people_name_list = get name+        num_people = length people_name_list+    in liftIO $ do+        putStrLn $ "There are " ++ show num_people ++ " people on the list."+        putStrLn " -- "+        forM_ people_name_list putStrLn++main :: IO ()+main = run_ greet
optparse-declarative.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.2 name:                optparse-declarative-version:             0.3.1+version:             0.4.0 synopsis:            Declarative command line option parser description:         Declarative and easy to use command line option parser homepage:            https://github.com/tanakh/optparse-declarative
src/Options/Declarative.hs view
@@ -29,7 +29,7 @@     Flag,     Arg, -    -- * Defining argment types+    -- * Defining argument types     ArgRead(..),     Def, @@ -41,12 +41,11 @@     run, run_,     ) where -import           Control.Applicative import           Control.Monad import           Control.Monad.Reader-import           Control.Monad.Trans import           Data.List import           Data.Maybe+import           Data.Monoid import           Data.Proxy import           GHC.TypeLits import           System.Console.GetOpt@@ -93,9 +92,9 @@     unwrap = id      -- | Argument parser-    argRead :: Maybe String -> Maybe a-    default argRead :: Read a => Maybe String -> Maybe a-    argRead s = readMaybe =<< s+    argRead :: [String] -> Maybe a+    default argRead :: Read a => [String] -> Maybe a+    argRead ss = getLast $ mconcat $ Last . readMaybe <$> ss      -- | Indicate this argument is mandatory     needArg :: Proxy a -> Bool@@ -107,22 +106,28 @@  instance ArgRead Double -instance ArgRead String where-    argRead = id+instance {-# OVERLAPPING #-} ArgRead String where+    argRead [] = Nothing+    argRead xs = Just $ last xs  instance ArgRead Bool where-    argRead Nothing = Just False-    argRead (Just "f") = Just False-    argRead (Just "t") = Just True-    argRead _ = Nothing+    argRead []    = Just False+    argRead ["f"] = Just False+    argRead ["t"] = Just True+    argRead _     = Nothing      needArg _ = False  instance ArgRead a => ArgRead (Maybe a) where-    argRead Nothing = Just Nothing-    argRead (Just a) = Just <$> argRead (Just a)+    argRead [] = Just Nothing+    argRead xs = Just <$> argRead xs --- | The argument which has defalut value+instance {-# OVERLAPPABLE #-} ArgRead a => ArgRead [a] where+    argRead xs = case mapMaybe (argRead . (:[])) xs of+                 [] -> Nothing+                 xs -> Just xs++-- | The argument which has default value newtype Def (defaultValue :: Symbol) a =     Def { getDef :: a } @@ -131,12 +136,14 @@     unwrap = unwrap . getDef      argRead s =-        let s' = fromMaybe (symbolVal (Proxy :: Proxy defaultValue)) s-        in Def <$> argRead (Just s')+        let s' = case s of+                 [] -> [symbolVal (Proxy :: Proxy defaultValue)]+                 v  -> v+        in Def <$> argRead s'  -- | Command newtype Cmd (help :: Symbol) a =-    Cmd { unCmd :: ReaderT Int IO a }+    Cmd (ReaderT Int IO a)     deriving (Functor, Applicative, Monad, MonadIO)  -- | Output string when the verbosity level is greater than or equal to `logLevel`@@ -210,7 +217,7 @@          , IsCmd c )          => IsCmd (Flag shortNames longNames placeholder help a -> c) where     getOptDescr f =-        let flagname = head $+        let flagName = head $                        symbolVals (Proxy :: Proxy longNames) ++                        [ [c] | c <- symbolVal (Proxy :: Proxy shortNames) ]         in Option@@ -218,23 +225,23 @@             (symbolVals (Proxy :: Proxy longNames))             (if needArg (Proxy :: Proxy a)              then ReqArg-                  (flagname, )+                  (flagName, )                   (symbolVal (Proxy :: Proxy placeholder))              else NoArg-                  (flagname, "t"))+                  (flagName, "t"))             (symbolVal (Proxy :: Proxy help))         : getOptDescr (f undefined)      runCmd f name mbver options nonOptions unrecognized =-        let flagname = head $+        let flagName = head $                        symbolVals (Proxy :: Proxy longNames) ++                        [ [c] | c <- symbolVal (Proxy :: Proxy shortNames) ]-            mbs = lookup flagname options+            mbs = map snd $ filter ((== flagName) . fst) options         in case (argRead mbs, mbs) of-            (Nothing, Nothing) ->-                errorExit name $ "flag must be specified: --" ++ flagname-            (Nothing, Just s) ->-                errorExit name $ "bad argument: --" ++ flagname ++ "=" ++ s+            (Nothing, []) ->+                errorExit name $ "flag must be specified: --" ++ flagName+            (Nothing, s:_) ->+                errorExit name $ "bad argument: --" ++ flagName ++ "=" ++ s             (Just arg, _) ->                 runCmd (f $ Flag arg) name mbver options nonOptions unrecognized @@ -261,7 +268,7 @@     case nonOptions of         [] -> errorExit name "not enough arguments"         (opt: rest) ->-            case argRead (Just opt) of+            case argRead [opt] of                 Nothing ->                     errorExit name $ "bad argument: " ++ opt                 Just arg ->@@ -274,7 +281,7 @@         " " ++ symbolVal (Proxy :: Proxy placeholder) ++ getUsageHeader (f undefined) prog      runCmd f name mbver options nonOptions unrecognized =-        case traverse argRead $ Just <$> nonOptions of+        case traverse argRead $ (:[]) <$> nonOptions of             Nothing ->                 errorExit name $ "bad arguments: " ++ unwords nonOptions             Just opts ->@@ -342,7 +349,7 @@             ++ [ Option "v" ["verbose"] (OptArg (\arg -> ("verbose", fromMaybe "" arg)) "n") "set verbosity level" ]          prog     = unwords name-        vermsg   = prog ++ maybe "" (" version " ++) mbver+        verMsg   = prog ++ maybe "" (" version " ++) mbver         header = "Usage: " ++ prog ++ " [OPTION...]" ++ getUsageHeader cmd prog ++ "\n" ++                  "  " ++ getCmdHelp cmd ++ "\n\n" ++                  "Options:"@@ -359,7 +366,7 @@                   putStr usage                   exitSuccess             | isJust (lookup "version" options) -> do-                  putStrLn vermsg+                  putStrLn verMsg                   exitSuccess             | otherwise ->                   runCmd cmd name mbver options nonOptions unrecognized