new query by tag implementation
This commit is contained in:
		
							parent
							
								
									2fb2aea056
								
							
						
					
					
						commit
						cb905a741c
					
				| @ -890,10 +890,9 @@ currently supported: | |||||||
| - `status:1` or `status:0` - match cleared/uncleared transactions | - `status:1` or `status:0` - match cleared/uncleared transactions | ||||||
| - `depth:N` - match (or display, depending on command) accounts at or above this depth | - `depth:N` - match (or display, depending on command) accounts at or above this depth | ||||||
| - `not:` before any of the above negates the match | - `not:` before any of the above negates the match | ||||||
|  | - `tag:NAME[=VALUEREGEX]` - match by exact [tag](#tags) name, and optionally match the tag value by regular expression | ||||||
| 
 | 
 | ||||||
| <!-- | <!-- | ||||||
| - `tag:TAGNAMEREGEX[:TAGVALUEREGEX]` - match a [tag](#tags) name, and |  | ||||||
|   optionally the value, by regular expression |  | ||||||
| - `TAGNAME:[TAGVALUEREGEX]` - match a tag name exactly, and optionally | - `TAGNAME:[TAGVALUEREGEX]` - match a tag name exactly, and optionally | ||||||
|   the value by regular expression. |   the value by regular expression. | ||||||
| - `code:CODEREGEX` --> | - `code:CODEREGEX` --> | ||||||
|  | |||||||
| @ -17,6 +17,8 @@ module Hledger.Data.Posting ( | |||||||
|   isBalancedVirtual, |   isBalancedVirtual, | ||||||
|   isEmptyPosting, |   isEmptyPosting, | ||||||
|   hasAmount, |   hasAmount, | ||||||
|  |   postingAllTags, | ||||||
|  |   transactionAllTags, | ||||||
|   -- * date operations |   -- * date operations | ||||||
|   postingDate, |   postingDate, | ||||||
|   isPostingInDateSpan, |   isPostingInDateSpan, | ||||||
| @ -127,6 +129,14 @@ postingCleared p = if pstatus p | |||||||
|                     then True |                     then True | ||||||
|                     else maybe False tstatus $ ptransaction p |                     else maybe False tstatus $ ptransaction p | ||||||
| 
 | 
 | ||||||
|  | -- | Tags for this posting including any inherited from its parent transaction. | ||||||
|  | postingAllTags :: Posting -> [Tag] | ||||||
|  | postingAllTags p = ptags p ++ maybe [] transactionAllTags (ptransaction p) | ||||||
|  | 
 | ||||||
|  | -- | Tags for this transaction including any inherited from above, when that is implemented. | ||||||
|  | transactionAllTags :: Transaction -> [Tag] | ||||||
|  | transactionAllTags t = ttags t | ||||||
|  | 
 | ||||||
| -- | Does this posting fall within the given date span ? | -- | Does this posting fall within the given date span ? | ||||||
| isPostingInDateSpan :: DateSpan -> Posting -> Bool | isPostingInDateSpan :: DateSpan -> Posting -> Bool | ||||||
| isPostingInDateSpan s = spanContainsDate s . postingDate | isPostingInDateSpan s = spanContainsDate s . postingDate | ||||||
|  | |||||||
| @ -36,14 +36,13 @@ import Data.Either | |||||||
| import Data.List | import Data.List | ||||||
| import Data.Maybe | import Data.Maybe | ||||||
| import Data.Time.Calendar | import Data.Time.Calendar | ||||||
| import Safe (readDef, headDef) | import Safe (readDef, headDef, headMay) | ||||||
| import Test.HUnit | import Test.HUnit | ||||||
| import Text.ParserCombinators.Parsec | import Text.ParserCombinators.Parsec | ||||||
| 
 | 
 | ||||||
| import Hledger.Utils | import Hledger.Utils | ||||||
| import Hledger.Data.Types | import Hledger.Data.Types | ||||||
| import Hledger.Data.AccountName | import Hledger.Data.AccountName | ||||||
| import Hledger.Data.Amount |  | ||||||
| import Hledger.Data.Dates | import Hledger.Data.Dates | ||||||
| import Hledger.Data.Posting | import Hledger.Data.Posting | ||||||
| import Hledger.Data.Transaction | import Hledger.Data.Transaction | ||||||
| @ -65,6 +64,8 @@ data Query = Any              -- ^ always match | |||||||
|            | Empty Bool       -- ^ if true, show zero-amount postings/accounts which are usually not shown |            | Empty Bool       -- ^ if true, show zero-amount postings/accounts which are usually not shown | ||||||
|                               --   more of a query option than a query criteria ? |                               --   more of a query option than a query criteria ? | ||||||
|            | Depth Int        -- ^ match if account depth is less than or equal to this value |            | Depth Int        -- ^ match if account depth is less than or equal to this value | ||||||
|  |            | Tag String (Maybe String)  -- ^ match if a tag with this exact name, and with value | ||||||
|  |                                         -- matching the regexp if provided, exists | ||||||
|     deriving (Show, Eq) |     deriving (Show, Eq) | ||||||
| 
 | 
 | ||||||
| -- | A query option changes a query's/report's behaviour and output in some way. | -- | A query option changes a query's/report's behaviour and output in some way. | ||||||
| @ -128,8 +129,6 @@ tests_parseQuery = [ | |||||||
|     parseQuery d "inacct:a inacct:b" `is` (Any, [QueryOptInAcct "a", QueryOptInAcct "b"]) |     parseQuery d "inacct:a inacct:b" `is` (Any, [QueryOptInAcct "a", QueryOptInAcct "b"]) | ||||||
|     parseQuery d "desc:'x x'" `is` (Desc "x x", []) |     parseQuery d "desc:'x x'" `is` (Desc "x x", []) | ||||||
|     parseQuery d "'a a' 'b" `is` (Or [Acct "a a",Acct "'b"], []) |     parseQuery d "'a a' 'b" `is` (Or [Acct "a a",Acct "'b"], []) | ||||||
|     -- parseQuery d "a b desc:x desc:y status:1" `is`  |  | ||||||
|     --   (And [Or [Acct "a", Acct "b"], Or [Desc "x", Desc "y"], Status True], []) |  | ||||||
|  ] |  ] | ||||||
| 
 | 
 | ||||||
| -- keep synced with patterns below, excluding "not" | -- keep synced with patterns below, excluding "not" | ||||||
| @ -200,6 +199,7 @@ parseQueryTerm _ ('s':'t':'a':'t':'u':'s':':':s) = Left $ Status $ parseStatus s | |||||||
| parseQueryTerm _ ('r':'e':'a':'l':':':s) = Left $ Real $ parseBool s | parseQueryTerm _ ('r':'e':'a':'l':':':s) = Left $ Real $ parseBool s | ||||||
| parseQueryTerm _ ('e':'m':'p':'t':'y':':':s) = Left $ Empty $ parseBool s | parseQueryTerm _ ('e':'m':'p':'t':'y':':':s) = Left $ Empty $ parseBool s | ||||||
| parseQueryTerm _ ('d':'e':'p':'t':'h':':':s) = Left $ Depth $ readDef 0 s | parseQueryTerm _ ('d':'e':'p':'t':'h':':':s) = Left $ Depth $ readDef 0 s | ||||||
|  | parseQueryTerm _ ('t':'a':'g':':':s) = Left $ Tag n v where (n,v) = parseTag s | ||||||
| parseQueryTerm _ "" = Left $ Any | parseQueryTerm _ "" = Left $ Any | ||||||
| parseQueryTerm d s = parseQueryTerm d $ defaultprefix++":"++s | parseQueryTerm d s = parseQueryTerm d $ defaultprefix++":"++s | ||||||
| 
 | 
 | ||||||
| @ -216,8 +216,15 @@ tests_parseQueryTerm = [ | |||||||
|     "date:2008" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2008/01/01") (Just $ parsedate "2009/01/01")) |     "date:2008" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2008/01/01") (Just $ parsedate "2009/01/01")) | ||||||
|     "date:from 2012/5/17" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2012/05/17") Nothing) |     "date:from 2012/5/17" `gives` (Left $ Date $ DateSpan (Just $ parsedate "2012/05/17") Nothing) | ||||||
|     "inacct:a" `gives` (Right $ QueryOptInAcct "a") |     "inacct:a" `gives` (Right $ QueryOptInAcct "a") | ||||||
|  |     "tag:a" `gives` (Left $ Tag "a" Nothing) | ||||||
|  |     "tag:a=some value" `gives` (Left $ Tag "a" (Just "some value")) | ||||||
|  ] |  ] | ||||||
| 
 | 
 | ||||||
