scripted 0.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.
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +6 -0
- data/MIT-LICENSE +22 -0
- data/README.md +423 -0
- data/Rakefile +39 -0
- data/bin/scripted +67 -0
- data/cucumber.yml +3 -0
- data/examples/important.rb +31 -0
- data/examples/parallel.rb +30 -0
- data/examples/pride.rb +37 -0
- data/examples/websockets.png +0 -0
- data/examples/websockets.rb +37 -0
- data/examples/websockets/public/ansiparse.js +156 -0
- data/examples/websockets/server.rb +32 -0
- data/examples/websockets/server.ru +10 -0
- data/examples/websockets/views/_client.handlebars +47 -0
- data/examples/websockets/views/app.coffee +210 -0
- data/examples/websockets/views/index.erb +1 -0
- data/examples/websockets/views/layout.erb +14 -0
- data/examples/websockets/views/style.sass +61 -0
- data/features/controlling_exit_status.feature +124 -0
- data/features/formatters.feature +142 -0
- data/features/rake_integration.feature +86 -0
- data/features/running_commands_in_parallel.feature +27 -0
- data/features/running_from_command_line.feature +56 -0
- data/features/running_from_ruby.feature +38 -0
- data/features/running_groups.feature +39 -0
- data/features/specifying_which_commands_to_run.feature +122 -0
- data/features/steps/scripted_steps.rb +25 -0
- data/features/support/aruba.rb +5 -0
- data/features/support/env.rb +2 -0
- data/install +5 -0
- data/lib/scripted.rb +28 -0
- data/lib/scripted/command.rb +82 -0
- data/lib/scripted/commands/rake.rb +25 -0
- data/lib/scripted/commands/ruby.rb +22 -0
- data/lib/scripted/commands/shell.rb +28 -0
- data/lib/scripted/configuration.rb +103 -0
- data/lib/scripted/error.rb +13 -0
- data/lib/scripted/formatters/announcer.rb +39 -0
- data/lib/scripted/formatters/blank.rb +97 -0
- data/lib/scripted/formatters/default.rb +62 -0
- data/lib/scripted/formatters/human_status.rb +38 -0
- data/lib/scripted/formatters/stats.rb +38 -0
- data/lib/scripted/formatters/table.rb +99 -0
- data/lib/scripted/formatters/websocket.rb +137 -0
- data/lib/scripted/group.rb +49 -0
- data/lib/scripted/output/command_logger.rb +42 -0
- data/lib/scripted/output/logger.rb +139 -0
- data/lib/scripted/rake_task.rb +24 -0
- data/lib/scripted/runner.rb +19 -0
- data/lib/scripted/running/execute.rb +16 -0
- data/lib/scripted/running/run_command.rb +101 -0
- data/lib/scripted/running/run_commands.rb +98 -0
- data/lib/scripted/running/select_commands.rb +22 -0
- data/lib/scripted/version.rb +3 -0
- data/scripted.gemspec +35 -0
- data/scripted.rb +16 -0
- data/spec/scripted/command_spec.rb +72 -0
- data/spec/scripted/commands/ruby_spec.rb +10 -0
- data/spec/scripted/commands/shell_spec.rb +18 -0
- data/spec/scripted/configuration_spec.rb +50 -0
- data/spec/scripted/formatters/websocket_spec.rb +14 -0
- data/spec/scripted/group_spec.rb +49 -0
- data/spec/scripted/running/run_command_spec.rb +157 -0
- data/spec/scripted/running/run_commands_spec.rb +150 -0
- data/spec/scripted/running/select_commands_spec.rb +28 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/expect_to_receive.rb +17 -0
- data/spec/support/io_capture.rb +50 -0
- metadata +340 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
require "scripted/formatters/blank"
|
2
|
+
|
3
|
+
# The default provides two things:
|
4
|
+
#
|
5
|
+
# * Print the regular command output
|
6
|
+
# * Print exceptions that happen during the output
|
7
|
+
#
|
8
|
+
# When running commands in parallel, output would be intertwined. That's why
|
9
|
+
# this formatter buffers the output if outputting to a file, than the file will
|
10
|
+
# have the output in the right order at the end.
|
11
|
+
module Scripted
|
12
|
+
module Formatters
|
13
|
+
class Default < Blank
|
14
|
+
|
15
|
+
def initialize(*)
|
16
|
+
super
|
17
|
+
@buffers = Hash.new { |h,k| h[k] = StringIO.new }
|
18
|
+
end
|
19
|
+
|
20
|
+
def each_char(output, command)
|
21
|
+
if file?
|
22
|
+
@buffers[command].print output
|
23
|
+
else
|
24
|
+
print output
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def exception(command, exception)
|
29
|
+
command_puts command, red(" #{exception.class} during the execution of #{command.name.inspect}:")
|
30
|
+
exception.to_s.split("\n").each do |line|
|
31
|
+
command_puts command, red(" #{line}")
|
32
|
+
end
|
33
|
+
clean_backtrace(exception.backtrace).each do |line|
|
34
|
+
command_puts command, cyan(" # #{line}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def close
|
39
|
+
if file?
|
40
|
+
@buffers.each do |command, output|
|
41
|
+
output.rewind
|
42
|
+
puts output.read
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def command_puts(command, *args)
|
48
|
+
if file?
|
49
|
+
@buffers[command].puts *args
|
50
|
+
else
|
51
|
+
puts *args
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def clean_backtrace(backtrace)
|
56
|
+
gem_path = File.expand_path("../../../", __FILE__)
|
57
|
+
backtrace.reject { |line| line.start_with?(gem_path) }
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Scripted
|
2
|
+
module Formatters
|
3
|
+
module HumanStatus
|
4
|
+
|
5
|
+
def human_status(command)
|
6
|
+
translations = {
|
7
|
+
:running => "running",
|
8
|
+
:success_ran_because_other_command_failed => "success, ran because other command failed",
|
9
|
+
:failed_ran_because_other_command_failed => "failed, ran because other command failed",
|
10
|
+
:skipped_because_no_other_command_failed => "skipped, because no other command failed",
|
11
|
+
:failed_but_ignored => "failed, but ignored",
|
12
|
+
:success => "success",
|
13
|
+
:failed_and_halted => "failed and halted",
|
14
|
+
:failed => "failed",
|
15
|
+
:not_executed => "didn't run",
|
16
|
+
:unknown => "unknown"
|
17
|
+
}
|
18
|
+
translations[status_code(command)]
|
19
|
+
end
|
20
|
+
|
21
|
+
def status_code(command)
|
22
|
+
case
|
23
|
+
when command.running? then :running
|
24
|
+
when command.only_when_failed? && command.success? then :success_ran_because_other_command_failed
|
25
|
+
when command.only_when_failed? && command.failed? then :failed_ran_because_other_command_failed
|
26
|
+
when command.only_when_failed? && !command.executed? then :skipped_because_no_other_command_failed
|
27
|
+
when command.failed_but_unimportant? then :failed_but_ignored
|
28
|
+
when command.success? then :success
|
29
|
+
when command.halted? then :failed_and_halted
|
30
|
+
when command.failed? then :failed
|
31
|
+
when !command.executed? then :not_executed
|
32
|
+
else :unknown
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "scripted/formatters/blank"
|
2
|
+
require 'scripted/formatters/human_status'
|
3
|
+
|
4
|
+
|
5
|
+
module Scripted
|
6
|
+
module Formatters
|
7
|
+
class Stats < Blank
|
8
|
+
include HumanStatus
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'fastercsv'
|
12
|
+
CSV = FasterCSV
|
13
|
+
rescue LoadError
|
14
|
+
require 'csv'
|
15
|
+
end
|
16
|
+
|
17
|
+
def stop(commands, runner)
|
18
|
+
if out.is_a?(File)
|
19
|
+
CSV.open(out.path, "wb", &csv(commands))
|
20
|
+
else
|
21
|
+
puts CSV.generate(&csv(commands))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def csv(commands)
|
28
|
+
lambda do |csv|
|
29
|
+
csv << ["name", "runtime", "status"]
|
30
|
+
commands.each do |command|
|
31
|
+
csv << [command.name, command.runtime, human_status(command)]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "scripted/formatters/blank"
|
4
|
+
require 'scripted/formatters/human_status'
|
5
|
+
|
6
|
+
module Scripted
|
7
|
+
module Formatters
|
8
|
+
class Table < Blank
|
9
|
+
include HumanStatus
|
10
|
+
|
11
|
+
def stop(commands, runner)
|
12
|
+
report_lines << [ Column["Command"], Column["Runtime"], Column["Status"] ]
|
13
|
+
commands.each do |command|
|
14
|
+
report_lines << [ Column[command.name], Column[command.runtime, "s"], Column[human_status(command)] ]
|
15
|
+
end
|
16
|
+
widths = report_lines.transpose.map { |line| line.max_by(&:size).size }
|
17
|
+
header = report_lines.shift
|
18
|
+
puts ""
|
19
|
+
puts ""
|
20
|
+
puts separator(widths, "┌", "┬", "┐")
|
21
|
+
puts report_line(header, widths)
|
22
|
+
puts separator(widths, "├", "┼", "┤")
|
23
|
+
report_lines.each do |line|
|
24
|
+
puts report_line(line, widths)
|
25
|
+
end
|
26
|
+
puts separator(widths, "└", "┴", "┘")
|
27
|
+
puts " Total runtime: #{Column[runner.runtime, "s"]}"
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def separator(widths, left, middle, right)
|
33
|
+
cyan(left) + widths.map { |width| force_encoding(cyan("─") * (width + 2)) }.join(cyan(middle)) + cyan(right)
|
34
|
+
end
|
35
|
+
|
36
|
+
def report_line(line, widths)
|
37
|
+
cyan("│ ") + force_encoding(line.zip(widths).map { |(column, width)| column.aligned(width) }.join(cyan(" │ "))) + cyan(" │")
|
38
|
+
end
|
39
|
+
|
40
|
+
def report_lines
|
41
|
+
@report_lines ||= []
|
42
|
+
end
|
43
|
+
|
44
|
+
def force_encoding(text)
|
45
|
+
if text.respond_to?(:force_encoding)
|
46
|
+
text.force_encoding('utf-8')
|
47
|
+
else # ruby 1.8
|
48
|
+
text
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Column
|
53
|
+
|
54
|
+
attr_reader :value, :suffix
|
55
|
+
|
56
|
+
def self.[](value, suffix = "")
|
57
|
+
new(value, suffix)
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(value, suffix)
|
61
|
+
@value, @suffix = value, suffix
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
string = case value
|
66
|
+
when Fixnum
|
67
|
+
value.to_s
|
68
|
+
when Numeric
|
69
|
+
"%.3f" % value
|
70
|
+
when NilClass
|
71
|
+
nil
|
72
|
+
else
|
73
|
+
value.to_s
|
74
|
+
end
|
75
|
+
if string
|
76
|
+
"#{string}#{suffix}"
|
77
|
+
else
|
78
|
+
""
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def size
|
83
|
+
to_s.size
|
84
|
+
end
|
85
|
+
|
86
|
+
def aligned(width)
|
87
|
+
case value
|
88
|
+
when Numeric
|
89
|
+
to_s.rjust(width)
|
90
|
+
else
|
91
|
+
to_s.ljust(width)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
require "scripted/formatters/blank"
|
5
|
+
require "scripted/formatters/human_status"
|
6
|
+
|
7
|
+
module Scripted
|
8
|
+
module Formatters
|
9
|
+
class Websocket < Blank
|
10
|
+
include HumanStatus
|
11
|
+
|
12
|
+
def initialize(out, configuration)
|
13
|
+
@uri = URI.parse(out)
|
14
|
+
super(@uri, configuration)
|
15
|
+
@buffers = Hash.new { |h,k| h[k] = "" }
|
16
|
+
@flusher = Thread.new do
|
17
|
+
loop do
|
18
|
+
flush
|
19
|
+
sleep 0.1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
publish :action => :initialize
|
23
|
+
end
|
24
|
+
|
25
|
+
def each_char(char, command)
|
26
|
+
@buffers[command] << char
|
27
|
+
end
|
28
|
+
|
29
|
+
def start(commands, runner)
|
30
|
+
flush!
|
31
|
+
publish :action => :start, :commands => commands.map { |command| command_json(command) }, :runner => runner_json(runner)
|
32
|
+
end
|
33
|
+
|
34
|
+
def stop(commands, runner)
|
35
|
+
flush!
|
36
|
+
publish :action => :stop, :commands => commands.map { |command| command_json(command) }, :runner => runner_json(runner)
|
37
|
+
end
|
38
|
+
|
39
|
+
def exception(command, exception)
|
40
|
+
flush!
|
41
|
+
publish :action => :exception, :command => command_json(command), :exception => exception, :backtrace => exception.backtrace
|
42
|
+
end
|
43
|
+
|
44
|
+
def done(command)
|
45
|
+
flush!
|
46
|
+
publish :action => :done, :command => command_json(command)
|
47
|
+
end
|
48
|
+
|
49
|
+
def halted(command)
|
50
|
+
flush!
|
51
|
+
publish :action => :halted, :command => command_json(command)
|
52
|
+
end
|
53
|
+
|
54
|
+
def execute(command)
|
55
|
+
flush!
|
56
|
+
publish :action => :execute, :command => command_json(command)
|
57
|
+
end
|
58
|
+
|
59
|
+
def flush!
|
60
|
+
flush
|
61
|
+
@buffer = ""
|
62
|
+
end
|
63
|
+
|
64
|
+
def flush
|
65
|
+
if output != ""
|
66
|
+
publish :action => :output, :output => output
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def close
|
71
|
+
@flusher.exit
|
72
|
+
flush!
|
73
|
+
publish :action => :close
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def publish(data)
|
79
|
+
message = {:channel => "/scripted", :data => data }
|
80
|
+
Net::HTTP.post_form(@uri, :message => message.to_json)
|
81
|
+
rescue Errno::ECONNREFUSED
|
82
|
+
unless @warned
|
83
|
+
warn red("No connection to #{@uri}")
|
84
|
+
@warned = true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def output
|
89
|
+
@buffers.map { |command, output| { :command => command_json(command), :output => output } }
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def command_json(command)
|
94
|
+
{ :name => command.name,
|
95
|
+
:forced => command.forced?,
|
96
|
+
:success => command.success?,
|
97
|
+
:halted => command.halted?,
|
98
|
+
:executed => command.executed?,
|
99
|
+
:only_when_failed => command.only_when_failed?,
|
100
|
+
:parallel => command.parallel?,
|
101
|
+
:parallel_id => command.parallel_id,
|
102
|
+
:failed_but_unimportant => command.failed_but_unimportant?,
|
103
|
+
:unimportant => command.unimportant?,
|
104
|
+
:command_type => command.command.executable.class.to_s,
|
105
|
+
:runtime => command.runtime,
|
106
|
+
:started_at => time(command.started_at),
|
107
|
+
:ended_at => time(command.ended_at),
|
108
|
+
:running => command.running?,
|
109
|
+
:id => command.object_id,
|
110
|
+
:human_status => human_status(command),
|
111
|
+
:status_code => status_code(command)
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def runner_json(runner)
|
116
|
+
{ :started_at => time(runner.started_at),
|
117
|
+
:ended_at => time(runner.ended_at),
|
118
|
+
:runtime => runner.runtime,
|
119
|
+
:halted => runner.halted?,
|
120
|
+
:running => runner.running?,
|
121
|
+
:executed => runner.executed?,
|
122
|
+
:failed => runner.failed?
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
def time(time)
|
127
|
+
if time
|
128
|
+
time.utc.iso8601
|
129
|
+
else
|
130
|
+
time
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "scripted/command"
|
2
|
+
|
3
|
+
module Scripted
|
4
|
+
class Group
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
@parallel = false
|
11
|
+
@parallel_id = (object_id ** 2)
|
12
|
+
end
|
13
|
+
|
14
|
+
def define(&block)
|
15
|
+
instance_eval &block if block
|
16
|
+
end
|
17
|
+
|
18
|
+
def run(name, &block)
|
19
|
+
next_parallel_id unless in_parallel?
|
20
|
+
commands << Command.new(name, :parallel_id => parallel_id, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def parallel(&block)
|
24
|
+
@parallel = true
|
25
|
+
yield
|
26
|
+
@parallel = false
|
27
|
+
next_parallel_id
|
28
|
+
end
|
29
|
+
|
30
|
+
def commands
|
31
|
+
@commands ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def parallel_id
|
37
|
+
@parallel_id
|
38
|
+
end
|
39
|
+
|
40
|
+
def next_parallel_id
|
41
|
+
@parallel_id += 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def in_parallel?
|
45
|
+
@parallel
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Scripted
|
2
|
+
module Output
|
3
|
+
class CommandLogger
|
4
|
+
|
5
|
+
attr_reader :logger, :command
|
6
|
+
|
7
|
+
attr_reader :read, :write, :reader
|
8
|
+
|
9
|
+
def initialize(logger, command)
|
10
|
+
|
11
|
+
@logger, @command = logger, command
|
12
|
+
|
13
|
+
@read, @write = IO.pipe
|
14
|
+
|
15
|
+
@reader = Thread.new do
|
16
|
+
read.each_char do |char|
|
17
|
+
logger.send_to_formatters :each_char, char, command
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_io
|
24
|
+
write
|
25
|
+
end
|
26
|
+
|
27
|
+
def sync
|
28
|
+
read.sync
|
29
|
+
write.sync
|
30
|
+
sleep 0.01
|
31
|
+
end
|
32
|
+
|
33
|
+
def close
|
34
|
+
sync
|
35
|
+
reader.exit
|
36
|
+
write.close
|
37
|
+
read.close
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|