Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

About this user

Scott Chacon

« Newer Snippets
Older Snippets »
Showing 1-4 of 4 total  RSS 

Automatic Bank Balance Checking at WaMu

I bank at Washington Mutual, and I wanted to see when I was spending too much, or get automated updates of what I was spending money on each day and what my balance was. However, it's really difficult to do and WaMu won't let you use an API or something helpful like that, so I wrote my own little screen-scraper to do it.

This requires WWW::Mechanize, but it works quite well.

require 'rubygems'
require 'mechanize'

agent = WWW::Mechanize.new

# login to the account
puts "Login"
page = agent.get('https://online.wamu.com/IdentityManagement/Logon.aspx')
login_form = page.forms.with.name("frmLogin").first
login_form.txtUserID = '<UNAME>'
login_form.password = '<PWD>'
page = agent.submit(login_form, login_form.buttons.first)

# click on the download button
puts "Download"
dl_link = page.links.with.text(/WAMU FREE CHECKING/)
page = agent.click(dl_link)

line_items = []

trs = (page/"table#_ctl0_depositTransactionsGrid/tr")
trs.shift
trs.each do |tr|
  tds = (tr/:td)

  dtd = tds[1].inner_html
  js_call = dtd.match(/showDDATransactionDetails\('(.*)'\);/)[1]
  js_fields = js_call.split("','")

  item = {}
  item['type'] = js_fields[1]
  item['descr'] = js_fields[3]
  item['amount'] = js_fields[4]
  item['tranid'] = js_fields[6]
  # if !tranid = ovedraft charge / bank fee

  item['date'] = tds[0].inner_html
  item['debit'] = tds[3].inner_html
  item['credit'] = tds[4].inner_html
  item['balance'] = tds[5].inner_html

  line_items << item
end

pp line_items.first


FizzBuzz

This is pretty stupid, but it's a response to the article recently Dugg here:
http://www.codinghorror.com/blog/archives/000781.html

It is about how a lot of job applicants have a hard time actually programming. My friend and I were amused by it and started going back and forth with different implementations until it got to this point.

The problem is:

'Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".'

Now, it can be done like this:
1.upto(100) { |n| puts n % 3 == 0 ? n % 5 == 0 ? "fizzbuzz" : "buzz" : n % 5 == 0 ? "fizz" : n }


But, this is what I ended up with, which turns out actually to be faster and obviously far more flexible:

class FizzBuzz

  def initialize(start_number, end_number)
    @starting = start_number
    @ending = end_number
    @phrase_multiples = []
  end

  def add_phrase_multiple(phrase, multiple)
    @phrase_multiples << [phrase, multiple]
  end

  def print_phrases
    fb_array = process_phrases
    puts fb_array.collect { |e| e[1] || e[0] }.join("\n")
  end

  private

  def process_phrases
    rarray = Array.new(@ending - @starting)
    rarray = rarray.each_with_index { |item, i| rarray[i] = [i + @starting, item] }
    @phrase_multiples.each { |pm| fill_multiples(rarray, pm[1], pm[0]) }
    rarray
  end

  def fill_multiples(fill_array, the_int, printed)
    (the_int - (fill_array[0][0] % the_int)).step(fill_array.size - 1, the_int) do |i|
      fill_array[i][1] = fill_array[i][1].to_s + printed.to_s
    end
  end

end

fb = FizzBuzz.new(1,100)
fb.add_phrase_multiple('fizz', 3)
fb.add_phrase_multiple('buzz', 5)
fb.print_phrases

Garmin Forerunner TCX file processing

This is a small ruby file I wrote to process the TCX file that I can download from MotionBased website that is processed from the data off my Garmin Forerunner 305. It processes the XML file and generates a little badge/infographic that I can put on a website.

// insert code here..
require "date"
require "rexml/document"
require 'rubygems'
require 'RMagick'

