spire_io 1.0.0.beta.3 → 1.0.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.
@@ -8,13 +8,14 @@ class Spire
8
8
 
9
9
  define_request(:billing_subscription) do |info|
10
10
  billing = properties["billing"]
11
+ capability = billing["capabilities"]["subscribe"]
11
12
  {
12
13
  :method => :put,
13
14
  :url => billing["url"],
14
15
  :body => info.to_json,
15
16
  :headers => {
16
17
  "Accept" => media_type, "Content-Type" => media_type,
17
- "Authorization" => "Capability #{billing["capability"]}"
18
+ "Authorization" => "Capability #{capability}"
18
19
  }
19
20
  }
20
21
  end
@@ -12,7 +12,7 @@ class Spire
12
12
  :url => @url,
13
13
  :body => string,
14
14
  :headers => {
15
- "Authorization" => "Capability #{@capability}",
15
+ "Authorization" => "Capability #{@capabilities["publish"]}",
16
16
  "Accept" => @spire.mediaType("message"),
17
17
  "Content-Type" => @spire.mediaType("message")
18
18
  }
@@ -0,0 +1,9 @@
1
+ class Spire
2
+ class API
3
+ class Message < Resource
4
+ def resource_name
5
+ "message"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -8,7 +8,7 @@ class Spire
8
8
  :method => :get,
9
9
  :url => @url,
10
10
  :headers => {
11
- "Authorization" => "Capability #{@capability}",
11
+ "Authorization" => "Capability #{@capabilities["get"]}",
12
12
  "Accept" => media_type
13
13
  }
14
14
  }
@@ -20,7 +20,7 @@ class Spire
20
20
  :url => @url,
21
21
  :body => properties.to_json,
22
22
  :headers => {
23
- "Authorization" => "Capability #{@capability}",
23
+ "Authorization" => "Capability #{@capabilities["update"]}",
24
24
  "Accept" => media_type,
25
25
  "Content-Type" => media_type
26
26
  }
@@ -32,20 +32,20 @@ class Spire
32
32
  :method => :delete,
33
33
  :url => @url,
34
34
  :headers => {
35
- "Authorization" => "Capability #{@capability}",
35
+ "Authorization" => "Capability #{@capabilities["delete"]}",
36
36
  "Accept" => media_type,
37
37
  "Content-Type" => media_type,
38
38
  }
39
39
  }
40
40
  end
41
41
 
42
- attr_reader :url, :capability, :properties
42
+ attr_reader :url, :properties, :capabilities, :capability
43
43
 
44
44
  def initialize(spire, data)
45
45
  @spire = spire
46
46
  @client = spire.client
47
47
  @url = data["url"]
48
- @capability = data["capability"]
48
+ @capabilities = data["capabilities"]
49
49
  @properties = data
50
50
  end
51
51
 
@@ -4,25 +4,15 @@ class Spire
4
4
  class Session
5
5
  include Requestable
6
6
 
7
- define_request(:account) do
8
- resource = @resources["account"]
9
- {
10
- :method => :get,
11
- :url => resource["url"],
12
- :headers => {
13
- "Authorization" => "Capability #{resource["capability"]}",
14
- "Accept" => @spire.mediaType("account")
15
- }
16
- }
17
- end
18
-
19
7
  define_request(:channels) do
20
8
  collection = @resources["channels"]
9
+ capability = collection["capabilities"]["all"]
10
+ url = collection["url"]
21
11
  request = {
22
12
  :method => :get,
23
13
  :url => collection["url"],
24
14
  :headers => {
25
- "Authorization" => "Capability #{collection["capability"]}",
15
+ "Authorization" => "Capability #{capability}",
26
16
  "Accept" => @spire.mediaType("channels"),
27
17
  }
28
18
  }
@@ -30,12 +20,14 @@ class Spire
30
20
 
31
21
  define_request(:channel_by_name) do |name|
32
22
  collection = @resources["channels"]
