Incerteza
Incerteza

Reputation: 34884

A simple way of parsing a program arguments

I've seen some approaches about parsing program arguments. They seem to be too complicated. I need a simple solution. But I also want to be able (preferably, not necessarily) to refer to the arguments by name. So if a command like looks like this:

./MyApp --arg1Name arg1Value --arg2Name arg2Value

then I'd like to treat them as args["arg1Name"] and args["arg2Name"] to get their values. I know that's not a valid Haskell code, though. What I have now is not much:

main = do
  [args] <- getArgs

I repeat, I'd like a simple solution, preferably without involving any third-party haskell libraries.

Upvotes: 1

Views: 209

Answers (2)

bisserlis
bisserlis

Reputation: 633

optparse-applicative is great for argument parsing, and very easy to use! Writing your own argument parser will be much more difficult to get right, change, extend, or otherwise manage than if you take 10 minutes to write a parser with optparse-applicative.

Start by importing the Options.Applicative module.

import Options.Applicative

Next create a data type for your command-line configuration.

data Configuration = Configuration
                     { foo :: String
                     , bar :: Int
                     }

Now for the workhorse, we create a parser by using the combinators exported from optparse-applicative. Read the documentation on Options.Applicative.Builder for the full experience.

configuration :: Parser Configuration
configuration = Configuration
            <$> strOption
                ( long "foo"
               <> metavar "ARG1"
                )
            <*> option
                ( long "bar"
               <> metavar "ARG2"
                )

Now we can execute our Parser Configuration in an IO action to get at our command-line data.

main :: IO ()
main = do 
          config <- execParser (info configuration fullDesc)
          putStrLn (show (bar config) ++ foo config)

And we're done! You can easily extend this parser to support a --help argument to print out usage documentation (make a new parser with helper <*> configuration and pass it to info), you can add default values for certain arguments (include a <> value "default" clause in the arguments to strOption or option), you can support flags, or sub-parsers or generate tab-completion data.

Libraries are a force multiplier! The investment you make in learning the basics of a good library will pay dividends in what you're able to accomplish, and tasks will often be easier (and quicker!) with the proper tool than with a "fast" solution thrown together out of duct tape.

Upvotes: 5

Ganesh Sittampalam
Ganesh Sittampalam

Reputation: 29100

How about just parsing them in pairs and adding them to a map:

simpleArgsMap :: [String] -> Map String String
simpleArgsMap [] = Map.empty
simpleArgsMap (('-':'-':name):value:rest)
    = Map.insert name value (simpleArgsMap rest)
simpleArgsMap xs = error $ "Couldn't parse arguments: " ++ show xs

A simple wrapper program to show this working:

module Args where

import System.Environment ( getArgs )
import Control.Applicative ( (<$>) )

import qualified Data.Map as Map
import Data.Map ( Map )

main = do
  argsMap <- simpleArgsMap <$> getArgs
  print $ Map.lookup "foo" argsMap

Upvotes: 3

Related Questions