Bryton 0.1

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 (5) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +23 -0
  3. data/lib/bryton/runner.rb +459 -0
  4. data/lib/bryton/tools.rb +394 -0
  5. metadata +45 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 515b6e28901538ec80a86cae04da951149a4c5e726d57869c86386ca34e8928f
4
+ data.tar.gz: c28f2fe9fb9962eef207fecdca9853e9b3489e0144a15d938bb38fbc784b7f47
5
+ SHA512:
6
+ metadata.gz: 49faf782a0a7b691a854fea4211e1038fca60df54c7000a0cae5bbbb298ba75530ba8688f2db878e1709e9b5dc53fbf2f7d7352812ec65461d4bb9d04a3405a7
7
+ data.tar.gz: 219c8f0a8106fb9715ecd05375d78346d7a2530b721680beb70ac80eb465669defb13ad9b76e96a8b51fd135dedcdb786468cd0b3fd7b66235e36835184b5501
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # Bryton
2
+
3
+ Bryton is a file based testing protocol. Each file contains one or more tests.
4
+ The results of the tests are reported in STDOUT as a JSON Object.
5
+
6
+ Bryton is not ready for use yet. Just sharing online for a few collaborators.
7
+
8
+ ## Install
9
+
10
+ ```
11
+ gem install bryton
12
+ ```
13
+
14
+ ## Author
15
+
16
+ Mike O'Sullivan
17
+ mike@idocs.com
18
+
19
+ ## History
20
+
21
+ | version | date | notes |
22
+ |----------|--------------|-----------------|
23
+ | 1.0 | May 16, 2023 | Initial upload. |
@@ -0,0 +1,459 @@
1
+ #!/usr/bin/ruby -w
2
+ require 'timeout'
3
+ require 'json'
4
+ require 'fso'
5
+ require 'hash/digger'
6
+ require 'talk-to-me'
7
+
8
+
9
+ #===============================================================================
10
+ # Bryton
11
+ #
12
+ module Bryton
13
+ end
14
+ #
15
+ # Bryton
16
+ #===============================================================================
17
+
18
+
19
+ # load bryton xeme classes
20
+ require 'bryton/xeme'
21
+
22
+
23
+ #===============================================================================
24
+ # Bryton::FSO
25
+ # Just initializing namespace.
26
+ #
27
+ module Bryton::FSO
28
+ end
29
+ #
30
+ # Bryton::FSO
31
+ #===============================================================================
32
+
33
+
34
+ #===============================================================================
35
+ # Bryton::FSO::Dir
36
+ #
37
+ class Bryton::FSO::Dir < FSO::Dir
38
+ attr_reader :xeme
39
+ attr_accessor :stop_on_fail
40
+ attr_accessor :verbose
41
+ attr_accessor :run_tests
42
+
43
+ #---------------------------------------------------------------------------
44
+ # classes
45
+ #
46
+ def self.classes
47
+ return {
48
+ 'dir'=>Bryton::FSO::Dir,
49
+ 'file'=>Bryton::FSO::File
50
+ };
51
+ end
52
+ #
53
+ # classes
54
+ #---------------------------------------------------------------------------
55
+
56
+
57
+ #---------------------------------------------------------------------------
58
+ # initialize
59
+ #
60
+ def initialize(p_path='./')
61
+ super(p_path)
62
+ @settings = nil
63
+ @children = nil
64
+ @stop_on_fail = false
65
+ end
66
+ #
67
+ # initialize
68
+ #---------------------------------------------------------------------------
69
+
70
+
71
+ #---------------------------------------------------------------------------
72
+ # settings
73
+ #
74
+ def settings
75
+ # cache bryton.json if necessary
76
+ if not @settings
77
+ settings_path = "#{path}/bryton.json"
78
+ @settings = {'active'=>true}
79
+
80
+ # slurp in settings file
81
+ if ::File.exist?(settings_path)
82
+ explicit = ::File.read(settings_path)
83
+ explicit = JSON.parse(explicit)
84
+ @settings = @settings.merge(explicit)
85
+ end
86
+ end
87
+
88
+ # return
89
+ return @settings
90
+ end
91
+ #
92
+ # settings
93
+ #---------------------------------------------------------------------------
94
+
95
+
96
+ #---------------------------------------------------------------------------
97
+ # meta
98
+ #
99
+ def meta
100
+ return settings['meta'] || {}
101
+ end
102
+ #
103
+ # meta
104
+ #---------------------------------------------------------------------------
105
+
106
+
107
+ #---------------------------------------------------------------------------
108
+ # children
109
+ #
110
+ def children(opts={})
111
+ # cache @children if it's not already defined
112
+ if not @children
113
+ chdir do
114
+ @children = []
115
+
116
+ # special case: files element exists but is false
117
+ if settings.has_key?('files') and (not settings['files'])
118
+ return @children
119
+ end
120
+
121
+ # build list of child files
122
+ children_explicit opts
123
+ children_implicit super(), opts
124
+ end
125
+ end
126
+
127
+ # return
128
+ return @children
129
+ end
130
+ #
131
+ # children
132
+ #---------------------------------------------------------------------------
133
+
134
+
135
+ #---------------------------------------------------------------------------
136
+ # children_explicit
137
+ #
138
+ def children_explicit(opts)
139
+ explicits = settings['files'] || {}
140
+
141
+ explicits.each do |child_path, child_settings|
142
+ if child_settings
143
+ if child = self.class.existing(child_path)
144
+ if child.settings['active']
145
+ @children.push child
146
+ end
147
+ else
148
+ raise 'non-existent-file-in-list: ' + child_path
149
+ end
150
+ end
151
+ end
152
+ end
153
+ #
154
+ # children_explicit
155
+ #---------------------------------------------------------------------------
156
+
157
+
158
+ #---------------------------------------------------------------------------
159
+ # children_implicit
160
+ # skips dev.* files
161
+ #
162
+ def children_implicit(kids, opts)
163
+ # early exit: if filter is true, don't import implicit children
164
+ settings['listed-only'] and return
165
+
166
+ kids.each do |child|
167
+ if not child_names.include?(child.name)
168
+ unless child.name.match(/\Adev\./mu)
169
+ if child.dir?
170
+ @children.push child
171
+ elsif child.executable?
172
+ @children.push child
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ #
179
+ # children_implicit
180
+ #---------------------------------------------------------------------------
181
+
182
+
183
+ #---------------------------------------------------------------------------
184
+ # child_names
185
+ #
186
+ def child_names
187
+ return @children.map{|child| child.name}
188
+ end
189
+ #
190
+ # child_names
191
+ #---------------------------------------------------------------------------
192
+
193
+
194
+ #---------------------------------------------------------------------------
195
+ # run
196
+ #
197
+ def run(opts={})
198
+ opts = {'nested'=>0}.merge(opts)
199
+ xeme = Xeme.new()
200
+
201
+ # verbosify
202
+ unless opts['first']
203
+ TTM.puts title(opts)
204
+ end
205
+
206
+ # operate in own directory
207
+ chdir() do
208
+ TTM.indent('skip'=>opts.delete('first')) do
209
+ run_children xeme, opts
210
+ run_commands xeme, opts
211
+ end
212
+ end
213
+
214
+ # At this point, all tests in the have been run. Attempt to succeed.
215
+ xeme.try_succeed
216
+
217
+ # if set as not ready, fail anyway
218
+ if xeme.success?
219
+ if meta.has_key?('ready') and (not meta['ready'])
220
+ xeme['success-but-not-ready'] = true
221
+ xeme.fail
222
+ end
223
+ end
224
+
225
+ # return xeme
226
+ return xeme
227
+ end
228
+ #
229
+ # run
230
+ #---------------------------------------------------------------------------
231
+
232
+
233
+ #---------------------------------------------------------------------------
234
+ # run_children
235
+ #
236
+ def run_children(xeme, opts)
237
+ children.each do |child|
238
+ if child.executable? or child.dir? and child.settings['active']
239
+ send_opts = opts.clone
240
+ send_opts['nested'] += 1
241
+
242
+ # run child, add to nested if any results
243
+ if nest = child.run(send_opts)
244
+ xeme['nested'].push nest
245
+
246
+ if nest.failure? and @stop_on_fail
247
+ return
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
253
+ #
254
+ # run_children
255
+ #---------------------------------------------------------------------------
256
+
257
+
258
+ #---------------------------------------------------------------------------
259
+ # run_commands
260
+ #
261
+ def run_commands(xeme, opts)
262
+ commands = settings['commands'] || []
263
+ commands.empty? and return
264
+
265
+ # load EzCapture
266
+ require 'ezcapture'
267
+
268
+ # loop through commands
269
+ commands.each do |cmd|
270
+ nest = run_command(cmd)
271
+ xeme['nested'].push nest
272
+
273
+ # exit if error
274
+ if nest.failure? and @stop_on_fail
275
+ return
276
+ end
277
+ end
278
+ end
279
+ #
280
+ # run_commands
281
+ #---------------------------------------------------------------------------
282
+
283
+
284
+ #---------------------------------------------------------------------------
285
+ # run_command
286
+ #
287
+ def run_command(cmd)
288
+ capture = EzCapture.new(*cmd)
289
+ xeme = Xeme.last_line(capture.stdout)
290
+
291
+ # if no stdout, return failure xeme
292
+ if not xeme
293
+ xeme = Xeme.new()
294
+ xeme.error 'did-not-get-xeme-in-stdout'
295
+ return xeme
296
+ end
297
+
298
+ # return
299
+ return xeme
300
+ end
301
+ #
302
+ # run_command
303
+ #---------------------------------------------------------------------------
304
+
305
+
306
+ #---------------------------------------------------------------------------
307
+ # title
308
+ # Determines the string that should be displayed when this file is listed
309
+ # when verbose setting is true.
310
+ #
311
+ def title(opts)
312
+ # if defined name for this directory
313
+ if explicit = settings.digger('meta', 'title')
314
+ return explicit
315
+
316
+ # else return the name of the directory
317
+ else
318
+ return name
319
+ end
320
+ end
321
+ #
322
+ # title
323
+ #---------------------------------------------------------------------------
324
+ end
325
+ #
326
+ # Bryton::FSO::Dir
327
+ #===============================================================================
328
+
329
+
330
+ #===============================================================================
331
+ # Bryton::FSO::File
332
+ #
333
+ class Bryton::FSO::File < FSO::File
334
+ #---------------------------------------------------------------------------
335
+ # classes
336
+ #
337
+ def self.classes
338
+ return {
339
+ 'file' => Bryton::FSO::File,
340
+ 'dir' => Bryton::FSO::Dir
341
+ };
342
+ end
343
+ #
344
+ # classes
345
+ #---------------------------------------------------------------------------
346
+
347
+
348
+ #---------------------------------------------------------------------------
349
+ # initialize
350
+ #
351
+ def initialize(*opts)
352
+ super(*opts)
353
+ @children = nil
354
+ @settings = nil
355
+ end
356
+ #
357
+ # initialize
358
+ #---------------------------------------------------------------------------
359
+
360
+
361
+ #---------------------------------------------------------------------------
362
+ # settings
363
+ #
364
+ def settings()
365
+ # cache if necessary
366
+ if not @settings
367
+ default = {'active'=>true}
368
+ explicit = dir.settings.digger('files', name)
369
+
370
+ # If no explicit definition of the file, initialize to empty hash.
371
+ if explicit.nil?
372
+ explicit = {}
373
+
374
+ # If explicit is defined but is not a hash, the non-hash value is
375
+ # assumed to be the "active" value.
376
+ elsif not explicit.is_a?(Hash)
377
+ explicit = {'active'=>explicit}
378
+ end
379
+
380
+ # by this point explicit is a hash
381
+
382
+ # merge explicit and default
383
+ @settings = default.merge(explicit)
384
+ end
385
+
386
+ # return
387
+ return @settings
388
+ end
389
+ #
390
+ # settings
391
+ #---------------------------------------------------------------------------
392
+
393
+
394
+ #---------------------------------------------------------------------------
395
+ # run
396
+ #
397
+ def run(opts={})
398
+ # If the file is not active, do nothing - don't even print the name.
399
+ if not settings['active']
400
+ return nil
401
+ end
402
+
403
+ # verbosify
404
+ if opts['verbose']
405
+ TTM.puts title(opts)
406
+
407
+ # message before running
408
+ if before = @settings.digger('messages', 'before')
409
+ TTM.indent do
410
+ TTM.puts before
411
+ end
412
+ end
413
+ end
414
+
415
+ # run and return xeme if necessary
416
+ unless opts['list_only']
417
+ return run_test(opts)
418
+ end
419
+ end
420
+ #
421
+ # run
422
+ #---------------------------------------------------------------------------
423
+
424
+
425
+ #---------------------------------------------------------------------------
426
+ # run_test
427
+ #
428
+ def run_test(opts)
429
+ capture = execute()
430
+ xeme = Xeme.last_line(capture.stdout)
431
+
432
+ # if no stdout, return failure xeme
433
+ if not xeme
434
+ xeme = Xeme.new()
435
+ xeme.error 'did-not-get-xeme-in-stdout'
436
+ return xeme
437
+ end
438
+
439
+ # return
440
+ return xeme
441
+ end
442
+ #
443
+ # run_test
444
+ #---------------------------------------------------------------------------
445
+
446
+
447
+ #---------------------------------------------------------------------------
448
+ # title
449
+ #
450
+ def title(opts)
451
+ return name
452
+ end
453
+ #
454
+ # title
455
+ #---------------------------------------------------------------------------
456
+ end
457
+ #
458
+ # Bryton::FSO::File
459
+ #===============================================================================
@@ -0,0 +1,394 @@
1
+ require 'xeme'
2
+ require 'talk-to-me'
3
+
4
+
5
+ #===============================================================================
6
+ # Bryton
7
+ #
8
+ module Bryton
9
+ # instance properties
10
+ @exit_on_fail = false
11
+ @ready = true
12
+ @keep_count = true
13
+ @line_comments = false
14
+
15
+
16
+ #---------------------------------------------------------------------------
17
+ # accessors
18
+ #
19
+ class << self
20
+ attr_accessor :exit_on_fail
21
+ attr_accessor :ready
22
+ attr_accessor :keep_count
23
+ attr_accessor :xeme
24
+ attr_accessor :line_comments
25
+ end
26
+ #
27
+ # accessors
28
+ #---------------------------------------------------------------------------
29
+
30
+
31
+ #---------------------------------------------------------------------------
32
+ # accessor methods
33
+ #
34
+ def self.verbose
35
+ return TTM.io ? true : false
36
+ end
37
+
38
+ def self.verbose=(bool)
39
+ if bool
40
+ TTM.io = STDERR
41
+ else
42
+ TTM.io = nil
43
+ end
44
+ end
45
+ #
46
+ # accessor methods
47
+ #---------------------------------------------------------------------------
48
+
49
+
50
+ #---------------------------------------------------------------------------
51
+ # reset
52
+ #
53
+ def self.reset()
54
+ self.verbose= true
55
+ @xeme = Xeme.new()
56
+ @xeme['count'] = 0
57
+ end
58
+
59
+ reset()
60
+ #
61
+ # reset
62
+ #---------------------------------------------------------------------------
63
+
64
+
65
+ #---------------------------------------------------------------------------
66
+ # pause_counting
67
+ #
68
+ def self.pause_counting
69
+ hold_keep_count = @keep_count
70
+ @keep_count = false
71
+ yield
72
+ ensure
73
+ @keep_count = hold_keep_count
74
+ end
75
+ #
76
+ # pause_counting
77
+ #---------------------------------------------------------------------------
78
+
79
+
80
+ #---------------------------------------------------------------------------
81
+ # increment_count
82
+ #
83
+ def self.increment_count
84
+ if @keep_count
85
+ @xeme['count'] += 1
86
+ end
87
+ end
88
+ #
89
+ # increment_count
90
+ #---------------------------------------------------------------------------
91
+
92
+
93
+ #---------------------------------------------------------------------------
94
+ # succeed
95
+ #
96
+ def self.succeed
97
+ if not @ready
98
+ @xeme.error 'not-ready'
99
+ end
100
+
101
+ # set success to true
102
+ @xeme.try_succeed()
103
+
104
+ # output
105
+ puts xeme.to_json()
106
+ end
107
+ #
108
+ # succeed
109
+ #---------------------------------------------------------------------------
110
+
111
+
112
+ #---------------------------------------------------------------------------
113
+ # line_error
114
+ #
115
+ def self.line_error(level=1)
116
+ loc = caller_locations[level]
117
+ error = @xeme.error('line-' + loc.lineno.to_s)
118
+
119
+ # get line comment if there is one
120
+ line_comment error, loc
121
+
122
+ # exit if exiting on first fail
123
+ maybe_exit_on_fail()
124
+ end
125
+ #
126
+ # line_error
127
+ #---------------------------------------------------------------------------
128
+
129
+
130
+ #---------------------------------------------------------------------------
131
+ # line_comment
132
+ #
133
+ def self.line_comment(error, loc)
134
+ @line_comments or return
135
+
136
+ # KLUDGE: I don't understand why loc.lineno and the line number are two
137
+ # apart.
138
+ idx = 2
139
+
140
+ # read file
141
+ File.foreach(loc.path) do |line|
142
+ if idx == loc.lineno
143
+ if line.sub!(/\A\s*\#\#\s*/mu, '')
144
+ if line.match(/\S/mu)
145
+ line = line.sub(/\s+\z/mu, '')
146
+ error['comment'] = line
147
+ end
148
+ end
149
+
150
+ # we're done
151
+ return
152
+ end
153
+
154
+ # increment current line number
155
+ idx += 1
156
+ end
157
+ end
158
+ #
159
+ # line_comment
160
+ #---------------------------------------------------------------------------
161
+
162
+
163
+ #---------------------------------------------------------------------------
164
+ # maybe_exit_on_fail
165
+ #
166
+ def self.maybe_exit_on_fail
167
+ if @exit_on_fail
168
+ puts xeme.to_json()
169
+ exit
170
+ end
171
+ end
172
+ #
173
+ # maybe_exit_on_fail
174
+ #---------------------------------------------------------------------------
175
+
176
+
177
+ ### tests
178
+
179
+
180
+ #---------------------------------------------------------------------------
181
+ # pass, fail
182
+ #
183
+ def self.pass
184
+ Bryton.increment_count()
185
+ end
186
+
187
+ def self.fail
188
+ increment_count()
189
+ line_error()
190
+ end
191
+ #
192
+ # pass, fail
193
+ #---------------------------------------------------------------------------
194
+
195
+
196
+ #---------------------------------------------------------------------------
197
+ # structure comparisons
198
+ #
199
+ def self.compare(should, is)
200
+ TTM.hrm
201
+ return Bryton::StructureComp.compare(should, is)
202
+ end
203
+ #
204
+ # structure comparisons
205
+ #---------------------------------------------------------------------------
206
+
207
+
208
+ #-------------------------------------------------------------------------------
209
+ # isa
210
+ #
211
+ def self.isa(obj, clss)
212
+ Bryton.increment_count()
213
+
214
+ # success
215
+ if obj.is_a?(clss)
216
+ return true
217
+
218
+ # failure
219
+ else
220
+ Bryton.line_error(2)
221
+ return false
222
+ end
223
+ end
224
+ #
225
+ # isa
226
+ #-------------------------------------------------------------------------------
227
+ end
228
+ #
229
+ # Bryton
230
+ #===============================================================================
231
+
232
+
233
+ #===============================================================================
234
+ # Bryton::ExceptionTest
235
+ #
236
+ class Bryton::ExceptionTest
237
+ attr_accessor :err_class
238
+
239
+
240
+ #---------------------------------------------------------------------------
241
+ # initialize
242
+ #
243
+ def initialize
244
+ @err_class = nil
245
+ end
246
+ #
247
+ # initialize
248
+ #---------------------------------------------------------------------------
249
+
250
+
251
+ #---------------------------------------------------------------------------
252
+ # exec
253
+ #
254
+ def exec
255
+ Bryton.increment()
256
+ success = false
257
+ e = nil
258
+
259
+ # Run the block. If it runs successfully then it fails the test.
260
+ begin
261
+ yield
262
+ success = true
263
+
264
+ # verbosify
265
+ TTM.puts 'block-did-not-fail'
266
+
267
+ # We want to get to this rescue block because that means the block
268
+ # failed, which is what it's supposed to do.
269
+ rescue => e
270
+ # verbosify if necessary
271
+
272
+ TTM.puts "error class: #{e.class.to_s}"
273
+ TTM.puts "error: #{e}"
274
+
275
+ # if error id, add that
276
+ if e.respond_to?('id')
277
+ TTM.puts "error id: #{e}"
278
+ end
279
+
280
+
281
+ # check error class
282
+ if @err_class
283
+ if not e.class == @err_class
284
+ if @verbose
285
+ puts "error class was #{e.class.to_s} but should have been #{@err_class.to_s}"
286
+ end
287
+
288
+ Bryton.line_error(2)
289
+ end
290
+ end
291
+ end
292
+
293
+ # if yield succeeded, then an exception did not occur
294
+ if success
295
+ Bryton.line_error()
296
+ end
297
+
298
+ # return error
299
+ return e
300
+ end
301
+ #
302
+ # exec
303
+ #---------------------------------------------------------------------------
304
+ end
305
+ #
306
+ # Bryton::ExceptionTest
307
+ #===============================================================================
308
+
309
+
310
+ #===============================================================================
311
+ # Bryton::StructureComp
312
+ #
313
+ module Bryton::StructureComp
314
+ #-------------------------------------------------------------------------------
315
+ # array_comp
316
+ #
317
+ def self.array_comp(should, is, opts={'recurse'=>true})
318
+ should.each_with_index do |should_el, idx|
319
+ element_comp should_el, is[idx], opts
320
+ end
321
+ end
322
+ #
323
+ # array_comp
324
+ #-------------------------------------------------------------------------------
325
+
326
+
327
+ #-------------------------------------------------------------------------------
328
+ # hash_comp
329
+ #
330
+ def self.hash_comp(should, is, opts={})
331
+ Bryton.increment()
332
+ opts = {'recurse'=>true}.merge(opts)
333
+
334
+
335
+ # early exit: is is null
336
+ defined is
337
+ is or return
338
+
339
+ # is a hash
340
+ isa is, Hash
341
+ is.is_a?(Hash) or return
342
+
343
+ # if not a hash, return
344
+ if not is.is_a?(Hash)
345
+ return false
346
+ end
347
+
348
+ # should be of same length
349
+ eq should.length, is.length
350
+
351
+ # loop through keys
352
+ should.keys.each do |should_k|
353
+ element_comp should[should_k], is[should_k], opts
354
+ end
355
+ end
356
+ #
357
+ # hash_comp
358
+ #-------------------------------------------------------------------------------
359
+
360
+
361
+ #-------------------------------------------------------------------------------
362
+ # compare
363
+ #
364
+ def self.compare(should, is, opts)
365
+ Bryton.increment_count()
366
+
367
+ # should be same classes
368
+ if not Bryton.isa(is, should.class)
369
+ return
370
+ end
371
+
372
+ # pause counting and recurse
373
+ Bryton.pause_counting do
374
+ # if should element is a hash
375
+ if should.is_a?(Hash)
376
+ return hash_comp(should, is, opts)
377
+
378
+ # if should is an array
379
+ elsif should.is_a?(Array)
380
+ return array_comp(should, is, opts)
381
+
382
+ # if should is a string
383
+ elsif should.is_a?(String)
384
+ return Bryon.eq(should, is)
385
+ end
386
+ end
387
+ end
388
+ #
389
+ # compare
390
+ #-------------------------------------------------------------------------------
391
+ end
392
+ #
393
+ # Bryton::StructureComp
394
+ #===============================================================================
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Bryton
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Mike O'Sullivan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-05-16 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: File based testing protocol.
14
+ email: mike@idocs.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - README.md
20
+ - lib/bryton/runner.rb
21
+ - lib/bryton/tools.rb
22
+ homepage: https://github.com/mikosullivan/bryton
23
+ licenses:
24
+ - MIT
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubygems_version: 3.1.2
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: Bryton
45
+ test_files: []