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.

   1  
   2  require 'rubygems'
   3  require 'mechanize'
   4  
   5  agent = WWW::Mechanize.new
   6  
   7  # login to the account
   8  puts "Login"
   9  page = agent.get('https://online.wamu.com/IdentityManagement/Logon.aspx')
  10  login_form = page.forms.with.name("frmLogin").first
  11  login_form.txtUserID = '<UNAME>'
  12  login_form.password = '<PWD>'
  13  page = agent.submit(login_form, login_form.buttons.first)
  14  
  15  # click on the download button
  16  puts "Download"
  17  dl_link = page.links.with.text(/WAMU FREE CHECKING/)
  18  page = agent.click(dl_link)
  19  
  20  line_items = []
  21  
  22  trs = (page/"table#_ctl0_depositTransactionsGrid/tr")
  23  trs.shift
  24  trs.each do |tr|
  25    tds = (tr/:td)
  26  
  27    dtd = tds[1].inner_html
  28    js_call = dtd.match(/showDDATransactionDetails\('(.*)'\);/)[1]
  29    js_fields = js_call.split("','")
  30  
  31    item = {}
  32    item['type'] = js_fields[1]
  33    item['descr'] = js_fields[3]
  34    item['amount'] = js_fields[4]
  35    item['tranid'] = js_fields[6]
  36    # if !tranid = ovedraft charge / bank fee
  37  
  38    item['date'] = tds[0].inner_html
  39    item['debit'] = tds[3].inner_html
  40    item['credit'] = tds[4].inner_html
  41    item['balance'] = tds[5].inner_html
  42  
  43    line_items << item
  44  end
  45  
  46  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  
   2  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:

   1  
   2  class FizzBuzz
   3  
   4    def initialize(start_number, end_number)
   5      @starting = start_number
   6      @ending = end_number
   7      @phrase_multiples = []
   8    end
   9  
  10    def add_phrase_multiple(phrase, multiple)
  11      @phrase_multiples << [phrase, multiple]
  12    end
  13  
  14    def print_phrases
  15      fb_array = process_phrases
  16      puts fb_array.collect { |e| e[1] || e[0] }.join("\n")
  17    end
  18  
  19    private
  20  
  21    def process_phrases
  22      rarray = Array.new(@ending - @starting)
  23      rarray = rarray.each_with_index { |item, i| rarray[i] = [i + @starting, item] }
  24      @phrase_multiples.each { |pm| fill_multiples(rarray, pm[1], pm[0]) }
  25      rarray
  26    end
  27  
  28    def fill_multiples(fill_array, the_int, printed)
  29      (the_int - (fill_array[0][0] % the_int)).step(fill_array.size - 1, the_int) do |i|
  30        fill_array[i][1] = fill_array[i][1].to_s + printed.to_s
  31      end
  32    end
  33  
  34  end
  35  
  36  fb = FizzBuzz.new(1,100)
  37  fb.add_phrase_multiple('fizz', 3)
  38  fb.add_phrase_multiple('buzz', 5)
  39  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.

   1  
   2  // insert code here..
   3  require "date"
   4  require "rexml/document"
   5  require 'rubygems'
   6  require 'RMagick'
   7  
   8  class ForeRunner
   9    
  10    M2MI = 1609.344 # meters to miles
  11    MINIMUM_LAP_TIME = 300 # minimum seconds to count as a full lap
  12    
  13    attr_accessor :laps, :total_time, :distance, :calories
  14    
  15    def initialize(file)
  16      @source_doc = REXML::Document.new file
  17      
  18      @laps = 0
  19      @lap_times = Array.new
  20      @lap_bpm = Array.new
  21      @full_laps = 0
  22  
  23      @total_time = 0    
  24      @distance = 0
  25      @calories = 0
  26      @map_data = []
  27      
  28      self.process_file  
  29    end
  30    
  31    def generate_infographic(output_filename)
  32      canvas = Magick::Image.new(250, 80)
  33      map_size = 50
  34      map_color = 'green'
  35  
  36      max_lap_height = 25
  37      
  38      gc = Magick::Draw.new
  39  
  40      # Draw ellipse
  41      gc.stroke('grey50')
  42      gc.stroke_width(2)
  43      gc.fill_opacity(0)
  44  
  45      # draw the relative lap times
  46      lap = 0
  47      max_time = @lap_times.max
  48      @lap_times.each do |s|
  49        lap += 1
  50        x = 10 + (lap * 5)
  51        y = 60 - (s / (max_time / max_lap_height))
  52        gc.line(x, 60, x, y)
  53      end
  54  
  55      #draw the heartbeat avg
  56      gc.stroke('#c9a')
  57      
  58      lap = 0
  59      lx = nil
  60      ly = nil
  61      max_bpm = @lap_bpm.max
  62      min_bpm = @lap_bpm.min
  63      
  64      max_bpm_height = 18
  65      
  66      @lap_bpm.each do |s|
  67        lap += 1
  68        x = 10 + (lap * 5)
  69        y = 21 - ( (s - min_bpm) / ((max_bpm - min_bpm) / max_bpm_height))
  70        if !lx
  71          lx = x
  72          ly = y
  73        end
  74  
  75        gc.line(lx, ly, x, y)
  76  
  77        lx = x
  78        ly = y
  79      end
  80  
  81      # draw the map
  82      lat_diff = (@map_data['max_lat'] - @map_data['min_lat']).abs
  83      lon_diff = (@map_data['max_lon'] - @map_data['min_lon']).abs
  84  
  85      lat_off = lat_diff / map_size
  86      lon_off = lon_diff / map_size
  87      
  88      gc.fill(map_color)
  89      @map_data['map_data'].each do |i|
  90        lt = (map_size - ((i[0] - @map_data['min_lat']) / lat_off)).round
  91        lg = ((i[1] - @map_data['min_lon']) / lon_off).round
  92        gc.point((240 - map_size) + lg, (65 - map_size) + lt)
  93      end
  94  
  95      # Annotate
  96      gc.stroke('transparent')
  97      gc.fill('black')
  98      gc.text(120, 15, @distance.to_s[0, 5] + ' mi')
  99      gc.text(120, 30, (@total_time / 60 / 60).to_s[0, 4] + ' hr')
 100      gc.text(120, 45, @calories.to_s + ' cal')
 101  
 102      gc.fill('#555')
 103      total_hr = 0 
 104      @lap_bpm.each { |hr| total_hr += hr }
 105      gc.text(15, 32, 'avg bpm: ' + (total_hr / @laps).round.to_s)
 106      gc.text(15, 75, 'avg pace: ' + ((@total_time / @full_laps) / 60).to_s[0, 4] + ' min/lap')
 107  
 108      gc.draw(canvas)
 109      canvas.write(output_filename)
 110    end
 111    
 112    protected 
 113    
 114    def process_file
 115      @source_doc.elements.each('TrainingCenterDatabase/Activities/Activity/Lap/*') do |element| 
 116        if element.name == "TotalTimeSeconds"
 117          @lap_times << element.text.to_f
 118          if element.text.to_f > 300  # for removing warmup and warmdown laps
 119            @total_time += element.text.to_f
 120            @full_laps += 1
 121          end
 122          @laps += 1
 123        end
 124  
 125        if element.name == "DistanceMeters" 
 126          @distance += (element.text.to_f / M2MI)
 127        end
 128  
 129        if element.name == "Calories" 
 130          @calories += element.text.to_f
 131        end
 132  
 133        if element.name == "AverageHeartRateBpm" 
 134          element.elements.each('Value') { |v| @lap_bpm << v.text.to_f }
 135        end
 136      end
 137      @map_data = self.generate_map_points
 138    end
 139    
 140    def generate_map_points  
 141      map = []
 142  
 143      max_lat = -300
 144      max_lon = -300
 145      min_lat = 300
 146      min_lon = 300
 147  
 148      @source_doc.elements.each('TrainingCenterDatabase/Activities/Activity/Lap/Track/Trackpoint/*') do |element|
 149        if element.elements['LatitudeDegrees']
 150          lat = element.elements['LatitudeDegrees'].text.to_f
 151          lon = element.elements['LongitudeDegrees'].text.to_f
 152  
 153          map << [lat, lon]
 154          max_lat = lat if (lat > max_lat) 
 155          max_lon = lon if (lon > max_lon) 
 156          min_lat = lat if (lat < min_lat) 
 157          min_lon = lon if (lon < min_lon) 
 158        end
 159      end
 160      {'map_data' => map, 'max_lat' => max_lat, 'min_lat' => min_lat, 'max_lon' => max_lon, 'min_lon' => min_lon}
 161    end
 162    
 163  
 164  end
 165  
 166  
 167  if ARGV[0].nil? 
 168    puts "This program takes one argument, a Garmin Forerunner TCX File"
 169    puts "Like this: run.rb filename.tcx"
 170    exit
 171  end
 172  
 173  output_filename = ARGV[0].gsub(/\.tcx$/, "-" + Time.now.strftime("%Y%m%d") + ".png")
 174  puts "Generating Graph #{output_filename}"
 175  
 176  fr = ForeRunner.new(File.new(ARGV[0]))
 177  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 :

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


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

   1  
   2  // insert code here..
   3  xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
   4  
   5  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
   6    xml.channel do
   7      xml.title @feed_title
   8      xml.link @link
   9      xml.language "en-us"
  10      xml.ttl "40"
  11      xml.description this_blog.blog_subtitle
  12  
  13      @articles.each do |a|
  14          xml.item do
  15            xml.title post_title(a)
  16            xml.content(:encoded) { |x| x << '<![CDATA[' + a.full_html + ']]>' }
  17            xml.pubDate a.published_at.rfc2822
  18            xml.guid "urn:uuid:#{a.guid}", "isPermaLink" => "false"
  19            author = a.user.name rescue a.author
  20            xml.author author
  21            xml.dc :creator, author
  22            for category in a.categories
  23              xml.category category.name
  24            end
  25            for tag in a.tags
  26              xml.category tag.display_name
  27            end
  28            xml.wp :post_id, a.id
  29            xml.wp :post_date, a.published_at.strftime("%Y-%m-%d %H:%M:%S")
  30            xml.wp :comment_status, 'closed'
  31            xml.wp :ping_status, 'closed'
  32            xml.wp :post_name, a.permalink
  33            xml.wp :status, 'publish'
  34            xml.wp :post_parent, '0'
  35            xml.wp :post_type, 'post'
  36            for comment in a.comments
  37              xml.wp(:comment) do
  38                xml.wp :comment_id, comment.id
  39                xml.wp :comment_author, comment.author
  40                xml.wp :comment_author_email, comment.email
  41                xml.wp :comment_author_url, comment.url
  42                xml.wp :comment_author_IP, comment.ip
  43                xml.wp :comment_author_date, comment.published_at.strftime("%Y-%m-%d %H:%M:%S")
  44                xml.wp(:comment_content) { |x| x << comment.full_html }
  45                xml.wp :comment_approved, '1'
  46                xml.wp :comment_parent, '0'
  47              end
  48            end
  49          end
  50      end
  51  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