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
@@ -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?