scripted 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +6 -0
  5. data/MIT-LICENSE +22 -0
  6. data/README.md +423 -0
  7. data/Rakefile +39 -0
  8. data/bin/scripted +67 -0
  9. data/cucumber.yml +3 -0
  10. data/examples/important.rb +31 -0
  11. data/examples/parallel.rb +30 -0
  12. data/examples/pride.rb +37 -0
  13. data/examples/websockets.png +0 -0
  14. data/examples/websockets.rb +37 -0
  15. data/examples/websockets/public/ansiparse.js +156 -0
  16. data/examples/websockets/server.rb +32 -0
  17. data/examples/websockets/server.ru +10 -0
  18. data/examples/websockets/views/_client.handlebars +47 -0
  19. data/examples/websockets/views/app.coffee +210 -0
  20. data/examples/websockets/views/index.erb +1 -0
  21. data/examples/websockets/views/layout.erb +14 -0
  22. data/examples/websockets/views/style.sass +61 -0
  23. data/features/controlling_exit_status.feature +124 -0
  24. data/features/formatters.feature +142 -0
  25. data/features/rake_integration.feature +86 -0
  26. data/features/running_commands_in_parallel.feature +27 -0
  27. data/features/running_from_command_line.feature +56 -0
  28. data/features/running_from_ruby.feature +38 -0
  29. data/features/running_groups.feature +39 -0
  30. data/features/specifying_which_commands_to_run.feature +122 -0
  31. data/features/steps/scripted_steps.rb +25 -0
  32. data/features/support/aruba.rb +5 -0
  33. data/features/support/env.rb +2 -0
  34. data/install +5 -0
  35. data/lib/scripted.rb +28 -0
  36. data/lib/scripted/command.rb +82 -0
  37. data/lib/scripted/commands/rake.rb +25 -0
  38. data/lib/scripted/commands/ruby.rb +22 -0
  39. data/lib/scripted/commands/shell.rb +28 -0
  40. data/lib/scripted/configuration.rb +103 -0
  41. data/lib/scripted/error.rb +13 -0
  42. data/lib/scripted/formatters/announcer.rb +39 -0
  43. data/lib/scripted/formatters/blank.rb +97 -0
  44. data/lib/scripted/formatters/default.rb +62 -0
  45. data/lib/scripted/formatters/human_status.rb +38 -0
  46. data/lib/scripted/formatters/stats.rb +38 -0
  47. data/lib/scripted/formatters/table.rb +99 -0
  48. data/lib/scripted/formatters/websocket.rb +137 -0
  49. data/lib/scripted/group.rb +49 -0
  50. data/lib/scripted/output/command_logger.rb +42 -0
  51. data/lib/scripted/output/logger.rb +139 -0
  52. data/lib/scripted/rake_task.rb +24 -0
  53. data/lib/scripted/runner.rb +19 -0
  54. data/lib/scripted/running/execute.rb +16 -0
  55. data/lib/scripted/running/run_command.rb +101 -0
  56. data/lib/scripted/running/run_commands.rb +98 -0
  57. data/lib/scripted/running/select_commands.rb +22 -0
  58. data/lib/scripted/version.rb +3 -0
  59. data/scripted.gemspec +35 -0
  60. data/scripted.rb +16 -0
  61. data/spec/scripted/command_spec.rb +72 -0
  62. data/spec/scripted/commands/ruby_spec.rb +10 -0
  63. data/spec/scripted/commands/shell_spec.rb +18 -0
  64. data/spec/scripted/configuration_spec.rb +50 -0
  65. data/spec/scripted/formatters/websocket_spec.rb +14 -0
  66. data/spec/scripted/group_spec.rb +49 -0
  67. data/spec/scripted/running/run_command_spec.rb +157 -0
  68. data/spec/scripted/running/run_commands_spec.rb +150 -0
  69. data/spec/scripted/running/select_commands_spec.rb +28 -0
  70. data/spec/spec_helper.rb +15 -0
  71. data/spec/support/expect_to_receive.rb +17 -0
  72. data/spec/support/io_capture.rb +50 -0
  73. 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
@@ -0,0 +1,3 @@
1
+ module Scripted
2
+ VERSION = "0.0.1"
3
+ end
@@ -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