|  | parseTag :: String -> (String, Maybe String) | ||||||
|  | parseTag s | '=' `elem` s = (n, Just $ tail v) | ||||||
|  |            | otherwise    = (s, Nothing) | ||||||
|  |            where (n,v) = break (=='=') s | ||||||
|  | 
 | ||||||
| -- | Parse the boolean value part of a "status:" query, allowing "*" as | -- | Parse the boolean value part of a "status:" query, allowing "*" as | ||||||
| -- another way to spell True, similar to the journal file format. | -- another way to spell True, similar to the journal file format. | ||||||
| parseStatus :: String -> Bool | parseStatus :: String -> Bool | ||||||
| @ -419,6 +426,7 @@ matchesAccount (Or ms) a = any (`matchesAccount` a) ms | |||||||
| matchesAccount (And ms) a = all (`matchesAccount` a) ms | matchesAccount (And ms) a = all (`matchesAccount` a) ms | ||||||
| matchesAccount (Acct r) a = regexMatchesCI r a | matchesAccount (Acct r) a = regexMatchesCI r a | ||||||
| matchesAccount (Depth d) a = accountNameLevel a <= d | matchesAccount (Depth d) a = accountNameLevel a <= d | ||||||
|  | matchesAccount (Tag _ _) _ = False | ||||||
| matchesAccount _ _ = True | matchesAccount _ _ = True | ||||||
| 
 | 
 | ||||||
