diff --git a/BalanceCommand.hs b/BalanceCommand.hs index 62e11858b..248ce0978 100644 --- a/BalanceCommand.hs +++ b/BalanceCommand.hs @@ -125,7 +125,7 @@ showBalanceReport opts args l = acctsstr ++ (if collapse then "" else totalstr) showatree t = showAccountTreeWithBalances matchedacctnames t matchedacctnames = balancereportacctnames l sub apats t t = (if empty then id else pruneZeroBalanceLeaves) $ ledgerAccountTree maxdepth l - apats = fst $ parseAccountDescriptionArgs args + apats = fst $ parseAccountDescriptionArgs opts args maxdepth = fromMaybe 9999 $ depthFromOpts opts sub = SubTotal `elem` opts || (isJust $ depthFromOpts opts) empty = Empty `elem` opts diff --git a/Ledger/AccountName.hs b/Ledger/AccountName.hs index ace77d78b..5316cee38 100644 --- a/Ledger/AccountName.hs +++ b/Ledger/AccountName.hs @@ -11,9 +11,6 @@ import Ledger.Utils import Ledger.Types --- change to permit options anywhere on the command line. ^ is a good choice -negativepatternchar = '-' - -- change to use a different separator for nested accounts acctsepchar = ':' @@ -171,9 +168,8 @@ match_negative_pats pats str = (not $ null ns) && (any match ns) match "" = True match p = matchregex (abspat p) str -isnegativepat pat = (== [negativepatternchar]) $ take 1 pat +isnegativepat pat = take 1 pat `elem` ["-","^"] abspat pat = if isnegativepat pat then drop 1 pat else pat positivepats = filter (not . isnegativepat) negativepats = filter isnegativepat matchregex pat str = containsRegex (mkRegexWithOpts pat True True) str - diff --git a/Options.hs b/Options.hs index 3260b867d..a0f1c2fb8 100644 --- a/Options.hs +++ b/Options.hs @@ -4,29 +4,35 @@ import System import System.Console.GetOpt import System.Directory import Text.Printf -import Ledger.AccountName (negativepatternchar) import Ledger.Parse (smartparsedate) import Ledger.Dates import Ledger.Utils -usagehdr = "Usage: hledger [OPTS] COMMAND [ACCTPATTERNS] [-- DESCPATTERNS]\n\nOptions"++warning++":" -warning = if negativepatternchar=='-' then " (must appear before command)" else " (can appear anywhere)" +usage opts = usageInfo usagehdr options ++ usageftr + +negativePatternChar opts + | OptionsAnywhere `elem` opts = '^' + | otherwise = '-' + +usagehdr = "Usage: hledger [OPTS] COMMAND [ACCTPATTERNS] [-- DESCPATTERNS]\n" ++ + "\n" ++ + "Options (before command, unless using --options-anywhere):" usageftr = "\n" ++ "Commands (may be abbreviated):\n" ++ " balance - show account balances\n" ++ " print - show formatted ledger entries\n" ++ " register - show register transactions\n" ++ "\n" ++ - "Account and description patterns can be used to filter by account name\n" ++ - "and entry description. They are regular expressions, optionally prefixed\n" ++ - "with " ++ [negativepatternchar] ++ " to make them negative.\n" ++ + "Account and description patterns are regular expressions which filter by\n" ++ + "account name and entry description. Prefix a pattern with - to negate it,\n" ++ + "and separate account and description patterns with --.\n" ++ + "(With --options-anywhere, use ^ and ^^.)\n" ++ "\n" ++ "Also: hledger [-v] test [TESTPATTERNS] to run self-tests.\n" ++ "\n" defaultfile = "~/.ledger" fileenvvar = "LEDGER" -optionorder = if negativepatternchar=='-' then RequireOrder else Permute -- | Command-line options we accept. options :: [OptDescr Opt] @@ -41,6 +47,7 @@ options = [ "(where EXPR is 'dOP[Y/M/D]', OP is <, <=, =, >=, >)"), Option ['E'] ["empty"] (NoArg Empty) "balance report: show accounts with zero balance", Option ['R'] ["real"] (NoArg Real) "report only on real (non-virtual) transactions", + Option [] ["options-anywhere"] (NoArg OptionsAnywhere) "allow options anywhere, use ^ to negate patterns", Option ['n'] ["collapse"] (NoArg Collapse) "balance report: no grand total", Option ['s'] ["subtotal"] (NoArg SubTotal) "balance report: show subaccounts", Option ['h'] ["help"] (NoArg Help) "show this help", @@ -62,6 +69,7 @@ data Opt = Display String | Empty | Real | + OptionsAnywhere | Collapse | SubTotal | Help | @@ -69,8 +77,6 @@ data Opt = Version deriving (Show,Eq) -usage = usageInfo usagehdr options ++ usageftr - versionno = "0.3pre" version = printf "hledger version %s \n" versionno :: String @@ -79,10 +85,11 @@ version = printf "hledger version %s \n" versionno :: String parseArguments :: IO ([Opt], String, [String]) parseArguments = do args <- getArgs - case (getOpt optionorder options args) of + let order = if "--options-anywhere" `elem` args then Permute else RequireOrder + case (getOpt order options args) of (opts,cmd:args,[]) -> return (opts, cmd, args) (opts,[],[]) -> return (opts, [], []) - (_,_,errs) -> ioError (userError (concat errs ++ usage)) + (opts,_,errs) -> ioError (userError (concat errs ++ usage opts)) -- | Get the ledger file path from options, an environment variable, or a default ledgerFilePathFromOpts :: [Opt] -> IO String @@ -153,11 +160,15 @@ displayFromOpts opts = -- | Gather any ledger-style account/description pattern arguments into -- two lists. These are 0 or more account patterns optionally followed by --- -- and 0 or more description patterns. -parseAccountDescriptionArgs :: [String] -> ([String],[String]) -parseAccountDescriptionArgs args = (as, ds') - where (as, ds) = break (=="--") args - ds' = dropWhile (=="--") ds +-- a separator and then 0 or more description patterns. The separator is +-- usually -- but with --options-anywhere is ^^ so we need to provide the +-- options as well. +parseAccountDescriptionArgs :: [Opt] -> [String] -> ([String],[String]) +parseAccountDescriptionArgs opts args = (as, ds') + where (as, ds) = break (==patseparator) args + ds' = dropWhile (==patseparator) ds + patseparator = replicate 2 negchar + negchar = negativePatternChar opts -- testoptions RequireOrder ["foo","-v"] -- testoptions Permute ["foo","-v"] @@ -168,5 +179,5 @@ parseAccountDescriptionArgs args = (as, ds') testoptions order cmdline = putStr $ case getOpt order options cmdline of (o,n,[] ) -> "options=" ++ show o ++ " args=" ++ show n - (_,_,errs) -> concat errs ++ usage + (o,_,errs) -> concat errs ++ usage o diff --git a/PrintCommand.hs b/PrintCommand.hs index ce99e6164..65c23d12c 100644 --- a/PrintCommand.hs +++ b/PrintCommand.hs @@ -18,4 +18,4 @@ showEntries :: [Opt] -> [String] -> Ledger -> String showEntries opts args l = concatMap showEntry $ filteredentries where filteredentries = entries $ filterRawLedgerEntriesByAccount apats $ rawledger l - (apats,_) = parseAccountDescriptionArgs args + (apats,_) = parseAccountDescriptionArgs opts args diff --git a/README b/README index 153c7610f..8d049319e 100644 --- a/README +++ b/README @@ -80,6 +80,7 @@ hledger-specific features: --depth=N balance report: maximum account depth to show --cost alias for basis + --options-anywhere allow options anywhere, use ^ for negative patterns ledger features not supported: diff --git a/RegisterCommand.hs b/RegisterCommand.hs index 4497f9994..3c8d21a17 100644 --- a/RegisterCommand.hs +++ b/RegisterCommand.hs @@ -30,7 +30,7 @@ showRegisterReport opts args l = showtxns ts nulltxn nullmixedamt where ts = filter matchapats $ ledgerTransactions l matchapats t = matchpats apats $ account t - apats = fst $ parseAccountDescriptionArgs args + apats = fst $ parseAccountDescriptionArgs opts args matchdisplayopt Nothing t = True matchdisplayopt (Just e) t = (fromparse $ parsewith datedisplayexpr e) t dopt = displayFromOpts opts diff --git a/Utils.hs b/Utils.hs index 28779a934..98b0db9e9 100644 --- a/Utils.hs +++ b/Utils.hs @@ -21,7 +21,7 @@ ledgerfromstring :: [String] -> String -> Ledger ledgerfromstring args s = cacheLedger apats $ filterRawLedger Nothing Nothing dpats False False l where - (apats,dpats) = parseAccountDescriptionArgs args + (apats,dpats) = parseAccountDescriptionArgs [] args l = rawledgerfromstring s -- | Get a RawLedger from the given file path, or a dummy one if there was an error. @@ -37,7 +37,7 @@ ledgerfromfile args f = do l <- rawledgerfromfile f return $ cacheLedger apats $ filterRawLedger Nothing Nothing dpats False False l where - (apats,dpats) = parseAccountDescriptionArgs args + (apats,dpats) = parseAccountDescriptionArgs [] args -- | Get a RawLedger from the file your LEDGER environment variable -- variable points to, or a dummy one if there was a problem. diff --git a/hledger.hs b/hledger.hs index 796d698a0..ab08a7ec6 100644 --- a/hledger.hs +++ b/hledger.hs @@ -57,13 +57,13 @@ main = do run cmd opts args where run cmd opts args - | Help `elem` opts = putStr usage + | Help `elem` opts = putStr $ usage opts | Version `elem` opts = putStr version | cmd `isPrefixOf` "balance" = parseLedgerAndDo opts args balance | cmd `isPrefixOf` "print" = parseLedgerAndDo opts args print' | cmd `isPrefixOf` "register" = parseLedgerAndDo opts args register | cmd `isPrefixOf` "test" = runtests opts args >> return () - | otherwise = putStr usage + | otherwise = putStr $ usage opts -- | parse the user's specified ledger file and do some action with it -- (or report a parse error). This function makes the whole thing go. @@ -74,7 +74,7 @@ parseLedgerAndDo opts args cmd = runcmd = cmd opts args . cacheLedger apats . filterRawLedger b e dpats c r . canonicaliseAmounts costbasis b = beginDateFromOpts opts e = endDateFromOpts opts - (apats,dpats) = parseAccountDescriptionArgs args + (apats,dpats) = parseAccountDescriptionArgs opts args c = Cleared `elem` opts r = Real `elem` opts costbasis = CostBasis `elem` opts