Module: Bankjob

Defined in:
lib/bankjob.rb,
lib/bankjob/cli.rb,
lib/bankjob/payee.rb,
lib/bankjob/scraper.rb,
lib/bankjob/support.rb,
lib/bankjob/statement.rb,
lib/bankjob/transaction.rb,
lib/bankjob/bankjob_runner.rb

Defined Under Namespace

Classes: BankjobRunner, CLI, Payee, Scraper, Statement, Transaction

Constant Summary collapse

BANKJOB_VERSION =
'0.5.2'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.capitalize_words(message) ⇒ Object

Takes a string and capitalizes the first letter of every word and forces the rest of the word to be lowercase.

This is a utility method for use in scrapers to make descriptions more readable.



62
63
64
# File 'lib/bankjob/support.rb', line 62

def self.capitalize_words(message)
  message.downcase.gsub(/\b\w/){$&.upcase}
end

.create_date_time(date_time_raw) ⇒ Object

Takes a date-time as a string or as a Time or DateTime object and returns it as either a Time object.

This is useful in the setter method of a date attribute allowing the date to be set as any type but stored internally as an object compatible with conversion through strftime() (Bankjob::Transaction uses this internally in the setter for date for example



16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/bankjob/support.rb', line 16

def self.create_date_time(date_time_raw)
  if (date_time_raw.is_a?(Time)) then
    # It's already a Time 
    return date_time_raw
  elsif (date_time_raw.to_s.strip.empty?)
    # Nil or non dates are returned as nil
    return nil
  else
    # Assume it can be converted to a time
    return Time.parse(date_time_raw.to_s)
  end
end

.date_time_to_csv(time) ⇒ Object

Takes a Time or DateTime and formats in a suitable format for comma separated values files. The format produced is suitable for loading into an Excel-like spreadsheet program being automatically treated as a date.

A string is returned with the format “YY-MM-DD HH:MM:SS”. For example, the 1st of February 2009 at 2:34PM and 56 second becomes “2009-02-01 14:34:56”

Note must use a Time, or DateTime, not a String, nor a Date.



51
52
53
# File 'lib/bankjob/support.rb', line 51

def self.date_time_to_csv(time)
  time.nil? ? "" : "#{time.strftime( '%Y-%m-%d %H:%M:%S' )}"
end

.date_time_to_ofx(time) ⇒ Object

Takes a Time or DateTime and formats it in the correct format for OFX date elements.

The OFX format is a string of digits in the format “YYMMDDHHMMSS”. For example, the 1st of February 2009 at 2:34PM and 56 second becomes “20090201143456”

Note must use a Time, or DateTime, not a String, nor a Date.



37
38
39
# File 'lib/bankjob/support.rb', line 37

def self.date_time_to_ofx(time)
  time.nil? ? "" : "#{time.strftime( '%Y%m%d%H%M%S' )}"
end

.string_to_float(string, decimal) ⇒ Object

converts a numeric string to a float given the specified decimal separator.



70
71
72
73
74
75
76
77
78
79
80
# File 'lib/bankjob/support.rb', line 70

def self.string_to_float(string, decimal)
  return nil if string.nil?
  amt = string.gsub(/\s/, '')
  if (decimal == ',') # E.g.  "1.000.030,99"
    amt.gsub!(/\./, '')  # strip out . 1000s separator
    amt.gsub!(/,/, '.')  # replace decimal , with .
  elsif (decimal == '.')
    amt.gsub!(/,/, '')  # strip out comma 1000s separator
  end
  return amt.to_f
end

.wesabe_help(wesabe_args, logger) ⇒ Object

Helps the user determine how to upload to their Wesabe account.

When used with no args, will give generic help information. When used with Wesabe account and password, will log into Wesabe and list the users accounts, and suggest command line args to upload to each account.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/bankjob/support.rb', line 170

def self.wesabe_help(wesabe_args, logger)
  if (wesabe_args.nil? or wesabe_args.length != 2)
    puts <<-EOF
Wesabe (http://www.wesabe.com) is an online bank account management tool (like Mint)
that allows you to upload (in some cases automatically) your bank statements and
automatically convert them into a more readable format to allow you to track
your spending and much more. Wesabe comes with its own community attached.

Bankjob has no affiliation with Wesabe, but allows you to upload the statements it
generates to your Wesabe account automatically.

To use Wesabe you need the Wesabe Ruby gem installed:
See the gem at http://github.com/wesabe/wesabe-rubygem
Install the gem with:
$ sudo gem install -r --source http://gems.github.com/ wesabe-wesabe
(on Windows, omit the "sudo")

You also need your Wesabe login name and password, and, if you have
more than one account on Wesabe, the id number of the account.
This is not a real account number - it's simply a counter that Wesabe uses.
If you have a single account it will be '1', if you have two accounts the
second account will be '2', etc.

Bankjob will help you find this number by listing your Wesabe accounts for you.
Simply use:
bankjob -wesabe_help "username password"
(The quotes are important - this is a single argument to Bankjob with two words)

If you already know the number of the account and you want to start uploading use:

bankjob [other bankjob args] --wesabe "username password id"

E.g.
bankjob --scraper bpi_scraper.rb --wesabe "bloggsy pw123 2"

If you only have a single account, you don't need to specify the id number
(but Bankjob will check and will fail with an error if you have more than one account)

bankjob [other bankjob args] --wesabe "username password"

If in any doubt --wesabe-help "username password" will set you straight.

Troubleshooting:
- If you see an error like Wesabe::Request::Unauthorized, then chances
are your username or password for Wesabe is incorrect.

- If you see an error "end of file reached" then it may be that you are logged
into the Wesabe account to which you are trying to upload - perhaps in a browser.
In this case, log out from Wesabe in the browser, _wait a minute_, then try again.
EOF
  else
    load_wesabe
    begin
      puts "Connecting to Wesabe...\n"
      wuser, wpass = *wesabe_args
      wesabe = Wesabe.new(wuser, wpass)
      puts "You have #{wesabe.accounts.length} Wesabe accounts:"
      wesabe.accounts.each do ||
        puts " Account Name: #{.name}"
        puts "    wesabe id: #{.id}"
        puts "   account no: #{.number}"
        puts "         type: #{.type}"
        puts "      balance: #{.balance}"
        puts "         bank: #{.financial_institution.name}"
        puts "To upload to this account use:"
        puts "  bankjob [other bankjob args] --wesabe \"#{wuser} password #{.id}\""
        puts ""
        if wesabe.accounts.length == 1
          puts "Since you have one account you do not need to specify the id number, use:"
          puts "  bankjob [other bankjob args] --wesabe \"#{wuser} password\""
        end
      end
    rescue Exception => e
      msg =<<-EOF
Failed to get Wesabe account information due to: #{e.message}.
Check your username and password or use:
bankjob --wesabe-help
with no arguments for more details.
      EOF
	logger.debug(msg)
	logger.debug(e)
	raise msg
    end
  end
end

.wesabe_upload(wesabe_args, ofx_doc, logger) ⇒ Object

Uploads the given OFX document to the Wesabe account specified in the wesabe_args



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/bankjob/support.rb', line 130

def self.wesabe_upload(wesabe_args, ofx_doc, logger)
  if (wesabe_args.nil? or (wesabe_args.length < 2 and wesabe_args.length > 3))
    raise "Incorrect number of args for Wesabe (#{wesabe_args}), should be 2 or 3."
  else
    load_wesabe
    wuser, wpass, windex = *wesabe_args
    wesabe = Wesabe.new(wuser, wpass)
    num_accounts = wesabe.accounts.length
    if num_accounts == 0
      raise "The user \"#{wuser}\" has no Wesabe accounts. Create one at www.wesabe.com before attempting to upload a statement."
    elsif (not windex.nil? and (num_accounts < windex.to_i))
      raise "The user \"#{wuser}\" has only #{num_accounts} Wesabe accounts, but the account index #{windex} was specified."
    elsif windex.nil?
      if num_accounts > 1
        raise "The user \"#{wuser}\" has #{num_accounts} Wesabe accounts, so the account index must be specified in the WESABE_ARGS."
      else
        # we have only one account, no need to specify the index
        windex = 1
      end
    elsif windex.to_i == 0
      raise "The Wesabe account index must be between 1 and #{num_accounts}. #{windex} is not acceptable"
    end
    logger.debug("Attempting to upload statement to the ##{windex} Wesabe account for user #{wuser}...")
    # Get the account at the index (which is not necessarily the index in the array 
    # so we use the account(index) method to get it
     = wesabe.(windex.to_i)
    uploader = .new_upload
    uploader.statement = ofx_doc
    uploader.upload!
    logger.info("Uploaded statement to Wesabe account #{.name}, the ##{windex} account for user #{wuser}, with the result: #{uploader.status}")
  end
end

Instance Method Details

#select_and_submit(page, form_name, select_name, selection) ⇒ Object

Finds a selector field in a named form in the given Mechanize page, selects the suggested label



85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/bankjob/support.rb', line 85

def select_and_submit(page, form_name, select_name, selection)
  option = nil
  form  = page.form(form_name)
  unless form.nil?
    selector = form.field(select_name)
    unless selector.nil?
      option = select_option(selector, selection)
      form.submit
    end
  end
  return option
end

#select_option(selector, selection) ⇒ Object

Given a Mechanize::Form:SelectList selector will attempt to select the option specified by selection. This algorithm is used:

The first option with a label equal to the +selection+ is selected.
 - if none is found then -
The first option with a value equal to the +selection+ is selected.
 - if none is found then -
The first option with a label or value that equal to the +selection+ is selected
after removing non-alphanumeric characters from the label or value
 - if none is found then -
The first option with a lable or value that _contains_ the +selection+

If matching option is found, the #select is called on it. If no option is found, nil is returned - otherwise the option is returned



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/bankjob/support.rb', line 114

def select_option(selector, selection)
  options = selector.options.select { |o| o.text == selection }
  options = selector.options.select { |o| o.value == selection } if options.empty?
  options = selector.options.select { |o| o.text.gsub(/[^a-zA-Z0-9]/,"") == selection } if options.empty?
  options = selector.options.select { |o| o.value.gsub(/[^a-zA-Z0-9]/,"") == selection } if options.empty?
  options = selector.options.select { |o| o.text.include?(selection) } if options.empty?
  options = selector.options.select { |o| o.value.include?(selection) } if options.empty?

  option = options.first
  option.select() unless option.nil?
  return option
end