posterous 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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