diff --git a/hledger-lib/Hledger/Query.hs b/hledger-lib/Hledger/Query.hs index 8f3ebd75f..c7a377789 100644 --- a/hledger-lib/Hledger/Query.hs +++ b/hledger-lib/Hledger/Query.hs @@ -20,6 +20,7 @@ module Hledger.Query ( generatedTransactionTag, -- * parsing parseQuery, + parseQueryList, simplifyQuery, filterQuery, -- * accessors @@ -141,9 +142,23 @@ data QueryOpt = QueryOptInAcctOnly AccountName -- ^ show an account register fo -- showAccountMatcher _ = Nothing --- | Convert a query expression containing zero or more --- space-separated terms to a query and zero or more query options; or --- return an error message if query parsing fails. +-- | A version of parseQueryList which acts on a single Text of +-- space-separated terms. +-- +-- The usual shell quoting rules are assumed. When a pattern contains +-- whitespace, it (or the whole term including prefix) should be enclosed +-- in single or double quotes. +-- +-- >>> parseQuery nulldate "expenses:dining out" +-- Right (Or [Acct (RegexpCI "expenses:dining"),Acct (RegexpCI "out")],[]) +-- +-- >>> parseQuery nulldate "\"expenses:dining out\"" +-- Right (Acct (RegexpCI "expenses:dining out"),[]) +parseQuery :: Day -> T.Text -> Either String (Query,[QueryOpt]) +parseQuery d = parseQueryList d . words'' prefixes + +-- | Convert a list of query expression containing to a query and zero +-- or more query options; or return an error message if query parsing fails. -- -- A query term is either: -- @@ -161,10 +176,6 @@ data QueryOpt = QueryOptInAcctOnly AccountName -- ^ show an account register fo -- -- inacct:FULLACCTNAME -- --- The usual shell quoting rules are assumed. When a pattern contains --- whitespace, it (or the whole term including prefix) should be enclosed --- in single or double quotes. --- -- Period expressions may contain relative dates, so a reference date is -- required to fully parse these. -- @@ -173,15 +184,8 @@ data QueryOpt = QueryOptInAcctOnly AccountName -- ^ show an account register fo -- 2. multiple description patterns are OR'd together -- 3. multiple status patterns are OR'd together -- 4. then all terms are AND'd together --- --- >>> parseQuery nulldate "expenses:dining out" --- Right (Or [Acct (RegexpCI "expenses:dining"),Acct (RegexpCI "out")],[]) --- --- >>> parseQuery nulldate "\"expenses:dining out\"" --- Right (Acct (RegexpCI "expenses:dining out"),[]) -parseQuery :: Day -> T.Text -> Either String (Query,[QueryOpt]) -parseQuery d s = do - let termstrs = words'' prefixes s +parseQueryList :: Day -> [T.Text] -> Either String (Query, [QueryOpt]) +parseQueryList d termstrs = do eterms <- sequence $ map (parseQueryTerm d) termstrs let (pats, opts) = partitionEithers eterms (descpats, pats') = partition queryIsDesc pats diff --git a/hledger-lib/Hledger/Reports/ReportOptions.hs b/hledger-lib/Hledger/Reports/ReportOptions.hs index 741bb367a..747eac4a6 100644 --- a/hledger-lib/Hledger/Reports/ReportOptions.hs +++ b/hledger-lib/Hledger/Reports/ReportOptions.hs @@ -92,7 +92,7 @@ data ReportOpts = ReportOpts { ,no_elide_ :: Bool ,real_ :: Bool ,format_ :: StringFormat - ,querystring_ :: T.Text + ,querystring_ :: [T.Text] -- ,average_ :: Bool -- for posting reports (register) @@ -141,7 +141,7 @@ defreportopts = ReportOpts , no_elide_ = False , real_ = False , format_ = def - , querystring_ = "" + , querystring_ = [] , average_ = False , related_ = False , txn_dates_ = False @@ -168,8 +168,7 @@ rawOptsToReportOpts rawopts = do let colorflag = stringopt "color" rawopts formatstring = maybestringopt "format" rawopts - querystring = T.pack . unwords . map quoteIfNeeded $ - listofstringopt "args" rawopts -- doesn't handle an arg like "" right + querystring = map T.pack $ listofstringopt "args" rawopts -- doesn't handle an arg like "" right format <- case parseStringFormat <$> formatstring of Nothing -> return defaultBalanceLineFormat @@ -237,7 +236,7 @@ defreportspec = ReportSpec -- | Generate a ReportSpec from a set of ReportOpts on a given day. reportOptsToSpec :: Day -> ReportOpts -> Either String ReportSpec reportOptsToSpec day ropts = do - (argsquery, queryopts) <- parseQuery day $ querystring_ ropts + (argsquery, queryopts) <- parseQueryList day $ querystring_ ropts return ReportSpec { rsOpts = ropts , rsToday = day diff --git a/hledger-ui/Hledger/UI/AccountsScreen.hs b/hledger-ui/Hledger/UI/AccountsScreen.hs index 7a11b7140..8d42cd37d 100644 --- a/hledger-ui/Hledger/UI/AccountsScreen.hs +++ b/hledger-ui/Hledger/UI/AccountsScreen.hs @@ -173,7 +173,7 @@ asDraw UIState{aopts=_uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec}} <+> toggles <+> str (" account " ++ if ishistorical then "balances" else "changes") <+> borderPeriodStr (if ishistorical then "at end of" else "in") (period_ ropts) - <+> borderQueryStr (T.unpack $ querystring_ ropts) + <+> borderQueryStr (unwords . map (quoteIfNeeded . T.unpack) $ querystring_ ropts) <+> borderDepthStr mdepth <+> str (" ("++curidx++"/"++totidx++")") <+> (if ignore_assertions_ $ inputopts_ copts diff --git a/hledger-ui/Hledger/UI/RegisterScreen.hs b/hledger-ui/Hledger/UI/RegisterScreen.hs index d37292569..aa81bdf63 100644 --- a/hledger-ui/Hledger/UI/RegisterScreen.hs +++ b/hledger-ui/Hledger/UI/RegisterScreen.hs @@ -200,7 +200,7 @@ rsDraw UIState{aopts=_uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec}} <+> togglefilters <+> str " transactions" -- <+> str (if ishistorical then " historical total" else " period total") - <+> borderQueryStr (T.unpack $ querystring_ ropts) + <+> borderQueryStr (unwords . map (quoteIfNeeded . T.unpack) $ querystring_ ropts) -- <+> str " and subs" <+> borderPeriodStr "in" (period_ ropts) <+> str " (" diff --git a/hledger-ui/Hledger/UI/TransactionScreen.hs b/hledger-ui/Hledger/UI/TransactionScreen.hs index 94215a810..c69bca6bb 100644 --- a/hledger-ui/Hledger/UI/TransactionScreen.hs +++ b/hledger-ui/Hledger/UI/TransactionScreen.hs @@ -97,7 +97,7 @@ tsDraw UIState{aopts=UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{ <+> withAttr ("border" <> "bold") (str $ show i) <+> str (" of "++show (length nts)) <+> togglefilters - <+> borderQueryStr (T.unpack $ querystring_ ropts) + <+> borderQueryStr (unwords . map (quoteIfNeeded . T.unpack) $ querystring_ ropts) <+> str (" in "++T.unpack (replaceHiddenAccountsNameWith "All" acct)++")") <+> (if ignore_assertions_ $ inputopts_ copts then withAttr ("border" <> "query") (str " ignoring balance assertions") else str "") where diff --git a/hledger-ui/Hledger/UI/UIState.hs b/hledger-ui/Hledger/UI/UIState.hs index 557163de3..2baa712f1 100644 --- a/hledger-ui/Hledger/UI/UIState.hs +++ b/hledger-ui/Hledger/UI/UIState.hs @@ -243,7 +243,8 @@ setFilter :: String -> UIState -> UIState setFilter s ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rspec@ReportSpec{rsOpts=ropts}}}} = ui{aopts=uopts{cliopts_=copts{reportspec_=newrspec}}} where - newrspec = either (const rspec) id $ reportOptsToSpec (rsToday rspec) ropts{querystring_=T.pack s} + newrspec = either (const rspec) id $ reportOptsToSpec (rsToday rspec) ropts{querystring_=querystring} + querystring = words'' prefixes $ T.pack s -- | Reset some filters & toggles. resetFilter :: UIState -> UIState @@ -255,7 +256,7 @@ resetFilter ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportspec_=rsp empty_=True ,statuses_=[] ,real_=False - ,querystring_="" + ,querystring_=[] --,period_=PeriodAll }}}}} @@ -312,7 +313,8 @@ showMinibuffer :: UIState -> UIState showMinibuffer ui = setMode (Minibuffer e) ui where e = applyEdit gotoEOL $ editor MinibufferEditor (Just 1) oldq - oldq = T.unpack . querystring_ . rsOpts . reportspec_ . cliopts_ $ aopts ui + oldq = unwords . map (quoteIfNeeded . T.unpack) + . querystring_ . rsOpts . reportspec_ . cliopts_ $ aopts ui -- | Close the minibuffer, discarding any edit in progress. closeMinibuffer :: UIState -> UIState diff --git a/hledger/Hledger/Cli/Commands/Aregister.hs b/hledger/Hledger/Cli/Commands/Aregister.hs index b7a316ccc..19f06aa0c 100644 --- a/hledger/Hledger/Cli/Commands/Aregister.hs +++ b/hledger/Hledger/Cli/Commands/Aregister.hs @@ -76,8 +76,8 @@ aregister opts@CliOpts{rawopts_=rawopts,reportspec_=rspec} j = do -- the first argument specifies the account, any remaining arguments are a filter query (apat,querystring) <- case listofstringopt "args" rawopts of [] -> fail "aregister needs an account, please provide an account name or pattern" - (a:as) -> return (a, T.pack . unwords $ map quoteIfNeeded as) - argsquery <- either fail (return . fst) $ parseQuery d querystring + (a:as) -> return (a, map T.pack as) + argsquery <- either fail (return . fst) $ parseQueryList d querystring let acct = headDef (error' $ show apat++" did not match any account") -- PARTIAL: . filterAccts $ journalAccountNames j diff --git a/hledger/Hledger/Cli/Commands/Tags.hs b/hledger/Hledger/Cli/Commands/Tags.hs index 5a7158016..2a30888b0 100755 --- a/hledger/Hledger/Cli/Commands/Tags.hs +++ b/hledger/Hledger/Cli/Commands/Tags.hs @@ -31,12 +31,12 @@ tags CliOpts{rawopts_=rawopts,reportspec_=rspec} j = do let args = listofstringopt "args" rawopts mtagpat <- mapM (either Fail.fail pure . toRegexCI) $ headMay args let - querystring = T.pack . unwords . map quoteIfNeeded $ drop 1 args + querystring = map T.pack $ drop 1 args values = boolopt "values" rawopts parsed = boolopt "parsed" rawopts empty = empty_ $ rsOpts rspec - argsquery <- either usageError (return . fst) $ parseQuery d querystring + argsquery <- either usageError (return . fst) $ parseQueryList d querystring let q = simplifyQuery $ And [queryFromFlags $ rsOpts rspec, argsquery] txns = filter (q `matchesTransaction`) $ jtxns $ journalSelectingAmountFromOpts (rsOpts rspec) j diff --git a/tests/cli/query-args.test b/tests/cli/query-args.test index 557892ce2..57e133c3c 100644 --- a/tests/cli/query-args.test +++ b/tests/cli/query-args.test @@ -1,17 +1,17 @@ # 1. account pattern with space -hledger -f- register 'a a' -<<< +< 2010/3/1 x a a 1 b ->>> + +$ hledger -f- register 'a a' +> 2010-03-01 x a a 1 1 ->>>=0 +>=0 # # 2. description pattern with space -hledger -f- register desc:'x x' -<<< +< 2010/3/1 x a 1 b @@ -19,19 +19,38 @@ hledger -f- register desc:'x x' 2010/3/2 x x a 1 b ->>> + +$ hledger -f- register desc:'x x' +> 2010-03-02 x x a 1 1 b -1 0 ->>>=0 +>=0 # # 3. multiple patterns, spaced and punctuated patterns -hledger -f- register 'a a' "'b" -<<< +< 2011/9/11 a a 1 'b ->>> + +$ hledger -f- register 'a a' "'b" +> 2011-09-11 a a 1 1 'b -1 0 ->>>=0 +>=0 + +# +# 4. patterns with quotation marks in them +< +2020-09-19 Quoting + assets:bank -5 + assets:unquoted 5 + +2020-09-20 Quoting + assets:bank -5 + assets:"quoted" 5 + +$ hledger -f- register '"quoted' +> +2020-09-20 Quoting assets:"quoted" 5 5 +>=0