journal: require a decimal point in commodity/format/D directives
A commodity directive that doesn't specify the decimal point character increases ambiguity and the chance of misparsing numbers, especially as it overrides all style information inferred from the journal amounts. In some cases it caused amounts with a decimal point to be parsed as if with a digit group separator so 1.234 became 1234. We could augment it with extra info from the journal amounts, when available, but it would still be possible to be ambiguous, and that won't be obvious. A commodity directive is what we recommend to nail down the style. It seems the simple and really only way to do this reliably is to require an explicit decimal point character. Most folks probably do this already. Unfortunately, it makes another potential incompatiblity with ledger and beancount journals. But the error message will be clear and easy to work around.
This commit is contained in:
		
							parent
							
								
									3f2827424c
								
							
						
					
					
						commit
						0b380971f7
					
				| @ -737,18 +737,19 @@ journalApplyCommodityStyles j@Journal{jtxns=ts, jmarketprices=mps} = j'' | ||||
|       fixposting p@Posting{pamount=a} = p{pamount=styleMixedAmount styles a} | ||||
|       fixmarketprice mp@MarketPrice{mpamount=a} = mp{mpamount=styleAmount styles a} | ||||
| 
 | ||||
| -- | Get all the amount styles defined in this journal, either  | ||||
| -- declared by a commodity directive (preferred) or inferred from amounts, | ||||
| -- as a map from symbol to style.  | ||||
| -- | Get all the amount styles defined in this journal, either declared by  | ||||
| -- a commodity directive or inferred from amounts, as a map from symbol to style.  | ||||
| -- Styles declared by commodity directives take precedence, and these also are | ||||
| -- guaranteed to know their decimal point character. | ||||
| journalCommodityStyles :: Journal -> M.Map CommoditySymbol AmountStyle | ||||
| journalCommodityStyles j = declaredstyles <> inferredstyles | ||||
|   where | ||||
|     declaredstyles = M.mapMaybe cformat $ jcommodities j | ||||
|     inferredstyles = jinferredcommodities j | ||||
| 
 | ||||
