kicker 3.0.0pre1 → 3.0.0pre2

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.
Files changed (28) hide show
  1. data/README.rdoc +10 -4
  2. data/lib/kicker.rb +26 -20
  3. data/lib/kicker/callback_chain.rb +14 -14
  4. data/lib/kicker/core_ext.rb +1 -1
  5. data/lib/kicker/fsevents.rb +6 -4
  6. data/lib/kicker/job.rb +57 -0
  7. data/lib/kicker/notification.rb +13 -24
  8. data/lib/kicker/options.rb +26 -22
  9. data/lib/kicker/recipes.rb +19 -16
  10. data/lib/kicker/recipes/could_not_handle_file.rb +1 -1
  11. data/lib/kicker/recipes/dot_kick.rb +8 -8
  12. data/lib/kicker/recipes/execute_cli_command.rb +1 -1
  13. data/lib/kicker/recipes/ignore.rb +4 -4
  14. data/lib/kicker/recipes/jstest.rb +1 -1
  15. data/lib/kicker/recipes/rails.rb +11 -11
  16. data/lib/kicker/recipes/ruby.rb +23 -23
  17. data/lib/kicker/utils.rb +44 -50
  18. data/lib/kicker/version.rb +1 -1
  19. metadata +108 -136
  20. data/vendor/terminal-notifier_v1.0/README.markdown +0 -70
  21. data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Info.plist +0 -52
  22. data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/MacOS/terminal-notifier +0 -0
  23. data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/PkgInfo +0 -1
  24. data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/Terminal.icns +0 -0
  25. data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/en.lproj/Credits.rtf +0 -29
  26. data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
  27. data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/Resources/en.lproj/MainMenu.nib +0 -0
  28. data/vendor/terminal-notifier_v1.0/terminal-notifier.app/Contents/_CodeSignature/CodeResources +0 -61
@@ -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 = File.join(directory, "#{name}.rb")
47
- return filename if File.exist?(filename)
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| File.basename(filename, '.rb').to_sym }
55
+ recipe_files.map { |filename| filename.basename('.rb').to_s.to_sym }
53
56
  end
54
-
57
+
55
58
  def recipe_files
56
- Dir.glob(File.join(RECIPES_DIR, '*.rb')) + Dir.glob(File.join(USER_RECIPES_DIR, '*.rb'))
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,4 +4,4 @@ post_process do |files|
4
4
  log("Could not handle: #{files.join(', ')}")
5
5
  log('')
6
6
  end
7
- end
7
+ 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
@@ -3,7 +3,7 @@ options.on('-e', '--execute [COMMAND]', 'The command to execute.') do |command|
3
3
  files.clear
4
4
  execute "sh -c #{command.inspect}"
5
5
  end
6
-
6
+
7
7
  startup callback
8
8
  pre_process callback
9
9
  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
@@ -7,4 +7,4 @@ recipe :jstest do
7
7
  end
8
8
  execute "jstest #{test_files.join(' ')}" unless test_files.empty?
9
9
  end
10
- end
10
+ end
@@ -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')
@@ -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
@@ -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(command)
17
- @last_command = command
18
- status = Status.new(command, 0, '')
19
- will_execute_command(status)
20
- yield status
21
- did_execute_command(status)
22
- status
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 last_command
33
- @last_command
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(status)
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(status.command) do |io|
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
- status.output = output.strip
75
- status.exit_code = last_command_status
76
- status
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(status)
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
- log "Executing: #{status.command}"
99
- Notification.change_occured(status) if Notification.use? && !Kicker.silent?
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(status)
103
- puts("\n#{status.output}\n\n") if Kicker.silent? && !status.success?
104
- log(status.success? ? "Success" : "Failed (#{status.exit_code})")
105
- Notification.result(status) if Notification.use?
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