<commit_before>#coding: utf-8
require 'pp'

module Reckon
  class App
    VERSION = "Reckon 0.3.10"
    attr_accessor :options, :accounts, :tokens, :seen, :csv_parser

    def initialize(options = {})
      self.options = options
      self.tokens = {}
      self.accounts = {}
      self.seen = {}
      self.options[:currency] ||= '$'
      options[:string] = File.read(options[:file]) unless options[:string]
      @csv_parser = CSVParser.new( options )
      learn!
    end

    def learn_from(ledger)
      LedgerParser.new(ledger).entries.each do |entry|
        entry[:accounts].each do |account|
          learn_about_account( account[:name],
                              [entry[:desc], account[:amount]].join(" ") ) unless account[:name] == options[:bank_account]
          seen[entry[:date]] ||= {}
          seen[entry[:date]][@csv_parser.pretty_money(account[:amount])] = true
        end
      end
    end

    def already_seen?(row)
      seen[row[:pretty_date]] && seen[row[:pretty_date]][row[:pretty_money]]
    end

    def learn!
      if options[:existing_ledger_file]
        fail "#{options[:existing_ledger_file]} doesn't exist!" unless File.exists?(options[:existing_ledger_file])
        ledger_data = File.read(options[:existing_ledger_file])
        learn_from(ledger_data)
      end
    end

    def learn_about_account(account, data)
      accounts[account] ||= 0
      tokenize(data).each do |token|
        tokens[token] ||= {}
        tokens[token][account] ||= 0
        tokens[token][account] += 1
        accounts[account] += 1
      end
    end

    def tokenize(str)
      str.downcase.split(/[\s\-]/)
    end

    def walk_backwards
      seen_anything_new = false
      each_row_backwards do |row|
        puts Terminal::Table.new(:rows => [ [ row[:pretty_date], row[:pretty_money], row[:description] ] ])

        if already_seen?(row)
          puts "NOTE: This row is very similar to a previous one!"
          if !seen_anything_new
            puts "Skipping..."
            next
          end
        else
          seen_anything_new = true
        end

        ledger = if row[:money] > 0
          out_of_account = ask("Which account provided this income? ([account]/[q]uit/[s]kip) ") { |q|
            q.readline = true
            q.default = guess_account(row)
          }
          finish if out_of_account == "quit" || out_of_account == "q"
          if out_of_account == "skip" || out_of_account == "s"
            puts "Skipping"
            next
          end

          ledger_format( row,
                         [options[:bank_account], row[:pretty_money]],
                         [out_of_account, row[:pretty_money_negated]] )
        else
          into_account = ask("To which account did this money go? ([account]/[q]uit/[s]kip) ") { |q|
            q.readline = true
            q.default = guess_account(row)
          }
          finish if into_account == "quit" || into_account == 'q'
          if into_account == "skip" || into_account == 's'
            puts "Skipping"
            next
          end

          ledger_format( row,
                         [into_account, row[:pretty_money_negated]],
                         [options[:bank_account], row[:pretty_money]] )
        end

        learn_from(ledger)
        output(ledger)
      end
    end

    def finish
      options[:output_file].close unless options[:output_file] == STDOUT
      puts "Exiting."
      exit
    end

    def output(ledger_line)<commit_msg>Enable tab completion for both debit and credit money<commit_after>#coding: utf-8
require 'pp'

module Reckon
  class App
    VERSION = "Reckon 0.3.10"
    attr_accessor :options, :accounts, :tokens, :seen, :csv_parser

    def initialize(options = {})
      self.options = options
      self.tokens = {}
      self.accounts = {}
      self.seen = {}
      self.options[:currency] ||= '$'
      options[:string] = File.read(options[:file]) unless options[:string]
      @csv_parser = CSVParser.new( options )
      learn!
    end

    def learn_from(ledger)
      LedgerParser.new(ledger).entries.each do |entry|
        entry[:accounts].each do |account|
          learn_about_account( account[:name],
                              [entry[:desc], account[:amount]].join(" ") ) unless account[:name] == options[:bank_account]
          seen[entry[:date]] ||= {}
          seen[entry[:date]][@csv_parser.pretty_money(account[:amount])] = true
        end
      end
    end

    def already_seen?(row)
      seen[row[:pretty_date]] && seen[row[:pretty_date]][row[:pretty_money]]
    end

    def learn!
      if options[:existing_ledger_file]
        fail "#{options[:existing_ledger_file]} doesn't exist!" unless File.exists?(options[:existing_ledger_file])
        ledger_data = File.read(options[:existing_ledger_file])
        learn_from(ledger_data)
      end
    end

    def learn_about_account(account, data)
      accounts[account] ||= 0
      tokenize(data).each do |token|
        tokens[token] ||= {}
        tokens[token][account] ||= 0
        tokens[token][account] += 1
        accounts[account] += 1
      end
    end

    def tokenize(str)
      str.downcase.split(/[\s\-]/)
    end

    def walk_backwards
      seen_anything_new = false
      each_row_backwards do |row|
        puts Terminal::Table.new(:rows => [ [ row[:pretty_date], row[:pretty_money], row[:description] ] ])

        if already_seen?(row)
          puts "NOTE: This row is very similar to a previous one!"
          if !seen_anything_new
            puts "Skipping..."
            next
          end
        else
          seen_anything_new = true
        end

        ledger = if row[:money] > 0
          out_of_account = ask("Which account provided this income? ([account]/[q]uit/[s]kip) ") { |q|
            possible_answers = weighted_account_match( row )
            q.completion = possible_answers
            q.readline = true
            q.default = possible_answers.first 
          }
          finish if out_of_account == "quit" || out_of_account == "q"
          if out_of_account == "skip" || out_of_account == "s"
            puts "Skipping"
            next
          end

          ledger_format( row,
                         [options[:bank_account], row[:pretty_money]],
                         [out_of_account, row[:pretty_money_negated]] )
        else
          into_account = ask("To which account did this money go? ([account]/[q]uit/[s]kip) ") { |q|
            possible_answers = weighted_account_match( row )
            q.completion = possible_answers
            q.readline = true
            q.default = possible_answers.first 
          }
          finish if into_account == "quit" || into_account == 'q'
          if into_account == "skip" || into_account == 's'
            puts "Skipping"
            next
          end

          ledger_format( row,
                         [into_account, row[:pretty_money_negated]],
                         [options[:bank_account], row[:pretty_money]] )
        end

        learn_from(ledger)
        output(ledger)
      end
    end

    def finish
      options[:output_file].close unless options[:output_file] == STDOUT
      puts "Exiting."
      exit
    end

    def output(ledger_line)
{'fraction_non_alphanumeric': 0.12113686534216335, 'fraction_numerical': 0.0027593818984547464, 'mean_word_length': 2.072912250953794, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6566264', 'n_tokens_mistral': 2236, 'n_tokens_neox': 2133, 'n_words': 459}