statsmix 0.0.6 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -8,6 +8,38 @@ StatsMix makes it easy to track, chart, and share application and business metri
8
8
 
9
9
  To get started, you'll need a API key for StatsMix. You can get a free developer account here: http://www.statsmix.com/try?plan=developer
10
10
 
11
+ Detail documentation is at http://www.statsmix.com/developers/ruby_gem
12
+
13
+ == Quick Start ==
14
+
15
+ require "statsmix"
16
+ StatsMix.api_key = "YOUR API KEY"
17
+
18
+ #push a stat with the value 1 (default) to a metric called "My First Metric"
19
+ StatsMix.track("My First Metric")
20
+
21
+ #push the value 20
22
+ StatsMix.track("My First Metric",20)
23
+
24
+ #add metadata - you can use this to add granularity to your chart via Chart Settings in StatsMix
25
+ #this example tracks file uploads by file type
26
+ StatsMix.track("File Uploads", 1, {:meta => {"file type" => "PDF"}})
27
+
28
+ #if you need the ability to update a stat after the fact, you can pass in a unique identifier ref_id (scoped to that metric)
29
+ StatsMix.track("File Uploads", 1, {:ref_id => "abc123", :meta => {"file type" => "PDF"}})
30
+
31
+ #if you need to timestamp the stat for something other than now, pass in a UTC datetime called generated_at
32
+ StatsMix.track("File Uploads", 1, {:generated_at => 1.days.ago })
33
+
34
+ #to turn off tracking in your development environment
35
+ StatsMix.ignore = true
36
+
37
+ #to redirect all stats in dev environment to a test metric
38
+ StatsMix.test_metric_name = "My Test Metric"
39
+
40
+ == More Documentation ==
41
+
42
+ The StatsMix gem supports all the methods documented at http://www.statsmix.com/developers/documentation
11
43
 
12
44
  == Contributing to statsmix
13
45
 
data/Rakefile CHANGED
@@ -17,8 +17,8 @@ Jeweler::Tasks.new do |gem|
17
17
  gem.license = "MIT"
18
18
  gem.summary = %Q{A Ruby gem for the StatsMix API.}
