lib,cli,ui: In ReportOpts, store query terms term-by-term in a list in

querystring_.

This helps deal with tricky quoting issues, as we no longer have to make
sure everything is quoted properly before merging it into a string.
This commit is contained in:
Stephen Morgan 2020-11-04 22:19:26 +11:00 committed by Simon Michael
parent c010a6df48
commit 83a518af99
9 changed files with 67 additions and 43 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 " ("

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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