optparse-applicative option with multiple values - haskell

I'm using optparse-applicative and I'd like to be able to parse command line arguments such as:
$ ./program -a file1 file2 -b filea fileb
i.e., two switches, both of which can take multiple arguments.
So I have a data type for my options which looks like this:
data MyOptions = MyOptions {
aFiles :: [String]
, bFiles :: [String] }
And then a Parser like this:
config :: Parser MyOptions
config = MyOptions
<$> option (str >>= parseStringList)
( short 'a' <> long "aFiles" )
<*> option (str >>= parseStringList)
( short 'b' <> long "bFiles" )
parseStringList :: Monad m => String -> m [String]
parseStringList = return . words
This approach fails in that it will give the expected result when just one argument is supplied for each switch, but if you supply a second argument you get "Invalid argument" for that second argument.
I wondered if I could kludge it by pretending that I wanted four options: a boolean switch (i.e. -a); a list of strings; another boolean switch (i.e. -b); and another list of strings. So I changed my data type:
data MyOptions = MyOptions {
isA :: Bool
, aFiles :: [String]
, isB :: Bool
, bFiles :: [String] }
And then modified the parser like this:
config :: Parser MyOptions
config = MyOptions
<$> switch
( short 'a' <> long "aFiles" )
<*> many (argument str (metavar "FILE"))
<*> switch
( short 'b' <> long "bFiles" )
<*> many (argument str (metavar "FILE"))
This time using the many and argument combinators instead of an explicit parser for a string list.
But now the first many (argument str (metavar "FILE")) consumes all of the arguments, including those following the -b switch.
So how can I write this arguments parser?

Aside from commands, optparse-applicative follows the getopts convention: a single argument on the command line corresponds to a single option argument. It's even a little bit more strict, since getopts will allow multiple options with the same switch:
./program-with-getopts -i input1 -i input2 -i input3
So there's no "magic" that can help you immediately to use your program like
./program-with-magic -a 1 2 3 -b foo bar crux
since Options.Applicative.Parser wasn't written with this in mind; it also contradicts the POSIX conventions, where options take either one argument or none.
However, you can tackle this problem from two sides: either use -a several times, as you would in getopts, or tell the user to use quotes:
./program-as-above -a "1 2 3" -b "foo bar crux"
# works already with your program!
To enable the multiple use of an option you have to use many (if they're optional) or some (if they aren't). You can even combine both variants:
multiString desc = concat <$> some single
where single = option (str >>= parseStringList) desc
config :: Parser MyOptions
config = MyOptions
<$> multiString (short 'a' <> long "aFiles" <> help "Use quotes/multiple")
<*> multiString (short 'b' <> long "bFiles" <> help "Use quotes/multiple")
which enables you to use
./program-with-posix-style -a 1 -a "2 3" -b foo -b "foo bar"
But your proposed style isn't supported by any parsing library I know, since the position of free arguments would be ambiguous. If you really want to use -a 1 2 3 -b foo bar crux, you have to parse the arguments yourself.

Related

Option.Applicative: How to parse a combined parser with a flag?