| tests_matchesAccount = [ | tests_matchesAccount = [ | ||||||
| @ -432,6 +440,7 @@ tests_matchesAccount = [ | |||||||
|     assertBool "" $ not $ Depth 2 `matchesAccount` "a:b:c" |     assertBool "" $ not $ Depth 2 `matchesAccount` "a:b:c" | ||||||
|     assertBool "" $ Date nulldatespan `matchesAccount` "a" |     assertBool "" $ Date nulldatespan `matchesAccount` "a" | ||||||
|     assertBool "" $ EDate nulldatespan `matchesAccount` "a" |     assertBool "" $ EDate nulldatespan `matchesAccount` "a" | ||||||
|  |     assertBool "" $ not $ (Tag "a" Nothing) `matchesAccount` "a" | ||||||
|  ] |  ] | ||||||
| 
 | 
 | ||||||
| -- | Does the match expression match this posting ? | -- | Does the match expression match this posting ? | ||||||
| @ -457,6 +466,8 @@ matchesPosting (Depth d) Posting{paccount=a} = Depth d `matchesAccount` a | |||||||
| -- matchesPosting (Empty False) Posting{pamount=a} = True | -- matchesPosting (Empty False) Posting{pamount=a} = True | ||||||
| -- matchesPosting (Empty True) Posting{pamount=a} = isZeroMixedAmount a | -- matchesPosting (Empty True) Posting{pamount=a} = isZeroMixedAmount a | ||||||
| matchesPosting (Empty _) _ = True | matchesPosting (Empty _) _ = True | ||||||
|  | matchesPosting (Tag n Nothing) p = isJust $ lookupTagByName n $ postingAllTags p | ||||||
|  | matchesPosting (Tag n (Just v)) p = isJust $ lookupTagByNameAndValue (n,v) $ postingAllTags p | ||||||
| -- matchesPosting _ _ = False | -- matchesPosting _ _ = False | ||||||
| 
 | 
 | ||||||
| tests_matchesPosting = [ | tests_matchesPosting = [ | ||||||
| @ -476,6 +487,15 @@ tests_matchesPosting = [ | |||||||
|     assertBool "real:1 on virtual posting fails" $ not $ (Real True) `matchesPosting` nullposting{ptype=VirtualPosting} |     assertBool "real:1 on virtual posting fails" $ not $ (Real True) `matchesPosting` nullposting{ptype=VirtualPosting} | ||||||
|     assertBool "real:1 on balanced virtual posting fails" $ not $ (Real True) `matchesPosting` nullposting{ptype=BalancedVirtualPosting} |     assertBool "real:1 on balanced virtual posting fails" $ not $ (Real True) `matchesPosting` nullposting{ptype=BalancedVirtualPosting} | ||||||
|     assertBool "" $ (Acct "'b") `matchesPosting` nullposting{paccount="'b"} |     assertBool "" $ (Acct "'b") `matchesPosting` nullposting{paccount="'b"} | ||||||
|  |     assertBool "" $ not $ (Tag "a" (Just "r$")) `matchesPosting` nullposting | ||||||
|  |     assertBool "" $ (Tag "foo" Nothing) `matchesPosting` nullposting{ptags=[("foo","")]} | ||||||
|  |     assertBool "" $ (Tag "foo" Nothing) `matchesPosting` nullposting{ptags=[("foo","baz")]} | ||||||
|  |     assertBool "" $ (Tag "foo" (Just "a")) `matchesPosting` nullposting{ptags=[("foo","bar")]} | ||||||
|  |     assertBool "" $ not $ (Tag "foo" (Just "a$")) `matchesPosting` nullposting{ptags=[("foo","bar")]} | ||||||
|  |     assertBool "" $ not $ (Tag " foo " (Just "a")) `matchesPosting` nullposting{ptags=[("foo","bar")]} | ||||||
|  |     assertBool "" $ not $ (Tag "foo foo" (Just " ar ba ")) `matchesPosting` nullposting{ptags=[("foo foo","bar bar")]} | ||||||
|  |     -- a tag match on a posting also sees inherited tags | ||||||
|  |     assertBool "" $ (Tag "txntag" Nothing) `matchesPosting` nullposting{ptransaction=Just nulltransaction{ttags=[("txntag","")]}} | ||||||
|  ] |  ] | ||||||
| 
 | 
 | ||||||
| -- | Does the match expression match this transaction ? | -- | Does the match expression match this transaction ? | ||||||
| @ -493,6 +513,9 @@ matchesTransaction (Status v) t = v == tstatus t | |||||||
| matchesTransaction (Real v) t = v == hasRealPostings t | matchesTransaction (Real v) t = v == hasRealPostings t | ||||||
| matchesTransaction (Empty _) _ = True | matchesTransaction (Empty _) _ = True | ||||||
| matchesTransaction (Depth d) t = any (Depth d `matchesPosting`) $ tpostings t | matchesTransaction (Depth d) t = any (Depth d `matchesPosting`) $ tpostings t | ||||||
|  | matchesTransaction (Tag n Nothing) t = isJust $ lookupTagByName n $ transactionAllTags t | ||||||
|  | matchesTransaction (Tag n (Just v)) t = isJust $ lookupTagByNameAndValue (n,v) $ transactionAllTags t | ||||||
|  | 
 | ||||||
| -- matchesTransaction _ _ = False | -- matchesTransaction _ _ = False | ||||||
| 
 | 
 | ||||||
| tests_matchesTransaction = [ | tests_matchesTransaction = [ | ||||||
| @ -501,8 +524,24 @@ tests_matchesTransaction = [ | |||||||
|    Any `matches` nulltransaction |    Any `matches` nulltransaction | ||||||
|    assertBool "" $ not $ (Desc "x x") `matchesTransaction` nulltransaction{tdescription="x"} |    assertBool "" $ not $ (Desc "x x") `matchesTransaction` nulltransaction{tdescription="x"} | ||||||
|    assertBool "" $ (Desc "x x") `matchesTransaction` nulltransaction{tdescription="x x"} |    assertBool "" $ (Desc "x x") `matchesTransaction` nulltransaction{tdescription="x x"} | ||||||
|  |    -- see posting for more tag tests | ||||||
|  |    assertBool "" $ (Tag "foo" (Just "a")) `matchesTransaction` nulltransaction{ttags=[("foo","bar")]} | ||||||
|  |    -- a tag match on a transaction usually ignores posting tags | ||||||
|  |    assertBool "" $ not $ (Tag "postingtag" Nothing) `matchesTransaction` nulltransaction{tpostings=[nullposting{ptags=[("postingtag","")]}]} | ||||||
|  ] |  ] | ||||||
| 
 | 
 | ||||||
|  | lookupTagByName :: String -> [Tag] -> Maybe Tag | ||||||
|  | lookupTagByName namepat tags = headMay [(n,v) | (n,v) <- tags, matchTagName namepat n] | ||||||
|  | 
 | ||||||
|  | lookupTagByNameAndValue :: Tag -> [Tag] -> Maybe Tag | ||||||
|  | lookupTagByNameAndValue (namepat, valpat) tags = headMay [(n,v) | (n,v) <- tags, matchTagName namepat n, matchTagValue valpat v] | ||||||
|  | 
 | ||||||
|  | matchTagName :: String -> String -> Bool | ||||||
|  | matchTagName pat name = pat == name | ||||||
|  | 
 | ||||||
|  | matchTagValue :: String -> String -> Bool | ||||||
|  | matchTagValue pat value = regexMatchesCI pat value | ||||||
|  | 
 | ||||||
| postingEffectiveDate :: Posting -> Maybe Day | postingEffectiveDate :: Posting -> Maybe Day | ||||||
| postingEffectiveDate p = maybe Nothing (Just . transactionEffectiveDate) $ ptransaction p | postingEffectiveDate p = maybe Nothing (Just . transactionEffectiveDate) $ ptransaction p | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -89,13 +89,15 @@ newtype MixedAmount = Mixed [Amount] deriving (Eq,Ord) | |||||||
| data PostingType = RegularPosting | VirtualPosting | BalancedVirtualPosting | data PostingType = RegularPosting | VirtualPosting | BalancedVirtualPosting | ||||||
|                    deriving (Eq,Show) |                    deriving (Eq,Show) | ||||||
| 
 | 
 | ||||||
|  | type Tag = (String, String) | ||||||
|  | 
 | ||||||
| data Posting = Posting { | data Posting = Posting { | ||||||
|       pstatus :: Bool, |       pstatus :: Bool, | ||||||
|       paccount :: AccountName, |       paccount :: AccountName, | ||||||
|       pamount :: MixedAmount, |       pamount :: MixedAmount, | ||||||
|       pcomment :: String, -- ^ this posting's non-tag comment lines, as a single non-indented string |       pcomment :: String, -- ^ this posting's non-tag comment lines, as a single non-indented string | ||||||
|       ptype :: PostingType, |       ptype :: PostingType, | ||||||
|       ptags :: [(String,String)], |       ptags :: [Tag], | ||||||
|       ptransaction :: Maybe Transaction  -- ^ this posting's parent transaction (co-recursive types). |       ptransaction :: Maybe Transaction  -- ^ this posting's parent transaction (co-recursive types). | ||||||
|                                         -- Tying this knot gets tedious, Maybe makes it easier/optional. |                                         -- Tying this knot gets tedious, Maybe makes it easier/optional. | ||||||
|     } |     } | ||||||
| @ -112,7 +114,7 @@ data Transaction = Transaction { | |||||||
|       tcode :: String, |       tcode :: String, | ||||||
|       tdescription :: String, |       tdescription :: String, | ||||||
|       tcomment :: String, -- ^ this transaction's non-tag comment lines, as a single non-indented string |       tcomment :: String, -- ^ this transaction's non-tag comment lines, as a single non-indented string | ||||||
|       ttags :: [(String,String)], |       ttags :: [Tag], | ||||||
|       tpostings :: [Posting],            -- ^ this transaction's postings (co-recursive types). |       tpostings :: [Posting],            -- ^ this transaction's postings (co-recursive types). | ||||||
|       tpreceding_comment_lines :: String |       tpreceding_comment_lines :: String | ||||||
|     } deriving (Eq) |     } deriving (Eq) | ||||||
|  | |||||||
| @ -752,8 +752,6 @@ commentline = do | |||||||
| 
 | 
 | ||||||
| -- newer comment parsers | -- newer comment parsers | ||||||
| 
 | 
 | ||||||
| type Tag = (String, String) |  | ||||||
| 
 |  | ||||||
| inlinecomment :: GenParser Char JournalContext ([String],[Tag]) | inlinecomment :: GenParser Char JournalContext ([String],[Tag]) | ||||||
| inlinecomment = try (do {md <- tagcomment; newline; return ([], [md])}) | inlinecomment = try (do {md <- tagcomment; newline; return ([], [md])}) | ||||||
|                     <|> (do {c <- comment; newline; return ([rstrip c], [])}) |                     <|> (do {c <- comment; newline; return ([rstrip c], [])}) | ||||||
|  | |||||||
							
								
								
									
										115
									
								
								tests/tags.test
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								tests/tags.test
									
									
									
									
									
								
							| @ -25,57 +25,80 @@ bin/hledger -f - print | |||||||
| >>>2 | >>>2 | ||||||
| >>>=0 | >>>=0 | ||||||
| 
 | 
 | ||||||
| # 2. print (and a few other commands) can filter by tag value | # 2. reports can filter by tag existence | ||||||
| # bin/hledger -f - print tag foo=bar | bin/hledger -f - print tag:foo | ||||||
| # <<< | <<< | ||||||
| # 2010/01/01 | 2010/01/01 ; foo:bar | ||||||
| #   ; foo:bar |   a             1 | ||||||
| #   a             1 |   b            -1 | ||||||
| #   b            -1 |  | ||||||
| 
 | 
 | ||||||
| # 2010/01/02 | 2010/01/02 | ||||||
| #   ; foo:baz |   ; foo:baz | ||||||
| #   c             1 |   c             1 | ||||||
| #   d            -1 |   d            -1 | ||||||
| 
 | 
 | ||||||
| # 2010/01/03 | 2010/01/03 | ||||||
| #     e             1 |     e             1 | ||||||
| #     f            -1 |     f            -1 | ||||||
| # >>> | >>> | ||||||
| # 2010/01/01 | 2010/01/01 | ||||||
| #     ; foo: bar |     ; foo: bar | ||||||
| #     a             1 |     a             1 | ||||||
| #     b            -1 |     b            -1 | ||||||
| 
 | 
 | ||||||
| # >>>2 | 2010/01/02 | ||||||
| # >>>=0 |     ; foo: baz | ||||||
|  |     c             1 | ||||||
|  |     d            -1 | ||||||
| 
 | 
 | ||||||
| # # 3. or tag existence ? not yet | >>>2 | ||||||
| # bin/hledger -f - print tag foo | >>>=0 | ||||||
| # <<< |  | ||||||
| # 2010/01/01 |  | ||||||
| #   ; foo:bar |  | ||||||
| #   a             1 |  | ||||||
| #   b            -1 |  | ||||||
| 
 | 
 | ||||||
| # 2010/01/02 | # 3. or tag value | ||||||
| #   ; foo:baz | bin/hledger -f - print tag:foo=bar | ||||||
| #   c             1 | <<< | ||||||
| #   d            -1 | 2010/01/01 | ||||||
|  |   ; foo:bar | ||||||
|  |   a             1 | ||||||
|  |   b            -1 | ||||||
| 
 | 
 | ||||||
| # 2010/01/03 | 2010/01/02 | ||||||
| #     e             1 |   ; foo:baz | ||||||
| #     f            -1 |   c             1 | ||||||
| # >>> |   d            -1 | ||||||
| # 2010/01/01 |  | ||||||
| #   ; foo:bar |  | ||||||
| #     a             1 |  | ||||||
| #     b            -1 |  | ||||||
| 
 | 
 | ||||||
| # 2010/01/02 | 2010/01/03 | ||||||
| #   ; foo:baz |     e             1 | ||||||
| #     c             1 |     f            -1 | ||||||
| #     d            -1 | >>> | ||||||
|  | 2010/01/01 | ||||||
|  |     ; foo: bar | ||||||
|  |     a             1 | ||||||
|  |     b            -1 | ||||||
|  | 
 | ||||||
|  | >>>2 | ||||||
|  | >>>=0 | ||||||
|  | 
 | ||||||
|  | # 4. postings inherit their transaction's tags | ||||||
|  | bin/hledger -f - register tag:foo=bar | ||||||
|  | <<< | ||||||
|  | 2010/01/01 | ||||||
|  |   a             1  ; foo:bar | ||||||
|  |   b            -1 | ||||||
|  | 
 | ||||||
|  | 2010/01/02 | ||||||
|  |   ; foo:baz | ||||||
|  |   c             1 | ||||||
|  |   d            -1 | ||||||
|  | 
 | ||||||
|  | 2010/01/03 | ||||||
|  |   ; foo:bar | ||||||
|  |   e             1 | ||||||
|  |   f            -1 | ||||||
|  | >>> | ||||||
|  | 2010/01/01                      a                                 1            1 | ||||||
|  | 2010/01/03                      e                                 1            2 | ||||||
|  |                                 f                                -1            1 | ||||||
|  | >>>2 | ||||||
|  | >>>=0 | ||||||
| 
 | 
 | ||||||
| # >>>2 |  | ||||||
| # >>>=0 |  | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user