19
19
  gem.description = %Q{A Ruby gem for the StatsMix API - http://www.statsmix.com/developers}
20
- gem.email = "tmarkiewicz@gmail.com"
21
- gem.authors = ["Tom Markiewicz"]
20
+ gem.email = ["tmarkiewicz@gmail.com","me@derekscruggs.com"]
21
+ gem.authors = ["Tom Markiewicz","Derek Scruggs"]
22
22
  # Include your dependencies below. Runtime dependencies are required when using your gem,
23
23
  # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
24
  # gem.add_runtime_dependency 'jabber4r', '> 0.1'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 0.1.3
data/lib/statsmix.rb CHANGED
@@ -1,128 +1,102 @@
1
1
  require 'net/http'
2
+ require 'rubygems'
3
+ require 'json'
2
4
 
3
5
  class StatsMix
4
6
 
5
- BASE_URI = 'http://www.statsmix.com/api/v2/'
6
- # BASE_URI = 'http://localhost:3000/api/v2/'
7
+ BASE_URI = 'https://statsmix.com/api/v2/'
7
8
 
8
9
  GEM_VERSION = File.exist?('../VERSION') ? File.read('../VERSION') : ""
9
-
10
- def initialize(api_key, format = 'xml')
11
- @api_key = api_key
12
- @format = format
13
- @user_agent = "StatsMix Ruby Gem " + GEM_VERSION
14
- end
15
-
16
- def connect(resource)
17
- # Resources available: stats, metrics, TODO: profiles
18
- @url = URI.parse(BASE_URI + resource)
19
- @connection = Net::HTTP.new(@url.host, @url.port)
20
-
10
+
11
+ # Track an event
12
+ #
13
+ # Required: name of metric
14
+ # Optional: value, options {:generated_at}
15
+ # Returns: Net::HTTP object
16
+ def self.track(name, value = nil, options = {})
17
+ self.connect('track')
18
+ @request_uri = @url.path + '.' + @format
19
+ @request = Net::HTTP::Get.new(@request_uri)
20
+ @params[:name] = name
21
+ if @test_metric_name
22
+ @params[:name] = @test_metric_name
23
+ end
24
+ @params[:value] = value if value != nil
25
+ @params.merge!(options)
26
+ @params[:meta] =
27
+ if @params[:meta] && !@params[:meta].is_a?(String)
28
+ if @params[:meta].respond_to?('to_json')
29
+ @params[:meta] = @params[:meta].to_json
30
+ end
31
+ end
32
+ return do_request
21
33
  end
22
-
23
34
  # Stats
24
35
 
25
36
  # List stats (index)
26
37
  #
27
38
  # Required: metric_id
28
39
  # Optional: limit
29
- def list_stats(metric_id, limit = nil)
30
- connect('stats')
31
- request = Net::HTTP::Get.new(@url.path + '.' + @format)
32
- request["User-Agent"] = @user_agent
33
-
34
- form_hash = Hash.new
35
- form_hash["api_key"] = @api_key
36
- form_hash["metric_id"] = metric_id
37
- if limit != nil
38
- form_hash["limit"] = limit
39
- end
40
-
41
- request.set_form_data(form_hash)
42
-
43
- response = @connection.request(request)
44
- return response.body
40
+ # Returns: Net::HTTP object
41
+ def self.list_stats(metric_id, limit = nil)
42
+ self.connect('stats')
43
+ @request_uri = @url.path + '.' + @format
44
+ @request = Net::HTTP::Get.new(@request_uri)
45
+ @params[:metric_id] = metric_id
46
+ @params[:limit] = limit if limit != nil
47
+ return do_request
45
48
  end
46
49
 
47
50
  # Get stat
48
51
  #
49
52
  # Required: stat_id
50
53
  # Optional: none
51
- def get_stat(stat_id)
54
+ # Returns: Net::HTTP object
55
+ def self.get_stat(stat_id)
52
56
  connect('stats')
53
- request = Net::HTTP::Get.new(@url.path + '/' + stat_id.to_s + '.' + @format)
54
- request["User-Agent"] = @user_agent
55
-
56
- request.set_form_data({"api_key" => @api_key})
57
-
58
- response = @connection.request(request)
59
- return response.body
57
+ @request_uri = @url.path + '/' + stat_id.to_s + '.' + @format
58
+ @request = Net::HTTP::Get.new(@request_uri)
59
+ return do_request
60
60
  end
61
61
 
62
62
  # Create stat
63
63
  #
64
64
  # Required: metric_id
65
- # Optional: value, generated_at, meta
66
- def create_stat(metric_id, value = 1, generated_at = Time.now, meta = nil)
65
+ # Optional: value, params[:generated_at, :meta]
66
+ # Returns: Net::HTTP object
67
+ def self.create_stat(metric_id, value = nil, params = {})
67
68
  connect('stats')
68
- request = Net::HTTP::Post.new(@url.path + '.' + @format)
69
- request["User-Agent"] = @user_agent
70
-
71
- form_hash = Hash.new
72
- form_hash["api_key"] = @api_key
73
- form_hash["value"] = value
74
- form_hash["metric_id"] = metric_id
75
- form_hash["generated_at"] = generated_at
76
- if meta != nil
77
- form_hash["meta"] = meta
78
- end
79
-
80
- request.set_form_data(form_hash)
81
-
82
- response = @connection.request(request)
83
- return response.body
69
+ @request_uri = @url.path + '.' + @format
70
+ @request = Net::HTTP::Post.new(@request_uri)
71
+ @params.merge!(params)
72
+ @params[:value] = value if value
73
+ return do_request
84
74
  end
85
75
 
86
76
  # Update stat
87
77
  #
88
78
  # Required: stat_id
89
79
  # Optional: value, generated_at, meta
90
- def update_stat(stat_id, value = nil, generated_at = nil, meta = nil)
80
+ # Returns: Net::HTTP object
81
+ def self.update_stat(stat_id, value = nil, params = {})
91
82
  connect('stats')
92
- request = Net::HTTP::Put.new(@url.path + '/' + stat_id.to_s + '.' + @format)
93
- request["User-Agent"] = @user_agent
94
-
95
- form_hash = Hash.new
96
- form_hash["api_key"] = @api_key
97
- if value != nil
98
- form_hash["value"] = value
99
- end
100
- if generated_at != nil
101
- form_hash["generated_at"] = generated_at
102
- end
103
- if meta != nil
104
- form_hash["meta"] = meta
105
- end
106
-
107
- request.set_form_data(form_hash)
108
-
109
- response = @connection.request(request)
110
- return response.body
83
+ @request_uri = @url.path + '/' + stat_id.to_s + '.' + @format
84
+ @request = Net::HTTP::Put.new(@request_uri)
85
+ @params.merge!(params)
86
+ @params[:value] = value if value != nil
87
+ return do_request
111
88
  end
112
89
 
113
90
  # Delete stat
114
91
  #
115
92
  # Required: stat_id
116
93
  # Optional: none
117
- def delete_stat(stat_id)
94
+ # Returns: Net::HTTP object
95
+ def self.delete_stat(stat_id)
118
96
  connect('stats')
119
- request = Net::HTTP::Delete.new(@url.path + '/' + stat_id.to_s + '.' + @format)
120
- request["User-Agent"] = @user_agent
121
-
122
- request.set_form_data({"api_key" => @api_key})
123
-
124
- response = @connection.request(request)
125
- return response.body
97
+ @request_uri = @url.path + '/' + stat_id.to_s + '.' + @format
98
+ @request = Net::HTTP::Delete.new(@request_uri)
99
+ return do_request
126
100
  end
127
101
 
128
102
  # Metrics
@@ -131,102 +105,194 @@ class StatsMix
131
105
  #
132
106
  # Required: none
133
107
  # Optional: profile_id, limit
134
- def list_metrics(profile_id = nil, limit = nil)
108
+ # Returns: Net::HTTP object
109
+ def self.list_metrics(profile_id = nil, limit = nil)
135
110
  connect('metrics')
136
- request = Net::HTTP::Get.new(@url.path + '.' + @format)
137
- request["User-Agent"] = @user_agent
138
-
139
- form_hash = Hash.new
140
- form_hash["api_key"] = @api_key
141
- if profile_id != nil
142
- form_hash["profile_id"] = profile_id
143
- end
144
- if limit != nil
145
- form_hash["limit"] = limit
146
- end
147
-
148
- request.set_form_data(form_hash)
111
+ @request_uri = @url.path + '.' + @format
112
+ @request = Net::HTTP::Get.new(@request_uri)
113
+
114
+ @params[:profile_id] = profile_id if profile_id != nil
115
+ @params[:limit] = limit if limit != nil
149
116
 
150
- response = @connection.request(request)
151
- return response.body
117
+ return do_request
152
118
  end
153
119
 
154
120
  # Get metric
155
121
  #
156
122
  # Required: metric_id
157
123
  # Optional: none
158
- def get_metric(metric_id)
124
+ # Returns: Net::HTTP object
125
+ def self.get_metric(metric_id)
159
126
  connect('metrics')
160
- request = Net::HTTP::Get.new(@url.path + '/' + metric_id.to_s + '.' + @format)
161
- request["User-Agent"] = @user_agent
162
-
163
- request.set_form_data({"api_key" => @api_key})
164
-
165
- response = @connection.request(request)
166
- return response.body
127
+ @request_uri = @url.path + '/' + metric_id.to_s + '.' + @format
128
+ @request = Net::HTTP::Get.new(@request_uri)
129
+ return do_request
167
130
  end
168
131
 
169
132
  # Create metric
170
133
  #
171
134
  # Required: name
172
- # Optional: profile_id, generated_at
173
- def create_metric(name, profile_id = nil)
135
+ # Optional: params[:profile_id, :sharing, :include_in_email]
136
+ # Returns: Net::HTTP object
137
+ def self.create_metric(name,params={})
174
138
  connect('metrics')
175
- request = Net::HTTP::Post.new(@url.path + '.' + @format)
176
- request["User-Agent"] = @user_agent
177
-
178
- form_hash = Hash.new
179
- form_hash["api_key"] = @api_key
180
- form_hash["name"] = name
181
- if profile_id != nil
182
- form_hash["profile_id"] = profile_id
183
- end
184
-
185
- request.set_form_data(form_hash)
186
-
187
- response = @connection.request(request)
188
- return response.body
139
+ @params.merge!(params)
140
+ @params[:name] = name
141
+ @request_uri = @url.path + '.' + @format
142
+ @request = Net::HTTP::Post.new(@request_uri)
143
+ return do_request
189
144
  end
190
145
 
191
146
  # Update metric
192
147
  #
193
148
  # Required: metric_id
194
- # Optional: name, sharing, include_in_email
195
- def update_metric(metric_id, name = nil, sharing = nil, include_in_email = nil)
149
+ # Optional: params[:profile_id, :sharing, :include_in_email]
150
+ # Returns: Net::HTTP object
151
+ def self.update_metric(metric_id, params = {})
196
152
  connect('metrics')
197
- request = Net::HTTP::Put.new(@url.path + '/' + metric_id.to_s + '.' + @format)
198
- request["User-Agent"] = @user_agent
199
-
200
- form_hash = Hash.new
201
- form_hash["api_key"] = @api_key
202
- if name != nil
203
- form_hash["name"] = name
204
- end
205
- if sharing != nil
206
- form_hash["sharing"] = sharing
207
- end
208
- if include_in_email != nil
209
- form_hash["include_in_email"] = include_in_email
210
- end
211
-
212
- request.set_form_data(form_hash)
153
+ @params = [] if @params.nil?
154
+ @params.merge!(params)
155
+ @request_uri = @url.path + '/' + metric_id.to_s + '.' + @format
156
+ @request = Net::HTTP::Put.new(@request_uri)
213
157
 
214
- response = @connection.request(request)
215
- return response.body
158
+ return do_request
216
159
  end
217
160
 
218
161
  # Delete metric
219
162
  #
220
163
  # Required: metric_id
221
164
  # Optional: none
222
- def delete_metric(metric_id)
165
+ # Returns: Net::HTTP object
166
+ def self.delete_metric(metric_id)
223
167
  connect('metrics')
224
- request = Net::HTTP::Delete.new(@url.path + '/' + metric_id.to_s + '.' + @format)
225
- request["User-Agent"] = @user_agent
168
+ @request_uri = @url.path + '/' + metric_id.to_s + '.' + @format
169
+ @request = Net::HTTP::Delete.new(@request_uri)
170
+ return do_request
171
+ end
172
+
173
+ def initialize(api_key = nil)
174
+ self.setup(api_key)
175
+ end
176
+
177
+ # Returns: Net::HTTP object
178
+ def self.response
179
+ @response
180
+ end
181
+
182
+ # Returns: string or boolean false
183
+ def self.error
184
+ @error
185
+ end
186
+
187
+ # Returns: hash
188
+ def self.params
189
+ @params
190
+ end
191
+
192
+ def self.api_key=(string)
193
+ @api_key = string
194
+ end
195
+
196
+ # Returns: string
197
+ def self.api_key
198
+ @api_key
199
+ end
200
+
201
+ def self.ignore=(boolean)
202
+ @ignore = boolean ? true : false
203
+ end
204
+
205
+ #Returns: boolean
206
+ def self.ignore
207
+ @ignore
208
+ end
209
+
210
+ def self.test_metric_name=(name)
211
+ @test_metric_name = name
212
+ end
213
+
214
+ #Returns: string or nil
215
+ def self.test_metric_name
216
+ @test_metric_name
217
+ end
218
+
219
+ # Returns: string
220
+ def self.api_key
221
+ @api_key
222
+ end
223
+
224
+ def self.api_from_env
225
+ return nil if ENV['STATSMIX_URL'].nil?
226
+ url = ENV['STATSMIX_URL']
227
+ pieces = url.gsub('http://','').gsub('https://','').split('/')
228
+ @api_key = pieces[2]
229
+ end
230
+
231
+ def self.format=(string)
232
+ string.downcase!
233
+ if string != 'json' && string != 'xml'
234
+ raise "format MUST be either xml or json"
235
+ end
236
+ @format = string
237
+ end
238
+
239
+ # Returns: string
240
+ def self.format
241
+ @format
242
+ end
243
+
244
+ # Returns: string
245
+ def self.request_uri
246
+ @request_uri
247
+ end
248
+ private
249
+
250
+ def self.setup(api_key = nil)
251
+ return if @initiliazed
252
+ if !api_key.nil?
253
+ @api_key = api_key
254
+ end
255
+ @format = 'xml' if @format.nil?
256
+ @ignore = false if @ignore.nil?
257
+ @user_agent = "StatsMix Ruby Gem " + GEM_VERSION
258
+ @initiliazed = true
259
+ @error = false
260
+ end
261
+
262
+ def self.connect(resource)
263
+ self.setup
226
264
 
227
- request.set_form_data({"api_key" => @api_key})
265
+ if @api_key.nil?
266
+ raise "API key not set. You must set it frist with StatsMix.api_key = [your api key]"
267
+ end
268
+ # Resources available: stats, metrics, TODO: profiles
269
+ @url = URI.parse(BASE_URI + resource)
270
+ @connection = Net::HTTP.new(@url.host, @url.port)
228
271
 
229
- response = @connection.request(request)
230
- return response.body
272
+ @request = Hash.new
273
+ @request["User-Agent"] = @user_agent
274
+ @params = Hash.new
275
+ @params[:api_key] = @api_key
231
276
  end
232
- end
277
+
278
+ def self.do_request
279
+ @error = false
280
+ return if @ignore
281
+ @request.set_form_data(@params)
282
+ @response = @connection.request(@request)
283
+ if @response.is_a?(Net::HTTPClientError)
284
+ if 'xml' == @format
285
+ begin
286
+ @error = @response.body.match('<error>(.)+</error>')[0].gsub('<error>','').gsub('</error>','')
287
+ rescue
288
+ @error = 'Unable to parse error message. Check StatsMix.response for more information'
289
+ end
290
+ else
291
+ @error = JSON.parse(@response.body)['errors']['error']
292
+ end
293
+ end
294
+ @response.body
295
+ end
296
+ end
297
+
298
+ StatsMix.api_from_env
data/statsmix.gemspec CHANGED
@@ -5,13 +5,13 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{statsmix}
8
- s.version = "0.0.6"
8
+ s.version = "0.1.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Tom Markiewicz"]
12
- s.date = %q{2011-06-22}
11
+ s.authors = ["Tom Markiewicz", "Derek Scruggs"]
12
+ s.date = %q{2011-07-05}
13
13
  s.description = %q{A Ruby gem for the StatsMix API - http://www.statsmix.com/developers}
14
- s.email = %q{tmarkiewicz@gmail.com}
14
+ s.email = ["tmarkiewicz@gmail.com", "me@derekscruggs.com"]
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE.txt",
17
17
  "README.rdoc"
@@ -3,7 +3,7 @@ require 'statsmix'
3
3
 
4
4
  class TestStatsmix < Test::Unit::TestCase
5
5
 
6
- # TODO use fakwweb gem for testing
6
+ # TODO use fakwweb gem for testing
7
7
  # http://technicalpickles.com/posts/stop-net-http-dead-in-its-tracks-with-fakeweb/
8
8
  # https://github.com/chrisk/fakeweb
9
9
  # http://fakeweb.rubyforge.org/
@@ -12,51 +12,16 @@ class TestStatsmix < Test::Unit::TestCase
12
12
  # http://www.rubyinside.com/vcr-a-recorder-for-all-your-tests-http-interactions-4169.html
13
13
  # https://github.com/myronmarston/vcr
14
14
 
15
- should "initialize StatsMix API" do
16
- statsmix = StatsMix.new('59f08613db2691f28afe', 'xml')
17
- response = statsmix.list_metrics
18
- puts response
15
+ should "Track a stat and view the results in xml" do
16
+ StatsMix.api_key = '59f08613db2691f28afe'
17
+ result = StatsMix.track('Ruby Gem Testing')
18
+ assert_response 200
19
+ if StatsMix.error
20
+ raise "Error in gem: #{StatsMix.error}"
21
+ end
22
+ assert !StatsMix.error
23
+ puts result
19
24
  end
20
25
 
21
- # # metrics
22
- # should "create metric" do
23
- #
24
- # end
25
- #
26
- # should "get metric" do
27
- #
28
- # end
29
- #
30
- # should "list metrics" do
31
- #
32
- # end
33
- #
34
- # should "update metric" do
35
- #
36
- # end
37
- #
38
- # should "delete metric" do
39
- #
40
- # end
41
26
 
42
- # # stats
43
- # should "create stat" do
44
- #
45
- # end
46
- #
47
- # should "get stat" do
48
- #
49
- # end
50
- #
51
- # should "list stats" do
52
- #
53
- # end
54
- #
55
- # should "update stat" do
56
- #
57
- # end
58
- #
59
- # should "delete stat" do
60
- #
61
- # end
62
27
  end
metadata CHANGED
@@ -1,28 +1,26 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: statsmix
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 29
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 0
9
- - 6
10
- version: 0.0.6
8
+ - 1
9
+ - 3
10
+ version: 0.1.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tom Markiewicz
14
+ - Derek Scruggs
14
15
  autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2011-06-22 00:00:00 -06:00
19
+ date: 2011-07-05 00:00:00 -06:00
19
20
  default_executable:
20
21
  dependencies:
21
22
  - !ruby/object:Gem::Dependency
22
- type: :development
23
- prerelease: false
24
- name: shoulda
25
- version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ requirement: &id001 !ruby/object:Gem::Requirement
26
24
  none: false
27
25
  requirements:
28
26
  - - ">="
@@ -31,12 +29,12 @@ dependencies:
31
29
  segments:
32
30
  - 0
33
31
  version: "0"
34
- requirement: *id001
35
- - !ruby/object:Gem::Dependency
36
32
  type: :development
33
+ name: shoulda
37
34
  prerelease: false
38
- name: bundler
39
- version_requirements: &id002 !ruby/object:Gem::Requirement
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ requirement: &id002 !ruby/object:Gem::Requirement
40
38
  none: false
41
39
  requirements:
42
40
  - - ~>
@@ -47,12 +45,12 @@ dependencies:
47
45
  - 0
48
46
  - 0
49
47
  version: 1.0.0
50
- requirement: *id002
51
- - !ruby/object:Gem::Dependency
52
48
  type: :development
49
+ name: bundler
53
50
  prerelease: false
54
- name: jeweler
55
- version_requirements: &id003 !ruby/object:Gem::Requirement
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ requirement: &id003 !ruby/object:Gem::Requirement
56
54
  none: false
57
55
  requirements:
58
56
  - - ~>
@@ -63,12 +61,12 @@ dependencies:
63
61
  - 5
64
62
  - 1
65
63
  version: 1.5.1
66
- requirement: *id003
67
- - !ruby/object:Gem::Dependency
68
64
  type: :development
65
+ name: jeweler
69
66
  prerelease: false
70
- name: rcov
71
- version_requirements: &id004 !ruby/object:Gem::Requirement
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ requirement: &id004 !ruby/object:Gem::Requirement
72
70
  none: false
73
71
  requirements:
74
72
  - - ">="
@@ -77,9 +75,14 @@ dependencies:
77
75
  segments:
78
76
  - 0
79
77
  version: "0"
80
- requirement: *id004
78
+ type: :development
79
+ name: rcov
80
+ prerelease: false
81
+ version_requirements: *id004
81
82
  description: A Ruby gem for the StatsMix API - http://www.statsmix.com/developers
82
- email: tmarkiewicz@gmail.com
83
+ email:
84
+ - tmarkiewicz@gmail.com
85
+ - me@derekscruggs.com
83
86
  executables: []
84
87
 
85
88
  extensions: []