qiwi 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +85 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +69 -0
- data/Rakefile +42 -0
- data/VERSION +1 -0
- data/config/routes.rb +3 -0
- data/lib/qiwi.rb +11 -0
- data/lib/qiwi/IShopClientWS.wsdl +107 -0
- data/lib/qiwi/client.rb +106 -0
- data/lib/qiwi/config.rb +21 -0
- data/lib/qiwi/engine.rb +8 -0
- data/lib/qiwi/handler.rb +50 -0
- data/lib/qiwi/request.rb +144 -0
- data/lib/qiwi/response.rb +24 -0
- data/lib/qiwi/server.rb +78 -0
- data/lib/qiwi/transaction.rb +93 -0
- data/qiwi.gemspec +102 -0
- data/spec/client_spec.rb +107 -0
- data/spec/handler_spec.rb +38 -0
- data/spec/qiwi_spec.rb +4 -0
- data/spec/request_spec.rb +33 -0
- data/spec/server_spec.rb +83 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/transaction_spec.rb +41 -0
- metadata +285 -0
data/lib/qiwi/config.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Qiwi
|
4
|
+
|
5
|
+
class Config
|
6
|
+
attr_accessor :login, :password, :endpoint, :logger, :transaction_handler
|
7
|
+
|
8
|
+
def logger
|
9
|
+
@logger ||= Logger.new(STDERR)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.configure
|
14
|
+
yield config
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.config
|
18
|
+
@config ||= Config.new
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/qiwi/engine.rb
ADDED
data/lib/qiwi/handler.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'qiwi/transaction'
|
2
|
+
|
3
|
+
module Qiwi
|
4
|
+
class Handler
|
5
|
+
def self.call(txn, status)
|
6
|
+
new(txn, status).handle
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :txn, :status
|
10
|
+
def initialize(txn, status)
|
11
|
+
@txn = Transaction.new(txn)
|
12
|
+
@status = status
|
13
|
+
end
|
14
|
+
|
15
|
+
def handle
|
16
|
+
check_transaction
|
17
|
+
end
|
18
|
+
|
19
|
+
def check_transaction
|
20
|
+
unless txn.exists?
|
21
|
+
logger.error "Transaction doesn't exist: #{txn.txn}"
|
22
|
+
return 210
|
23
|
+
end
|
24
|
+
|
25
|
+
if status != txn.remote_status
|
26
|
+
logger.error "Stati don't match: #{txn.status} vs. #{status}"
|
27
|
+
return 300
|
28
|
+
end
|
29
|
+
|
30
|
+
unless txn.valid_amount?
|
31
|
+
logger.error "Incorrect amount: #{txn.amount}"
|
32
|
+
return 241
|
33
|
+
end
|
34
|
+
|
35
|
+
if txn.valid?
|
36
|
+
return 0
|
37
|
+
else
|
38
|
+
logger.error "Unknown error: #{txn.inspect}"
|
39
|
+
return 300
|
40
|
+
end
|
41
|
+
|
42
|
+
ensure
|
43
|
+
txn.commit!
|
44
|
+
end
|
45
|
+
|
46
|
+
def logger
|
47
|
+
Qiwi.logger
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/qiwi/request.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'active_model/validations'
|
3
|
+
|
4
|
+
module Qiwi
|
5
|
+
module Request
|
6
|
+
|
7
|
+
class Base
|
8
|
+
include ActiveModel::Validations
|
9
|
+
|
10
|
+
def self.inherited(klass)
|
11
|
+
klass.attributes :login, :password
|
12
|
+
klass.validates_presence_of :login, :password
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.attributes(*attrs)
|
16
|
+
if attrs.empty?
|
17
|
+
@_attributes
|
18
|
+
else
|
19
|
+
@_attributes ||= []
|
20
|
+
@_attributes += attrs
|
21
|
+
attr_accessor(*attrs)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# See concrete classes for parameters description.
|
26
|
+
#
|
27
|
+
# @param [Qiwi::Client] client
|
28
|
+
# @param [Hash] params
|
29
|
+
def initialize(client, params)
|
30
|
+
self.class.attributes.each { |attr| send(:"#{attr}=", params[attr]) }
|
31
|
+
@login, @password = client.login, client.password
|
32
|
+
end
|
33
|
+
|
34
|
+
def body
|
35
|
+
with_envelope { |xml| soap_body(xml) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def with_envelope(&block)
|
39
|
+
Nokogiri::XML::Builder.new do |xml|
|
40
|
+
xml.Envelope("xmlns:soapenv" => "http://www.w3.org/2003/05/soap-envelope",
|
41
|
+
"xmlns:tns" => "http://server.ishop.mw.ru/") do
|
42
|
+
xml.parent.namespace = xml.parent.namespace_definitions.first
|
43
|
+
xml.Header
|
44
|
+
xml.Body(&block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def soap_body(xml)
|
50
|
+
xml['tns'].send(method) do
|
51
|
+
self.class.attributes.each do |attr|
|
52
|
+
# Underscore, so 'comment' is used as a parameter
|
53
|
+
# some ugly way to remove the namespace
|
54
|
+
xml.send("#{attr}_", send(attr)).instance_variable_get(:@node).namespace = nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# The SOAP method name
|
60
|
+
def method
|
61
|
+
@method ||= self.class.to_s.split('::').last.camelize(:lower)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Make sense of what is returned by the server
|
65
|
+
#
|
66
|
+
# @param [Nokogiri::XML::Document] xml
|
67
|
+
def result_from_xml(xml)
|
68
|
+
xml.xpath("//#{method}Response/#{method}Result").text.to_i
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class CreateBill < Base
|
73
|
+
attributes :user, :amount, :comment, :txn, :lifetime, :alarm, :create
|
74
|
+
|
75
|
+
validates_presence_of :user, :amount, :txn
|
76
|
+
validates_numericality_of :amount
|
77
|
+
validates_length_of :comment, :maximum => 255
|
78
|
+
validates_length_of :txn, :maximum => 30
|
79
|
+
validates_format_of :lifetime, :with => /^\d{2}\.\d{2}\.\d{4}\s\d{2}:\d{2}:\d{2}$/, :allow_nil => true
|
80
|
+
|
81
|
+
# @param [Hash] params
|
82
|
+
# @option params [String] :user e.g. a phone number
|
83
|
+
# @option params [Float] :amount
|
84
|
+
# @option params [String] :comment
|
85
|
+
# @option params [String] :txn unique bill identifier
|
86
|
+
# @option params [String, Time] :lifetime in "dd.MM.yyyy HH:mm:ss" format
|
87
|
+
# @option params [Fixnum] :alarm
|
88
|
+
# @option params [Boolean] :create
|
89
|
+
def initialize(client, params)
|
90
|
+
super
|
91
|
+
@alarm ||= 0
|
92
|
+
@create = true if @create.nil?
|
93
|
+
@lifetime = @lifetime.strftime('%d.%m.%Y %H:%M:%S') if @lifetime.respond_to?(:strftime)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class CancelBill < Base
|
98
|
+
attributes :txn
|
99
|
+
end
|
100
|
+
|
101
|
+
class CheckBill < Base
|
102
|
+
attributes :txn
|
103
|
+
|
104
|
+
# @example
|
105
|
+
# {:user=>"name", :amount=>1000.0, :date=>"07.09.2012 13:33", :status=>60}
|
106
|
+
def result_from_xml(xml)
|
107
|
+
el = xml.xpath("//checkBillResponse")
|
108
|
+
OpenStruct.new({
|
109
|
+
user: el.at('user').text,
|
110
|
+
amount: el.at('amount').text.to_f,
|
111
|
+
date: el.at('date').text,
|
112
|
+
lifetime: el.at('lifetime').text,
|
113
|
+
status: el.at('status').text.to_i
|
114
|
+
})
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class GetBillList < Base
|
119
|
+
attributes :dateFrom, :dateTo, :status
|
120
|
+
|
121
|
+
validates_presence_of :status
|
122
|
+
validates_numericality_of :status
|
123
|
+
|
124
|
+
# @param [Hash] params
|
125
|
+
# @option params [String] :user e.g. a phone number
|
126
|
+
# @option params [Time] :date_from
|
127
|
+
# @option params [Time] :date_to
|
128
|
+
# @option params [Fixnum] :status
|
129
|
+
def initialize(client, hash)
|
130
|
+
super
|
131
|
+
@dateFrom = hash[:date_from].strftime('%d.%m.%Y %H:%M:%S') if hash[:date_from]
|
132
|
+
@dateTo = hash[:date_to].strftime('%d.%m.%Y %H:%M:%S') if hash[:date_to]
|
133
|
+
end
|
134
|
+
|
135
|
+
def result_from_xml(xml)
|
136
|
+
el = xml.xpath("//getBillListResponse")
|
137
|
+
OpenStruct.new({
|
138
|
+
txns: el.at('txns').text,
|
139
|
+
count: el.at('count').text.to_i
|
140
|
+
})
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Qiwi
|
2
|
+
module Response
|
3
|
+
STATUS = {
|
4
|
+
50 => :set,
|
5
|
+
52 => :being_processed,
|
6
|
+
60 => :paid,
|
7
|
+
150 => :cancelled_by_terminal,
|
8
|
+
151 => :cancelled_no_auth,
|
9
|
+
160 => :cancelled,
|
10
|
+
161 => :cancelled_expired
|
11
|
+
}
|
12
|
+
STATUS.default_proc = lambda do |hash, status|
|
13
|
+
if status >= 51 and status <= 59
|
14
|
+
hash[52]
|
15
|
+
elsif status < 50
|
16
|
+
hash[50]
|
17
|
+
elsif status > 100
|
18
|
+
hash[160]
|
19
|
+
else
|
20
|
+
:unknown
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/qiwi/server.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'qiwi/handler'
|
4
|
+
|
5
|
+
module Qiwi
|
6
|
+
class Server
|
7
|
+
attr_accessor :handler
|
8
|
+
|
9
|
+
# Creates a new instance of Rack application
|
10
|
+
#
|
11
|
+
# @param [String] login
|
12
|
+
# @param [String] password
|
13
|
+
# @param [Proc] handler must return 0 on success
|
14
|
+
def initialize(login = nil, password = nil, handler = nil)
|
15
|
+
@login = login || Qiwi.config.login
|
16
|
+
@password = password || Qiwi.config.password
|
17
|
+
@handler = handler || Qiwi::Handler
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(env)
|
21
|
+
@logger = env['rack.logger']
|
22
|
+
|
23
|
+
if env['QUERY_STRING'] == 'wsdl'
|
24
|
+
body = File.read(File.join(File.dirname(__FILE__), 'IShopClientWS.wsdl'))
|
25
|
+
else
|
26
|
+
body = env['rack.input'].read
|
27
|
+
result = handle_soap_body(body)
|
28
|
+
body = <<-EOF
|
29
|
+
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:cli="http://client.ishop.mw.ru/">
|
30
|
+
<soap:Body>
|
31
|
+
<cli:updateBillResponse>
|
32
|
+
<updateBillResult>#{result}</updateBillResult>
|
33
|
+
</cli:updateBillResponse>
|
34
|
+
</soap:Body>
|
35
|
+
</soap:Envelope>
|
36
|
+
EOF
|
37
|
+
end
|
38
|
+
headers = {'Content-Type' => 'application/soap+xml', 'Cache-Control' => 'no-cache'}
|
39
|
+
[200, headers, [body]]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def handle_soap_body(body)
|
44
|
+
xml = Nokogiri::XML(body).remove_namespaces!
|
45
|
+
nodeset = xml.xpath('//updateBill')
|
46
|
+
return 300 if nodeset.empty?
|
47
|
+
|
48
|
+
params = %w[login password txn status].each_with_object({}) do |field, h|
|
49
|
+
h[field.to_sym] = nodeset.at(field).text
|
50
|
+
end
|
51
|
+
|
52
|
+
unless authorized?(params)
|
53
|
+
logger.info "Unauthorized: #{params.inspect}"
|
54
|
+
return 150
|
55
|
+
end
|
56
|
+
|
57
|
+
txn, status = params.values_at(:txn, :status)
|
58
|
+
handler.call(txn, status.to_i).tap do |res|
|
59
|
+
logger.info "Qiwi handler returned #{res}"
|
60
|
+
end
|
61
|
+
rescue => e
|
62
|
+
# Unknown error
|
63
|
+
logger.error "Error: #{e.message}\n#{e.backtrace.slice(0,2).join("\n")}"
|
64
|
+
return 300
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check if the password matches
|
68
|
+
def authorized?(params)
|
69
|
+
params[:login] == @login and
|
70
|
+
params[:password] == Digest::MD5.hexdigest(params[:txn] + Digest::MD5.hexdigest(@password).upcase).upcase
|
71
|
+
end
|
72
|
+
|
73
|
+
def logger
|
74
|
+
@logger ||= Logger.new(STDERR)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'qiwi/client'
|
2
|
+
require 'observer'
|
3
|
+
require 'active_model'
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
5
|
+
|
6
|
+
module Qiwi
|
7
|
+
class Transaction
|
8
|
+
include Observable
|
9
|
+
include ActiveModel::Validations
|
10
|
+
|
11
|
+
class CorrectAmountValidator < ActiveModel::Validator
|
12
|
+
def validate(txn)
|
13
|
+
unless txn.remote_amount.to_i == txn.amount.to_i
|
14
|
+
txn.errors.add(:amount, :invalid)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
validates_presence_of :txn
|
20
|
+
validates_presence_of :persisted, :remote, :message => 'transaction not found'
|
21
|
+
validates :amount, :numericality => true, :correct_amount => true
|
22
|
+
|
23
|
+
delegate :amount, :status, :to => :remote, :prefix => true, :allow_nil => true
|
24
|
+
|
25
|
+
# Transaction id
|
26
|
+
attr_reader :txn
|
27
|
+
|
28
|
+
# Finder should respond_to?(:find_by_txn) and return an object,
|
29
|
+
# which can respond_to?(:amount)
|
30
|
+
attr_accessor :finder
|
31
|
+
|
32
|
+
def initialize(txn)
|
33
|
+
@txn = txn
|
34
|
+
|
35
|
+
# A logging observer
|
36
|
+
add_observer(self, :log_transaction)
|
37
|
+
|
38
|
+
if block_given?
|
39
|
+
yield self
|
40
|
+
else
|
41
|
+
Qiwi.config.transaction_handler.call(self) if Qiwi.config.transaction_handler
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def inspect
|
46
|
+
error_msgs = errors.full_messages.join(', ')
|
47
|
+
%{<Qiwi::Transaction id: #{txn}, remote: #{remote.inspect} persisted: #{persisted.inspect} errors: #{error_msgs}}
|
48
|
+
end
|
49
|
+
|
50
|
+
def commit!
|
51
|
+
changed
|
52
|
+
notify_observers(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
def exists?
|
56
|
+
!!persisted
|
57
|
+
end
|
58
|
+
|
59
|
+
def valid_amount?
|
60
|
+
valid?
|
61
|
+
errors[:amount].empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
def amount
|
65
|
+
persisted.amount if exists?
|
66
|
+
end
|
67
|
+
|
68
|
+
def log_transaction(transaction)
|
69
|
+
Qiwi.logger.info "Transaction update: #{transaction.inspect}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def remote
|
73
|
+
@remote ||= Qiwi::Client.new.check_bill(txn: txn)
|
74
|
+
end
|
75
|
+
|
76
|
+
def persisted
|
77
|
+
@persisted ||= find(txn)
|
78
|
+
end
|
79
|
+
|
80
|
+
def find(txn)
|
81
|
+
finder.find_by_txn(txn) if finder
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# # Example:
|
88
|
+
# Qiwi.configure do |config|
|
89
|
+
# config.transaction_handler = lambda do |txn|
|
90
|
+
# txn.finder = PendingTransactions
|
91
|
+
# txn.add_observer(TransactionHandler.new)
|
92
|
+
# end
|
93
|
+
# end
|
data/qiwi.gemspec
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "qiwi"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Roman Shterenzon"]
|
12
|
+
s.date = "2012-10-31"
|
13
|
+
s.description = "Qiwi payments solution"
|
14
|
+
s.email = "romanbsd@yahoo.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".rspec",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"Guardfile",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.md",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"config/routes.rb",
|
29
|
+
"lib/qiwi.rb",
|
30
|
+
"lib/qiwi/IShopClientWS.wsdl",
|
31
|
+
"lib/qiwi/client.rb",
|
32
|
+
"lib/qiwi/config.rb",
|
33
|
+
"lib/qiwi/engine.rb",
|
34
|
+
"lib/qiwi/handler.rb",
|
35
|
+
"lib/qiwi/request.rb",
|
36
|
+
"lib/qiwi/response.rb",
|
37
|
+
"lib/qiwi/server.rb",
|
38
|
+
"lib/qiwi/transaction.rb",
|
39
|
+
"qiwi.gemspec",
|
40
|
+
"spec/client_spec.rb",
|
41
|
+
"spec/handler_spec.rb",
|
42
|
+
"spec/qiwi_spec.rb",
|
43
|
+
"spec/request_spec.rb",
|
44
|
+
"spec/server_spec.rb",
|
45
|
+
"spec/spec_helper.rb",
|
46
|
+
"spec/transaction_spec.rb"
|
47
|
+
]
|
48
|
+
s.homepage = "http://github.com/romanbsd/qiwi"
|
49
|
+
s.licenses = ["MIT"]
|
50
|
+
s.require_paths = ["lib"]
|
51
|
+
s.rubygems_version = "1.8.24"
|
52
|
+
s.summary = "Qiwi payments solution"
|
53
|
+
|
54
|
+
if s.respond_to? :specification_version then
|
55
|
+
s.specification_version = 3
|
56
|
+
|
57
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
58
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
|
59
|
+
s.add_runtime_dependency(%q<activemodel>, [">= 0"])
|
60
|
+
s.add_runtime_dependency(%q<nokogiri>, [">= 0"])
|
61
|
+
s.add_runtime_dependency(%q<faraday>, [">= 0"])
|
62
|
+
s.add_runtime_dependency(%q<rack>, [">= 0"])
|
63
|
+
s.add_development_dependency(%q<webmock>, [">= 0"])
|
64
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.11.0"])
|
65
|
+
s.add_development_dependency(%q<yard>, ["~> 0.7"])
|
66
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
67
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
|
68
|
+
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
69
|
+
s.add_development_dependency(%q<rb-fsevent>, ["~> 0.9.1"])
|
70
|
+
s.add_development_dependency(%q<growl>, [">= 0"])
|
71
|
+
else
|
72
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
73
|
+
s.add_dependency(%q<activemodel>, [">= 0"])
|
74
|
+
s.add_dependency(%q<nokogiri>, [">= 0"])
|
75
|
+
s.add_dependency(%q<faraday>, [">= 0"])
|
76
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
77
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
78
|
+
s.add_dependency(%q<rspec>, ["~> 2.11.0"])
|
79
|
+
s.add_dependency(%q<yard>, ["~> 0.7"])
|
80
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
81
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
82
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
83
|
+
s.add_dependency(%q<rb-fsevent>, ["~> 0.9.1"])
|
84
|
+
s.add_dependency(%q<growl>, [">= 0"])
|
85
|
+
end
|
86
|
+
else
|
87
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
88
|
+
s.add_dependency(%q<activemodel>, [">= 0"])
|
89
|
+
s.add_dependency(%q<nokogiri>, [">= 0"])
|
90
|
+
s.add_dependency(%q<faraday>, [">= 0"])
|
91
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
92
|
+
s.add_dependency(%q<webmock>, [">= 0"])
|
93
|
+
s.add_dependency(%q<rspec>, ["~> 2.11.0"])
|
94
|
+
s.add_dependency(%q<yard>, ["~> 0.7"])
|
95
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
96
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
|
97
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
98
|
+
s.add_dependency(%q<rb-fsevent>, ["~> 0.9.1"])
|
99
|
+
s.add_dependency(%q<growl>, [">= 0"])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|