class ForeRunner
  
  M2MI = 1609.344 # meters to miles
  MINIMUM_LAP_TIME = 300 # minimum seconds to count as a full lap
  
  attr_accessor :laps, :total_time, :distance, :calories
  
  def initialize(file)
    @source_doc = REXML::Document.new file
    
    @laps = 0
    @lap_times = Array.new
    @lap_bpm = Array.new
    @full_laps = 0

    @total_time = 0    
    @distance = 0
    @calories = 0
    @map_data = []
    
    self.process_file  
  end
  
  def generate_infographic(output_filename)
    canvas = Magick::Image.new(250, 80)
    map_size = 50
    map_color = 'green'

    max_lap_height = 25
    
    gc = Magick::Draw.new

    # Draw ellipse
    gc.stroke('grey50')
    gc.stroke_width(2)
    gc.fill_opacity(0)

    # draw the relative lap times
    lap = 0
    max_time = @lap_times.max
    @lap_times.each do |s|
      lap += 1
      x = 10 + (lap * 5)
      y = 60 - (s / (max_time / max_lap_height))
      gc.line(x, 60, x, y)
    end

    #draw the heartbeat avg
    gc.stroke('#c9a')
    
    lap = 0
    lx = nil
    ly = nil
    max_bpm = @lap_bpm.max
    min_bpm = @lap_bpm.min
    
    max_bpm_height = 18
    
    @lap_bpm.each do |s|
      lap += 1
      x = 10 + (lap * 5)
      y = 21 - ( (s - min_bpm) / ((max_bpm - min_bpm) / max_bpm_height))
      if !lx
        lx = x
        ly = y
      end

      gc.line(lx, ly, x, y)

      lx = x
      ly = y
    end

    # draw the map
    lat_diff = (@map_data['max_lat'] - @map_data['min_lat']).abs
    lon_diff = (@map_data['max_lon'] - @map_data['min_lon']).abs

    lat_off = lat_diff / map_size
    lon_off = lon_diff / map_size
    
    gc.fill(map_color)
    @map_data['map_data'].each do |i|
      lt = (map_size - ((i[0] - @map_data['min_lat']) / lat_off)).round
      lg = ((i[1] - @map_data['min_lon']) / lon_off).round
      gc.point((240 - map_size) + lg, (65 - map_size) + lt)
    end

    # Annotate
    gc.stroke('transparent')
    gc.fill('black')
    gc.text(120, 15, @distance.to_s[0, 5] + ' mi')
    gc.text(120, 30, (@total_time / 60 / 60).to_s[0, 4] + ' hr')
    gc.text(120, 45, @calories.to_s + ' cal')

    gc.fill('#555')
    total_hr = 0 
    @lap_bpm.each { |hr| total_hr += hr }
    gc.text(15, 32, 'avg bpm: ' + (total_hr / @laps).round.to_s)
    gc.text(15, 75, 'avg pace: ' + ((@total_time / @full_laps) / 60).to_s[0, 4] + ' min/lap')

    gc.draw(canvas)
    canvas.write(output_filename)
  end
  
  protected 
  
  def process_file
    @source_doc.elements.each('TrainingCenterDatabase/Activities/Activity/Lap/*') do |element| 
      if element.name == "TotalTimeSeconds"
        @lap_times << element.text.to_f
        if element.text.to_f > 300  # for removing warmup and warmdown laps
          @total_time += element.text.to_f
          @full_laps += 1
        end
        @laps += 1
      end

      if element.name == "DistanceMeters" 
        @distance += (element.text.to_f / M2MI)
      end

      if element.name == "Calories" 
        @calories += element.text.to_f
      end

      if element.name == "AverageHeartRateBpm" 
        element.elements.each('Value') { |v| @lap_bpm << v.text.to_f }
      end
    end
    @map_data = self.generate_map_points
  end
  
  def generate_map_points  
    map = []

    max_lat = -300
    max_lon = -300
    min_lat = 300
    min_lon = 300

    @source_doc.elements.each('TrainingCenterDatabase/Activities/Activity/Lap/Track/Trackpoint/*') do |element|
      if element.elements['LatitudeDegrees']
        lat = element.elements['LatitudeDegrees'].text.to_f
        lon = element.elements['LongitudeDegrees'].text.to_f

        map << [lat, lon]
        max_lat = lat if (lat > max_lat) 
        max_lon = lon if (lon > max_lon) 
        min_lat = lat if (lat < min_lat) 
        min_lon = lon if (lon < min_lon) 
      end
    end
    {'map_data' => map, 'max_lat' => max_lat, 'min_lat' => min_lat, 'max_lon' => max_lon, 'min_lon' => min_lon}
  end
  

end


if ARGV[0].nil? 
  puts "This program takes one argument, a Garmin Forerunner TCX File"
  puts "Like this: run.rb filename.tcx"
  exit
end

output_filename = ARGV[0].gsub(/\.tcx$/, "-" + Time.now.strftime("%Y%m%d") + ".png")
puts "Generating Graph #{output_filename}"

fr = ForeRunner.new(File.new(ARGV[0]))
fr.generate_infographic(output_filename)

Typo export to WordPress WXR

If you want to move from Typo to Wordpress, you can use this to export the strange Wordpress RSS file format (comments and all).

Just add this to the XmlController :

  def wp
    @articles = Article.find( :all, :include => [:categories, :tags, :user, :blog])
  end


And then add this file (app/views/xml/wp.rxml):

// insert code here..
xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"

xml.rss 'version' => "2.0", 'xmlns:content' => "http://purl.org/rss/1.0/modules/content/", 'xmlns:wfw' => "http://wellformedweb.org/CommentAPI/", 'xmlns:dc' => "http://purl.org/dc/elements/1.1/", 'xmlns:wp' => "http://wordpress.org/export/1.0/" do
  xml.channel do
    xml.title @feed_title
    xml.link @link
    xml.language "en-us"
    xml.ttl "40"
    xml.description this_blog.blog_subtitle

    @articles.each do |a|
        xml.item do
          xml.title post_title(a)
          xml.content(:encoded) { |x| x << '<![CDATA[' + a.full_html + ']]>' }
          xml.pubDate a.published_at.rfc2822
          xml.guid "urn:uuid:#{a.guid}", "isPermaLink" => "false"
          author = a.user.name rescue a.author
          xml.author author
          xml.dc :creator, author
          for category in a.categories
            xml.category category.name
          end
          for tag in a.tags
            xml.category tag.display_name
          end
          xml.wp :post_id, a.id
          xml.wp :post_date, a.published_at.strftime("%Y-%m-%d %H:%M:%S")
          xml.wp :comment_status, 'closed'
          xml.wp :ping_status, 'closed'
          xml.wp :post_name, a.permalink
          xml.wp :status, 'publish'
          xml.wp :post_parent, '0'
          xml.wp :post_type, 'post'
          for comment in a.comments
            xml.wp(:comment) do
              xml.wp :comment_id, comment.id
              xml.wp :comment_author, comment.author
              xml.wp :comment_author_email, comment.email
              xml.wp :comment_author_url, comment.url
              xml.wp :comment_author_IP, comment.ip
              xml.wp :comment_author_date, comment.published_at.strftime("%Y-%m-%d %H:%M:%S")
              xml.wp(:comment_content) { |x| x << comment.full_html }
              xml.wp :comment_approved, '1'
              xml.wp :comment_parent, '0'
            end
          end
        end
    end
end


Then hit your typo blog at http://myblog/xml/wp and save the file to import through the WordPress interface.
« Newer Snippets
Older Snippets »
Showing 1-4 of 4 total  RSS