posterous 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +4 -0
  5. data/README.md +65 -0
  6. data/Rakefile +12 -0
  7. data/autotest/discovery.rb +1 -0
  8. data/bin/posterous +80 -0
  9. data/lib/posterous/association_proxy.rb +39 -0
  10. data/lib/posterous/connection.rb +52 -0
  11. data/lib/posterous/inheritable.rb +70 -0
  12. data/lib/posterous/model.rb +115 -0
  13. data/lib/posterous/models/comment.rb +6 -0
  14. data/lib/posterous/models/external_site.rb +6 -0
  15. data/lib/posterous/models/like.rb +6 -0
  16. data/lib/posterous/models/link.rb +6 -0
  17. data/lib/posterous/models/link_category.rb +8 -0
  18. data/lib/posterous/models/page.rb +6 -0
  19. data/lib/posterous/models/post.rb +9 -0
  20. data/lib/posterous/models/profile.rb +20 -0
  21. data/lib/posterous/models/site.rb +22 -0
  22. data/lib/posterous/models/subscriber.rb +6 -0
  23. data/lib/posterous/models/subscription.rb +10 -0
  24. data/lib/posterous/models/tag.rb +6 -0
  25. data/lib/posterous/models/user.rb +19 -0
  26. data/lib/posterous/version.rb +3 -0
  27. data/lib/posterous.rb +39 -0
  28. data/posterous.gemspec +28 -0
  29. data/spec/fixtures/metal.png +0 -0
  30. data/spec/helper.rb +11 -0
  31. data/spec/posterous.yml.sample +3 -0
  32. data/spec/unit/lib/posterous/association_proxy_spec.rb +40 -0
  33. data/spec/unit/lib/posterous/connection_spec.rb +41 -0
  34. data/spec/unit/lib/posterous/models/comment_spec.rb +53 -0
  35. data/spec/unit/lib/posterous/models/external_site_spec.rb +55 -0
  36. data/spec/unit/lib/posterous/models/like_spec.rb +45 -0
  37. data/spec/unit/lib/posterous/models/link_category_spec.rb +52 -0
  38. data/spec/unit/lib/posterous/models/link_spec.rb +53 -0
  39. data/spec/unit/lib/posterous/models/page_spec.rb +52 -0
  40. data/spec/unit/lib/posterous/models/post_spec.rb +52 -0
  41. data/spec/unit/lib/posterous/models/profile_spec.rb +32 -0
  42. data/spec/unit/lib/posterous/models/site_spec.rb +53 -0
  43. data/spec/unit/lib/posterous/models/subscriber_spec.rb +44 -0
  44. data/spec/unit/lib/posterous/models/user.rb +10 -0
  45. data/spec/unit/lib/posterous/models/user_spec.rb +20 -0
  46. data/spec/unit/lib/posterous_spec.rb +22 -0
  47. metadata +204 -0
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ Gemfile.lock
2
+ .bundle
3
+ test.log
4
+ .DS_Store
5
+ config/flowb.yml
6
+ spec/postly.yml
7
+ pkg/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format=nested
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.2@postly
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in posterous.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Posterous #
2
+
3
+ ## Installation ##
4
+
5
+ gem install posterous
6
+
7
+ ## Basic Usage ##
8
+
9
+ require 'posterous'
10
+
11
+ Posterous.config = {
12
+ 'username' => <username>,
13
+ 'password' => <password>,
14
+ 'api_token' => '<api_token>'
15
+ }
16
+
17
+ include Posterous
18
+
19
+ ### Sites ###
20
+ p Site.all
21
+ p Site.primary
22
+ p Site.find('twoism')
23
+
24
+ ### Posts ###
25
+ p Site.primary.posts
26
+ p Site.primary.posts.create(:title => 'New Post', :body => 'From posterous API', :media => [File.open('/path/to/file')])
27
+
28
+
29
+ ## Interactive Console Usage ##
30
+
31
+ In your terminal type...
32
+
33
+ $ posterous
34
+
35
+ You will then be walked through the setup process. Type `newb` to get some help.
36
+
37
+ ****************************************************************************************************
38
+ Hi <username>, welcome to the Posterous API Console! For help type `newb`.
39
+ ****************************************************************************************************
40
+ > newb
41
+
42
+ # Get your primary site
43
+ > Site.primary
44
+ => <#<Posterous::Site:0x000001013e9b88> ... }>
45
+
46
+ # Get all your sites
47
+ > Site.all
48
+ => [<#<Posterous::Site:0x000001013e9b88> ... }>]
49
+
50
+ # Get any public site
51
+ > Site.find('twoism')
52
+ => <#<Posterous::Site:0x000001013e9b88> ... }>
53
+
54
+ # Get some posts
55
+ > s = Site.primary
56
+ > s.posts(:page => 1)
57
+ => [<#<Posterous::Post:0x0000010138ced8> ... }>]
58
+
59
+ # Create a post and add a comment to it
60
+ > s = Site.primary
61
+ > post = s.posts.create(:title => 'Woo Hoo!')
62
+ => <#<Posterous::Post:0x00000101398670>
63
+ > post.comments.create(:body => 'Kittens are radical!')
64
+ => <#<Posterous::Comment:0x0000010135f758> ... }>
65
+
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rspec/core/rake_task"
5
+ task :spec do
6
+ RSpec::Core::RakeTask.new(:core) do |spec|
7
+ spec.pattern = 'spec/**/*_spec.rb'
8
+ spec.rspec_opts = ['--backtrace']
9
+ end
10
+ end
11
+
12
+ task :default => :spec
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
data/bin/posterous ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ RC_PATH = "#{ENV['HOME']}/.posterousrc"
4
+ HELP_DOC = <<DOC
5
+ ## API Help ##
6
+
7
+ # Get your primary site
8
+ > Site.primary
9
+ => <#<Posterous::Site:0x000001013e9b88> ... }>
10
+
11
+ # Get all your sites
12
+ > Site.all
13
+ => [<#<Posterous::Site:0x000001013e9b88> ... }>]
14
+
15
+ # Get any public site
16
+ > Site.find('twoism')
17
+ => <#<Posterous::Site:0x000001013e9b88> ... }>
18
+
19
+ # Get some posts
20
+ > s = Site.primary
21
+ > s.posts(:page => 1)
22
+ => [<#<Posterous::Post:0x0000010138ced8> ... }>]
23
+
24
+ # Create a post and add a comment to it
25
+ > s = Site.primary
26
+ > post = s.posts.create(:title => 'Woo Hoo!')
27
+ => <#<Posterous::Post:0x00000101398670>
28
+ > post.comments.create(:body => 'Kittens are radical!')
29
+ => <#<Posterous::Comment:0x0000010135f758> ... }>
30
+ DOC
31
+
32
+ def newb
33
+ puts HELP_DOC
34
+ end
35
+
36
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
37
+
38
+ %w{posterous irb}.each { |f| require f }
39
+
40
+ rc = File.open(RC_PATH,File::RDWR|File::APPEND|File::CREAT)
41
+ yml = YAML.load_file(rc)
42
+
43
+ if yml.is_a?(Hash)
44
+ cfg = yml
45
+ else
46
+ cfg = {}
47
+ puts "Email Address:"
48
+ cfg['username'] = gets.chomp
49
+ puts "Password:"
50
+ cfg['password'] = gets.chomp
51
+ puts "Api Token:"
52
+ cfg['api_token'] = gets.chomp
53
+ rc.puts cfg.to_yaml
54
+ end
55
+
56
+ rc.close
57
+
58
+ Posterous.config = cfg
59
+
60
+ include Posterous
61
+
62
+ @user = User.me
63
+ puts "*"*100
64
+ puts "Hi #{@user.nickname}, welcome to the Posterous API Console! For help type `newb`.\n"
65
+ puts "*"*100
66
+
67
+ IRB.setup(nil)
68
+ irb = IRB::Irb.new
69
+
70
+ IRB.conf[:MAIN_CONTEXT] = irb.context
71
+
72
+ irb.context.evaluate("require 'irb/completion'", 0)
73
+
74
+ trap("SIGINT") do
75
+ irb.signal_handle
76
+ end
77
+
78
+ catch(:IRB_EXIT) do
79
+ irb.eval_input
80
+ end
@@ -0,0 +1,39 @@
1
+ module Posterous
2
+ class AssociationProxy
3
+ attr_reader :klass, :proxied
4
+
5
+ instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
6
+
7
+ def initialize proxied, klass, association_type, *args
8
+ @association_klass = klass
9
+ @proxied = proxied
10
+ @association = nil
11
+
12
+ @association_klass.finder_opts[klass.parent_resource] = proxied.id
13
+ @association_klass.finder_opts.merge!(@proxied.finder_opts)
14
+
15
+ load_method = association_type == :many ? :all : :load
16
+ @association = @association_klass.send(load_method, *args)
17
+ end
18
+
19
+ def method_missing sym, *args, &block
20
+ [@association_klass, @association].each do |assoc|
21
+ return assoc.send(sym, *args, &block) if assoc.respond_to?(sym)
22
+ end
23
+ super(sym, *args, &block)
24
+ end
25
+
26
+ def respond_to?(*args)
27
+ [@association, @association_klass].any?{|a| a.respond_to?(*args) }
28
+ end
29
+
30
+ def new
31
+ @association_klass.new
32
+ end
33
+
34
+ def inspect
35
+ @association.inspect
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,52 @@
1
+ module Posterous
2
+ module Connection
3
+
4
+ ConnectionError = Class.new(StandardError)
5
+
6
+ extend self
7
+
8
+ def http
9
+ Typhoeus::Request
10
+ end
11
+
12
+ def default_options
13
+ @default_options ||= {
14
+ :username => Posterous.config['username'],
15
+ :password => Posterous.config['password']
16
+ }
17
+ end
18
+
19
+ def default_params
20
+ @default_params ||= { :api_token => Posterous.config['api_token'] }
21
+ end
22
+
23
+ def parse json
24
+ parsed = JSON.parse json
25
+ parsed.delete('comments')
26
+ return parsed.map{|r| r.delete('comments'); OpenStruct.new(r) } if parsed.is_a?(Array)
27
+ OpenStruct.new(parsed)
28
+ rescue
29
+ nil
30
+ end
31
+
32
+ [:get, :post, :put, :delete].each do |verb|
33
+ define_method verb do |path, params={}|
34
+ puts "POSTLY :: #{verb.upcase} #{path} #{params}\n\n" if ENV['POSTLY_DEBUG']
35
+
36
+ response = http.send(verb, "#{Posterous::BASE_API_URL}#{path}",
37
+ default_options.merge!(:params => default_params.merge!(params)))
38
+ result = parse(response.body)
39
+
40
+ puts "POSTLY :: #{response.body}\n\n" if ENV['POSTLY_DEBUG']
41
+
42
+ unless [200,201].include?(response.code)
43
+ msg = result.nil? ? response.body : "#{result.error} #{result.message}"
44
+ raise ConnectionError, msg
45
+ end
46
+
47
+ result
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,70 @@
1
+ module Posterous
2
+ module Inheritable
3
+
4
+ def self.included klass
5
+ klass.class_eval do
6
+ extend ClassMethods
7
+ include InstanceMethods
8
+
9
+ inherited_attributes :finder_opts, :parent_resource, :resource_path
10
+ @finder_opts ||= {}
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ def inherited_attributes(*args)
16
+ @inherited_attributes ||= [:inherited_attributes]
17
+ @inherited_attributes += args
18
+ args.each do |arg|
19
+ class_eval %(
20
+ class << self; attr_accessor :#{arg} end
21
+ )
22
+ end
23
+ @inherited_attributes
24
+ end
25
+
26
+ def inherited(subclass)
27
+ @inherited_attributes.each do |inheritable_attribute|
28
+ instance_var = "@#{inheritable_attribute}"
29
+ subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
30
+ end
31
+ end
32
+
33
+ def resource path
34
+ @resource_path = path
35
+ end
36
+
37
+ def parent sym
38
+ @parent_resource = sym
39
+ end
40
+
41
+ def many collection_name, klass
42
+ define_method collection_name do |*args|
43
+ AssociationProxy.new self, klass, :many, *args
44
+ end
45
+ end
46
+
47
+ def one collection_name, klass
48
+ define_method collection_name do |*args|
49
+ AssociationProxy.new self, klass, :one, *args
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ module InstanceMethods
56
+ def resource_path
57
+ self.class.resource_path
58
+ end
59
+
60
+ def parent_resource
61
+ self.class.parent_resource
62
+ end
63
+
64
+ def finder_opts
65
+ self.class.finder_opts
66
+ end
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,115 @@
1
+ module Posterous
2
+ class Model
3
+ include Inheritable
4
+ extend Connection
5
+
6
+ attr_reader :struct
7
+
8
+ # Get a collection for a model
9
+ #
10
+ # Site.posts.all(:page => 1)
11
+ def self.all params={}
12
+ result = get( parsed_resource_url, params )
13
+ result.collect{|s| self.new(s) }
14
+ end
15
+
16
+ # Get a model from a collection by its id.
17
+ #
18
+ # Site.primary.posts.find(123)
19
+ # Site.find(123)
20
+ def self.find mid
21
+ new get( parsed_resource_url + "/#{mid}")
22
+ end
23
+
24
+ # loads the model data from the server and
25
+ # instantiates a new instance of its class
26
+ #
27
+ # Site.primary.profile.load
28
+ def self.load
29
+ new get(parsed_resource_url) rescue self.new(OpenStruct.new)
30
+ end
31
+
32
+ # Used to scope query params for a given model
33
+ #
34
+ # Posterous::ExternalSite => external_site
35
+ def self.param_scope
36
+ underscore(self.to_s.split('::').last).to_sym
37
+ end
38
+
39
+ def param_scope
40
+ self.class.param_scope
41
+ end
42
+
43
+ # lifted from ActiveSupport.
44
+ def self.underscore(camel_cased_word)
45
+ camel_cased_word.to_s.gsub(/::/, '/').
46
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
47
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
48
+ tr("-", "_").
49
+ downcase
50
+ end
51
+
52
+ # Creates a new model with the given params.
53
+ # Converts a :media array into a hash to make
54
+ # the Rails app happy.
55
+ #
56
+ # Site.primary.posts.create(:title => 'Awesome!', :media => [File.open('../some/path')])
57
+ def self.create params={}
58
+ media_array = params.delete(:media)
59
+ media = Hash[media_array.each_with_index.map{|v,i| [i,v] }] unless media_array.nil?
60
+
61
+ new post(parsed_resource_url, param_scope => params, :media => media)
62
+ end
63
+
64
+ def save
65
+ return if hash_for_update.empty?
66
+ @struct = self.class.post(parsed_resource_url + "/#{self.id}", { param_scope => hash_for_update, '_method' => 'put' } )
67
+ changed_fields.clear
68
+ end
69
+
70
+ def destroy
71
+ self.class.delete(parsed_resource_url + "/#{self.id}")
72
+ end
73
+
74
+ def reload
75
+ self.class.find(self.id)
76
+ end
77
+
78
+ def self.parsed_resource_url
79
+ resource_path.gsub(/:\w+/) {|sym| finder_opts[sym.sub(/:/,'').to_sym] }
80
+ end
81
+
82
+ def parsed_resource_url
83
+ self.class.parsed_resource_url
84
+ end
85
+
86
+ def initialize struct
87
+ @struct = struct
88
+ end
89
+
90
+ def changed_fields
91
+ @changed_fields ||= []
92
+ end
93
+
94
+ def hash_for_update
95
+ Hash[changed_fields.collect{ |f| [f, self.send(f)] }]
96
+ end
97
+
98
+ def respond_to? *args
99
+ struct.respond_to?(*args) || super
100
+ end
101
+
102
+ def method_missing sym, *args, &block
103
+ if struct.respond_to? sym
104
+ changed_fields.push(sym.to_s.sub('=','').to_sym) if sym.to_s =~ /=/
105
+ return struct.send(sym,*args)
106
+ end
107
+ super(sym, *args, &block)
108
+ end
109
+
110
+ def inspect
111
+ "<#{self} #{struct.send(:table).to_s}>"
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,6 @@
1
+ module Posterous
2
+ class Comment < Posterous::Model
3
+ parent :post_id
4
+ resource "/sites/:site_id/posts/:post_id/comments"
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Posterous
2
+ class ExternalSite < Posterous::Model
3
+ parent :site_id
4
+ resource "/sites/:site_id/external_sites"
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Posterous
2
+ class Like < Posterous::Model
3
+ parent :post_id
4
+ resource "/sites/:site_id/posts/:post_id/likes"
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Posterous
2
+ class Link < Posterous::Model
3
+ parent :link_category_id
4
+ resource "/sites/:site_id/link_categories/:link_category_id/links"
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ module Posterous
2
+ class LinkCategory < Posterous::Model
3
+ parent :site_id
4
+ resource "/sites/:site_id/link_categories"
5
+
6
+ many :links, Link
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ module Posterous
2
+ class Page < Posterous::Model
3
+ parent :site_id
4
+ resource "/sites/:site_id/pages"
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Posterous
2
+ class Post < Posterous::Model
3
+ parent :site_id
4
+ resource "/sites/:site_id/posts"
5
+
6
+ many :comments, Comment
7
+ many :likes, Like
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ module Posterous
2
+ class Profile < Posterous::Model
3
+ parent :site_id
4
+ resource "/sites/:site_id/profile"
5
+
6
+ def self.param_scope
7
+ 'site_profile'
8
+ end
9
+
10
+ def save
11
+ return if hash_for_update.empty?
12
+ @struct = self.class.post(parsed_resource_url, { param_scope => hash_for_update, '_method' => 'put' } )
13
+ changed_fields.clear
14
+ end
15
+
16
+ def destroy
17
+ self.class.delete(parsed_resource_url)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module Posterous
2
+ class Site < Posterous::Model
3
+ resource "/sites"
4
+
5
+ one :profile, Profile
6
+ many :tags, Tag
7
+ many :posts, Post
8
+ many :pages, Page
9
+ many :link_categories, LinkCategory
10
+ many :external_sites, ExternalSite
11
+ many :subscribers, Subscriber
12
+
13
+ def self.primary
14
+ find('primary')
15
+ end
16
+
17
+ def photos
18
+ self.class.get(parsed_resource_url + "#{self.id}/photos")
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ module Posterous
2
+ class Subscriber < Posterous::Model
3
+ parent :site_id
4
+ resource "/sites/:site_id/subscribers"
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ module Posterous
2
+ class Subscription < Posterous::Model
3
+ parent :user_id
4
+ resource "/users/:user_id/subscriptions"
5
+
6
+ def self.posts params={}
7
+ get(parsed_resource_url + '/posts',params).collect{|p| Post.new(p) }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ module Posterous
2
+ class Tag < Posterous::Model
3
+ parent :site_id
4
+ resource "/sites/:site_id/tags"
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ module Posterous
2
+ class User < Posterous::Model
3
+ resource "/users"
4
+ many :subscriptions, Subscription
5
+
6
+ def self.me
7
+ find('me')
8
+ end
9
+
10
+ def favorites
11
+ self.class.get("/users/#{self.id}/favorites").collect{|f| Post.new(f) }
12
+ end
13
+
14
+ def likes
15
+ self.class.get("/users/#{self.id}/likes").collect{|f| Like.new(f) }
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,3 @@
1
+ module Posterous
2
+ VERSION = "0.2.0"
3
+ end
data/lib/posterous.rb ADDED
@@ -0,0 +1,39 @@
1
+ %w[typhoeus yaml json ostruct].each { |lib| require lib }
2
+
3
+ module Posterous
4
+ BASE_API_URL = 'http://posterous.com/api/v2'
5
+
6
+ extend self
7
+
8
+ attr_reader :config
9
+
10
+ autoload :Connection, 'posterous/connection'
11
+ autoload :Inheritable, 'posterous/inheritable'
12
+ autoload :Model, 'posterous/model'
13
+ autoload :Tag, 'posterous/models/tag'
14
+ autoload :User, 'posterous/models/user'
15
+ autoload :Site, 'posterous/models/site'
16
+ autoload :Profile, 'posterous/models/profile'
17
+ autoload :Post, 'posterous/models/post'
18
+ autoload :Page, 'posterous/models/page'
19
+ autoload :Comment, 'posterous/models/comment'
20
+ autoload :Like, 'posterous/models/like'
21
+ autoload :Link, 'posterous/models/link'
22
+ autoload :LinkCategory, 'posterous/models/link_category'
23
+ autoload :ExternalSite, 'posterous/models/external_site'
24
+ autoload :Subscriber, 'posterous/models/subscriber'
25
+ autoload :Subscription, 'posterous/models/subscription'
26
+ autoload :AssociationProxy, 'posterous/association_proxy'
27
+
28
+
29
+ def config=(cfg)
30
+ @config = case
31
+ when cfg.is_a?(File)
32
+ YAML.load_file(cfg)
33
+ when cfg.is_a?(Hash)
34
+ cfg
35
+ else
36
+ raise ArgumentError, 'Invalid Config'
37
+ end
38
+ end
39
+ end
data/posterous.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "posterous/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "posterous"
7
+ s.version = Posterous::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Christopher Burnett @twoism"]
10
+ s.email = ["signalstatic@gmail.com"]
11
+ s.homepage = "http://github.com/posterous/posterous-gem"
12
+ s.summary = %q{Ruby wrapper for the Posterous REST API}
13
+ s.description = %q{Official Ruby wrapper for the Posterous REST API}
14
+
15
+ s.rubyforge_project = "posterous"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency 'typhoeus'
23
+ s.add_dependency 'json'
24
+ s.add_development_dependency 'rspec'
25
+ s.add_development_dependency 'ZenTest'
26
+ s.add_development_dependency 'autotest-growl'
27
+ s.add_development_dependency 'autotest-fsevent'
28
+ end