consular-terminator 0.1.0
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 +3 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +61 -0
- data/LICENSE.txt +674 -0
- data/README.rdoc +13 -0
- data/Rakefile +23 -0
- data/consular-terminator.gemspec +50 -0
- data/cucumber.yml +1 -0
- data/features/support/env.rb +18 -0
- data/lib/consular/terminator/version.rb +16 -0
- data/lib/consular/terminator.rb +260 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/terminator_spec.rb +56 -0
- data/tests.watchr +151 -0
- metadata +172 -0
data/README.rdoc
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
= Terminator support for Consular
|
|
2
|
+
|
|
3
|
+
This gem is an add-on to Consular that implements support for the Terminator
|
|
4
|
+
terminal emulator on Linux.
|
|
5
|
+
|
|
6
|
+
Since Terminator does not have an API for controlling it, a commandline tool
|
|
7
|
+
called `xdotool` is used to send keyboard shortcuts to a running Terminator
|
|
8
|
+
in order to control it.
|
|
9
|
+
|
|
10
|
+
== Copyright
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2011 Ilkka Laukkanen. See LICENSE.txt for further details.
|
|
13
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'bundler'
|
|
3
|
+
Bundler::GemHelper.install_tasks
|
|
4
|
+
|
|
5
|
+
require 'rspec/core'
|
|
6
|
+
require 'rspec/core/rake_task'
|
|
7
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
|
8
|
+
t.pattern = 'spec/**/*_spec.rb'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
|
12
|
+
t.pattern = 'spec/**/*_spec.rb'
|
|
13
|
+
t.rcov = true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
require 'cucumber/rake/task'
|
|
17
|
+
Cucumber::Rake::Task.new(:features)
|
|
18
|
+
|
|
19
|
+
require 'yard'
|
|
20
|
+
YARD::Rake::YardocTask.new
|
|
21
|
+
|
|
22
|
+
task :default => [:features, :spec]
|
|
23
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Terminator support for Consular
|
|
2
|
+
# Copyright (C) 2011 Ilkka Laukkanen
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
18
|
+
require 'consular/terminator/version'
|
|
19
|
+
|
|
20
|
+
Gem::Specification.new do |s|
|
|
21
|
+
s.name = 'consular-terminator'
|
|
22
|
+
s.version = Consular::Terminator::VERSION
|
|
23
|
+
s.authors = ['Ilkka Laukkanen']
|
|
24
|
+
s.email = [%q{ilkka.s.laukkanen@gmail.com}]
|
|
25
|
+
s.homepage = %q{http://github.com/ilkka/consular-terminator}
|
|
26
|
+
s.summary = %q{Terminator support for Consular}
|
|
27
|
+
s.licenses = ["GPLv3"]
|
|
28
|
+
s.description =
|
|
29
|
+
%q{Add support for automating the Terminator terminal with Consular.}
|
|
30
|
+
|
|
31
|
+
s.files = `git ls-files`.split("\n")
|
|
32
|
+
s.test_files = `git ls-files -- test/* spec/* features/*`.split("\n")
|
|
33
|
+
|
|
34
|
+
s.extra_rdoc_files = [
|
|
35
|
+
'LICENSE.txt',
|
|
36
|
+
'README.rdoc'
|
|
37
|
+
]
|
|
38
|
+
s.require_paths = ["lib"]
|
|
39
|
+
|
|
40
|
+
s.add_runtime_dependency('consular')
|
|
41
|
+
|
|
42
|
+
s.add_development_dependency('rake')
|
|
43
|
+
s.add_development_dependency('rspec')
|
|
44
|
+
s.add_development_dependency('yard')
|
|
45
|
+
s.add_development_dependency('cucumber')
|
|
46
|
+
s.add_development_dependency('spork')
|
|
47
|
+
s.add_development_dependency('watchr')
|
|
48
|
+
s.add_development_dependency('bundler')
|
|
49
|
+
s.add_development_dependency('mocha')
|
|
50
|
+
end
|
data/cucumber.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
default: --color --drb --port 8990
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'spork'
|
|
2
|
+
|
|
3
|
+
Spork.prefork do
|
|
4
|
+
require 'bundler'
|
|
5
|
+
begin
|
|
6
|
+
Bundler.setup(:default, :development)
|
|
7
|
+
rescue Bundler::BundlerError => e
|
|
8
|
+
$stderr.puts e.message
|
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
|
10
|
+
exit e.status_code
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require 'rspec/expectations'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Spork.each_run do
|
|
17
|
+
|
|
18
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'consular'
|
|
2
|
+
|
|
3
|
+
module Consular
|
|
4
|
+
class Terminator < Core
|
|
5
|
+
module Version
|
|
6
|
+
MAJOR = 0
|
|
7
|
+
MINOR = 1
|
|
8
|
+
PATCH = 0
|
|
9
|
+
BUILD = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
VERSION = [
|
|
13
|
+
Version::MAJOR, Version::MINOR, Version::PATCH, Version::BUILD
|
|
14
|
+
].compact.join('.')
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
require 'consular'
|
|
2
|
+
require 'rbconfig'
|
|
3
|
+
|
|
4
|
+
module Consular
|
|
5
|
+
# Consular core for interacting with Terminator.
|
|
6
|
+
#
|
|
7
|
+
# Since we don't have any real API to work with and just send
|
|
8
|
+
# keypresses via XTEST, we don't have tab references either.
|
|
9
|
+
# Instead we just count tabs and return tab indexes.
|
|
10
|
+
#
|
|
11
|
+
# Largely adapted from http://github.com/achiu/consular-osx
|
|
12
|
+
class Terminator < Core
|
|
13
|
+
|
|
14
|
+
Consular.add_core self
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# Check to see if current system is valid for this core
|
|
18
|
+
#
|
|
19
|
+
# @api public
|
|
20
|
+
def valid_system?
|
|
21
|
+
if (RbConfig::CONFIG['host_os'] =~ /linux/) != nil
|
|
22
|
+
if !(xdotool = `which xdotool`.chomp).empty?
|
|
23
|
+
begin
|
|
24
|
+
return File::Stat.new(xdotool).executable?
|
|
25
|
+
rescue
|
|
26
|
+
return false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
return false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Initialize
|
|
35
|
+
#
|
|
36
|
+
# @param [String] path
|
|
37
|
+
# path to Termfile.
|
|
38
|
+
#
|
|
39
|
+
# @api public
|
|
40
|
+
def initialize(path)
|
|
41
|
+
super
|
|
42
|
+
@tabidx = nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Method called by runner to Execute Termfile setup.
|
|
46
|
+
#
|
|
47
|
+
# @api public
|
|
48
|
+
def setup!
|
|
49
|
+
@termfile[:setup].each { |cmd| execute_command(cmd, :in => active_window) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Method called by runner to execute Termfile.
|
|
53
|
+
#
|
|
54
|
+
# @api public
|
|
55
|
+
def process!
|
|
56
|
+
windows = @termfile[:windows]
|
|
57
|
+
default = windows.delete('default')
|
|
58
|
+
execute_window(default, :default => true) unless default[:tabs].empty?
|
|
59
|
+
windows.each_pair { |_, cont| execute_window(cont) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Executes the commands for each designated window.
|
|
63
|
+
# .run_windows will iterate through each of the tabs in
|
|
64
|
+
# sorted order to execute the tabs in the order they were set.
|
|
65
|
+
# The logic follows this:
|
|
66
|
+
#
|
|
67
|
+
# If the content is for the 'default' window,
|
|
68
|
+
# then use the current active window and generate the commands.
|
|
69
|
+
#
|
|
70
|
+
# If the content is for a new window,
|
|
71
|
+
# then generate a new window and activate the windows.
|
|
72
|
+
#
|
|
73
|
+
# Otherwise, open a new tab and execute the commands.
|
|
74
|
+
#
|
|
75
|
+
# @param [Hash] content
|
|
76
|
+
# The hash contents of the window from the Termfile.
|
|
77
|
+
# @param [Hash] options
|
|
78
|
+
# Addional options to pass. You can use:
|
|
79
|
+
# :default - Whether this is being run as the default window.
|
|
80
|
+
#
|
|
81
|
+
# @example
|
|
82
|
+
# @core.execute_window contents, :default => true
|
|
83
|
+
# @core.execute_window contents, :default => true
|
|
84
|
+
#
|
|
85
|
+
# @api public
|
|
86
|
+
def execute_window(content, options = {})
|
|
87
|
+
window_options = content[:options]
|
|
88
|
+
_contents = content[:tabs]
|
|
89
|
+
_first_run = true
|
|
90
|
+
|
|
91
|
+
_contents.keys.sort.each do |key|
|
|
92
|
+
_content = _contents[key]
|
|
93
|
+
_options = content[:options]
|
|
94
|
+
_name = options[:name]
|
|
95
|
+
|
|
96
|
+
_tab =
|
|
97
|
+
if _first_run && !options[:default]
|
|
98
|
+
open_window options.merge(window_options)
|
|
99
|
+
else
|
|
100
|
+
key == 'default' ? nil : open_tab(_options)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
_first_run = false
|
|
104
|
+
commands = prepend_befores _content[:commands], _contents[:befores]
|
|
105
|
+
commands = set_title _name, commands
|
|
106
|
+
commands.each { |cmd| execute_command cmd, :in => _tab }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Prepend a title setting command prior to the other commands.
|
|
112
|
+
#
|
|
113
|
+
# @param [String] title
|
|
114
|
+
# The title to set for the context of the commands.
|
|
115
|
+
# @param [Array<String>] commands
|
|
116
|
+
# The context of commands to preprend to.
|
|
117
|
+
#
|
|
118
|
+
# @api public
|
|
119
|
+
def set_title(title, commands)
|
|
120
|
+
cmd = "PS1=\"$PS1\\[\\e]2;#{title}\\a\\]\""
|
|
121
|
+
title ? commands.insert(0, cmd) : commands
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Prepends the :before commands to the current context's
|
|
125
|
+
# commands if it exists.
|
|
126
|
+
#
|
|
127
|
+
# @param [Array<String>] commands
|
|
128
|
+
# The current tab commands
|
|
129
|
+
# @param [Array<String>] befores
|
|
130
|
+
# The current window's :befores
|
|
131
|
+
#
|
|
132
|
+
# @return [Array<String>]
|
|
133
|
+
# The current context commands with the :before commands prepended
|
|
134
|
+
#
|
|
135
|
+
# @api public
|
|
136
|
+
def prepend_befores(commands, befores = nil)
|
|
137
|
+
unless befores.nil? || befores.empty?
|
|
138
|
+
commands.insert(0, befores).flatten!
|
|
139
|
+
else
|
|
140
|
+
commands
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Execute the given command in the context of the
|
|
145
|
+
# active window.
|
|
146
|
+
#
|
|
147
|
+
# @param [String] cmd
|
|
148
|
+
# The command to execute.
|
|
149
|
+
# @param [Hash] options
|
|
150
|
+
# Additional options to pass into appscript for the context.
|
|
151
|
+
#
|
|
152
|
+
# @example
|
|
153
|
+
# @osx.execute_command 'ps aux', :in => @tab_object
|
|
154
|
+
#
|
|
155
|
+
# @api public
|
|
156
|
+
def execute_command(cmd, options = {})
|
|
157
|
+
run_in_active_terminator cmd, options
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Opens a new tab and return the last instantiated tab(itself).
|
|
161
|
+
#
|
|
162
|
+
# @param [Hash] options
|
|
163
|
+
# Options to further customize the window. You can use:
|
|
164
|
+
# :settings -
|
|
165
|
+
# :selected -
|
|
166
|
+
#
|
|
167
|
+
# @return
|
|
168
|
+
# Returns a refernce to the last instantiated tab of the
|
|
169
|
+
# window.
|
|
170
|
+
#
|
|
171
|
+
# @api public
|
|
172
|
+
def open_tab(options = nil)
|
|
173
|
+
send_keypress(active_terminator_window, "ctrl+shift+t")
|
|
174
|
+
# FIXME: horribly hacky but can't come up with any way
|
|
175
|
+
# to truly make sure tab has opened.
|
|
176
|
+
sleep 1
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Opens a new window and returns its
|
|
180
|
+
# last instantiated tab.(The first 'tab' in the window).
|
|
181
|
+
#
|
|
182
|
+
# @param [Hash] options
|
|
183
|
+
# Options to further customize the window. You can use:
|
|
184
|
+
# :bound - Set the bounds of the windows
|
|
185
|
+
# :visible - Set whether or not the current window is visible
|
|
186
|
+
# :miniaturized - Set whether or not the window is minimized
|
|
187
|
+
#
|
|
188
|
+
# @return
|
|
189
|
+
# Returns a refernce to the last instantiated tab of the
|
|
190
|
+
# window.
|
|
191
|
+
#
|
|
192
|
+
# @api public
|
|
193
|
+
def open_window(options = nil)
|
|
194
|
+
windowid_before = active_terminator_window
|
|
195
|
+
send_keypress(active_terminator_window, "ctrl+shift+i")
|
|
196
|
+
# wait for the active window to change -> new window opened
|
|
197
|
+
while active_terminator_window == windowid_before
|
|
198
|
+
sleep 1
|
|
199
|
+
end
|
|
200
|
+
return (@tabidx = 0)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
private
|
|
204
|
+
|
|
205
|
+
# Send keypresses to window winid
|
|
206
|
+
#
|
|
207
|
+
# @api private
|
|
208
|
+
def send_keypress(winid, keys)
|
|
209
|
+
xdotool("windowfocus #{winid}")
|
|
210
|
+
xdotool("key #{keys}")
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Run command with active terminator
|
|
214
|
+
#
|
|
215
|
+
# @api private
|
|
216
|
+
def run_in_active_terminator(cmd, options = {})
|
|
217
|
+
type_in_window(active_terminator_window, "#{cmd}\n")
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Get active terminator window winid.
|
|
221
|
+
# Gets the active window and returns it if it is
|
|
222
|
+
# a Terminator window. If it is not a terminator window,
|
|
223
|
+
# the method gets all terminator windows and returns the
|
|
224
|
+
# first one. If no terminator windows exist, aborts.
|
|
225
|
+
#
|
|
226
|
+
# @api private
|
|
227
|
+
def active_terminator_window
|
|
228
|
+
active = xdotool("getactivewindow").chomp
|
|
229
|
+
if not all_terminator_windows.include? active
|
|
230
|
+
active = all_terminator_windows.first
|
|
231
|
+
end
|
|
232
|
+
if not active
|
|
233
|
+
abort("No Terminator windows found")
|
|
234
|
+
end
|
|
235
|
+
return active
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Get window IDs of all terminator windows.
|
|
239
|
+
#
|
|
240
|
+
# @api private
|
|
241
|
+
def all_terminator_windows
|
|
242
|
+
xdotool("search --onlyvisible --class terminator").split("\n")
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Type text in window winid.
|
|
246
|
+
#
|
|
247
|
+
# @api private
|
|
248
|
+
def type_in_window(winid, text)
|
|
249
|
+
xdotool("windowfocus #{winid}")
|
|
250
|
+
xdotool("type \"#{text}\"")
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Execute xdotool with the given args and return output
|
|
254
|
+
#
|
|
255
|
+
# @api private
|
|
256
|
+
def xdotool(cmd)
|
|
257
|
+
IO.popen("xdotool #{cmd}").read
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'spork'
|
|
2
|
+
|
|
3
|
+
Spork.prefork do
|
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
5
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
6
|
+
require 'rspec'
|
|
7
|
+
require 'mocha'
|
|
8
|
+
|
|
9
|
+
# Requires supporting files with custom matchers and macros, etc,
|
|
10
|
+
# in ./support/ and its subdirectories.
|
|
11
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
|
12
|
+
|
|
13
|
+
RSpec.configure do |config|
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
require 'tempfile'
|
|
17
|
+
|
|
18
|
+
class EmptyTermfile < Tempfile
|
|
19
|
+
def initialize
|
|
20
|
+
super('consular')
|
|
21
|
+
write(%q(setup "echo 'setup'"))
|
|
22
|
+
rewind
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Spork.each_run do
|
|
28
|
+
require 'consular/terminator'
|
|
29
|
+
end
|
|
30
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
|
2
|
+
require 'rbconfig'
|
|
3
|
+
|
|
4
|
+
describe Consular::Terminator do
|
|
5
|
+
it 'should be added as a core to Consular' do
|
|
6
|
+
Consular.cores.should include Consular::Terminator
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'should treat linux as a valid system' do
|
|
10
|
+
RbConfig::CONFIG.expects(:[]).with('host_os').returns('x86-linux')
|
|
11
|
+
Consular::Terminator.expects(:`).with('which xdotool').returns('/usr/bin/xdotool')
|
|
12
|
+
stat = mock()
|
|
13
|
+
stat.expects(:executable?).returns(true)
|
|
14
|
+
File::Stat.expects(:new).returns(stat)
|
|
15
|
+
Consular::Terminator.valid_system?.should == true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'should treat non-linux as an invalid system' do
|
|
19
|
+
RbConfig::CONFIG.expects(:[]).with('host_os').returns('darwin')
|
|
20
|
+
Consular::Terminator.valid_system?.should == false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'should treat systems without "which" as invalid' do
|
|
24
|
+
RbConfig::CONFIG.expects(:[]).with('host_os').returns('x86-linux')
|
|
25
|
+
Consular::Terminator.expects(:`).with('which xdotool').returns('which: command not found')
|
|
26
|
+
File::Stat.expects(:new).throws('no such file')
|
|
27
|
+
Consular::Terminator.valid_system?.should == false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'should treat systems without "xdotool" as invalid' do
|
|
31
|
+
RbConfig::CONFIG.expects(:[]).with('host_os').returns('x86-linux')
|
|
32
|
+
Consular::Terminator.expects(:`).with('which xdotool').returns('')
|
|
33
|
+
File::Stat.expects(:new).throws('no such file')
|
|
34
|
+
Consular::Terminator.valid_system?.should == false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'should send ctrl+shift+i when creating new window' do
|
|
38
|
+
f = EmptyTermfile.new
|
|
39
|
+
core = Consular::Terminator.new f.path
|
|
40
|
+
core.expects(:active_terminator_window).once.returns(1)
|
|
41
|
+
core.expects(:active_terminator_window).once.returns(1)
|
|
42
|
+
core.expects(:xdotool).with("windowfocus 1")
|
|
43
|
+
core.expects(:xdotool).with("key ctrl+shift+i")
|
|
44
|
+
core.expects(:active_terminator_window).once.returns(2)
|
|
45
|
+
core.open_window
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'should send ctrl+shift+t when creating new tab' do
|
|
49
|
+
f = EmptyTermfile.new
|
|
50
|
+
core = Consular::Terminator.new f.path
|
|
51
|
+
core.expects(:active_terminator_window).returns(1)
|
|
52
|
+
core.expects(:xdotool).with("windowfocus 1")
|
|
53
|
+
core.expects(:xdotool).with("key ctrl+shift+t")
|
|
54
|
+
core.open_tab
|
|
55
|
+
end
|
|
56
|
+
end
|
data/tests.watchr
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env watchr
|
|
2
|
+
# vim:ft=ruby
|
|
3
|
+
|
|
4
|
+
def have_notify_send?
|
|
5
|
+
case `which notify-send`.empty?
|
|
6
|
+
when true
|
|
7
|
+
false
|
|
8
|
+
else
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def have_growl?
|
|
14
|
+
@have_growl ||= begin
|
|
15
|
+
require 'growl'
|
|
16
|
+
rescue LoadError
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def error_icon_name
|
|
22
|
+
"gtk-dialog-error"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def success_icon_name
|
|
26
|
+
"gtk-dialog-info"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Rules
|
|
30
|
+
watch('^spec/.+_spec\.rb$') { |md| spec md[0] }
|
|
31
|
+
watch('^lib/.+\.rb$') { |md| spec "spec/#{File.basename(md[0]).gsub(/\..*?$/, '')}_spec.rb" }
|
|
32
|
+
watch('^features/.+\.feature$') { |md| feature md[0] }
|
|
33
|
+
watch('^features/step_definitions/(.+)_steps\.rb$') { |md| feature "features/#{md[1]}.feature" }
|
|
34
|
+
|
|
35
|
+
# Notify using notify-send.
|
|
36
|
+
#
|
|
37
|
+
# @param icon [String] name of stock icon to use.
|
|
38
|
+
# @param title [String] title of notification.
|
|
39
|
+
# @param message [String] message for notification body.
|
|
40
|
+
# @return [Boolean] true if the command ran successfully, false
|
|
41
|
+
# otherwise.
|
|
42
|
+
def notify(icon, title, message)
|
|
43
|
+
system("notify-send -t 3000 -i #{icon} \"#{title}\" \"#{message}\"")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Notify of success.
|
|
47
|
+
#
|
|
48
|
+
def notify_success
|
|
49
|
+
puts "Success"
|
|
50
|
+
if have_notify_send?
|
|
51
|
+
notify success_icon_name, "All green!", "Now write more tests :)"
|
|
52
|
+
elsif have_growl?
|
|
53
|
+
Growl.notify_ok "All green!"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Notify of failure.
|
|
58
|
+
#
|
|
59
|
+
def notify_failure
|
|
60
|
+
puts "Failure"
|
|
61
|
+
if have_notify_send?
|
|
62
|
+
notify error_icon_name, "Something is broken", "Now go fix it :)"
|
|
63
|
+
elsif have_growl?
|
|
64
|
+
Growl.notify_error "Something is broken"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Run a single ruby command. Notify appropriately.
|
|
69
|
+
#
|
|
70
|
+
# @param cmd [String] command to run.
|
|
71
|
+
def run(cmd)
|
|
72
|
+
system('clear')
|
|
73
|
+
puts "Running #{cmd}"
|
|
74
|
+
if system(cmd)
|
|
75
|
+
notify_success
|
|
76
|
+
true
|
|
77
|
+
else
|
|
78
|
+
notify_failure
|
|
79
|
+
false
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Run a single spec.
|
|
84
|
+
#
|
|
85
|
+
# @param specfiles [String] path to specfile or [Array] of paths.
|
|
86
|
+
def spec(specfiles)
|
|
87
|
+
specs = case specfiles.respond_to? :join
|
|
88
|
+
when true
|
|
89
|
+
specfiles.join(" ")
|
|
90
|
+
when false
|
|
91
|
+
specfiles
|
|
92
|
+
end
|
|
93
|
+
if run(%Q(rspec #{specs}))
|
|
94
|
+
if @last_run_failed
|
|
95
|
+
@last_run_failed = false
|
|
96
|
+
run_all_specs
|
|
97
|
+
end
|
|
98
|
+
else
|
|
99
|
+
@last_run_failed = true
|
|
100
|
+
end
|
|
101
|
+
!@last_run_failed
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Run a single feature.
|
|
105
|
+
#
|
|
106
|
+
# @param featurefiles [String] path to feature file or [Array] of paths.
|
|
107
|
+
def feature(featurefiles)
|
|
108
|
+
features = case featurefiles.respond_to? :join
|
|
109
|
+
when true
|
|
110
|
+
featurefiles.join(" ")
|
|
111
|
+
when false
|
|
112
|
+
featurefiles
|
|
113
|
+
end
|
|
114
|
+
run(%Q(cucumber #{features}))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Run all specs.
|
|
118
|
+
#
|
|
119
|
+
def run_all_specs
|
|
120
|
+
spec Dir['spec/*_spec.rb']
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Run all features.
|
|
124
|
+
#
|
|
125
|
+
def run_features
|
|
126
|
+
feature Dir['features/*.feature']
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Run specs and features.
|
|
130
|
+
#
|
|
131
|
+
def run_suite
|
|
132
|
+
run_all_specs && run_features
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Run all specs on Ctrl-\
|
|
136
|
+
Signal.trap('QUIT') { run_all_specs }
|
|
137
|
+
|
|
138
|
+
# Run full suite on one Ctrl-C, quit on two
|
|
139
|
+
@interrupted = false
|
|
140
|
+
Signal.trap('INT') do
|
|
141
|
+
if @interrupted
|
|
142
|
+
abort("\n")
|
|
143
|
+
else
|
|
144
|
+
puts "Interrupt a second time to quit"
|
|
145
|
+
@interrupted = true
|
|
146
|
+
Kernel.sleep 1.5
|
|
147
|
+
run_suite
|
|
148
|
+
@interrupted = false
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|