<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>Rakefile</filename>
    </added>
    <added>
      <filename>TODO</filename>
    </added>
    <added>
      <filename>bin/gnip</filename>
    </added>
    <added>
      <filename>doc/api.html</filename>
    </added>
    <added>
      <filename>gemspec.rb</filename>
    </added>
    <added>
      <filename>gnip-1.0.0.gem</filename>
    </added>
    <added>
      <filename>lib/gnip/api.rb</filename>
    </added>
    <added>
      <filename>lib/gnip/arguments.rb</filename>
    </added>
    <added>
      <filename>lib/gnip/blankslate.rb</filename>
    </added>
    <added>
      <filename>lib/gnip/list.rb</filename>
    </added>
    <added>
      <filename>lib/gnip/options.rb</filename>
    </added>
    <added>
      <filename>lib/gnip/orderedhash.rb</filename>
    </added>
    <added>
      <filename>lib/gnip/resource.rb</filename>
    </added>
    <added>
      <filename>lib/gnip/template.rb</filename>
    </added>
    <added>
      <filename>lib/gnip/util.rb</filename>
    </added>
    <added>
      <filename>sample/data/activity.yml</filename>
    </added>
    <added>
      <filename>test/auth.rb</filename>
    </added>
    <added>
      <filename>test/config.yml</filename>
    </added>
    <added>
      <filename>test/data/activity.xml</filename>
    </added>
    <added>
      <filename>test/data/activity_only_required.xml</filename>
    </added>
    <added>
      <filename>test/data/activity_with_payload.xml</filename>
    </added>
    <added>
      <filename>test/data/activity_with_place.xml</filename>
    </added>
    <added>
      <filename>test/data/activity_with_place_wo_bounds.xml</filename>
    </added>
    <added>
      <filename>test/data/activity_with_unbounded_media_urls.xml</filename>
    </added>
    <added>
      <filename>test/data/activity_without_bounds.xml</filename>
    </added>
    <added>
      <filename>test/helper.rb</filename>
    </added>
    <added>
      <filename>test/helper.rb.bak</filename>
    </added>
    <added>
      <filename>test/integration/auth.rb</filename>
    </added>
    <added>
      <filename>test/integration/publisher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller/helpers.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller/macros.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller/matchers.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller/matchers/assign_to_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller/matchers/filter_param_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller/matchers/render_with_layout_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller/matchers/respond_with_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller/matchers/route_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller/matchers/set_session_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_mailer.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_mailer/assertions.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_view.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/action_view/macros.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/assertions.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/helpers.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/macros.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/allow_value_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/association_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/have_db_column_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/have_index_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/active_record/matchers/validation_matcher.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/assertions.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/autoload_macros.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/context.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/helpers.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/macros.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/private_helpers.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/proc_extensions.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/rails.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/rspec.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/tasks.rb</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/tasks/list_tests.rake</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/tasks/yaml_to_shoulda.rake</filename>
    </added>
    <added>
      <filename>test/lib/shoulda/test_unit.rb</filename>
    </added>
    <added>
      <filename>test/lib/xmlsimple.rb</filename>
    </added>
    <added>
      <filename>test/loader.rb</filename>
    </added>
    <added>
      <filename>test/unit/activity.rb</filename>
    </added>
    <added>
      <filename>test/unit/util.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -1,163 +1,144 @@
-Welcome to the Gnip Ruby convenience library!
-
-= Overview =
-This library provides a Ruby API for accessing Gnip web services.  This library supports
-two types of Gnip users -- publishers and consumers.  
-
-= Dependencies = 
-This library has two runtime dependencies:
-  - rubygems
-  - xml-simple
-
-There are also two test dependencies:
-  - rake
-  - rspec
-
-Instructions for installing the libraries in Ruby are here:
-
-  http://www.rubygems.org/read/book/1 
-
-= Installing = 
-
-To build and install the library, run: 
-
-  $&gt; rake gem
-  $&gt; gem install pkg/gnip-2.0.6.gem --local
-
-Once installed, the Ruby library can be included in a Ruby script with:
-
-  require 'gnip'
-
-= Testing = 
-
-To run the tests for the library, do:
-
-  $&gt; rake spec
-
-= Debugging =
-The Gnip Ruby library uses the Ruby Logger to send messages to the console.
-By default, the Logger is configured to send messages at the ERROR level 
-and above; when developing a Gnip client, however, it can be helpful to see
-more verbose logging messages from the library.  The Logger used in the library
-is provided by the Gnip::Config instance.  To set a logging level on the Logger,
-call:
-
-  config = Gnip::Config(&quot;me@mydomain.com&quot;, &quot;my-password&quot;)
-  config.logger.level = Logger::DEBUG
-
-and remember to re-initialize any Gnip::Connection objects with this Gnip::Config
-instance.
-
-= Consuming data from Gnip = 
-
-Example 1: Retrieve all recent activities for a publisher
-
-As a consumer one thing you might be interested in immediately is to
-grab recent activity at a particular publisher.  To do this you must
-create a connection to Gnip using your user name and password.  Once
-that connection is established you can get the publisher and request
-it's activities stream.  
-
-    require 'rubygems'
-    require 'gnip' 
-
-    gnip = Gnip::Connection.new(Gnip::Config.new(&quot;me@mydomain.com&quot;, &quot;my-password&quot;))
-
-    _,digg = gnip.get_publisher('digg')
-    activities = gnip.publisher_activities_stream(digg)
-    puts &quot;Received #{activities != nil ? activities.size : 0} activities&quot;
-
-Example 2: Retrieve all activities for a publisher around a specific time
-
-Some times you will want to get activity information from before now.
-Doing this look much like getting the recent activity, except that you
-past a time when getting the activity stream.  This will return the
-activity stream as it existed around that time.  The results will be
-include some activities before and after the time you specify.
-
-    require 'rubygems'
-    require 'gnip' 
-
-    gnip = Gnip::Connection.new(Gnip::Config.new(&quot;me@mydomain.com&quot;, &quot;my-password&quot;))
-
-    _,digg = gnip.get_publisher('digg')
-    activities = gnip.publisher_activities_stream(digg, Time.now - 3600)  # 1 hour ago
-    puts &quot;Received #{activities != nil ? activities.size ? 0} activities&quot;
-
-Example 3: Create an activity stream that includes only activities done by users you care about
-
-If you would like to filter a set of publishers by the user that
-performed the activity you may create a filter to do so.  Once
-created a filter's activity stream is retrieved much like a
-publishers.  Activity that has already occured will not be included in
-a filter.  Therefore any new filter will be empty until some
-new matching activity has occured.
-
-    require 'rubygems'
-    require 'gnip' 
-
-    gnip = Gnip::Connection.new(Gnip::Config.new(&quot;me@mydomain.com&quot;, &quot;my-password&quot;))
-
-    digg = Gnip::Publisher.new('digg')
-    my_filter = Gnip::Filter.new('my-filter')
-    my_filter.add_a_rule(Gnip::Rule.new('actor', 'Burento'))
-
-    gnip.create_filter(digg, my_filter)
+NAME
+  gnip
+
+SYNOPSIS
+  gnip (config|ping|clock_drift|encode|decode|version|publisher|activity|notification|filter) [options]+
+
+DESCRIPTION
+  gnip.rb is *the* clinically proven, most effective way, to keep your ruby
+  codez on the pulse of the freaking interwebs.  gnip.rb slings rest-fu
+  around like mr.t slings suckas while rocking out to deep-house and pouring
+  wine for the ladeez.  gnip.rb brings you power and speed with a silkly
+  smooth mixture of equal parts restclient, nokogiri, tagz, and threadify.
+  sit back and let gnip.rb bring the very tastiest portions of the tubes
+  down onto your very own hard drive for your dining pleasure.
+  
+  we have what you are looking for.
+
+PARAMETERS
+  --username=[username], -U (0 ~&gt; username) 
+  --password=[password], -P (0 ~&gt; password) 
+  --scope=[scope], -S (0 ~&gt; scope=gnip) 
+  --name=[name], -N (0 ~&gt; name) 
+  --ago=[ago], -a (0 ~&gt; integer(ago)) 
+  --thru=[thru], -t (0 ~&gt; thru) 
+  --bucket=[bucket], -b (0 ~&gt; bucket) 
+  --filter=[filter], -f (0 ~&gt; filter) 
+  --style=[style], -s (0 ~&gt; style) 
+  --force=[force] (0 ~&gt; boolean(force)) 
+  --log=[log], -l (0 ~&gt; log) 
+  --input=[input], -i (0 ~&gt; input) 
+  --help, -h 
+
+AUTHOR
+  ara.t.howard@gmail.com
+
+EXAMPLES
+  @quick
+    . sup on digg recently?
+        gnip notification digg --ago=30 --thru=now
+  
+    . and twitter right now?
+        gnip notification twitter
+  
+    . stalk someone on tumblr
+        gnip filter rule create tumblr catch-him-in-the-act actor:boyfriend
+        gnip activity digg --filter=catch-him-in-the-act --bucket=all
+  
+  @setup
+    . dump the current gnip config
+        gnip config
+  
+    . create a ~/.gnip.yml config file
+        gnip config generate
     
