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,139 @@
|
|
1
|
+
require 'scripted/output/command_logger'
|
2
|
+
|
3
|
+
module Scripted
|
4
|
+
module Output
|
5
|
+
class Logger
|
6
|
+
|
7
|
+
attr_reader :configuration
|
8
|
+
|
9
|
+
def initialize(configuration)
|
10
|
+
@configuration = configuration
|
11
|
+
if block_given?
|
12
|
+
begin
|
13
|
+
yield self
|
14
|
+
ensure
|
15
|
+
close
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def command_loggers
|
21
|
+
@command_loggers ||= Hash.new { |h,k| h[k] = CommandLogger.new(self, k) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.delegate_to_formatters(name)
|
25
|
+
define_method(name) { |*args, &block|
|
26
|
+
sync
|
27
|
+
send_to_formatters(name, *args, &block)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
delegate_to_formatters :stop
|
32
|
+
delegate_to_formatters :done
|
33
|
+
delegate_to_formatters :halted
|
34
|
+
delegate_to_formatters :execute
|
35
|
+
delegate_to_formatters :exception
|
36
|
+
|
37
|
+
def start(commands, runner)
|
38
|
+
# create all the command loggers
|
39
|
+
commands.each { |c| command_loggers[c] }
|
40
|
+
sync
|
41
|
+
send_to_formatters :start, commands, runner
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_io(command)
|
45
|
+
command_loggers[command].to_io
|
46
|
+
end
|
47
|
+
|
48
|
+
def send_to_formatters(*args, &block)
|
49
|
+
formatters.each { |formatter| formatter.send(*args, &block) }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def sync
|
55
|
+
command_loggers.each { |c, l| l.sync }
|
56
|
+
end
|
57
|
+
|
58
|
+
def close
|
59
|
+
command_loggers.each { |c, l| l.close }
|
60
|
+
send_to_formatters :close
|
61
|
+
end
|
62
|
+
|
63
|
+
def formatters
|
64
|
+
@formatters ||= build_formatters
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_formatters
|
68
|
+
formatter_names = configuration.formatters
|
69
|
+
if formatter_names.empty?
|
70
|
+
formatter_names = [ {:name => "default"} ]
|
71
|
+
end
|
72
|
+
formatters = formatter_names.uniq { |fn| fn[:name] }.map do |formatter|
|
73
|
+
find_formatter(formatter.fetch(:name)).new(formatter.fetch(:out, STDERR), configuration)
|
74
|
+
end
|
75
|
+
formatters
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_formatter(name)
|
79
|
+
built_in_formatter(name) || custom_formatter(name)
|
80
|
+
end
|
81
|
+
|
82
|
+
def built_in_formatter(formatter_ref)
|
83
|
+
case formatter_ref.to_s.downcase
|
84
|
+
when 'announcer', 'a', 'ann', 'announce'
|
85
|
+
require 'scripted/formatters/announcer'
|
86
|
+
Scripted::Formatters::Announcer
|
87
|
+
when 'blank'
|
88
|
+
require 'scripted/formatters/blank'
|
89
|
+
Scripted::Formatters::Blank
|
90
|
+
when 'default', 'd'
|
91
|
+
require 'scripted/formatters/default'
|
92
|
+
Scripted::Formatters::Default
|
93
|
+
when 'table', 't'
|
94
|
+
require 'scripted/formatters/table'
|
95
|
+
Scripted::Formatters::Table
|
96
|
+
when 'stats', 's', 'stat'
|
97
|
+
require 'scripted/formatters/stats'
|
98
|
+
Scripted::Formatters::Stats
|
99
|
+
when 'websockets', 'w', 'web', 'websocket'
|
100
|
+
require 'scripted/formatters/websocket'
|
101
|
+
Scripted::Formatters::Websocket
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def custom_formatter(formatter_ref)
|
106
|
+
if Class === formatter_ref
|
107
|
+
formatter_ref
|
108
|
+
elsif string_const?(formatter_ref)
|
109
|
+
begin
|
110
|
+
eval(formatter_ref)
|
111
|
+
rescue NameError
|
112
|
+
require path_for(formatter_ref)
|
113
|
+
eval(formatter_ref)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def string_const?(str)
|
119
|
+
str.is_a?(String) && /\A[A-Z][a-zA-Z0-9_:]*\z/ =~ str
|
120
|
+
end
|
121
|
+
|
122
|
+
def path_for(const_ref)
|
123
|
+
underscore(const_ref)
|
124
|
+
end
|
125
|
+
|
126
|
+
# activesupport/lib/active_support/inflector/methods.rb, line 48
|
127
|
+
def underscore(camel_cased_word)
|
128
|
+
word = camel_cased_word.to_s.dup
|
129
|
+
word.gsub!(/::/, '/')
|
130
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
131
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
132
|
+
word.tr!("-", "_")
|
133
|
+
word.downcase!
|
134
|
+
word
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'scripted'
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/tasklib'
|
5
|
+
|
6
|
+
module Scripted
|
7
|
+
class RakeTask < ::Rake::TaskLib
|
8
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
9
|
+
|
10
|
+
def initialize(name, *group_names, &block)
|
11
|
+
group_names = [:default] if group_names.empty?
|
12
|
+
unless Rake.application.last_description
|
13
|
+
desc "Run scripted groups: #{group_names.map(&:to_s).join(', ')}"
|
14
|
+
end
|
15
|
+
task name do
|
16
|
+
config = Scripted.configure(&block)
|
17
|
+
config.with_default_config_file!
|
18
|
+
config.load_files
|
19
|
+
Scripted.run(config, *group_names)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "scripted/output/logger"
|
2
|
+
require "scripted/running/select_commands"
|
3
|
+
require "scripted/running/run_commands"
|
4
|
+
|
5
|
+
module Scripted
|
6
|
+
class Runner
|
7
|
+
|
8
|
+
def self.start!(configuration, *group_names)
|
9
|
+
Output::Logger.new(configuration) do |logger|
|
10
|
+
select_commands = Running::SelectCommands.new(configuration, logger)
|
11
|
+
commands = select_commands.commands(group_names)
|
12
|
+
run_commands = Running::RunCommands.new(logger)
|
13
|
+
run_commands.run(commands)
|
14
|
+
raise RunningFailed, "One or more commands have failed" if run_commands.failed?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Scripted
|
2
|
+
module Running
|
3
|
+
module Execute
|
4
|
+
|
5
|
+
def self.call(command, delegate, logger)
|
6
|
+
command.execute!(logger.to_io(delegate))
|
7
|
+
delegate.success!
|
8
|
+
rescue Exception => exception
|
9
|
+
logger.exception(delegate, exception)
|
10
|
+
delegate.failed!(exception)
|
11
|
+
return false
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "scripted/running/execute"
|
2
|
+
|
3
|
+
module Scripted
|
4
|
+
module Running
|
5
|
+
class RunCommand
|
6
|
+
|
7
|
+
attr_reader :command, :logger
|
8
|
+
attr_reader :started_at, :ended_at, :runtime, :exception, :delegate
|
9
|
+
|
10
|
+
def initialize(command, logger)
|
11
|
+
@command = command
|
12
|
+
@logger = logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute!(delegate)
|
16
|
+
return if executed?
|
17
|
+
@delegate = delegate
|
18
|
+
@running = true
|
19
|
+
@started_at = Time.now
|
20
|
+
logger.execute(self)
|
21
|
+
Execute.call(command, self, logger)
|
22
|
+
end
|
23
|
+
|
24
|
+
def done
|
25
|
+
@executed = true
|
26
|
+
@running = false
|
27
|
+
@ended_at = Time.now
|
28
|
+
@runtime = @ended_at - @started_at
|
29
|
+
delegate.done(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
def success!
|
33
|
+
@success = true
|
34
|
+
done
|
35
|
+
end
|
36
|
+
|
37
|
+
def failed!(exception)
|
38
|
+
@success = false
|
39
|
+
@exception = exception
|
40
|
+
if important?
|
41
|
+
@halted = true
|
42
|
+
delegate.halt!(self)
|
43
|
+
end
|
44
|
+
done
|
45
|
+
end
|
46
|
+
|
47
|
+
def important?
|
48
|
+
command.important?
|
49
|
+
end
|
50
|
+
|
51
|
+
def success?
|
52
|
+
executed? && !!@success
|
53
|
+
end
|
54
|
+
|
55
|
+
def failed?
|
56
|
+
executed? && !unimportant? && !@success
|
57
|
+
end
|
58
|
+
|
59
|
+
def running?
|
60
|
+
!!@running
|
61
|
+
end
|
62
|
+
|
63
|
+
def executed?
|
64
|
+
!!@executed
|
65
|
+
end
|
66
|
+
|
67
|
+
def halted?
|
68
|
+
!!@halted
|
69
|
+
end
|
70
|
+
|
71
|
+
def unimportant?
|
72
|
+
command.unimportant?
|
73
|
+
end
|
74
|
+
|
75
|
+
def failed_but_unimportant?
|
76
|
+
executed? && !@success && unimportant?
|
77
|
+
end
|
78
|
+
|
79
|
+
def parallel_id
|
80
|
+
command.parallel_id
|
81
|
+
end
|
82
|
+
|
83
|
+
def parallel?
|
84
|
+
command.parallel?
|
85
|
+
end
|
86
|
+
|
87
|
+
def only_when_failed?
|
88
|
+
command.only_when_failed?
|
89
|
+
end
|
90
|
+
|
91
|
+
def forced?
|
92
|
+
command.forced?
|
93
|
+
end
|
94
|
+
|
95
|
+
def name
|
96
|
+
command.name
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Scripted
|
2
|
+
module Running
|
3
|
+
class RunCommands
|
4
|
+
|
5
|
+
attr_reader :logger
|
6
|
+
|
7
|
+
attr_reader :started_at, :ended_at, :runtime
|
8
|
+
|
9
|
+
def initialize(logger)
|
10
|
+
@logger = logger
|
11
|
+
@executed = false
|
12
|
+
@halted = false
|
13
|
+
@running = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(commands)
|
17
|
+
logged commands do
|
18
|
+
per_parallel_id commands do |parallel_commands|
|
19
|
+
threads = []
|
20
|
+
parallel_commands.each do |command|
|
21
|
+
if should_execute?(command)
|
22
|
+
threads << Thread.new do
|
23
|
+
command.execute!(self)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
threads.each(&:join)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def completed
|
33
|
+
@completed ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
def done(command)
|
37
|
+
logger.done(command)
|
38
|
+
completed << command
|
39
|
+
end
|
40
|
+
|
41
|
+
def failed?
|
42
|
+
completed.any?(&:failed?)
|
43
|
+
end
|
44
|
+
|
45
|
+
def halt!(command)
|
46
|
+
logger.halted(command)
|
47
|
+
@halted = true
|
48
|
+
end
|
49
|
+
|
50
|
+
def halted?
|
51
|
+
@halted
|
52
|
+
end
|
53
|
+
|
54
|
+
def running?
|
55
|
+
@running
|
56
|
+
end
|
57
|
+
|
58
|
+
def executed?
|
59
|
+
@executed
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# these are re-sorted, because in Ruby 1.8, hashes aren't sorted
|
65
|
+
def per_parallel_id(commands)
|
66
|
+
commands.group_by(&:parallel_id).values.sort_by { |commands|
|
67
|
+
commands.first.parallel_id
|
68
|
+
}.each { |commands|
|
69
|
+
yield commands
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def logged(commands)
|
74
|
+
@started_at = Time.now
|
75
|
+
@running = true
|
76
|
+
logger.start(commands, self)
|
77
|
+
yield
|
78
|
+
ensure
|
79
|
+
@executed = true
|
80
|
+
@running = false
|
81
|
+
@ended_at = Time.now
|
82
|
+
@runtime = @ended_at - @started_at
|
83
|
+
logger.stop(commands, self)
|
84
|
+
end
|
85
|
+
|
86
|
+
def should_execute?(command)
|
87
|
+
if halted?
|
88
|
+
command.forced? or command.only_when_failed?
|
89
|
+
elsif failed?
|
90
|
+
true
|
91
|
+
else
|
92
|
+
not command.only_when_failed?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "scripted/running/run_command"
|
2
|
+
|
3
|
+
module Scripted
|
4
|
+
module Running
|
5
|
+
class SelectCommands
|
6
|
+
|
7
|
+
attr_reader :configuration, :logger
|
8
|
+
|
9
|
+
def initialize(configuration, logger)
|
10
|
+
@configuration = configuration
|
11
|
+
@logger = logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def commands(group_names)
|
15
|
+
group_names = [:default] if group_names.empty?
|
16
|
+
groups = configuration.groups.values_at(*group_names.map(&:to_sym))
|
17
|
+
groups.map { |group| group.commands }.flatten.map { |command| RunCommand.new(command, logger) }
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/scripted.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/scripted/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["iain"]
|
6
|
+
gem.email = ["iain@iain.nl"]
|
7
|
+
gem.description = %q{Easily specify which scripts to run, which ones to run in parallel, control their exit status and stream their output to a websockt.}
|
8
|
+
gem.summary = %q{A framework for organizing scripts}
|
9
|
+
gem.homepage = "https://github.com/iain/scripted"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "scripted"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Scripted::VERSION
|
17
|
+
|
18
|
+
gem.add_runtime_dependency "childprocess", ">= 0.3.4"
|
19
|
+
|
20
|
+
# For testing
|
21
|
+
gem.add_development_dependency "rspec"
|
22
|
+
gem.add_development_dependency "cucumber"
|
23
|
+
gem.add_development_dependency "aruba"
|
24
|
+
gem.add_development_dependency "rake"
|
25
|
+
gem.add_development_dependency "fivemat"
|
26
|
+
|
27
|
+
# For the websockets example
|
28
|
+
gem.add_development_dependency "thin"
|
29
|
+
gem.add_development_dependency "faye"
|
30
|
+
gem.add_development_dependency "launchy"
|
31
|
+
gem.add_development_dependency "sinatra"
|
32
|
+
gem.add_development_dependency "coffee-script"
|
33
|
+
gem.add_development_dependency "sass"
|
34
|
+
|
35
|
+
end
|