23
+ capability = collection["capabilities"]["get_by_name"]
24
+ url = collection["url"]
33
25
  request = {
34
26
  :method => :get,
35
- :url => collection["url"],
27
+ :url => url,
36
28
  :query => {:name => name},
37
29
  :headers => {
38
- "Authorization" => "Capability #{collection["capability"]}",
30
+ "Authorization" => "Capability #{capability}",
39
31
  "Accept" => @spire.mediaType("channels"),
40
32
  }
41
33
  }
@@ -43,43 +35,76 @@ class Spire
43
35
 
44
36
  define_request(:create_channel) do |name|
45
37
  collection = @resources["channels"]
38
+ capability = collection["capabilities"]["create"]
39
+ url = collection["url"]
46
40
  {
47
41
  :method => :post,
48
- :url => collection["url"],
42
+ :url => url,
49
43
  :body => { :name => name }.to_json,
50
44
  :headers => {
51
- "Authorization" => "Capability #{collection["capability"]}",
45
+ "Authorization" => "Capability #{capability}",
52
46
  "Accept" => @spire.mediaType("channel"),
53
47
  "Content-Type" => @spire.mediaType("channel")
54
48
  }
55
49
  }
56
50
  end
57
51
 
52
+ define_request(:subscriptions) do
53
+ collection = @resources["subscriptions"]
54
+ capability = collection["capabilities"]["all"]
55
+ url = collection["url"]
56
+ {
57
+ :method => :get,
58
+ :url => url,
59
+ :headers => {
60
+ "Authorization" => "Capability #{capability}",
61
+ "Accept" => @spire.mediaType("subscriptions"),
62
+ }
63
+ }
64
+ end
65
+
58
66
  define_request(:create_subscription) do |subscription_name, channel_urls|
59
67
  collection = @resources["subscriptions"]
68
+ capability = collection["capabilities"]["create"]
69
+ url = collection["url"]
60
70
  {
61
71
  :method => :post,
62
- :url => collection["url"],
72
+ :url => url,
63
73
  :body => {
64
74
  :channels => channel_urls,
65
75
  :name => subscription_name
66
76
  }.to_json,
67
77
  :headers => {
68
- "Authorization" => "Capability #{collection["capability"]}",
78
+ "Authorization" => "Capability #{capability}",
69
79
  "Accept" => @spire.mediaType("subscription"),
70
80
  "Content-Type" => @spire.mediaType("subscription")
71
81
  }
72
82
  }
73
83
  end
74
84
 
75
- attr_reader :url, :resources, :schema, :capability
85
+ define_request(:subscription_by_name) do |name|
86
+ collection = @resources["subscriptions"]
87
+ capability = collection["capabilities"]["get_by_name"]
88
+ url = collection["url"]
89
+ request = {
90
+ :method => :get,
91
+ :url => url,
92
+ :query => {:name => name},
93
+ :headers => {
94
+ "Authorization" => "Capability #{capability}",
95
+ "Accept" => @spire.mediaType("subscriptions"),
96
+ }
97
+ }
98
+ end
99
+
100
+ attr_reader :url, :resources, :schema, :capabilities, :capability
76
101
 
77
102
  def initialize(spire, data)
78
103
  @spire = spire
79
104
  @client = spire.client
80
105
  @schema = spire.schema["session"]
81
106
  @url = data["url"]
82
- @capability = data["capability"]
107
+ @capabilities = data["capabilities"]
83
108
  @resources = data["resources"]
84
109
  end
85
110
 
@@ -107,7 +132,11 @@ class Spire
107
132
  raise "Error creating Subscription: (#{response.status}) #{response.body}"
108
133
  end
109
134
  data = response.data
110
- subscriptions[data["name"]] = API::Subscription.new(@spire, data)
135
+ subscription = API::Subscription.new(@spire, data)
136
+ if subscription_name
137
+ subscriptions[data["name"]] = subscription
138
+ end
139
+ subscription
111
140
  end
112
141
 
113
142
  def channels!
@@ -127,17 +156,6 @@ class Spire
127
156
  @channels ||= channels!
128
157
  end
129
158
 
