command line options API updates, possibly fixing hledger-web build

This commit is contained in:
Simon Michael 2013-09-22 22:31:06 -07:00
parent 13f8c0f938
commit a66a715eeb
5 changed files with 124 additions and 71 deletions

View File

@ -11,14 +11,12 @@ import Data.List
import Data.Ord import Data.Ord
import Hledger import Hledger
import Hledger.Cli import Hledger.Cli
import Hledger.Cli.Print (print')
main = do main = do
opts <- getHledgerCliOpts [] opts <- getCliOpts (defCommandMode ["hledger-print-unique"])
withJournalDo opts $ withJournalDo opts $
\opts j@Journal{jtxns=ts} -> print' opts j{jtxns=uniquify ts} \opts j@Journal{jtxns=ts} -> print' opts j{jtxns=uniquify ts}
where where
uniquify = nubBy (\t1 t2 -> thingToCompare t1 == thingToCompare t2) . sortBy (comparing thingToCompare) uniquify = nubBy (\t1 t2 -> thingToCompare t1 == thingToCompare t2) . sortBy (comparing thingToCompare)
thingToCompare = tdescription
thingToCompare = tdescription -- thingToCompare = tdate
-- thingToCompare = tdate

View File

@ -40,9 +40,9 @@ main = do
runWith :: WebOpts -> IO () runWith :: WebOpts -> IO ()
runWith opts runWith opts
| "help" `in_` (rawopts_ $ cliopts_ opts) = putStr (showModeHelp webmode) >> exitSuccess | "help" `inRawOpts` (rawopts_ $ cliopts_ opts) = putStr (showModeHelp webmode) >> exitSuccess
| "version" `in_` (rawopts_ $ cliopts_ opts) = putStrLn prognameandversion >> exitSuccess | "version" `inRawOpts` (rawopts_ $ cliopts_ opts) = putStrLn prognameandversion >> exitSuccess
| "binary-filename" `in_` (rawopts_ $ cliopts_ opts) = putStrLn (binaryfilename progname) | "binary-filename" `inRawOpts` (rawopts_ $ cliopts_ opts) = putStrLn (binaryfilename progname)
| otherwise = do | otherwise = do
requireJournalFileExists =<< journalFilePathFromOpts (cliopts_ opts) requireJournalFileExists =<< journalFilePathFromOpts (cliopts_ opts)
withJournalDo' opts web withJournalDo' opts web

View File

@ -31,11 +31,11 @@ webflags = [
webmode :: Mode [([Char], [Char])] webmode :: Mode [([Char], [Char])]
webmode = (mode "hledger-web" [("command","web")] webmode = (mode "hledger-web" [("command","web")]
"start serving the hledger web interface" "start serving the hledger web interface"
mainargsflag []){ (argsFlag "[PATTERNS]") []){
modeGroupFlags = Group { modeGroupFlags = Group {
groupUnnamed = webflags groupUnnamed = webflags
,groupHidden = [flagNone ["binary-filename"] (setboolopt "binary-filename") "show the download filename for this executable, and exit"] ,groupHidden = [flagNone ["binary-filename"] (setboolopt "binary-filename") "show the download filename for this executable, and exit"]
,groupNamed = [(generalflagstitle, generalflags1)] ,groupNamed = [generalflagsgroup1]
} }
,modeHelpSuffix=[ ,modeHelpSuffix=[
-- "Reads your ~/.hledger.journal file, or another specified by $LEDGER_FILE or -f, and starts the full-window curses ui." -- "Reads your ~/.hledger.journal file, or another specified by $LEDGER_FILE or -f, and starts the full-window curses ui."
@ -61,7 +61,7 @@ defwebopts = WebOpts
toWebOpts :: RawOpts -> IO WebOpts toWebOpts :: RawOpts -> IO WebOpts
toWebOpts rawopts = do toWebOpts rawopts = do
cliopts <- toCliOpts rawopts cliopts <- rawOptsToCliOpts rawopts
let p = fromMaybe defport $ maybeintopt "port" rawopts let p = fromMaybe defport $ maybeintopt "port" rawopts
return defwebopts { return defwebopts {
port_ = p port_ = p

View File

@ -99,7 +99,7 @@ main = do
addons <- getHledgerAddonCommands addons <- getHledgerAddonCommands
-- parse arguments with cmdargs -- parse arguments with cmdargs
opts <- getHledgerCliOpts addons opts <- argsToCliOpts args addons
-- select an action and run it. -- select an action and run it.
let let

View File

@ -7,7 +7,7 @@ Command-line options for the hledger program, and related utilities.
module Hledger.Cli.Options ( module Hledger.Cli.Options (
-- * cmdargs modes -- * cmdargs modes & flags
-- | These tell cmdargs how to parse the command line arguments. -- | These tell cmdargs how to parse the command line arguments.
-- There's one mode for each internal subcommand, plus a main mode. -- There's one mode for each internal subcommand, plus a main mode.
mainmode, mainmode,
@ -22,7 +22,15 @@ module Hledger.Cli.Options (
statsmode, statsmode,
testmode, testmode,
convertmode, convertmode,
defCommandMode,
argsFlag,
helpflags,
inputflags,
reportflags,
generalflagsgroup1,
generalflagsgroup2,
generalflagsgroup3,
-- * raw options -- * raw options
-- | To allow the cmdargs modes to be reused and extended by other -- | To allow the cmdargs modes to be reused and extended by other
-- packages (eg, add-ons which want to mimic the standard hledger -- packages (eg, add-ons which want to mimic the standard hledger
@ -30,6 +38,14 @@ module Hledger.Cli.Options (
-- association list, not a fixed ADT. -- association list, not a fixed ADT.
RawOpts, RawOpts,
inRawOpts, inRawOpts,
boolopt,
intopt,
maybeintopt,
stringopt,
maybestringopt,
listofstringopt,
setopt,
setboolopt,
-- * CLI options -- * CLI options
-- | Raw options are converted to a more convenient, -- | Raw options are converted to a more convenient,
@ -37,10 +53,9 @@ module Hledger.Cli.Options (
-- throughout hledger CLI code. -- throughout hledger CLI code.
CliOpts(..), CliOpts(..),
defcliopts, defcliopts,
toCliOpts,
-- * CLI option accessors -- * CLI option accessors
-- | Some options require more processing. Possibly these should be merged into toCliOpts. -- | Some options require more processing. Possibly these should be merged into argsToCliOpts.
aliasesFromOpts, aliasesFromOpts,
formatFromOpts, formatFromOpts,
journalFilePathFromOpts, journalFilePathFromOpts,
@ -53,10 +68,15 @@ module Hledger.Cli.Options (
-- * utilities -- * utilities
getHledgerAddonCommands, getHledgerAddonCommands,
getHledgerCliOpts, argsToCliOpts,
moveFlagsAfterCommand, moveFlagsAfterCommand,
decodeRawOpts,
checkCliOpts,
rawOptsToCliOpts,
optserror, optserror,
showModeHelp, showModeHelp,
debugArgs,
getCliOpts,
-- * tests -- * tests
tests_Hledger_Cli_Options tests_Hledger_Cli_Options
@ -66,9 +86,11 @@ where
import qualified Control.Exception as C import qualified Control.Exception as C
-- import Control.Monad (filterM) -- import Control.Monad (filterM)
import Control.Monad (when)
import Data.List import Data.List
import Data.List.Split import Data.List.Split
import Data.Maybe import Data.Maybe
import Data.PPrint (pprint)
import Data.Time.Calendar import Data.Time.Calendar
import Safe import Safe
import System.Console.CmdArgs import System.Console.CmdArgs
@ -76,6 +98,7 @@ import System.Console.CmdArgs.Explicit
import System.Console.CmdArgs.Text import System.Console.CmdArgs.Text
import System.Directory import System.Directory
import System.Environment import System.Environment
import System.Exit
import Test.HUnit import Test.HUnit
import Text.ParserCombinators.Parsec as P import Text.ParserCombinators.Parsec as P
import Text.Printf import Text.Printf
@ -107,7 +130,7 @@ helpflags = [
inputflags = [ inputflags = [
flagReq ["file","f"] (\s opts -> Right $ setopt "file" s opts) "FILE" "use a different journal file; - means stdin" flagReq ["file","f"] (\s opts -> Right $ setopt "file" s opts) "FILE" "use a different journal file; - means stdin"
,flagReq ["rules-file"] (\s opts -> Right $ setopt "rules-file" s opts) "RULESFILE" "conversion rules for CSV (default: FILE.rules)" ,flagReq ["rules-file"] (\s opts -> Right $ setopt "rules-file" s opts) "RULESFILE" "conversion rules for CSV (default: FILE.rules)"
,flagReq ["alias"] (\s opts -> Right $ setopt "alias" s opts) "ACCT=ALIAS" "display ACCT's name as ALIAS in reports" ,flagReq ["alias"] (\s opts -> Right $ setopt "alias" s opts) "ACCT=ALIAS" "convert ACCT's name to ALIAS"
] ]
-- | Common report-related flags: --period, --cost, --display etc. -- | Common report-related flags: --period, --cost, --display etc.
@ -137,7 +160,9 @@ generalflagsgroup1 = (generalflagstitle, inputflags ++ reportflags ++ helpflags)
generalflagsgroup2 = (generalflagstitle, inputflags ++ helpflags) generalflagsgroup2 = (generalflagstitle, inputflags ++ helpflags)
generalflagsgroup3 = (generalflagstitle, helpflags) generalflagsgroup3 = (generalflagstitle, helpflags)
-- | Template for creating our modes. -- cmdargs modes
-- | A basic mode template.
defMode :: Mode RawOpts defMode :: Mode RawOpts
defMode = Mode { defMode = Mode {
modeNames = [] modeNames = []
@ -147,11 +172,44 @@ defMode = Mode {
,modeCheck = Right ,modeCheck = Right
,modeReform = const Nothing ,modeReform = const Nothing
,modeExpandAt = True ,modeExpandAt = True
,modeGroupFlags = toGroup [] ,modeGroupFlags = Group {
groupNamed = []
,groupUnnamed = [
flagNone ["help","h","?"] (setboolopt "help") "Display command help."
]
,groupHidden = []
}
,modeArgs = ([], Nothing) ,modeArgs = ([], Nothing)
,modeGroupModes = toGroup [] ,modeGroupModes = toGroup []
} }
-- | A basic subcommand mode with the given command name(s).
defCommandMode names = defMode {
modeNames=names
,modeValue=[("command", headDef "" names)]
,modeArgs = ([], Just $ argsFlag "[PATTERNS]")
}
-- | A basic subcommand mode suitable for an add-on command.
defAddonCommandMode addon = defMode {
modeNames = [addon]
,modeHelp = printf "run %s-%s" progname addon
,modeValue=[("command",addon)]
,modeGroupFlags = Group {
groupUnnamed = []
,groupHidden = []
,groupNamed = [generalflagsgroup1]
}
,modeArgs = ([], Just $ argsFlag "[ARGS]")
}
-- | Add command aliases to the command's help string.
withAliases :: String -> [String] -> String
s `withAliases` [] = s
s `withAliases` (a:[]) = s ++ " (alias: " ++ a ++ ")"
s `withAliases` as = s ++ " (aliases: " ++ intercalate ", " as ++ ")"
-- | The top-level cmdargs mode for hledger. -- | The top-level cmdargs mode for hledger.
mainmode addons = defMode { mainmode addons = defMode {
modeNames = [progname] modeNames = [progname]
@ -211,49 +269,7 @@ mainmode addons = defMode {
-- -- ,"When using both, not: comes last." -- -- ,"When using both, not: comes last."
-- ] -- ]
-- -- visible subcommand modes
-- cmdargs modes for subcommands
--
-- | Make a basic command mode given the command's name and any aliases.
defCommandMode names = defMode {
modeNames=names
,modeValue=[("command", headDef "" names)]
,modeArgs = ([], Just $ argsFlag "[PATTERNS]")
}
-- | Make a basic command mode suitable for an add-on command.
defAddonCommandMode addon = defMode {
modeNames = [addon]
,modeHelp = printf "run %s-%s" progname addon
,modeValue=[("command",addon)]
,modeGroupFlags = Group {
groupUnnamed = []
,groupHidden = []
,groupNamed = [generalflagsgroup1]
}
,modeArgs = ([], Just $ argsFlag "[ARGS]")
}
withAliases :: String -> [String] -> String
s `withAliases` [] = s
s `withAliases` (a:[]) = s ++ " (alias: " ++ a ++ ")"
s `withAliases` as = s ++ " (aliases: " ++ intercalate ", " as ++ ")"
-- hidden commands
convertmode = (defCommandMode ["convert"]) {
modeValue = [("command","convert")]
,modeHelp = "convert is no longer needed, just use -f FILE.csv"
,modeArgs = ([], Just $ argsFlag "[CSVFILE]")
,modeGroupFlags = Group {
groupUnnamed = []
,groupHidden = helpflags
,groupNamed = []
}
}
-- visible commands
addmode = (defCommandMode ["add"]) { addmode = (defCommandMode ["add"]) {
modeHelp = "prompt for new transaction entries and add them to the journal" modeHelp = "prompt for new transaction entries and add them to the journal"
@ -375,6 +391,19 @@ testmode = (defCommandMode ["test"]) {
} }
} }
-- hidden commands
convertmode = (defCommandMode ["convert"]) {
modeValue = [("command","convert")]
,modeHelp = "convert is no longer needed, just use -f FILE.csv"
,modeArgs = ([], Just $ argsFlag "[CSVFILE]")
,modeGroupFlags = Group {
groupUnnamed = []
,groupHidden = helpflags
,groupNamed = []
}
}
-- --
-- 2. A package-specific data structure holding options used in this -- 2. A package-specific data structure holding options used in this
-- package and above, parsed from RawOpts. This represents the -- package and above, parsed from RawOpts. This represents the
@ -411,8 +440,8 @@ instance Default CliOpts where def = defcliopts
-- | Parse raw option string values to the desired final data types. -- | Parse raw option string values to the desired final data types.
-- Any relative smart dates will be converted to fixed dates based on -- Any relative smart dates will be converted to fixed dates based on
-- today's date. Parsing failures will raise an error. -- today's date. Parsing failures will raise an error.
toCliOpts :: RawOpts -> IO CliOpts rawOptsToCliOpts :: RawOpts -> IO CliOpts
toCliOpts rawopts = do rawOptsToCliOpts rawopts = do
d <- getCurrentDay d <- getCurrentDay
return defcliopts { return defcliopts {
rawopts_ = rawopts rawopts_ = rawopts
@ -451,16 +480,15 @@ toCliOpts rawopts = do
} }
} }
-- | Parse hledger CLI options from the command line arguments and -- | Parse hledger CLI options from these command line arguments and
-- specified add-on command names, or raise any error. -- add-on command names, or raise any error.
getHledgerCliOpts :: [String] -> IO CliOpts argsToCliOpts :: [String] -> [String] -> IO CliOpts
getHledgerCliOpts addons = do argsToCliOpts args addons = do
args <- getArgs
let let
args' = moveFlagsAfterCommand args args' = moveFlagsAfterCommand args
cmdargsopts = System.Console.CmdArgs.Explicit.processValue (mainmode addons) args' cmdargsopts = System.Console.CmdArgs.Explicit.processValue (mainmode addons) args'
cmdargsopts' = decodeRawOpts cmdargsopts cmdargsopts' = decodeRawOpts cmdargsopts
toCliOpts cmdargsopts' >>= checkCliOpts rawOptsToCliOpts cmdargsopts' >>= checkCliOpts
-- | A hacky workaround for cmdargs not accepting flags before the -- | A hacky workaround for cmdargs not accepting flags before the
-- subcommand name: try to detect and move such flags after the -- subcommand name: try to detect and move such flags after the
@ -505,7 +533,9 @@ checkCliOpts opts@CliOpts{reportopts_=ropts} = do
Right _ -> return () Right _ -> return ()
return opts return opts
--
-- utils -- utils
--
-- | Get the unique suffixes (without hledger-) of hledger-* executables -- | Get the unique suffixes (without hledger-) of hledger-* executables
-- found in the current user's PATH, or the empty list if there is any -- found in the current user's PATH, or the empty list if there is any
@ -677,6 +707,31 @@ showModeHelp =
. .
(helpText [] HelpFormatDefault :: Mode a -> [Text]) (helpText [] HelpFormatDefault :: Mode a -> [Text])
-- | Print debug info about arguments and options if --debug is present.
debugArgs :: [String] -> CliOpts -> IO ()
debugArgs args opts =
when ("--debug" `elem` args) $ do
progname <- getProgName
putStrLn $ "running: " ++ progname
putStrLn $ "raw args: " ++ show args
putStrLn $ "processed opts:\n" ++ show opts
putStrLn . show =<< pprint opts
d <- getCurrentDay
putStrLn $ "search query: " ++ (show $ queryFromOpts d $ reportopts_ opts)
-- | Parse hledger CLI options from the command line using the given
-- cmdargs mode, and either return them or, if a help flag is present,
-- print the mode help and exit the program.
getCliOpts :: Mode RawOpts -> IO CliOpts
getCliOpts mode = do
args <- getArgs
let rawopts = decodeRawOpts $ processValue mode args
opts <- rawOptsToCliOpts rawopts >>= checkCliOpts
debugArgs args opts
-- if any (`elem` args) ["--help","-h","-?"]
when ("help" `inRawOpts` rawopts_ opts) $
putStr (showModeHelp mode) >> exitSuccess
return opts
tests_Hledger_Cli_Options = TestList tests_Hledger_Cli_Options = TestList
[ [