wip-runner 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +45 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/wip-runner +4 -0
- data/lib/wip/runner/cli/help.rb +27 -0
- data/lib/wip/runner/cli/version.rb +11 -0
- data/lib/wip/runner/cli.rb +117 -0
- data/lib/wip/runner/command.rb +103 -0
- data/lib/wip/runner/commands.rb +47 -0
- data/lib/wip/runner/errors.rb +24 -0
- data/lib/wip/runner/options.rb +7 -0
- data/lib/wip/runner/parser.rb +93 -0
- data/lib/wip/runner/shell/handlers/base.rb +46 -0
- data/lib/wip/runner/shell/handlers/script.rb +63 -0
- data/lib/wip/runner/shell/handlers/system.rb +22 -0
- data/lib/wip/runner/shell/handlers.rb +17 -0
- data/lib/wip/runner/shell/runner.rb +147 -0
- data/lib/wip/runner/shell/task.rb +50 -0
- data/lib/wip/runner/shell.rb +9 -0
- data/lib/wip/runner/version.rb +5 -0
- data/lib/wip/runner/workflow/builder/component.rb +43 -0
- data/lib/wip/runner/workflow/builder/step.rb +15 -0
- data/lib/wip/runner/workflow/builder/task.rb +20 -0
- data/lib/wip/runner/workflow/builder/workflow.rb +50 -0
- data/lib/wip/runner/workflow/builder.rb +18 -0
- data/lib/wip/runner/workflow/runner.rb +518 -0
- data/lib/wip/runner/workflow.rb +49 -0
- data/lib/wip/runner.rb +17 -0
- data/wip-runner.gemspec +37 -0
- metadata +139 -0
@@ -0,0 +1,518 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module WIP
|
5
|
+
module Runner
|
6
|
+
module Workflow
|
7
|
+
class Runner
|
8
|
+
def initialize(io, workflow)
|
9
|
+
@io = io
|
10
|
+
@workflow = workflow
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(options)
|
14
|
+
indent_size = @io.indent_size
|
15
|
+
@io.indent_size = 2
|
16
|
+
@options = options
|
17
|
+
@context = []
|
18
|
+
@env = {}
|
19
|
+
|
20
|
+
process_overview
|
21
|
+
process_workflow unless @options.overview
|
22
|
+
|
23
|
+
@io.indent_size = indent_size
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def stylize(text, style)
|
29
|
+
stylize? ? @io.color(text, style) : text
|
30
|
+
end
|
31
|
+
|
32
|
+
def stylize?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def process_overview
|
37
|
+
@io.newline
|
38
|
+
@io.indent do
|
39
|
+
@io.say "# #{stylize(@workflow.heading, :underline)}"
|
40
|
+
|
41
|
+
unless @workflow.overview.nil?
|
42
|
+
@io.newline
|
43
|
+
@io.say @workflow.overview
|
44
|
+
end
|
45
|
+
|
46
|
+
unless @workflow.prologue.nil?
|
47
|
+
@io.newline
|
48
|
+
@io.say @workflow.prologue
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def process_workflow
|
54
|
+
@context.push({ sources: [] })
|
55
|
+
|
56
|
+
@io.indent do
|
57
|
+
process_configs unless @options.preview
|
58
|
+
process_guards unless @options.preview
|
59
|
+
|
60
|
+
@workflow.shells.each do |mode, content|
|
61
|
+
process_shell(content, mode)
|
62
|
+
end
|
63
|
+
|
64
|
+
@workflow.tasks.each do |task|
|
65
|
+
process_task(task)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
@context.pop
|
70
|
+
rescue GuardError, HaltSignal
|
71
|
+
# no-op (execution already blocked)
|
72
|
+
end
|
73
|
+
|
74
|
+
def process_task(task, overview = true)
|
75
|
+
process_block('##', task, overview, :underline) do
|
76
|
+
if overview && ! (task.shells.empty? && task.steps.empty?)
|
77
|
+
@io.newline
|
78
|
+
@io.say 'Steps...'
|
79
|
+
end
|
80
|
+
|
81
|
+
if @options.preview
|
82
|
+
task.shells.each do |mode, content|
|
83
|
+
process_shell(content, mode)
|
84
|
+
end
|
85
|
+
|
86
|
+
task.steps.each do |step|
|
87
|
+
process_step(step)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
if overview
|
91
|
+
@options.preview = true
|
92
|
+
task.shells.each do |mode, content|
|
93
|
+
process_shell(content, mode)
|
94
|
+
end
|
95
|
+
@options.preview = false
|
96
|
+
|
97
|
+
task.steps.each do |step|
|
98
|
+
@io.newline
|
99
|
+
@io.say("- [ ] #{step.heading}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
@io.newline
|
104
|
+
choice = @io.choose('yes', 'no', 'skip', 'step', 'preview') do |menu|
|
105
|
+
menu.header = 'Continue?'
|
106
|
+
menu.flow = :inline
|
107
|
+
menu.index = :none
|
108
|
+
end
|
109
|
+
|
110
|
+
case choice
|
111
|
+
when 'yes'
|
112
|
+
proceed_with_task(task)
|
113
|
+
when 'no'
|
114
|
+
raise HaltSignal
|
115
|
+
when 'skip'
|
116
|
+
@io.indent_level -= 1
|
117
|
+
return
|
118
|
+
when 'step'
|
119
|
+
@options.stepwise = true
|
120
|
+
task.shells.each do |mode, content|
|
121
|
+
process_shell(content, mode)
|
122
|
+
end
|
123
|
+
|
124
|
+
task.steps.each do |step|
|
125
|
+
process_step(step)
|
126
|
+
end
|
127
|
+
@options.stepwise = false
|
128
|
+
when 'preview'
|
129
|
+
@options.preview = true
|
130
|
+
task.shells.each do |mode, content|
|
131
|
+
process_shell(content, mode)
|
132
|
+
end
|
133
|
+
task.steps.each do |step|
|
134
|
+
process_step(step)
|
135
|
+
end
|
136
|
+
@options.preview = false
|
137
|
+
|
138
|
+
@io.indent(-1) do
|
139
|
+
process_task(task, false)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def process_step(step, overview = true)
|
147
|
+
process_block('- [ ]', step, overview) do
|
148
|
+
if @options.preview
|
149
|
+
step.shells.each do |mode, content|
|
150
|
+
process_shell(content, mode)
|
151
|
+
end
|
152
|
+
else
|
153
|
+
if @options.stepwise
|
154
|
+
@options.preview = true
|
155
|
+
step.shells.each do |content, mode|
|
156
|
+
process_shell(mode, content)
|
157
|
+
end
|
158
|
+
@options.preview = false
|
159
|
+
|
160
|
+
@io.newline
|
161
|
+
choice = @io.choose('yes', 'no', 'skip') do |menu|
|
162
|
+
menu.header = 'Continue?'
|
163
|
+
menu.flow = :inline
|
164
|
+
menu.index = :none
|
165
|
+
end
|
166
|
+
|
167
|
+
case choice
|
168
|
+
when 'yes'
|
169
|
+
proceed_with_step(step)
|
170
|
+
when 'no'
|
171
|
+
raise HaltSignal
|
172
|
+
when 'skip'
|
173
|
+
@io.indent_level -= 1
|
174
|
+
return
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
else
|
179
|
+
proceed_with_step(step)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def process_configs
|
186
|
+
unless @workflow.configs.empty?
|
187
|
+
@io.newline
|
188
|
+
@io.say "## #{stylize('Configuration', :underline)}"
|
189
|
+
@io.indent do
|
190
|
+
@io.newline
|
191
|
+
@io.say 'Please provide values for the following...'
|
192
|
+
|
193
|
+
@workflow.configs.each do |key, options|
|
194
|
+
answer = @io.ask("- #{key}: ") do |q|
|
195
|
+
q.default = (options[:default] || ENV[key])
|
196
|
+
if options[:required]
|
197
|
+
# q.validate = Proc.new { |a| ! a.empty? }
|
198
|
+
q.validate = /^.+$/
|
199
|
+
end
|
200
|
+
end
|
201
|
+
@env[key] = answer unless answer.empty?
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def process_guards
|
208
|
+
@workflow.guards.each do |description, command, check|
|
209
|
+
Open3.popen2e(@env, command) do |stdin, stdoe, wait_thread|
|
210
|
+
status = wait_thread.value
|
211
|
+
|
212
|
+
if status.success?
|
213
|
+
expected = check
|
214
|
+
unless expected.nil?
|
215
|
+
actual = stdoe.readlines.join.strip
|
216
|
+
|
217
|
+
if check.is_a?(Regexp)
|
218
|
+
operation = :=~
|
219
|
+
else
|
220
|
+
operation = :==
|
221
|
+
expected = clean(expected)
|
222
|
+
end
|
223
|
+
|
224
|
+
unless actual.send(operation, expected)
|
225
|
+
guard_error(description, command, expected, actual)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
else
|
229
|
+
guard_error(description, command, nil, status.exitstatus)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def process_block(prefix, component, overview = true, style = nil)
|
236
|
+
@context.push({ sources: [] })
|
237
|
+
|
238
|
+
if overview
|
239
|
+
@io.newline
|
240
|
+
@io.say("#{prefix} #{stylize(component.heading, style)}")
|
241
|
+
end
|
242
|
+
|
243
|
+
@io.indent do
|
244
|
+
unless component.prologue.nil?
|
245
|
+
@io.newline
|
246
|
+
@io.say component.prologue
|
247
|
+
end if overview
|
248
|
+
|
249
|
+
yield if block_given?
|
250
|
+
end
|
251
|
+
|
252
|
+
@context.pop
|
253
|
+
end
|
254
|
+
|
255
|
+
def process_shell(content, mode)
|
256
|
+
if @options.preview
|
257
|
+
preview(content, mode) unless [:export, :source].include?(mode)
|
258
|
+
else
|
259
|
+
execute(content, mode)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# ---
|
264
|
+
|
265
|
+
def preview(content, mode)
|
266
|
+
if mode == :script
|
267
|
+
prefix = nil
|
268
|
+
content = "```\n#{content}\n```"
|
269
|
+
else
|
270
|
+
prefix = '→ '
|
271
|
+
end
|
272
|
+
|
273
|
+
@io.newline
|
274
|
+
content.split("\n").each do |action|
|
275
|
+
if action.empty?
|
276
|
+
@io.newline
|
277
|
+
else
|
278
|
+
@io.say("#{prefix}#{stylize(action, :bold)}")
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# ---
|
284
|
+
|
285
|
+
def execute(content, mode)
|
286
|
+
case mode
|
287
|
+
when :export
|
288
|
+
@context.last[:sources] << load_export(content)
|
289
|
+
when :source
|
290
|
+
@context.last[:sources] << load_source(content)
|
291
|
+
when :script
|
292
|
+
execute_script(content)
|
293
|
+
when :lines
|
294
|
+
execute_lines(content)
|
295
|
+
when :popen
|
296
|
+
execute_popen(content)
|
297
|
+
when :ticks
|
298
|
+
execute_ticks(content)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def load_export(content)
|
303
|
+
command = content.gsub(/"/, '\"').gsub(/\$/, "\\$").lstrip
|
304
|
+
command = %Q{bash -c "#{command}"}
|
305
|
+
|
306
|
+
Open3.popen3(@env, command) do |stdin, stdout, stderr, wait_thread|
|
307
|
+
status = wait_thread.value
|
308
|
+
|
309
|
+
unless status.success?
|
310
|
+
while line = stderr.gets
|
311
|
+
error(line)
|
312
|
+
end
|
313
|
+
exit 1
|
314
|
+
end
|
315
|
+
|
316
|
+
YAML.load(clean(stdout.read)).map do |key, value|
|
317
|
+
%Q(export #{key.upcase}="#{value}")
|
318
|
+
end.join("\n").gsub(/"/, '\"').gsub(/\$/, "\\$")
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def load_source(content)
|
323
|
+
content.gsub(/"/, '\"').gsub(/\$/, "\\$")
|
324
|
+
end
|
325
|
+
|
326
|
+
def execute_script(content)
|
327
|
+
preview(content, :script)
|
328
|
+
|
329
|
+
if @options.stepwise
|
330
|
+
@io.newline
|
331
|
+
choice = @io.choose('yes', 'no', 'skip') do |menu|
|
332
|
+
menu.header = 'Continue?'
|
333
|
+
menu.flow = :inline
|
334
|
+
menu.index = :none
|
335
|
+
end
|
336
|
+
|
337
|
+
case choice
|
338
|
+
when 'yes'
|
339
|
+
@io.newline
|
340
|
+
proceed_with_script(content)
|
341
|
+
when 'no'
|
342
|
+
raise HaltSignal
|
343
|
+
when 'skip'
|
344
|
+
@io.newline
|
345
|
+
end
|
346
|
+
else
|
347
|
+
proceed_with_script(content)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def execute_lines(content)
|
352
|
+
actions = content.split("\n")
|
353
|
+
actions.each do |action|
|
354
|
+
preview(action, :lines)
|
355
|
+
|
356
|
+
if @options.stepwise
|
357
|
+
@io.newline
|
358
|
+
choice = @io.choose('yes', 'no', 'skip') do |menu|
|
359
|
+
menu.header = 'Continue?'
|
360
|
+
menu.flow = :inline
|
361
|
+
menu.index = :none
|
362
|
+
end
|
363
|
+
|
364
|
+
case choice
|
365
|
+
when 'yes'
|
366
|
+
@io.newline
|
367
|
+
proceed_with_line(action)
|
368
|
+
next
|
369
|
+
when 'no'
|
370
|
+
raise HaltSignal
|
371
|
+
when 'skip'
|
372
|
+
@io.newline
|
373
|
+
next
|
374
|
+
end
|
375
|
+
else
|
376
|
+
proceed_with_line(action)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def execute_popen(content)
|
382
|
+
lines = content.split("\n")
|
383
|
+
lines.each do |line|
|
384
|
+
preview(line, :popen)
|
385
|
+
|
386
|
+
# TODO: stepwise
|
387
|
+
command = line.gsub(/"/, '\"').gsub(/\$/, "\\$")
|
388
|
+
command = (sources + [command]).join("\n")
|
389
|
+
command = %Q{bash -c "#{command} 2>&1"}
|
390
|
+
|
391
|
+
IO.popen(@env, command) do |pipe|
|
392
|
+
@io.indent do
|
393
|
+
pipe.each do |line|
|
394
|
+
@io.say(line)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
exit 1 unless $?.success?
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# def execute_ticks(content)
|
404
|
+
# content = content.gsub(/(\$[a-zA-Z0-9_]+)/) { |match| @env[match[1..-1]] }
|
405
|
+
# `#{content}`
|
406
|
+
# end
|
407
|
+
|
408
|
+
# ---
|
409
|
+
|
410
|
+
def proceed_with_task(task)
|
411
|
+
task.shells.each do |mode, content|
|
412
|
+
process_shell(content, mode)
|
413
|
+
end
|
414
|
+
|
415
|
+
task.steps.each do |step|
|
416
|
+
process_step(step)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def proceed_with_step(step)
|
421
|
+
step.shells.each do |mode, content|
|
422
|
+
process_shell(content, mode)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
def proceed_with_script(script)
|
427
|
+
script = script.gsub(/"/, '\"').gsub(/\$/, "\\$")
|
428
|
+
script = (sources + [script]).join("\n")
|
429
|
+
script = %Q{bash -c "#{script}"}
|
430
|
+
|
431
|
+
Open3.popen2e(@env, script) do |stdin, stdoe, wait_thread|
|
432
|
+
status = wait_thread.value
|
433
|
+
|
434
|
+
@io.indent do
|
435
|
+
while line = stdoe.gets
|
436
|
+
@io.say("⫶ #{line}")
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
exit 1 unless status.success?
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
def proceed_with_line(line)
|
445
|
+
command = line.gsub(/"/, '\"').gsub(/\$/, "\\$")
|
446
|
+
command = (sources + [command]).join("\n")
|
447
|
+
command = %Q{bash -c "#{command}"}
|
448
|
+
|
449
|
+
Open3.popen2e(@env, command) do |stdin, stdoe, wait_thread|
|
450
|
+
status = wait_thread.value
|
451
|
+
|
452
|
+
@io.indent do
|
453
|
+
while line = stdoe.gets
|
454
|
+
@io.say("⫶ #{line}")
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
exit 1 unless status.success?
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
# ---
|
463
|
+
|
464
|
+
def sources
|
465
|
+
@context.map { |c| c[:sources] }.flatten
|
466
|
+
end
|
467
|
+
|
468
|
+
def clean(string)
|
469
|
+
return if string.nil?
|
470
|
+
|
471
|
+
indent = (string.scan(/^[ \t]*(?=\S)/).min || '').size
|
472
|
+
string.gsub(/^[ \t]{#{indent}}/, '').strip
|
473
|
+
end
|
474
|
+
|
475
|
+
def error(message)
|
476
|
+
@io.say stylize(message, :red)
|
477
|
+
end
|
478
|
+
|
479
|
+
def guard_error(description, command, check, actual)
|
480
|
+
case check
|
481
|
+
when nil
|
482
|
+
message = "Exit code was #{actual}"
|
483
|
+
when String
|
484
|
+
message = ['Output did not equal expected', check, actual]
|
485
|
+
when Regexp
|
486
|
+
message = ['Output did not match expected', check.inspect, actual]
|
487
|
+
end
|
488
|
+
|
489
|
+
@io.newline
|
490
|
+
error "Guard failed: '#{description}'"
|
491
|
+
@io.indent do
|
492
|
+
error "→ #{command}"
|
493
|
+
end
|
494
|
+
|
495
|
+
if message.is_a?(Array)
|
496
|
+
error message[0]
|
497
|
+
|
498
|
+
@io.newline
|
499
|
+
@io.say stylize('Expected:', :bold)
|
500
|
+
@io.indent do
|
501
|
+
@io.say message[1]
|
502
|
+
end
|
503
|
+
|
504
|
+
@io.newline
|
505
|
+
@io.say stylize('Actual:', :bold)
|
506
|
+
@io.indent do
|
507
|
+
@io.say message[2]
|
508
|
+
end
|
509
|
+
else
|
510
|
+
error message
|
511
|
+
end
|
512
|
+
|
513
|
+
raise GuardError, message[0]
|
514
|
+
end
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'wip/runner/workflow/builder'
|
2
|
+
require 'wip/runner/workflow/builder/component'
|
3
|
+
require 'wip/runner/workflow/builder/workflow'
|
4
|
+
require 'wip/runner/workflow/builder/task'
|
5
|
+
require 'wip/runner/workflow/builder/step'
|
6
|
+
require 'wip/runner/workflow/runner'
|
7
|
+
|
8
|
+
module WIP
|
9
|
+
module Runner
|
10
|
+
module Workflow
|
11
|
+
class Error < WIP::Runner::Error; end
|
12
|
+
class GuardError < Error; end
|
13
|
+
class HaltSignal < Error; end
|
14
|
+
|
15
|
+
def self.define(&block)
|
16
|
+
command = (eval 'self', block.send(:binding))
|
17
|
+
command.send(:include, InstanceMethods)
|
18
|
+
|
19
|
+
command.class_exec do
|
20
|
+
options do |parser, config|
|
21
|
+
config.overview = false
|
22
|
+
config.preview = false
|
23
|
+
|
24
|
+
parser.on('--overview', 'Prints workflow overview') do
|
25
|
+
config.no_validate = true
|
26
|
+
config.overview = true
|
27
|
+
end
|
28
|
+
|
29
|
+
parser.on('--preview', 'Prints workflow preview') do
|
30
|
+
config.preview = true
|
31
|
+
end
|
32
|
+
|
33
|
+
define_method(:builder) do
|
34
|
+
@builder ||= Builder.new(self, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module InstanceMethods
|
41
|
+
def execute(arguments, options)
|
42
|
+
workflow = builder.build(arguments, options)
|
43
|
+
runner = Runner.new(@io, workflow)
|
44
|
+
runner.run(options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/wip/runner.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'wip/runner/version'
|
2
|
+
require 'wip/runner/errors'
|
3
|
+
require 'wip/runner/command'
|
4
|
+
require 'wip/runner/options'
|
5
|
+
require 'wip/runner/parser'
|
6
|
+
|
7
|
+
require 'wip/runner/cli'
|
8
|
+
require 'wip/runner/cli/help'
|
9
|
+
require 'wip/runner/cli/version'
|
10
|
+
|
11
|
+
require 'wip/runner/shell'
|
12
|
+
require 'wip/runner/workflow'
|
13
|
+
require 'wip/runner/commands'
|
14
|
+
|
15
|
+
module WIP
|
16
|
+
module Runner ; end
|
17
|
+
end
|
data/wip-runner.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'wip/runner/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "wip-runner"
|
8
|
+
spec.version = WIP::Runner::VERSION
|
9
|
+
spec.authors = ["Corey Innis"]
|
10
|
+
spec.email = ["corey@coolerator.net"]
|
11
|
+
|
12
|
+
spec.summary = %q{wip-runner is a generic CLI stand-alone and library.}
|
13
|
+
spec.description = %q{
|
14
|
+
wip-runner...
|
15
|
+
- A generic CLI (loads context-based commands)
|
16
|
+
- A library for building such commands
|
17
|
+
}
|
18
|
+
spec.homepage = "https://github.com/coreyti/wip-runner"
|
19
|
+
spec.license = "MIT"
|
20
|
+
|
21
|
+
if spec.respond_to?(:metadata)
|
22
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
23
|
+
else
|
24
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
25
|
+
end
|
26
|
+
|
27
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
28
|
+
spec.bindir = "exe"
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
spec.add_dependency "highline"
|
33
|
+
|
34
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
35
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
36
|
+
spec.add_development_dependency "rspec"
|
37
|
+
end
|