130
- define_request(:subscriptions) do
131
- {
132
- :method => :get,
133
- :url => @resources["subscriptions"]["url"],
134
- :headers => {
135
- "Authorization" => "Capability #{@resources["subscriptions"]["capability"]}",
136
- "Accept" => @spire.mediaType("subscriptions"),
137
- }
138
- }
139
- end
140
-
141
159
  def subscriptions
142
160
  @subscriptions ||= subscriptions!
143
161
  end
@@ -19,7 +19,7 @@ class Spire
19
19
  "delay" => options[:delay]
20
20
  },
21
21
  :headers => {
22
- "Authorization" => "Capability #{@capability}",
22
+ "Authorization" => "Capability #{@capabilities["messages"]}",
23
23
  "Accept" => @spire.mediaType("events")
24
24
  }
25
25
  }
@@ -43,8 +43,10 @@ class Spire
43
43
  unless response.status == 200
44
44
  raise "Error retrieving messages from #{self.class.name}: (#{response.status}) #{response.body}"
45
45
  end
46
- messages = response.data["messages"]
47
- @last = messages.last["timestamp"] unless messages.empty?
46
+ messages = response.data["messages"].map do |message|
47
+ API::Message.new(@spire, message)
48
+ end
49
+ @last = messages.last.timestamp unless messages.empty?
48
50
  messages.each do |message|
49
51
  listeners.each do |listener|
50
52
  listener.call(message)
data/lib/spire/api.rb CHANGED
@@ -10,6 +10,7 @@ require "spire/api/session"
10
10
  require "spire/api/account"
11
11
  require "spire/api/channel"
12
12
  require "spire/api/subscription"
13
+ require "spire/api/message"
13
14
 
14
15
  class Spire
15
16
 
@@ -39,11 +40,11 @@ class Spire
39
40
  }
40
41
  end
41
42
 
