kicker 3.0.0pre1 → 3.0.0pre2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +10 -4
- data/lib/kicker.rb +26 -20
- data/lib/kicker/callback_chain.rb +14 -14
- data/lib/kicker/core_ext.rb +1 -1
- data/lib/kicker/fsevents.rb +6 -4
- data/lib/kicker/job.rb +57 -0
- data/lib/kicker/notification.rb +13 -24
- data/lib/kicker/options.rb +26 -22
- data/lib/kicker/recipes.rb +19 -16
- data/lib/kicker/recipes/could_not_handle_file.rb +1 -1
- data/lib/kicker/recipes/dot_kick.rb +8 -8
- data/lib/kicker/recipes/execute_cli_command.rb +1 -1
- data/lib/kicker/recipes/ignore.rb +4 -4
- data/lib/kicker/recipes/jstest.rb +1 -1
- data/lib/kicker/recipes/rails.rb +11 -11
- data/lib/kicker/recipes/ruby.rb +23 -23
- data/lib/kicker/utils.rb +44 -50
- data/lib/kicker/version.rb +1 -1
- metadata +108 -136
- data/vendor/terminal-notifier_v1.0/README.markdown +0 -70
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Info.plist +0 -52
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/MacOS/terminal-notifier +0 -0
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/PkgInfo +0 -1
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/Terminal.icns +0 -0
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/en.lproj/Credits.rtf +0 -29
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/en.lproj/MainMenu.nib +0 -0
- data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/_CodeSignature/CodeResources +0 -61
data/lib/kicker/recipes.rb
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
RECIPES_DIR = File.expand_path('../recipes', __FILE__)
|
2
|
-
USER_RECIPES_DIR = File.expand_path('~/.kick')
|
3
|
-
|
4
1
|
module Kernel
|
5
2
|
# If only given a <tt>name</tt>, the specified recipe will be loaded. For
|
6
3
|
# instance, the following, in a <tt>.kick</tt> file, will load the Rails
|
@@ -25,6 +22,12 @@ end
|
|
25
22
|
|
26
23
|
class Kicker
|
27
24
|
module Recipes #:nodoc:
|
25
|
+
RECIPES_DIR = Pathname.new('../recipes').expand_path(__FILE__)
|
26
|
+
USER_RECIPES_DIR = Pathname.new('~/.kick').expand_path
|
27
|
+
CURRENT_RECIPES_DIR = Pathname.pwd.join('.kick').expand_path
|
28
|
+
|
29
|
+
RECIPES_DIRS = [RECIPES_DIR, USER_RECIPES_DIR, CURRENT_RECIPES_DIR]
|
30
|
+
|
28
31
|
class << self
|
29
32
|
def reset!
|
30
33
|
@recipes = nil
|
@@ -33,33 +36,33 @@ class Kicker
|
|
33
36
|
load_recipe :could_not_handle_file
|
34
37
|
load_recipe :dot_kick
|
35
38
|
end
|
36
|
-
|
39
|
+
|
37
40
|
def recipes
|
38
41
|
@recipes ||= {}
|
39
42
|
end
|
40
|
-
|
43
|
+
|
41
44
|
def recipe_filename(name)
|
42
45
|
[
|
43
46
|
USER_RECIPES_DIR,
|
44
47
|
RECIPES_DIR
|
45
48
|
].each do |directory|
|
46
|
-
filename =
|
47
|
-
return filename if
|
49
|
+
filename = directory.join("#{name}.rb")
|
50
|
+
return filename if filename.exist?
|
48
51
|
end
|
49
52
|
end
|
50
|
-
|
53
|
+
|
51
54
|
def recipe_names
|
52
|
-
recipe_files.map { |filename|
|
55
|
+
recipe_files.map { |filename| filename.basename('.rb').to_s.to_sym }
|
53
56
|
end
|
54
|
-
|
57
|
+
|
55
58
|
def recipe_files
|
56
|
-
|
59
|
+
RECIPES_DIRS.map{|dir| Pathname.glob(dir.join('*.rb')) }.flatten.uniq.map(&:expand_path)
|
57
60
|
end
|
58
|
-
|
61
|
+
|
59
62
|
def define_recipe(name, &block)
|
60
63
|
recipes[name] = block
|
61
64
|
end
|
62
|
-
|
65
|
+
|
63
66
|
def load_recipe(name)
|
64
67
|
if recipe_names.include?(name)
|
65
68
|
load recipe_filename(name)
|
@@ -67,7 +70,7 @@ class Kicker
|
|
67
70
|
raise LoadError, "Can't load recipe `#{name}', it doesn't exist on disk. Loadable recipes are: #{recipe_names[0..-2].join(', ')}, and #{recipe_names[-1]}"
|
68
71
|
end
|
69
72
|
end
|
70
|
-
|
73
|
+
|
71
74
|
def activate_recipe(name)
|
72
75
|
unless recipes.has_key?(name)
|
73
76
|
load_recipe(name)
|
@@ -78,7 +81,7 @@ class Kicker
|
|
78
81
|
raise ArgumentError, "Can't activate the recipe `#{name}' because it hasn't been defined yet."
|
79
82
|
end
|
80
83
|
end
|
81
|
-
|
84
|
+
|
82
85
|
# See Kernel#recipe for more information about the usage.
|
83
86
|
def recipe(name, &block)
|
84
87
|
name = name.to_sym
|
@@ -89,7 +92,7 @@ class Kicker
|
|
89
92
|
end
|
90
93
|
end
|
91
94
|
end
|
92
|
-
|
95
|
+
|
93
96
|
reset!
|
94
97
|
end
|
95
98
|
end
|
@@ -4,32 +4,32 @@ module ReloadDotKick #:nodoc
|
|
4
4
|
@features_before_dot_kick = $LOADED_FEATURES.dup
|
5
5
|
@chains_before_dot_kick = Kicker.full_chain.map { |c| c.dup }
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
def call(files)
|
9
9
|
reset! if files.delete('.kick')
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def use?
|
13
13
|
File.exist?('.kick')
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def load!
|
17
17
|
load '.kick'
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def reset!
|
21
21
|
remove_loaded_features!
|
22
22
|
reset_chains!
|
23
23
|
load!
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
def reset_chains!
|
27
27
|
Kicker.full_chain = nil
|
28
|
-
|
28
|
+
|
29
29
|
chains = @chains_before_dot_kick.map { |c| c.dup }
|
30
30
|
Kicker.pre_process_chain, Kicker.process_chain, Kicker.post_process_chain = *chains
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
def remove_loaded_features!
|
34
34
|
($LOADED_FEATURES - @features_before_dot_kick).each do |feat|
|
35
35
|
$LOADED_FEATURES.delete(feat)
|
@@ -44,4 +44,4 @@ if ReloadDotKick.use?
|
|
44
44
|
ReloadDotKick.save_state
|
45
45
|
ReloadDotKick.load!
|
46
46
|
end
|
47
|
-
end
|
47
|
+
end
|
@@ -7,11 +7,11 @@ module Ignore
|
|
7
7
|
def self.call(files) #:nodoc:
|
8
8
|
files.reject! { |file| ignores.any? { |ignore| file =~ ignore } }
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def self.ignores #:nodoc:
|
12
12
|
@ignores ||= []
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def self.ignore(regexp_or_string) #:nodoc:
|
16
16
|
ignores << (regexp_or_string.is_a?(Regexp) ? regexp_or_string : /^#{regexp_or_string}$/)
|
17
17
|
end
|
@@ -33,9 +33,9 @@ end
|
|
33
33
|
|
34
34
|
recipe :ignore do
|
35
35
|
pre_process Ignore
|
36
|
-
|
36
|
+
|
37
37
|
ignore("tmp")
|
38
38
|
ignore(/\w+\.log/)
|
39
39
|
ignore(/\.(svn|git)\//)
|
40
40
|
ignore("svn-commit.tmp")
|
41
|
-
end
|
41
|
+
end
|
data/lib/kicker/recipes/rails.rb
CHANGED
@@ -6,7 +6,7 @@ class Kicker::Recipes::Rails < Kicker::Recipes::Ruby
|
|
6
6
|
%w{ test_type runner_bin test_cases_root test_options }.each do |delegate|
|
7
7
|
define_method(delegate) { Kicker::Recipes::Ruby.send(delegate) }
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
# Maps +type+, for instance `models', to a test directory.
|
11
11
|
def type_to_test_dir(type)
|
12
12
|
if test_type == 'test'
|
@@ -43,7 +43,7 @@ class Kicker::Recipes::Rails < Kicker::Recipes::Ruby
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
# Returns an array of all tests related to the given model.
|
48
48
|
def tests_for_model(model)
|
49
49
|
if test_type == 'test'
|
@@ -60,37 +60,37 @@ class Kicker::Recipes::Rails < Kicker::Recipes::Ruby
|
|
60
60
|
}
|
61
61
|
end.map { |f| test_file f }
|
62
62
|
end
|
63
|
-
|
63
|
+
|
64
64
|
def handle!
|
65
65
|
@tests.concat(@files.take_and_map do |file|
|
66
66
|
case file
|
67
67
|
# Run all functional tests when routes.rb is saved
|
68
68
|
when 'config/routes.rb'
|
69
69
|
Kicker::Recipes::Rails.all_controller_tests
|
70
|
-
|
70
|
+
|
71
71
|
# Match lib/*
|
72
72
|
when /^(lib\/.+)\.rb$/
|
73
73
|
test_file($1)
|
74
|
-
|
74
|
+
|
75
75
|
# Map fixtures to their related tests
|
76
76
|
when %r{^#{test_cases_root}/fixtures/(\w+)\.yml$}
|
77
77
|
tests_for_model($1)
|
78
|
-
|
78
|
+
|
79
79
|
# Match any file in app/ and map it to a test file
|
80
80
|
when %r{^app/(\w+)([\w/]*)/([\w\.]+)\.\w+$}
|
81
81
|
type, namespace, file = $1, $2, $3
|
82
|
-
|
82
|
+
|
83
83
|
if dir = Kicker::Recipes::Rails.type_to_test_dir(type)
|
84
84
|
if type == "views"
|
85
85
|
namespace = namespace.split('/')[1..-1]
|
86
86
|
file = "#{namespace.pop}_controller"
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
test_file File.join(dir, namespace, file)
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end)
|
93
|
-
|
93
|
+
|
94
94
|
# And let the Ruby handler match other stuff.
|
95
95
|
super
|
96
96
|
end
|
@@ -99,9 +99,9 @@ end
|
|
99
99
|
recipe :rails do
|
100
100
|
require 'rubygems' rescue LoadError
|
101
101
|
require 'active_support/inflector'
|
102
|
-
|
102
|
+
|
103
103
|
process Kicker::Recipes::Rails
|
104
|
-
|
104
|
+
|
105
105
|
# When changing the schema, prepare the test database.
|
106
106
|
process do |files|
|
107
107
|
execute 'rake db:test:prepare' if files.delete('db/schema.rb')
|
data/lib/kicker/recipes/ruby.rb
CHANGED
@@ -2,19 +2,19 @@ class Kicker::Recipes::Ruby
|
|
2
2
|
class << self
|
3
3
|
# Assigns the type of tests to run. Eg: `test' or `spec'.
|
4
4
|
attr_writer :test_type
|
5
|
-
|
5
|
+
|
6
6
|
# Returns the type of tests to run. Eg: `test' or `spec'.
|
7
7
|
#
|
8
8
|
# Defaults to `test' if no `spec' directory exists.
|
9
9
|
def test_type
|
10
10
|
@test_type ||= File.exist?('spec') ? 'spec' : 'test'
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
# Assigns the ruby command to run the tests with. Eg: `ruby19' or `specrb'.
|
14
14
|
#
|
15
15
|
# This can be set from the command line with the `-b' or `--ruby' options.
|
16
16
|
attr_writer :runner_bin
|
17
|
-
|
17
|
+
|
18
18
|
# Returns the ruby command to run the tests with. Eg: `ruby' or `spec'.
|
19
19
|
#
|
20
20
|
# Defaults to `ruby' if test_type is `test' and `spec' if test_type is
|
@@ -22,33 +22,33 @@ class Kicker::Recipes::Ruby
|
|
22
22
|
def runner_bin
|
23
23
|
@runner_bin ||= test_type == 'test' ? 'ruby' : 'rspec'
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
# Assigns the root directory of where test cases will be looked up.
|
27
27
|
attr_writer :test_cases_root
|
28
|
-
|
28
|
+
|
29
29
|
# Returns the root directory of where test cases will be looked up.
|
30
30
|
#
|
31
31
|
# Defaults to the value of test_type. Eg: `test' or `spec'.
|
32
32
|
def test_cases_root
|
33
33
|
@test_cases_root ||= test_type
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
attr_writer :test_options #:nodoc:
|
37
|
-
|
37
|
+
|
38
38
|
# Assigns extra options that are to be passed on to the runner_bin.
|
39
39
|
#
|
40
40
|
# Ruby.test_options << '-I ./lib/foo'
|
41
41
|
def test_options
|
42
42
|
@test_options ||= []
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
def reset!
|
46
46
|
@test_type = nil
|
47
47
|
@runner_bin = nil
|
48
48
|
@test_cases_root = nil
|
49
49
|
@test_options = nil
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
def runner_command(*parts)
|
53
53
|
parts.map do |part|
|
54
54
|
case part
|
@@ -59,63 +59,63 @@ class Kicker::Recipes::Ruby
|
|
59
59
|
end
|
60
60
|
end.compact.join(' ')
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
# Runs the given tests, if there are any, with the method defined by
|
64
64
|
# test_type. If test_type is `test' the run_with_test_runner method is
|
65
65
|
# used. The same applies when test_type is `spec'.
|
66
66
|
def run_tests(tests)
|
67
67
|
send("run_with_#{test_type}_runner", tests) unless tests.empty?
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
def test_runner_command(tests)
|
71
71
|
tests_without_ext = tests.map { |f| f[0,f.size-3] }
|
72
72
|
runner_command(runner_bin, %w{ -I. } + test_options, '-r', tests_without_ext.join(' -r '), "-e ''")
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
# Runs the given tests with `ruby' as unit-test tests.
|
76
76
|
def run_with_test_runner(tests)
|
77
77
|
execute(test_runner_command(tests))
|
78
78
|
end
|
79
|
-
|
79
|
+
|
80
80
|
def spec_runner_command(tests)
|
81
81
|
runner_command(runner_bin, test_options, tests)
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
84
|
# Runs the given tests with `spec' as RSpec tests.
|
85
85
|
def run_with_spec_runner(tests)
|
86
86
|
execute(spec_runner_command(tests))
|
87
87
|
end
|
88
88
|
end
|
89
|
-
|
89
|
+
|
90
90
|
def self.call(files) #:nodoc:
|
91
91
|
handler = new(files)
|
92
92
|
handler.handle!
|
93
93
|
run_tests(handler.tests)
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
# The list of collected tests.
|
97
97
|
attr_reader :tests
|
98
|
-
|
98
|
+
|
99
99
|
def initialize(files) #:nodoc:
|
100
100
|
@files = files
|
101
101
|
@tests = []
|
102
102
|
end
|
103
|
-
|
103
|
+
|
104
104
|
# A shortcut to Ruby.test_type.
|
105
105
|
def test_type
|
106
106
|
self.class.test_type
|
107
107
|
end
|
108
|
-
|
108
|
+
|
109
109
|
# A shortcut to Ruby.runner_bin.
|
110
110
|
def runner_bin
|
111
111
|
self.class.runner_bin
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
# A shortcut to Ruby.test_cases_root.
|
115
115
|
def test_cases_root
|
116
116
|
self.class.test_cases_root
|
117
117
|
end
|
118
|
-
|
118
|
+
|
119
119
|
# Returns the file for +name+ if it exists.
|
120
120
|
#
|
121
121
|
# test_file('foo') # => "test/foo_test.rb"
|
@@ -125,7 +125,7 @@ class Kicker::Recipes::Ruby
|
|
125
125
|
file = File.join(test_cases_root, "#{name}_#{test_type}.rb")
|
126
126
|
file if File.exist?(file)
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
# This method is called to collect tests. Override this if you're subclassing
|
130
130
|
# and make sure to call +super+.
|
131
131
|
def handle!
|
@@ -134,7 +134,7 @@ class Kicker::Recipes::Ruby
|
|
134
134
|
# Match any ruby test file
|
135
135
|
when /^#{test_cases_root}\/.+_#{test_type}\.rb$/
|
136
136
|
file
|
137
|
-
|
137
|
+
|
138
138
|
# A file such as ./lib/namespace/foo.rb is mapped to:
|
139
139
|
# * ./test/namespace/foo_test.rb
|
140
140
|
# * ./test/foo_test.rb
|
data/lib/kicker/utils.rb
CHANGED
@@ -1,36 +1,32 @@
|
|
1
1
|
require 'shellwords' if RUBY_VERSION >= "1.9"
|
2
2
|
|
3
3
|
class Kicker
|
4
|
-
Status = Struct.new(:command, :exit_code, :output) do
|
5
|
-
def success?
|
6
|
-
exit_code == 0
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
4
|
module Utils #:nodoc:
|
11
5
|
extend self
|
12
6
|
|
13
7
|
attr_accessor :should_clear_screen
|
14
8
|
alias_method :should_clear_screen?, :should_clear_screen
|
15
9
|
|
16
|
-
def perform_work(
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
def execute(command)
|
26
|
-
perform_work(command) do |status|
|
27
|
-
_execute(status)
|
28
|
-
yield status if block_given?
|
10
|
+
def perform_work(command_or_options)
|
11
|
+
if command_or_options.is_a?(Hash)
|
12
|
+
options = command_or_options
|
13
|
+
elsif command_or_options.is_a?(String)
|
14
|
+
options = { :command => command_or_options }
|
15
|
+
else
|
16
|
+
raise ArgumentError, "Should be a string or a hash."
|
29
17
|
end
|
18
|
+
job = Job.new(options)
|
19
|
+
will_execute_command(job)
|
20
|
+
yield job
|
21
|
+
did_execute_command(job)
|
22
|
+
job
|
30
23
|
end
|
31
24
|
|
32
|
-
def
|
33
|
-
|
25
|
+
def execute(command_or_options)
|
26
|
+
perform_work(command_or_options) do |job|
|
27
|
+
_execute(job)
|
28
|
+
yield job if block_given?
|
29
|
+
end
|
34
30
|
end
|
35
31
|
|
36
32
|
def log(message)
|
@@ -41,14 +37,6 @@ class Kicker
|
|
41
37
|
puts "#{now.strftime('%H:%M:%S')}.#{now.usec.to_s[0,2]} | #{message}"
|
42
38
|
end
|
43
39
|
end
|
44
|
-
|
45
|
-
def last_command_succeeded?
|
46
|
-
$?.success?
|
47
|
-
end
|
48
|
-
|
49
|
-
def last_command_status
|
50
|
-
$?.exitstatus
|
51
|
-
end
|
52
40
|
|
53
41
|
def clear_console!
|
54
42
|
puts(CLEAR) if Kicker.clear_console?
|
@@ -58,22 +46,22 @@ class Kicker
|
|
58
46
|
|
59
47
|
CLEAR = "\e[H\e[2J"
|
60
48
|
|
61
|
-
def _execute(
|
49
|
+
def _execute(job)
|
62
50
|
silent = Kicker.silent?
|
63
51
|
unless silent
|
64
52
|
puts
|
65
53
|
sync_before, $stdout.sync = $stdout.sync, true
|
66
54
|
end
|
67
55
|
output = ""
|
68
|
-
popen(
|
56
|
+
popen(job.command) do |io|
|
69
57
|
while str = io.read(1)
|
70
58
|
output << str
|
71
59
|
$stdout.print str unless silent
|
72
60
|
end
|
73
61
|
end
|
74
|
-
|
75
|
-
|
76
|
-
|
62
|
+
job.output = output.strip
|
63
|
+
job.exit_code = $?.exitstatus
|
64
|
+
job
|
77
65
|
ensure
|
78
66
|
unless silent
|
79
67
|
$stdout.sync = sync_before
|
@@ -90,19 +78,30 @@ class Kicker
|
|
90
78
|
IO.popen("#{command} 2>&1", &block)
|
91
79
|
end
|
92
80
|
end
|
93
|
-
|
94
|
-
def will_execute_command(
|
81
|
+
|
82
|
+
def will_execute_command(job)
|
95
83
|
puts(CLEAR) if Kicker.clear_console? && should_clear_screen?
|
96
84
|
@should_clear_screen = false
|
97
85
|
|
98
|
-
|
99
|
-
|
86
|
+
if message = job.print_before
|
87
|
+
log(message)
|
88
|
+
end
|
89
|
+
|
90
|
+
if notification = job.notify_before
|
91
|
+
Notification.notify(notification)
|
92
|
+
end
|
100
93
|
end
|
101
|
-
|
102
|
-
def did_execute_command(
|
103
|
-
|
104
|
-
|
105
|
-
|
94
|
+
|
95
|
+
def did_execute_command(job)
|
96
|
+
if message = job.print_after
|
97
|
+
puts(message)
|
98
|
+
end
|
99
|
+
|
100
|
+
log(job.success? ? "Success" : "Failed (#{job.exit_code})")
|
101
|
+
|
102
|
+
if notification = job.notify_after
|
103
|
+
Notification.notify(notification)
|
104
|
+
end
|
106
105
|
end
|
107
106
|
end
|
108
107
|
end
|
@@ -112,22 +111,17 @@ module Kernel
|
|
112
111
|
def log(message)
|
113
112
|
Kicker::Utils.log(message)
|
114
113
|
end
|
115
|
-
|
114
|
+
|
116
115
|
# When you perform some work (like shelling out a command to run without
|
117
116
|
# using +execute+) you need to call this method, with a block in which you
|
118
117
|
# perform your work, which will take care of logging the work appropriately.
|
119
118
|
def perform_work(command, &block)
|
120
119
|
Kicker::Utils.perform_work(command, &block)
|
121
120
|
end
|
122
|
-
|
121
|
+
|
123
122
|
# Executes the +command+, logs the output, and optionally sends user
|
124
123
|
# notifications on Mac OS X (10.8 or higher).
|
125
124
|
def execute(command, &block)
|
126
125
|
Kicker::Utils.execute(command, &block)
|
127
126
|
end
|
128
|
-
|
129
|
-
# Returns the last executed command.
|
130
|
-
def last_command
|
131
|
-
Kicker::Utils.last_command
|
132
|
-
end
|
133
127
|
end
|