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