42
- define_request(:create_session) do |key|
43
+ define_request(:create_session) do |secret|
43
44
  {
44
45
  :method => :post,
45
46
  :url => @description["resources"]["sessions"]["url"],
46
- :body => {:key => key}.to_json,
47
+ :body => {:secret => secret}.to_json,
47
48
  :headers => {
48
49
  "Accept" => mediaType("session"),
49
50
  "Content-Type" => mediaType("account")
@@ -110,9 +111,9 @@ class Spire
110
111
  schema[name]["mediaType"]
111
112
  end
112
113
 
113
- def create_session(key)
114
- response = request(:create_session, key)
115
- raise "Error starting a key-based session" if response.status != 201
114
+ def create_session(secret)
115
+ response = request(:create_session, secret)
116
+ raise "Error starting a secret-based session" if response.status != 201
116
117
  session_data = response.data
117
118
  API::Session.new(self, session_data)
118
119
  end
@@ -0,0 +1,59 @@
1
+ require "spire/commands/register"
2
+ require "spire/commands/console"
3
+
4
+ class Spire
5
+ module Commands
6
+
7
+ # This is the spire command
8
+ module CLI
9
+
10
+ COMMANDS = {
11
+ "register" => Spire::Commands::Register,
12
+ "console" => Spire::Commands::Console
13
+ }
14
+
15
+ def self.run(subcommand,*args)
16
+ if command = COMMANDS[subcommand]
17
+ begin
18
+ command.run(args)
19
+ rescue => e
20
+ $stderr.puts "spire: #{e.message}"
21
+ exit(-1)
22
+ end
23
+ else
24
+ usage "#{subcommand} is not a supported command"
25
+ end
26
+ end
27
+
28
+ def self.rc
29
+ @rc ||= (YAML.load_file(File.expand_path("~/.spirerc")) rescue {})
30
+ end
31
+
32
+ def self.save_rc
33
+ File.open(File.expand_path("~/.spirerc"),"w") { |f| YAML.dump(rc,f) }
34
+ end
35
+
36
+ def self.url
37
+ rc["url"]||"https://api.spire.io"
38
+ end
39
+
40
+ def self.usage(message)
41
+ $stderr.puts "spire: #{message}"
42
+ $stderr.puts <<-eos
43
+ Usage: spire <subcommand> <options>
44
+
45
+ The spire command provides command line access to the spire.io API.
46
+
47
+ Valid commands:
48
+
49
+ register Register a new account
50
+ console Open up an IRB session with an open spire session.
51
+
52
+ You can get more options for any command with --help or -h.
53
+ eos
54
+ exit(-1)
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,32 @@
1
+ require "spire_io"
2
+ require "spire/commands/mixins/authenticate"
3
+ require "spire/commands/mixins/help"
4
+ require "mixlib/cli"
5
+ require "highline/import"
6
+
7
+ require "irb"
8
+ require "irb/completion"
9
+
10
+ class Spire
11
+ module Commands
12
+
13
+ # This is the spire command
14
+ class Console
15
+
16
+ include Mixins::Authenticate
17
+ include Mixins::Help
18
+
19
+ def self.run(args)
20
+ self.new.run(args)
21
+ end
22
+
23
+ def run(args)
24
+ parse_options(args)
25
+ $spire = connect
26
+ ARGV.clear
27
+ IRB.start
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ require "yaml"
2
+
3
+ class Spire
4
+ module Commands
5
+ module Mixins
6
+ module Authenticate
7
+
8
+ def self.included(target)
9
+ target.module_eval do
10
+
11
+ include Mixlib::CLI
12
+
13
+ option :email,
14
+ :short => "-e EMAIL",
15
+ :long => "--email EMAIL",
16
+ :description => "The email for your account"
17
+
18
+ option :password,
19
+ :short => "-p PASSWORD",
20
+ :long => "--password PASSWORD",
21
+ :description => "The password for your account"
22
+
23
+ option :secret,
24
+ :short => "-s SECRET",
25
+ :long => "--secret SECRET",
26
+ :description => "The account secret for your account"
27
+ end
28
+
29
+ end
30
+
31
+ def connect
32
+ if config[:email]
33
+ if !config[:password]
34
+ config[:password] = ask("Password:") { |q| q.echo = false}
35
+ end
36
+ spire = Spire.new(CLI.url)
37
+ spire.login(config[:email],config[:password])
38
+ return spire
39
+ else
40
+ if !config[:secret]
41
+ if CLI.rc["secret"]
42
+ config[:secret] = CLI.rc["secret"]
43
+ else
44
+ raise "No secret provided or found in ~/.spirerc"
45
+ end
46
+ end
47
+ spire = Spire.new(CLI.url)
48
+ spire.start(config[:secret])
49
+ return spire
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+
@@ -0,0 +1,23 @@
1
+ class Spire
2
+ module Commands
3
+ module Mixins
4
+ module Help
5
+ def self.included(target)
6
+ target.module_eval do
7
+ include Mixlib::CLI
8
+ option :help,
9
+ :short => "-h",
10
+ :long => "--help",
11
+ :description => "Show this message",
12
+ :on => :tail,
13
+ :boolean => true,
14
+ :show_options => true,
15
+ :exit => 0
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,42 @@
1
+ require "yaml"
2
+ require "spire_io"
3
+ require "spire/commands/mixins/help"
4
+ require "mixlib/cli"
5
+
6
+ class Spire
7
+ module Commands
8
+
9
+ # This is the spire command
10
+ class Register
11
+
12
+ include Mixlib::CLI
13
+ include Mixins::Help
14
+
15
+ option :email,
16
+ :short => "-e EMAIL",
17
+ :long => "--email EMAIL",
18
+ :required => true,
19
+ :description => "The email for the account you're registering"
20
+
21
+ option :password,
22
+ :short => "-p PASSWORD",
23
+ :long => "--password PASSWORD",
24
+ :required => true,
25
+ :description => "Set the password for managing this account"
26
+
27
+ def self.run(args)
28
+ self.new.run(args)
29
+ end
30
+
31
+ def run(args)
32
+ parse_options(args)
33
+ spire = Spire.new(CLI.url)
34
+ spire.register(:email => config[:email], :password => config[:password])
35
+ CLI.rc["secret"] = spire.secret
36
+ CLI.save_rc
37
+ $stdout.puts spire.secret
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1 @@
1
+ require "spire/commands/cli"
data/lib/spire_io.rb CHANGED
@@ -18,8 +18,8 @@ class Spire
18
18
  discover
19
19
  end
20
20
 
21
- def key
22
- @session.resources["account"]["key"]
21
+ def secret
22
+ @session.resources["account"]["secret"]
23
23
  end
24
24
 
25
25
  def discover
@@ -27,8 +27,8 @@ class Spire
27
27
  self
28
28
  end
29
29
 
30
- def start(key)
31
- @session = @api.create_session(key)
30
+ def start(secret)
31
+ @session = @api.create_session(secret)
32
32
  self
33
33
  end
34
34
 
@@ -96,7 +96,7 @@ class Spire
96
96
  return channel
97
97
  else
98
98
  @channel_error_counts[name] += 1
99
- retry unless @channel_error_counts >= RETRY_CREATION_LIMIT
99
+ retry unless @channel_error_counts[name] >= RETRY_CREATION_LIMIT
100
100
  end
101
101
 
102
102
  else
@@ -126,6 +126,7 @@ class Spire
126
126
  if subscription = @session.subscriptions![subscription_name]
127
127
  return subscription
128
128
  else
129
+ @subscription_error_counts[subscription_name] += 1
129
130
  retry unless @subscription_error_counts >= RETRY_CREATION_LIMIT
130
131
  end
131
132
 
@@ -163,7 +164,7 @@ class Spire
163
164
  #
164
165
  # You can get a channel object by calling [] on a Spire object
165
166
  # * spire = Spire.new
166
- # * spire.start("your api key")
167
+ # * spire.start("your api secret")
167
168
  # * channel = spire["channel name"]
168
169
  class Channel < SimpleDelegator
169
170
  def initialize(spire, channel)
@@ -191,7 +192,7 @@ class Spire
191
192
  # by calling subscribe on a channel object
192
193
  #
193
194
  # * spire = Spire.new
194
- # * spire.start("your api key")
195
+ # * spire.start("your api secret")
195
196
  # *THEN*
196
197
  # * subscription = spire.subscribe("subscription name", "channel name")
197
198
  # *OR*
@@ -215,11 +216,11 @@ class Spire
215
216
  # wraps the underlying Subscription#add_listener to
216
217
  # provided named listeners, threading, and a
217
218
  # stop_listening method.
218
- def add_listener(name=nil, &block)
219
+ def add_listener(listener_name = nil, &block)
219
220
  raise ArgumentError unless block_given?
220
- name ||= generate_listener_name
221
+ listener_name ||= generate_listener_name
221
222
  listener = wrap_listener(&block)
222
- listeners[name] = listener
223
+ listeners[listener_name] = listener
223
224
  __getobj__.add_listener(&listener)
224
225
  end
225
226
 
@@ -227,8 +228,8 @@ class Spire
227
228
  if arg.is_a? String
228
229
  listener = listeners.delete(arg)
229
230
  else
230
- name, _listener = listeners.detect {|k,v| v == arg }
231
- listener = listeners.delete(name)
231
+ listener_name, _listener = listeners.detect {|k,v| v == arg }
232
+ listener = listeners.delete(listener_name)
232
233
  end
233
234
 
234
235
  if listener
metadata CHANGED
@@ -1,15 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spire_io
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3899554913
5
- prerelease: 6
4
+ hash: 23
5
+ prerelease: false
6
6
  segments:
7
7
  - 1
8
8
  - 0
9
9
  - 0
10
- - beta
11
- - 3
12
- version: 1.0.0.beta.3
10
+ version: 1.0.0
13
11
  platform: ruby
14
12
  authors:
15
13
  - Dan Yoder
@@ -19,7 +17,7 @@ autorequire:
19
17
  bindir: bin
20
18
  cert_chain: []
21
19
 
22
- date: 2012-02-03 00:00:00 -08:00
20
+ date: 2012-03-08 00:00:00 -08:00
23
21
  default_executable:
24
22
  dependencies:
25
23
  - !ruby/object:Gem::Dependency
@@ -53,9 +51,41 @@ dependencies:
53
51
  type: :runtime
54
52
  version_requirements: *id002
55
53
  - !ruby/object:Gem::Dependency
56
- name: rspec
54
+ name: mixlib-cli
57
55
  prerelease: false
58
56
  requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 27
62
+ segments:
63
+ - 1
64
+ - 2
65
+ - 2
66
+ version: 1.2.2
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: highline
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 25
78
+ segments:
79
+ - 1
80
+ - 6
81
+ - 11
82
+ version: 1.6.11
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: rspec
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
59
89
  none: false
60
90
  requirements:
61
91
  - - ~>
@@ -66,11 +96,11 @@ dependencies:
66
96
  - 7
67
97
  version: "2.7"
68
98
  type: :development
69
- version_requirements: *id003
99
+ version_requirements: *id005
70
100
  - !ruby/object:Gem::Dependency
71
101
  name: yard
72
102
  prerelease: false
73
- requirement: &id004 !ruby/object:Gem::Requirement
103
+ requirement: &id006 !ruby/object:Gem::Requirement
74
104
  none: false
75
105
  requirements:
76
106
  - - ~>
@@ -81,11 +111,11 @@ dependencies:
81
111
  - 7
82
112
  version: "0.7"
83
113
  type: :development
84
- version_requirements: *id004
114
+ version_requirements: *id006
85
115
  - !ruby/object:Gem::Dependency
86
116
  name: redcarpet
87
117
  prerelease: false
88
- requirement: &id005 !ruby/object:Gem::Requirement
118
+ requirement: &id007 !ruby/object:Gem::Requirement
89
119
  none: false
90
120
  requirements:
91
121
  - - ~>
@@ -97,7 +127,7 @@ dependencies:
97
127
  - 0
98
128
  version: 2.1.0
99
129
  type: :development
100
- version_requirements: *id005
130
+ version_requirements: *id007
101
131
  description: "\t\tThe spire_io gem allows you to quickly and easily use the spire.io service\n\
102
132
  \t\tusing Ruby. See http://www.spire.io/ for more.\n"
103
133
  email:
@@ -113,11 +143,18 @@ extra_rdoc_files: []
113
143
  files:
114
144
  - lib/spire/api/account.rb
115
145
  - lib/spire/api/channel.rb
146
+ - lib/spire/api/message.rb
116
147
  - lib/spire/api/requestable.rb
117
148
  - lib/spire/api/resource.rb
118
149
  - lib/spire/api/session.rb
119
150
  - lib/spire/api/subscription.rb
120
151
  - lib/spire/api.rb
152
+ - lib/spire/commands/cli.rb
153
+ - lib/spire/commands/console.rb
154
+ - lib/spire/commands/mixins/authenticate.rb
155
+ - lib/spire/commands/mixins/help.rb
156
+ - lib/spire/commands/register.rb
157
+ - lib/spire/commands.rb
121
158
  - lib/spire_io.rb
122
159
  has_rdoc: true
123
160
  homepage: https://github.com/spire-io/spire.io.rb
@@ -140,18 +177,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
140
177
  required_rubygems_version: !ruby/object:Gem::Requirement
141
178
  none: false
142
179
  requirements:
143
- - - ">"
180
+ - - ">="
144
181
  - !ruby/object:Gem::Version
145
- hash: 25
182
+ hash: 3
146
183
  segments:
147
- - 1
148
- - 3
149
- - 1
150
- version: 1.3.1
184
+ - 0
185
+ version: "0"
151
186
  requirements: []
152
187
 
153
188
  rubyforge_project:
154
- rubygems_version: 1.6.2
189
+ rubygems_version: 1.3.7
155
190
  signing_key:
156
191
  specification_version: 3
157
192
  summary: Ruby client for spire.io