cli: add diff command
This merges the external hledger-diff addon, which is now deprecated. https://github.com/gebner/hledger-diff/
This commit is contained in:
		
							parent
							
								
									f9aa71caf1
								
							
						
					
					
						commit
						ceb193f85e
					
				| @ -69,6 +69,7 @@ import Hledger.Cli.Commands.Checkdates | |||||||
| import Hledger.Cli.Commands.Checkdupes | import Hledger.Cli.Commands.Checkdupes | ||||||
| import Hledger.Cli.Commands.Close | import Hledger.Cli.Commands.Close | ||||||
| import Hledger.Cli.Commands.Commodities | import Hledger.Cli.Commands.Commodities | ||||||
|  | import Hledger.Cli.Commands.Diff | ||||||
| import Hledger.Cli.Commands.Files | import Hledger.Cli.Commands.Files | ||||||
| import Hledger.Cli.Commands.Help | import Hledger.Cli.Commands.Help | ||||||
| import Hledger.Cli.Commands.Import | import Hledger.Cli.Commands.Import | ||||||
| @ -102,6 +103,7 @@ builtinCommands = [ | |||||||
|   ,(helpmode               , help') |   ,(helpmode               , help') | ||||||
|   ,(importmode             , importcmd) |   ,(importmode             , importcmd) | ||||||
|   ,(filesmode              , files) |   ,(filesmode              , files) | ||||||
|  |   ,(diffmode               , diff) | ||||||
|   ,(incomestatementmode    , incomestatement) |   ,(incomestatementmode    , incomestatement) | ||||||
|   ,(pricesmode             , prices) |   ,(pricesmode             , prices) | ||||||
|   ,(printmode              , print') |   ,(printmode              , print') | ||||||
|  | |||||||
							
								
								
									
										126
									
								
								hledger/Hledger/Cli/Commands/Diff.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								hledger/Hledger/Cli/Commands/Diff.hs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,126 @@ | |||||||
|  | {-| | ||||||
|  | 
 | ||||||
|  | The @diff@ command compares two diff. | ||||||
|  | 
 | ||||||
|  | -} | ||||||
|  | 
 | ||||||
|  | {-# LANGUAGE OverloadedStrings #-} | ||||||
|  | {-# LANGUAGE TemplateHaskell #-} | ||||||
|  | 
 | ||||||
|  | module Hledger.Cli.Commands.Diff ( | ||||||
|  |   diffmode | ||||||
|  |  ,diff | ||||||
|  | ) where | ||||||
|  | 
 | ||||||
|  | import Data.List | ||||||
|  | import Data.Function | ||||||
|  | import Data.Ord | ||||||
|  | import Data.Maybe | ||||||
|  | import Data.Time | ||||||
|  | import Data.Either | ||||||
|  | import qualified Data.Text as T | ||||||
|  | import System.Exit | ||||||
|  | 
 | ||||||
|  | import Hledger | ||||||
|  | import Prelude hiding (putStrLn) | ||||||
|  | import Hledger.Utils.UTF8IOCompat (putStrLn) | ||||||
|  | import Hledger.Cli.CliOptions | ||||||
|  | 
 | ||||||
|  | -- | Command line options for this command. | ||||||
|  | diffmode = hledgerCommandMode | ||||||
|  |   $(embedFileRelative "Hledger/Cli/Commands/Diff.txt") | ||||||
|  |   [] | ||||||
|  |   [generalflagsgroup2] | ||||||
|  |   [] | ||||||
|  |   ([], Just $ argsFlag "[ACCOUNT] -f [JOURNAL1] -f [JOURNAL2]") | ||||||
|  | 
 | ||||||
|  | data PostingWithPath = PostingWithPath { | ||||||
|  |                      ppposting :: Posting, | ||||||
|  |                      pptxnidx :: Int, | ||||||
|  |                      pppidx :: Int } | ||||||
|  |                  deriving (Show) | ||||||
|  | 
 | ||||||
|  | instance Eq PostingWithPath where | ||||||
|  |     a == b = pptxnidx a == pptxnidx b | ||||||
|  |           && pppidx a == pppidx b | ||||||
|  | 
 | ||||||
|  | pptxn :: PostingWithPath -> Transaction | ||||||
|  | pptxn = fromJust . ptransaction . ppposting | ||||||
|  | 
 | ||||||
|  | ppamountqty :: PostingWithPath -> Quantity | ||||||
|  | ppamountqty = aquantity . head . amounts . pamount . ppposting | ||||||
|  | 
 | ||||||
|  | allPostingsWithPath :: Journal -> [PostingWithPath] | ||||||
|  | allPostingsWithPath j = do | ||||||
|  |     (txnidx, txn) <- zip [0..] $ jtxns j | ||||||
|  |     (pidx, p) <- zip [0..] $ tpostings txn | ||||||
|  |     return PostingWithPath { ppposting = p, pptxnidx = txnidx, pppidx = pidx } | ||||||
|  | 
 | ||||||
|  | binBy :: Ord b => (a -> b) -> [a] -> [[a]] | ||||||
|  | binBy f = groupBy ((==) `on` f) . sortBy (comparing f) | ||||||
|  | 
 | ||||||
|  | combine :: ([a], [b]) -> [Either a b] | ||||||
|  | combine (ls, rs) = map Left ls ++ map Right rs | ||||||
|  | 
 | ||||||
|  | combinedBinBy :: Ord b => (a -> b) -> ([a], [a]) -> [([a], [a])] | ||||||
|  | combinedBinBy f = map partitionEithers . binBy (either f f) . combine | ||||||
|  | 
 | ||||||
|  | greedyMaxMatching :: (Eq a, Eq b) => [(a,b)] -> [(a,b)] | ||||||
|  | greedyMaxMatching = greedyMaxMatching' [] | ||||||
|  | 
 | ||||||
|  | greedyMaxMatching' :: (Eq a, Eq b) => [Either a b] -> [(a,b)] -> [(a,b)] | ||||||
|  | greedyMaxMatching' alreadyUsed ((l,r):rest) | ||||||
|  |   | Left l `elem` alreadyUsed || Right r `elem` alreadyUsed | ||||||
|  |       = greedyMaxMatching' alreadyUsed rest | ||||||
|  |   | otherwise = (l,r) : greedyMaxMatching' (Left l : Right r : alreadyUsed) rest | ||||||
|  | greedyMaxMatching' _ [] = [] | ||||||
|  | 
 | ||||||
|  | dateCloseness :: (PostingWithPath, PostingWithPath) -> Integer | ||||||
|  | dateCloseness = negate . uncurry (diffDays `on` tdate.pptxn) | ||||||
|  | 
 | ||||||
|  | type Matching = [(PostingWithPath, PostingWithPath)] | ||||||
|  | 
 | ||||||
|  | matching :: [PostingWithPath] -> [PostingWithPath] -> Matching | ||||||
|  | matching ppl ppr = do | ||||||
|  |     (left, right) <- combinedBinBy ppamountqty (ppl, ppr) -- TODO: probably not a correct choice of bins | ||||||
|  |     greedyMaxMatching $ sortBy (comparing dateCloseness) [ (l,r) | l <- left, r <- right ] | ||||||
|  | 
 | ||||||
|  | readJournalFile' :: FilePath -> IO Journal | ||||||
|  | readJournalFile' fn = | ||||||
|  |     readJournalFile definputopts {ignore_assertions_ = True} fn >>= either error' return | ||||||
|  | 
 | ||||||
|  | matchingPostings :: AccountName -> Journal -> [PostingWithPath] | ||||||
|  | matchingPostings acct j = filter ((== acct) . paccount . ppposting) $ allPostingsWithPath j | ||||||
|  | 
 | ||||||
|  | pickSide :: Side -> (a,a) -> a | ||||||
|  | pickSide L (l,_) = l | ||||||
|  | pickSide R (_,r) = r | ||||||
|  | 
 | ||||||
|  | unmatchedtxns :: Side -> [PostingWithPath] -> Matching -> [Transaction] | ||||||
|  | unmatchedtxns s pp m = | ||||||
|  |     map pptxn $ nubBy ((==) `on` pptxnidx) $ pp \\ map (pickSide s) m | ||||||
|  | 
 | ||||||
|  | -- | The diff command. | ||||||
|  | diff :: CliOpts -> Journal -> IO () | ||||||
|  | diff CliOpts{file_=[f1, f2], reportopts_=ReportOpts{query_=acctName}} _ = do | ||||||
|  |   j1 <- readJournalFile' f1 | ||||||
|  |   j2 <- readJournalFile' f2 | ||||||
|  | 
 | ||||||
|  |   let acct = T.pack acctName | ||||||
|  |   let pp1 = matchingPostings acct j1 | ||||||
|  |   let pp2 = matchingPostings acct j2 | ||||||
|  | 
 | ||||||
|  |   let m = matching pp1 pp2 | ||||||
|  | 
 | ||||||
|  |   let unmatchedtxn1 = unmatchedtxns L pp1 m | ||||||
|  |   let unmatchedtxn2 = unmatchedtxns R pp2 m | ||||||
|  | 
 | ||||||
|  |   putStrLn "Unmatched transactions in the first journal:\n" | ||||||
|  |   mapM_ (putStr . showTransaction) unmatchedtxn1 | ||||||
|  | 
 | ||||||
|  |   putStrLn "Unmatched transactions in the second journal:\n" | ||||||
|  |   mapM_ (putStr . showTransaction) unmatchedtxn2 | ||||||
|  | 
 | ||||||
|  | diff _ _ = do | ||||||
|  |   putStrLn "Specifiy exactly two journal files" | ||||||
|  |   exitFailure | ||||||
							
								
								
									
										35
									
								
								hledger/Hledger/Cli/Commands/Diff.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								hledger/Hledger/Cli/Commands/Diff.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | diff\ | ||||||
|  | Compares two journal files.  It looks at the transactions of a single | ||||||
|  | account and prints out the transactions which are in one journal file but not | ||||||
|  | in the other. | ||||||
|  | 
 | ||||||
|  | This is particularly useful for reconciling existing journals with bank | ||||||
|  | statements.  Many banks provide a way to export the transactions between two | ||||||
|  | given dates, which can be converted to ledger files using custom scripts or | ||||||
|  | read directly as CSV files.  With the diff command you can make sure that these | ||||||
|  | transactions from bank match up exactly with the transactions in your ledger | ||||||
|  | file, and that the resulting balance is correct.  (One possible concrete | ||||||
|  | workflow is to have one ledger file per year and export the transactions for | ||||||
|  | the current year, starting on January 1.) | ||||||
|  | 
 | ||||||
|  | This command compares the postings of a single account (which needs to have the | ||||||
|  | same name in both files), and only checks the amount of the postings (not the | ||||||
|  | name or the date of the transactions).  Postings are compared (instead of | ||||||
|  | transactions) so that you can combine multiple transactions from the bank | ||||||
|  | statement in a single transaction in the ledger file. | ||||||
|  | 
 | ||||||
|  | _FLAGS_ | ||||||
|  | 
 | ||||||
|  | Examples: | ||||||
|  | 
 | ||||||
|  | ```shell | ||||||
|  | $ hledger diff assets:bank:giro -f 2014.journal -f bank.journal | ||||||
|  | Unmatched transactions in the first journal: | ||||||
|  | 
 | ||||||
|  | 2014/01/01 Opening Balances | ||||||
|  |     assets:bank:giro              EUR ... | ||||||
|  |     ... | ||||||
|  |     equity:opening balances       EUR -... | ||||||
|  | 
 | ||||||
|  | Unmatched transactions in the second journal: | ||||||
|  | ``` | ||||||
							
								
								
									
										35
									
								
								hledger/Hledger/Cli/Commands/Diff.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								hledger/Hledger/Cli/Commands/Diff.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | diff | ||||||
|  | Compares two journal files. It looks at the transactions of a single | ||||||
|  | account and prints out the transactions which are in one journal file | ||||||
|  | but not in the other. | ||||||
|  | 
 | ||||||
|  | This is particularly useful for reconciling existing journals with bank | ||||||
|  | statements. Many banks provide a way to export the transactions between | ||||||
|  | two given dates, which can be converted to ledger files using custom | ||||||
|  | scripts or read directly as CSV files. With the diff command you can | ||||||
|  | make sure that these transactions from bank match up exactly with the | ||||||
|  | transactions in your ledger file, and that the resulting balance is | ||||||
|  | correct. (One possible concrete workflow is to have one ledger file per | ||||||
|  | year and export the transactions for the current year, starting on | ||||||
|  | January 1.) | ||||||
|  | 
 | ||||||
|  | This command compares the postings of a single account (which needs to | ||||||
|  | have the same name in both files), and only checks the amount of the | ||||||
|  | postings (not the name or the date of the transactions). Postings are | ||||||
|  | compared (instead of transactions) so that you can combine multiple | ||||||
|  | transactions from the bank statement in a single transaction in the | ||||||
|  | ledger file. | ||||||
|  | 
 | ||||||
|  | _FLAGS_ | ||||||
|  | 
 | ||||||
|  | Examples: | ||||||
|  | 
 | ||||||
|  | $ hledger diff assets:bank:giro -f 2014.journal -f bank.journal | ||||||
|  | Unmatched transactions in the first journal: | ||||||
|  | 
 | ||||||
|  | 2014/01/01 Opening Balances | ||||||
|  |     assets:bank:giro              EUR ... | ||||||
|  |     ... | ||||||
|  |     equity:opening balances       EUR -... | ||||||
|  | 
 | ||||||
|  | Unmatched transactions in the second journal: | ||||||
| @ -4,7 +4,7 @@ cabal-version: 1.12 | |||||||
| -- | -- | ||||||
| -- see: https://github.com/sol/hpack | -- see: https://github.com/sol/hpack | ||||||
| -- | -- | ||||||
| -- hash: 0682613dd00c2d6d52d5a9079bb63755cc25a0a09be9c996f55a0c0ad60fbafa | -- hash: c936fb07d9099bcceeb59ad6e75b91aae6928718a53608affb3d9e7b6c4fe89d | ||||||
| 
 | 
 | ||||||
| name:           hledger | name:           hledger | ||||||
| version:        1.14.1 | version:        1.14.1 | ||||||
| @ -72,6 +72,7 @@ extra-source-files: | |||||||
|     Hledger/Cli/Commands/Checkdupes.txt |     Hledger/Cli/Commands/Checkdupes.txt | ||||||
|     Hledger/Cli/Commands/Close.txt |     Hledger/Cli/Commands/Close.txt | ||||||
|     Hledger/Cli/Commands/Commodities.txt |     Hledger/Cli/Commands/Commodities.txt | ||||||
|  |     Hledger/Cli/Commands/Diff.txt | ||||||
|     Hledger/Cli/Commands/Files.txt |     Hledger/Cli/Commands/Files.txt | ||||||
|     Hledger/Cli/Commands/Help.txt |     Hledger/Cli/Commands/Help.txt | ||||||
|     Hledger/Cli/Commands/Import.txt |     Hledger/Cli/Commands/Import.txt | ||||||
| @ -121,6 +122,7 @@ library | |||||||
|       Hledger.Cli.Commands.Checkdupes |       Hledger.Cli.Commands.Checkdupes | ||||||
|       Hledger.Cli.Commands.Close |       Hledger.Cli.Commands.Close | ||||||
|       Hledger.Cli.Commands.Commodities |       Hledger.Cli.Commands.Commodities | ||||||
|  |       Hledger.Cli.Commands.Diff | ||||||
|       Hledger.Cli.Commands.Help |       Hledger.Cli.Commands.Help | ||||||
|       Hledger.Cli.Commands.Files |       Hledger.Cli.Commands.Files | ||||||
|       Hledger.Cli.Commands.Import |       Hledger.Cli.Commands.Import | ||||||
|  | |||||||
| @ -65,6 +65,7 @@ extra-source-files: | |||||||
| - Hledger/Cli/Commands/Checkdupes.txt | - Hledger/Cli/Commands/Checkdupes.txt | ||||||
| - Hledger/Cli/Commands/Close.txt | - Hledger/Cli/Commands/Close.txt | ||||||
| - Hledger/Cli/Commands/Commodities.txt | - Hledger/Cli/Commands/Commodities.txt | ||||||
|  | - Hledger/Cli/Commands/Diff.txt | ||||||
| - Hledger/Cli/Commands/Files.txt | - Hledger/Cli/Commands/Files.txt | ||||||
| - Hledger/Cli/Commands/Help.txt | - Hledger/Cli/Commands/Help.txt | ||||||
| - Hledger/Cli/Commands/Import.txt | - Hledger/Cli/Commands/Import.txt | ||||||
| @ -165,6 +166,7 @@ library: | |||||||
|   - Hledger.Cli.Commands.Checkdupes |   - Hledger.Cli.Commands.Checkdupes | ||||||
|   - Hledger.Cli.Commands.Close |   - Hledger.Cli.Commands.Close | ||||||
|   - Hledger.Cli.Commands.Commodities |   - Hledger.Cli.Commands.Commodities | ||||||
|  |   - Hledger.Cli.Commands.Diff | ||||||
|   - Hledger.Cli.Commands.Help |   - Hledger.Cli.Commands.Help | ||||||
|   - Hledger.Cli.Commands.Files |   - Hledger.Cli.Commands.Files | ||||||
|   - Hledger.Cli.Commands.Import |   - Hledger.Cli.Commands.Import | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user