Magnap
Magnap

Reputation: 375

How do I simplify the following command line options parsing code?

I am working on a homework manager for personal use, developing it with Haskell. I have, as a start, made the following code for parsing the commandline arguments, code which i should be easily extandable later through adding to dispatch. The functions themselves are dummies for testing purposes. I do, however, feel that this code can be made simpler, and more failsafe, though i cannot see how. The code is this:

module Main where

import System.Environment

main = getArgs >>= parse

parse :: [String] -> IO ()
parse [] = usage
parse (file:mode:[]) = if mode == "list"
                       then list file
                       else case lookup mode dispatch of
                         Just _ -> putStrLn "That mode requires arguments" >> usage
                         Nothing -> putStrLn "Please supply a valid mode" >> usage
parse (file:mode:args) = case lookup mode dispatch of
  Nothing -> do case lookup file dispatch of
                  Just _ -> putStrLn "Please supply a data file"
                  Nothing -> putStrLn "Please supply a valid mode"
                usage
  Just fun -> fun file args
parse (file:[]) = case lookup file dispatch of
  Just _ -> putStrLn "Please supply a datafile" >> usage
  Nothing -> list file

usage :: IO ()
usage = mapM_ putStrLn helptext

helptext :: [String]
helptext = ["Homework manager by Marcus Medom Ryding <[email protected]>"
           ,"Copyright 2013, licensed under BSD3"
           ,"Usage: homework filepath [mode] [arguments]"]

dispatch :: [(String, String -> [String] -> IO ())]
dispatch  = [("new",new)]

new :: String -> [String] -> IO ()
new file args = putStrLn ("NEW: " ++ file) >> mapM_ putStrLn args

list :: String -> IO ()
list file = putStrLn ("LIST: " ++ file)

Upvotes: 0

Views: 237

Answers (2)

Gert Cuykens
Gert Cuykens

Reputation: 7155

In addition to the above answer, just based on your code:

module Main where

import System.Environment

main∷ IO ()
main = getArgs »= parse2

parse2 ∷ [String] → IO ()
parse2 [] = usage
parse2 (file:mode:[])
    | mode ≡ "list" = list file
    | mode ≠ "list" =
    case lookup mode dispatch of
        Just _ → putStrLn "That mode requires arguments" » usage
        Nothing → putStrLn "Please supply a valid mode" » usage
parse2 (file:mode:args) =
    case lookup mode dispatch of
        Nothing → err (lookup file dispatch) » usage
        Just fun → fun file args
        where err (Just _) = putStrLn "Please supply a data file"
              err Nothing = putStrLn "Please supply a valid mode"
parse2 (file:[]) =
    case lookup file dispatch of
        Just _ → putStrLn "Please supply a datafile" » usage
        Nothing → list file

usage ∷ IO ()
usage = mapM_ putStrLn helptext

helptext ∷ [String]
helptext = ["Homework manager by Marcus Medom Ryding <[email protected]>"
           ,"Copyright 2013, licensed under BSD3"
           ,"Usage: homework filepath [mode] [arguments]"]

dispatch ∷ [(String, String → [String] → IO ())]
dispatch  = [("new",new)]

new ∷ String → [String] → IO ()
new file args = putStrLn ("NEW: " ⧺ file) » mapM_ putStrLn args

list ∷ String → IO ()
list file = putStrLn ("LIST: " ⧺ file)

Upvotes: 1

scvalex
scvalex

Reputation: 15335

Rather than parsing the command line manually, you should use a library. Have a look at cmdargs.

It lets you define command line options in a declarative fashion:

{-# LANGUAGE DeriveDataTypeable #-}

import System.Console.CmdArgs

data MyProg = New {comment :: String}
             | Delete {howMany :: Int}
               deriving (Show, Data, Typeable)

new = New {comment = "this is a parameter with a default value"}
delete = Delete {howMany = 10}

main = print =<< cmdArgs (modes [new, delete])

This gives something like:

% ./prog new -c "hello world"
New {comment = "hello world"}
% ./prog delete              
Delete {howMany = 10}

You also get help for free:

% ./prog --help      
The myprog program

myprog [COMMAND] ... [OPTIONS]

Common flags:
  -? --help          Display help message
  -V --version       Print version information

myprog new [OPTIONS]

  -c --comment=ITEM

myprog delete [OPTIONS]

  -h --howmany=INT

The author also has a nice tutorial. You'll want to do something similar to the "Multiple Modes" section.

Upvotes: 4

Related Questions