kicker 2.1.0 → 2.2.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 +10 -0
- data/.kick +37 -0
- data/README.rdoc +24 -21
- data/Rakefile +0 -1
- data/TODO.rdoc +4 -34
- data/VERSION +1 -0
- data/html/images/kikker.jpg +0 -0
- data/kicker.gemspec +106 -0
- data/lib/kicker.rb +46 -60
- data/lib/kicker/callback_chain.rb +4 -4
- data/lib/kicker/core_ext.rb +9 -1
- data/lib/kicker/growl.rb +54 -19
- data/lib/kicker/log_status_helper.rb +38 -0
- data/lib/kicker/options.rb +59 -34
- data/lib/kicker/recipes.rb +58 -0
- data/lib/kicker/recipes/could_not_handle_file.rb +5 -3
- data/lib/kicker/recipes/dot_kick.rb +17 -5
- data/lib/kicker/recipes/execute_cli_command.rb +1 -1
- data/lib/kicker/recipes/ignore.rb +8 -6
- data/lib/kicker/recipes/jstest.rb +7 -5
- data/lib/kicker/recipes/rails.rb +108 -43
- data/lib/kicker/recipes/ruby.rb +155 -0
- data/lib/kicker/utils.rb +36 -32
- data/test/callback_chain_test.rb +1 -1
- data/test/core_ext_test.rb +15 -5
- data/test/filesystem_change_test.rb +1 -1
- data/test/growl_test.rb +85 -0
- data/test/initialization_test.rb +25 -56
- data/test/log_status_helper_test.rb +56 -0
- data/test/options_test.rb +50 -12
- data/test/recipes/could_not_handle_file_test.rb +10 -0
- data/test/recipes/dot_kick_test.rb +1 -5
- data/test/recipes/execute_cli_command_test.rb +3 -3
- data/test/recipes/ignore_test.rb +1 -1
- data/test/recipes/jstest_test.rb +1 -1
- data/test/recipes/rails_test.rb +118 -18
- data/test/recipes/ruby_test.rb +154 -0
- data/test/recipes_test.rb +39 -0
- data/test/test_helper.rb +1 -1
- data/test/utils_test.rb +103 -48
- metadata +19 -6
- data/VERSION.yml +0 -4
- data/lib/kicker/validate.rb +0 -24
data/lib/kicker/core_ext.rb
CHANGED
@@ -9,11 +9,19 @@ class Kicker
|
|
9
9
|
# b # => [4]
|
10
10
|
# a # => [1, 3]
|
11
11
|
#
|
12
|
+
# If +pattern+ is specified then files matching the pattern will be taken.
|
13
|
+
#
|
14
|
+
# a = [ 'bar', 'foo/bar' ]
|
15
|
+
# b = a.take_and_map('*/bar') { |x| x }
|
16
|
+
# b # => ['foo/bar']
|
17
|
+
# a # => ['bar']
|
18
|
+
#
|
12
19
|
# If +flatten_and_compact+ is +true+, the result array will be flattened
|
13
20
|
# and compacted. The default is +true+.
|
14
|
-
def take_and_map(flatten_and_compact = true)
|
21
|
+
def take_and_map(pattern = nil, flatten_and_compact = true)
|
15
22
|
took = []
|
16
23
|
reject! do |x|
|
24
|
+
next if pattern and !File.fnmatch?(pattern, x)
|
17
25
|
if result = yield(x)
|
18
26
|
took << result
|
19
27
|
end
|
data/lib/kicker/growl.rb
CHANGED
@@ -1,24 +1,59 @@
|
|
1
1
|
require 'growlnotifier/growl_helpers'
|
2
2
|
|
3
3
|
class Kicker
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
4
|
+
module Growl #:nodoc:
|
5
|
+
NOTIFICATIONS = {
|
6
|
+
:change => 'Change occured',
|
7
|
+
:succeeded => 'Command succeeded',
|
8
|
+
:failed => 'Command failed'
|
9
|
+
}
|
10
|
+
|
11
|
+
DEFAULT_CALLBACK = lambda do
|
12
|
+
OSX::NSWorkspace.sharedWorkspace.launchApplication('Terminal')
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
include ::Growl
|
17
|
+
attr_accessor :use, :command
|
18
|
+
|
19
|
+
Growl.use = true
|
20
|
+
Growl.command = nil
|
21
|
+
|
22
|
+
def use?
|
23
|
+
@use
|
24
|
+
end
|
25
|
+
|
26
|
+
def notifications
|
27
|
+
NOTIFICATIONS
|
28
|
+
end
|
29
|
+
|
30
|
+
def start!
|
31
|
+
::Growl::Notifier.sharedInstance.register('Kicker', NOTIFICATIONS.values)
|
32
|
+
end
|
33
|
+
|
34
|
+
def change_occured(status)
|
35
|
+
growl(notifications[:change], 'Kicker: Executing', status.call(:growl) || status.command)
|
36
|
+
end
|
37
|
+
|
38
|
+
def command_callback
|
39
|
+
lambda { system(command) } if command
|
40
|
+
end
|
41
|
+
|
42
|
+
def result(status)
|
43
|
+
status.success? ? succeeded(status) : failed(status)
|
44
|
+
end
|
45
|
+
|
46
|
+
def succeeded(status)
|
47
|
+
callback = command_callback || DEFAULT_CALLBACK
|
48
|
+
body = status.call(:growl) || (Kicker.silent? ? '' : status.output)
|
49
|
+
growl(notifications[:succeeded], "Kicker: Success", body, &callback)
|
50
|
+
end
|
51
|
+
|
52
|
+
def failed(status)
|
53
|
+
message = "Kicker: Failed (#{status.exit_code})"
|
54
|
+
body = status.call(:growl) || (Kicker.silent? ? '' : status.output)
|
55
|
+
growl(notifications[:failed], message, body, &DEFAULT_CALLBACK)
|
56
|
+
end
|
57
|
+
end
|
23
58
|
end
|
24
59
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Kicker
|
2
|
+
class LogStatusHelper
|
3
|
+
attr_reader :command, :output, :exit_code
|
4
|
+
|
5
|
+
def initialize(proc, command)
|
6
|
+
@proc, @command, @output, @success = proc, command
|
7
|
+
end
|
8
|
+
|
9
|
+
def result(output, success, exit_code)
|
10
|
+
@output, @success, @exit_code = output, success, exit_code
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(logger_type)
|
14
|
+
@logger_type = logger_type
|
15
|
+
@proc.call(self) if @proc
|
16
|
+
end
|
17
|
+
|
18
|
+
def stdout?
|
19
|
+
@logger_type == :stdout
|
20
|
+
end
|
21
|
+
|
22
|
+
def growl?
|
23
|
+
@logger_type == :growl
|
24
|
+
end
|
25
|
+
|
26
|
+
def before?
|
27
|
+
@output.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
def after?
|
31
|
+
!before?
|
32
|
+
end
|
33
|
+
|
34
|
+
def success?
|
35
|
+
@success
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/kicker/options.rb
CHANGED
@@ -1,49 +1,74 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
|
3
3
|
class Kicker
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@option_parser ||= OptionParser.new do |opt|
|
14
|
-
opt.banner = "Usage: #{$0} [options] [paths to watch]"
|
4
|
+
class << self
|
5
|
+
attr_accessor :latency, :paths, :silent, :quiet
|
6
|
+
|
7
|
+
def silent?
|
8
|
+
@silent
|
9
|
+
end
|
10
|
+
|
11
|
+
def quiet?
|
12
|
+
@quiet
|
15
13
|
end
|
16
14
|
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
self.latency = 1
|
17
|
+
self.paths = %w{ . }
|
18
|
+
self.silent = false
|
19
|
+
self.quiet = false
|
20
|
+
|
21
|
+
module Options #:nodoc:
|
22
|
+
DONT_SHOW_RECIPES = %w{ could_not_handle_file execute_cli_command dot_kick }
|
22
23
|
|
23
|
-
|
24
|
-
|
24
|
+
def self.recipes_for_display
|
25
|
+
Kicker::Recipes.recipe_files.map { |f| File.basename(f, '.rb') } - DONT_SHOW_RECIPES
|
25
26
|
end
|
26
27
|
|
27
|
-
|
28
|
-
|
28
|
+
def self.parser
|
29
|
+
@parser ||= OptionParser.new do |opt|
|
30
|
+
opt.banner = "Usage: #{$0} [options] [paths to watch]"
|
31
|
+
opt.separator " "
|
32
|
+
opt.separator " Available recipes: #{recipes_for_display.join(", ")}."
|
33
|
+
opt.separator " "
|
34
|
+
|
35
|
+
opt.on('-s', '--silent', 'Keep output to a minimum.') do |silent|
|
36
|
+
Kicker.silent = true
|
37
|
+
end
|
38
|
+
|
39
|
+
opt.on('-q', '--quiet', "Quiet output. Don't print timestamps when logging.") do |quiet|
|
40
|
+
Kicker.silent = Kicker.quiet = true
|
41
|
+
end
|
42
|
+
|
43
|
+
opt.on('--[no-]growl', 'Whether or not to use Growl. Default is to use growl.') do |growl|
|
44
|
+
Kicker::Growl.use = growl
|
45
|
+
end
|
46
|
+
|
47
|
+
opt.on('--growl-command [COMMAND]', 'The command to execute when the Growl succeeded message is clicked.') do |command|
|
48
|
+
Kicker::Growl.command = command
|
49
|
+
end
|
50
|
+
|
51
|
+
opt.on('-l', '--latency [FLOAT]', "The time to collect file change events before acting on them. Defaults to #{Kicker.latency} second.") do |latency|
|
52
|
+
Kicker.latency = Float(latency)
|
53
|
+
end
|
54
|
+
|
55
|
+
opt.on('-r', '--recipe [NAME]', 'A named recipe to load.') do |name|
|
56
|
+
recipe(name)
|
57
|
+
end
|
58
|
+
end
|
29
59
|
end
|
30
60
|
|
31
|
-
|
32
|
-
(
|
61
|
+
def self.parse(argv)
|
62
|
+
parser.parse!(argv)
|
63
|
+
Kicker.paths = argv unless argv.empty?
|
33
64
|
end
|
34
|
-
|
35
|
-
option_parser.separator " "
|
36
|
-
option_parser.separator " Available recipes:"
|
37
|
-
Kicker.recipes_for_display.each { |recipe| option_parser.separator " - #{recipe}" }
|
38
|
-
|
39
|
-
option_parser
|
40
65
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
66
|
+
end
|
67
|
+
|
68
|
+
module Kernel
|
69
|
+
# Returns the global OptionParser instance that recipes can use to add
|
70
|
+
# options.
|
71
|
+
def options
|
72
|
+
Kicker::Options.parser
|
48
73
|
end
|
49
74
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
RECIPES_DIR = File.expand_path('../recipes', __FILE__)
|
2
|
+
USER_RECIPES_DIR = File.expand_path('~/.kick')
|
3
|
+
|
4
|
+
$:.unshift(RECIPES_DIR)
|
5
|
+
$:.unshift(USER_RECIPES_DIR) if File.exist?(USER_RECIPES_DIR)
|
6
|
+
|
7
|
+
module Kernel
|
8
|
+
# If only given a <tt>name</tt>, the specified recipe will be loaded. For
|
9
|
+
# instance, the following, in a <tt>.kick</tt> file, will load the Rails
|
10
|
+
# recipe:
|
11
|
+
#
|
12
|
+
# recipe :rails
|
13
|
+
#
|
14
|
+
# However, this same method is used to define a callback that is called _if_
|
15
|
+
# the recipe is loaded. For instance, the following, in a recipe file, will
|
16
|
+
# be called if the recipe is actually used:
|
17
|
+
#
|
18
|
+
# recipe :rails do
|
19
|
+
# # Load anything needed for the recipe.
|
20
|
+
# process do
|
21
|
+
# # ...
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
def recipe(name, &block)
|
25
|
+
Kicker::Recipes.recipe(name, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Kicker
|
30
|
+
module Recipes #:nodoc:
|
31
|
+
class << self
|
32
|
+
def recipes
|
33
|
+
@recipes ||= {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def recipe(name, &block)
|
37
|
+
name = name.to_sym
|
38
|
+
if block_given?
|
39
|
+
recipes[name] = block
|
40
|
+
else
|
41
|
+
if recipe = recipes[name]
|
42
|
+
recipe.call
|
43
|
+
else
|
44
|
+
raise LoadError, "Recipe `#{name}' does not exist."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def recipe_files
|
50
|
+
Dir.glob(File.join(RECIPES_DIR, '*.rb')) + Dir.glob(File.join(USER_RECIPES_DIR, '*.rb'))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# We don't want this option to show up at the end
|
55
|
+
require 'execute_cli_command'
|
56
|
+
recipe_files.each { |file| require file }
|
57
|
+
end
|
58
|
+
end
|
@@ -6,15 +6,21 @@ module ReloadDotKick #:nodoc
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def call(files)
|
9
|
-
if files.delete('.kick')
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
reset! if files.delete('.kick')
|
10
|
+
end
|
11
|
+
|
12
|
+
def use?
|
13
|
+
File.exist?('.kick')
|
14
|
+
end
|
15
|
+
|
16
|
+
def load!
|
17
|
+
load '.kick'
|
13
18
|
end
|
14
19
|
|
15
20
|
def reset!
|
16
21
|
remove_loaded_features!
|
17
22
|
reset_chains!
|
23
|
+
load!
|
18
24
|
end
|
19
25
|
|
20
26
|
def reset_chains!
|
@@ -32,4 +38,10 @@ module ReloadDotKick #:nodoc
|
|
32
38
|
end
|
33
39
|
end
|
34
40
|
|
35
|
-
|
41
|
+
if ReloadDotKick.use?
|
42
|
+
startup do
|
43
|
+
pre_process ReloadDotKick
|
44
|
+
ReloadDotKick.save_state
|
45
|
+
ReloadDotKick.load!
|
46
|
+
end
|
47
|
+
end
|
@@ -31,9 +31,11 @@ module Kernel
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
ignore(
|
38
|
-
ignore(
|
39
|
-
ignore(
|
34
|
+
recipe :ignore do
|
35
|
+
pre_process Ignore
|
36
|
+
|
37
|
+
ignore("tmp")
|
38
|
+
ignore(/\w+\.log/)
|
39
|
+
ignore(/\.(svn|git)\//)
|
40
|
+
ignore("svn-commit.tmp")
|
41
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
recipe :jstest do
|
2
|
+
process do |files|
|
3
|
+
test_files = files.take_and_map do |file|
|
4
|
+
if file =~ %r{^(test|public)/javascripts/(\w+?)(_test)*\.(js|html)$}
|
5
|
+
"test/javascripts/#{$2}_test.html"
|
6
|
+
end
|
5
7
|
end
|
8
|
+
execute "jstest #{test_files.join(' ')}" unless test_files.empty?
|
6
9
|
end
|
7
|
-
execute "jstest #{test_files.join(' ')}" unless test_files.empty?
|
8
10
|
end
|
data/lib/kicker/recipes/rails.rb
CHANGED
@@ -1,54 +1,119 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
when "concerns"
|
8
|
-
"unit/concerns"
|
9
|
-
when "controllers", "views"
|
10
|
-
"functional"
|
11
|
-
when "helpers"
|
12
|
-
"unit/helpers"
|
1
|
+
# Need to define these modules, because AS breaks if these aren't defined. Need to fix that in AS...
|
2
|
+
module ActiveSupport #:nodoc:
|
3
|
+
module CoreExtensions #:nodoc:
|
4
|
+
module String #:nodoc:
|
5
|
+
module Inflections #:nodoc:
|
6
|
+
end
|
13
7
|
end
|
14
8
|
end
|
15
|
-
|
16
|
-
# Returns an array consiting of all functional tests.
|
17
|
-
def self.all_functional_tests
|
18
|
-
Dir.glob("test/functional/**/*_test.rb")
|
19
|
-
end
|
20
9
|
end
|
21
10
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
when 'config/routes.rb'
|
31
|
-
Rails.all_functional_tests
|
32
|
-
|
33
|
-
# Match lib/*
|
34
|
-
when /^(lib\/.+)\.rb$/
|
35
|
-
"test/#{$1}_test.rb"
|
11
|
+
require 'ruby'
|
12
|
+
|
13
|
+
class Rails < Ruby
|
14
|
+
class << self
|
15
|
+
# Call these options on the Ruby class which takes the cli options.
|
16
|
+
%w{ test_type runner_bin test_cases_root test_options }.each do |delegate|
|
17
|
+
define_method(delegate) { Ruby.send(delegate) }
|
18
|
+
end
|
36
19
|
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
20
|
+
# Maps +type+, for instance `models', to a test directory.
|
21
|
+
def type_to_test_dir(type)
|
22
|
+
if test_type == 'test'
|
23
|
+
case type
|
24
|
+
when "models"
|
25
|
+
"unit"
|
26
|
+
when "concerns"
|
27
|
+
"unit/concerns"
|
28
|
+
when "controllers", "views"
|
29
|
+
"functional"
|
30
|
+
when "helpers"
|
31
|
+
"unit/helpers"
|
32
|
+
end
|
33
|
+
elsif test_type == 'spec'
|
34
|
+
case type
|
35
|
+
when "models"
|
36
|
+
"models"
|
37
|
+
when "concerns"
|
38
|
+
"models/concerns"
|
39
|
+
when "controllers", "views"
|
40
|
+
"controllers"
|
41
|
+
when "helpers"
|
42
|
+
"helpers"
|
45
43
|
end
|
46
|
-
|
47
|
-
test_file = File.join("test", dir, namespace, "#{file}_test.rb")
|
48
|
-
test_file if File.exist?(test_file)
|
49
44
|
end
|
50
45
|
end
|
46
|
+
|
47
|
+
# Returns an array consiting of all controller tests.
|
48
|
+
def all_controller_tests
|
49
|
+
if test_type == 'test'
|
50
|
+
Dir.glob("#{test_cases_root}/functional/**/*_test.rb")
|
51
|
+
else
|
52
|
+
Dir.glob("#{test_cases_root}/controllers/**/*_spec.rb")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns an array of all tests related to the given model.
|
58
|
+
def tests_for_model(model)
|
59
|
+
if test_type == 'test'
|
60
|
+
%W{
|
61
|
+
unit/#{model.singularize}
|
62
|
+
unit/helpers/#{model.pluralize}_helper
|
63
|
+
functional/#{model.pluralize}_controller
|
64
|
+
}
|
65
|
+
else
|
66
|
+
%W{
|
67
|
+
models/#{model.singularize}
|
68
|
+
helpers/#{model.pluralize}_helper
|
69
|
+
controllers/#{model.pluralize}_controller
|
70
|
+
}
|
71
|
+
end.map { |f| test_file f }
|
72
|
+
end
|
73
|
+
|
74
|
+
def handle!
|
75
|
+
@tests.concat(@files.take_and_map do |file|
|
76
|
+
case file
|
77
|
+
# Run all functional tests when routes.rb is saved
|
78
|
+
when 'config/routes.rb'
|
79
|
+
Rails.all_controller_tests
|
80
|
+
|
81
|
+
# Match lib/*
|
82
|
+
when /^(lib\/.+)\.rb$/
|
83
|
+
test_file($1)
|
84
|
+
|
85
|
+
# Map fixtures to their related tests
|
86
|
+
when %r{^#{test_cases_root}/fixtures/(\w+)\.yml$}
|
87
|
+
tests_for_model($1)
|
88
|
+
|
89
|
+
# Match any file in app/ and map it to a test file
|
90
|
+
when %r{^app/(\w+)([\w/]*)/([\w\.]+)\.\w+$}
|
91
|
+
type, namespace, file = $1, $2, $3
|
92
|
+
|
93
|
+
if dir = Rails.type_to_test_dir(type)
|
94
|
+
if type == "views"
|
95
|
+
namespace = namespace.split('/')[1..-1]
|
96
|
+
file = "#{namespace.pop}_controller"
|
97
|
+
end
|
98
|
+
|
99
|
+
test_file File.join(dir, namespace, file)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end)
|
103
|
+
|
104
|
+
# And let the Ruby handler match other stuff.
|
105
|
+
super
|
51
106
|
end
|
107
|
+
end
|
108
|
+
|
109
|
+
recipe :rails do
|
110
|
+
require 'rubygems' rescue LoadError
|
111
|
+
require 'active_support/core_ext/string'
|
112
|
+
|
113
|
+
process Rails
|
52
114
|
|
53
|
-
|
115
|
+
# When changing the schema, prepare the test database.
|
116
|
+
process do |files|
|
117
|
+
execute 'rake db:test:prepare' if files.delete('db/schema.rb')
|
118
|
+
end
|
54
119
|
end
|