Skip to content

Instantly share code, notes, and snippets.

@shkm
Last active February 29, 2016 06:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shkm/69d0e5275716bac3f9d1 to your computer and use it in GitHub Desktop.
Save shkm/69d0e5275716bac3f9d1 to your computer and use it in GitHub Desktop.
Quick 'n dirty Rabobank CSV export parser, parsing into a format that can easily be read by GnuCash.
#!/usr/bin/env ruby
# Parses Rabobank's CSV export into a CSV format more readable by GnuCash.
#
# To import, use the following settings:
#
# Start import on row: 2
# Separators: Semicolon
# Date format: y-m-d
# Currency format: Period
#
# Matchers should be defined in order to auto-sort
# to categories.
require 'csv'
require 'date'
module RaboParser
OUTPUT_SEPARATOR = ';'
COLUMN_ORDER = %i(date deposit withdrawal description category)
class Matcher
attr_reader :category
def initialize(category, comparison: :all, party: [], description: [])
@category = category
@comparison = comparison
@party = [*party]
@description = [*description]
unless %i(any all).include? @comparison
raise "Comparison must be :any or :all"
end
if @party.none? && @description.none?
raise "At least one pattern must be defined"
end
end
def match?(row)
self.send("#{@comparison}?", row)
end
private
def any?(row)
matches(row)
.any? { |m| m == :match }
end
def all?(row)
matches = matches(row)
.reject { |m| m == :no_patterns }
.all? { |m| m == :match }
return matches
end
def matches(row)
[match_attr?(row, :party), match_attr?(row, :description)]
end
def match_attr?(row, attr)
patterns = instance_variable_get "@#{attr}"
attr = row.send(attr)
return :no_patterns if patterns.none?
patterns.each do |p|
match = (p.is_a?(Regexp) ? !!p.match(attr) : p == attr)
return :match if match
end
:no_match
end
end
class CSV
def initialize(input_path, output_path)
@input_path = input_path
@output_path = output_path
end
def parse
parsed_rows = [header]
::CSV.foreach(@input_path) do |raw_row|
row = Row.new(raw_row)
parsed_rows << row.to_s
end
File.open(@output_path, 'w') do |file|
file.write(parsed_rows.join("\n"))
end
end
def header
COLUMN_ORDER
.map { |c| c.to_s.capitalize }
.join(OUTPUT_SEPARATOR)
end
end
class Row
def initialize(row)
@row = row
end
def date
Date.parse(@row[2]).to_s
end
def category
MATCHERS.each { |m| return m.category if m.match?(self) }
''
end
def deposit
deposit? ? amount : ''
end
def withdrawal
withdrawal? ? amount : ''
end
def deposit?
@row[3] == 'C'
end
def withdrawal?
@row[3] == 'D'
end
def amount
@row[4].strip
end
def description
# other party name + all descriptive fields
str = ([party] + descriptions)
.map(&:strip)
.reject(&:empty?)
.join(' ')
str
end
def descriptions
@row[10..15].map(&:strip).reject(&:empty?)
end
def party
@row[6].strip
end
def to_s
COLUMN_ORDER.map { |c| send(c) }.join(OUTPUT_SEPARATOR)
end
end
MATCHERS = [
# Define your matchers here. Examples below.
Matcher.new('Expenses:Bank Service Charge', description: [
/\AKosten \(Extra\) RaboCard/,
/\AKosten Rabo DirectPakket/,
/\AKosten \(Extra\) WereldPas/
]),
Matcher.new('Expenses:Groceries', description: [
/\ANettorama/,
/\AJumbo/,
/\APlus/,
]),
Matcher.new('Expenses:Utilities:Internet',
party: 'XS4ALL Internet B.V.')
]
end
def main
unless ARGV[0] && ARGV[1]
puts 'Usage: raboparser input_filename output_filename'
return
end
RaboParser::CSV.new(ARGV[0], ARGV[1]).parse
end
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment