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.
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