wip-runner 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,63 @@
|
|
1
|
+
require 'expect'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module WIP
|
5
|
+
module Runner
|
6
|
+
module Shell
|
7
|
+
module Handlers
|
8
|
+
class Script < Base
|
9
|
+
attr_reader :prompts
|
10
|
+
|
11
|
+
def initialize(*)
|
12
|
+
@prompts = []
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
@description ||= "```\n#{@content}\n```"
|
18
|
+
# @description ||= @content
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute(io, env, &block)
|
22
|
+
prompts.empty? ? simplex!(io, env, &block) : complex!(io, env, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def prompt(term, options = {})
|
26
|
+
@prompts << [term, options] # Prompt.new(...)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def simplex!(io, env, &block)
|
32
|
+
Open3.popen2e(env, executable) do |stdin, stdoe, thread|
|
33
|
+
while line = stdoe.gets
|
34
|
+
block.call(line)
|
35
|
+
end
|
36
|
+
|
37
|
+
thread.value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def complex!(io, env, &block)
|
42
|
+
Open3.popen2e(env, executable) do |stdin, stdoe, thread|
|
43
|
+
prompts.each do |term, options|
|
44
|
+
stdoe.expect(term) do |result|
|
45
|
+
stdin.puts io.ask(term) do |q|
|
46
|
+
options.each { |k, v| q.send(:"#{k}=", v) }
|
47
|
+
end
|
48
|
+
stdoe.gets
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
while line = stdoe.gets
|
53
|
+
block.call(line)
|
54
|
+
end
|
55
|
+
|
56
|
+
thread.value
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module WIP
|
2
|
+
module Runner
|
3
|
+
module Shell
|
4
|
+
module Handlers
|
5
|
+
class System < Base
|
6
|
+
def description
|
7
|
+
# @description ||= "```\n#{@content}\n```"
|
8
|
+
@description ||= @content
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute(io, env, &block)
|
12
|
+
IO.popen(env, executable, 'r') do |pipe|
|
13
|
+
pipe.each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
$?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'wip/runner/shell/handlers/base'
|
2
|
+
require 'wip/runner/shell/handlers/script'
|
3
|
+
require 'wip/runner/shell/handlers/system'
|
4
|
+
|
5
|
+
module WIP
|
6
|
+
module Runner
|
7
|
+
module Shell
|
8
|
+
module Handlers
|
9
|
+
class << self
|
10
|
+
def locate(key)
|
11
|
+
const_get(key.to_s.capitalize)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module WIP
|
4
|
+
module Runner
|
5
|
+
module Shell
|
6
|
+
class Runner
|
7
|
+
attr_reader :arguments, :options
|
8
|
+
|
9
|
+
def initialize(io, tasks, env = {})
|
10
|
+
@io = io
|
11
|
+
@tasks = tasks
|
12
|
+
@env = env
|
13
|
+
@io.indent_size = 2
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(arguments, options)
|
17
|
+
@arguments = arguments
|
18
|
+
@options = options
|
19
|
+
|
20
|
+
@io.indent do
|
21
|
+
@tasks.each do |task|
|
22
|
+
evaluate(task)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def evaluate(task)
|
30
|
+
task.build(arguments, options)
|
31
|
+
prefix = options.preview ? :preview : :execute
|
32
|
+
|
33
|
+
section('Config') do
|
34
|
+
# @io.newline
|
35
|
+
# @io.say '```'
|
36
|
+
task.configs.each do |term, options|
|
37
|
+
send(:"#{prefix}_config", term, options)
|
38
|
+
end
|
39
|
+
# @io.say '```'
|
40
|
+
end unless task.configs.empty?
|
41
|
+
|
42
|
+
task.shells.each do |shell|
|
43
|
+
section("Shell #{shell.type.downcase}") do
|
44
|
+
@io.newline
|
45
|
+
send(:"#{prefix}_shell", shell)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# p task.children
|
50
|
+
task.children.each do |child|
|
51
|
+
section('Task') do
|
52
|
+
evaluate(child)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# ---
|
58
|
+
|
59
|
+
# class Execute
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# class Preview
|
63
|
+
# end
|
64
|
+
|
65
|
+
def execute_config(term, options = {})
|
66
|
+
query = options[:required] ? "#{term} (*)" : term
|
67
|
+
answer = @io.ask("- #{query}: ") do |q|
|
68
|
+
q.default = (options[:default] || ENV[term]) unless options[:password]
|
69
|
+
q.echo = false if options[:password]
|
70
|
+
q.validate = /^.+$/ if options[:required]
|
71
|
+
end
|
72
|
+
@env[term] = answer unless answer.empty?
|
73
|
+
@env[term] ||= ENV[term]
|
74
|
+
end
|
75
|
+
|
76
|
+
def execute_shell(shell)
|
77
|
+
preview_shell(shell)
|
78
|
+
|
79
|
+
if approved?
|
80
|
+
@io.newline
|
81
|
+
result = shell.execute(@io, @env) do |line|
|
82
|
+
# @io.say "> `#{line.rstrip}`<br>"
|
83
|
+
@io.say "> #{line.rstrip}"
|
84
|
+
# puts "#{@io.indentation}> `#{line.rstrip}` "
|
85
|
+
# @io.instance_variable_get(:@output).puts "> `#{line.rstrip}` "
|
86
|
+
|
87
|
+
# @io.send((action || :say), line)
|
88
|
+
end
|
89
|
+
@io.newline
|
90
|
+
|
91
|
+
# TODO: raise instead of exit.
|
92
|
+
exit 1 unless result.success?
|
93
|
+
else
|
94
|
+
@io.newline
|
95
|
+
@io.say '> (skipped)'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def preview_config(term, options = {})
|
100
|
+
message = options[:required] ? "#{term} (*)" : term
|
101
|
+
@io.say "- #{message}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def preview_shell(shell)
|
105
|
+
@io.say shell.description
|
106
|
+
end
|
107
|
+
|
108
|
+
# ---
|
109
|
+
|
110
|
+
def section(heading, &block)
|
111
|
+
@io.say "- [ ] #{heading}..."
|
112
|
+
@io.indent(&block)
|
113
|
+
end
|
114
|
+
|
115
|
+
# $ wip-runner x --format=markdown
|
116
|
+
# $ wip-runner x --log=out.md # log format determined by extension
|
117
|
+
#
|
118
|
+
# @formatter = Formatter::Markdown.new(@io)
|
119
|
+
#
|
120
|
+
# def item(text)
|
121
|
+
# formatter.item(text)
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# class Formatter::Markdown
|
125
|
+
# def item(text, check = true)
|
126
|
+
# @io.say check ? "- [ ] #{text}" : "- #{text}"
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# class Formatter::Plain
|
131
|
+
# class Formatter::Color
|
132
|
+
# class Formatter::HTML
|
133
|
+
# class Formatter::JSON
|
134
|
+
|
135
|
+
# ---
|
136
|
+
|
137
|
+
def approved?
|
138
|
+
return true unless @options.stepwise
|
139
|
+
|
140
|
+
# case choice
|
141
|
+
# when 'yes'
|
142
|
+
# end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module WIP
|
2
|
+
module Runner
|
3
|
+
module Shell
|
4
|
+
class Task
|
5
|
+
attr_reader :configs, :shells, :children
|
6
|
+
|
7
|
+
def initialize(command, &block)
|
8
|
+
@command = command
|
9
|
+
@configs = []
|
10
|
+
@shells = []
|
11
|
+
@children = []
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def build(arguments, options)
|
16
|
+
self.instance_exec(arguments, options, &@block) ; self
|
17
|
+
end
|
18
|
+
|
19
|
+
def config(term, options = {})
|
20
|
+
@configs << [term.to_s, options] # Config.new(...)
|
21
|
+
end
|
22
|
+
|
23
|
+
def shell(handler, content, &block)
|
24
|
+
shells << Handlers.locate(handler).new(content, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def task(&block)
|
28
|
+
children << Task.new(@command, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def method_missing(method_name, *args, &block)
|
34
|
+
if @command.respond_to?(method_name)
|
35
|
+
@command.send(method_name, *args, &block)
|
36
|
+
else
|
37
|
+
# super
|
38
|
+
@command.instance_eval {
|
39
|
+
method_missing(method_name, *args, &block)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def respond_to_missing?(method_name, include_private = false)
|
45
|
+
@command.respond_to?(method_name) || super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module WIP
|
2
|
+
module Runner
|
3
|
+
module Workflow
|
4
|
+
class Builder::Component
|
5
|
+
def prologue(value = nil)
|
6
|
+
@prologue = clean(value) unless value.nil?
|
7
|
+
@prologue
|
8
|
+
end
|
9
|
+
|
10
|
+
def shell(*args)
|
11
|
+
unless args.empty?
|
12
|
+
shells << [args[0], clean(args[1])]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def shells
|
17
|
+
@shells ||= []
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method_name, *args, &block)
|
21
|
+
if @command.respond_to?(method_name)
|
22
|
+
@command.send(method_name, *args, &block)
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def respond_to_missing?(method_name, include_private = false)
|
29
|
+
@command.respond_to?(method_name) || super
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def clean(string)
|
35
|
+
return if string.nil?
|
36
|
+
|
37
|
+
indent = (string.scan(/^[ \t]*(?=\S)/).min || '').size
|
38
|
+
string.gsub(/^[ \t]{#{indent}}/, '').strip
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module WIP
|
2
|
+
module Runner
|
3
|
+
module Workflow
|
4
|
+
class Builder::Step < Builder::Component
|
5
|
+
attr_reader :heading
|
6
|
+
|
7
|
+
def initialize(command, heading, &block)
|
8
|
+
@command = command
|
9
|
+
@heading = heading
|
10
|
+
instance_exec(&block) if block_given?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module WIP
|
2
|
+
module Runner
|
3
|
+
module Workflow
|
4
|
+
class Builder::Task < Builder::Component
|
5
|
+
attr_reader :heading, :steps
|
6
|
+
|
7
|
+
def initialize(command, heading, &block)
|
8
|
+
@command = command
|
9
|
+
@heading = heading
|
10
|
+
@steps = []
|
11
|
+
instance_exec(&block) if block_given?
|
12
|
+
end
|
13
|
+
|
14
|
+
def step(name, &block)
|
15
|
+
steps << Builder::Step.new(@command, name, &block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module WIP
|
2
|
+
module Runner
|
3
|
+
module Workflow
|
4
|
+
class Builder::Workflow < Builder::Component
|
5
|
+
def initialize(command)
|
6
|
+
@command = command
|
7
|
+
@configs = []
|
8
|
+
@guards = []
|
9
|
+
@tasks = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# ---
|
13
|
+
|
14
|
+
def config(key, options = {})
|
15
|
+
@configs << [key.to_s, options]
|
16
|
+
end
|
17
|
+
|
18
|
+
def guard(description, check, expectation = nil)
|
19
|
+
@guards << [description, check, expectation]
|
20
|
+
end
|
21
|
+
|
22
|
+
def task(heading, &block)
|
23
|
+
@tasks << Builder::Task.new(@command, heading, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
# ---
|
27
|
+
|
28
|
+
def heading
|
29
|
+
[@command.class.name.split('::').last, 'Workflow'].join(' ')
|
30
|
+
end
|
31
|
+
|
32
|
+
def overview
|
33
|
+
clean(@command.class.overview)
|
34
|
+
end
|
35
|
+
|
36
|
+
def configs
|
37
|
+
@configs
|
38
|
+
end
|
39
|
+
|
40
|
+
def guards
|
41
|
+
@guards
|
42
|
+
end
|
43
|
+
|
44
|
+
def tasks
|
45
|
+
@tasks
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module WIP
|
2
|
+
module Runner
|
3
|
+
module Workflow
|
4
|
+
class Builder
|
5
|
+
def initialize(command, &block)
|
6
|
+
@command = command
|
7
|
+
@block = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def build(arguments, options)
|
11
|
+
workflow = Workflow.new(@command)
|
12
|
+
workflow.instance_exec(arguments, options, &@block)
|
13
|
+
workflow
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|