Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

insert progression records

  • Loading branch information...
commit 1a3fc7ba600d41af34cd0f0a7a20fc839e390355 0 parents
@technoweenie technoweenie authored
Showing with 3,843 additions and 0 deletions.
  1. +20 −0 LICENSE
  2. +12 −0 Rakefile
  3. +55 −0 lib/seinfeld_calendar.rb
  4. +10 −0 spec/spec_helper.rb
  5. +63 −0 spec/user_spec.rb
  6. +19 −0 vendor/feed_me/.autotest
  7. +20 −0 vendor/feed_me/LICENSE
  8. +23 −0 vendor/feed_me/README
  9. +68 −0 vendor/feed_me/Rakefile
  10. +5 −0 vendor/feed_me/TODO
  11. +33 −0 vendor/feed_me/lib/feed_me.rb
  12. +114 −0 vendor/feed_me/lib/feed_me/abstract_parser.rb
  13. +68 −0 vendor/feed_me/lib/feed_me/consts.rb
  14. +57 −0 vendor/feed_me/lib/feed_me/feed_parser.rb
  15. +13 −0 vendor/feed_me/lib/feed_me/feed_struct.rb
  16. +25 −0 vendor/feed_me/lib/feed_me/item_parser.rb
  17. +6 −0 vendor/feed_me/lib/feed_me/merbtasks.rb
  18. +15 −0 vendor/feed_me/lib/feed_me/simple_struct.rb
  19. +275 −0 vendor/feed_me/spec/feed_parser_spec.rb
  20. +129 −0 vendor/feed_me/spec/fixtures/real/andy_rutledge.xml
  21. +394 −0 vendor/feed_me/spec/fixtures/real/gem.xml
  22. +470 −0 vendor/feed_me/spec/fixtures/real/mezzoblue.xml
  23. +1,576 −0 vendor/feed_me/spec/fixtures/real/mootools.xml
  24. +46 −0 vendor/feed_me/spec/fixtures/welformed.atom
  25. +56 −0 vendor/feed_me/spec/fixtures/welformed.rss1
  26. +50 −0 vendor/feed_me/spec/fixtures/welformed.rss2
  27. +182 −0 vendor/feed_me/spec/item_parser_spec.rb
  28. +14 −0 vendor/feed_me/spec/simple_struct_spec.rb
  29. +25 −0 vendor/feed_me/spec/spec_helper.rb
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-* Rick Olson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12 Rakefile
@@ -0,0 +1,12 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+require 'spec/rake/spectask'
+
+file_list = FileList['spec/*_spec.rb']
+
+Spec::Rake::SpecTask.new('spec') do |t|
+ t.spec_files = file_list
+end
+
+desc 'Default: run specs.'
+task :default => 'spec'
55 lib/seinfeld_calendar.rb
@@ -0,0 +1,55 @@
+$: << File.join(File.dirname(__FILE__), '..', 'vendor', 'feed_me', 'lib')
+require 'rubygems'
+require 'open-uri'
+require 'dm-core'
+require 'feed_me'
+
+module Seinfeld
+ class User
+ include DataMapper::Resource
+ property :id, Integer, :serial => true
+ property :login, String
+ property :email, String
+ has n, :progressions, :class_name => "Seinfeld::Progression", :order => [:created_at.desc]
+
+ def update_progress
+ transaction do
+ save if new_record?
+ days = scan_for_progress
+ unless days.empty?
+ existing = progressions(:created_at => days).map { |p| p.created_at }
+ (days - existing).each do |day|
+ progressions.create(:created_at => day)
+ end
+ end
+ end
+ end
+
+ def scan_for_progress
+ feed = get_feed
+ feed.entries.inject({}) do |selected, entry|
+ if entry.title =~ %r{^#{login} committed}
+ updated = entry.updated_at
+ date = Time.utc(updated.year, updated.month, updated.day)
+ selected.update date => nil
+ else
+ selected
+ end
+ end.keys.sort
+ end
+
+ private
+ def get_feed
+ feed = nil
+ open("http://github.com/#{login}.atom") { |f| feed = FeedMe.parse(f.read) }
+ feed
+ end
+ end
+
+ class Progression
+ include DataMapper::Resource
+ property :id, Integer, :serial => true
+ property :created_at, DateTime
+ belongs_to :user, :class_name => "Seinfeld::User"
+ end
+end
10 spec/spec_helper.rb
@@ -0,0 +1,10 @@
+$: << File.join(File.dirname(__FILE__), '..', 'lib')
+require 'seinfeld_calendar'
+require 'ruby-debug'
+require 'ostruct'
+require 'spec'
+
+DataMapper.setup(:default, 'sqlite3::memory:')
+DataMapper.auto_migrate!
+
+Debugger.start
63 spec/user_spec.rb
@@ -0,0 +1,63 @@
+require File.join( File.dirname(__FILE__), "spec_helper" )
+
+module Seinfeld
+ describe User do
+ before :all do
+ @feed = OpenStruct.new
+ @feed.entries = [
+ OpenStruct.new(:title => "bob committed something", :updated_at => Time.utc(2008, 1, 1, 22)),
+ OpenStruct.new(:title => "bob watched something"),
+ OpenStruct.new(:title => "bob committed something", :updated_at => Time.utc(2008, 1, 1, 23)),
+ OpenStruct.new(:title => "bob committed something", :updated_at => Time.utc(2008, 1, 2, 23)),
+ ]
+ end
+
+ before do
+ @user = Seinfeld::User.new :login => 'bob'
+ end
+
+ describe "#scan_for_progress" do
+ before do
+ @user.stub!(:get_feed).and_return(@feed)
+ end
+
+ it "returns array" do
+ @user.scan_for_progress.should be_kind_of(Array)
+ end
+
+ it "returns unique days" do
+ @user.scan_for_progress.should == [Time.utc(2008, 1, 1), Time.utc(2008, 1, 2)]
+ end
+
+ it "matches against login name" do
+ @user.login = 'not bob'
+ @user.scan_for_progress.should be_empty
+ end
+ end
+
+ describe "#update_progress" do
+ before do
+ @user.stub!(:get_feed).and_return(@feed)
+ User.transaction do
+ User.all.destroy!
+ Progression.all.destroy!
+ end
+ end
+
+ it "saves record if necessary" do
+ @feed.stub!(:entries).and_return([])
+ @user.update_progress
+ @user.id.should_not be_nil
+ end
+
+ it "inserts progression records" do
+ lambda { @user.update_progress }.should change { Progression.all(:user_id => @user.id).size }.by(2)
+ end
+
+ it "inserts only unique progression records" do
+ Progression.create! :user_id => @user.id, :created_at => @feed.entries.first.updated_at
+ lambda { @user.update_progress }.should change { Progression.all(:user_id => @user.id).size }.by(1)
+ end
+ end
+ end
+end
19 vendor/feed_me/.autotest
@@ -0,0 +1,19 @@
+# Redefines the mappings in order for Autotest to pick up on a merb app
+puts "loaded .autotest"
+require 'autotest/redgreen'
+
+Autotest.add_hook :initialize do |at|
+ at.clear_mappings
+ at.add_exception(/\.git/)
+
+ at.add_mapping(%r{^spec/.*_spec\.rb$}) { |filename, _| filename}
+
+ at.add_mapping(%r{^spec/spec_helper\.rb$}) { |_, m| at.files_matching %r{^spec/.*_spec\.rb$} }
+ at.add_mapping(%r{^spec/(.*)/spec_helper\.rb$}) { |_, m| at.files_matching %r{^spec/#{m[1]}/.*_spec\.rb$} }
+
+ # map root libs
+ at.add_mapping(%r{^lib/([^/]+)\.rb$}) { |_, m| at.files_matching %r{^spec/([^/]+)_spec\.rb$} }
+
+ # map libs
+ at.add_mapping(%r{^lib/[^/]+/([^/]+)\.rb$}) { |_, m| at.files_matching %r{^spec/#{m[1]}_spec\.rb$} }
+end
20 vendor/feed_me/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 Jonas Nicklas
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 vendor/feed_me/README
@@ -0,0 +1,23 @@
+Feed Me
+=======
+
+Feed Me is a simple parser for RSS2 and Atom feed, adding other feed formats should be trivial. Feed Me is pretty minimal and basically only does translation/cleanup from different feed formats to a consistent API. It is designed to be minimal.
+
+Feed Me is built on the excellent Hpricot parser written by _why the lucky stiff.
+
+Use it like this:
+
+ file = File.read('some_feed.atom')
+
+ feed = FeedMe.parse(file)
+
+ feed.title
+ feed.author.name
+ feed.entries.each do |entry|
+ entry.title
+ entry.content
+ end
+
+Check out the specs or lib/feed_me/consts.rb for the complete API.
+
+DISCLAIMER: This is very much alpha software, use at your own risk!
68 vendor/feed_me/Rakefile
@@ -0,0 +1,68 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+require 'spec/rake/spectask'
+
+file_list = FileList['spec/*_spec.rb']
+
+namespace :spec do
+ desc "Run all examples with RCov"
+ Spec::Rake::SpecTask.new('rcov') do |t|
+ t.spec_files = file_list
+ t.rcov = true
+ t.rcov_dir = "doc/coverage"
+ t.rcov_opts = ['--exclude', 'spec']
+ end
+
+ desc "Generate an html report"
+ Spec::Rake::SpecTask.new('report') do |t|
+ t.spec_files = file_list
+ t.rcov_opts = ['--exclude', 'spec']
+ t.spec_opts = ["--format", "html:doc/reports/specs.html"]
+ t.fail_on_error = false
+ end
+
+end
+
+desc 'Default: run specs.'
+task :default => 'spec:rcov'
+
+PLUGIN = "feed_me"
+NAME = "feed_me"
+VERSION = "0.0.1"
+AUTHOR = "Jonas Nicklas"
+EMAIL = "jonas.nicklas@gmail.com"
+HOMEPAGE = "http://merb-plugins.rubyforge.org/feed_me/"
+SUMMARY = "Merb plugin that provides ..."
+
+spec = Gem::Specification.new do |s|
+ s.name = NAME
+ s.version = VERSION
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
+ s.summary = SUMMARY
+ s.description = s.summary
+ s.author = AUTHOR
+ s.email = EMAIL
+ s.homepage = HOMEPAGE
+ s.require_path = 'lib'
+ s.autorequire = PLUGIN
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,specs}/**/*")
+end
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.gem_spec = spec
+end
+
+task :install => [:package] do
+ sh %{sudo gem install pkg/#{NAME}-#{VERSION}}
+end
+
+namespace :jruby do
+
+ desc "Run :package and install the resulting .gem with jruby"
+ task :install => :package do
+ sh %{#{SUDO} jruby -S gem install pkg/#{NAME}-#{Merb::VERSION}.gem --no-rdoc --no-ri}
+ end
+
+end
5 vendor/feed_me/TODO
@@ -0,0 +1,5 @@
+TODO:
+Fix LICENSE with your name
+Fix Rakefile with your name and contact info
+Add your code to lib/feed_me.rb
+Add your Merb rake tasks to lib/feed_me/merbtasks.rb
33 vendor/feed_me/lib/feed_me.rb
@@ -0,0 +1,33 @@
+# make sure we're running inside Merb
+if defined?(Merb::Plugins)
+ dependency 'hpricot'
+else
+ require 'rubygems'
+ require 'hpricot'
+end
+
+unless nil.respond_to? :try
+ # the ultimate duck
+ class Object
+ def try(method, *args)
+ self.send(method, *args)
+ rescue NoMethodError
+ nil
+ end
+ end
+end
+
+module FeedMe
+ def self.parse(feed)
+ FeedMe::FeedParser.parse(feed)
+ end
+
+ def self.open(file)
+ FeedMe::FeedParser.parse(file)
+ end
+end
+
+['consts', 'abstract_parser', 'feed_struct', 'simple_struct',
+ 'feed_parser', 'item_parser'].each do |f|
+ require File.join(File.dirname(__FILE__), 'feed_me', f)
+end
114 vendor/feed_me/lib/feed_me/abstract_parser.rb
@@ -0,0 +1,114 @@
+class FeedMe::AbstractParser
+
+ class << self
+
+ attr_accessor :properties, :root_nodes
+
+ def build(xml, format, *args)
+ # in a world with activesupport this would have been written as
+ # format_parser = (format.to_s.camelize + self.to_s).constantize
+ camelized_format = format.to_s.split('_').map{ |w| w.capitalize }.join('')
+ bare_class = self.to_s.split('::').last
+
+ begin
+ format_parser = FeedMe.const_get(camelized_format + bare_class)
+ rescue NameError
+ end
+
+ if format_parser.is_a?(Class) and format_parser.ancestors.include?(self)
+ return format_parser.new(xml, format, *args)
+ else
+ return self.new(xml, format, *args)
+ end
+
+ end
+
+ end
+
+ def initialize(xml, format)
+ self.xml = xml
+ self.format = format
+ self.properties = self.class.properties[self.format]
+
+ append_methods
+ end
+
+ def to_hash
+ hash = {}
+ self.properties.each do |method, p|
+ hash[method] = self.send(method)
+ end
+ return hash
+ end
+
+ attr_accessor :xml, :format, :properties
+
+ alias_method :root_node, :xml
+
+ protected
+
+ def fetch_rss_person(selector)
+ item = fetch(selector)
+ if(item)
+ email, name = item.split(/\s+/, 2)
+ name = name.match( /\((.*?)\)/ ).to_a[1] if name # strip parentheses
+ else
+ name, email = nil
+ end
+ FeedMe::SimpleStruct.new(:email => email, :name => name, :uri => nil)
+ end
+
+ def append_methods
+ self.properties.each do |method, p|
+ unless respond_to?(method)
+ block = get_proc_for_property(method, p)
+ # meta programming magic
+ (class << self; self; end).module_eval do
+ define_method method, &block
+ end
+ end
+ end
+ end
+
+ def get_proc_for_property(method, p)
+ if p.class == Array
+ return caching_proc(method, &proc { fetch("/#{p[0]}", root_node, p[1].to_sym) })
+ elsif p.class == Hash
+ return caching_proc(method, &proc { FeedMe::FeedStruct.new(root_node, p) })
+ elsif p != :undefined
+ return caching_proc(method, &proc { fetch("/#{p}", root_node) })
+ else
+ return proc { nil }
+ end
+ end
+
+ def caching_proc(name, &block)
+ proc do
+ ivar = instance_variable_get("@#{name}")
+ unless ivar
+ result = yield
+ instance_variable_set("@#{name}", result)
+ return result
+ end
+ end
+ end
+
+ def fetch(selector, search_in = xml, method = :inner_html)
+ item = search_in.at(selector)
+
+ self.try("extract_" + method.to_s, item) if item
+ end
+
+ def extract_inner_html(item)
+ item.inner_html
+ end
+
+ def extract_href(item)
+ item[:href]
+ end
+
+ def extract_time(item)
+ Time.parse(item.inner_html).utc
+ end
+
+end
68 vendor/feed_me/lib/feed_me/consts.rb
@@ -0,0 +1,68 @@
+module FeedMe
+
+ ROOT_NODES = {
+ :atom => "//feed[@xmlns='http://www.w3.org/2005/Atom']",
+ :rss2 => "//rss[@version=2.0]/channel"
+ }
+
+ FEED_PROPERTIES = {
+ :atom => {
+ :title => :title,
+ :updated_at => [:updated, :time],
+ :feed_id => :id,
+ :url => ["link[@rel=alternate]", :href],
+ :href => ["link[@rel=self]", :href],
+ :description => :subtitle,
+ :generator => :generator,
+ :author => {
+ :email => 'author/email',
+ :name => 'author/name',
+ :uri => 'author/uri'
+ },
+ :entries => :special
+ },
+ :rss2 => {
+ :title => :title,
+ :updated_at => [:lastBuildDate, :time],
+ :feed_id => :undefined,
+ :url => :link,
+ :href => :undefined,
+ :description => :description,
+ :generator => :generator,
+ :author => :special,
+ :entries => :special
+ }
+ }
+
+ ITEM_PROPERTIES = {
+ :atom => {
+ :title => :title,
+ :updated_at => [:updated, :time],
+ :item_id => :id,
+ :url => ["link[@rel=alternate]", :href],
+ :content => :content,
+ :author => {
+ :email => 'author/email',
+ :name => 'author/name',
+ :uri => 'author/uri'
+ }
+ },
+ :rss2 => {
+ :title => :title,
+ :updated_at => :undefined,
+ :item_id => :guid,
+ :url => :link,
+ :content => :description,
+ :author => :special
+ }
+ }
+
+ AUTHOR_PROPERTIES = {
+ :atom => {
+ :name => :name,
+ :uri => :uri,
+ :email => :email
+ }
+ }
+
+end
57 vendor/feed_me/lib/feed_me/feed_parser.rb
@@ -0,0 +1,57 @@
+module FeedMe
+
+ class FeedParser < AbstractParser
+
+ self.properties = FEED_PROPERTIES
+
+ class << self
+
+ def open(file)
+ self.parse(Kernel.open(file).read)
+ end
+
+ # parses the passed feed and identifeis what kind of feed it is
+ # then returns a parser object
+ def parse(feed)
+ xml = Hpricot.XML(feed)
+
+ root_node, format = self.identify(xml)
+ self.build(root_node, format)
+ end
+
+ protected
+
+ def identify(xml)
+ FeedMe::ROOT_NODES.each do |f, s|
+ item = xml.at(s)
+ return item, f if item
+ end
+ end
+
+ end
+ end
+
+ class AtomFeedParser < FeedParser
+ self.properties = FEED_PROPERTIES
+
+ def entries
+ xml.search('entry').map do |el|
+ ItemParser.build(el, self.format, self)
+ end
+ end
+ end
+
+ class Rss2FeedParser < FeedParser
+ self.properties = FEED_PROPERTIES
+
+ def entries
+ xml.search('item').map do |el|
+ ItemParser.build(el, self.format, self)
+ end
+ end
+
+ def author
+ fetch_rss_person("managingEditor")
+ end
+ end
+end
13 vendor/feed_me/lib/feed_me/feed_struct.rb
@@ -0,0 +1,13 @@
+module FeedMe
+
+ class FeedStruct < AbstractParser
+
+ def initialize(xml, properties)
+ self.xml = xml
+ self.properties = properties
+ append_methods
+ end
+
+ end
+
+end
25 vendor/feed_me/lib/feed_me/item_parser.rb
@@ -0,0 +1,25 @@
+module FeedMe
+
+ class ItemParser < AbstractParser
+
+ self.properties = ITEM_PROPERTIES
+
+ attr_accessor :feed
+
+ def initialize(xml, format, feed)
+ super(xml, format)
+ self.feed = feed
+ end
+
+ end
+
+ class Rss2ItemParser < ItemParser
+
+ self.properties = ITEM_PROPERTIES
+
+ def author
+ fetch_rss_person("author")
+ end
+
+ end
+end
6 vendor/feed_me/lib/feed_me/merbtasks.rb
@@ -0,0 +1,6 @@
+namespace :feed_me do
+ desc "Do something for feed_me"
+ task :default do
+ puts "feed_me doesn't do anything"
+ end
+end
15 vendor/feed_me/lib/feed_me/simple_struct.rb
@@ -0,0 +1,15 @@
+module FeedMe
+
+ class SimpleStruct
+
+ def initialize(hash = {})
+ (class << self; self; end).module_eval do
+ hash.each do |method, result|
+ define_method( method ) { result }
+ end
+ end
+ end
+
+ end
+
+end
275 vendor/feed_me/spec/feed_parser_spec.rb
@@ -0,0 +1,275 @@
+require File.join( File.dirname(__FILE__), "spec_helper" )
+
+require 'feed_me'
+
+describe "all parsing methods", :shared => true do
+ it "should identify an atom feed" do
+ @atom.should be_an_instance_of(FeedMe::AtomFeedParser)
+ @atom.format.should == :atom
+ @atom.root_node.xpath == "//feed[@xmlns='http://www.w3.org/2005/Atom']"
+ end
+
+ it "should identify an rss2 feed" do
+ @rss2.should be_an_instance_of(FeedMe::Rss2FeedParser)
+ @rss2.format.should == :rss2
+ @rss2.root_node.xpath == "//rss[@version=2.0]/channel"
+ end
+end
+
+describe FeedMe::FeedParser do
+
+ before :each do
+ @atom_feed = hpricot_fixture('welformed.atom') / "//feed[@xmlns='http://www.w3.org/2005/Atom']"
+ @atom = FeedMe::FeedParser.build(@atom_feed, :atom)
+ @rss2_feed = hpricot_fixture('welformed.rss2') / "//rss[@version=2.0]/channel"
+ @rss2 = FeedMe::FeedParser.build(@rss2_feed, :rss2)
+ end
+
+ it "should be an atom parser for an atom feed" do
+ @atom.should be_an_instance_of(FeedMe::AtomFeedParser)
+ end
+
+ describe ".parse" do
+ before(:each) do
+ @atom = FeedMe::FeedParser.parse(open(fixture('welformed.atom')).read)
+ @rss2 = FeedMe::FeedParser.parse(open(fixture('welformed.rss2')).read)
+ end
+
+ it_should_behave_like "all parsing methods"
+ end
+
+ describe ".open" do
+ before(:each) do
+ @atom = FeedMe::FeedParser.open(fixture('welformed.atom'))
+ @rss2 = FeedMe::FeedParser.open(fixture('welformed.rss2'))
+ end
+
+ it_should_behave_like "all parsing methods"
+ end
+
+ describe '#title' do
+ it "should be valid for an atom feed" do
+ @atom.title.should == "Test feed"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.title.should == "Lift Off News"
+ end
+ end
+
+ describe '#description' do
+ it "should be valid for an atom feed" do
+ @atom.description.should == "Monkey test feed"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.description.should == "Liftoff to Space Exploration."
+ end
+ end
+
+ describe '#feed_id' do
+ it "should be valid for an atom feed" do
+ @atom.feed_id.should == "tag:imaginary.host:nyheter"
+ end
+
+ it "should be nil for an rss2 feed" do
+ @rss2.feed_id.should be_nil
+ end
+ end
+
+ describe '#updated_at' do
+ it "should be valid for an atom feed" do
+ @atom.updated_at.should == Time.utc(2008, 3, 7, 20, 41, 10)
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.updated_at.should == Time.utc(2003, 6, 10, 9, 41, 1)
+ end
+ end
+
+ describe '#href' do
+ it "should be valid for an atom feed" do
+ @atom.href.should == "http://imaginary.host/posts.atom"
+ end
+
+ it "should be nil for an atom feed" do
+ @rss2.href.should be_nil
+ end
+ end
+
+ describe '#url' do
+ it "should be valid for an atom feed" do
+ @atom.url.should == "http://imaginary.host/posts"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.url.should == "http://liftoff.msfc.nasa.gov/"
+ end
+ end
+
+ describe '#generator' do
+ it "should be valid for an atom feed" do
+ @atom.generator.should == "Roll your own"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.generator.should == "Weblog Editor 2.0"
+ end
+ end
+
+ describe '#format' do
+ it "should be :atom for an atom feed" do
+ @atom.format.should == :atom
+ end
+
+ it "should be :rss2 for an rss2 feed" do
+ @rss2.format.should == :rss2
+ end
+ end
+
+ describe '#author.name' do
+ it "should be valid for an atom feed" do
+ @atom.author.name.should == "Frank"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.author.name.should == "Mary Jo"
+ end
+ end
+
+ describe '#author.email' do
+ it "should be valid for an atom feed" do
+ @atom.author.email.should == "frank@imaginary.host"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.author.email.should == "editor@example.com"
+ end
+ end
+
+ describe '#author.uri' do
+ it "should be valid for an atom feed" do
+ @atom.author.uri.should == "http://imaginary.host/students/frank"
+ end
+
+ it "should be nil for an rss2 feed" do
+ @rss2.author.uri.should be_nil
+ end
+ end
+
+ describe '#entries' do
+ it "should return an array of entries for an atom feed" do
+ @atom.entries.should be_an_instance_of(Array)
+ end
+
+ it "should have the correct length for an atom feed" do
+ @atom.should have(3).entries
+ end
+
+ it "should return items that are properly parsed for an atom feed" do
+ @atom.entries.first.title.should == "First title"
+ @atom.entries.first.url.should == "http://imaginary.host/posts/3"
+ end
+
+ it "should return an array of entries for an rss2 feed" do
+ @rss2.entries.should be_an_instance_of(Array)
+ end
+
+ it "should have the correct length for an rss2 feed" do
+ @rss2.should have(4).entries
+ end
+
+ it "should return items that are properly parsed for an rss2 feed" do
+ @rss2.entries.first.title.should == "Star City"
+ @rss2.entries.first.url.should == "http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp"
+ end
+ end
+
+ describe '#to_hash' do
+ it "should serialize the title of an atom feed" do
+ @atom.to_hash[:title].should == "Test feed"
+ end
+
+ it "should serialize the description of an atom feed" do
+ @atom.to_hash[:description].should == "Monkey test feed"
+ end
+
+ it "should serialize the feed_id of an atom feed" do
+ @atom.to_hash[:feed_id].should == "tag:imaginary.host:nyheter"
+ end
+
+ it "should serialize the updated_at time of an atom feed" do
+ @atom.to_hash[:updated_at].should == Time.utc(2008, 3, 7, 20, 41, 10)
+ end
+
+ it "should serialize the href of an atom feed" do
+ @atom.to_hash[:href].should == "http://imaginary.host/posts.atom"
+ end
+
+ it "should serialize the url of an atom feed" do
+ @atom.to_hash[:url].should == "http://imaginary.host/posts"
+ end
+
+ it "should serialize the generator of an atom feed" do
+ @atom.to_hash[:generator].should == "Roll your own"
+ end
+
+ it "should serialize the entries of an atom feed" do
+ @atom.to_hash[:entries].should be_an_instance_of(Array)
+ @atom.to_hash[:entries].first.title.should == "First title"
+ @atom.to_hash[:entries].first.url.should == "http://imaginary.host/posts/3"
+ end
+
+ it "should serialize the author of an atom feed" do
+ author = @atom.to_hash[:author]
+
+ author.name.should == "Frank"
+ author.email.should == "frank@imaginary.host"
+ author.uri.should == "http://imaginary.host/students/frank"
+ end
+
+ it "should serialize the title of an rss2 feed" do
+ @rss2.to_hash[:title].should == "Lift Off News"
+ end
+
+ it "should serialize the description of an rss2 feed" do
+ @rss2.to_hash[:description].should == "Liftoff to Space Exploration."
+ end
+
+ it "should serialize the feed_id of an rss2 feed" do
+ @rss2.to_hash[:feed_id].should be_nil
+ end
+
+ it "should serialize the updated_at time of an rss2 feed" do
+ @rss2.to_hash[:updated_at].should == Time.utc(2003, 6, 10, 9, 41, 1)
+ end
+
+ it "should serialize the href of an rss2 feed" do
+ @rss2.to_hash[:href].should be_nil
+ end
+
+ it "should serialize the url of an rss2 feed" do
+ @rss2.to_hash[:url].should == "http://liftoff.msfc.nasa.gov/"
+ end
+
+ it "should serialize the generator of an rss2 feed" do
+ @rss2.to_hash[:generator].should == "Weblog Editor 2.0"
+ end
+
+ it "should serialize the entries of an rss2 feed" do
+ @rss2.to_hash[:entries].should be_an_instance_of(Array)
+ @rss2.to_hash[:entries].first.title.should == "Star City"
+ @rss2.to_hash[:entries].first.url.should == "http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp"
+ end
+
+ it "should serialize the author of an rss2 feed" do
+
+ author = @rss2.to_hash[:author]
+
+ author.name.should == "Mary Jo"
+ author.email.should == "editor@example.com"
+ author.uri.should be_nil
+ end
+ end
+
+end
129 vendor/feed_me/spec/fixtures/real/andy_rutledge.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<rss version="2.0">
+ <channel>
+ <title>Andy Rutledge : Design View</title>
+ <link>http://andyrutledge.com/</link>
+ <description></description>
+ <language>en</language>
+ <copyright>Copyright 2007</copyright>
+ <lastBuildDate>Sat, 19 Apr 2008 10:10:31 -0600</lastBuildDate>
+
+ <item>
+ <title>Calculating Hours - the Client Factors</title>
+ <description>
+ <![CDATA[<p>Pricing for services is one of the vital realms where design professionals
+ do not often possess significant skill. And this is a shame because price
+ estimation skill is often directly tied to a designer’s ability to keep clients
+ happy and derive a sense of satisfaction from the work; important stuff.
+ So in this article I want to examine some of the common but not oft-cited
+ factors that affect project hours’ calculations and how these factors affect our pricing
+ considerations, all in an effort to help designers avoid pricing mistakes.</p>]]>
+ </description>
+ <link>http://www.andyrutledge.com/calculating-hours.php</link>
+ <guid>http://www.andyrutledge.com/calculating-hours.php</guid>
+ <pubDate>Sat, 19 Apr 2008 10:10:31 -0600</pubDate>
+ </item>
+
+ <item>
+ <title>Poynter's Eye-tracking Follies</title>
+ <description>
+ <![CDATA[<p>Poynter released their EyeTrack ’07 study findings some months ago
+ and while this effort was not a completely useless study like their previous
+ one, I thought it would be good to revisit how these sorts of studies can
+ unnecessarily distract us in our design efforts. What follows is an updated,
+ edited, and expanded version of my previously published article on Poynter’s
+ Eyetrack III study.</p>]]>
+ </description>
+ <link>http://www.andyrutledge.com/poynters-eye-track-follies.php#fragment-4</link>
+ <guid>http://www.andyrutledge.com/poynters-eye-track-follies.php#fragment-4</guid>
+ <pubDate>Sun, 13 Apr 2008 12:50:31 -0600</pubDate>
+ </item>
+
+ <item>
+ <title>A Sense of Non-Commitment</title>
+ <description>
+ <![CDATA[<p>I think that there’s a disconnect or disharmony in motivations and
+ a certain obliviousness to (or ignorance of) facts among many designers.
+ I think that many designers believe that building a reputation as a
+ designer is the same thing as building a reputation as a professional.
+ It’s not; certainly not as demonstrated by many designers.</p>]]>
+ </description>
+ <link>http://www.andyrutledge.com/non-commitment.php</link>
+ <guid>http://www.andyrutledge.com/non-commitment.php</guid>
+ <pubDate>Thu, 27 Mar 2008 18:10:31 -0600</pubDate>
+ </item>
+
+ <item>
+ <title>How and Why: Styling Text Links</title>
+ <description>
+ <![CDATA[<p>Poorly styled text links can detract
+ from or ruin an otherwise well-designed page, while appropriately styled
+ text links can add significant support to the brand and enhance the user
+ experience. Since text links are a significant feature of the online experience,
+ it’s an element of design worth getting right.</p>]]>
+ </description>
+ <link>http://www.andyrutledge.com/styling-text-links.php</link>
+ <guid>http://www.andyrutledge.com/styling-text-links.php</guid>
+ <pubDate>Wed, 19 Mar 2008 18:05:31 -0600</pubDate>
+ </item>
+
+ <item>
+ <title>My Teacher</title>
+ <description>
+ <![CDATA[<p>Ongoing education is something I expect all designers who are long out
+ of school crave. Of course many of us work to educate ourselves daily by
+ various means, but the luckiest among us have teachers or mentors who provide
+ hands-on instruction on some regular basis. Last week my teacher came for
+ his annual visit and I’m still reeling from the experience.</p>]]>
+ </description>
+ <link>http://www.andyrutledge.com/my-teacher.php#fragment-5</link>
+ <guid>http://www.andyrutledge.com/my-teacher.php#fragment-5</guid>
+ <pubDate>Mon, 03 Mar 2008 13:05:31 -0600</pubDate>
+ </item>
+
+ <item>
+ <title>A Brand Synopsis</title>
+ <description>
+ <![CDATA[<p>I probably should have read a few brand promises from other companies
+ before resolving that ours is &#8220;done&#8221; or &#8220;right,&#8221; but we believe that we’re
+ somewhat unique as a company, in many respects. So we’re going with this
+ one. Therefore, this is something of a blind study. I hope it might spark
+ some thought on the subject and hope that it might even help others working
+ on similar efforts.</p>]]>
+ </description>
+ <link>http://www.andyrutledge.com/a-brand-synopsis.php</link>
+ <guid>http://www.andyrutledge.com/a-brand-synopsis.php</guid>
+ <pubDate>Thu, 07 Feb 2008 10:15:31 -0600</pubDate>
+ </item>
+
+ <item>
+ <title>People, Work With Me Here</title>
+ <description>
+ <![CDATA[<p>This is a cry for help. No, really. Our groovy little unit, <a href="http://www.unitinteractive.com/">Unit
+ Interactive</a>, is awash in cool
+ projects and we need a world-class designer/developer to join our team.
+ If this describes you and you're in the Dallas area Metroplex, please keep
+ reading and <em>heed the clarion call!</em></p>]]>
+ </description>
+ <link>http://www.andyrutledge.com/work-with-me-here.php</link>
+ <guid>http://www.andyrutledge.com/work-with-me-here.php</guid>
+ <pubDate>Wed, 30 Jan 2008 08:10:31 -0600</pubDate>
+ </item>
+
+ <item>
+ <title>Falling Down</title>
+ <description>
+ <![CDATA[<p>I’ve been a martial arts student for a couple of decades and in that time
+ I’ve known quite a few others who were brilliant technicians and were otherwise
+ formidable individuals, but lacked a fundamental skill set: the ability to
+ fall safely and gracefully. This is a grave omission, because training with
+ the belief that things will always go well and as-planned is delusional.
+ Delusion is not a sound context for any serious endeavor; like design, for
+ instance.</p>]]>
+ </description>
+ <link>http://www.andyrutledge.com/falling-down.php</link>
+ <guid>http://www.andyrutledge.com/falling-down.php</guid>
+ <pubDate>Tue, 22 Jan 2008 08:00:31 -0600</pubDate>
+ </item>
+ </channel>
+</rss>
394 vendor/feed_me/spec/fixtures/real/gem.xml
@@ -0,0 +1,394 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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:atom="http://www.w3.org/2005/Atom"
+ xmlns:media="http://search.yahoo.com/mrss"
+ >
+
+<channel>
+ <title>Gem Install That</title>
+ <atom:link href="http://geminstallthat.wordpress.com/feed/" rel="self" type="application/rss+xml" />
+ <link>http://geminstallthat.wordpress.com</link>
+ <description>Ruby on Rails Geek Blog</description>
+ <pubDate>Mon, 07 Apr 2008 02:45:35 +0000</pubDate>
+ <generator>http://wordpress.org/?v=MU</generator>
+ <language>en</language>
+
+
+ <item>
+ <title>MySQL column defaults ActiveRecord::SchemaDumper</title>
+ <link>http://geminstallthat.wordpress.com/2008/02/26/mysql-column-defaults-activerecordschemadumper/</link>
+ <comments>http://geminstallthat.wordpress.com/2008/02/26/mysql-column-defaults-activerecordschemadumper/#comments</comments>
+ <pubDate>Tue, 26 Feb 2008 20:42:39 +0000</pubDate>
+ <dc:creator>John Dewey</dc:creator>
+
+ <category><![CDATA[ActiveRecord]]></category>
+
+ <category><![CDATA[Database]]></category>
+
+ <category><![CDATA[MySQL]]></category>
+
+ <category><![CDATA[Rails]]></category>
+ <guid isPermaLink="false">http://geminstallthat.wordpress.com/?p=32</guid>
+
+
+ <description><![CDATA[ActiveRecord::SchemaDumper will use empty strings as the default value for a column marked as NOT NULL, in MySQL versions &#60; 5.0.51.  This behavior recently changed; defaulting to null (nil).  This caused our specs to fail for various reasons.  Something to keep in mind when upgrading.  We had been coding to a MySQL bug.
+ ]]></description>
+
+ <content:encoded><![CDATA[<div class='snap_preview'><br /><p><span class="Apple-style-span" style="font-family:'Lucida Grande';font-size:14px;line-height:19px;"><span style="font-style:italic;" class="Apple-style-span">ActiveRecord::SchemaDumper</span> will use empty strings as the default value for a column marked as <span class="Apple-style-span" style="font-weight:bold;">NOT NULL</span>, in <a href="http://mysql.com"><span class="Apple-style-span" style="font-weight:bold;">MySQL</span> </a>versions &lt; 5.0.51.  This behavior <span class="Apple-style-span" style="font-weight:bold;"><a href="http://bugs.mysql.com/bug.php?id=27747">recently changed</a>; </span>defaulting to null (nil).  This caused our <a href="http://rspec.info/"><span class="Apple-style-span" style="font-weight:bold;">specs</span></a> to fail for various reasons.  Something to keep in mind when upgrading.  We had been coding to a <a href="http://mysql.com"><span class="Apple-style-span" style="text-decoration:none;"><span class="Apple-style-span" style="font-weight:bold;">MySQL</span></span></a> bug.</span></p>
+<img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/geminstallthat.wordpress.com/32/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/geminstallthat.wordpress.com/32/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/geminstallthat.wordpress.com/32/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/geminstallthat.wordpress.com/32/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/geminstallthat.wordpress.com/32/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/geminstallthat.wordpress.com/32/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/geminstallthat.wordpress.com/32/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/geminstallthat.wordpress.com/32/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/geminstallthat.wordpress.com/32/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/geminstallthat.wordpress.com/32/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/geminstallthat.wordpress.com/32/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/geminstallthat.wordpress.com/32/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=geminstallthat.wordpress.com&blog=309932&post=32&subd=geminstallthat&ref=&feed=1" /></div>]]></content:encoded>
+
+
+ <wfw:commentRss>http://geminstallthat.wordpress.com/2008/02/26/mysql-column-defaults-activerecordschemadumper/feed/</wfw:commentRss>
+
+
+ <media:content url="http://a.wordpress.com/avatar/retr0h-128.jpg" medium="image">
+ <media:title type="html">retr0h</media:title>
+ </media:content>
+
+ </item>
+
+
+ <item>
+ <title>Run Rake Tasks with Delayed Job</title>
+ <link>http://geminstallthat.wordpress.com/2008/02/25/run-rake-tasks-with-delayedjob-dj/</link>
+ <comments>http://geminstallthat.wordpress.com/2008/02/25/run-rake-tasks-with-delayedjob-dj/#comments</comments>
+ <pubDate>Tue, 26 Feb 2008 03:37:51 +0000</pubDate>
+ <dc:creator>John Dewey</dc:creator>
+
+ <category><![CDATA[Code]]></category>
+
+ <category><![CDATA[Delayed::Job]]></category>
+
+ <category><![CDATA[Example]]></category>
+
+ <category><![CDATA[Rails]]></category>
+
+ <category><![CDATA[Rake]]></category>
+ <guid isPermaLink="false">http://geminstallthat.wordpress.com/?p=31</guid>
+
+
+ <description><![CDATA[We needed to execute a Rake Task in the background, when a particular event occurred in Rails.  We decided to give Delayed::Job aka DJ a shot.  This &#8220;decorator&#8221; will execute a Rake task, while optionally passing ENV variables to the task.
+ ]]></description>
+
+ <content:encoded><![CDATA[<div class='snap_preview'><br /><p>We needed to execute a <a href="http://rake.rubyforge.org/"><span style="font-weight:bold;" class="Apple-style-span">Rake</span></a> Task in the background, when a particular event occurred in Rails.  We decided to give<span style="font-weight:bold;" class="Apple-style-span"> </span><a href="http://github.com/tobi/delayed_job/tree/master"><span style="font-weight:bold;" class="Apple-style-span">Delayed::Job</span></a> aka <a href="http://github.com/tobi/delayed_job/tree/master"><span style="font-weight:bold;" class="Apple-style-span">DJ</span></a> a shot.  <a href="http://pastie.caboo.se/157390"><span style="font-weight:bold;" class="Apple-style-span">This &#8220;decorator&#8221; will execute a Rake task, while optionally passing ENV variables to the task</span></a>.</p>
+<img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/geminstallthat.wordpress.com/31/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/geminstallthat.wordpress.com/31/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/geminstallthat.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/geminstallthat.wordpress.com/31/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/geminstallthat.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/geminstallthat.wordpress.com/31/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/geminstallthat.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/geminstallthat.wordpress.com/31/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/geminstallthat.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/geminstallthat.wordpress.com/31/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/geminstallthat.wordpress.com/31/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/geminstallthat.wordpress.com/31/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=geminstallthat.wordpress.com&blog=309932&post=31&subd=geminstallthat&ref=&feed=1" /></div>]]></content:encoded>
+
+
+ <wfw:commentRss>http://geminstallthat.wordpress.com/2008/02/25/run-rake-tasks-with-delayedjob-dj/feed/</wfw:commentRss>
+
+
+ <media:content url="http://a.wordpress.com/avatar/retr0h-128.jpg" medium="image">
+ <media:title type="html">retr0h</media:title>
+ </media:content>
+
+ </item>
+
+
+ <item>
+ <title>Execute a Rake task from another task</title>
+ <link>http://geminstallthat.wordpress.com/2008/02/18/execute-a-rake-task-from-another-task/</link>
+ <comments>http://geminstallthat.wordpress.com/2008/02/18/execute-a-rake-task-from-another-task/#comments</comments>
+ <pubDate>Tue, 19 Feb 2008 00:00:57 +0000</pubDate>
+ <dc:creator>John Dewey</dc:creator>
+
+ <category><![CDATA[Example]]></category>
+
+ <category><![CDATA[Rake]]></category>
+
+ <category><![CDATA[Ruby]]></category>
+ <guid isPermaLink="false">http://geminstallthat.wordpress.com/?p=29</guid>
+
+
+ <description><![CDATA[Executing a Rake task from inside another Rake task is a nice addition to your toolshed.  Passing parameters to the Rake task is even cooler.  &#8217;Rake::Task["TaskName"].execute&#8217; and &#8216;Rake::Task["TaskName"].invoke&#8216; to the rescue.  A Rakefile that demonstrates the execution of a task with parameter passing.
+ ]]></description>
+
+ <content:encoded><![CDATA[<div class='snap_preview'><br /><p>Executing a <a href="http://rake.rubyforge.org/"><span style="font-weight:bold;" class="Apple-style-span">Rake</span></a> task from inside another <a href="http://rake.rubyforge.org/"><span style="font-weight:bold;" class="Apple-style-span">Rake</span></a> task is a nice addition to your toolshed.  <span style="font-weight:bold;" class="Apple-style-span">Passing parameters</span> to the <a href="http://rake.rubyforge.org/"><span style="font-weight:bold;" class="Apple-style-span">Rake</span></a> task is even cooler.  &#8217;<span style="font-style:italic;" class="Apple-style-span">Rake::Task["TaskName"].execut</span>e&#8217; and &#8216;<span style="font-style:italic;" class="Apple-style-span">Rake::Task["TaskName"].invoke</span>&#8216; to the rescue.  <a href="http://pastie.caboo.se/pastes/153991"><span style="font-weight:bold;" class="Apple-style-span">A Rakefile that demonstrates the execution of a task with parameter passing</span></a>.</p>
+<img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/geminstallthat.wordpress.com/29/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/geminstallthat.wordpress.com/29/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/geminstallthat.wordpress.com/29/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/geminstallthat.wordpress.com/29/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/geminstallthat.wordpress.com/29/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/geminstallthat.wordpress.com/29/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/geminstallthat.wordpress.com/29/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/geminstallthat.wordpress.com/29/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/geminstallthat.wordpress.com/29/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/geminstallthat.wordpress.com/29/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/geminstallthat.wordpress.com/29/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/geminstallthat.wordpress.com/29/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=geminstallthat.wordpress.com&blog=309932&post=29&subd=geminstallthat&ref=&feed=1" /></div>]]></content:encoded>
+
+
+ <wfw:commentRss>http://geminstallthat.wordpress.com/2008/02/18/execute-a-rake-task-from-another-task/feed/</wfw:commentRss>
+
+
+ <media:content url="http://a.wordpress.com/avatar/retr0h-128.jpg" medium="image">
+ <media:title type="html">retr0h</media:title>
+ </media:content>
+
+ </item>
+
+
+ <item>
+ <title>Run a Capistrano task on a single server in a given role</title>
+ <link>http://geminstallthat.wordpress.com/2008/02/07/run-a-capistrano-task-on-a-single-server-in-a-given-role/</link>
+ <comments>http://geminstallthat.wordpress.com/2008/02/07/run-a-capistrano-task-on-a-single-server-in-a-given-role/#comments</comments>
+ <pubDate>Fri, 08 Feb 2008 05:51:16 +0000</pubDate>
+ <dc:creator>John Dewey</dc:creator>
+
+ <category><![CDATA[Code]]></category>
+
+ <category><![CDATA[Command Line]]></category>
+
+ <category><![CDATA[Deployment]]></category>
+
+ <category><![CDATA[Monkey Patch]]></category>
+
+ <category><![CDATA[capistrano]]></category>
+ <guid isPermaLink="false">http://geminstallthat.wordpress.com/?p=28</guid>
+
+
+ <description><![CDATA[There are times I want to run a Capistrano task on a single server, rather than every system in the role. Adding this monkey patch to deploy.rb adds this functionality. To utilize this feature append &#8217;server=IP_ADDRESS&#8217; to the Cap command. Tasks are defined to run on particular role(s), this feature will target [...]]]></description>
+
+ <content:encoded><![CDATA[<div class='snap_preview'><br /><p>There are times I want to run a <strong><a href="http://capify.org/">Capistrano</a></strong> task on a single server, rather than every system in the role. <strong><a href="http://pastie.caboo.se/149129"><span style="text-decoration:line-through;">Adding this monkey patch to deploy.rb</span></a></strong><span style="text-decoration:line-through;"> adds this functionality. To utilize this feature append </span><em><span style="text-decoration:line-through;">&#8217;server=IP_ADDRESS&#8217;</span></em><span style="text-decoration:line-through;"> to the </span><strong><span style="text-decoration:line-through;">Cap</span></strong><span style="text-decoration:line-through;"> command. Tasks are defined to run on particular role(s), this feature will target the server inside a particular tasks role. It will not execute a task on an arbitrary server.</span>  <strong><a rel="external nofollow" href="http://cj.kihlbom.se/">CJ Kihlbom </a><span style="font-weight:normal;">filled me in and stated this can be accomplished with `cap deploy HOSTS=IP_ADDRESS`.  See his comments attached to this post.</span></strong></p>
+<form action="http://geminstallthat.wordpress.com/wp-comments-post.php" method="post"></form>
+<img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/geminstallthat.wordpress.com/28/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/geminstallthat.wordpress.com/28/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/geminstallthat.wordpress.com/28/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/geminstallthat.wordpress.com/28/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/geminstallthat.wordpress.com/28/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/geminstallthat.wordpress.com/28/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/geminstallthat.wordpress.com/28/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/geminstallthat.wordpress.com/28/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/geminstallthat.wordpress.com/28/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/geminstallthat.wordpress.com/28/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/geminstallthat.wordpress.com/28/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/geminstallthat.wordpress.com/28/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=geminstallthat.wordpress.com&blog=309932&post=28&subd=geminstallthat&ref=&feed=1" /></div>]]></content:encoded>
+
+
+ <wfw:commentRss>http://geminstallthat.wordpress.com/2008/02/07/run-a-capistrano-task-on-a-single-server-in-a-given-role/feed/</wfw:commentRss>
+
+
+ <media:content url="http://a.wordpress.com/avatar/retr0h-128.jpg" medium="image">
+ <media:title type="html">retr0h</media:title>
+ </media:content>
+
+ </item>
+
+
+ <item>
+ <title>Rake tasks :through Capistrano</title>
+ <link>http://geminstallthat.wordpress.com/2008/01/27/rake-tasks-through-capistrano/</link>
+ <comments>http://geminstallthat.wordpress.com/2008/01/27/rake-tasks-through-capistrano/#comments</comments>
+ <pubDate>Mon, 28 Jan 2008 00:37:06 +0000</pubDate>
+ <dc:creator>John Dewey</dc:creator>
+
+ <category><![CDATA[Example]]></category>
+
+ <category><![CDATA[Rake]]></category>
+
+ <category><![CDATA[capistrano]]></category>
+ <guid isPermaLink="false">http://geminstallthat.wordpress.com/?p=27</guid>
+
+
+ <description><![CDATA[Executing Rake tasks on host(s) through Capistrano is nothing new, and there are quite a few ways to do it. Some of the configurations out there, use a common Capistrano task to execute all Rake tasks. This works quite well, but you run into problems when scoping tasks to specific roles. [...]]]></description>
+
+ <content:encoded><![CDATA[<div class='snap_preview'><br /><p>Executing <a href="http://rake.rubyforge.org/"><span class="Apple-style-span" style="font-weight:bold;">Rake</span></a> tasks on host(s) through <a href="http://capify.org/"><span class="Apple-style-span" style="font-weight:bold;">Capistrano</span></a> is nothing new, and there are quite a few ways to do it. Some of the configurations out there, use a common <a href="http://capify.org/"><span class="Apple-style-span" style="font-weight:bold;">Capistrano</span></a> task to execute all <a href="http://rake.rubyforge.org/"><span class="Apple-style-span" style="font-weight:bold;">Rake</span></a> tasks. This works quite well, but you run into <span style="font-weight:bold;" class="Apple-style-span">problems</span> when scoping tasks to specific roles. The common <a href="http://rake.rubyforge.org/"><span class="Apple-style-span" style="font-weight:bold;">Rake</span></a> exec task is not scoped to a specific role (because it could run any task on any given role), so if you call it from a <a href="http://capify.org/"><span class="Apple-style-span" style="font-weight:bold;">Cap</span></a> task scoped to :db, it will still execute across all roles.</p>
+<p>This was corrected by creating a <a href="http://pastie.caboo.se/143948"><span style="font-weight:bold;" class="Apple-style-span">Rake exec method</span></a> instead of a <a href="http://capify.org/"><span class="Apple-style-span" style="font-weight:bold;">Cap</span></a> task. The method can pass any arbitrary variables and/or <span class="Apple-style-span" style="font-weight:bold;">Rakefile</span> to <a href="http://rake.rubyforge.org/"><span class="Apple-style-span" style="font-weight:bold;">Rake</span></a>.<a href="http://pastie.caboo.se/143950"><span style="font-weight:bold;" class="Apple-style-span">An example Capistrano task</span></a>, displays it&#8217;s usage. There are a few <a href="http://capify.org/"><span class="Apple-style-span" style="font-weight:bold;">Cap</span></a> variables already set, such as rake and rails_env. Using the rakefile variable allows the execution of a specific <span class="Apple-style-span" style="font-weight:bold;">Rakefile</span>, which comes in handy when wanting to ignore the Rails <span class="Apple-style-span" style="font-weight:bold;">Rakefile</span>.</p>
+<p>Why ignore the <a href="http://rubyonrails.org/"><span class="Apple-style-span" style="font-weight:bold;">Rails</span></a> <span class="Apple-style-span" style="font-weight:bold;">Rakefile</span>? In our configuration the <span class="Apple-style-span" style="font-weight:bold;">Rakefile</span> loads environment.rb, which sets the load_paths to our custom <span class="Apple-style-span" style="font-weight:bold;">rubygems</span> path - that means loading <a href="http://rubyonrails.org/"><span class="Apple-style-span" style="font-weight:bold;">Rails</span></a>! What happens when <a href="http://rubyonrails.org/"><span class="Apple-style-span" style="font-weight:bold;">Rails</span></a> is <span class="Apple-style-span" style="font-weight:bold;">not</span> installed, and you need a bootstrap task. The ability to pass in a <span class="Apple-style-span" style="font-weight:bold;">Rakefile</span> all of the sudden becomes useful.</p>
+<img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/geminstallthat.wordpress.com/27/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/geminstallthat.wordpress.com/27/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/geminstallthat.wordpress.com/27/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/geminstallthat.wordpress.com/27/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/geminstallthat.wordpress.com/27/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/geminstallthat.wordpress.com/27/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/geminstallthat.wordpress.com/27/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/geminstallthat.wordpress.com/27/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/geminstallthat.wordpress.com/27/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/geminstallthat.wordpress.com/27/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/geminstallthat.wordpress.com/27/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/geminstallthat.wordpress.com/27/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=geminstallthat.wordpress.com&blog=309932&post=27&subd=geminstallthat&ref=&feed=1" /></div>]]></content:encoded>
+
+
+ <wfw:commentRss>http://geminstallthat.wordpress.com/2008/01/27/rake-tasks-through-capistrano/feed/</wfw:commentRss>
+
+
+ <media:content url="http://a.wordpress.com/avatar/retr0h-128.jpg" medium="image">
+ <media:title type="html">retr0h</media:title>
+ </media:content>
+
+ </item>
+
+
+ <item>
+ <title>Multi-App Mongrel Cluster Init Script</title>
+ <link>http://geminstallthat.wordpress.com/2008/01/22/multi-app-mongrel-cluster-init-script/</link>
+ <comments>http://geminstallthat.wordpress.com/2008/01/22/multi-app-mongrel-cluster-init-script/#comments</comments>
+ <pubDate>Wed, 23 Jan 2008 01:34:44 +0000</pubDate>
+ <dc:creator>John Dewey</dc:creator>
+
+ <category><![CDATA[Deployment]]></category>
+
+ <category><![CDATA[Mongrel]]></category>
+
+ <category><![CDATA[Rails]]></category>
+
+ <category><![CDATA[capistrano]]></category>
+
+ <category><![CDATA[configuration]]></category>
+ <guid isPermaLink="false">http://geminstallthat.wordpress.com/2008/01/22/multi-app-mongrel-cluster-init-script/</guid>
+
+
+ <description><![CDATA[I found the mongrel_cluster.sh script, everyone uses &#8212; doesn&#8217;t quite cut it.   Especially, when a system may host several RoR applications.  Why, you ask?  The ability to easily control, and dynamically discover applications is missing.  In most cases, this doesn&#8217;t matter.  Especially when the applications are deployed with Capistrano, since it is leveraged for various tasks (including [...]]]></description>
+
+ <content:encoded><![CDATA[<div class='snap_preview'><br /><p>I found the <span style="font-weight:bold;" class="Apple-style-span">mongrel_cluster.sh</span> script, everyone uses &#8212; doesn&#8217;t quite cut it.   Especially, when a system may host several RoR applications.  <span style="font-weight:bold;" class="Apple-style-span">Why, you ask?</span>  The ability to <span style="font-weight:bold;" class="Apple-style-span">easily control</span>, and <span style="font-weight:bold;" class="Apple-style-span">dynamically discover applications</span> is missing.  In most cases, this doesn&#8217;t matter.  Especially when the applications are deployed with <a href="http://www.capify.org/"><span style="font-weight:bold;" class="Apple-style-span">Capistrano</span></a>, since it is leveraged for various tasks (including stop, starts, and restarts).</p>
+<p>The <a href="http://en.wikipedia.org/wiki/System_admin"><span style="font-weight:bold;" class="Apple-style-span">SAs</span></a> on the other hand, usually do not use <a href="http://www.capify.org/"><span style="font-weight:bold;" class="Apple-style-span">Capistrano</span></a> (they should!), or have a recent version of the app checked out.  Having a standard init script across all your applications and servers becomes useful.</p>
+<p>You get bonus points for dynamically discovering new application installs.We can make certain assumptions in our environment.</p>
+<p>We use <a href="http://www.capify.org/"><span style="font-weight:bold;" class="Apple-style-span">Capistrano</span></a>, apps run as the same user,  live under the same base directory, and link to common Subversion <a href="http://rake.rubyforge.org/"><span style="font-weight:bold;" class="Apple-style-span">Rake</span></a> tasks.  That enables the use of a generic <a href="http://rake.rubyforge.org/"><span style="font-weight:bold;" class="Apple-style-span">Rake</span></a> task <a href="http://pastie.caboo.se/167187"><span class="Apple-style-span" style="font-weight:bold;">to control all RoR applications</span></a>. Furthermore, the <a href="http://rake.rubyforge.org/"><span class="Apple-style-span" style="font-weight:bold;">Rake</span></a> task can be wrapped by a system <a href="http://pastie.caboo.se/167184"><span class="Apple-style-span" style="font-weight:bold;">init script</span></a> for use under /etc/init.d/.</p>
+<img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/geminstallthat.wordpress.com/25/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/geminstallthat.wordpress.com/25/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/geminstallthat.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/geminstallthat.wordpress.com/25/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/geminstallthat.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/geminstallthat.wordpress.com/25/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/geminstallthat.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/geminstallthat.wordpress.com/25/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/geminstallthat.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/geminstallthat.wordpress.com/25/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/geminstallthat.wordpress.com/25/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/geminstallthat.wordpress.com/25/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=geminstallthat.wordpress.com&blog=309932&post=25&subd=geminstallthat&ref=&feed=1" /></div>]]></content:encoded>
+
+
+ <wfw:commentRss>http://geminstallthat.wordpress.com/2008/01/22/multi-app-mongrel-cluster-init-script/feed/</wfw:commentRss>
+
+
+ <media:content url="http://a.wordpress.com/avatar/retr0h-128.jpg" medium="image">
+ <media:title type="html">retr0h</media:title>
+ </media:content>
+
+ </item>
+
+
+ <item>
+ <title>Patching Sake for hidden tasks</title>
+ <link>http://geminstallthat.wordpress.com/2008/01/18/patching-sake-for-hidden-tasks/</link>
+ <comments>http://geminstallthat.wordpress.com/2008/01/18/patching-sake-for-hidden-tasks/#comments</comments>
+ <pubDate>Sat, 19 Jan 2008 01:05:15 +0000</pubDate>
+ <dc:creator>John Dewey</dc:creator>
+
+ <category><![CDATA[Rake]]></category>
+
+ <category><![CDATA[Sake]]></category>
+ <guid isPermaLink="false">http://geminstallthat.wordpress.com/2008/01/18/patching-sake-for-hidden-tasks/</guid>
+
+
+ <description><![CDATA[Rake will &#8220;hide&#8221; tasks from &#8216;rake -T&#8217; when a description is missing from an associated task. This behavior is was missing from Sake until today.  I recently submitted a patch to Sake, which Chris applied to version (1.0.12).
+To display all installed tasks, run &#8217;sake -Tv&#8217;, otherwise Sake will hide tasks without a description.
+ ]]></description>
+
+ <content:encoded><![CDATA[<div class='snap_preview'><br /><p><a href="http://rake.rubyforge.org/"><span class="Apple-style-span" style="font-weight:bold;">Rake</span></a> will &#8220;<span class="Apple-style-span" style="font-weight:bold;">hide</span>&#8221; tasks from &#8216;rake -T&#8217; when a description is missing from an associated task. This behavior <span class="Apple-style-span" style="text-decoration:line-through;">is</span> was missing from <a href="http://errtheblog.com/posts/60-sake-bomb"><span class="Apple-style-span" style="font-weight:bold;">Sake</span></a> until today.  I recently submitted a patch to <a href="http://errtheblog.com/posts/60-sake-bomb"><span class="Apple-style-span" style="font-weight:bold;">Sake</span></a>, which Chris applied to version (1.0.12).</p>
+<p>To display all installed tasks, run &#8217;sake -Tv&#8217;, otherwise <a href="http://errtheblog.com/posts/60-sake-bomb"><span class="Apple-style-span" style="font-weight:bold;">Sake</span></a> will hide tasks without a description.</p>
+<img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/geminstallthat.wordpress.com/23/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/geminstallthat.wordpress.com/23/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/geminstallthat.wordpress.com/23/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/geminstallthat.wordpress.com/23/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/geminstallthat.wordpress.com/23/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/geminstallthat.wordpress.com/23/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/geminstallthat.wordpress.com/23/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/geminstallthat.wordpress.com/23/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/geminstallthat.wordpress.com/23/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/geminstallthat.wordpress.com/23/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/geminstallthat.wordpress.com/23/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/geminstallthat.wordpress.com/23/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=geminstallthat.wordpress.com&blog=309932&post=23&subd=geminstallthat&ref=&feed=1" /></div>]]></content:encoded>
+
+
+ <wfw:commentRss>http://geminstallthat.wordpress.com/2008/01/18/patching-sake-for-hidden-tasks/feed/</wfw:commentRss>
+
+
+ <media:content url="http://a.wordpress.com/avatar/retr0h-128.jpg" medium="image">
+ <media:title type="html">retr0h</media:title>
+ </media:content>
+
+ </item>
+
+
+ <item>
+ <title>Use ActiveResource to Consume Simple Objects Over REST</title>
+ <link>http://geminstallthat.wordpress.com/2008/01/16/use-activeresource-to-consume-any-object-over-rest/</link>
+ <comments>http://geminstallthat.wordpress.com/2008/01/16/use-activeresource-to-consume-any-object-over-rest/#comments</comments>
+ <pubDate>Thu, 17 Jan 2008 06:43:44 +0000</pubDate>
+ <dc:creator>reidmix</dc:creator>
+
+ <category><![CDATA[ActiveResource]]></category>
+
+ <category><![CDATA[Code]]></category>
+
+ <category><![CDATA[Example]]></category>
+
+ <category><![CDATA[Rails]]></category>
+
+ <category><![CDATA[REST]]></category>
+ <guid isPermaLink="false">http://geminstallthat.wordpress.com/2008/01/16/use-activeresource-to-consume-any-object-over-rest/</guid>
+
+
+ <description><![CDATA[I&#8217;m sure that many organizations have services available via REST that are not produced by Rails and do not follow Rails conventions. That doesn&#8217;t mean you have to write your own client. For simple objects, use ActiveResource.
+To take advantage of ActiveResource in this way use the ActiveResource::Base#find method. You need to find(:all), find(:first), [...]]]></description>
+
+ <content:encoded><![CDATA[<div class='snap_preview'><br /><p>I&#8217;m sure that many organizations have services available via REST that are not produced by Rails and do not follow Rails conventions. That doesn&#8217;t mean you have to write your own client. For simple objects, use ActiveResource.</p>
+<p>To take advantage of ActiveResource in this way use the <b>ActiveResource::Base#find</b> method. You need to <b>find(:all)</b>, <b>find(:first)</b>, or <b>find(:one)</b> but you need to pass a string of the path to the :from option along with any <b>:params</b> you need. If you pass a symbol as the <b>:from</b> option, this will not work and will employ all of the the <b>ActiveResource::Base</b> mechanics. A string will bypass this, for example:</p>
+<pre name="code" class="ruby">
+
+class RestClient &lt; ActiveResource::Base
+ self.site = &quot;http://java-based-service.company.com&quot;
+
+ def self.invoke(params = {})
+ find(:one, :from =&gt; &quot;/nonconventional/path/to/rest/service/&quot;, :params =&gt; params)
+ end
+end
+
+object = RestClient.invoke(:irregular_id =&gt; 10) #=&gt; service returns &lt;data&gt;&lt;name&gt;reid&lt;/name&gt;&lt;role&gt;blogger&lt;/role&gt;&lt;/data&gt;
+object.name #=&gt; reid
+object.role #=&gt; blogger
+</pre>
+<img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/geminstallthat.wordpress.com/24/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/geminstallthat.wordpress.com/24/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/geminstallthat.wordpress.com/24/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/geminstallthat.wordpress.com/24/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/geminstallthat.wordpress.com/24/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/geminstallthat.wordpress.com/24/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/geminstallthat.wordpress.com/24/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/geminstallthat.wordpress.com/24/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/geminstallthat.wordpress.com/24/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/geminstallthat.wordpress.com/24/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/geminstallthat.wordpress.com/24/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/geminstallthat.wordpress.com/24/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=geminstallthat.wordpress.com&blog=309932&post=24&subd=geminstallthat&ref=&feed=1" /></div>]]></content:encoded>
+
+
+ <wfw:commentRss>http://geminstallthat.wordpress.com/2008/01/16/use-activeresource-to-consume-any-object-over-rest/feed/</wfw:commentRss>
+
+
+ <media:content url="http://a.wordpress.com/avatar/reidmix-128.jpg" medium="image">
+ <media:title type="html">reidmix</media:title>
+ </media:content>
+
+ </item>
+
+
+ <item>
+ <title>Advanced Rails Recipes Contribution</title>
+ <link>http://geminstallthat.wordpress.com/2008/01/06/advanced-rails-recipes-contribution/</link>
+ <comments>http://geminstallthat.wordpress.com/2008/01/06/advanced-rails-recipes-contribution/#comments</comments>
+ <pubDate>Sun, 06 Jan 2008 22:40:05 +0000</pubDate>
+ <dc:creator>John Dewey</dc:creator>
+
+ <category><![CDATA[capistrano]]></category>
+
+ <category><![CDATA[configuration]]></category>
+ <guid isPermaLink="false">http://geminstallthat.wordpress.com/2008/01/06/advanced-rails-recipes-contribution/</guid>
+
+
+ <description><![CDATA[Giles and I had our contributions selected for the upcoming Advanced Rais Recipes Book &#8211; pretty cool!
+
+ 
+ ]]></description>
+
+ <content:encoded><![CDATA[<div class='snap_preview'><br /><p><a href="http://gilesbowkett.blogspot.com/"><span style="font-weight:bold;" class="Apple-style-span">Giles</span></a> and I had our contributions selected for the upcoming <a href="http://pragprog.com/titles/fr_arr"><span class="Apple-style-span" style="font-weight:bold;">Advanced Rais Recipes Book</span></a><span class="Apple-style-span" style="font-weight:bold;"> </span>&#8211; pretty cool!
+<div></div>
+<div> </div>
+<img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/geminstallthat.wordpress.com/20/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/geminstallthat.wordpress.com/20/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/geminstallthat.wordpress.com/20/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/geminstallthat.wordpress.com/20/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/geminstallthat.wordpress.com/20/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/geminstallthat.wordpress.com/20/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/geminstallthat.wordpress.com/20/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/geminstallthat.wordpress.com/20/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/geminstallthat.wordpress.com/20/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/geminstallthat.wordpress.com/20/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/geminstallthat.wordpress.com/20/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/geminstallthat.wordpress.com/20/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=geminstallthat.wordpress.com&blog=309932&post=20&subd=geminstallthat&ref=&feed=1" /></div>]]></content:encoded>
+
+
+ <wfw:commentRss>http://geminstallthat.wordpress.com/2008/01/06/advanced-rails-recipes-contribution/feed/</wfw:commentRss>
+
+
+ <media:content url="http://a.wordpress.com/avatar/retr0h-128.jpg" medium="image">
+ <media:title type="html">retr0h</media:title>
+ </media:content>
+
+ </item>
+
+
+ <item>
+ <title>Scrape mongrel status from Apache&#8217;s load balancer manager</title>
+ <link>http://geminstallthat.wordpress.com/2008/01/04/scrape-mongrel-status-from-apaches-load-balancer-manager/</link>
+ <comments>http://geminstallthat.wordpress.com/2008/01/04/scrape-mongrel-status-from-apaches-load-balancer-manager/#comments</comments>
+ <pubDate>Sat, 05 Jan 2008 05:48:36 +0000</pubDate>
+ <dc:creator>John Dewey</dc:creator>
+
+ <category><![CDATA[Apache]]></category>
+
+ <category><![CDATA[Code]]></category>
+
+ <category><![CDATA[Command Line]]></category>
+
+ <category><![CDATA[Example]]></category>
+
+ <category><![CDATA[Rails]]></category>
+
+ <category><![CDATA[Rake]]></category>
+
+ <category><![CDATA[WebServers]]></category>
+
+ <category><![CDATA[capistrano]]></category>
+
+ <category><![CDATA[configuration]]></category>
+ <guid isPermaLink="false">http://geminstallthat.wordpress.com/2008/01/04/scrape-mongrel-status-from-apaches-load-balancer-manager/</guid>
+
+
+ <description><![CDATA[Access to Apache&#8217;s load balancer manager is typically protected.  Rather than maintaining an ever changing list of ACLs or configuring HTTP authentication, I wrote a simple scraper Rake task.
+This task is bundled with all of our rails applications as a svn external, and included in our common Capistrano tasks.This task is a quick and dirty solution to checking the status of [...]]]></description>
+
+ <content:encoded><![CDATA[<div class='snap_preview'><br /><p>Access to <a href="http://httpd.apache.org/docs/2.2/mod/mod_proxy_balancer.html"><span class="Apple-style-span" style="font-weight:bold;">Apache&#8217;s load balancer manager</span></a> is typically protected.  Rather than maintaining an ever changing list of ACLs or configuring HTTP authentication, I wrote a simple scraper<span style="font-weight:bold;" class="Apple-style-span"> <a href="http://rake.rubyforge.org/">Rake</a> </span>task.</p>
+<p>This task is bundled with all of our rails applications as a svn external, and included in our common <a href="http://www.capify.org/"><span class="Apple-style-span" style="font-weight:bold;">Capistrano</span></a> tasks.This task is a quick and dirty solution to checking the status of your <a href="http://mongrel.rubyforge.org/"><span class="Apple-style-span" style="font-weight:bold;">Mongrels</span></a>.</p>
+<p>Feel free to modify, and provide a pastie.<a href="http://pastie.caboo.se/135223">http://pastie.caboo.se/135223</a></p>
+<p>P.S. The SAs can plug this into their monitoring software as well.</p>
+<img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/geminstallthat.wordpress.com/19/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/geminstallthat.wordpress.com/19/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/geminstallthat.wordpress.com/19/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/geminstallthat.wordpress.com/19/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/geminstallthat.wordpress.com/19/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/geminstallthat.wordpress.com/19/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/geminstallthat.wordpress.com/19/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/geminstallthat.wordpress.com/19/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/geminstallthat.wordpress.com/19/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/geminstallthat.wordpress.com/19/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/geminstallthat.wordpress.com/19/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/geminstallthat.wordpress.com/19/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=geminstallthat.wordpress.com&blog=309932&post=19&subd=geminstallthat&ref=&feed=1" /></div>]]></content:encoded>
+
+
+ <wfw:commentRss>http://geminstallthat.wordpress.com/2008/01/04/scrape-mongrel-status-from-apaches-load-balancer-manager/feed/</wfw:commentRss>
+
+
+ <media:content url="http://a.wordpress.com/avatar/retr0h-128.jpg" medium="image">
+ <media:title type="html">retr0h</media:title>
+ </media:content>
+
+ </item>
+
+
+</channel>
+</rss>
470 vendor/feed_me/spec/fixtures/real/mezzoblue.xml
@@ -0,0 +1,470 @@
+<?xml version="1.0" encoding="utf-8"?>
+<rss version="2.0"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
+ xmlns:admin="http://webns.net/mvcb/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:content="http://purl.org/rss/1.0/modules/content/">
+
+ <channel>
+ <title>Dave Shea's mezzoblue</title>
+ <link>http://mezzoblue.com/</link>
+ <description></description>
+ <dc:language>en-us</dc:language>
+ <dc:creator>dave@shea.net</dc:creator>
+ <dc:rights>Copyright 2008</dc:rights>
+ <dc:date>2008-03-18T15:08:59-08:00</dc:date>
+ <admin:generatorAgent rdf:resource="http://www.movabletype.org/?v=3.35" />
+ <admin:errorReportsTo rdf:resource="mailto:dave@mezzoblue.com"/>
+ <sy:updatePeriod>hourly</sy:updatePeriod>
+ <sy:updateFrequency>1</sy:updateFrequency>
+ <sy:updateBase>2000-01-01T12:00+00:00</sy:updateBase>
+
+ <item>
+ <title>Mediatyping</title>
+ <link>http://mezzoblue.com/archives/2008/03/18/mediatyping/</link>
+ <description>
+ Just prior to jetting off to Austin last week, I started playing around with a mobile version of this site. While a personal blog is hardly a site that really needs one (unlike, say, an app with a proven mobile...
+ </description>
+ <guid isPermaLink="false">1420@http://mezzoblue.com/</guid>
+ <content:encoded><![CDATA[
+ <p>Just prior to jetting off to Austin last week, I started playing around with a mobile version of this site. While a personal blog is hardly a site that really <em>needs</em> one (unlike, say, an app with a proven mobile user-base like <a href="http://m.twitter.com/">Twitter</a>), I wanted to see what would be involved in re-factoring this design into something more fitting for a mobile environment.</p>
+
+
+<div class="image-holder-left medium">
+<img width="420" height="210" alt="Mezzoblue Mobile" src="http://mezzoblue.com/i/articles/2008/mar/mediatyping.jpg"/>
+</div>
+
+<p>The first step was to create a <a href="http://mezzoblue.com/css/fountain/layout-mobile.css">mobile style sheet</a>. For this I duplicated the CSS file I've already built for large screens, and started stripping out the style that doesn't work so well on a smaller screen. The layout was simplified into a more linear single column, and some elements were re-done to provide a larger target area for a maximum <a href="http://particletree.com/features/visualizing-fittss-law/">Fitts</a> factor, and background images were dropped wherever possible to cut down on bandwidth demands. I tried building something that would work well on more mobile devices than just the iPhone, but given that it's my testing device, it works best on that platform for now.</p>
+
+<p>And while I was at it, I thought hey, why not do a TV style sheet too? I've got a <a href="http://en.wikipedia.org/wiki/Wii">Wii</a>, it's got a <a href="http://www.opera.com/products/devices/nintendo/">great browser</a>, and a low-res TV screen could benefit from the same kind of special attention given a mobile device. So I built <a href="http://mezzoblue.com/css/fountain/layout-tv.css">one of those</a> as well, doing things like increasing font size, increasing border widths, and stretching images to double their original size.</p>
+
+<p>But here's the thing about media-specific style sheets: the browser in question has to support them. Mobile Safari grabs all <code>screen</code> media style sheets, and ignores the <code>handheld</code> media type entirely. So despite good intentions, my efforts were wasted on it. And that's what led me down the road of user agent sniffing...</p>
+
+<p>Okay, let's get this out of the way up front: <a href="http://mezzoblue.com/archives/2007/11/12/detect_this/">user agent sniffing sucks</a>. Devices like the Wii and the iPhone have incredibly capable browsers that can render these sites the same as any desktop browser, so it's reasonable to assume users will want to do so from time to time. (And I suspect that's why Mobile Safari uses <code>screen</code> in the first place.) Forcing a specific version sucks... if done improperly. But when used well, and <em>not mandatory</em> for the user, I think it's not entirely evil. And it leads to other potential improvements beyond what CSS can provide, like selective content serving.</p>
+
+<p>The way to sensibly handle sniffing seemed to lie in providing an out: the mobile and TV versions of the site both have a special header on every page that provides a "regular site" toggle link. Any time someone wants to switch back to the regular site, the link is right there in front of them. And all versions now have toggle links in the footer to switch between different media types; given that I've seen this on multiple mobile sites, it feels like the site's footer is resolving into a standard place for where these type of switches ultimately belong.</p>
+
+<p>To make all this work, I had to bust out the PHP. I'll preface this by saying I'm hardly a proficient coder, so there are bound to be ways to optimize what I'm showing below. I started with a pair of arrays: a list of mobile browser user agent strings, and a second list of TV browsers. The latter is a bit light at the moment, due to my lack of knowledge of what sort of browsers are available for use on TVs. (And that underscores why having a media toggle is useful: if the browser in question isn't flagged by the sniff, the user can manually invoke the TV version.)</p>
+
+
+<pre><code class="php">// ==========================
+// media check
+
+// array of mobile devices
+$userAgentsMobile = array (
+ "Blackberry",
+ "Blazer",
+ "Handspring",
+ "iPhone",
+ "iPod",
+ "Kyocera",
+ "LG",
+ "Motorola",
+ "Nokia",
+ "Palm",
+ "PlayStation Portable",
+ "Samsung",
+ "Smartphone",
+ "SonyEricsson",
+ "Symbian",
+ "WAP",
+ "Windows CE",
+);
+
+// array of tv devices
+$userAgentsTv = array (
+ "Nintendo Wii",
+ "Playstation 3",
+ "WebTV"
+);</code></pre>
+
+<p>Arrays in place, the next step was building a few functions to do things like comparing these arrays with the user's actual user agent string, and setting cookies to make these media types persist. More on the latter in a second.</p>
+
+<pre><code class="php">// this function takes two arguments: an array of user
+// agents, and a specific user agent.
+// it will then try to see if the specific user agent exists
+// within the array. If so, it will return true, otherwise
+// it returns false.
+function checkMediaType($uaList, $uaKnown) {
+ // check user agent string against array
+ // return true if found, or false if not found
+ if(in_array($uaKnown, $uaList)) {
+ return true;
+ } else {
+ return false;
+ }
+}
+// this function takes one argument: a string value that
+// specifies a media profile. It will then set a cookie in
+// the user's browser. It returns the media profile value,
+// to be used as a variable later in the page
+function selectMedia($media) {
+ setcookie ('media', $media, time()+31536000, '/');
+ return $media;
+}</code></pre>
+
+<p>With those functions in place, the code below ended up being the core of my script. In the first major <code>if</code> statement, I'm checking to see whether a cookie is set; the cookie exists to avoid parsing the user agent arrays every single time the site is loaded. I doubt I'm saving that much time if any, given my currently very simple arrays. But I can see them growing over time, so it seems to make sense that this value should persist on the user's end once the user agent has been determined.</p>
+
+<p>But if no cookie is found, then I'm doing the actual sniffing. I check the user agent string against both the mobile and TV arrays, and then act on them if they match one or the other. If neither matches, I default to the screen version of the site.</p>
+
+<pre><code class="php">// show standard screen version by default
+$mediaVersion = "screen";
+
+// toggle media version if cookie is set
+if (isset($_COOKIE["media"])) {
+ if ($_COOKIE["media"] == "mobile") {
+ $mediaVersion = selectMedia("mobile");
+ } elseif ($_COOKIE["media"] == "tv") {
+ $mediaVersion = selectMedia("tv");
+ } elseif ($_COOKIE["media"] == "screen") {
+ $mediaVersion = selectMedia("screen");
+ }
+} else {
+ // if no cookie found, sniff media type then set cookie
+ $knownUserAgent = false;
+
+ // compare the device arrays against the
+ // client's user agent
+ $mediaTypeMobile =
+ checkMediaType($userAgentsMobile, $_SERVER['HTTP_USER_AGENT']);
+ $mediaTypeTV =
+ checkMediaType($userAgentsTv, $_SERVER['HTTP_USER_AGENT']);
+
+ // if media version is found, set a media type cookie
+ // otherwise flag this browser as screen to save
+ // time on future loads
+ if ($mediaTypeMobile) {
+ $mediaVersion = selectMedia("mobile");
+ } elseif ($mediaTypeTV) {
+ $mediaVersion = selectMedia("tv");
+ } else {
+ $mediaVersion = selectMedia("screen");
+ }
+}</code></pre>
+
+<p>And then the last step is checking to see whether any of the media toggle links have been selected. If a user is viewing the mobile site and wants to switch over to the regular site, I need to re-set the cookie so their new preference persists. This is all done through simple query strings; the HTTP GET variable being checked for (<code>media</code>) can be invoked simply by adding <code>?media=mobile</code> to the end of a URL or link.</p>
+
+<pre><code class="php">// override media version and set a new cookie
+// if they have selected a "show {media} site" link
+if ($_GET["media"]) {
+ if ($_GET["media"] == "mobile") {
+ $mediaVersion = selectMedia("mobile");
+ } elseif ($_GET["media"] == "tv") {
+ $mediaVersion = selectMedia("tv");
+ } elseif ($_GET["media"] == "screen") {
+ $mediaVersion = selectMedia("screen");
+ }
+}</code></pre>
+
+<p>Now that the cookie exists and the <var>$mediaVersion</var> variable has a value, I'm all set. Anywhere in the rest of my site, I can use a simple <code>if</code> statement to filter out the specific media version I'm targetting, and selectively show or hide content for it. This is how I'm hiding my header photo from the mobile version, for example (simplified for clarity):</p>
+
+<pre><code class="php">&lt;?php
+ // don't serve this up if we're talking mobile
+ if ($mediaVersion == "screen" || $mediaVersion == "tv") {
+?&gt;
+ &lt;div id="header-photo"&gt;
+ &lt;img src="/i/photos/&lt;?php
+ echo $currentPhoto["largephoto"]?&gt;"
+ width="505" height="243"
+ alt="&lt;?php echo $currentPhoto["description"]?&gt;" /&gt;
+ &lt;/div&gt;
+&lt;?php
+ }
+?&gt;</code></pre>
+
+<p>I put together the entire set of functions and a couple of small demonstrations of selective content into a <a href="http://mezzoblue.com/articles/supplements/2008/03/mobile.html">file</a> you can grab. (<a href="http://mezzoblue.com/articles/supplements/2008/03/">live demo</a>)</p>
+
+<p>And to finish, a couple of highlights from the alternate media versions of this site. The mobile version goes small by stripping out images where appropriate to save on download times; the header photo is gone, avatars on comment pages are gone, and most of the decorative PNGs have been removed or converted to CSS border properties. While the TV version goes big by doubling font size, increasing border widths, stretching the header image to fill the entire horizontal screen width, doubles avatar sizes, and using a higher-resolution version of the site's logo.</p>
+
+<p>Quick caveat: I've only had the opportunity to test the alternate media styles on a limited range of devices, and likely won't any time soon. Device testing is too hard if you don't already have access to a wide range of devices. I tried stripping out some of the more complex CSS tricks like overflow clearing and absolute positioning, so even my Treo doesn't make a horrible mess out of the site, but no guarantees.</p>
+
+<p>And the bonus question: why am I serving up the media versions with media profiles set to <code>all</code>? Simple reason: clicking through and viewing the TV-only version on a computer was seriously ugly, because it ignored the style sheet. As it should. But people will click through anyway, so that was problematic. Simple fix: <code>media="all"</code></p>
+
+<p><ins>Updated</ins> to include iPod Touch.</p>
+<p><ins>Updated</ins> PHP for minor optimizations in response to comments.</p>
+
+ ]]></content:encoded>
+ <dc:subject>Technology</dc:subject>
+ <dc:date>2008-03-18T15:08:59-08:00</dc:date>
+ </item>
+ <item>
+ <title>Producing</title>
+ <link>http://mezzoblue.com/archives/2008/03/04/producing/</link>
+ <description>
+ So here&apos;s a not-so-hypothetical question I&apos;d like to put out there: as a designer what are the saleable products one might go about creating? I mean products in the sense of re-sellable items that appeal to a mass market, instead...
+ </description>
+ <guid isPermaLink="false">1416@http://mezzoblue.com/</guid>
+ <content:encoded><![CDATA[
+ <p>So here's a not-so-hypothetical question I'd like to put out there: as a <em>designer</em> what are the saleable products one might go about creating?</p>
+
+<p>I mean products in the sense of re-sellable items that appeal to a mass market, instead of services like individual design work sold to a single client. And at the moment I'm biased toward independently produced and sold products. I've been thinking about this question quite a bit lately, as might be evident by the <a href="http://www.mezzoblue.com/icons/chalkwork/">icons</a> I built last year.</p>
+
+<p>It seems like everyone else is building a web application of some sort. While that's becoming a well-trodden path, you can't exactly slap together a few static Photoshop mockups and convince people to put up money. There's a heavy coding requirement that a lot of designers can't handle. Yeah, uber-genius designer + coder types like Shaun Inman can bang out one-man apps like <a href="http://haveamint.com/">Mint</a> in their sleep. And sure, you can always team up with a talented coder and collaborate. But for the purposes of this post, let's put aside those possibilities and focus on strictly that which a visual designer is capable of producing on their own steam.</p>
+
+<p>Here are the few ideas I've managed to scrounge up thus far:</p>
+
+<dl>
+<dt>T-Shirts</dt>
+<dd>
+<p>The big obvious one. With the success of <a href="http://threadless.com/">Threadless</a> there's clearly money in it. People will always need clothes. It's a safe bet. But it's also crowded; it seems like everyone and their dog is selling shirts these days, through <a href="http://cafepress.com/">CafePress</a> or through their own sites.</p>
+<p>Pros: services exist to help you get started, probably a safe choice. Cons: inventory and distribution management, finding an audience. Examples: <a href="https://www.cottyn.com/">Cottyn</a>, <a href="http://www.simplebits.com/shop/">SimpleBits</a> (though it seems to be on hold at the moment).</p>
+</dd>
+
+<dt>Buttons</dt>
+<dd>
+<p>Also a possibility, but buttons seem like less of a product, and more like schwag. I might be under-estimating the draw of well-designed buttons, but my suspicion is that it's hard to retire off selling them.</p>
+<p>Pros &amp; Cons - same as t-Shirts, I'd expect. Examples: <a href="http://www.dsbuttons.com/">DS Buttons</a>, <a href="http://www.el-boton.com/">El Boton</a></p>
+</dd>
+
+<dt>Icons</dt>
+<dd>
+<p>A lot of designers seem to be coming out with their own icon sets lately, and it's for good reason: icons may be the easiest product to get started with. There's no inventory overhead, and they're useful to designers in a way similar to stock photography or type, which both have proven markets. Of course producing <em>good</em> icons is awfully hard to do, so depending on where your talents lie, the barrier to entry might be higher than simply managing overhead.</p>
+<p>Pros: low management overhead. Cons: unlicensed digital files are impossible to control, requires illustration abilities. Examples: <a href="http://iconshoppe.com/">Icon Shoppe</a>, <a href="http://www.iconbuffet.com/">IconBuffet</a>, <a href="http://www.chalkwork.com/">Chalkwork</a></p>
+</dd>
+
+<dt>Stock Photography</dt>
+<dd>
+<p>Not only does this take strong photography skills, which (while related) are quite different from design skills, it strikes me as being fairly hard to do independently.</p>
+<p>Pros: low management overhead. Cons: requires expensive gear, unlicensed digital files are impossible to control, requires photography abilities. Examples: aside from the now Getty-owned <a href="http://istockphoto.com/index.php">iStockPhoto</a> or Corbis-owned <a href="http://veer.com/">Veer</a>, I can't really think of any significant independent stock photography providers. They must exist...</p>
+</dd>
+
+<dt>Typefaces</dt>
+<dd>
+<p>I'll mention this for the sake of completeness, but it's not easy to crack into designing typefaces (or at least designing good ones) without years of prior experience. The few times I've customized type outlines I've been fairly glad it's not my job to do this for a living.</p>
+<p>Pros: proven market. Cons: unlicensed digital files are impossible to control, requires a lot of experience and talent. Examples: <a href="http://www.typography.com/">Hoefler &amp; Frere-Jones</a>, <a href="http://www.ms-studio.com/fontsales.html">Mark Simonson</a></p>
+</dd>
+
+<dt>Re-sellable blog/web site templates</dt>
+<dd>
+<p>Given the quality free themes for blog software, I think it would be rather difficult to put together a premium template and license it. The market has decided what themes cost: nothing. Again listed for the sake of completeness, but someone please point me to a good theme that someone's selling for actual cash money, that's also doing well? I think crickets are chirping.</p>
+</dd>
+</dl>
+
+<p>I've seen some other interesting ideas at places like <a href="http://www.t26.com/illustrations">T.26</a> and their <a href="http://www.t26.com/illustrations/Chopsticks">chopsticks packages</a> designed by Carlos Segura (hat tip: <a href="http://www.zeldman.com/2008/03/03/monday-links/">Zeldman</a>), and moving a little further out, more traditional products at indie designer stores like <a href="http://meomi.com/store.html">Meomi</a> and <a href="http://www.gama-go.com/home.php">Gama-Go</a>.</p>
+
+<p>It seems a bit ironic that moving away from the web and into physical products is the most obvious path for web designers to pursue; I wonder if it's possible to do both? Web-based products? Or at the very least, staying on the digital side as much as possible. I've been enjoying watching Dan Cederholm experiment with both <a href="http://iconshoppe.com/">digital</a> and <a href="http://foamee.com/">web</a> products on top of more traditional <a href="http://www.simplebits.com/shop/">products</a> and <a href="http://www.simplebits.com/publications/">publications</a>, it'll be interesting to see how these various ideas evolve over the next few years.</p>
+
+<p>And as for myself, well I've already mentioned the <a href="http://chalkwork.com/">icons</a>. I'm not getting rich off those, but I've seen a few interesting things happen since launching that I think will encourage me to keep producing new sets for a while. The numbers aren't there yet, but they're promising.</p>
+
+<p>One thing I have learned though is what everyone will always tell you about marketing: it's not enough to have a product, you have to tell people about it, and convince them they want it. So at SXSW this year I've got a whole pile of sheets of icon stickers that I'll be giving away; each sheet features a mixed variety of icon stickery goodness, collect the whole set!</p>
+
+<div class="image-holder-1up">
+<img src="http://mezzoblue.com/i/articles/2008/mar/chalkwork-stickers.jpg" width="500" height="300" alt="Chalkwork sticker sheet preview" />
+<p class="caption"><span class="caption-title">Figure:</span> Preview of Chalkwork icon sticker sheets.</p>
+</div>
+
+<p>Ironic, isn't it? Giving away a physical item to promote a digital product? Welcome to this strange new world the internet's creating.</p>
+
+<p><ins>Update:</ins> I removed the word "web" from the very first sentence of this post because I think it was tainting people's interpretation of what I'm really asking for here. Medium isn't necessarily a factor, so much as existing skillset. If I had to boil down this entire post to a single question, it's this:</p>
+
+<p><em>Hey, I've got these visual design skills, what can I do with them that will result in a product I can sell to people?</em></p>
+
+<p>I realize I also talk about the irony of web designers creating physical products above, but that was meant as more of an aside than the theme of the post. The context of the quoted examples above point to where I'm trying to go with this, and they're mostly design-oriented items, and not specifically web-related.</p>
+
+ ]]></content:encoded>
+ <dc:subject>Design</dc:subject>
+ <dc:date>2008-03-04T15:00:00-08:00</dc:date>
+ </item>
+ <item>
+ <title>Upcoming</title>
+ <link>http://mezzoblue.com/archives/2008/02/28/upcoming/</link>
+ <description>
+ Ah yes, that unmistakable smell of conference season is in the air. A few places I&apos;ll be making an appearance in coming weeks: SXSW I seriously considered not attending this year. It seems there&apos;s a growing feeling amongst those who...
+ </description>
+ <guid isPermaLink="false">1418@http://mezzoblue.com/</guid>
+ <content:encoded><![CDATA[
+ <p>Ah yes, that unmistakable smell of conference season is in the air. A few places I'll be making an appearance in coming weeks:</p>
+
+<dl>
+<dt><a href="http://sxsw.com/">SXSW</a></dt>
+<dd>
+<p>I seriously considered <a href="http://meyerweb.com/eric/thoughts/2008/02/22/south-bypass/">not attending</a> this year. It seems there's a <a href="http://www.airbagindustries.com/archives/airbag/hampton.php">growing feeling</a> amongst those who have been going for years that this event is getting to be a bit too much to handle, especially with the plethora of other web conferences that have sprung up in the past few years in their more intimate settings.</p>
+
+<p>But, I'll be there after all! Paul Hammond invited me to be the "independent contractor" or "designer" or "hardly qualified, but maybe we'll get a few good stories out of him anyway" voice on his panel about <a href="http://2008.sxsw.com/interactive/programming/panels_schedule/?action=show&amp;id=IAP060370">collaboration</a> during web projects. Don't let the web app focus in the title fool you, I don't do nearly enough of those to be that specific about the type of projects I've collaborated with others on; I'll be drawing from the experience I've had plugging myself into established teams, and working with clients directly. With a line up of three super-smart Brits and a talented Aussie, plus the token Canadian, come and see the Commonwealth throw down on Tuesday afternoon.</p>
+
+<p>(And this year I'll be feeling <a href="http://adactio.com/journal/1266?delicious-tag=sxsw">Jeremy's plight</a>, as it's looking increasingly like that room at the Hampton is going to be single occupancy. Sadly, Lauren probably won't make it this year, so if I'm a little distracted during any hallway chatter, it's not you! I promise!)</p>
+</dd>
+
+<dt><a href="http://aneventapart.com/">An Event Apart</a></dt>
+<dd>
+<p>When Jeffrey and Eric asked me to come speak at an earlier event, it pained me that circumstances didn't work out. I have never been to An Event Apart, and I've really really been wanting to go for some time now.</p>
+<p>Happily, the latter part of April was clear for me, and I'll be heading down to join what looks like is going to be a crazy-good <a href="http://aneventapart.com/events/2008/neworleans/#schedule">New Orleans</a> date. My talk, "Living, Breathing Design", will be a look at how web sites change and grow during and after the design process, and how we can choose to add growth into our work. As always, expect lots of design and code talk.</p>
+<p>(psst... and if you haven't registered yet, the magic code is <em>AEADSCSS</em>. It's $50 in your pocket. Use it.)</p>
+</dd>
+</dl>
+
+<p>While I'm at it, a few more things I've been meaning to mention in some capacity or another for a while now (funny how far off the wagon you fall when running a conference of your own):</p>
+
+<dl>
+<dt>Rosenfeld Media's First Book: <a href="http://www.rosenfeldmedia.com/books/mental-models/">Mental Models</a></dt>
+<dd>
+<p>This one is particularly close to my heart as I've been working with Lou for the past few years, and during that time witnessed his publishing company evolve from a concept to reality. We were very privileged to have Indi Young, the author of Mental Models, join us at WDN this year, and <a href="http://www.rosenfeldmedia.com/books/mental-models/content/testimonials/">early reviews</a> have pegged this one as a winner. If you grab a copy, make sure to use the code <em>FOSHEA10</em> for a small discount.</p>
+</dd>
+
+<dt>The Lab with Leo</dt>
+<dd>
+<p>Back in November I went in and did an interview with Leo Laporte, which is now up on <a href="http://video.google.com/videoplay?docid=-6291092183811739596">Google Video</a>. It's just a basic overview of CSS, and like all rapid-fire on-the-spot TV interviews there wasn't much room for nuance and error-correction as we went, so pardon the stalling for time while trying to figure out how to explain things like the history of CSS... Still, I think it came off rather well, for an introduction of a style language to the more general audience who watch the show. (Ian Lloyd, you owe me a beer.)</p>
+</dd>
+</dl>
+
+ ]]></content:encoded>
+ <dc:subject>Events</dc:subject>
+ <dc:date>2008-02-28T16:29:29-08:00</dc:date>
+ </item>
+ <item>
+ <title>Third Party</title>
+ <link>http://mezzoblue.com/archives/2008/02/26/third_party/</link>
+ <description>
+ Ahead of the possibly-delayed iPhone/iPod Touch Software Development Kit, and the plethora of applications that will undoubtedly ensue, I thought I&apos;d do a quick run-down of some of the more interesting third-party applications that already exist, in an unofficial capacity....
+ </description>
+ <guid isPermaLink="false">1417@http://mezzoblue.com/</guid>
+ <content:encoded><![CDATA[
+ <p>Ahead of the <a href="http://www.businessweek.com/technology/ByteOfTheApple/blog/archives/2008/02/the_iphone_sdk.html">possibly-delayed</a> iPhone/iPod Touch Software Development Kit, and the plethora of applications that will undoubtedly ensue, I thought I'd do a quick run-down of some of the more interesting third-party applications that <em>already</em> exist, in an unofficial capacity.</p>
+
+<p>Since I had to jailbreak my phone in order to get it working outside the four countries where it has been officially launched, it was hardly a stretch to start playing around with the software that's being developed anyway, SDK or no. This could very well end up being a preview of some of the first applications that will be officially available once the SDK is released.</p>
+
+<p>My list below isn't comprehensive by any means; these are just some of the apps I think deserve mention due to their utility or polish. There's a lot of crap to wade through already, so I think it's worth calling out some of the gems. All these are available directly from Installer.app, provided you have also installed Community Sources.</p>
+
+
+<div class="image-holder-grid four-up"><div class="wrapper">
+ <a href="http://www.mezzoblue.com/i/articles/2008/feb/apollo-large.jpg">
+ <img src="http://www.mezzoblue.com/i/articles/2008/feb/apollo-sm.jpg" width="113" height="119" alt="Apollo" />
+ <span class="caption">Apollo</span>
+ </a>
+
+ <a href="http://www.mezzoblue.com/i/articles/2008/feb/fivedice-large.jpg">
+ <img src="http://www.mezzoblue.com/i/articles/2008/feb/fivedice-sm.jpg" width="113" height="119" alt="FiveDice" />
+ <span class="caption">FiveDice</span>
+ </a>
+
+ <a href="http://www.mezzoblue.com/i/articles/2008/feb/iano-large.jpg">
+ <img src="http://www.mezzoblue.com/i/articles/2008/feb/iano-sm.jpg" width="113" height="119" alt="iAno" />
+ <span class="caption">iAno</span>
+ </a>
+
+ <a href="http://www.mezzoblue.com/i/articles/2008/feb/imatrix-large.jpg">
+ <img src="http://www.mezzoblue.com/i/articles/2008/feb/imatrix-sm.jpg" width="113" height="119" alt="iMatrix" />
+ <span class="caption">iMatrix</span>
+ </a>
+
+ <a href="http://www.mezzoblue.com/i/articles/2008/feb/isolitaire-large.jpg">
+ <img src="http://www.mezzoblue.com/i/articles/2008/feb/isolitaire-sm.jpg" width="113" height="119" alt="iSolitaire" />
+ <span class="caption">iSolitaire</span>
+ </a>
+
+ <a href="http://www.mezzoblue.com/i/articles/2008/feb/labyrinth-large.jpg">
+ <img src="http://www.mezzoblue.com/i/articles/2008/feb/labyrinth-sm.jpg" width="113" height="119" alt="Labyrinth" />
+ <span class="caption">Labyrinth</span>
+ </a>
+
+ <a href="http://www.mezzoblue.com/i/articles/2008/feb/photoboard-large.jpg">
+ <img src="http://www.mezzoblue.com/i/articles/2008/feb/photoboard-sm.jpg" width="113" height="119" alt="Photoboard" />
+ <span class="caption">Photoboard</span>
+ </a>
+
+ <a href="http://www.mezzoblue.com/i/articles/2008/feb/sketches-large.jpg">
+ <img src="http://www.mezzoblue.com/i/articles/2008/feb/sketches-sm.jpg" width="113" height="119" alt="Sketches" />
+ <span class="caption">Sketches</span>
+ </a>
+</div></div>
+
+
+<p>The images above link to larger screenshots. I just <a href="http://flickr.com/photos/mezzoblue/2289229685/">recently updated</a> to firmware 1.1.3, and all of the existing iPhone Screenshot utilities don't seem to be working for me anymore. So pardon the grainy photos.</p>
+
+<h3>Apollo</h3>
+<p>There are a few native IM clients out at the moment, and obviously <a href="http://www.meebo.com/">Meebo</a> works just fine too. But I'm finding none of the native clients thus far are any good, or even good enough. <a href="http://code.google.com/p/apolloim/">Apollo</a> is the one I've been using occasionally when I need IM on my phone, but it still needs a lot of work. Active development stopped back in October, with the developers stating they're focusing on an official version once the SDK is released, so this may turn out to be one of the first official IM clients for the iPhone. Hopefully it will have seen some massive updates in the interim.</p>
+
+<p>Killer feature: IM over EDGE. It's not great for long chat sessions, but damn if it isn't useful to have the ability to ask an IM contact a quick question when out and about. (<a href="http://www.youtube.com/watch?v=3C1m7s31Dxo">Terrible video of Apollo in action</a>)</p>
+
+<h3>FiveDive</h3>
+<p>Just your average <a href="http://www.google.ca/search?q=yahtzee">Yahtzee</a> clone, but polished to a shiny finish. <a href="http://code.google.com/p/5dice/">FiveDice</a> is one of the few games worth having thus far, in my opinion.</p>
+
+<h3>iAno</h3>
+<p>Almost more of a proof of concept than a useful app at this point, iAno is still fun. It's a piano simulator that crams a full octave on screen at once, and can play any note between C3 on the low end and C7 on the high end. What makes iAno stand out is its use of Multi-touch; chords are possible, you can play three notes at the same time. Here's a video of <a href="http://www.youtube.com/watch?v=ycIjt-ykWRo">iAno in action</a>, and you've probably already seen it on the top phone in the <a href="http://www.youtube.com/watch?v=Mh0VX74alwk">iBand video</a> going around recently. If they end up adding a sequencer to the app, I'd expect to start seeing rather a lot of iPhone-generated music in the future.</p>
+
+<h3>iMatrix</h3>
+<p>My screenshot above is mostly useless, as the entire point of <a href="http://www.imatrix.lt/default.aspx?page=about">iMatrix</a> is what the iPhone's camera sees and I wasn't able to demo that. What it does is two-fold.</p>
+
+<p>First, if you happen to run across a <a href="http://en.wikipedia.org/wiki/QR_Code">QR Code</a> or other 2D code, you take a quick snapshot of it with the phone. Then iMatrix will process the data contained within, and automatically (with your permission) use the data to open a URL in Safari, add a contact to your address book, dial a phone number, or a few other actions.</p>
+
+<p>Second, it's able to generate 2D codes on the screen of the phone that can be read with scanners. Some of the applications this opens up: virtual ticketing, coupons, identifaction, and other purposes where simple authentication or data exchange is useful.</p>
+
+<p>The idea of 2D codes isn't unique to this application by any means, they've been around for over a decade. They're not common in North America yet, but given time and more devices that are able to process them, it seems like momentum's building. They may be as common as URLs in a few years. Here's a poorly-narrated video of <a href="http://www.youtube.com/watch?v=7hSSzkeQD7k">iMatrix contact exchange in action</a>.</p>
+
+<h3>iSolitaire</h3>
+<p>I don't really need to describe Solitaire, do I? This particular <a href="http://iphone.rustyredwagon.com/">version</a> just happens to look great, and makes fine use of the touch screen for dragging cards around. If it's not one of the first official solitaire apps, I'll be very surprised.</p>
+
+<h3>Labyrinth</h3>
+<p>You know the classic <a href="http://www.otherlandtoys.co.uk/labyrinth500.jpg">wooden labyrinth</a> puzzles? Yeah, it's just like those. Using the accelerometer, <a href="http://labyrinth.codify.se/video.html">Labyrinth</a> has you tilt the entire phone to guide the ball into the hole. It makes for a great demo of what having accelerometers in these devices can get you. Unlike most of the other apps in this list, Labyrinth is a demo; there's a full version available for purchase. The iPhone software market has already begun.</p>
+
+<h3>Photoboard</h3>
+<p>Remember that wow feeling after watching <a href="http://www.ted.com/index.php/talks/view/id/65">Jeff Han's TED demo</a> of a multi touch interface a few years back? Photoboard brings a bit of that to the iPhone/iPod Touch with its rotation and zoom ability. It's a fairly simple toy at this point, akin to a basic lightboard for photos. You pull in photos from your device's library and then use two fingers to rotate them, pinch zoom in and out, and layer them on the screen. It's cramped on the small screen, doesn't save your setup when exiting, and isn't really altogether useful yet, but it's a great tech demo and shows promise for what might be possible in the future. Here's a video of <a href="http://www.youtube.com/watch?v=PBzb1pIN05M">Photoboard in use.</a>
+
+<h3>Sketches</h3>
+<p>A basic note-taking and sketching app, <a href="http://sketchesapp.com/Home.html">Sketches</a> has a few basic drawing tools that turn your finger into a pencil. You can import photos from your library or the iPhone's camera and use them as a canvas, save your creations, email them, etc. Given the limitations of using your finger as an imprecise stylus, this feels like the perfect mix of basic utility without going too far into more advanced and harder to use features. There may be apps in the future that allow more complex image editing on the iPhone, but right now this seems like the furthest you'd <em>want</em> to go.</p>
+
+<p>Plus, to erase the image? You just shake the device. How cool is that? There's a reason the icon looks like an Etch-a-Sketch. Here's a video about using <a href="http://www.youtube.com/watch?v=z_8ua0YVlB8">Sketches' tools</a>.
+
+
+<h3>Others</h3>
+<p>This is just scratching the surface; it seems like every week there are a handful of new ones worth keeping. A few more notable applications:</p>
+
+<dl>
+ <dt>Categories</dt>
+ <dd>This is a recent discovery; it allows for folder/group creation on the iPhone/iPod's home screen. You choose an icon for the folder, then drag a few apps into it. To get at them later from the home screen, you tap the group, then a new menu pops up with those icons. Pretty much as you'd expect.</dd>
+
+ <dt>weDict</dt>
+ <dd>A reusable dictionary/lookup app. If I get it, and I'm not 100% sure I do, you plug in various installable dictionaries, and then use the application to search them. Right now I have French &gt; English and English &gt; French dictionaries, and if I look up "neige" it tells me that the French &gt; English dictionary thinks that word means "snow".</dd>
+
+ <dt>Books</dt>
+ <dd>A basic eBook reader. It's ugly, but it works. I've seen some public domain titles available for install, and a few newer CC-licensed works, like Cory Doctorow's books.</dd>
+
+ <dt>Evolution RGB</dt>
+ <dd>This is a little toy/game thing that doesn't make any sense at all when it first loads. In a nutshell, you have pre-defined elements like fire, water, plant, clay etc. You draw them onto the screen, then watch water interact with fire, fire interact with plant, etc. It's generative like the old-school evolution game from decades past, and setting up some opposing elements and watching them interact can be vaguely hypnotic. This is the best <a href="http://www.youtube.com/watch?v=ztujpEztZh4">video</a> I could find that demonstrates how it works. It won't win any beauty contests, but it's really worth taking a look.</dd>
+
+ <dt>NES, gpSPhone, Frotz, ScummVM</dt>
+ <dd>Various emulators for games past, of varying and questionable legitimacy. In order: <a href="http://www.youtube.com/watch?v=LVh429Dp7r0">Nintendo Entertainment System</a>, <a href="http://www.youtube.com/watch?v=-rCVfUbqZ-8">Game Boy Advance</a>, Infocom text adventure games like Zork, and the LucasArts Scumm system that powered classics like <a href="http://www.youtube.com/watch?v=V21ejSiNunc&feature=related">Monkey Island</a> and <a href="http://www.youtube.com/watch?v=V2ZVQ_5tolQ">Day of the Tentacle</a>. (I say "questionable legitimacy" because the practice of emulting ROMs is kind of a <a href="http://en.wikipedia.org/wiki/ROM_image#Legal_status_of_ROMs">legal grey area</a>.)</dd>
+
+ <dt>VNotes</dt>
+ <dd>A simple audio recorder that uses the iPhone's built-in mic. Something that ought to be officially available sooner or later, but isn't yet.</dd>
+</dl>
+
+<p><ins>Update:</ins> And it figures that as soon as I hit publish, I discovered that firmware 1.1.4 came out today. Not sure yet what it changes, if anything.</p>
+
+ ]]></content:encoded>
+ <dc:subject>Technology</dc:subject>
+ <dc:date>2008-02-26T13:00:00-08:00</dc:date>
+ </item>
+ <item>
+ <title>8 + 2 = &amp;#x263A;</title>
+ <link>http://mezzoblue.com/archives/2007/12/19/8_2_x2/</link>
+ <description>
+ Christmas came early: Internet Explorer 8 passes Acid2.
+ </description>
+ <guid isPermaLink="false">1415@http://mezzoblue.com/</guid>
+ <content:encoded><![CDATA[
+ <p>Okay, I lied. One more for 2007, but only because this is an early Christmas present from Microsoft, and when was the last time you got one of those?</p>
+
+<div class="image-holder-left medium">
+<img width="420" height="210" alt="Acid2 + IE8 = Smily Face" src="http://mezzoblue.com/i/articles/2007/dec19-ie8-acid2.jpg"/>
+</div>
+
+<p><a href="http://blogs.msdn.com/ie/archive/2007/12/19/internet-explorer-8-and-acid2-a-milestone.aspx">Internet Explorer 8 passes Acid2</a>.</p>
+
+<p>For as <a href="http://www.mezzoblue.com/archives/2005/07/28/ie7_css_upda/">much crap</a> as I've given the IE team over the years, I have to say I'm really happy with the path they're on after reading this today. Rendering the Acid2 test correctly is a huge leap forward ("<a href="http://blogs.msdn.com/ie/archive/2007/12/19/internet-explorer-8-and-acid2-a-milestone.aspx#6810394">IE8 Standards Mode</a>" or not).</p>
+
+<p>Last year, IE7 wasn't nearly the big expansive update I'd have hoped, but the improvements they made <a href="http://www.mezzoblue.com/archives/2006/03/23/ie7_details/">hit all the right notes</a> for me at the time. They seemed genuinely concerned about the problems that made our lives harder, and took steps to correct them. And testing against IE7 has been more good than bad; it's rare to get something working in Firefox and Safari that blows up in IE7. At most a few glitches here and there have required a bit of fixing, but nothing that has sucked away an entire afternoon like certain other past versions. (Now if they could just make IE6 go away once and for all, then we'd really be on to something...)</p>
+
+<p>Using the past updates as a starting point and extrapolating, I've been cautiously optimistic that IE8 would deliver much more of the same. The lack of news hadn't really been bugging me until I realized a few months ago that, oh yeah, they <em>have</em> been pretty silent lately haven't they? No doubt last week's legal proceedings have something to do with the timing, but today I think we're seeing the first signs of what I'd been hoping, and the associated <a href="http://channel9.msdn.com/showpost.aspx?postid=367207">video</a> suggests there's more to be revealed.</p>
+
+<p>Obviously Acid2 is just one test, and there are other technologies that will become more and more relevant in the coming years. HTML5, CSS3, and SVG come to mind. But for the first reveal of what we can expect from the new browser, this is a welcome start.</p>
+
+ ]]></content:encoded>
+ <dc:subject></dc:subject>
+ <dc:date>2007-12-19T15:12:23-08:00</dc:date>
+ </item>
+
+
+ </channel>
+</rss>
1,576 vendor/feed_me/spec/fixtures/real/mootools.xml
1,576 additions, 0 deletions not shown
46 vendor/feed_me/spec/fixtures/welformed.atom
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+ <title>Test feed</title>
+ <subtitle>Monkey test feed</subtitle>
+ <link href="http://imaginary.host/posts.atom" rel="self"/>
+ <link href="http://imaginary.host/posts" rel="alternate"/>
+ <id>tag:imaginary.host:nyheter</id>
+ <updated>2008-03-07T20:41:10Z</updated>
+ <generator>Roll your own</generator>
+ <author>
+ <name>Frank</name>
+ <uri>http://imaginary.host/students/frank</uri>
+ <email>frank@imaginary.host</email>
+ </author>
+ <entry>
+ <title>First title</title>
+ <link rel="alternate" href="http://imaginary.host/posts/3"/>
+ <id>tag:imaginary.host,2008-03-07:nyheter/3</id>
+ <updated>2008-03-07T20:41:10Z</updated>
+ <author>
+ <name>Jonas Nicklas</name>
+ <uri>http://imaginary.host/students/jnicklas</uri>
+ <email>jonas.nicklas@imaginary.host</email>
+ </author>
+ <content type="html">Here be content</content>
+ </entry>
+ <entry>
+ <title>Another title with no author</title>
+ <link rel="alternate" href="http://imaginary.host/posts/2"/>
+ <id>tag:imaginary.host,2008-03-07:nyheter/2</id>
+ <updated>2008-03-07T20:40:58Z</updated>
+ <content type="html">Moar content!</content>
+ </entry>
+ <entry>
+ <title>Third title with source</title>
+ <link rel="alternate" href="http://imaginary.host/posts/1"/>
+ <id>tag:imaginary.host,2008-03-07:nyheter/1</id>
+ <updated>2008-03-07T20:40:44Z</updated>
+ <author>
+ <name>Jonas Nicklas</name>
+ <uri>http://imaginary.host/students/jnicklas</uri>
+ <email>jonas.nicklas@imaginary.host</email>
+ </author>
+ <content type="html">Also Content!</content>
+ </entry>
+</feed>
56 vendor/feed_me/spec/fixtures/welformed.rss1
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+
+<rdf:RDF
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://purl.org/rss/1.0/">
+
+ <channel rdf:about="http://www.xml.com/xml/news.rss">
+ <title>XML.com</title>
+ <link>http://xml.com/pub</link>
+ <description>
+ XML.com features a rich mix of information and services
+ for the XML community.
+ </description>
+ <image rdf:resource="http://xml.com/universal/images/xml_tiny.gif" />
+ <items>
+ <rdf:Seq>
+ <rdf:li rdf:resource="http://xml.com/pub/2000/08/09/xslt/xslt.html" />
+ <rdf:li rdf:resource="http://xml.com/pub/2000/08/09/rdfdb/index.html" />
+ </rdf:Seq>
+ </items>
+ <textinput rdf:resource="http://search.xml.com" />
+ </channel>
+
+ <image rdf:about="http://xml.com/universal/images/xml_tiny.gif">
+ <title>XML.com</title>
+ <link>http://www.xml.com</link>
+ <url>http://xml.com/universal/images/xml_tiny.gif</url>
+ </image>
+
+ <item rdf:about="http://xml.com/pub/2000/08/09/xslt/xslt.html">
+ <title>Processing Inclusions with XSLT</title>
+ <link>http://xml.com/pub/2000/08/09/xslt/xslt.html</link>
+ <description>
+ Processing document inclusions with general XML tools can be
+ problematic. This article proposes a way of preserving inclusion
+ information through SAX-based processing.
+ </description>
+ </item>
+
+ <item rdf:about="http://xml.com/pub/2000/08/09/rdfdb/index.html">
+ <title>Putting RDF to Work</title>
+ <link>http://xml.com/pub/2000/08/09/rdfdb/index.html</link>
+ <description>
+ Tool and API support for the Resource Description Framework
+ is slowly coming of age. Edd Dumbill takes a look at RDFDB,
+ one of the most exciting new RDF toolkits.
+ </description>
+ </item>
+
+ <textinput rdf:about="http://search.xml.com">
+ <title>Search XML.com</title>
+ <description>Search XML.com's XML collection</description>
+ <name>s</name>
+ <link>http://search.xml.com</link>
+ </textinput>
+</rdf:RDF>
50 vendor/feed_me/spec/fixtures/welformed.rss2
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<rss version="2.0">
+ <channel>
+ <title>Lift Off News</title>
+ <link>http://liftoff.msfc.nasa.gov/</link>
+ <description>Liftoff to Space Exploration.</description>
+ <language>en-us</language>
+ <pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate>
+ <lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate>
+ <docs>http://blogs.law.harvard.edu/tech/rss</docs>
+ <generator>Weblog Editor 2.0</generator>
+ <managingEditor>editor@example.com (Mary Jo)</managingEditor>
+ <webMaster>webmaster@example.com (John Doe)</webMaster>
+ <ttl>5</ttl>
+ <item>
+ <title>Star City</title>
+ <link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link>
+ <author>da_man@example.com (Chuck Norris)</author>
+ <description>This is content</description>
+ <pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>
+ <guid>http://liftoff.msfc.nasa.gov/2003/06/03.html#item573</guid>
+ </item>
+ <item>
+ <title>Space Exploration</title>
+ <link>http://liftoff.msfc.nasa.gov/</link>
+ <description>Sky watchers in Europe, Asia, and parts of Alaska and Canada
+ will experience a partial eclipse of the Sun on Saturday, May 31st.</description>
+ <pubDate>Fri, 30 May 2003 11:06:42 GMT</pubDate>
+ <guid>http://liftoff.msfc.nasa.gov/2003/05/30.html#item572</guid>
+ </item>
+ <item>
+ <title>The Engine That Does More</title>
+ <link>http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp</link>
+ <description>Before man travels to Mars, NASA hopes to design new engines
+ that will let us fly through the Solar System more quickly. The proposed
+ VASIMR engine would do that.</description>
+ <pubDate>Tue, 27 May 2003 08:37:32 GMT</pubDate>
+ <guid>http://liftoff.msfc.nasa.gov/2003/05/27.html#item571</guid>
+ </item>
+ <item>
+ <title>Astronauts' Dirty Laundry</title>
+ <link>http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp</link>
+ <description>Compared to earlier spacecraft, the International Space
+ Station has many luxuries, but laundry facilities are not one of them.
+ Instead, astronauts have other options.</description>
+ <pubDate>Tue, 20 May 2003 08:56:02 GMT</pubDate>
+ <guid>http://liftoff.msfc.nasa.gov/2003/05/20.html#item570</guid>
+ </item>
+ </channel>
+</rss>
182 vendor/feed_me/spec/item_parser_spec.rb
@@ -0,0 +1,182 @@
+require File.join( File.dirname(__FILE__), "spec_helper" )
+
+require 'feed_me'
+
+describe FeedMe::ItemParser do
+
+ before :each do
+ @atom_feed = FeedMe::FeedParser.open(fixture('welformed.atom'))
+ @atom = FeedMe::ItemParser.build(@atom_feed.root_node.search('/entry').first, :atom, @atom_feed)
+ @rss2_feed = FeedMe::FeedParser.open(fixture('welformed.rss2'))
+ @rss2 = FeedMe::ItemParser.build(@rss2_feed.root_node.search('/item').first, :rss2, @rss2_feed)
+ end
+
+ describe '#to_hash' do
+ it "should serialize the parsed properties to a hash" do
+
+ end
+ end
+
+ describe '#title' do
+ it "should be valid for an atom feed" do
+ @atom.title.should == "First title"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.title.should == "Star City"
+ end
+ end
+
+ describe '#content' do
+ it "should be valid for an atom feed" do
+ @atom.content.should == "Here be content"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.content.should == "This is content"
+ end
+ end
+
+ describe '#item_id' do
+ it "should be valid for an atom feed" do
+ @atom.item_id.should == "tag:imaginary.host,2008-03-07:nyheter/3"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.item_id.should == "http://liftoff.msfc.nasa.gov/2003/06/03.html#item573"
+ end
+ end
+
+ describe '#updated_at' do
+ it "should be valid for an atom feed" do
+ @atom.updated_at.should == Time.utc( 2008, 3, 7, 20, 41, 10 )
+ end
+
+ it "should be nil for an rss2 feed" do
+ @rss2.updated_at.should be_nil
+ end
+ end
+
+ describe '#url' do
+ it "should be valid for an atom feed" do
+ @atom.url.should == "http://imaginary.host/posts/3"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.url.should == "http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp"
+ end
+ end
+
+ describe '#format' do
+ it "should be :atom for an atom feed" do
+ @atom.format.should == :atom
+ end
+
+ it "should be :rss2 for an rss2 feed" do
+ @rss2.format.should == :rss2
+ end
+ end
+
+ describe '#author.name' do
+ it "should be valid for an atom feed" do
+ @atom.author.name.should == "Jonas Nicklas"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.author.name.should == "Chuck Norris"
+ end
+ end
+
+ describe '#author.email' do
+ it "should be valid for an atom feed" do
+ @atom.author.email.should == "jonas.nicklas@imaginary.host"
+ end
+
+ it "should be valid for an rss2 feed" do
+ @rss2.author.email.should == "da_man@example.com"
+ end
+ end
+