I have complicated command line options, as
data Arguments = Arguments Bool (Maybe SubArguments)
data SubArguments = SubArguments String String
I want to parse these subarguments with a flag:
programName --someflag --subarguments "a" "b"
programName --someflag
I already have
subArgParser = SubArguments <$> argument str <*> argument str
mainParser = MainArgs <$> switch
(long "someflag"
<> help "Some argument flag")
<*> ???
(long "subarguments"
<> help "Sub arguments"
What do I have to write at the ???
Your question turned out to be more complicated than you think. Current optparse-applicative API is not supposed to be used with such cases. So you probably may want to change the way you handle CLI arguments or switch to another CLI parsing library. But I will describe most closest way of achieving your goal.
First, you need to read other two SO questions:
1. How to parse Maybe with optparse-applicative
2. Is it possible to have a optparse-applicative option with several parameters?
From first question you know how to parse optional arguments using optional function. From second you learn some problems with parsing multiple arguments. So I will write here several approaches how you can workaround this problem.
1. Naive and ugly
You can represent pair of strings as pair of String type and use just naive show of this pair. Here is code:
mainParser :: Parser Arguments
mainParser = Arguments
<$> switch (long "someflag" <> help "Some argument flag")
<*> optional (uncurry SubArguments <$>
(option auto $ long "subarguments" <> help "some desc"))
getArguments :: IO Arguments
getArguments = do
(res, ()) <- simpleOptions "main example" "" "desc" mainParser empty
return res
main :: IO ()
main = getArguments >>= print
Here is result in ghci:
ghci> :run main --someflag --subarguments "(\"a\",\"b\")"
Arguments True (Just (SubArguments "a" "b"))
2. Less naive
From answer to second question you should learn how pass multiple arguments inside one string. Here is code for parsing:
subArgParser :: ReadM SubArguments
subArgParser = do
input <- str
-- no error checking, don't actually do this
let [a,b] = words input
pure $ SubArguments a b
mainParser :: Parser Arguments
mainParser = Arguments
<$> switch (long "someflag" <> help "Some argument flag")
<*> optional (option subArgParser $ long "subarguments" <> help "some desc")
And here is ghci output:
ghci> :run main --someflag --subarguments "x yyy"
Arguments True (Just (SubArguments "x" "yyy"))
The only bad thing in second solution is that error checking is absent. Thus you can use another general purpose parsing library, for example megaparsec, instead of just let [a,b] = words input.
It's not possibile, at least not directly. You might find some indirect encoding that works for you, but I'm not sure. Options take arguments, not subparsers. You can have subparsers, but they are introduced by a "command", not an option (i.e. without the leading --).

How to read three consecutive integers from stdin in Haskell?

I want to read an input like 12 34 56 into three integers using Haskell.
For a single integer, one might use myInteger <- readLn. But for this case, I have not found any solution, except the one of first reading a line, then replacing all spaces with ,, (using something like:
spaceToCommas str =
let repl ' ' = ','
repl c = c
in map repl str
) and then calling read '[' ++ str ++ ']' which feels very hackish. Also, it does not allow me to state that I want to read three integers, it will attempt to read any amount of integers from stdin.
There has to be a better way.
Note that I would like a solution that does not rely on external packages. Using e.g. Parsec is of course great, but this simple example should not require the use of a full-fledged Parser Combinator framework, right?
What about converting the string like:
convert :: Read a => String -> [a]
convert = map read . words
words splits the given string into a list of strings (the "words") and then we perform a read on every element using map.
and for instance use it like:
main = do
line <- getLine
let [a,b,c] = convert line :: [Int] in putStrLn (show (c,a,b))
or if you for instance want to read the first three elements and don't care about the rest (yes this apparently requires super-creativity skills):
main = do
line <- getLine
let (a:b:c:_) = convert line :: [Int] in putStrLn (show (c,a,b))
I here returned a tuple that is rotated one place to the right to show parsing is done.

Howto create a nested/conditional option with optparse-applicative?

Is possible to create a haskell expression, using the methods in optparse-applicative, that parses program options like this?
program [-a [-b]] ...
-a and -b are optionals flags (implemented using switch), with the constraint that the -b option only is valid if -a is typed before.
Thanks
This is possible, with slight tweaks, two different ways:
You can make a parser that only allows -b if you've got -a, but you can't insist then that the -a comes first, since optparse-applicative's <*> combinator doesn't specify an order.
You can insist that the -b option follows the a option, but you do this by implementing a as a command, so you lose the - in front of it.
Applicative is definitely strong enough for this, since there's no need to inspect the values returned by the parsers to determine whether -b is allowed, so >>= is not necessary; If -a succeeds with any output, -b is allowed.
Examples
I'll use a data type to represent which arguments are present, but in reality these would be more meaningful.
import Options.Applicative
data A = A (Maybe B) deriving Show
data B = B deriving Show
So the options to our program maybe contain an A, which might have a B, and always have a string.
boption :: Parser (Maybe B)
boption = flag Nothing (Just B) (short 'b')
Way 1: standard combinators - -b can only come with -a (any order)
I'll use flag' () (short 'a') which just insists that -a is there, but then use *> instead of <*> to ignore the return value () and just return whatever the boption parser returns, giving options -a [-b]. I'll then tag that with A :: Maybe B -> A and finally I'll make the whole thing optional, so you have options [-a [-b]]
aoption :: Parser (Maybe A)
aoption = optional $ A <$> (flag' () (short 'a' ) *> boption)
main = execParser (info (helper <*> aoption)
(fullDesc <> progDesc "-b is only valid with -a"))
>>= print
Notice that since <*> allows any order, we can put -a after -b (which isn't quite what you asked for, but works OK and makes sense for some applications).
ghci> :main -a
Just (A Nothing)
ghci> :main -a -b
Just (A (Just B))
ghci> :main -b -a
Just (A (Just B))
ghci> :main -b
Usage: <interactive> [-a] [-b]
-b is only valid with -a
*** Exception: ExitFailure 1
Way 2: command subparser - -b can only follow a
You can use command to make a subparser which is only valid when the command string is present. You can use it to handle arguments like cabal does, so that cabal install and cabal update have completely different options. Since command takes a ParserInfo argument, any parser you can give to execParser can be used, so you can actually nest commands arbitrarily deeply. Sadly, commands can't start with -, so it'll be program [a [-b]] ... instead of program [-a [-b]] ....
acommand :: Parser A
acommand = subparser $ command "a" (info (A <$> (helper <*> boption))
(progDesc "you can '-b' if you like with 'a'"))
main = execParser (info (helper <*> optional acommand) fullDesc) >>= print
Which runs like this:
ghci> :main
Nothing
ghci> :main a
Just (A Nothing)
ghci> :main a -b
Just (A (Just B))
ghci> :main -b a
Usage: <interactive> [COMMAND]
*** Exception: ExitFailure 1
So you have to precede -b with a.
I'm afraid you can't. This is precisely the scenario that Applicative alone can't handle while Monad can: changing the structure of later actions based on earlier results. In an applicative computation, the "shape" always needs to be known beforehand; this has some advantages (like speeding up so array combinations, or giving out a nice readable help screen for command-line options), but here it limits you to parsing "flat" options.
The interface of optparse-applicative also has Alternative though, which does allow dependent parsing, albeit in a different way as shown by AndrewC.

Haskell interact function

I’m new to Haskell and have a problem with interact function. This is my sample program:
main :: IO ()
main = interact inputLength
inputLength :: String -> String
inputLength input = show $ length input
It compiles but when running doesn’t print the output - just prints the string that is passed to it and moves to the next line. When I pass the interact another String -> String function like this:
upperCase :: String -> String
upperCase input = map toUpper input
it runs ok and prints the argument in uppercase as expected – so what is wrong with the first function?
The String -> String argument given to interact should take a string containing all the input and return a string containing all the output. The reason you see output after pressing enter with interact (map toUpper) is because map toUpper acts lazily -- it can start giving output before all the input is known. Finding the length of a string is not like this -- the whole string must be known before any output can be produced.
You need to either signal an EOF to say that you are done entering input (in the console, this is Control-D on Unix/Mac systems, I believe it's Control-Z on Windows), then it will give you the length. Or you can find the length of each line by saying so:
interact (unlines . map inputLength . lines)
This will always be lazy in each line, so you know you can get one output after each input.
Since acting on lines is such a common pattern, I like to define a little helper function:
eachLine :: (String -> String) -> (String -> String)
eachLine f = unlines . map f . lines
Then you can do:
main = interact (eachLine inputLength)
A more reusable solution:
main = interactLineByLine processLine
-- this wrapper does the boring thing of mapping, unlining etc.... you have to do all the times for user interaction
interactLineByLine:: (String -> String) -> IO ()
interactLineByLine f = interact (unlines . (map processLine) . lines)
-- this function does the actual work line by line, i.e. what is
-- really desired most of the times
processLine:: String -> String
processLine line = "<" ++ line ++ ">"

Finding and replacing words with asterisk, in a text file output

Hello I am new at Haskell and i'm having problems trying to get this script to work. This script reads in arguements from a command line and find them in a seperate text file.
E.G: cat.txt | ./redact house big cat (in compiler)
It redacts certain words in a text file by replacing them with stars (**)asterisks. The number of stars used for each redacted word should equal the number of characters in the word.
module Main where
import System
import Data.Char
import Data.List
lowercase :: String -> String
lowercase = map toLower
main = do
arg1 <- getArgs
txt <- getContents
putStr (redact txt arg1)
redact :: String -> String -> String
redact input xWords = unlines [ work line | line <- lines input ]
where work line = unwords [ foo word | word <- words line ]
foo w | lowercase(w) == lowercase(xWords) = convertWord w 1
| otherwise = w
convertWord Eq a => [a] -> [a]
convertWord = map (const '*')
However, when i try to compile this, GHCi returns the error:
redact.hs:13:38:
Couldn't match expected thye 'Char' with actual type '[Char]'
Expected type: String
Actual type: [String]
In the second argument of 'redact', namely 'arg1'
In the first of 'putStr', namely '<redact txt arg1>'
Failed, module loaded: none.
So the code:
putStr (redact txt arg1)
is causing the problem.
Thank you in advance for any help and if you can improve the code in anyway that would be great.
EDIT:
I want to enter as many args as possible, it doesnt matter how many args you enter, i tried:
(arg1:arg2:arg3:arg4:arg5:_) <- getArgs
but I have to enter EXACT 5 args, It shouldn't matter how many args I enter.
I was thinking of using some kind of loop but I am not sure?
Again thank you for your help.
To get it to work with multiple arguments, use getArgs as you have it. The problem lies with
foo w | lowercase(w) == lowercase(xWords) = convertWord w 1
| otherwise = w
where you compare the lowercase of one word to lowercase of multiple words. The latter is not defined, you'd like to compare it to the lowercase of each of the xWords. So first you need to bring them all to lowercase, that's most efficiently done by calling from main redact txt (map lowercase arg1) rather than just redact txt arg1. Then you need to determine if a read word is in the list xWords, that's what the elem function is there for.
foo w | lowercase w `elem` xWords = convertWord w 1
| otherwise = w
BTW, you should maybe not call this function foo even if it's only a local one.
getArgs :: IO [String], so after arg1 <- getArgs, arg1 has the type [String]: it contains all the arguments passed to your program, as a list. But you're using it as String, thus the error: GHC expected arg1 to be a String, but it's a [String].
You can pattern-match on the result like this:
arg1:_ <- getArgs
This results in arg1 containing the first element of the list, and discards the rest of the list. If you don't pass an argument, it'll result in a runtime error. Of course, if you want more specialised behaviour (say, printing an error when no arguments are given), you could use a more complex method of extracting the first argument, such as a case expression.
As far as improvements to your program go:
You can simplify the definition of work using function composition and map rather than the list comprehension: work = unwords . map foo . words (read: "map foo over all the elements of the words, then unwords them").
redact can be simplified similarly, to redact input xWords = unlines . map work . lines $ input.
lowercase(w) is better written as lowercase w.
But your program looks basically fine to me, apart from some oddities (like the missing :: in convertWord's type signature, the additional 1 you pass to it in foo — but going by the somewhat erratic indentation, I guess you edited the code before posting it). I wouldn't make the first two changes unless you understand how they work and are comfortable writing code like that.

Resources