guard 2.13.0 → 2.14.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0c6b3428b071bd5e34a0b9a05235d5814f7fe190
4
- data.tar.gz: 19dae3ff061b4d697789e01dcfd3a4558047672e
3
+ metadata.gz: ee12e6183bd7f80c82c6c173e73da077423efd8b
4
+ data.tar.gz: 658e7a0eaf51b6d4ba16cf8126f1cebe9ef75e10
5
5
  SHA512:
6
- metadata.gz: f539620c78fa10c40df9b8fb11bab2519772b6f2ad1c3839c964f1e2efda97e0ab10397baa591dc036552b30c77ef91e05e1901912db83a55527a01798070eb9
7
- data.tar.gz: f234c272662f566f641a3fb684d010291c809630e5b4fc6ee6fc7bb8b0a0e1c2a859e64c5d6b892b8ed1d447287bc71fe3488d1846f3874bd498599ab6a6ea59
6
+ metadata.gz: 7d40ddd8ef223df2e0bbf59791583eeaf4b460eac7a32a674e3b2d16c51ca9f8158b71b1458c605c0a3e9b078d724362443ea1f7dd73a3a85e419ff6fbc28fa8
7
+ data.tar.gz: 5fb257cf77358225a124a6b659812e78f9aea514c66578993f1b8d2dd874b7f0a174e68d3d8fda209dc515674897b72e32775f6c0d8186db36dfdfedd29e7f17
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2009-2014 Thibaud Guillaume-Gentil
3
+ Copyright (c) 2009-2016 Thibaud Guillaume-Gentil
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,14 +1,15 @@
1
- :exclamation::exclamation: Guard is [currently accepting more maintainers/contributors](https://groups.google.com/forum/#!topic/guard-dev/2Td0QTvTIsE). :exclamation::exclamation:
1
+ ## IMPORTANT: [Ruby 2.1 is officially outdated and unsupported!](https://www.ruby-lang.org/en/news/2016/03/30/ruby-2-1-9-released/) Please upgrade to Ruby 2.2 before installing Guard!
2
2
 
3
+ ### (To install for older versions, update Bundler to a prerelease version: `gem install bundler --pre` and Bundler should correctly resolve to an earlier gems for your given Ruby version).
3
4
 
4
- :exclamation::exclamation: Please [contact me](mailto:thibaud@thibaud.gg) or [Cezary Baginski](mailto:cezary@chronomantic.net) if you're interested in joining the team. :exclamation::exclamation:
5
+ :exclamation: Guard is currently accepting more maintainers. Please [read this](https://github.com/guard/guard/wiki/Maintainers) if you're interested in joining the team.
5
6
 
6
7
  Guard
7
8
  =====
8
9
 
9
10
  [![Gem Version](https://img.shields.io/gem/v/guard.svg?style=flat)](https://rubygems.org/gems/guard) [![Build Status](https://travis-ci.org/guard/guard.svg)](https://travis-ci.org/guard/guard) [![Dependency Status](https://gemnasium.com/guard/guard.png)](https://gemnasium.com/guard/guard) [![Code Climate](https://codeclimate.com/github/guard/guard/badges/gpa.svg)](https://codeclimate.com/github/guard/guard) [![Test Coverage](https://codeclimate.com/github/guard/guard/badges/coverage.svg)](https://codeclimate.com/github/guard/guard) [![Inline docs](http://inch-ci.org/github/guard/guard.svg)](http://inch-ci.org/github/guard/guard)
10
11
 
11
- <img src="http://cl.ly/image/1k3o1r2Z3a0J/guard-Icon.png" alt="Guard Icon" align="right" />
12
+ <img src="http://f.cl.ly/items/0A0M3W2x3I1P450z341U/guard-Icon.png" alt="Guard Icon" align="right" />
12
13
  Guard automates various tasks by running custom rules whenever file or directories are modified.
13
14
 
14
15
  It's frequently used by software developers, web designers, writers and other specialists to avoid mundane, repetitive actions and commands such as "relaunching" tools after changing source files or configurations.
@@ -22,7 +23,7 @@ any questions about Guard or want to share some information with the Guard commu
22
23
  the following places:
23
24
 
24
25
  * [Guard Wiki](https://github.com/guard/guard/wiki)
25
- * [Google+ community](https://plus.google.com/u/1/communities/110022199336250745477).
26
+ * [Google+ community](https://plus.google.com/communities/110022199336250745477).
26
27
  * [Google group](http://groups.google.com/group/guard-dev).
27
28
  * [StackOverflow](http://stackoverflow.com/questions/tagged/guard).
28
29
  * IRC channel `#guard` (irc.freenode.net) for chatting.
@@ -46,7 +47,7 @@ Two nice screencasts are available to help you get started:
46
47
  Installation
47
48
  ------------
48
49
 
49
- The simplest way to install Guard is to use [Bundler](http://gembundler.com/).
50
+ The simplest way to install Guard is to use [Bundler](http://bundler.io).
50
51
 
51
52
  Add Guard (and any other dependencies) to a `Gemfile` in your project’s root:
52
53
 
@@ -98,7 +99,7 @@ If you're getting sick of typing `bundle exec` all the time, try one of the foll
98
99
 
99
100
  *NOTE: this Rubygems feature is still under development still lacks many features of bundler*
100
101
 
101
- * Or, for RubyGems < 2.2.0 check out the [Rubygems Bundler](https://github.com/mpapis/rubygems-bundler).
102
+ * Or, for RubyGems < 2.2.0 check out the [Rubygems Bundler](https://github.com/rvm/rubygems-bundler).
102
103
 
103
104
  #### Add Guard plugins
104
105
 
@@ -156,26 +157,15 @@ Development / Contributing
156
157
 
157
158
  See the [Contributing Guide](https://github.com/guard/guard/blob/master/CONTRIBUTING.md#development).
158
159
 
159
-
160
- #### Open Commit Bit
161
-
162
- Guard has an open commit bit policy: Anyone with an accepted pull request gets added as a repository collaborator.
163
- Please try to follow these simple rules:
164
-
165
- * Commit directly onto the master branch only for typos, improvements to the readme and documentation (please add
166
- `[ci skip]` to the commit message).
167
- * Create a feature branch and open a pull-request early for any new features to get feedback.
168
- * Make sure you adhere to the general pull request rules above.
169
-
170
160
  #### Author
171
161
 
172
- [Thibaud Guillaume-Gentil](https://github.com/thibaudgg) ([@thibaudgg](http://twitter.com/thibaudgg))
162
+ [Thibaud Guillaume-Gentil](https://github.com/thibaudgg) ([@thibaudgg](https://twitter.com/thibaudgg))
173
163
 
174
164
  #### Core Team
175
165
 
176
- * R.I.P. :broken_heart: [Michael Kessler](https://github.com/netzpirat) ([@netzpirat](http://twitter.com/netzpirat), [flinkfinger.com](http://www.flinkfinger.com)).
166
+ * R.I.P. :broken_heart: [Michael Kessler](https://github.com/netzpirat) ([flinkfinger.com](http://www.flinkfinger.com)).
177
167
  * [Rémy Coutable](https://github.com/rymai).
178
- * [Thibaud Guillaume-Gentil](https://github.com/thibaudgg) ([@thibaudgg](http://twitter.com/thibaudgg), [thibaud.gg](http://thibaud.gg/)).
168
+ * [Thibaud Guillaume-Gentil](https://github.com/thibaudgg) ([@thibaudgg](https://twitter.com/thibaudgg), [thibaud.gg](http://thibaud.gg/)).
179
169
 
180
170
  #### Contributors
181
171
 
@@ -151,7 +151,8 @@ module Guard
151
151
  # TODO: not tested because collides with ongoing refactoring
152
152
  def _guardfile_deprecated_check(modified)
153
153
  modified.map!(&:to_s)
154
- guardfiles = modified.select { |path| /^(?:.+\/)?Guardfile$/.match(path) }
154
+ regexp = %r{^(?:.+/)?Guardfile$}
155
+ guardfiles = modified.select { |path| regexp.match(path) }
155
156
  return if guardfiles.empty?
156
157
 
157
158
  guardfile = Pathname("Guardfile").realpath
@@ -159,14 +160,10 @@ module Guard
159
160
  /^Guardfile$/.match(path) || Pathname(path).expand_path == guardfile
160
161
  end
161
162
 
162
- if real_guardfiles
163
- UI.warning "Guardfile changed -- _guard-core will exit.\n"
164
- exit 2 # nonzero to break any while loop
165
- else # e.g. templates/Guardfile
166
- msg = "Config changed: %s - Guard will exit so it can be restarted."
167
- UI.info format(msg, guardfiles.inspect)
168
- exit 0 # 0 so any shell while loop can continue
169
- end
163
+ return unless real_guardfiles
164
+
165
+ UI.warning "Guardfile changed -- _guard-core will exit.\n"
166
+ exit 2 # nonzero to break any while loop
170
167
  end
171
168
  end
172
169
  end
@@ -46,13 +46,19 @@ module Guard
46
46
  rescue Guard::Guardfile::Evaluator::NoPluginsError
47
47
  end
48
48
 
49
- if plugin_names.empty?
50
- generator.initialize_all_templates
51
- else
52
- plugin_names.each do |plugin_name|
53
- generator.initialize_template(plugin_name)
49
+ begin
50
+ if plugin_names.empty?
51
+ generator.initialize_all_templates
52
+ else
53
+ plugin_names.each do |plugin_name|
54
+ generator.initialize_template(plugin_name)
55
+ end
54
56
  end
57
+ rescue Guardfile::Generator::Error => e
58
+ UI.error(e.message)
59
+ return 1
55
60
  end
61
+
56
62
  # TODO: capture exceptions to show msg and return exit code on
57
63
  # failures
58
64
  0 # exit code
@@ -92,7 +92,7 @@ module Guard
92
92
  return if pause == paused
93
93
 
94
94
  listener.public_send(pause ? :pause : :start)
95
- UI.info "File modification listening is now #{pause.to_s.upcase}"
95
+ UI.info "File event handling has been #{pause ? 'paused' : 'resumed'}"
96
96
  end
97
97
 
98
98
  def show
@@ -29,7 +29,7 @@ module Guard
29
29
  end
30
30
 
31
31
  def reevaluate_guardfile
32
- # require guard only when needed, becuase
32
+ # require guard only when needed, because
33
33
  # guard's deprecations require us
34
34
  require "guard"
35
35
  UI.deprecation(REEVALUATE_GUARDFILE)
@@ -1,6 +1,8 @@
1
1
  require "guard/config"
2
2
  fail "Deprecations disabled (strict mode)" if Guard::Config.new.strict?
3
3
 
4
+ require "forwardable"
5
+
4
6
  require "guard/ui"
5
7
  require "guard/internals/session"
6
8
  require "guard/internals/state"
@@ -209,9 +211,9 @@ module Guard
209
211
  }
210
212
  end
211
213
 
212
- def to_a
213
- to_hash.to_a
214
- end
214
+ extend Forwardable
215
+ delegate [:to_a, :keys] => :to_hash
216
+ delegate [:include?] => :keys
215
217
 
216
218
  def fetch(key, *args)
217
219
  hash = to_hash
@@ -230,14 +232,6 @@ module Guard
230
232
  end
231
233
  end
232
234
 
233
- def keys
234
- to_hash.keys
235
- end
236
-
237
- def include?(value)
238
- keys.include? value
239
- end
240
-
241
235
  private
242
236
 
243
237
  def verify_key!(hash, key)
@@ -4,8 +4,8 @@ fail "Deprecations disabled (strict mode)" if Guard::Config.new.strict?
4
4
  module Guard
5
5
  module Deprecated
6
6
  module Watcher
7
- def self.add_deprecated(dsl_klass)
8
- dsl_klass.send(:extend, ClassMethods)
7
+ def self.add_deprecated(klass)
8
+ klass.send(:extend, ClassMethods)
9
9
  end
10
10
 
11
11
  module ClassMethods
@@ -326,7 +326,7 @@ module Guard
326
326
  options[:level] = options[:level].to_sym
327
327
 
328
328
  unless [:debug, :info, :warn, :error].include? options[:level]
329
- UI.warning WARN_INVALID_LOG_LEVEL % [options[:level]]
329
+ UI.warning(format(WARN_INVALID_LOG_LEVEL, options[:level]))
330
330
  options.delete :level
331
331
  end
332
332
  end
@@ -58,11 +58,9 @@ module Guard
58
58
  if opts[:contents]
59
59
  @type = :inline
60
60
  @contents = opts[:contents]
61
- else
62
- if opts[:guardfile]
63
- @type = :custom
64
- @path = Pathname(opts[:guardfile]) # may be updated by _read
65
- end
61
+ elsif opts[:guardfile]
62
+ @type = :custom
63
+ @path = Pathname(opts[:guardfile]) # may be updated by _read
66
64
  end
67
65
  end
68
66
 
@@ -162,7 +160,7 @@ module Guard
162
160
  source_from_option = @source.nil? && options[:guardfile_contents]
163
161
  inline = @source == :inline
164
162
 
165
- return false unless (source_from_option) || inline
163
+ return false unless source_from_option || inline
166
164
 
167
165
  @source = :inline
168
166
  @guardfile_contents = options[:guardfile_contents]
@@ -27,7 +27,8 @@ module Guard
27
27
  # The Guardfile template for `guard init`
28
28
  GUARDFILE_TEMPLATE = File.expand_path(
29
29
  "../../../guard/templates/Guardfile",
30
- __FILE__)
30
+ __FILE__
31
+ )
31
32
 
32
33
  # The location of user defined templates
33
34
  begin
@@ -38,6 +39,24 @@ module Guard
38
39
  HOME_TEMPLATES = Pathname("/").expand_path
39
40
  end
40
41
 
42
+ class Error < RuntimeError
43
+ end
44
+
45
+ class NoSuchPlugin < Error
46
+ attr_reader :plugin_name, :class_name
47
+
48
+ def initialize(plugin_name)
49
+ @plugin_name = plugin_name
50
+ @class_name = plugin_name.delete("-").capitalize
51
+ end
52
+
53
+ def message
54
+ "Could not load 'guard/#{plugin_name}'"\
55
+ " or '~/.guard/templates/#{plugin_name}'"\
56
+ " or find class Guard::#{class_name}\n"
57
+ end
58
+ end
59
+
41
60
  # Creates the initial Guardfile template when it does not
42
61
  # already exist.
43
62
  #
@@ -86,14 +105,8 @@ module Guard
86
105
 
87
106
  _ui(:info, format(INFO_TEMPLATE_ADDED, plugin_name))
88
107
 
89
- rescue Errno::ENOENT => error
90
-
91
- name = plugin_name.downcase
92
- class_name = name.gsub("-", "").capitalize
93
- _ui(:error, "Could not load 'guard/#{name}'"\
94
- " or '~/.guard/templates/#{name}'"\
95
- " or find class Guard::#{class_name}")
96
- _ui(:error, "Error is: #{error}")
108
+ rescue Errno::ENOENT
109
+ fail NoSuchPlugin, plugin_name.downcase
97
110
  end
98
111
 
99
112
  # Adds the templates of all installed Guard implementations to an
@@ -1,3 +1,5 @@
1
+ require "forwardable"
2
+
1
3
  module Guard
2
4
  class Interactor
3
5
  # Initializes the interactor. This configures
@@ -19,19 +21,8 @@ module Guard
19
21
  @interactive
20
22
  end
21
23
 
22
- # Run in foreground and wait until interrupted or closed
23
- def foreground
24
- idle_job.foreground
25
- end
26
-
27
- # Remove interactor so other tasks can run in foreground
28
- def background
29
- idle_job.background
30
- end
31
-
32
- def handle_interrupt
33
- idle_job.handle_interrupt
34
- end
24
+ extend Forwardable
25
+ delegate [:foreground, :background, :handle_interrupt] => :idle_job
35
26
 
36
27
  # TODO: everything below is just so the DSL can set options
37
28
  # before setup() is called, which makes it useless for when
@@ -13,6 +13,7 @@ module Guard
13
13
  class << self
14
14
  TRACES = [
15
15
  [Kernel, :system],
16
+ [Kernel, :spawn],
16
17
  [Kernel, :`],
17
18
  [Open3, :popen3]
18
19
  ]
@@ -28,9 +28,9 @@ module Guard
28
28
  def matcher_for(filter)
29
29
  case filter
30
30
  when String, Symbol
31
- lambda { |group| group.name == filter.to_sym }
31
+ ->(group) { group.name == filter.to_sym }
32
32
  when Regexp
33
- lambda { |group| group.name.to_s =~ filter }
33
+ ->(group) { group.name.to_s =~ filter }
34
34
  else
35
35
  fail "Invalid filter: #{filter.inspect}"
36
36
  end
@@ -31,16 +31,16 @@ module Guard
31
31
  def matcher_for(filter)
32
32
  case filter
33
33
  when String, Symbol
34
- shortname = filter.to_s.downcase.gsub("-", "")
35
- lambda { |plugin| plugin.name == shortname }
34
+ shortname = filter.to_s.downcase.delete("-")
35
+ ->(plugin) { plugin.name == shortname }
36
36
  when Regexp
37
- lambda { |plugin| plugin.name =~ filter }
37
+ ->(plugin) { plugin.name =~ filter }
38
38
  when Hash
39
39
  lambda do |plugin|
40
40
  filter.all? do |k, v|
41
41
  case k
42
42
  when :name
43
- plugin.name == v.to_s.downcase.gsub("-", "")
43
+ plugin.name == v.to_s.downcase.delete("-")
44
44
  when :group
45
45
  plugin.group.name == v.to_sym
46
46
  end
@@ -8,7 +8,8 @@ module Guard
8
8
 
9
9
  # Process the change queue, running tasks within the main Guard thread
10
10
  def process
11
- actions, changes = [], { modified: [], added: [], removed: [] }
11
+ actions = []
12
+ changes = { modified: [], added: [], removed: [] }
12
13
 
13
14
  while pending?
14
15
  if (item = @queue.pop).first.is_a?(Symbol)
@@ -24,7 +25,7 @@ module Guard
24
25
  end
25
26
 
26
27
  def pending?
27
- ! @queue.empty?
28
+ !@queue.empty?
28
29
  end
29
30
 
30
31
  def <<(changes)
@@ -156,9 +156,7 @@ module Guard
156
156
  @guardfile_notifier_options.merge!(config)
157
157
  end
158
158
 
159
- def interactor_name
160
- @interactor_name
161
- end
159
+ attr_reader :interactor_name
162
160
 
163
161
  # TODO: call this from within action, not within interactor command
164
162
  def convert_scope(entries)
@@ -160,7 +160,7 @@ module Guard
160
160
  # @param [String] plugin_location the plugin location
161
161
  #
162
162
  def self.template(plugin_location)
163
- File.read TEMPLATE_FORMAT % [plugin_location, non_namespaced_name]
163
+ File.read(format(TEMPLATE_FORMAT, plugin_location, non_namespaced_name))
164
164
  end
165
165
 
166
166
  # Called once when Guard starts. Please override initialize method to
@@ -112,9 +112,10 @@ module Guard
112
112
  # TODO: return value or move exception higher
113
113
  rescue LoadError => error
114
114
  unless options[:fail_gracefully]
115
- UI.error ERROR_NO_GUARD_OR_CLASS % [name.downcase, _constant_name]
116
- UI.error "Error is: #{error}"
117
- UI.error error.backtrace.join("\n")
115
+ msg = format(ERROR_NO_GUARD_OR_CLASS, name.downcase, _constant_name)
116
+ UI.error(msg)
117
+ UI.error("Error is: #{error}")
118
+ UI.error(error.backtrace.join("\n"))
118
119
  # TODO: return value or move exception higher
119
120
  end
120
121
  end
@@ -158,7 +159,7 @@ module Guard
158
159
  #
159
160
  def _plugin_constant
160
161
  @_plugin_constant ||= Guard.constants.detect do |c|
161
- c.to_s.downcase == _constant_name.downcase
162
+ c.to_s.casecmp(_constant_name.downcase).zero?
162
163
  end
163
164
  end
164
165
 
@@ -169,20 +170,22 @@ module Guard
169
170
  # => "Rspec"
170
171
  #
171
172
  def _constant_name
172
- @_constant_name ||= name.gsub(/\/(.?)/) { "::#{ $1.upcase }" }.
173
+ @_constant_name ||= name.gsub(%r{/(.?)}) { "::#{ $1.upcase }" }.
173
174
  gsub(/(?:^|[_-])(.)/) { $1.upcase }
174
175
  end
175
176
 
176
- def self._gem_valid?(gem)
177
- return false if gem.name == "guard-compat"
178
- return true if gem.name =~ /^guard-/
179
- full_path = gem.full_gem_path
180
- file = File.join(full_path, "lib", "guard", "#{gem.name}.rb")
181
- File.exist?(file)
182
- end
183
-
184
177
  def _full_gem_path(name)
185
178
  Gem::Specification.find_by_name(name).full_gem_path
186
179
  end
180
+
181
+ class << self
182
+ def _gem_valid?(gem)
183
+ return false if gem.name == "guard-compat"
184
+ return true if gem.name =~ /^guard-/
185
+ full_path = gem.full_gem_path
186
+ file = File.join(full_path, "lib", "guard", "#{gem.name}.rb")
187
+ File.exist?(file)
188
+ end
189
+ end
187
190
  end
188
191
  end
@@ -2,6 +2,7 @@ require "guard/ui/colors"
2
2
 
3
3
  require "guard/terminal"
4
4
  require "guard/options"
5
+ require "forwardable"
5
6
 
6
7
  # TODO: rework this class from the bottom-up
7
8
  # - remove dependency on Session and Scope
@@ -26,7 +27,8 @@ module Guard
26
27
  require "lumberjack"
27
28
  Lumberjack::Logger.new(
28
29
  options.fetch(:device) { $stderr },
29
- options)
30
+ options
31
+ )
30
32
  end
31
33
  end
32
34
 
@@ -46,7 +48,8 @@ module Guard
46
48
  @options ||= Options.new(
47
49
  level: :info,
48
50
  template: ":time - :severity - :message",
49
- time_format: "%H:%M:%S")
51
+ time_format: "%H:%M:%S"
52
+ )
50
53
  end
51
54
 
52
55
  # Set the logger options
@@ -62,9 +65,8 @@ module Guard
62
65
  end
63
66
 
64
67
  # Assigns a log level
65
- def level=(new_level)
66
- logger.level = new_level
67
- end
68
+ extend Forwardable
69
+ def_delegator :logger, :level=
68
70
 
69
71
  # Show an info message.
70
72
  #
@@ -202,7 +204,7 @@ module Guard
202
204
  # @return [String] the Guard plugin name
203
205
  #
204
206
  def calling_plugin_name(depth = 2)
205
- name = /(guard\/[a-z_]*)(\/[a-z_]*)?.rb:/i.match(caller[depth])
207
+ name = %r{(guard\/[a-z_]*)(/[a-z_]*)?.rb:}i.match(caller[depth])
206
208
  return "Guard" unless name
207
209
  name[1].split("/").map do |part|
208
210
  part.split(/[^a-z0-9]/i).map(&:capitalize).join
@@ -217,26 +219,19 @@ module Guard
217
219
  @color_enabled_initialized ||= false
218
220
  @color_enabled = nil unless @color_enabled_initialized
219
221
  @color_enabled_initialized = true
220
- if @color_enabled.nil?
221
- if Gem.win_platform?
222
- if ENV["ANSICON"]
223
- @color_enabled = true
224
- else
225
- begin
226
- require "rubygems" unless ENV["NO_RUBYGEMS"]
227
- require "Win32/Console/ANSI"
228
- @color_enabled = true
229
- rescue LoadError
230
- @color_enabled = false
231
- info "Run 'gem install win32console' to use color on Windows"
232
- end
233
- end
234
- else
235
- @color_enabled = true
236
- end
237
- end
222
+ return @color_enabled unless @color_enabled.nil?
223
+ return (@color_enabled = true) unless Gem.win_platform?
224
+ return (@color_enabled = true) if ENV["ANSICON"]
238
225
 
239
- @color_enabled
226
+ @color_enabled =
227
+ begin
228
+ require "rubygems" unless ENV["NO_RUBYGEMS"]
229
+ require "Win32/Console/ANSI"
230
+ true
231
+ rescue LoadError
232
+ info "Run 'gem install win32console' to use color on Windows"
233
+ false
234
+ end
240
235
  end
241
236
 
242
237
  # Colorizes a text message. See the constant in the UI class for possible
@@ -1,3 +1,3 @@
1
1
  module Guard
2
- VERSION = "2.13.0"
2
+ VERSION = "2.14.0"
3
3
  end
@@ -2,6 +2,7 @@ require "guard/config"
2
2
  require "guard/deprecated/watcher" unless Guard::Config.new.strict?
3
3
 
4
4
  require "guard/ui"
5
+ require "guard/watcher/pattern"
5
6
 
6
7
  module Guard
7
8
  # The watcher defines a RegExp that will be matched against file system
@@ -21,30 +22,8 @@ module Guard
21
22
  # the Guard plugin
22
23
  #
23
24
  def initialize(pattern, action = nil)
24
- @pattern, @action = pattern, action
25
- @@warning_printed ||= false
26
-
27
- # deprecation warning
28
- regexp = /(^(\^))|(>?(\\\.)|(\.\*))|(\(.*\))|(\[.*\])|(\$$)/
29
- return unless @pattern.is_a?(String) && @pattern =~ regexp
30
-
31
- unless @@warning_printed
32
- UI.info "*" * 20 + "\nDEPRECATION WARNING!\n" + "*" * 20
33
- UI.info <<-MSG
34
- You have a string in your Guardfile watch patterns that seem to
35
- represent a Regexp.
36
-
37
- Guard matches String with == and Regexp with Regexp#match.
38
-
39
- You should either use plain String (without Regexp special
40
- characters) or real Regexp.
41
- MSG
42
- @@warning_printed = true
43
- end
44
-
45
- new_regexp = Regexp.new(@pattern).inspect
46
- UI.info "\"#{@pattern}\" has been converted to #{ new_regexp }\n"
47
- @pattern = Regexp.new(@pattern)
25
+ @action = action
26
+ @pattern = Pattern.create(pattern)
48
27
  end
49
28
 
50
29
  # Finds the files that matches a Guard plugin.
@@ -59,7 +38,7 @@ module Guard
59
38
  files.inject([]) do |paths, file|
60
39
  guard.watchers.each do |watcher|
61
40
  matches = watcher.match(file)
62
- next unless matches
41
+ next(paths) unless matches
63
42
 
64
43
  if watcher.action
65
44
  result = watcher.call_action(matches)
@@ -68,7 +47,7 @@ module Guard
68
47
  elsif result.respond_to?(:empty?) && !result.empty?
69
48
  paths << Array(result)
70
49
  else
71
- next
50
+ next(paths)
72
51
  end
73
52
  else
74
53
  paths << matches[0]
@@ -81,20 +60,9 @@ module Guard
81
60
  end
82
61
  end
83
62
 
84
- # Test the watchers pattern against a file.
85
- #
86
- # @param [String] file the file to test
87
- # @return [Array<String>] an array of matches (or containing a single path
88
- # if the pattern is a string)
89
- #
90
63
  def match(string_or_pathname)
91
- # TODO: use only match() - and show fnmatch example
92
- file = string_or_pathname.to_s
93
- return (file == @pattern ? [file] : nil) unless @pattern.is_a?(Regexp)
94
- return unless (m = @pattern.match(file))
95
- m = m.to_a
96
- m[0] = file
97
- m
64
+ m = pattern.match(string_or_pathname)
65
+ m.nil? ? nil : Pattern::MatchResult.new(m, string_or_pathname)
98
66
  end
99
67
 
100
68
  # Executes a watcher action.
@@ -106,7 +74,7 @@ module Guard
106
74
  def call_action(matches)
107
75
  @action.arity > 0 ? @action.call(matches) : @action.call
108
76
  rescue => ex
109
- UI.error "Problem with watch action!\n#{ ex.message }"
77
+ UI.error "Problem with watch action!\n#{ex.message}"
110
78
  UI.error ex.backtrace.join("\n")
111
79
  end
112
80
  end
@@ -0,0 +1,24 @@
1
+ require "guard/ui"
2
+
3
+ require_relative "pattern/match_result"
4
+ require_relative "pattern/matcher"
5
+ require_relative "pattern/deprecated_regexp"
6
+ require_relative "pattern/simple_path"
7
+ require_relative "pattern/pathname_path"
8
+
9
+ module Guard
10
+ class Watcher
11
+ class Pattern
12
+ def self.create(pattern)
13
+ if DeprecatedRegexp.new(pattern).deprecated?
14
+ DeprecatedRegexp.show_deprecation(pattern)
15
+ return DeprecatedRegexp.convert(pattern)
16
+ end
17
+
18
+ return PathnamePath.new(pattern) if pattern.is_a?(Pathname)
19
+ return SimplePath.new(pattern) if pattern.is_a?(String)
20
+ Matcher.new(pattern)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "matcher"
2
+
3
+ module Guard
4
+ class Watcher
5
+ class Pattern
6
+ # TODO: remove before Guard 3.x
7
+ class DeprecatedRegexp
8
+ def initialize(pattern)
9
+ @original_pattern = pattern
10
+ end
11
+
12
+ def self.convert(pattern)
13
+ Matcher.new(Regexp.new(pattern))
14
+ end
15
+
16
+ def deprecated?
17
+ regexp = /(^(\^))|(>?(\\\.)|(\.\*))|(\(.*\))|(\[.*\])|(\$$)/
18
+ @original_pattern.is_a?(String) && regexp.match(@original_pattern)
19
+ end
20
+
21
+ def self.show_deprecation(pattern)
22
+ @warning_printed ||= false
23
+
24
+ unless @warning_printed
25
+ msg = "*" * 20 + "\nDEPRECATION WARNING!\n" + "*" * 20
26
+ msg += <<-MSG
27
+ You have a string in your Guardfile watch patterns that seem to
28
+ represent a Regexp.
29
+
30
+ Guard matches String with == and Regexp with Regexp#match.
31
+
32
+ You should either use plain String (without Regexp special
33
+ characters) or real Regexp.
34
+ MSG
35
+ UI.deprecation(msg)
36
+ @warning_printed = true
37
+ end
38
+
39
+ new_regexp = Regexp.new(pattern).inspect
40
+ UI.info "\"#{pattern}\" will be converted to #{new_regexp}\n"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ module Guard
2
+ class Watcher
3
+ class Pattern
4
+ class MatchResult
5
+ def initialize(match_result, original_value)
6
+ @match_result = match_result
7
+ @original_value = original_value
8
+ end
9
+
10
+ def [](index)
11
+ return @match_result[index] if index.is_a?(Symbol)
12
+ return @original_value if index.zero?
13
+ @match_result.to_a[index]
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ module Guard
2
+ class Watcher
3
+ class Pattern
4
+ class Matcher
5
+ def initialize(obj)
6
+ @matcher = obj
7
+ end
8
+
9
+ def match(string_or_pathname)
10
+ @matcher.match(normalized(string_or_pathname))
11
+ end
12
+
13
+ private
14
+
15
+ def normalized(string_or_pathname)
16
+ path = Pathname.new(string_or_pathname).cleanpath
17
+ return path.to_s if @matcher.is_a?(Regexp)
18
+ path
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "simple_path"
2
+
3
+ module Guard
4
+ class Watcher
5
+ class Pattern
6
+ class PathnamePath < SimplePath
7
+ protected
8
+
9
+ def normalize(string_or_pathname)
10
+ Pathname.new(string_or_pathname).cleanpath
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ module Guard
2
+ class Watcher
3
+ class Pattern
4
+ class SimplePath
5
+ def initialize(string_or_pathname)
6
+ @path = normalize(string_or_pathname)
7
+ end
8
+
9
+ def match(string_or_pathname)
10
+ cleaned = normalize(string_or_pathname)
11
+ return nil unless @path == cleaned
12
+ [cleaned]
13
+ end
14
+
15
+ protected
16
+
17
+ def normalize(string_or_pathname)
18
+ Pathname.new(string_or_pathname).cleanpath.to_s
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,116 @@
1
+ # TODO: extract to gem?
2
+
3
+ class Releaser
4
+ def initialize(options = {})
5
+ @project_name = options.delete(:project_name) do
6
+ fail "project_name is needed!"
7
+ end
8
+
9
+ @gem_name = options.delete(:gem_name) do
10
+ fail "gem_name is needed!"
11
+ end
12
+
13
+ @github_repo = options.delete(:github_repo) do
14
+ fail "github_repo is needed!"
15
+ end
16
+
17
+ @version = options.delete(:version) do
18
+ fail "version is needed!"
19
+ end
20
+ end
21
+
22
+ def full
23
+ rubygems
24
+ github
25
+ end
26
+
27
+ def rubygems
28
+ input = nil
29
+ loop do
30
+ STDOUT.puts "Release #{@project_name} #{@version} to RubyGems? (y/n)"
31
+ input = STDIN.gets.chomp.downcase
32
+ break if %w(y n).include?(input)
33
+ end
34
+
35
+ exit if input == "n"
36
+
37
+ Rake::Task["release"].invoke
38
+ end
39
+
40
+ def github
41
+ tag_name = "v#{@version}"
42
+
43
+ require "gems"
44
+
45
+ _verify_released
46
+ _verify_tag_pushed
47
+
48
+ require "octokit"
49
+ gh_client = Octokit::Client.new(netrc: true)
50
+
51
+ gh_release = _detect_gh_release(gh_client, tag_name, true)
52
+ return unless gh_release
53
+
54
+ STDOUT.puts "Draft release for #{tag_name}:\n"
55
+ STDOUT.puts gh_release.body
56
+ STDOUT.puts "\n-------------------------\n\n"
57
+
58
+ _confirm_publish
59
+
60
+ return unless _update_release(gh_client, gh_release, tag_name)
61
+
62
+ gh_release = _detect_gh_release(gh_client, tag_name, false)
63
+
64
+ _success_summary(gh_release, tag_name)
65
+ end
66
+
67
+ private
68
+
69
+ def _verify_released
70
+ if @version != Gems.info(@gem_name)["version"]
71
+ STDOUT.puts "#{@project_name} #{@version} is not yet released."
72
+ STDOUT.puts "Please release it first with: rake release:gem"
73
+ exit
74
+ end
75
+ end
76
+
77
+ def _verify_tag_pushed
78
+ tags = `git ls-remote --tags origin`.split("\n")
79
+ return if tags.detect { |tag| tag =~ /v#{@version}$/ }
80
+
81
+ STDOUT.puts "The tag v#{@version} has not yet been pushed."
82
+ STDOUT.puts "Please push it first with: rake release:gem"
83
+ exit
84
+ end
85
+
86
+ def _success_summary(gh_release, tag_name)
87
+ href = gh_release.rels[:html].href
88
+ STDOUT.puts "GitHub release #{tag_name} has been published!"
89
+ STDOUT.puts "\nPlease enjoy and spread the word!"
90
+ STDOUT.puts "Lack of inspiration? Here's a tweet you could improve:\n\n"
91
+ STDOUT.puts "Just released #{@project_name} #{@version}! #{href}"
92
+ end
93
+
94
+ def _detect_gh_release(gh_client, tag_name, draft)
95
+ gh_releases = gh_client.releases(@github_repo)
96
+ gh_releases.detect { |r| r.tag_name == tag_name && r.draft == draft }
97
+ end
98
+
99
+ def _confirm_publish
100
+ input = nil
101
+ loop do
102
+ STDOUT.puts "Would you like to publish this GitHub release now? (y/n)"
103
+ input = STDIN.gets.chomp.downcase
104
+ break if %w(y n).include?(input)
105
+ end
106
+
107
+ exit if input == "n"
108
+ end
109
+
110
+ def _update_release(gh_client, gh_release, tag_name)
111
+ result = gh_client.update_release(gh_release.rels[:self].href, draft: false)
112
+ return true if result
113
+ STDOUT.puts "GitHub release #{tag_name} couldn't be published!"
114
+ false
115
+ end
116
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: guard
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.13.0
4
+ version: 2.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thibaud Guillaume-Gentil
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-27 00:00:00.000000000 Z
11
+ date: 2016-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -31,7 +31,7 @@ dependencies:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '2.7'
34
- - - "<="
34
+ - - "<"
35
35
  - !ruby/object:Gem::Version
36
36
  version: '4.0'
37
37
  type: :runtime
@@ -41,7 +41,7 @@ dependencies:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
43
  version: '2.7'
44
- - - "<="
44
+ - - "<"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '4.0'
47
47
  - !ruby/object:Gem::Dependency
@@ -198,6 +198,13 @@ files:
198
198
  - lib/guard/ui/colors.rb
199
199
  - lib/guard/version.rb
200
200
  - lib/guard/watcher.rb
201
+ - lib/guard/watcher/pattern.rb
202
+ - lib/guard/watcher/pattern/deprecated_regexp.rb
203
+ - lib/guard/watcher/pattern/match_result.rb
204
+ - lib/guard/watcher/pattern/matcher.rb
205
+ - lib/guard/watcher/pattern/pathname_path.rb
206
+ - lib/guard/watcher/pattern/simple_path.rb
207
+ - lib/tasks/releaser.rb
201
208
  - man/guard.1
202
209
  - man/guard.1.html
203
210
  homepage: http://guardgem.org
@@ -220,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
220
227
  version: '0'
221
228
  requirements: []
222
229
  rubyforge_project:
223
- rubygems_version: 2.4.7
230
+ rubygems_version: 2.5.1
224
231
  signing_key:
225
232
  specification_version: 4
226
233
  summary: Guard keeps an eye on your file modifications