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