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,39 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ Bundler.setup
5
+
6
+ require 'rspec/core/rake_task'
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ require 'cucumber/rake/task'
10
+ Cucumber::Rake::Task.new(:cucumber)
11
+
12
+ require 'scripted/rake_task'
13
+ Scripted::RakeTask.new(:scripted)
14
+
15
+ task :default => [:scripted]
16
+
17
+ namespace :examples do
18
+
19
+ desc "Runs the websockets example (really cool)"
20
+ Scripted::RakeTask.new(:websockets) do
21
+ config_file "examples/websockets.rb"
22
+ end
23
+
24
+ desc "Runs the parallel example"
25
+ Scripted::RakeTask.new(:parallel) do
26
+ config_file "examples/parallel.rb"
27
+ end
28
+
29
+ desc "Runs the important example"
30
+ Scripted::RakeTask.new(:important) do
31
+ config_file "examples/important.rb"
32
+ end
33
+
34
+ desc "Runs the pride example"
35
+ Scripted::RakeTask.new(:pride) do
36
+ config_file "examples/pride.rb"
37
+ end
38
+
39
+ end
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'scripted'
4
+ require 'optparse'
5
+ require 'set'
6
+
7
+ config = Scripted.configure
8
+ groups = Set.new
9
+
10
+ parser = OptionParser.new do |opts|
11
+
12
+ opts.on_tail("-h", "--help", "Shows the available options") do
13
+ puts opts
14
+ exit
15
+ end
16
+
17
+ opts.on_tail("-v", "--version", "Shows the version") do
18
+ puts Scripted::VERSION
19
+ exit
20
+ end
21
+
22
+ opts.on("-r", "--require a,b", Array, "Which files should be loaded (defaults to `scripted.rb`)") do |file_names|
23
+ config.config_file(*file_names)
24
+ end
25
+
26
+ opts.on("-g", "--group a,b", Array, "Which groups to run (defaults to `:default`)") do |group_names|
27
+ groups += group_names
28
+ end
29
+
30
+ opts.on("-f", "--format name", "Specify a formatter (defaults to `default`)") do |formatter|
31
+ config.formatter formatter
32
+ end
33
+
34
+ opts.on("-o", "--out output", "Specify the output of the previously specified formatter") do |out|
35
+ config.out out
36
+ end
37
+
38
+ opts.on("-c", "--[no-]color", "Use color in formatters? (defaults to yes)") do |color|
39
+ config.color = color
40
+ end
41
+
42
+ opts.on("-I PATH", "Add a directory to your load path") do |load_path|
43
+ $LOAD_PATH.unshift(load_path)
44
+ end
45
+
46
+ end
47
+
48
+ begin
49
+ parser.parse!
50
+ config.with_default_config_file!
51
+ config.load_files
52
+ rescue OptionParser::InvalidOption => error
53
+ puts error
54
+ puts ""
55
+ puts parser
56
+ exit 1
57
+ rescue Scripted::ConfigurationError => error
58
+ puts "#{error} at #{error.backtrace.first}"
59
+ exit 1
60
+ end
61
+
62
+ begin
63
+ Scripted.run config, *groups
64
+ rescue Scripted::RunningFailed => error
65
+ puts error
66
+ exit 1
67
+ end
@@ -0,0 +1,3 @@
1
+ default: --format Fivemat --strict --tags ~@wip --color
2
+ wip: --format pretty --wip --tags @wip --color
3
+ no-color: --format Fivemat --strict --tags ~@wip
@@ -0,0 +1,31 @@
1
+ formatter :table
2
+ formatter :announcer
3
+ formatter :default
4
+
5
+ run "an unimportant failing command" do
6
+ `false`
7
+ unimportant!
8
+ end
9
+
10
+ run "a normal command" do
11
+ `echo You should see this output`
12
+ end
13
+
14
+ run "an important failing command" do
15
+ `false`
16
+ important!
17
+ end
18
+
19
+ run "another normal command" do
20
+ `echo You should never see this`
21
+ end
22
+
23
+ run "a forced command" do
24
+ `true`
25
+ forced!
26
+ end
27
+
28
+ run "this runs because it failed" do
29
+ `true`
30
+ only_when_failed!
31
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This example shows of parallel commands.
4
+ #
5
+ # The output should look something like:
6
+ #
7
+ # ┌─────────┬─────────┬─────────┐
8
+ # │ Command │ Runtime │ Status │
9
+ # ├─────────┼─────────┼─────────┤
10
+ # │ sleep 1 │ 1.061s │ success │
11
+ # │ sleep 1 │ 1.062s │ success │
12
+ # │ sleep 1 │ 1.064s │ success │
13
+ # │ sleep 1 │ 1.066s │ success │
14
+ # │ sleep 1 │ 1.064s │ success │
15
+ # └─────────┴─────────┴─────────┘
16
+ # Total runtime: 2.307s
17
+ #
18
+ # To run: rake examples:parallel
19
+
20
+ formatter :table
21
+
22
+ parallel do
23
+ run "sleep 1"
24
+ run "sleep 1"
25
+ end
26
+ parallel do
27
+ run "sleep 1"
28
+ run "sleep 1"
29
+ run "sleep 1"
30
+ end
@@ -0,0 +1,37 @@
1
+ # Taken from Minitest's Pride formatter
2
+ # Meant as example of custom formatters
3
+
4
+ require "scripted/formatters/blank"
5
+
6
+ class Pride < Scripted::Formatters::Blank
7
+
8
+ PI_3 = Math::PI / 3
9
+
10
+ def initialize(*)
11
+ super
12
+ @index = 0
13
+ @colors = (0...(6 * 7)).map { |n|
14
+ n *= 1.0 / 6
15
+ r = (3 * Math.sin(n ) + 3).to_i
16
+ g = (3 * Math.sin(n + 2 * PI_3) + 3).to_i
17
+ b = (3 * Math.sin(n + 4 * PI_3) + 3).to_i
18
+ 36 * r + 6 * g + b + 16
19
+ }
20
+ @size = @colors.size
21
+ end
22
+
23
+ def each_char(char, command)
24
+ print pride(char)
25
+ end
26
+
27
+ def pride(string)
28
+ color = @colors[@index % @size]
29
+ @index += 1
30
+ "\e[38;5;#{color}m#{string}\e[0m"
31
+ end
32
+ end
33
+
34
+ formatter Pride
35
+
36
+ run "rspec --no-color"
37
+ run "cucumber -p no-color"
Binary file
@@ -0,0 +1,37 @@
1
+ # This is an example of using the websockets formatter.
2
+ #
3
+ # The web application is made with Ember.js. Also, even though some commands
4
+ # might run in parallel, their output will appear properly in the web view.
5
+ #
6
+ # To run this: rake examples:websockets
7
+
8
+ dir = File.expand_path("../websockets", __FILE__)
9
+
10
+ formatter :default
11
+ formatter :table
12
+
13
+ run "start server" do
14
+ `bundle exec thin -e production -R #{File.join(dir, "server.ru")} -p 9292 -d start`
15
+ end
16
+
17
+ # give the server some time to start
18
+ run "sleep 1"
19
+
20
+ # opens the web page with the ember.js app
21
+ run "open client" do
22
+ `bundle exec launchy http://localhost:9292/`
23
+ end
24
+
25
+ # how unbelievable meta! :)
26
+ run "scripted with websocket formatter" do
27
+ `bundle exec scripted -f websocket -o http://localhost:9292/faye`
28
+ end
29
+
30
+ # keep the connection open for just a bit longer
31
+ run "sleep 1"
32
+
33
+ # forcefully shut down, because websocket connections will cause a timeout anyway
34
+ run "shutdown server" do
35
+ `bundle exec thin -f stop`
36
+ forced!
37
+ end
@@ -0,0 +1,156 @@
1
+ // "Borrowed" from TravisCI. Love you guys!
2
+ ansiparse = function (str) {
3
+ //
4
+ // I'm terrible at writing parsers.
5
+ //
6
+ var matchingControl = null,
7
+ matchingData = null,
8
+ matchingText = '',
9
+ ansiState = [],
10
+ result = [],
11
+ state = {};
12
+
13
+ //
14
+ // General workflow for this thing is:
15
+ // \033\[33mText
16
+ // | | |
17
+ // | | matchingText
18
+ // | matchingData
19
+ // matchingControl
20
+ //
21
+ // In further steps we hope it's all going to be fine. It usually is.
22
+ //
23
+
24
+ for (var i = 0; i < str.length; i++) {
25
+ if (matchingControl != null) {
26
+ if (matchingControl == '\033' && str[i] == '\[') {
27
+ //
28
+ // We've matched full control code. Lets start matching formating data.
29
+ //
30
+
31
+ //
32
+ // "emit" matched text with correct state
33
+ //
34
+ if (matchingText) {
35
+ state.text = matchingText;
36
+ result.push(state);
37
+ state = {};
38
+ matchingText = "";
39
+ }
40
+
41
+ matchingControl = null;
42
+ matchingData = '';
43
+ }
44
+ else {
45
+ //
46
+ // We failed to match anything - most likely a bad control code. We
47
+ // go back to matching regular strings.
48
+ //
49
+ matchingText += matchingControl + str[i];
50
+ matchingControl = null;
51
+ }
52
+ continue;
53
+ }
54
+ else if (matchingData != null) {
55
+ if (str[i] == ';') {
56
+ //
57
+ // `;` separates many formatting codes, for example: `\033[33;43m`
58
+ // means that both `33` and `43` should be applied.
59
+ //
60
+ // TODO: this can be simplified by modifying state here.
61
+ //
62
+ ansiState.push(matchingData);
63
+ matchingData = '';
64
+ }
65
+ else if (str[i] == 'm') {
66
+ //
67
+ // `m` finished whole formatting code. We can proceed to matching
68
+ // formatted text.
69
+ //
70
+ ansiState.push(matchingData);
71
+ matchingData = null;
72
+ matchingText = '';
73
+
74
+ //
75
+ // Convert matched formatting data into user-friendly state object.
76
+ //
77
+ // TODO: DRY.
78
+ //
79
+ ansiState.forEach(function (ansiCode) {
80
+ if (ansiparse.foregroundColors[ansiCode]) {
81
+ state.foreground = ansiparse.foregroundColors[ansiCode];
82
+ }
83
+ else if (ansiparse.backgroundColors[ansiCode]) {
84
+ state.background = ansiparse.backgroundColors[ansiCode];
85
+ }
86
+ else if (ansiCode == 39) {
87
+ delete state.foreground;
88
+ }
89
+ else if (ansiCode == 49) {
90
+ delete state.background;
91
+ }
92
+ else if (ansiparse.styles[ansiCode]) {
93
+ state[ansiparse.styles[ansiCode]] = true;
94
+ }
95
+ else if (ansiCode == 22) {
96
+ state.bold = false;
97
+ }
98
+ else if (ansiCode == 23) {
99
+ state.italic = false;
100
+ }
101
+ else if (ansiCode == 24) {
102
+ state.underline = false;
103
+ }
104
+ });
105
+ ansiState = [];
106
+ }
107
+ else {
108
+ matchingData += str[i];
109
+ }
110
+ continue;
111
+ }
112
+
113
+ if (str[i] == '\033') {
114
+ matchingControl = str[i];
115
+
116
+ }
117
+ else {
118
+ matchingText += str[i];
119
+ }
120
+ }
121
+
122
+ if (matchingText) {
123
+ state.text = matchingText + (matchingControl ? matchingControl : '');
124
+ result.push(state);
125
+ }
126
+ return result;
127
+ }
128
+
129
+ ansiparse.foregroundColors = {
130
+ '30': 'black',
131
+ '31': 'red',
132
+ '32': 'green',
133
+ '33': 'yellow',
134
+ '34': 'blue',
135
+ '35': 'magenta',
136
+ '36': 'cyan',
137
+ '37': 'white',
138
+ '90': 'grey'
139
+ };
140
+
141
+ ansiparse.backgroundColors = {
142
+ '40': 'black',
143
+ '41': 'red',
144
+ '42': 'green',
145
+ '43': 'yellow',
146
+ '44': 'blue',
147
+ '45': 'magenta',
148
+ '46': 'cyan',
149
+ '47': 'white'
150
+ };
151
+
152
+ ansiparse.styles = {
153
+ '1': 'bold',
154
+ '3': 'italic',
155
+ '4': 'underline'
156
+ };
@@ -0,0 +1,32 @@
1
+ require 'sinatra'
2
+ require 'coffee-script'
3
+ require 'sass'
4
+
5
+ Tilt.register Tilt::ERBTemplate, 'handlebars'
6
+
7
+ helpers do
8
+ def handlebars(name)
9
+ partial = render :handlebars, :"_#{name}"
10
+ "<script type='text/x-handlebars'>#{partial}</script>"
11
+ end
12
+ end
13
+
14
+ error do
15
+ <<-HTML
16
+ <h1>HALP!</h1>
17
+ <p>The shit hit the fan in such a way that I don't know what to do.</p>
18
+ <p>The error message: #{request.env['sinatra.error'].message}</p>
19
+ HTML
20
+ end
21
+
22
+ get "/" do
23
+ erb :index
24
+ end
25
+
26
+ get '/app.js' do
27
+ coffee :app
28
+ end
29
+
30
+ get '/style.css' do
31
+ sass :style
32
+ end
@@ -0,0 +1,10 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require File.expand_path("../server.rb", __FILE__)
5
+
6
+ require 'faye'
7
+ Faye::WebSocket.load_adapter 'thin'
8
+ use Faye::RackAdapter, :mount => '/faye'
9
+
10
+ run Sinatra::Application