-    _,publisher,activities = gnip.filter_activities_stream(digg, my_filter)
-    puts &quot;Received #{activities != nil ? activities.size : 0} activities&quot;
-   
-Note, a Gnip Filter only collects activities that match the Filter's rules.  
-As such, the &quot;activities&quot; value returned from teh filter_activities_stream
-may be empty if the rules haven't matched an activity for that Publisher.
-
-Example 4: Delete a filter
-
-If you decide you no longer need a filter you have created in the
-past you can remove it.
-
-    require 'rubygems'
-    require 'gnip' 
-
-    gnip = Gnip::Connection.new(Gnip::Config.new(&quot;me@mydomain.com&quot;, &quot;my-password&quot;))
-    digg = Gnip::Publisher.new('digg')
-    _,my_filter = gnip.get_filter(digg, &quot;my-filter&quot;)
-
-    gnip.remove_filter(digg, my_filter)
-
-= Publishing data to Gnip = 
-
-Example 1: Create a publisher and publish some activities
-
-If you are interested in publishing activity you will need to create a
-publisher.  Once the publisher resource is created, activities can be
-published in it's activity stream.
-
-    require 'rubygems'
-    require 'gnip' 
-
-    gnip = Gnip::Connection.new(Gnip::Config.new(&quot;me@mydomain.com&quot;, &quot;my-password&quot;))
-
-    my_publisher = Gnip::Publisher.new('myservice')
-
-    gnip.create(my_publisher)
-
-    activity = Gnip::Activity.new('joe', 'post', Time.now, 'http://mydomain.com/joe/my-new-blog-post')
-
-    gnip.publish(my_publisher, activity)
-
-= Contributing =
- 
-Contributions to this library are welcome.
-
-Source         :: git://github.com/gnip/gnip-ruby.git
-Community Site :: {gnip-community}[http://groups.google.com/group/gnip-community]
-Mailing List   :: gnip-community@googlegroups.com
-
-To get started create a clone of the main repository,
-&lt;git://github.com/gnip/gnip-ruby.git&gt;, and hack away.  Feel free
-discuss any changes you are making on the mailing list to get feedback 
-from the other users.  Once you are ready to publish your changes
-you can send them to the mailing list or, if you are using GitHub,
-send a pull request to the owner of the main repositiory.
+    . ping gnip to verify your config
+        gnip ping
+  
+    . show gnip clock shift from your local time
+        gnip clock_drift
+  
+  @publisher
+    . list gnip publishers
+        gnip publisher list
+  
+    . list publishers in the /my scope
+        gnip publisher list --scope=my
+  
+    . create a publisher name 'publisher-name' in the /my scope with actor and keyword rules
+        gnip publisher create my-publisher actor keyword --scope=my
+  
+    . show a publisher by name
+        gnip publisher show my-publisher
+  
+    . delete a publisher in the /my scope
+        gnip publisher delete my-publisher --scope=my
+  
+  
+  @activity | @notification
+    . list the activity stream of a publisher
+        gnip notification stream digg
+  
+    . list the activity of a publisher
+        gnip notification list digg
+  
+    . list the activity of a publisher 10 minutes ago
+        gnip notification list digg --ago=10
+  
+    . list all activity of a publisher starting 60-10 minutes (parallel requests)
+        gnip notification list digg --ago=60 --thru=10
+  
+    . list all possible activity of a publisher
+        gnip notification list digg --bucket=all
+  
+    . list the filtered activity of a publisher
+        gnip activity list digg --filter=my-filter-name --ago=60 --thru=42
+  
+  @filter
+    . create a publisher filter for two actors
+        gnip filter create digg my-filter-name --full_data=true actor:mohadata actor:livefree12
+  
+    . create a publisher filter from a file full of many rules (same format as command-line, one per line)
+        gnip filter create digg my-filter-name --input=file-full-of-rules.txt
+  
+    . append rules to an existing publisher filter
+        gnip filter rule create digg my-filter-name actor:foobar actor:barfoo
+  
+    . append rules to an existing publisher filter in bulk
+        gnip filter rule create digg my-filter-name --input=input
+  
+    . list a specific rule for a publisher's filter
+        gnip filter rule list digg my-filter-name actor:livefree12
+  
+    . delete a specific rule from an existing publisher filter
+        gnip filter rule delete digg my-filter-name actor:barfoo
+  
+    . list your filters on a publisher
+        gnip filter list digg
+  
+  @publish
+    . push a yml activity to a publisher
+        gnip activity publish my-publisher ./sample/data/activity.yml --scope=my
+  
+    . scan for what you just posted (roughly)
+        gnip activity list my-publisher --scope=my --ago=2 --thru=now
+  
+  @utility
+    . encode data
+        gnip encode 'some content'
+  
+    . decode data
+        gnip encode 'some content' | gnip decode -
+  
+    . round trip data
+        echo 'some content' | gnip encode - | gnip decode -
+
+LIBRARY
+  see this program, samples/*,  and test/**/**, for usage
+
+URI
+  http://gnip.com
+
+INSTALL
+  $sudo gem install gnip
 </diff>
      <filename>README</filename>
    </modified>
    <modified>
      <diff>@@ -1,42 +1,71 @@
-require 'rubygems'
-require 'base64'
-require 'xmlsimple'
-require 'net/http'
-require 'net/https'
-require 'zlib'
-require 'time'
-require 'logger'
-require 'cgi'
-require 'pathname'
-require Pathname(__FILE__).dirname + 'ext/time'
-
-class Gnip
-  class &lt;&lt; self
-    attr_accessor :connection
-
-    # @return [Logger]   The logger that is used by this library.
-    def logger    
-      if connection
-        connection.config.logger
-      else
-        # Fake a logger up so that we can muddle through until we are configured properly.
-        l = Logger.new(STDERR)
-        l.level = Logger::INFO
-        return l
-      end
-    end
+# built-in libs
+#
+  require 'yaml'
+  require 'time'
+  require 'zlib'
+  require 'enumerator'
+  require 'base64'
+  # require 'erb'
+
+# gems and third party libs
+#
+  begin
+    require 'rubygems'
+    gem 'tagz', '&gt;= 5.0.1'
+  rescue LoadError
+    'oh noes!'
+  end
+
+  begin
+    require 'restclient'
+  rescue
+    abort 'sudo gem install rest-client'
+  end
+
+  begin
+    require 'nokogiri'
+  rescue
+    abort 'sudo gem install nokogiri #=&gt; depends on libxml + libxslt'
   end
 
-  def self.header_xml
-    '&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;'
+  begin
+    require 'tagz'
+  rescue
+    abort 'sudo gem install tagz'
   end
 
-  class Gnip::Base
+  begin
+    require 'threadify'
+  rescue
+    abort 'sudo gem install threadify'
   end
 
-  dir = File.dirname(__FILE__)
-  Dir[&quot;#{dir}/gnip/*.rb&quot;].each do |file|
-    require file
+# gnip libs
+#
+  module Gnip
+    Version = '1.0.0'
+
+    def version
+      Gnip::Version
+    end
+
+    def libdir(*args)
+      @libdir ||= File.expand_path(__FILE__).sub(/\.rb$/,'')
+      args.empty? ? @libdir : File.join(@libdir, *args)
+    end
+    extend self
   end
-end
 
+  require Gnip.libdir('util')
+  require Gnip.libdir('orderedhash')
+  # require Gnip.libdir('blankslate')
+  require Gnip.libdir('arguments')
+  require Gnip.libdir('options')
+  require Gnip.libdir('list')
+  require Gnip.libdir('config')
+  require Gnip.libdir('resource')
+  # require Gnip.libdir('template')
+  require Gnip.libdir('publisher')
+  require Gnip.libdir('filter')
+  require Gnip.libdir('activity')
+  require Gnip.libdir('api')</diff>
      <filename>lib/gnip.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,143 +1,663 @@
-class Gnip::Activity
-
-  attr_reader :actor, :at, :url, :action, :tos, :regardingURL, :source, :tags, :payload
-
-  def initialize(actor, action, at = Time.now, url = nil, tos = [], regardingURL = nil, source = nil, tags = [], payload = nil)
-    @actor = actor
-    @action = action
-    if (at.class == Time)
-      @at = at.xmlschema
-    else
-      @at = at unless Time.xmlschema(at).nil?
-    end
-    @url = url
-    @tos = tos
-    @regardingURL = regardingURL
-    @source = source
-    @tags = tags
-    @payload = payload
-  end
+module Gnip
+  class Activity
 
-  def to_xml()
-    the_hash = self.to_hash
-    XmlSimple.xml_out(the_hash, {'RootName' =&gt; ''})
-  end
+    def Activity.list_from_xml(xml, &amp;block)
+      list = []
+      parse_xml(xml) do |activity|
+        block ? block.call(activity) : list.push(activity)
+      end
+      block ? nil : list
+    end
 
-  def to_hash()
-    result = {}
-    result['at'] = [@at]
-    result['action'] = [@action]
-    result['actor'] = [@actor] if @actor
-    result['url'] = [@url] if @url
-    result['to'] = @tos if @tos
-    result['regardingURL'] = [@regardingURL] if @regardingURL
-    result['source'] = [@source] if @source
-    result['tag'] = @tags if @tags
-    result['payload'] = @payload.to_hash if @payload
-
-    {'activity' =&gt; result }
-  end
+    def Activity.from_xml(xml)
+      parse_xml(xml){|activity| return activity}
+      nil
+    end
 
-  def ==(another)
-    another.instance_of?(self.class) &amp;&amp; another.at == at &amp;&amp; another.url == url &amp;&amp; another.action == action &amp;&amp; another.actor == actor
-  end
+    def Activity.parse_xml(xml, &amp;block)
+      doc = Nokogiri::XML.parse(xml)
+      list = []
+      selectors = '*/activities', 'activity'
+      selectors.each do |selector|
+        search = doc.search(selector)
+        next unless search.size &gt; 0
+        search.each do |node|
+          activity = Activity.from_node(node)
+          block ? block.call(activity) : list.push(activity)
+        end
+      end
+      block ? nil : list
+    end
 
-  alias eql? ==
+    def Activity.from_node(node)
+      activity = new
 
-  def self.from_hash(hash)
-    return if hash.nil? || hash.empty?    
-    payload = Gnip::Payload.from_hash(hash['payload'].first) if hash['payload']
-    Gnip::Activity.new(first(hash['actor']), first(hash['action']), first(hash['at']),
-             first(hash['url']), hash['to'], first(hash['regardingURL']), first(hash['source']), hash['tag'], payload)
-  end
+      if value = node.at('at')
+        activity.at = value.content
+      end
+      if value = node.at('action')
+        activity.action = value.content
+      end
+      if value = node.at('activityID')
+        activity.activity_id = value.content
+      end
+      if value = node.at('URL')
+        activity.url = value.content
+      end
+      if values = node.search('source')
+        values.each{|value| activity.sources &lt;&lt; value.content}
+      end
+      if values = node.search('keyword')
+        values.each{|value| activity.keywords &lt;&lt; value.content}
+      end
+      if places = node.search('place')
+        places.each do |value|
+          place = activity.places.build
+          %w( point elev floor featuretypetag featurename relationshiptag ).each do |tag|
+            tag_value = value.at(tag)
+            place.send(&quot;#{ tag }=&quot;, tag_value.content) if tag_value
+          end
+        end
+      end
+      if values = node.search('actor')
+        values.each do |value|
+          actor = activity.actors.build
+          actor.content = value.content
+          actor.meta_url = value['metaURL']
+          actor.uid = value['uid']
+        end
+      end
+      if values = node.search('destinationURL')
+        values.each do |value|
+          destination_url = activity.destination_urls.build
+          destination_url.content = value.content
+          destination_url.meta_url = value['metaURL']
+        end
+      end
+      if values = node.search('tag')
+        values.each do |value|
+          tag = activity.tags.build
+          tag.content = value.content
+          tag.meta_url = value['metaURL']
+        end
+      end
+      if values = node.search('to')
+        values.each do |value|
+          to = activity.tos.build
+          to.content = value.content
+          to.meta_url = value['metaURL']
+        end
+      end
+      if values = node.search('regardingURL')
+        values.each do |value|
+          regarding_url = activity.regarding_urls.build
+          regarding_url.content = value.content
+          regarding_url.meta_url = value['metaURL']
+        end
+      end
+      if payload_node = node.at('payload')
+        activity.payload! do |payload|
+          if title_node = payload_node.at('title')
+            payload.title = title_node.content
+          end
+          if body_node = payload_node.at('body')
+            payload.body = body_node.content
+          end
+          if raw_node = payload_node.at('raw')
+            payload.raw = payload.decode(raw_node.content)
+          end
+          if media_url_nodes = payload_node.search('mediaURL')
+            media_url_nodes.each do |media_url_node|
+              media_url = payload.media_urls.build
+              media_url.content = media_url_node.content
+              media_url.height = media_url_node['height']
+              media_url.width = media_url_node['width']
+              media_url.duration = media_url_node['duration']
+              media_url.mime_type = media_url_node['mimeType']
+              media_url.type = media_url_node['type']
+            end
+          end
+        end
+      end
 
-  def self.from_xml(document)
-    hash = XmlSimple.xml_in(document)
-    self.from_hash(hash)
-  end
+      activity
+    end
 
-  def self.list_to_xml(activity_list)
-    activity_list = [] if activity_list.nil?
-    return XmlSimple.xml_out(activity_list.collect { |activity| activity.to_hash}, {'RootName' =&gt; 'activities', 'AnonymousTag' =&gt; nil, 'XmlDeclaration' =&gt; Gnip.header_xml})
-  end
+    def Activity.from_hash(*args)
+      args, options = Gnip.args_for(args)
 
-  def self.list_from_xml(activities_xml)
-    return [] if activities_xml.nil?
-    activities_list = XmlSimple.xml_in(activities_xml)
-    publisher_name = activities_list['publisher']
-    publisher = Gnip::Publisher.new(publisher_name) if publisher_name
-    activities = (activities_list.empty? ? [] : activities_list['activity'].collect { |activity_hash| Gnip::Activity.from_hash(activity_hash)})
-    if (publisher.nil?)
-      return activities
-    else
-      return publisher, activities
-    end
-  end
+      activity = new
 
-  class Gnip::Activity::Builder
+      %w(
+        at
+        action
+        activity_id
+        url
+        sources
+        keywords
+        places
+        actors
+        destination_urls
+        tags
+        tos
+        regarding_urls
+        payload
+      ).each do |opt|
+        if options.hasopt?(opt)
+          activity.send(&quot;#{ opt }=&quot;, options.getopt(opt))
+        end
+      end
 
-    def initialize(action, at = Time.now)
-      @action = action
-      @at = at
-      @tags = []
-      @tos = []
+      activity
     end
 
-    def actor(actor)
-      @actor = actor
-      self
+    def Activity.from_yaml(string)
+      string = string.read if string.respond_to?(:read)
+      to_hash(YAML.load(string))
     end
 
-    def url(url)
-      @url = url
-      self
+    Attributes = []
+
+    Attributes &lt;&lt; 'activity_id'
+      def activity_id
+        @activity_id ||= nil
+      end
+      def activity_id= value
+        @activity_id = value.to_s
+      end
+
+    Attributes &lt;&lt; 'at'
+      def at
+        @at ||= nil
+      end
+      def at= value
+        @at = Time === value ? value : Time.parse(value.to_s).utc #.iso8601(3)
+      end
+
+    Attributes &lt;&lt; 'action'
+      def action
+        @action ||= nil
+      end
+      def action= value
+        @action = value.to_s
+      end
+
+    Attributes &lt;&lt; 'url'
+      def url
+        @url ||= nil
+      end
+      def url= value
+        @url = value.to_s
+      end
+
+    Attributes &lt;&lt; 'sources'
+      def sources
+        @sources ||= List.of(String)
+      end
+      def sources= value
+        sources.replace(value)
+      end
+
+    Attributes &lt;&lt; 'keywords'
+      def keywords
+        @keywords ||= List.of(String)
+      end
+      def keywords= value
+        keywords.replace(value)
+      end
+
+    Attributes &lt;&lt; 'places'
+      def places
+        @places ||= List.of(Place)
+      end
+      def places= value
+        places.replace(value)
+      end
+
+    Attributes &lt;&lt; 'actors'
+      def actors
+        @actors ||= List.of(Actor)
+      end
+      def actors= value
+        actors.replace(value)
+      end
+
+    Attributes &lt;&lt; 'destination_urls'
+      def destination_urls
+        @destination_urls ||= List.of(DestinationURL)
+      end
+      def destination_urls= value
+        destination_urls.replace(value)
+      end
+
+    Attributes &lt;&lt; 'tags'
+      def tags
+        @tags ||= List.of(Tag)
+      end
+      def tags= value
+        tags.replace(value)
+      end
+
+    Attributes &lt;&lt; 'tos'
+      def tos
+        @tos ||= List.of(To)
+      end
+      def tos= value
+        tos.replace(value)
+      end
+
+    Attributes &lt;&lt; 'regarding_urls'
+      def regarding_urls
+        @regarding_urls ||= List.of(RegardingURL)
+      end
+      def regarding_urls= value
+        regarding_urls.replace(value)
+      end
+
+# TODO - extend this pattern to other elements
+#
+    Attributes &lt;&lt; 'payload'
+      def payload(*args, &amp;block)
+        return payload!(*args, &amp;block) if block
+        @payload ||= nil
+      end
+      def payload!(*args, &amp;block)
+        @payload = Payload.for(*args)
+        block ? block.call(@payload) : @payload
+      end
+      def payload= value
+        @payload = Payload.for(value)
+      end
+
+    def initialize(options = {})
+      options = Gnip.options_for(options)
+      options.each{|key, value| send(&quot;#{ key }=&quot;, value)}
     end
 
-    def tos(tos = [])
-      @tos = tos
-      self
+    include Tagz
+    def to_xml(*args)
+      args, options = Gnip.args_for(args)
+      doc = args.shift
+
+      tagz(doc) {
+        activity_{
+          at_{ at.utc.iso8601(3) }
+          action_{ action.to_s }
+    
+          if activity_id
+            activityID_{ activity_id }
+          end
+          if url
+            URL_{ url }
+          end
+          if sources
+            sources.each{|source| source_{ source } }
+          end
+          if keywords
+            keywords.each{|keyword| keyword_{ keyword } }
+          end
+          if places
+            places.each{|place| place.to_xml(tagz)}
+          end
+          if actors
+            actors.each{|actor| actor.to_xml(tagz)}
+          end
+          if destination_urls
+            destination_urls.each{|destination_url| destination_url.to_xml(tagz)}
+          end
+          if tags
+            tags.each{|tag| tag.to_xml(tagz)}
+          end
+          if tos
+            tos.each{|to| to.to_xml(tagz)}
+          end
+          if regarding_urls
+            regarding_urls.each{|regarding_url| regarding_url.to_xml(tagz)}
+          end
+          if payload
+            payload.to_xml(tagz)
+          end
+        }
+      }
     end
 
-    def to(to)
-      @tos.push(to)
-      self
+    class Place
+      def Place.for(*args, &amp;block)
+        arg = args.first if(args.size == 1 and args.first.is_a?(Place))
+        new(*args, &amp;block)
+      end
+
+      def initialize(options = {}, &amp;block)
+        options.each{|key, value| send &quot;#{ key }=&quot;, value}
+      end
+
+      class Point
+        def Point.for(*args, &amp;block)
+          new(*args, &amp;block)
+        end
+
+        attr_accessor :lat
+        attr_accessor :lon
+
+        def initialize(*args)
+          @lat, @lon = Point.parse(*args)
+        end
+
+        def Point.parse(*args)
+          string = args.join(' ')
+          coords = string.strip.split(%r/\s+/, 2).map{|coord| Util.number_for(coord)}
+          raise ArgumentError, args.inspect unless coords.size == 2
+          coords
+        end
+
+        def to_s
+          [@lat, @lon].join(' ')
+        end
+
+        def format n
+          '%03.3f' % n
+        end
+      end
+
+      attr :point
+      def point= value
+        @point = Point.for(value)
+      end
+
+      attr :elev
+      def elev= value
+        @elev = Util.number_for(value)
+      end
+
+      attr :floor
+      def floor= value
+        @floor = Util.number_for(value).to_i
+      end
+
+      attr :featuretypetag
+      def featuretypetag= value
+        @featuretypetag = String(value)
+      end
+
+      attr :featurename
+      def featurename= value
+        @featurename = String(value)
+      end
+
+      attr :relationshiptag
+      def relationshiptag= value
+        @relationshiptag = String(value)
+      end
+
+      def to_yaml(*a, &amp;b)
+        oh = OrderedHash.new
+        oh['point'] = [point.lat, point.lon] if point
+        oh['elev'] = elev
+        oh['floor'] = floor
+        oh['featuretypetag'] = featuretypetag
+        oh['featurename'] = featurename
+        oh['relationshiptag'] = relationshiptag
+        oh.to_yaml(*a, &amp;b)
+      end
+
+      include Tagz
+      def to_xml(*args)
+        args, options = Gnip.args_for(args)
+        doc = args.shift
+
+        tagz(doc) {
+          place_{
+            point_{ point } if point
+            elev_{ elev } if elev
+            floor_{ floor } if floor
+            featuretypetag_{ featuretypetag } if featuretypetag
+            featurename_{ featurename } if featurename
+            relationshiptag_{ relationshiptag } if relationshiptag
+          }
+        }
+      end
     end
 
-    def tags(tags = [])
-      @tags = tags
-      self
+    class MetaURL
+      attr_accessor :content
+      attr_accessor :meta_url
+
+      def MetaURL.for(*args)
+        arg = args.flatten.compact.first
+        self.class === arg ? arg : new(*args)
+      end
+
+      def initialize(*args)
+        args, options = Gnip.args_for(args)
+        self.content = args.join unless args.empty?
+        options.each{|k,v| send &quot;#{ k }=&quot;, v}
+      end
+
+      def to_yaml(*a, &amp;b)
+        oh = OrderedHash.new
+        oh['content'] = content
+        oh['meta_url'] = meta_url
+        oh.to_yaml(*a, &amp;b)
+      end
+
+      include Tagz
+      def to_xml(*args)
+        args, options = Gnip.args_for(args)
+        doc = args.shift
+        tagz(doc){ send(&quot;#{ xml_tag }_&quot;, xml_attributes){ content } }
+      end
+
+      def xml_attributes
+        attributes = {}
+        attributes.update(:metaURL =&gt; meta_url) if meta_url
+        attributes
+      end
+
+      def xml_tag
+        self.class.const_get(:XML_TAG)
+      end
     end
 
-    def tag(tag)
-      @tags.push(tag)
-      self
+    class Actor &lt; MetaURL
+      XML_TAG = 'actor'
+
+      attr_accessor :uid
+
+      def to_yaml(*a, &amp;b)
+        oh = OrderedHash.new
+        oh['content'] = content
+        oh['meta_url'] = meta_url
+        oh['uid'] = uid
+        oh.to_yaml(*a, &amp;b)
+      end
+
+      def xml_attributes
+        attributes = super
+        attributes.update(:uid =&gt; uid) if uid
+        attributes
+      end
     end
 
-    def regardingURL(regardingURL)
-      @regardingURL = regardingURL
-      self
+    class Tag &lt; MetaURL
+      XML_TAG = 'tag'
     end
 
-    def source(source)
-      @source = source
-      self
+    class To &lt; MetaURL
+      XML_TAG = 'to'
     end
 
-    def payload(payload)
-      @payload = payload
-      self
+    class DestinationURL &lt; MetaURL
+      XML_TAG = 'destinationURL'
     end
 
-    def build
-      Gnip::Activity.new(@actor, @action, @at, @url, @tos, @regardingURL, @source, @tags, @payload)
+    class RegardingURL &lt; MetaURL
+      XML_TAG = 'regardingURL'
     end
 
-  end
+    class Payload
+      def Payload.for(*args)
+        return args.first if(args.size == 1 and args.first.is_a?(Place))
+        new(*args)
+      end
 
-  private
+      def initialize(options = {}, &amp;block)
+        options.each{|key, value| send &quot;#{ key }=&quot;, value}
+      end
 
-  def self.first(array)
-    array[0] unless array.nil?
-  end
+      def title
+        @title ||= nil
+      end
+      def title= title
+        @title = String(title)
+      end
+
+      def body
+        @body ||= nil
+      end
+      def body= body
+        @body = String(body)
+      end
+
+      def media_urls
+        @media_urls ||= List.of(MediaURL)
+      end
+      def media_urls= value
+        media_urls.replace value
+      end
+
+      def raw
+        @raw ||= nil
+      end
+      def __raw__
+        @raw ||= nil
+      end
+      def raw= raw
+        @raw = raw
+      end
+      def __raw__= raw
+        @raw = raw
+      end
 
+      include Tagz
+      def to_xml(*args)
+        args, options = Gnip.args_for(args)
+        doc = args.shift
+        tagz(doc){
+          payload_{
+            title_{ title } if title
+            body_{ normalize(body) } if body
+            media_urls.each{|media_url| media_url.to_xml(tagz)} if media_urls
+            raw_{ encode(raw) } if raw
+          }
+        }
+      end
+
+      def encode(data)
+        Util.encode(raw)
+      end
+
+      def decode(raw)
+        Util.decode(raw)
+      end
+
+      def normalize(string)
+        Util.normalize!(string)
+      end
+
+      class MediaURL
+        attr_accessor :content
+        attr_accessor :height
+        attr_accessor :width
+        attr_accessor :duration
+        attr_accessor :mime_type
+        attr_accessor :type
+
+        def initialize(*args)
+          args, options = Gnip.args_for(args)
+          self.content = args.join
+          options.each{|k,v| send &quot;#{ k }=&quot;, v}
+        end
+
+        def to_yaml(*a, &amp;b)
+          oh = OrderedHash.new
+          oh['content'] = content
+          oh['height'] = height
+          oh['width'] = width
+          oh['duration'] = duration
+          oh['mime_type'] = mime_type
+          oh['type'] = type
+          oh.to_yaml(*a, &amp;b)
+        end
+
+        include Tagz
+        def to_xml(*args)
+          args, options = Gnip.args_for(args)
+          doc = args.shift
+          tagz(doc){
+            options = {}
+            options['height'] = height if height
+            options['width'] = width if width
+            options['duration'] = duration if duration
+            options['mimeType'] = mime_type if mime_type
+            options['type'] = type if type
+            mediaURL_(options){ content }
+          }
+        end
+      end
+    end
+
+    class Stream
+      def Stream.from_xml(xml, options = {}, &amp;block)
+        doc = Nokogiri::XML.parse(xml)
+        selector = 'activityStream'
+        node = doc.at(selector)
+        Stream.from_node(node)
+      end
+
+      def Stream.from_node(node)
+        updated_at = node.at('activitiesAddedAt').content
+        buckets = []
+        node.search('bucket').each do |bucket|
+          buckets &lt;&lt; bucket['href']
+        end
+        Stream.new(:updated_at =&gt; updated_at, :buckets =&gt; buckets)
+      end
+
+      Attributes = []
+
+      Attributes &lt;&lt; 'updated_at'
+        def updated_at
+          @updated_at ||= nil
+        end
+        def updated_at= value
+          @updated_at = Time.parse(value.to_s)
+        end
+
+      Attributes &lt;&lt; 'buckets'
+        def buckets
+          @buckets ||= List.of(String)
+        end
+        def buckets= value
+          buckets.replace(value)
+        end
+
+        def initialize(options = {})
+          options = Gnip.options_for(options)
+          options.each{|key, value| send(&quot;#{ key }=&quot;, value)}
+        end
+    end
+
+    def Activity.stream(options = {})
+      options = Gnip.options_for(options)
+      scope = options.getopt(:scope, Gnip.scope)
+      resource = options.getopt(:resource, Gnip.default.resource)
+      style = options.getopt(:style, 'activity')
+      publisher = options.getopt(:publisher)
+      endpoint = resource.endpoint &quot;#{ scope }/publishers/#{ publisher.name }/#{ style }.xml&quot;
+      response = endpoint.get
+      xml = response.to_s
+      Activity::Stream.from_xml(xml)
+    end
+
+  end
 end</diff>
      <filename>lib/gnip/activity.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,15 +1,144 @@
-class Gnip::Config
-  attr_reader :base_url, :user, :password, :use_gzip
-  attr_accessor :logger, :http_read_timeout
-
-  def initialize(user, password, gnip_server='prod.gnipcentral.com', use_gzip = true, http_read_timeout = 5)
-    @base_url = gnip_server
-    @user = user
-    @password = password
-    @use_gzip = use_gzip
-    @http_read_timeout = http_read_timeout
-    @logger = Logger.new(STDERR)
-    @logger.level = Logger::ERROR
-  end
+module Gnip
+  class Config
+    def Config.for(arg)
+      config = new
+      updates =
+        case arg
+          when Hash
+            arg
+          when NilClass
+            {}
+          else
+            if arg.respond_to?(:read)
+              YAML.load(arg.read)
+            else
+              YAML.load(IO.read(arg.to_s))
+            end
+        end
+      config.update(updates)
+      config
+    end
+
+    def Config.default_path
+      File.join(Gnip.util.homedir, '.gnip.yml')
+    end
+
+    def Config.default
+      @default ||= (
+        update_from_env(
+          begin
+            Config.for(default_path)
+          rescue Errno::ENOENT
+            Config.for(nil)
+          end
+        )
+      )
+    end
+
+    def Config.update_from_env config
+      username = ENV['GNIP_USERNAME']
+      password = ENV['GNIP_PASSWORD']
+      auth = ENV['GNIP_AUTH']
+      if(auth and not (username and password))
+        username, password = auth.split(%r/:/, 2)
+      end
+      uri = ENV['GNIP_URI'] || Gnip.default.uri
+      config.username = username if username
+      config.password = password if password
+      config.uri = uri if uri
+      config
+    end
+
+    def Config.normalized hash
+      stringified_keys(hash)
+    end
+
+    def Config.stringified_keys hash
+      hash.keys.inject(Hash.new){|h,k| h.update(k.to_s =&gt; hash.fetch(k))}
+    end
+
+    attr :config
+
+    def initialize options = {}
+      @config = {}
+      options.each do |key, value|
+        msg = &quot;#{ key }=&quot;
+        if respond_to?(msg)
+          send msg, value
+        else
+          @config[key.to_s] = value
+        end
+      end
+    end
+
+    alias_method 'to_hash', 'config'
+
+    def to_yaml(*args, &amp;block)
+      to_hash.to_yaml(*args, &amp;block)
+    end
+
+    def username= username
+      config['username'] = username.to_s
+    end
+    def username
+      config['username'].to_s
+    end
 
+    def password= password
+      config['password'] = password.to_s
+    end
+    def password
+      config['password'].to_s
+    end
+
+    def uri= uri
+      config['uri'] = uri.to_s
+    end
+    def uri
+      URI.parse(config['uri'].to_s) if config['uri']
+    end
+
+    def inspect
+      config.inspect
+    end
+
+    def normalize!
+      config.replace normalized
+    end
+
+    def normalized hash = config
+      Config.normalized(hash)
+    end
+
+    def [] key
+      config[key.to_s]
+    end
+    alias_method 'get', '[]'
+
+    def []= key, val
+      config[key.to_s] = val
+    end
+    alias_method 'set', '[]='
+
+    def has_key? key
+      config.has_key? key.to_s
+    end
+
+    def update hash
+      config.update normalized(hash)
+      self
+    end
+
+    def method_missing m, *a, &amp;b
+      key, setter = m.to_s.split(/(=)/)
+      if setter
+        val = a.first
+        return(set(key, val))
+      end
+      if has_key?(key)
+        return(get(key))
+      end
+      super
+    end
+  end
 end</diff>
      <filename>lib/gnip/config.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,98 +1,304 @@
-class Gnip::Filter
-
-    attr_accessor :post_url
-    attr_reader :rules, :name, :full_data
+module Gnip
+  class Filter
+    def Filter.list_from_xml(xml, options = {}, &amp;block)
+      list = []
+      parse_xml(xml, options) do |filter|
+        block ? block.call(filter) : list.push(filter)
+      end
+      block ? nil : list
+    end
 
-    def initialize(name, full_data = true)
-        @name = name
-        @full_data = full_data
-        @rules = []
+    def Filter.parse_xml(xml, options = {}, &amp;block)
+      doc = Nokogiri::XML.parse(xml)
+      list = []
+      selectors = 'filter'
+      selectors.each do |selector|
+        search = doc.search(selector)
+        next unless search.size &gt; 0
+        search.each do |node|
+          filter = Filter.from_node(node, options)
+          block ? block.call(filter) : list.push(filter)
+        end
+      end
+      block ? nil : list
     end
 
-    def uri
-        'filters'
+    def Filter.from_node(node, options = {})
+      full_data = node['fullData'] =~ /true/ ? true : false
+      name = node['name']
+      post_url = nil
+      rules = []
+      node.search('rule').each do |rule_node|
+        type = rule_node['type']
+        value = rule_node.content
+        rules &lt;&lt; {:type =&gt; type, :value =&gt; value}
+      end
+      if post_url_node = node.at('postURL')
+        post_url = post_url_node.content
+      end
+      new(name, rules, :full_data =&gt; full_data, :post_url =&gt; post_url)
     end
 
-    def add_rule(type, value)
-        @rules &lt;&lt; Gnip::Rule.new(type, value)
+    def Filter.from_xml(xml, options = {})
+      parse_xml(xml, options){|filter| return filter}
     end
 
-    def remove_rule(type, value)
-        @rules.delete(Gnip::Rule.new(type, value))
+    Attributes = []
+
+    Attributes &lt;&lt; 'name'
+      def name
+        @name ||= nil
+      end
+      def name= value
+        @name = String(value)
+      ensure
+        raise ArgumentError, @name unless @name =~ %r/^[a-zA-Z0-9.+-]+$/
+      end
+
+    Attributes &lt;&lt; 'post_url'
+      def post_url
+        @post_url ||= nil
+      end
+      def post_url= value
+        @post_url = String(value)
+      end
+
+    Attributes &lt;&lt; 'rules'
+      def rules
+        @rules ||= List.of(Rule)
+      end
+      def rules= value
+        rules.replace(value)
+      end
+
+    Attributes &lt;&lt; 'full_data'
+      def full_data
+        @full_data ||= nil
+      end
+      def full_data= value
+        @full_data = !!value
+      end
+
+    attr_accessor :publisher
+
+    def initialize(*args)
+      args, options = Gnip.args_for(args)
+      self.name = args.shift unless args.empty?
+      self.rules = args unless args.empty?
+      options.each{|key, value| send(&quot;#{ key }=&quot;, value)}
     end
 
-    def to_xml()
-        return XmlSimple.xml_out(self.to_hash, {'RootName' =&gt; nil, 'XmlDeclaration' =&gt; Gnip.header_xml})
+    class Rule
+      def Rule.from_xml(xml, options = {})
+        parse_xml(xml, options){|rule| return rule}
+      end
+
+      def Rule.parse_xml(xml, options = {}, &amp;block)
+        doc = Nokogiri::XML.parse(xml)
+        list = []
+        selectors = 'rule'
+        selectors.each do |selector|
+          search = doc.search(selector)
+          next unless search.size &gt; 0
+          search.each do |node|
+            rule = Rule.from_node(node, options)
+            block ? block.call(rule) : list.push(rule)
+          end
+        end
+        block ? nil : list
+      end
+
+      def Rule.from_node(node, options = {})
+        type = node['type']
+        value = node.content
+        new(:type =&gt; type, :value =&gt; value)
+      end
+
+      def Rule.for(*args)
+        arg = args.first if args.size == 1
+        if Rule === arg
+          arg
+        elsif Hash === arg
+          new(arg)
+        else
+          new(*args)
+        end
+      end
+
+      attr_accessor :type
+      attr_accessor :value
+
+      def initialize(*args)
+        args, options = Gnip.args_for(args)
+        type = value = nil
+        case args.size
+          when 1
+            type, value = args.first.to_s.split(%r/:/, 2)
+          when 2
+            type, value = args
+        end
+        self.type = Publisher.rule.for options.getopt(:type, type)
+        self.value = options.getopt(:value, value)
+      end
+
+      def inspect
+        &quot;#{ type }:#{ value }&quot;
+      end
+
+      def to_s
+        inspect
+      end
+
+      def to_yaml(*a, &amp;b)
+        to_s.to_yaml(*a, &amp;b)
+      end
+
+=begin
+      def Rule.template
+        @template ||=
+          Template.new do
+            &quot;
+              &lt;rule type=&lt;%= type.inspect %&gt;&gt;
+                &lt;%= value %&gt;
+              &lt;/rule&gt;
+            &quot;
+          end
+      end
+
+      def to_xml(options = {})
+        Rule.template.expand(self)
+      end
+=end
+
+      include Tagz
+      def to_xml(*args)
+        args, options = Gnip.args_for(args)
+        doc = args.shift
+
+        tagz(doc) {
+          rule_(:type =&gt; type){ value }
+        }
+      end
+
+      attr_accessor :filter
+
+      def resource
+        raise 'filter not set!' unless filter
+        rule = self
+        filter.publisher.filter.resource[&quot;filters/#{ filter.name }/rules?type=#{ rule.type }&amp;value=#{ rule.value }&quot;]
+      end
+
+      def delete(options = {})
+        Gnip.optify!(options)
+        filter = options.getopt(:filter, self.filter)
+        resource.delete
+        self
+      end
     end
 
-    def to_hash()
-        result = {}
-        result['name'] = @name
-        result['fullData'] = @full_data
-        result['postUrl'] = [@post_url]  if @post_url
-        result['rule'] = rules.collect { |rule| rule.to_hash()}
-        { 'filter' =&gt; result }
+    include Tagz
+    def to_xml(*args)
+      tagz {
+        filter_(:name =&gt; name, :fullData =&gt; !!full_data){
+          rules.each do |rule|
+            rule_(:type =&gt; rule.type){ rule.value }
+          end
+        }
+      }
     end
 
-    def eql?(object)
-        self == object
+=begin
+    def Filter.template
+      @template ||=
+        Template.new do
+          &quot;
+            &lt;filter name=&lt;%= name.inspect %&gt; fullData=&lt;%= (!!full_data).to_s.inspect %&gt;&gt;
+            % if post_url
+              &lt;postURL&gt;&lt;%= post_url %&gt;&lt;/postURL&gt;
+            % end
+            % rules.each do |rule|
+              &lt;rule type=&lt;%= rule.type.inspect %&gt;&gt;&lt;%= rule.value %&gt;&lt;/rule&gt;
+            % end
+            &lt;/filter&gt;
+          &quot;
+        end
     end
 
-    def ==(object)
-        object.instance_of?(self.class) &amp;&amp; @name == object.name
+    def to_xml(options = {})
+      Filter.template.expand(self)
     end
+=end
 
-    def full_data=(full_data)
-        @full_data = Gnip::Filter.value_to_boolean(full_data)
+    def resource
+      raise 'publisher not set!' unless publisher
+      publisher.filter.resource[&quot;filters/#{ name }.xml&quot;]
     end
 
-    def self.from_hash(hash)
-        filter = new(hash['name'])
-        filter.full_data = value_to_boolean(hash['fullData'])
-        filter.post_url = hash['postUrl'].first if hash['postUrl']
-        rules = hash['rule']
-        if rules
-            rules.each do |rule_hash|
-                filter.add_a_rule(Gnip::Rule.from_hash(rule_hash))
-            end
-        end
-        return filter
+    def get(*args, &amp;block)
+      resource.get(*args, &amp;block)
     end
 
-    def self.from_xml(document)
-        hash = XmlSimple.xml_in(document)
-        return self.from_hash(hash)
+    def delete(*args, &amp;block)
+      resource.delete(*args, &amp;block)
     end
 
+    def put(*args, &amp;block)
+      resource.put(*args, &amp;block)
+    end
 
-    # The URI of this filters activity stream.
-    def activity_stream_uri
-        &quot;filters/#{name}/activity&quot;
+    def post(*args, &amp;block)
+      resource.post(*args, &amp;block)
     end
 
-    # The URI to a particular activity bucket of this filters
-    # activity stream.
-    def activity_bucket_uri_for(at_time)
-        &quot;filters/#{name}/activity/#{at_time.to_gnip_bucket_id}.xml&quot;
+    def replace(other)
+      put(other.to_xml)
+      publisher.filter.for(name)
     end
 
-    def add_a_rule(rule)
-        @rules &lt;&lt; rule
+    def rule
+      @rule ||= RuleResource.new(self)
     end
 
-    private
+    class RuleResource
+      attr_accessor :filter
 
-    # The logger this object should use.
-    def logger
-        Gnip.logger
-    end
+      def initialize filter
+        @filter = filter
+      end
 
-    def self.value_to_boolean(value)
-        if value == true || value == false
-            value
-        else
-            %w(true t 1).include?(value.to_s.downcase)
+      def publisher
+        filter.publisher
+      end
+
+      def resource
+        publisher.resource
+      end
+
+      def list options = {}
+        Gnip.optify!(options)
+        type = options.getopt(:type) or raise 'no type'
+        value = options.getopt(:value) or raise 'no type'
+        xml = resource[&quot;filters/#{ filter.name }/rules?type=#{ type }&amp;value=#{ value }&quot;].get
+        rule = Rule.from_xml(xml)
+        rule.filter = filter
+        rule
+      end
+
+      def delete options = {}
+        list(options).delete(:filter =&gt; filter)
+      end
+
+      def create(*rules)
+        rules = rules.map{|rule| Rule.for(rule)}
+        slices = [] and rules.each_slice(5000){|slice| slices &lt;&lt; slice}
+        msg = slices.size &gt; 1 ? 'threadify' : 'each'
+        slices.send(msg) do |slice|
+          xml = slice.map{|rule| rule.to_xml(:declaration =&gt; false)}.join
+          resource[&quot;filters/#{ filter.name }/rules&quot;].post(&quot;&lt;rules&gt;#{ xml }&lt;/rules&gt;&quot;)
         end
+        rules.each{|rule| rule.filter = filter}
+        rules
+      end
     end
-
+  end
 end</diff>
      <filename>lib/gnip/filter.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,53 +1,309 @@
-class Gnip::Publisher
-  attr_reader :name
-  attr_accessor :supported_rule_types
-  attr_accessor :scope
-
-  def initialize(name, suppported_rule_types = [], scope = 'my')
-    @name = name
-    @scope = scope
-    @supported_rule_types = suppported_rule_types
-  end
+module Gnip
+  class Publisher
+    def Publisher.list(options = {})
+      options = Gnip.optify!(options)
+      scope = options.getopt(:scope, Gnip.scope)
+      resource = options.getopt(:resource, Gnip.default.resource)
+      endpoint = resource.endpoint &quot;#{ scope }/publishers.xml&quot;
+      response = endpoint.get
+      xml = response.to_s
+      Publisher.list_from_xml(xml, :scope =&gt; scope)
+    end
 
-  def uri
-    &quot;/#{@scope}/publishers/#{@name}&quot;
-  end
+    def Publisher.list_from_xml(xml, options = {}, &amp;block)
+      list = []
+      parse_xml(xml, options) do |publisher|
+        block ? block.call(publisher) : list.push(publisher)
+      end
+      block ? nil : list
+    end
 
-  def to_xml()
-    return XmlSimple.xml_out(self.to_hash, {'RootName' =&gt; nil, 'XmlDeclaration' =&gt; Gnip.header_xml})
-  end
+    def Publisher.from_xml(xml, options = {}, &amp;block)
+      parse_xml(xml, options){|publisher| return publisher}
+    end
 
-  def to_hash()
-    result = {}
-    result['name'] = @name
-    result['supportedRuleTypes'] = @supported_rule_types.collect { |type| type.to_hash}
-    { 'publisher' =&gt; result }
-  end
+    def Publisher.parse_xml(xml, options = {}, &amp;block)
+      doc = Nokogiri::XML.parse(xml)
+      list = []
+      selectors = '*/publishers', 'publisher'
+      selectors.each do |selector|
+        search = doc.search(selector)
+        next unless search.size &gt; 0
+        search.each do |node|
+          publisher = Publisher.from_node(node, options)
+          block ? block.call(publisher) : list.push(publisher)
+        end
+      end
+      block ? nil : list
+    end
 
-  def ==(object)
-    object.instance_of?(self.class) &amp;&amp; @name == object.name
-  end
-  alias :eql? :==
-
-  def self.from_hash(hash, scope = 'my')
-      found_rule_types = []
-      rule_types = hash['supportedRuleTypes']
-        if rule_types
-            rule_types.each do |rule_type_hash|                
-                found_rule_types &lt;&lt; Gnip::RuleType.from_hash(rule_type_hash['type'].first)
-            end
+    def Publisher.from_node(node, options = {})
+      name = node['name']
+      rules = node.search('supportedRuleTypes/type').map{|type| type.content}
+      Publisher.new(name, options.update(:rules =&gt; rules))
+    end
+
+    def Publisher.for name, options = {}
+      options = Gnip.optify!(options)
+      scope = options.getopt(:scope, Gnip.scope)
+      resource = options.getopt(:resource, Gnip.default.resource)
+      endpoint = resource[&quot;#{ scope }/publishers/#{ name }.xml&quot;]
+      response = endpoint.get
+      xml = response.to_s
+      publisher = Publisher.from_xml(xml)
+      publisher.scope = scope
+      publisher
+    end
+
+    def Publisher.exists?(*args, &amp;block)
+      Publisher.for(*args, &amp;block) rescue false
+    end
+
+    def Publisher.resource
+      @resource ||= Gnip.default.resource
+    end
+
+    def Publisher.create(*args, &amp;block)
+      publisher = new(*args, &amp;block)
+      resource = Publisher.resource[&quot;#{ publisher.scope }/publishers.xml&quot;]
+      resource.post(publisher.to_xml(:declaration =&gt; true))
+      Publisher.for(publisher.name, :scope =&gt; publisher.scope)
+    end
+
+    def Publisher.delete(name, options = {})
+      if publisher = Publisher.exists?(name, options)
+        publisher.delete
+      end
+    end
+
+    Attributes = []
+
+    Attributes &lt;&lt; 'name'
+      def name
+        @name ||= nil
+      end
+      def name= value
+        @name = String(value)
+      ensure
+        raise ArgumentError, @name unless @name =~ %r/^[a-zA-Z0-9.+-]+$/
+      end
+
+    Attributes &lt;&lt; 'rules'
+      def rules
+        @rules ||= List.of(String)
+      end
+      def rules= value
+        rules.replace(value)
+      end
+
+    attr_accessor :scope
+
+    def initialize(*args)
+      args, options = Gnip.args_for(args)
+      self.name = args.shift if args.first
+      @scope = options.getopt(:scope, Gnip.scope).to_s
+      @resource = options.getopt(:resource, Gnip.default.resource)
+      rules = options.getopt(:rules, []).flatten.compact
+      @rules = rules.map{|rule| Rule.for(rule)}
+    end
+
+    def resource
+      @resource[&quot;#{ scope }/publishers/#{ name }&quot;]
+    end
+
+
+
+    class Rule &lt; ::String
+      List = []
+
+      %w[ actor tag to regarding source keyword ].each do |name|
+        module_eval &lt;&lt;-code
+          def Rule.#{ name }
+            @#{ name } ||= Rule.new('#{ name }').freeze
+          end
+        code
+        List &lt;&lt; Rule.send(name)
+      end
+
+      List.freeze
+
+      def Rule.list
+        List
+      end
+
+      def Rule.for(name)
+        send(name.to_s.downcase.strip)
+      rescue
+        raise ArgumentError, &quot;bad rule type #{ name.inspect }&quot; 
+      end
+
+      def Rule.[] name
+        Rule.for(name)
+      end
+    end
+
+    def Publisher.rule
+      Rule
+    end
+
+    include Tagz
+    def to_xml(*args)
+      args, options = Gnip.args_for(args)
+      doc = args.first
+
+      tagz(doc) {
+        publisher_(:name =&gt; name){
+          supportedRuleTypes_{
+            rules.each{|rule| type_{ rule }}
+          }
+        }
+      }
+    end
+
+=begin
+    def Publisher.template
+      @template ||=
+        Template.new do
+          &quot;
+            &lt;publisher name=&lt;%= name.inspect %&gt;&gt;
+              &lt;supportedRuleTypes&gt;
+            % rules.each do |rule|
+              &lt;type&gt;&lt;%= rule %&gt;&lt;/type&gt;
+            % end
+              &lt;/supportedRuleTypes&gt;
+            &lt;/publisher&gt;
+          &quot;
         end
-    return Gnip::Publisher.new(hash['name'], found_rule_types, scope)
-  end
+    end
 
-  def self.from_xml(document, scope = 'my')
-    hash = XmlSimple.xml_in(document)
-    return self.from_hash(hash, scope)
-  end
+    def to_xml(options = {})
+      Publisher.template.expand(self)
+    end
+=end
+
+
+    def delete
+      Publisher.resource[&quot;#{ scope }/publishers/#{ name }&quot;].delete
+      self
+    end
+
+    def activity_stream options = {}
+      Activity.stream(options.update(:publisher =&gt; self, :scope =&gt; scope))
+    end
+
+    def activity options = {}, &amp;block
+      Gnip.optify!(options)
+
+      style = options.getopt(:style, 'activity').to_s
+      raise ArgumentError unless %w( activity notification ).include?(style)
+
+
+      bucket = options.getopt(:bucket)
+      ago = options.getopt(:ago)
+      thru = options.getopt(:thru, options.getopt(:through))
+
+      if Range === ago
+        ago, thru = [ago.begin, ago.end].sort.reverse
+      end
+
+      unless bucket
+        bucket = ago ? bucket_for_minutes_ago(ago) : 'current'
+      end
+
+      filter = options.getopt(:filter)
+      filter = filter.value if(filter and filter.respond_to?(:value))
+
+      buckets =
+        if thru
+          thru = 0 if thru =~ /current|now/i
+          a, b = [ago, Integer(thru)].sort
+          (a..b).to_a.reverse.map{|i| bucket_for_minutes_ago(i)}
+        else
+          if bucket =~ /all/i
+            stream = send(&quot;#{ style }_stream&quot;)
+            stream.buckets.map{|bucket| File.basename(bucket, '.xml')}
+          else
+            [bucket]
+          end
+        end
+
+      activities = []
+
+      msg = buckets.size &gt; 1 ? 'threadify' : 'each'
+
+      buckets.send(msg) do |bucket|
+        path = &quot;#{ style }/#{ bucket }.xml&quot;
+        path = &quot;filters/#{ filter }/#{ path }&quot; if filter
+        xml = resource[path].get
+        list = Activity.list_from_xml(xml, &amp;block)
+        activities.push(*list) unless block
+      end
+
+      activities unless block
+    end
+
+    def bucket_for_minutes_ago ago
+      bucket = Gnip.time - (Integer(ago) * 60)
+      bucket.strftime('%Y%m%d%H%M')
+    end
+
+    def notification_stream options = {}
+      Activity.stream(options.update(:publisher =&gt; self, :scope =&gt; scope, :style =&gt; 'notification'))
+    end
+
+    def notifications options = {}
+      Gnip.optify!(options)
+      options.setopt!(:style, 'notification') unless options.hasopt?(:style)
+      activity(options)
+    end
+
+    def publish *activities
+      activities.flatten!
+      activities.compact!
+      #xml = &quot;&lt;activiies&gt;#{ activities.map{|activity| activity.to_xml} }&lt;/activities&gt;&quot;
+      xml = tagz { activities_{ activities.map{|activity| activity.to_xml(tagz.doc)} } }
+      resource['activity.xml'].post(xml)
+      activities
+    end
+
+
+    def filter
+      @filter ||= FilterResource.new(self)
+    end
+
+    class FilterResource
+      attr_accessor :publisher
+
+      def initialize publisher
+        @publisher = publisher
+      end
+
+      def resource
+        publisher.resource
+      end
+
+      def list
+        xml = resource['filters.xml'].get
+        Filter.list_from_xml(xml).each{|filter| filter.publisher = publisher}
+      end
+
+      def for name
+        xml = resource[&quot;filters/#{ name }.xml&quot;].get
+        filter = Filter.from_xml(xml)
+      ensure
+        filter.publisher = publisher if filter
+      end
 
-  private
+      alias_method '[]', 'for'
 
-  def path
-    &quot;/my/publishers/#{name}/activity&quot;
+      def create(name, rules, options)
+        Gnip.optify!(options)
+        filter = Filter.new(name, rules, options)
+        resource['filters.xml'].post(filter.to_xml(:declaration =&gt; true))
+        filter = self.for(name)
+      ensure
+        filter.publisher = publisher if filter
+      end
+    end
   end
 end</diff>
      <filename>lib/gnip/publisher.rb</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>gnip-ruby.gemspec</filename>
    </removed>
    <removed>
      <filename>lib/ext/time.rb</filename>
    </removed>
    <removed>
      <filename>lib/gnip/connection.rb</filename>
    </removed>
    <removed>
      <filename>lib/gnip/payload.rb</filename>
    </removed>
    <removed>
      <filename>lib/gnip/rule.rb</filename>
    </removed>
    <removed>
      <filename>lib/gnip/rule_type.rb</filename>
    </removed>
    <removed>
      <filename>rakefile.rb</filename>
    </removed>
    <removed>
      <filename>spec/ext/time_spec.rb</filename>
    </removed>
    <removed>
      <filename>spec/gnip/activity_spec.rb</filename>
    </removed>
    <removed>
      <filename>spec/gnip/connection_spec.rb</filename>
    </removed>
    <removed>
      <filename>spec/gnip/filter_spec.rb</filename>
    </removed>
    <removed>
      <filename>spec/gnip/payload_spec.rb</filename>
    </removed>
    <removed>
      <filename>spec/gnip/publisher_spec.rb</filename>
    </removed>
    <removed>
      <filename>spec/gnip/rule_spec.rb</filename>
    </removed>
    <removed>
      <filename>spec/gnip/rule_type_spec.rb</filename>
    </removed>
    <removed>
      <filename>spec/gnip_spec.rb</filename>
    </removed>
    <removed>
      <filename>spec/spec_helper.rb</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>50e50ecb5f67b0a0a84d57d5edf21fa4b7ddf969</id>
    </parent>
  </parents>
  <author>
    <name>ara.t.howard</name>
    <email>ara.t.howard@gmail.com</email>
  </author>
  <url>http://github.com/gnip/gnip-ruby/commit/44fc0adb9ebbbaf33c01f5e8249bfbc6324e66e2</url>
  <id>44fc0adb9ebbbaf33c01f5e8249bfbc6324e66e2</id>
  <committed-date>2009-03-27T15:18:53-07:00</committed-date>
  <authored-date>2009-03-27T15:18:53-07:00</authored-date>
  <message>importing new work for api-v21</message>
  <tree>815690ba255490f742b45cecc4d055fdb706eecf</tree>
  <committer>
    <name>ara.t.howard</name>
    <email>ara.t.howard@gmail.com</email>
  </committer>
</commit>
