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
@@ -11,6 +11,11 @@ Meet king kikker, kicking stuff in your computers is his dream come true!
11
11
  <i>Drawing by Manfred Stienstra. The character is purely fictional, so if you
12
12
  feel offended; live with it.</i>
13
13
 
14
+ == Ruby Version support
15
+ Beginning with 3.0.0 we are dropping Ruby 1.8 support.
16
+ We are not running tests automatically so nothing is guaranteed.
17
+ But it still might work. If it does not work for you. Use latest 2.x release.
18
+
14
19
  == Installation
15
20
 
16
21
  $ gem install kicker -s http://gemcutter.org
@@ -18,9 +23,9 @@ feel offended; live with it.</i>
18
23
  == The short version
19
24
 
20
25
  Usage: ./bin/kicker [options] [paths to watch]
21
-
26
+
22
27
  Available recipes: ignore, jstest, rails, ruby.
23
-
28
+
24
29
  -s, --silent Keep output to a minimum.
25
30
  -q, --quiet Quiet output. Don't print timestamps when logging.
26
31
  -c, --clear Clear console before each run.
@@ -64,7 +69,8 @@ A few recipes come shipped with Kicker:
64
69
  HeadlessSquirrel[http://github.com/Fingertips/Headless-squirrel].
65
70
  * Ignore, ignores logs, tmp, and svn and git files.
66
71
 
67
- Add your own shared recipes to <tt>~/.kick</tt>.
72
+ Add your own shared recipes to <tt>~/.kick</tt> folder or
73
+ current working directory <tt>.kick</tt>.
68
74
 
69
75
  === Project specific handlers
70
76
 
@@ -110,7 +116,7 @@ be added:
110
116
  test_files = files.take_and_map do |file|
111
117
  if path =~ %r{^app/views/mailer/\w+\.erb$}
112
118
  'test/unit/mailer_test.rb'
113
-
119
+
114
120
  # elsif ... handle more app specific stuff
115
121
  end
116
122
  end
@@ -2,6 +2,7 @@ require 'kicker/version'
2
2
  require 'kicker/fsevents'
3
3
  require 'kicker/callback_chain'
4
4
  require 'kicker/core_ext'
5
+ require 'kicker/job'
5
6
  require 'kicker/notification'
6
7
  require 'kicker/options'
7
8
  require 'kicker/utils'
@@ -10,43 +11,49 @@ require 'kicker/recipes'
10
11
  class Kicker #:nodoc:
11
12
  def self.run(argv = ARGV)
12
13
  Kicker::Options.parse(argv)
13
- new.start
14
+ new.start.loop!
14
15
  end
15
-
16
+
16
17
  attr_reader :last_event_processed_at
17
-
18
+
18
19
  def initialize
19
20
  finished_processing!
20
21
  end
21
-
22
+
22
23
  def paths
23
24
  @paths ||= Kicker.paths.map { |path| File.expand_path(path) }
24
25
  end
25
-
26
+
26
27
  def start
27
28
  validate_options!
28
-
29
+
29
30
  log "Watching for changes on: #{paths.join(', ')}"
30
31
  log ''
31
-
32
+
32
33
  run_startup_chain
33
34
  run_watch_dog!
35
+
36
+ self
37
+ end
38
+
39
+ def loop!
40
+ (Thread.list - [Thread.current, Thread.main]).each(&:join)
34
41
  end
35
-
42
+
36
43
  private
37
-
44
+
38
45
  def validate_options!
39
46
  validate_paths_and_command!
40
47
  validate_paths_exist!
41
48
  end
42
-
49
+
43
50
  def validate_paths_and_command!
44
51
  if startup_chain.empty? && process_chain.empty? && pre_process_chain.empty?
45
52
  puts Kicker::Options.parser.help
46
53
  exit
47
54
  end
48
55
  end
49
-
56
+
50
57
  def validate_paths_exist!
51
58
  paths.each do |path|
52
59
  unless File.exist?(path)
@@ -55,28 +62,27 @@ class Kicker #:nodoc:
55
62
  end
56
63
  end
57
64
  end
58
-
65
+
59
66
  def run_watch_dog!
60
67
  dirs = @paths.map { |path| File.directory?(path) ? path : File.dirname(path) }
61
68
  watch_dog = Kicker::FSEvents.start_watching(dirs, :latency => self.class.latency) do |events|
62
69
  process events
63
70
  end
64
-
65
71
  trap('INT') do
66
72
  log "Exiting ..."
67
73
  watch_dog.stop
68
74
  exit
69
75
  end
70
76
  end
71
-
77
+
72
78
  def run_startup_chain
73
79
  startup_chain.call([], false)
74
80
  end
75
-
81
+
76
82
  def finished_processing!
77
83
  @last_event_processed_at = Time.now
78
84
  end
79
-
85
+
80
86
  def process(events)
81
87
  unless (files = changed_files(events)).empty?
82
88
  Utils.should_clear_screen = true
@@ -84,25 +90,25 @@ class Kicker #:nodoc:
84
90
  finished_processing!
85
91
  end
86
92
  end
87
-
93
+
88
94
  def changed_files(events)
89
95
  make_paths_relative(events.map do |event|
90
96
  files_in_directory(event.path).select { |file| file_changed_since_last_event? file }
91
97
  end.flatten.uniq.sort)
92
98
  end
93
-
99
+
94
100
  def files_in_directory(dir)
95
101
  Dir.entries(dir).sort[2..-1].map { |f| File.join(dir, f) }
96
102
  rescue Errno::ENOENT
97
103
  []
98
104
  end
99
-
105
+
100
106
  def file_changed_since_last_event?(file)
101
107
  File.mtime(file) > @last_event_processed_at
102
108
  rescue Errno::ENOENT
103
109
  false
104
110
  end
105
-
111
+
106
112
  def make_paths_relative(files)
107
113
  return files if files.empty?
108
114
  wd = Dir.pwd
@@ -2,7 +2,7 @@ class Kicker
2
2
  class CallbackChain < Array #:nodoc:
3
3
  alias_method :append_callback, :push
4
4
  alias_method :prepend_callback, :unshift
5
-
5
+
6
6
  def call(files, stop_when_empty = true)
7
7
  each do |callback|
8
8
  break if stop_when_empty and files.empty?
@@ -10,50 +10,50 @@ class Kicker
10
10
  end
11
11
  end
12
12
  end
13
-
13
+
14
14
  class << self
15
15
  attr_writer :startup_chain
16
16
  def startup_chain
17
17
  @startup_chain ||= CallbackChain.new
18
18
  end
19
-
19
+
20
20
  attr_writer :pre_process_chain
21
21
  def pre_process_chain
22
22
  @pre_process_chain ||= CallbackChain.new
23
23
  end
24
-
24
+
25
25
  attr_writer :process_chain
26
26
  def process_chain
27
27
  @process_chain ||= CallbackChain.new
28
28
  end
29
-
29
+
30
30
  attr_writer :post_process_chain
31
31
  def post_process_chain
32
32
  @post_process_chain ||= CallbackChain.new
33
33
  end
34
-
34
+
35
35
  attr_writer :full_chain
36
36
  def full_chain
37
37
  @full_chain ||= CallbackChain.new([pre_process_chain, process_chain, post_process_chain])
38
38
  end
39
39
  end
40
-
40
+
41
41
  def startup_chain
42
42
  self.class.startup_chain
43
43
  end
44
-
44
+
45
45
  def pre_process_chain
46
46
  self.class.pre_process_chain
47
47
  end
48
-
48
+
49
49
  def process_chain
50
50
  self.class.process_chain
51
51
  end
52
-
52
+
53
53
  def post_process_chain
54
54
  self.class.post_process_chain
55
55
  end
56
-
56
+
57
57
  def full_chain
58
58
  self.class.full_chain
59
59
  end
@@ -68,7 +68,7 @@ module Kernel
68
68
  def startup(callback = nil, &block)
69
69
  Kicker.startup_chain.append_callback(block ? block : callback)
70
70
  end
71
-
71
+
72
72
  # Adds a handler to the pre_process chain. This chain is ran before the
73
73
  # process chain and is processed from first to last.
74
74
  #
@@ -76,7 +76,7 @@ module Kernel
76
76
  def pre_process(callback = nil, &block)
77
77
  Kicker.pre_process_chain.append_callback(block ? block : callback)
78
78
  end
79
-
79
+
80
80
  # Adds a handler to the process chain. This chain is ran in between the
81
81
  # pre_process and post_process chains. It is processed from first to last.
82
82
  #
@@ -84,7 +84,7 @@ module Kernel
84
84
  def process(callback = nil, &block)
85
85
  Kicker.process_chain.append_callback(block ? block : callback)
86
86
  end
87
-
87
+
88
88
  # Adds a handler to the post_process chain. This chain is ran after the
89
89
  # process chain and is processed from last to first.
90
90
  #
@@ -35,4 +35,4 @@ class Kicker
35
35
  end
36
36
  end
37
37
 
38
- Array.send(:include, Kicker::ArrayExt)
38
+ Array.send(:include, Kicker::ArrayExt)
@@ -6,11 +6,11 @@ class Kicker
6
6
  class FSEvents
7
7
  class FSEvent
8
8
  attr_reader :path
9
-
9
+
10
10
  def initialize(path)
11
11
  @path = path
12
12
  end
13
-
13
+
14
14
  def files
15
15
  Dir.glob("#{File.expand_path(path)}/*").map do |filename|
16
16
  begin
@@ -21,14 +21,16 @@ class Kicker
21
21
  end.compact.sort.reverse.map { |_, filename| filename }
22
22
  end
23
23
  end
24
-
24
+
25
25
  def self.start_watching(paths, options={}, &block)
26
26
  listener = Listen.to(*(paths.dup << options))
27
27
  listener.change do |modified, added, removed|
28
28
  files = modified + added + removed
29
29
  directories = files.map { |file| File.dirname(file) }.uniq
30
30
  yield directories.map { |directory| Kicker::FSEvents::FSEvent.new(directory) }
31
- end.start
31
+ end
32
+ listener.start
33
+ listener
32
34
  end
33
35
  end
34
36
  end
@@ -0,0 +1,57 @@
1
+ class Kicker
2
+ class Job
3
+ def self.attr_with_default(name, merge_hash = false, &default)
4
+ # If `nil` this returns the `default`, unless explicitely set to `nil` by
5
+ # the user.
6
+ define_method(name) do
7
+ if instance_variable_get("@#{name}_assigned")
8
+ if assigned_value = instance_variable_get("@#{name}")
9
+ merge_hash ? instance_eval(&default).merge(assigned_value) : assigned_value
10
+ end
11
+ else
12
+ instance_eval(&default)
13
+ end
14
+ end
15
+ define_method("#{name}=") do |value|
16
+ instance_variable_set("@#{name}_assigned", true)
17
+ instance_variable_set("@#{name}", value)
18
+ end
19
+ end
20
+
21
+ attr_accessor :command, :exit_code, :output
22
+
23
+ def initialize(attributes)
24
+ @exit_code = 0
25
+ @output = ''
26
+ attributes.each { |k,v| send("#{k}=", v) }
27
+ end
28
+
29
+ def success?
30
+ exit_code == 0
31
+ end
32
+
33
+ attr_with_default(:print_before) do
34
+ "Executing: #{command}"
35
+ end
36
+
37
+ attr_with_default(:print_after) do
38
+ # Show all output if it wasn't shown before and the command fails.
39
+ "\n#{output}\n\n" if Kicker.silent? && !success?
40
+ end
41
+
42
+ # TODO default titles??
43
+
44
+ attr_with_default(:notify_before, true) do
45
+ { :title => "Kicker: Executing", :message => command }
46
+ end
47
+
48
+ attr_with_default(:notify_after, true) do
49
+ message = Kicker.silent? ? "" : output
50
+ if success?
51
+ { :title => "Kicker: Success", :message => message }
52
+ else
53
+ { :title => "Kicker: Failed (#{exit_code})", :message => message }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,37 +1,26 @@
1
+ require 'notify'
2
+
1
3
  class Kicker
2
4
  module Notification #:nodoc:
3
- vendor = File.expand_path('../../../vendor', __FILE__)
4
- TERMINAL_NOTIFICATION_BIN = File.join(vendor, 'terminal-notifier_v1.0/terminal-notifier.app/Contents/MacOS/terminal-notifier')
5
+ TITLE = 'Kicker'
5
6
 
6
7
  class << self
7
8
  attr_accessor :use, :app_bundle_identifier
8
9
  alias_method :use?, :use
9
10
 
10
- def usable?
11
- @usable ||= `uname`.strip == 'Darwin' && `sw_vers -productVersion`.strip >= '10.8'
12
- end
13
-
14
- def change_occured(status)
15
- notify('Kicker: Executing', status.command)
16
- end
17
-
18
- def result(status)
19
- status.success? ? succeeded(status) : failed(status)
20
- end
11
+ def notify(options)
12
+ return unless use?
21
13
 
22
- def succeeded(status)
23
- body = Kicker.silent? ? '' : status.output
24
- notify('Kicker: Success', body)
25
- end
14
+ unless message = options.delete(:message)
15
+ raise "A notification requires a `:message'"
16
+ end
26
17
 
27
- def failed(status)
28
- message = "Kicker: Failed (#{status.exit_code})"
29
- body = Kicker.silent? ? '' : status.output
30
- notify(message, body)
31
- end
18
+ options = {
19
+ :group => Dir.pwd,
20
+ :activate => app_bundle_identifier
21
+ }.merge(options)
32
22
 
33
- def notify(title, message)
34
- `'#{TERMINAL_NOTIFICATION_BIN}' #{Dir.pwd} '#{title}' '#{message}' '#{app_bundle_identifier}'`
23
+ Notify.notify(TITLE, message, options)
35
24
  end
36
25
  end
37
26
  end
@@ -3,79 +3,83 @@ require 'optparse'
3
3
  class Kicker
4
4
  class << self
5
5
  attr_accessor :latency, :paths, :silent, :quiet, :clear_console
6
-
6
+
7
7
  def silent?
8
8
  @silent
9
9
  end
10
-
10
+
11
11
  def quiet?
12
12
  @quiet
13
13
  end
14
-
14
+
15
15
  def clear_console?
16
16
  @clear_console
17
17
  end
18
+
19
+
20
+ def osx?
21
+ RUBY_PLATFORM.downcase.include?("darwin")
22
+ end
18
23
  end
19
-
24
+
20
25
  self.latency = 1
21
26
  self.paths = %w{ . }
22
27
  self.silent = false
23
28
  self.quiet = false
24
29
  self.clear_console = false
25
-
30
+
26
31
  module Options #:nodoc:
27
32
  DONT_SHOW_RECIPES = %w{ could_not_handle_file execute_cli_command dot_kick }
28
-
33
+
29
34
  def self.recipes_for_display
30
35
  Kicker::Recipes.recipe_files.map { |f| File.basename(f, '.rb') } - DONT_SHOW_RECIPES
31
36
  end
32
-
37
+
33
38
  def self.parser
34
39
  @parser ||= OptionParser.new do |opt|
35
40
  opt.banner = "Usage: #{$0} [options] [paths to watch]"
36
41
  opt.separator " "
37
42
  opt.separator " Available recipes: #{recipes_for_display.join(", ")}."
38
43
  opt.separator " "
39
-
44
+
40
45
  opt.on('-v', 'Print the Kicker version') do
41
- puts VERSION
46
+ puts Kicker::VERSION
42
47
  exit
43
48
  end
44
49
 
45
50
  opt.on('-s', '--silent', 'Keep output to a minimum.') do |silent|
46
51
  Kicker.silent = true
47
52
  end
48
-
53
+
49
54
  opt.on('-q', '--quiet', "Quiet output. Don't print timestamps when logging.") do |quiet|
50
55
  Kicker.silent = Kicker.quiet = true
51
56
  end
52
-
57
+
53
58
  opt.on('-c', '--clear', "Clear console before each run.") do |clear|
54
59
  Kicker.clear_console = true
55
60
  end
56
-
57
- if Notification.usable?
58
- opt.on('--[no-]notification', 'Whether or not to send user notifications (on Mac OS X). Defaults to enabled.') do |notifications|
59
- Notification.use = notifications
60
- end
61
-
61
+
62
+
63
+ opt.on('--[no-]notification', 'Whether or not to send user notifications (on Mac OS X). Defaults to enabled.') do |notifications|
64
+ Notification.use = notifications
65
+ end
66
+
67
+ if Kicker.osx?
62
68
  opt.on('--activate-app [BUNDLE ID]', "The application to activate when a notification is clicked. Defaults to `com.apple.Terminal'.") do |bundle_id|
63
69
  Kicker::Notification.app_bundle_identifier = bundle_id
64
70
  end
65
- else
66
- Kicker::Notification.use = false
67
71
  end
68
-
72
+
69
73
  opt.on('-l', '--latency [FLOAT]', "The time to collect file change events before acting on them. Defaults to #{Kicker.latency} second.") do |latency|
70
74
  Kicker.latency = Float(latency)
71
75
  end
72
-
76
+
73
77
  opt.on('-r', '--recipe [NAME]', 'A named recipe to load.') do |name|
74
78
  recipe(name)
75
79
  end
76
80
  end
77
81
  end
78
-
82
+
79
83
  def self.parse(argv)
80
84
  parser.parse!(argv)
81
85
  Kicker.paths = argv unless argv.empty?