hledger 0.5 → 0.5.1
raw patch · 3 files changed
+118/−3 lines, 3 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- ConvertCommand.hs +114/−0
- Version.hs +1/−1
- hledger.cabal +3/−2
+ ConvertCommand.hs view
@@ -0,0 +1,114 @@+{-|++Convert account data in CSV format (eg downloaded from a bank) to ledger+format, and print it on stdout.++Usage: hledger convert CSVFILE ACCOUNTNAME RULESFILE++ACCOUNTNAME is the base account to use for transactions. RULESFILE+provides some rules to help convert the data. It should contain paragraphs+separated by one blank line. The first paragraph is a single line of five+comma-separated numbers, which are the csv field positions corresponding+to the ledger transaction's date, status, code, description, and amount.+All other paragraphs specify one or more regular expressions, followed by+the ledger account to use when a transaction's description matches any of+them. A regexp may optionally have a replacement pattern specified after =.+Here's an example rules file:++> 0,2,3,4,1+>+> ATM DEPOSIT+> assets:bank:checking+>+> (TO|FROM) SAVINGS+> assets:bank:savings+>+> ITUNES+> BLKBSTR=BLOCKBUSTER+> expenses:entertainment++Roadmap: +Support for other formats will be added. To update a ledger file, pipe the+output into the import command. The rules will move to a hledger config+file. When no rule matches, accounts will be guessed based on similarity+to descriptions in the current ledger, with interactive prompting and+optional rule saving.++-}++module ConvertCommand where+import Data.Maybe (isJust)+import Data.List.Split (splitOn)+import Options -- (Opt,Debug)+import Ledger.Types (Ledger,AccountName)+import Ledger.Utils (strip)+import System (getArgs)+import System.IO (stderr, hPutStrLn)+import Text.CSV (parseCSVFromFile, Record)+import Text.Printf (printf)+import Text.Regex.PCRE ((=~))+import Data.Maybe+import Ledger.Dates (firstJust, showDate)+import System.Locale (defaultTimeLocale)+import Data.Time.Format (parseTime)+import Control.Monad (when)+++convert :: [Opt] -> [String] -> Ledger -> IO ()+convert opts args l = do+ when (length args /= 3) (error "please specify a csv file, base account, and import rules file.")+ let [csvfile,baseacct,rulesfile] = args+ rulesstr <- readFile rulesfile+ (fieldpositions,rules) <- parseRules rulesstr+ parse <- parseCSVFromFile csvfile+ let records = case parse of+ Left e -> error $ show e+ Right rs -> reverse rs+ mapM_ (print_ledger_txn (Debug `elem` opts) (baseacct,fieldpositions,rules)) records+++type Rule = ([[String]] -- list of [pattern,replacement]. replacement may or may not be present.+ ,AccountName) -- account name to use for a transaction matching this rule++parseRules :: String -> IO ([Int],[Rule])+parseRules s = do+ let ls = map strip $ lines s+ let paras = splitOn [""] ls+ let fieldpositions = map read $ splitOn "," $ head $ head paras+ let rules = [(map (splitOn "=") $ init p, last p) | p <- tail paras]+ return (fieldpositions,rules)++print_ledger_txn debug (baseacct,fieldpositions,rules) record@(a:b:c:d:e) = do+ let [date,cleared,number,description,amount] = map (record !!) fieldpositions+ amount' = strnegate amount where strnegate ('-':s) = s+ strnegate s = '-':s+ unknownacct | (read amount' :: Double) < 0 = "income:unknown"+ | otherwise = "expenses:unknown"+ (acct,desc) = choose_acct_desc rules (unknownacct,description)+ when (debug) $ hPutStrLn stderr $ printf "using %s for %s" desc description+ putStrLn $ printf "%s%s %s" (fixdate date) (if not (null number) then printf " (%s)" number else "") desc+ putStrLn $ printf " %-30s %15s" acct (printf "$%s" amount' :: String)+ putStrLn $ printf " %s\n" baseacct+print_ledger_txn True _ record = do+ hPutStrLn stderr $ printf "ignoring %s" $ show record+print_ledger_txn _ _ _ = return ()++choose_acct_desc rules (acct,desc) | null matches = (acct,desc)+ | otherwise = (a,d)+ where+ matches = filter (any (desc =~) . map head . fst) rules+ (pats,a) = head matches :: Rule+ ((before,match,after,groups),repl) = head $ filter isMatch $ map (\(pat:repl) -> (desc=~pat,repl)) pats+ d = head $ repl ++ [match] -- show the replacement text if any, or the matched text++isMatch :: ((String, String, String, [String]),[String]) -> Bool+isMatch ((_,m,_,_),_) = not $ null m++fixdate :: String -> String+fixdate s = maybe "0000/00/00" showDate $ + firstJust+ [parseTime defaultTimeLocale "%Y/%m/%d" s+ ,parseTime defaultTimeLocale "%Y-%m-%d" s+ ,parseTime defaultTimeLocale "%m/%d/%Y" s+ ,parseTime defaultTimeLocale "%m-%d-%Y" s+ ]
Version.hs view
@@ -5,7 +5,7 @@ import Options (progname) -- updated by build process from VERSION-version = "0.5.0"+version = "0.5.1" #ifdef PATCHES -- a "make" development build defines PATCHES from the repo state patchlevel = "." ++ show PATCHES -- must be numeric !
hledger.cabal view
@@ -1,6 +1,6 @@ Name: hledger -- updated by build process from VERSION-Version: 0.5+Version: 0.5.1 Category: Finance Synopsis: A ledger-compatible text-based accounting tool. Description: hledger is a partial haskell clone of John Wiegley's "ledger" text-based@@ -20,7 +20,7 @@ Flag vty description: enable the curses ui- default: True+ default: False Flag happs description: enable the web ui@@ -58,6 +58,7 @@ Other-Modules: AddCommand BalanceCommand+ ConvertCommand HistogramCommand Options PrintCommand