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.
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +6 -0
- data/MIT-LICENSE +22 -0
- data/README.md +423 -0
- data/Rakefile +39 -0
- data/bin/scripted +67 -0
- data/cucumber.yml +3 -0
- data/examples/important.rb +31 -0
- data/examples/parallel.rb +30 -0
- data/examples/pride.rb +37 -0
- data/examples/websockets.png +0 -0
- data/examples/websockets.rb +37 -0
- data/examples/websockets/public/ansiparse.js +156 -0
- data/examples/websockets/server.rb +32 -0
- data/examples/websockets/server.ru +10 -0
- data/examples/websockets/views/_client.handlebars +47 -0
- data/examples/websockets/views/app.coffee +210 -0
- data/examples/websockets/views/index.erb +1 -0
- data/examples/websockets/views/layout.erb +14 -0
- data/examples/websockets/views/style.sass +61 -0
- data/features/controlling_exit_status.feature +124 -0
- data/features/formatters.feature +142 -0
- data/features/rake_integration.feature +86 -0
- data/features/running_commands_in_parallel.feature +27 -0
- data/features/running_from_command_line.feature +56 -0
- data/features/running_from_ruby.feature +38 -0
- data/features/running_groups.feature +39 -0
- data/features/specifying_which_commands_to_run.feature +122 -0
- data/features/steps/scripted_steps.rb +25 -0
- data/features/support/aruba.rb +5 -0
- data/features/support/env.rb +2 -0
- data/install +5 -0
- data/lib/scripted.rb +28 -0
- data/lib/scripted/command.rb +82 -0
- data/lib/scripted/commands/rake.rb +25 -0
- data/lib/scripted/commands/ruby.rb +22 -0
- data/lib/scripted/commands/shell.rb +28 -0
- data/lib/scripted/configuration.rb +103 -0
- data/lib/scripted/error.rb +13 -0
- data/lib/scripted/formatters/announcer.rb +39 -0
- data/lib/scripted/formatters/blank.rb +97 -0
- data/lib/scripted/formatters/default.rb +62 -0
- data/lib/scripted/formatters/human_status.rb +38 -0
- data/lib/scripted/formatters/stats.rb +38 -0
- data/lib/scripted/formatters/table.rb +99 -0
- data/lib/scripted/formatters/websocket.rb +137 -0
- data/lib/scripted/group.rb +49 -0
- data/lib/scripted/output/command_logger.rb +42 -0
- data/lib/scripted/output/logger.rb +139 -0
- data/lib/scripted/rake_task.rb +24 -0
- data/lib/scripted/runner.rb +19 -0
- data/lib/scripted/running/execute.rb +16 -0
- data/lib/scripted/running/run_command.rb +101 -0
- data/lib/scripted/running/run_commands.rb +98 -0
- data/lib/scripted/running/select_commands.rb +22 -0
- data/lib/scripted/version.rb +3 -0
- data/scripted.gemspec +35 -0
- data/scripted.rb +16 -0
- data/spec/scripted/command_spec.rb +72 -0
- data/spec/scripted/commands/ruby_spec.rb +10 -0
- data/spec/scripted/commands/shell_spec.rb +18 -0
- data/spec/scripted/configuration_spec.rb +50 -0
- data/spec/scripted/formatters/websocket_spec.rb +14 -0
- data/spec/scripted/group_spec.rb +49 -0
- data/spec/scripted/running/run_command_spec.rb +157 -0
- data/spec/scripted/running/run_commands_spec.rb +150 -0
- data/spec/scripted/running/select_commands_spec.rb +28 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/expect_to_receive.rb +17 -0
- data/spec/support/io_capture.rb +50 -0
- metadata +340 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
<div class="container">
|
2
|
+
<div class="page-header">
|
3
|
+
{{#view Scripted.ConnectionStatusView}}{{content.message}}{{/view}}
|
4
|
+
<h1>Scripted <small>live command line output via websockets</small></h1>
|
5
|
+
</div>
|
6
|
+
|
7
|
+
{{#view Scripted.RunnerView}}{{message}}{{/view}}
|
8
|
+
|
9
|
+
{{#if Scripted.CommandsController.hasCommands}}
|
10
|
+
<table class="table table-striped table-bordered">
|
11
|
+
<thead>
|
12
|
+
<tr>
|
13
|
+
<th>Command</th>
|
14
|
+
<th>Runtime (s)</th>
|
15
|
+
<th>Status</th>
|
16
|
+
</tr>
|
17
|
+
</thead>
|
18
|
+
<tbody>
|
19
|
+
{{#each Scripted.CommandsController}}
|
20
|
+
{{#view Scripted.CommandView contentBinding="this"}}
|
21
|
+
<tr>
|
22
|
+
<td>{{content.name}}</td>
|
23
|
+
<td>{{content.roundedRuntime}}</td>
|
24
|
+
<td>{{#view Scripted.CommandStatusView contentBinding="content"}}{{text}}{{/view}}</td>
|
25
|
+
</tr>
|
26
|
+
{{/view}}
|
27
|
+
{{/each}}
|
28
|
+
</tbody>
|
29
|
+
</table>
|
30
|
+
|
31
|
+
{{#each Scripted.CommandsController}}
|
32
|
+
{{#view Scripted.CommandView contentBinding="this"}}
|
33
|
+
<h3>{{content.name}} {{#view Scripted.CommandStatusView contentBinding="content"}}{{text}}{{/view}}</h3>
|
34
|
+
{{#if hasOutput}}
|
35
|
+
<div class="output">
|
36
|
+
<pre>{{output}}</pre>
|
37
|
+
</div>
|
38
|
+
{{/if}}
|
39
|
+
{{/view}}
|
40
|
+
{{/each}}
|
41
|
+
{{else}}
|
42
|
+
<div class="well">
|
43
|
+
Run <tt>bundle exec scripted --format websocket --out http://localhost:9292/faye</tt> to start.
|
44
|
+
</div>
|
45
|
+
{{/if}}
|
46
|
+
|
47
|
+
</div>
|
@@ -0,0 +1,210 @@
|
|
1
|
+
Scripted = Em.Application.create
|
2
|
+
|
3
|
+
received: (data) ->
|
4
|
+
action = @["on_#{data.action}"]
|
5
|
+
action(data) if action
|
6
|
+
|
7
|
+
on_start: (data) ->
|
8
|
+
Scripted.CommandsController.clear()
|
9
|
+
Scripted.Runner.setProperties(data.runner)
|
10
|
+
for command in data.commands
|
11
|
+
Scripted.updateCommand(command)
|
12
|
+
|
13
|
+
on_stop: (data) ->
|
14
|
+
Scripted.Runner.setProperties(data.runner)
|
15
|
+
for command in data.commands
|
16
|
+
Scripted.updateCommand(command)
|
17
|
+
|
18
|
+
on_execute: (data) ->
|
19
|
+
Scripted.updateCommand(data.command)
|
20
|
+
|
21
|
+
on_done: (data) ->
|
22
|
+
Scripted.updateCommand(data.command)
|
23
|
+
|
24
|
+
on_output: (data) ->
|
25
|
+
for output in data.output
|
26
|
+
command = Scripted.updateCommand(output.command).set('output', output.output)
|
27
|
+
|
28
|
+
updateCommand: (command) ->
|
29
|
+
Scripted.CommandsController.updateOrCreate(command)
|
30
|
+
|
31
|
+
Scripted.ConnectionStatus = Em.Object.create
|
32
|
+
|
33
|
+
status: "error"
|
34
|
+
message: "No Connection"
|
35
|
+
|
36
|
+
success: ->
|
37
|
+
if @get('hasErrored')
|
38
|
+
window.location.reload()
|
39
|
+
@set 'status', 'success'
|
40
|
+
@set 'message', 'Connected'
|
41
|
+
|
42
|
+
error: (message) ->
|
43
|
+
@set 'status', 'error'
|
44
|
+
@set 'message', "Error: #{message}"
|
45
|
+
@set 'hasErrored', true
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
Scripted.ConnectionStatusView = Em.View.extend
|
50
|
+
|
51
|
+
contentBinding: 'Scripted.ConnectionStatus'
|
52
|
+
tagName: 'span'
|
53
|
+
classNameBindings: ['default', 'status']
|
54
|
+
|
55
|
+
default: "pull-right label"
|
56
|
+
|
57
|
+
status: (->
|
58
|
+
switch @get('content').get('status')
|
59
|
+
when "success" then "label-success"
|
60
|
+
when "error" then "label-important"
|
61
|
+
else "label-warning"
|
62
|
+
).property('content.status')
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
Scripted.CommandStatusView = Em.View.extend
|
67
|
+
|
68
|
+
tagName: 'span'
|
69
|
+
classNameBindings: ['className']
|
70
|
+
|
71
|
+
text: (->
|
72
|
+
@get('content').get('human_status')
|
73
|
+
).property('content.human_status')
|
74
|
+
|
75
|
+
className: (->
|
76
|
+
status_code = @get('content').get('status_code')
|
77
|
+
switch status_code
|
78
|
+
when "running" then "label label-info"
|
79
|
+
when "success", "success_ran_because_other_command_failed" then "label label-success"
|
80
|
+
when "failed", "failed_ran_because_other_command_failed" then "label label-important"
|
81
|
+
when "failed_but_ignored" then "label label-warning"
|
82
|
+
when "failed_and_halted" then "label label-inverse"
|
83
|
+
else "label"
|
84
|
+
).property('content.status_code')
|
85
|
+
|
86
|
+
Scripted.CommandView = Em.View.extend
|
87
|
+
|
88
|
+
classNameBindings: ['content.status_code']
|
89
|
+
|
90
|
+
output: (->
|
91
|
+
html = ""
|
92
|
+
output = @get('content').get('output')
|
93
|
+
nodes = ansiparse(output)
|
94
|
+
for node in nodes
|
95
|
+
html += "<span class='#{node.foreground}'>#{node.text}</span>"
|
96
|
+
new Handlebars.SafeString(html)
|
97
|
+
).property('content.output')
|
98
|
+
|
99
|
+
hasOutput: (->
|
100
|
+
output = @get('content').get('output')
|
101
|
+
output and output != ""
|
102
|
+
).property('content.output')
|
103
|
+
|
104
|
+
outputChanged: (->
|
105
|
+
output = this.$('.output')
|
106
|
+
scrollDown = -> output[0].scrollTop = output[0].scrollHeight + 100000
|
107
|
+
if output.length > 0
|
108
|
+
setTimeout scrollDown, 100
|
109
|
+
setTimeout scrollDown, 500
|
110
|
+
).observes('output', 'content.status_code')
|
111
|
+
|
112
|
+
Scripted.Runner = Em.Object.create
|
113
|
+
|
114
|
+
running: false
|
115
|
+
executed: false
|
116
|
+
started_at: null
|
117
|
+
|
118
|
+
roundedRuntime: (->
|
119
|
+
value = @get('runtime')
|
120
|
+
if value
|
121
|
+
value.toFixed(3)
|
122
|
+
else
|
123
|
+
""
|
124
|
+
).property('runtime')
|
125
|
+
|
126
|
+
Scripted.RunnerView = Em.View.extend
|
127
|
+
|
128
|
+
contentBinding: 'Scripted.Runner'
|
129
|
+
classNameBindings: ["alert", "alertType"]
|
130
|
+
|
131
|
+
alert: (-> "alert").property()
|
132
|
+
|
133
|
+
message: (->
|
134
|
+
content = @get('content')
|
135
|
+
commandsCount = "<strong>#{Scripted.CommandsController.content.length}</strong>"
|
136
|
+
started_at = content.get('started_at')
|
137
|
+
text = if Scripted.CommandsController.get('hasCommands')
|
138
|
+
if not content.get('executed')
|
139
|
+
"Running #{commandsCount} commands"
|
140
|
+
else if content.get('failed')
|
141
|
+
"One or more commands have failed"
|
142
|
+
else
|
143
|
+
"Success! Ran #{commandsCount} commands in <strong>#{content.get('roundedRuntime')}</strong> seconds"
|
144
|
+
else
|
145
|
+
"There are <strong>no</strong> commands yet."
|
146
|
+
new Handlebars.SafeString(text)
|
147
|
+
).property('content.started_at', 'content.failed', 'content.executed', 'content.roundedRuntime', 'Scripted.CommandsController.hasCommands')
|
148
|
+
|
149
|
+
alertType: (->
|
150
|
+
failed = @get('content').get('failed')
|
151
|
+
running = @get('content').get('running')
|
152
|
+
executed = @get('content').get('executed')
|
153
|
+
if failed
|
154
|
+
"alert-error"
|
155
|
+
else if running
|
156
|
+
"alert-info"
|
157
|
+
else if executed
|
158
|
+
"alert-success"
|
159
|
+
else
|
160
|
+
""
|
161
|
+
).property('content.failed', 'content.running', 'content.executed')
|
162
|
+
|
163
|
+
Scripted.Command = Em.Object.extend
|
164
|
+
|
165
|
+
id: null
|
166
|
+
name: ""
|
167
|
+
status_code: "unknown"
|
168
|
+
output: ''
|
169
|
+
|
170
|
+
roundedRuntime: (->
|
171
|
+
value = @get('runtime')
|
172
|
+
if value
|
173
|
+
value.toFixed(3)
|
174
|
+
else
|
175
|
+
""
|
176
|
+
).property('runtime')
|
177
|
+
|
178
|
+
Scripted.CommandsController = Em.ArrayController.create
|
179
|
+
|
180
|
+
content: []
|
181
|
+
|
182
|
+
updateOrCreate: (data) ->
|
183
|
+
command = @findCommand(data)
|
184
|
+
if command
|
185
|
+
command.setProperties(data)
|
186
|
+
else
|
187
|
+
command = Scripted.Command.create(data)
|
188
|
+
@pushObject(command)
|
189
|
+
command
|
190
|
+
|
191
|
+
|
192
|
+
findCommand: (data) ->
|
193
|
+
if @get('hasCommands')
|
194
|
+
@find((command) -> command.get('id') is data.id)
|
195
|
+
else
|
196
|
+
null
|
197
|
+
|
198
|
+
hasCommands: (->
|
199
|
+
@get('content').length > 0
|
200
|
+
).property('lastObject')
|
201
|
+
|
202
|
+
jQuery ->
|
203
|
+
client = new Faye.Client('http://localhost:9292/faye')
|
204
|
+
subscription = client.subscribe '/scripted', (data) -> Scripted.received(data)
|
205
|
+
subscription.callback -> Scripted.ConnectionStatus.success()
|
206
|
+
subscription.errback (error) -> Scripted.ConnectionStatus.error(error.message)
|
207
|
+
client.bind 'transport:down', -> Scripted.ConnectionStatus.error("No Connection")
|
208
|
+
client.bind 'transport:up', -> Scripted.ConnectionStatus.success()
|
209
|
+
|
210
|
+
window.Scripted = Scripted
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= handlebars "client" %>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Websocket Client</title>
|
5
|
+
<script type="text/javascript" src="http://localhost:9292/faye/client.js"></script>
|
6
|
+
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
|
7
|
+
<script type="text/javascript" src="http://cloud.github.com/downloads/emberjs/ember.js/ember-0.9.8.1.min.js"></script>
|
8
|
+
<script type="text/javascript" src="/ansiparse.js"></script>
|
9
|
+
<script type="text/javascript" src="/app.js"></script>
|
10
|
+
<link href="http://current.bootstrapcdn.com/bootstrap-v204/css/bootstrap-combined.min.css" rel="stylesheet">
|
11
|
+
<link href="/style.css" rel="stylesheet">
|
12
|
+
</head>
|
13
|
+
<body><%= yield %></body>
|
14
|
+
</html>
|
@@ -0,0 +1,61 @@
|
|
1
|
+
.black
|
2
|
+
color: #555555
|
3
|
+
.red
|
4
|
+
color: #FF6C60
|
5
|
+
.green
|
6
|
+
color: #99CC99
|
7
|
+
.yellow
|
8
|
+
color: #FFFFB6
|
9
|
+
.blue
|
10
|
+
color: #6699CC
|
11
|
+
.magenta
|
12
|
+
color: #FF73FD
|
13
|
+
.cyan
|
14
|
+
color: #C6C5FE
|
15
|
+
.white
|
16
|
+
color: #F6F3E8
|
17
|
+
.grey
|
18
|
+
color: #555555
|
19
|
+
|
20
|
+
h2
|
21
|
+
margin: 10px 0
|
22
|
+
|
23
|
+
pre
|
24
|
+
border: 0 none
|
25
|
+
background: black
|
26
|
+
color: #F6F3E8
|
27
|
+
font-family: "DejaVu Sans Mono", "Droid Sans Mono", "Menlo", "Monaco", "Courier New", monospace
|
28
|
+
font-size: 14px
|
29
|
+
margin: 0
|
30
|
+
|
31
|
+
.output pre
|
32
|
+
vertical-align: bottom
|
33
|
+
|
34
|
+
.output
|
35
|
+
font-size: 14px
|
36
|
+
max-height: 1000px
|
37
|
+
overflow-y: scroll
|
38
|
+
overflow-x: hidden
|
39
|
+
|
40
|
+
.success .output
|
41
|
+
transition-duration: 0.5s
|
42
|
+
-moz-transition-duration: 0.5s
|
43
|
+
-webkit-transition-duration: 0.5s
|
44
|
+
-o-transition-duration: 0.5s
|
45
|
+
max-height: 100px
|
46
|
+
|
47
|
+
h3 .label
|
48
|
+
vertical-align: 2px
|
49
|
+
|
50
|
+
.cursor
|
51
|
+
background: #F6F3E8
|
52
|
+
.prompt
|
53
|
+
color: #6699CC
|
54
|
+
|
55
|
+
tt
|
56
|
+
background-color: #DDD
|
57
|
+
padding: 3px 6px
|
58
|
+
margin: 0 5px
|
59
|
+
-webkit-border-radius: 4px
|
60
|
+
-moz-border-radius: 4px
|
61
|
+
border-radius: 4px
|
@@ -0,0 +1,124 @@
|
|
1
|
+
Feature: Controlling Exit Status
|
2
|
+
|
3
|
+
By default, Scripted will run all commands. If a command failed (e.g. if you
|
4
|
+
have failing tests) the entire run will be marked as failed. In Unix terms,
|
5
|
+
this means the exit code will be zero for successful runs and non-zero for
|
6
|
+
failed runs.
|
7
|
+
|
8
|
+
Sometimes it doesn't make sense to continue running commands if one failed,
|
9
|
+
like when an important setup command failed. If this is the case, you can
|
10
|
+
mark the command as important. If this commands fails, it will not run the
|
11
|
+
following commands.
|
12
|
+
|
13
|
+
Here RSpec won't run if the database cannot be created:
|
14
|
+
|
15
|
+
``` ruby
|
16
|
+
run "create the database" do
|
17
|
+
rake "db:create"
|
18
|
+
important!
|
19
|
+
end
|
20
|
+
|
21
|
+
run "rspec"
|
22
|
+
```
|
23
|
+
|
24
|
+
Sometimes you don't care if the command failed or not. The recommended
|
25
|
+
approach is to change the command itself so it does behave, but if you can't
|
26
|
+
do that, you can mark it with `unimportant!`.
|
27
|
+
|
28
|
+
``` ruby
|
29
|
+
run "some command" do
|
30
|
+
unimportant!
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
Sometimes a command can be very important to you. An example might be
|
35
|
+
shutting down daemons after a test run. You always want these commands to
|
36
|
+
run, even if your test suite failed. You can mark these with `forced!`
|
37
|
+
|
38
|
+
``` ruby
|
39
|
+
run "stop xvfb" do
|
40
|
+
`/etc/init.d/xvfb`
|
41
|
+
forced!
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
If the run did fail, you might want to do some extra work, like sending an
|
46
|
+
email. You can mark those with `only_when_failed!`.
|
47
|
+
|
48
|
+
``` ruby
|
49
|
+
run "email" do
|
50
|
+
`mail -s Failure you@example.org`
|
51
|
+
only_when_failed!
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
|
56
|
+
Scenario: Having failing commands
|
57
|
+
Given the configuration:
|
58
|
+
"""
|
59
|
+
run "false"
|
60
|
+
run "echo this should still run"
|
61
|
+
"""
|
62
|
+
When I run scripted
|
63
|
+
Then it should fail
|
64
|
+
And the output should contain "this should still run"
|
65
|
+
|
66
|
+
Scenario: A failing important command
|
67
|
+
Given the configuration:
|
68
|
+
"""
|
69
|
+
run "false" do
|
70
|
+
important!
|
71
|
+
end
|
72
|
+
run "echo this command will not be run"
|
73
|
+
"""
|
74
|
+
When I run scripted
|
75
|
+
Then it should fail
|
76
|
+
And the output should not contain "this command will not be run"
|
77
|
+
|
78
|
+
Scenario: Enforcing commands
|
79
|
+
Given the configuration:
|
80
|
+
"""
|
81
|
+
run "false" do
|
82
|
+
important!
|
83
|
+
end
|
84
|
+
run "echo this command is forced" do
|
85
|
+
forced!
|
86
|
+
end
|
87
|
+
"""
|
88
|
+
When I run scripted
|
89
|
+
Then it should fail
|
90
|
+
And the output should contain "this command is forced"
|
91
|
+
|
92
|
+
Scenario: A failing command that would otherwise have failed.
|
93
|
+
Given the configuration:
|
94
|
+
"""
|
95
|
+
run "false" do
|
96
|
+
unimportant!
|
97
|
+
end
|
98
|
+
"""
|
99
|
+
When I run scripted
|
100
|
+
Then it should pass
|
101
|
+
|
102
|
+
Scenario: Only when failed with a failing command
|
103
|
+
Given the configuration:
|
104
|
+
"""
|
105
|
+
run "false"
|
106
|
+
run "echo only when failed has run" do
|
107
|
+
only_when_failed!
|
108
|
+
end
|
109
|
+
"""
|
110
|
+
When I run scripted
|
111
|
+
Then it should fail
|
112
|
+
And the output should contain "only when failed has run"
|
113
|
+
|
114
|
+
Scenario: Only when failed when nothing failed
|
115
|
+
Given the configuration:
|
116
|
+
"""
|
117
|
+
run "true"
|
118
|
+
run "echo only when failed has run" do
|
119
|
+
only_when_failed!
|
120
|
+
end
|
121
|
+
"""
|
122
|
+
When I run scripted
|
123
|
+
Then it should pass
|
124
|
+
And the output should not contain "only when failed has run"
|