packages feed

hmt-0.18: Music/Theory/Opt.hs

{- | Very simple CLI option parser.

Only allows options of the form --key=value, with the form --key equal to --key=True.

A list of OPT_USR describes the options and provides default values.

'get_opt_arg' merges user and default values into a table with values for all options.

To fetch options use 'opt_get' and 'opt_read'.

-}
module Music.Theory.Opt where

import Control.Monad {- base -}
import Data.Either {- base -}
import Data.List {- base -}
import Data.Maybe {- base -}
import System.Environment {- base -}
import System.Exit {- base -}

import qualified Data.List.Split as Split {- split -}

import qualified Music.Theory.Read as T {- hmt -}

-- | (KEY,VALUE)
--   Key does not include leading '--'.
type OPT = (String,String)

-- | (KEY,DEFAULT-VALUE,TYPE,NOTE)
type OPT_USR = (String,String,String,String)

-- | Re-write default values at USR_OPT.
opt_usr_rw_def :: [OPT] -> [OPT_USR] -> [OPT_USR]
opt_usr_rw_def rw =
  let f (k,v,ty,dsc) = case lookup k rw of
                         Just v' -> (k,v',ty,dsc)
                         Nothing -> (k,v,ty,dsc)
  in map f

-- | OPT_USR to OPT.
opt_plain :: OPT_USR -> OPT
opt_plain (k,v,_,_) = (k,v)

-- | OPT_USR to help string, indent is two spaces.
opt_usr_help :: OPT_USR -> String
opt_usr_help (k,v,t,n) = concat ["  ",k,":",t," -- ",n,"; default=",v]

-- | 'unlines' of 'opt_usr_help'
opt_help :: [OPT_USR] -> String
opt_help = unlines . map opt_usr_help

-- | Lookup KEY in OPT, error if non-existing.
opt_get :: [OPT] -> String -> String
opt_get o k = fromMaybe (error ("opt_get: " ++ k)) (lookup k o)

-- | Variant that returns Nothing if the result is the empty string, else Just the result.
opt_get_nil :: [OPT] -> String -> Maybe String
opt_get_nil o k = let r = opt_get o k in if null r then Nothing else Just r

-- | 'read' of 'get_opt'
opt_read :: Read t => [OPT] -> String -> t
opt_read o = T.read_err . opt_get o

-- | Parse k or k=v string, else error.
opt_param_parse :: String -> OPT
opt_param_parse p =
  case Split.splitOn "=" p of
    [lhs] -> (lhs,"True")
    [lhs,rhs] -> (lhs,rhs)
    _ -> error ("opt_param_parse: " ++ p)

-- | Parse option string of form "--opt" or "--key=value".
--
-- > opt_parse "--opt" == Just ("opt","True")
-- > opt_parse "--key=value" == Just ("key","value")
opt_parse :: String -> Maybe OPT
opt_parse s =
  case s of
    '-':'-':p -> Just (opt_param_parse p)
    _ -> Nothing

-- | Parse option sequence, collating options and non-options.
--
-- > opt_set_parse (words "--a --b=c d") == ([("a","True"),("b","c")],["d"])
opt_set_parse :: [String] -> ([OPT],[String])
opt_set_parse =
  let f s = maybe (Right s) Left (opt_parse s)
  in partitionEithers . map f

-- | Left-biased OPT merge.
opt_merge :: [OPT] -> [OPT] -> [OPT]
opt_merge p q =
  let x = map fst p
  in p ++ filter (\(k,_) -> k `notElem` x) q

-- | Process argument list.
opt_proc :: [OPT_USR] -> [String] -> ([OPT], [String])
opt_proc def arg =
  let (o,a) = opt_set_parse arg
  in (opt_merge o (map opt_plain def),a)

-- | Usage text
type OPT_USG = [String]

-- | Print usage pre-amble and 'opt_help'.
opt_usage :: OPT_USG -> [OPT_USR] -> IO ()
opt_usage usg def = putStrLn (unlines (usg ++ ["",opt_help def])) >> exitWith ExitSuccess

-- | Verify that all OPT have keys that are in OPT_USR
opt_verify :: OPT_USG -> [OPT_USR] -> [OPT] -> IO ()
opt_verify usg def =
  let k_set = map (fst . opt_plain) def
      f (k,_) = if k `elem` k_set
                then return ()
                else putStrLn ("UNKNOWN KEY: " ++ k ++ "\n") >> opt_usage usg def
  in mapM_ f

-- | 'opt_set_parse' and maybe 'opt_verify' and 'opt_merge' of 'getArgs'.
--   If arguments include -h or --help run 'opt_usage'
opt_get_arg :: Bool -> OPT_USG -> [OPT_USR] -> IO ([OPT],[String])
opt_get_arg chk usg def = do
  a <- getArgs
  when ("-h" `elem` a || "--help" `elem` a) (opt_usage usg def)
  let (o,p) = opt_set_parse a
  when chk (opt_verify usg def o)
  return (opt_merge o (map opt_plain def),p)

-- | Parse param set, one parameter per line.
--
-- > opt_param_set_parse "a\nb=c" == [("a","True"),("b","c")]
opt_param_set_parse :: String -> [OPT]
opt_param_set_parse = map opt_param_parse . lines

-- | Simple scanner over argument list.
opt_scan :: [String] -> String -> Maybe String
opt_scan a k =
  let (o,_) = opt_set_parse a
  in fmap snd (find ((== k) . fst) o)

-- | Scanner with default value.
opt_scan_def :: [String] -> (String,String) -> String
opt_scan_def a (k,v) = fromMaybe v (opt_scan a k)

-- | Reading scanner with default value.
opt_scan_read :: Read t => [String] -> (String,t) -> t
opt_scan_read a (k,v) = maybe v read (opt_scan a k)