func getTransactions() ([]*ledger.Transaction, error) { ledgerLock.Lock() defer ledgerLock.Unlock() ledgerFileReader, err := os.Open(ledgerFileName) if err != nil { return nil, err } trans, terr := ledger.ParseLedger(ledgerFileReader) if terr != nil { return nil, fmt.Errorf("%s:%s", ledgerFileName, terr.Error()) } ledgerFileReader.Close() return trans, nil }
func main() { var startDate, endDate time.Time startDate = time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local) endDate = time.Now().Add(time.Hour * 24) var startString, endString string var columnWidth, transactionDepth int var showEmptyAccounts bool var columnWide bool var ledgerFileName string ledger.TransactionDateFormat = "2006/01/02" TransactionDateFormat := ledger.TransactionDateFormat flag.StringVar(&ledgerFileName, "f", "", "Ledger file name (*Required).") flag.StringVar(&startString, "b", startDate.Format(TransactionDateFormat), "Begin date of transaction processing.") flag.StringVar(&endString, "e", endDate.Format(TransactionDateFormat), "End date of transaction processing.") flag.BoolVar(&showEmptyAccounts, "empty", false, "Show empty (zero balance) accounts.") flag.IntVar(&transactionDepth, "depth", -1, "Depth of transaction output (balance).") flag.IntVar(&columnWidth, "columns", 80, "Set a column width for output.") flag.BoolVar(&columnWide, "wide", false, "Wide output (same as --columns=132).") flag.Parse() if columnWidth == 80 && columnWide { columnWidth = 132 } if len(ledgerFileName) == 0 { flag.Usage() return } parsedStartDate, tstartErr := time.Parse(TransactionDateFormat, startString) parsedEndDate, tendErr := time.Parse(TransactionDateFormat, endString) if tstartErr != nil || tendErr != nil { fmt.Println("Unable to parse start or end date string argument.") fmt.Println("Expected format: YYYY/MM/dd") return } args := flag.Args() if len(args) == 0 { fmt.Println("Specify a command.") return } ledgerFileReader, err := os.Open(ledgerFileName) if err != nil { fmt.Println(err) return } defer ledgerFileReader.Close() generalLedger, parseError := ledger.ParseLedger(ledgerFileReader) if parseError != nil { fmt.Println(parseError) return } timeStartIndex, timeEndIndex := 0, 0 for idx := 0; idx < len(generalLedger); idx++ { if generalLedger[idx].Date.After(parsedStartDate) { timeStartIndex = idx break } } for idx := len(generalLedger) - 1; idx >= 0; idx-- { if generalLedger[idx].Date.Before(parsedEndDate) { timeEndIndex = idx break } } generalLedger = generalLedger[timeStartIndex : timeEndIndex+1] containsFilterArray := args[1:] switch strings.ToLower(args[0]) { case "balance", "bal": PrintBalances(ledger.GetBalances(generalLedger, containsFilterArray), showEmptyAccounts, transactionDepth, columnWidth) case "print": PrintLedger(generalLedger, columnWidth) case "register", "reg": PrintRegister(generalLedger, containsFilterArray, columnWidth) case "stats": PrintStats(generalLedger) } }
func main() { var ledgerFileName string var accountSubstring, csvFileName, csvDateFormat string var negateAmount bool var fieldDelimiter string flag.BoolVar(&negateAmount, "neg", false, "Negate amount column value.") flag.StringVar(&ledgerFileName, "f", "", "Ledger file name (*Required).") flag.StringVar(&csvDateFormat, "date-format", "01/02/2006", "Date format.") flag.StringVar(&fieldDelimiter, "delimiter", ",", "Field delimiter.") flag.Parse() args := flag.Args() if len(args) != 2 { usage() } else { accountSubstring = args[0] csvFileName = args[1] } csvFileReader, err := os.Open(csvFileName) if err != nil { fmt.Println("CSV: ", err) return } defer csvFileReader.Close() ledgerFileReader, err := os.Open(ledgerFileName) if err != nil { fmt.Println("Ledger: ", err) return } defer ledgerFileReader.Close() generalLedger, parseError := ledger.ParseLedger(ledgerFileReader) if parseError != nil { fmt.Println(parseError) return } var matchingAccount string matchingAccounts := ledger.GetBalances(generalLedger, []string{accountSubstring}) if len(matchingAccounts) < 1 { fmt.Println("Unable to find matching account.") return } else { matchingAccount = matchingAccounts[len(matchingAccounts)-1].Name } allAccounts := ledger.GetBalances(generalLedger, []string{}) csvReader := csv.NewReader(csvFileReader) csvReader.Comma, _ = utf8.DecodeRuneInString(fieldDelimiter) csvRecords, _ := csvReader.ReadAll() classes := make([]bayesian.Class, len(allAccounts)) for i, bal := range allAccounts { classes[i] = bayesian.Class(bal.Name) } classifier := bayesian.NewClassifier(classes...) for _, tran := range generalLedger { payeeWords := strings.Split(tran.Payee, " ") for _, accChange := range tran.AccountChanges { if strings.Contains(accChange.Name, "Expense") { classifier.Learn(payeeWords, bayesian.Class(accChange.Name)) } } } // Find columns from header var dateColumn, payeeColumn, amountColumn int dateColumn, payeeColumn, amountColumn = -1, -1, -1 for fieldIndex, fieldName := range csvRecords[0] { fieldName = strings.ToLower(fieldName) if strings.Contains(fieldName, "date") { dateColumn = fieldIndex } else if strings.Contains(fieldName, "description") { payeeColumn = fieldIndex } else if strings.Contains(fieldName, "payee") { payeeColumn = fieldIndex } else if strings.Contains(fieldName, "amount") { amountColumn = fieldIndex } else if strings.Contains(fieldName, "expense") { amountColumn = fieldIndex } } if dateColumn < 0 || payeeColumn < 0 || amountColumn < 0 { fmt.Println("Unable to find columns required from header field names.") return } expenseAccount := ledger.Account{Name: "unknown:unknown", Balance: new(big.Rat)} csvAccount := ledger.Account{Name: matchingAccount, Balance: new(big.Rat)} for _, record := range csvRecords[1:] { inputPayeeWords := strings.Split(record[payeeColumn], " ") csvDate, _ := time.Parse(csvDateFormat, record[dateColumn]) if !existingTransaction(generalLedger, csvDate, inputPayeeWords[0]) { // Classify into expense account _, likely, _ := classifier.LogScores(inputPayeeWords) if likely >= 0 { expenseAccount.Name = string(classifier.Classes[likely]) } // Negate amount if required expenseAccount.Balance.SetString(record[amountColumn]) if negateAmount { expenseAccount.Balance.Neg(expenseAccount.Balance) } // Csv amount is the negative of the expense amount csvAccount.Balance.Neg(expenseAccount.Balance) // Create valid transaction for print in ledger format trans := &ledger.Transaction{Date: csvDate, Payee: record[payeeColumn]} trans.AccountChanges = []ledger.Account{csvAccount, expenseAccount} PrintTransaction(trans, 80) } } }