check: add "payees" check requiring payee declarations
This commit is contained in:
		
							parent
							
								
									e092b38631
								
							
						
					
					
						commit
						3c232fbd7d
					
				| @ -52,6 +52,7 @@ module Hledger.Data.Journal ( | ||||
|   -- overJournalAmounts, | ||||
|   -- traverseJournalAmounts, | ||||
|   -- journalCanonicalCommodities, | ||||
|   journalPayeesDeclared, | ||||
|   journalCommoditiesDeclared, | ||||
|   journalDateSpan, | ||||
|   journalStartDate, | ||||
| @ -183,6 +184,7 @@ instance Semigroup Journal where | ||||
|     -- ,jparsetransactioncount     = jparsetransactioncount     j1 +  jparsetransactioncount     j2 | ||||
|     ,jparsetimeclockentries     = jparsetimeclockentries     j1 <> jparsetimeclockentries     j2 | ||||
|     ,jincludefilestack          = jincludefilestack j2 | ||||
|     ,jdeclaredpayees            = jdeclaredpayees            j1 <> jdeclaredpayees            j2 | ||||
|     ,jdeclaredaccounts          = jdeclaredaccounts          j1 <> jdeclaredaccounts          j2 | ||||
|     ,jdeclaredaccounttypes      = jdeclaredaccounttypes      j1 <> jdeclaredaccounttypes      j2 | ||||
|     ,jglobalcommoditystyles     = jglobalcommoditystyles     j1 <> jglobalcommoditystyles     j2 | ||||
| @ -211,6 +213,7 @@ nulljournal = Journal { | ||||
|   -- ,jparsetransactioncount     = 0 | ||||
|   ,jparsetimeclockentries     = [] | ||||
|   ,jincludefilestack          = [] | ||||
|   ,jdeclaredpayees            = [] | ||||
|   ,jdeclaredaccounts          = [] | ||||
|   ,jdeclaredaccounttypes      = M.empty | ||||
|   ,jglobalcommoditystyles     = M.empty | ||||
| @ -273,6 +276,10 @@ journalPostings = concatMap tpostings . jtxns | ||||
| journalCommoditiesDeclared :: Journal -> [AccountName] | ||||
| journalCommoditiesDeclared = nubSort . M.keys . jcommodities | ||||
| 
 | ||||
| -- | Sorted unique payees declared by payee directives in this journal. | ||||
| journalPayeesDeclared :: Journal -> [Payee] | ||||
| journalPayeesDeclared = nubSort . map fst . jdeclaredpayees | ||||
| 
 | ||||
| -- | Sorted unique account names posted to by this journal's transactions. | ||||
| journalAccountNamesUsed :: Journal -> [AccountName] | ||||
| journalAccountNamesUsed = accountNamesFromPostings . journalPostings | ||||
|  | ||||
| @ -126,6 +126,7 @@ instance ToJSON AccountAlias | ||||
| instance ToJSON AccountType | ||||
| instance ToJSONKey AccountType | ||||
| instance ToJSON AccountDeclarationInfo | ||||
| instance ToJSON PayeeDeclarationInfo | ||||
| instance ToJSON Commodity | ||||
| instance ToJSON TimeclockCode | ||||
| instance ToJSON TimeclockEntry | ||||
|  | ||||
| @ -132,6 +132,8 @@ data Interval = | ||||
| 
 | ||||
| instance Default Interval where def = NoInterval | ||||
| 
 | ||||
| type Payee = Text | ||||
| 
 | ||||
| type AccountName = Text | ||||
| 
 | ||||
| data AccountType = | ||||
| @ -453,6 +455,7 @@ data Journal = Journal { | ||||
|   ,jparsetimeclockentries :: [TimeclockEntry]                       -- ^ timeclock sessions which have not been clocked out | ||||
|   ,jincludefilestack      :: [FilePath] | ||||
|   -- principal data | ||||
|   ,jdeclaredpayees        :: [(Payee,PayeeDeclarationInfo)]         -- ^ Accounts declared by account directives, in parse order (after journal finalisation) | ||||
|   ,jdeclaredaccounts      :: [(AccountName,AccountDeclarationInfo)] -- ^ Accounts declared by account directives, in parse order (after journal finalisation) | ||||
|   ,jdeclaredaccounttypes  :: M.Map AccountType [AccountName]        -- ^ Accounts whose type has been declared in account directives (usually 5 top-level accounts) | ||||
|   ,jglobalcommoditystyles :: M.Map CommoditySymbol AmountStyle      -- ^ per-commodity display styles declared globally, eg by command line option or import command | ||||
| @ -482,6 +485,17 @@ type ParsedJournal = Journal | ||||
| -- The --output-format option selects one of these for output. | ||||
| type StorageFormat = String | ||||
| 
 | ||||
| -- | Extra information found in a payee directive. | ||||
| data PayeeDeclarationInfo = PayeeDeclarationInfo { | ||||
|    pdicomment :: Text   -- ^ any comment lines following the payee directive | ||||
|   ,pditags    :: [Tag]  -- ^ tags extracted from the comment, if any | ||||
| } deriving (Eq,Show,Generic) | ||||
| 
 | ||||
| nullpayeedeclarationinfo = PayeeDeclarationInfo { | ||||
|    pdicomment          = "" | ||||
|   ,pditags             = [] | ||||
| } | ||||
| 
 | ||||
| -- | Extra information about an account that can be derived from | ||||
| -- its account directive (and the other account directives). | ||||
| data AccountDeclarationInfo = AccountDeclarationInfo { | ||||
|  | ||||
| @ -45,6 +45,7 @@ module Hledger.Read.Common ( | ||||
|   parseAndFinaliseJournal, | ||||
|   parseAndFinaliseJournal', | ||||
|   journalFinalise, | ||||
|   journalCheckPayeesDeclared, | ||||
|   setYear, | ||||
|   getYear, | ||||
|   setDefaultCommodityAndStyle, | ||||
| @ -368,6 +369,20 @@ journalFinalise InputOpts{auto_,ignore_assertions_,commoditystyles_,strict_} f t | ||||
|                 ) | ||||
|             & fmap journalInferMarketPricesFromTransactions  -- infer market prices from commodity-exchanging transactions | ||||
| 
 | ||||
| -- | Check that all the journal's transactions have payees declared with | ||||
| -- payee directives, returning an error message otherwise. | ||||
| journalCheckPayeesDeclared :: Journal -> Either String () | ||||
| journalCheckPayeesDeclared j = sequence_ $ map checkpayee $ jtxns j | ||||
|   where | ||||
|     checkpayee t | ||||
|       | p `elem` ps = Right () | ||||
|       | otherwise          =  | ||||
|           Left $ "\nundeclared payee \""++T.unpack p++"\"" | ||||
|             ++ "\nin transaction at: "++showGenericSourcePos (tsourcepos t) | ||||
|       where | ||||
|         p  = transactionPayee t | ||||
|         ps = journalPayeesDeclared j | ||||
| 
 | ||||
| -- | Check that all the journal's postings are to accounts declared with | ||||
| -- account directives, returning an error message otherwise. | ||||
| journalCheckAccountsDeclared :: Journal -> Either String () | ||||
|  | ||||
| @ -397,6 +397,17 @@ addAccountDeclaration (a,cmt,tags) = | ||||
|              in | ||||
|                j{jdeclaredaccounts = d:decls}) | ||||
| 
 | ||||
| -- Add a payee declaration to the journal. | ||||
| addPayeeDeclaration :: (Payee,Text,[Tag]) -> JournalParser m () | ||||
| addPayeeDeclaration (p, cmt, tags) = | ||||
|   modify' (\j@Journal{jdeclaredpayees} -> j{jdeclaredpayees=d:jdeclaredpayees}) | ||||
|              where | ||||
|                d = (p | ||||
|                    ,nullpayeedeclarationinfo{ | ||||
|                      pdicomment = cmt | ||||
|                     ,pditags    = tags | ||||
|                     }) | ||||
| 
 | ||||
| indentedlinep :: JournalParser m String | ||||
| indentedlinep = lift skipNonNewlineSpaces1 >> (rstrip <$> lift restofline) | ||||
| 
 | ||||
| @ -524,8 +535,9 @@ payeedirectivep :: JournalParser m () | ||||
| payeedirectivep = do | ||||
|   string "payee" <?> "payee directive" | ||||
|   lift skipNonNewlineSpaces1 | ||||
|   _ <- lift $ some nonspace | ||||
|   lift restofline | ||||
|   payee <- lift descriptionp  -- all text until ; or \n | ||||
|   (comment, tags) <- lift transactioncommentp | ||||
|   addPayeeDeclaration (payee, comment, tags) | ||||
|   return () | ||||
| 
 | ||||
| defaultyeardirectivep :: JournalParser m () | ||||
|  | ||||
| @ -18,6 +18,8 @@ import Data.Either (partitionEithers) | ||||
| import Data.Char (toUpper) | ||||
| import Safe (readMay) | ||||
| import Control.Monad (forM_) | ||||
| import System.IO (stderr, hPutStr) | ||||
| import System.Exit (exitFailure) | ||||
| 
 | ||||
| checkmode :: Mode RawOpts | ||||
| checkmode = hledgerCommandMode | ||||
| @ -40,8 +42,11 @@ check copts@CliOpts{rawopts_} j = do | ||||
|     ([], checks) -> forM_ checks $ runCheck copts' j | ||||
|        | ||||
| -- | A type of error check that we can perform on the data. | ||||
| -- (Currently, just the optional checks that only the check command | ||||
| -- can do; not the checks done by default or with --strict.) | ||||
| data Check = | ||||
|     Ordereddates | ||||
|   | Payees | ||||
|   | Uniqueleafnames | ||||
|   deriving (Read,Show,Eq) | ||||
| 
 | ||||
| @ -63,6 +68,7 @@ parseCheckArgument s = | ||||
|   where | ||||
|     (checkname:checkargs) = words' s | ||||
| 
 | ||||
| -- XXX do all of these print on stderr ? | ||||
| -- | Run the named error check, possibly with some arguments,  | ||||
| -- on this journal with these options. | ||||
| runCheck :: CliOpts -> Journal -> (Check,[String]) -> IO () | ||||
| @ -70,6 +76,10 @@ runCheck copts@CliOpts{rawopts_} j (check,args) = | ||||
|   case check of | ||||
|     Ordereddates    -> checkdates copts' j | ||||
|     Uniqueleafnames -> checkdupes copts' j | ||||
|     Payees          -> | ||||
|       case journalCheckPayeesDeclared j of | ||||
|         Right () -> return () | ||||
|         Left err -> hPutStr stderr err >> exitFailure | ||||
|   where | ||||
|     -- Hack: append the provided args to the raw opts, | ||||
|     -- in case the check can use them (like checkdates --unique).  | ||||
|  | ||||
| @ -50,6 +50,8 @@ These checks can be run by specifying their names as arguments to the check comm | ||||
| 
 | ||||
| - **ordereddates** - transactions are ordered by date (similar to the old `check-dates` command) | ||||
| 
 | ||||
| - **payees** - all payees used by transactions have been declared | ||||
| 
 | ||||
| - **uniqueleafnames** - all account leaf names are unique (similar to the old `check-dupes` command) | ||||
| 
 | ||||
| ### Add-on checks | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user