kicker 3.0.0pre1 → 3.0.0pre2

Sign up to get free protection for your applications and to get access to all the features.
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