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:
		
							parent
							
								
									c010a6df48
								
							
						
					
					
						commit
						83a518af99
					
				| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 " (" | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user