| -- | Infer a display format for each commodity based on the amounts parsed. | ||||
| -- "hledger... will use the format of the first posting amount in the | ||||
| -- commodity, and the highest precision of all posting amounts in the commodity." | ||||
| -- | Collect and save inferred amount styles for each commodity based on | ||||
| -- the posting amounts in that commodity (excluding price amounts), ie: | ||||
| -- "the format of the first amount, adjusted to the highest precision of all amounts". | ||||
| journalInferCommodityStyles :: Journal -> Journal | ||||
| journalInferCommodityStyles j = | ||||
|   j{jinferredcommodities = | ||||
|  | ||||
| @ -274,15 +274,21 @@ commoditydirectivep = try commoditydirectiveonelinep <|> commoditydirectivemulti | ||||
| -- | ||||
| -- >>> Right _ <- rejp commoditydirectiveonelinep "commodity $1.00" | ||||
| -- >>> Right _ <- rejp commoditydirectiveonelinep "commodity $1.00 ; blah\n" | ||||
| commoditydirectiveonelinep :: Monad m => JournalParser m () | ||||
| commoditydirectiveonelinep :: Monad m => ErroringJournalParser m () | ||||
| commoditydirectiveonelinep = do | ||||
|   string "commodity" | ||||
|   lift (skipSome spacenonewline) | ||||
|   pos <- getPosition | ||||
|   Amount{acommodity,astyle} <- amountp | ||||
|   lift (skipMany spacenonewline) | ||||
|   _ <- followingcommentp <|> (lift eolof >> return "") | ||||
|   let comm = Commodity{csymbol=acommodity, cformat=Just astyle} | ||||
|   modify' (\j -> j{jcommodities=M.insert acommodity comm $ jcommodities j}) | ||||
|   let comm = Commodity{csymbol=acommodity, cformat=Just $ dbg2 "style from commodity directive" astyle} | ||||
|   if asdecimalpoint astyle == Nothing | ||||
|   then parserErrorAt pos pleaseincludedecimalpoint | ||||
|   else modify' (\j -> j{jcommodities=M.insert acommodity comm $ jcommodities j}) | ||||
| 
 | ||||
| pleaseincludedecimalpoint :: String | ||||
| pleaseincludedecimalpoint = "to avoid ambiguity, please include a decimal point in commodity directives" | ||||
| 
 | ||||
| -- | Parse a multi-line commodity directive, containing 0 or more format subdirectives. | ||||
| -- | ||||
| @ -309,7 +315,10 @@ formatdirectivep expectedsym = do | ||||
|   Amount{acommodity,astyle} <- amountp | ||||
|   _ <- followingcommentp <|> (lift eolof >> return "") | ||||
|   if acommodity==expectedsym | ||||
|     then return astyle | ||||
|     then  | ||||
|       if asdecimalpoint astyle == Nothing | ||||
|       then parserErrorAt pos pleaseincludedecimalpoint | ||||
|       else return $ dbg2 "style from format subdirective" astyle | ||||
|     else parserErrorAt pos $ | ||||
|          printf "commodity directive symbol \"%s\" and format directive symbol \"%s\" should be the same" expectedsym acommodity | ||||
| 
 | ||||
| @ -395,13 +404,16 @@ defaultyeardirectivep = do | ||||
|   failIfInvalidYear y | ||||
|   setYear y' | ||||
| 
 | ||||
| defaultcommoditydirectivep :: Monad m => JournalParser m () | ||||
| defaultcommoditydirectivep :: Monad m => ErroringJournalParser m () | ||||
| defaultcommoditydirectivep = do | ||||
|   char 'D' <?> "default commodity" | ||||
|   lift (skipSome spacenonewline) | ||||
|   Amount{..} <- amountp | ||||
|   pos <- getPosition | ||||
|   Amount{acommodity,astyle} <- amountp | ||||
|   lift restofline | ||||
|   setDefaultCommodityAndStyle (acommodity, astyle) | ||||
|   if asdecimalpoint astyle == Nothing | ||||
|   then parserErrorAt pos pleaseincludedecimalpoint | ||||
|   else setDefaultCommodityAndStyle (acommodity, astyle) | ||||
| 
 | ||||
| marketpricedirectivep :: Monad m => JournalParser m MarketPrice | ||||
| marketpricedirectivep = do | ||||
|  | ||||
| @ -799,9 +799,7 @@ line containing just `end comment` ends it. See [comments](#comments). | ||||
| 
 | ||||
| ### commodity directive | ||||
| 
 | ||||
| The `commodity` directive predefines commodities (currently this is just informational), | ||||
| and also it may define the display format for amounts in this commodity (overriding the automatically inferred format). | ||||
| 
 | ||||
| The `commodity` directive declares commodities which may be used in the journal (though currently we do not enforce this). | ||||
| It may be written on a single line, like this: | ||||
| 
 | ||||
| ```journal | ||||
| @ -827,6 +825,12 @@ commodity INR | ||||
|   format INR 9,99,99,999.00 | ||||
| ``` | ||||
| 
 | ||||
| Commodity directives have a second purpose: they define the standard display format for amounts in the commodity.  | ||||
| Normally the display format is inferred from journal entries, but this can be unpredictable;  | ||||
| declaring it with a commodity directive overrides this and removes ambiguity.  | ||||
| Towards this end, amounts in commodity directives must always be written with a decimal point  | ||||
| (a period or comma, followed by 0 or more decimal digits).  | ||||
| 
 | ||||
| ### Default commodity | ||||
| 
 | ||||
| The D directive sets a default commodity (and display format), to be used for amounts without a commodity symbol (ie, plain numbers). | ||||
| @ -843,6 +847,8 @@ D $1,000.00 | ||||
|   b | ||||
| ``` | ||||
| 
 | ||||
| As with the `commodity` directive, the amount must always be written with a decimal point.  | ||||
| 
 | ||||
| ### Default year | ||||
| 
 | ||||
| You can set a default year to be used for subsequent dates which don't | ||||
|  | ||||
| @ -126,26 +126,17 @@ commodity €1,000.00 | ||||
| >>>2 | ||||
| >>>=0 | ||||
| 
 | ||||
| # 11. No decimals but have hint from commodity directive with groups | ||||
| # 11. Commodity directive requires a decimal point | ||||
| hledger bal -f - | ||||
| <<< | ||||
| commodity 1,000,000 EUR | ||||
| commodity 1000 EUR | ||||
| >>>2 /please include a decimal point/ | ||||
| >>>=1 | ||||
| 
 | ||||
| 2017/1/1 | ||||
| 	a   1,000 EUR | ||||
| 	b  -1,000.00 EUR | ||||
| >>> | ||||
|            1,000 EUR  a | ||||
|           -1,000 EUR  b | ||||
| -------------------- | ||||
|                    0 | ||||
| >>>2 | ||||
| >>>=0 | ||||
| 
 | ||||
| # 12. No decimals but have hint from commodity directive with zero precision | ||||
| # 12. Commodity directive with zero precision | ||||
| hledger bal -f - | ||||
| <<< | ||||
| commodity 100 EUR | ||||
| commodity 100. EUR | ||||
| 
 | ||||
| 2017/1/1 | ||||
| 	a   1,000 EUR | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user