guard 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ module Guard
2
+
3
+ # A group of Guards.
4
+ #
5
+ class Group
6
+
7
+ attr_accessor :name, :options
8
+
9
+ # Initialize a Group.
10
+ #
11
+ # @param [String] name the name of the group
12
+ # @option options [Boolean] halt_on_fail if a task execution
13
+ # should be halted for all Guards in this group if one Guard throws `:task_has_failed`
14
+ #
15
+ def initialize(name, options = {})
16
+ @name = name.to_sym
17
+ @options = options
18
+ end
19
+
20
+ end
21
+
22
+ end
data/lib/guard/guard.rb CHANGED
@@ -1,21 +1,39 @@
1
1
  module Guard
2
+
3
+ # Main class that every Guard implementation must subclass.
4
+ #
5
+ # Guard will trigger the `start`, `stop`, `reload`, `run_all` and `run_on_change`
6
+ # methods depending on user interaction and file modification.
7
+ #
8
+ # Each Guard should provide a template Guardfile located within the Gem
9
+ # at `lib/guard/guard-name/templates/Guardfile`.
10
+ #
2
11
  class Guard
3
12
  include Hook
4
13
 
5
14
  attr_accessor :watchers, :options, :group
6
15
 
16
+ # Initialize a Guard.
17
+ #
18
+ # @param [Array<Guard::Watcher>] watchers the Guard file watchers
19
+ # @param [Hash] options the custom Guard options
20
+ #
7
21
  def initialize(watchers = [], options = {})
8
- @group = options.delete(:group) || :default
22
+ @group = options[:group] ? options.delete(:group).to_sym : :default
9
23
  @watchers, @options = watchers, options
10
24
  end
11
25
 
12
- # Guardfile template needed inside guard gem
26
+ # Initialize the Guard. This will copy the Guardfile template inside the Guard gem.
27
+ # The template Guardfile must be located within the Gem at `lib/guard/guard-name/templates/Guardfile`.
28
+ #
29
+ # @param [String] name the name of the Guard
30
+ #
13
31
  def self.init(name)
14
32
  if ::Guard::Dsl.guardfile_include?(name)
15
- ::Guard::UI.info "Guardfile already includes #{name} guard"
33
+ ::Guard::UI.info "Guardfile already includes #{ name } guard"
16
34
  else
17
35
  content = File.read('Guardfile')
18
- guard = File.read("#{::Guard.locate_guard(name)}/lib/guard/#{name}/templates/Guardfile")
36
+ guard = File.read("#{ ::Guard.locate_guard(name) }/lib/guard/#{ name }/templates/Guardfile")
19
37
  File.open('Guardfile', 'wb') do |f|
20
38
  f.puts(content)
21
39
  f.puts("")
@@ -25,34 +43,56 @@ module Guard
25
43
  end
26
44
  end
27
45
 
28
- # ================
29
- # = Guard method =
30
- # ================
31
-
32
- # Call once when guard starts
33
- # Please override initialize method to init stuff
46
+ # Call once when Guard starts. Please override initialize method to init stuff.
47
+ #
48
+ # @return [Boolean] Whether the start action was successful or not
49
+ #
34
50
  def start
35
51
  true
36
52
  end
37
53
 
38
- # Call once when guard quit
54
+ # Call once when Guard quit.
55
+ #
56
+ # @return [Boolean] Whether the stop action was successful or not
57
+ #
39
58
  def stop
40
59
  true
41
60
  end
42
61
 
43
- # Should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
62
+ # Should be used for "reload" (really!) actions like reloading passenger/spork/bundler/...
63
+ #
64
+ # @return [Boolean] Whether the reload action was successful or not
65
+ #
44
66
  def reload
45
67
  true
46
68
  end
47
69
 
48
- # Should be principally used for long action like running all specs/tests/...
70
+ # Should be used for long action like running all specs/tests/...
71
+ #
72
+ # @return [Boolean] Whether the run_all action was successful or not
73
+ #
49
74
  def run_all
50
75
  true
51
76
  end
52
77
 
78
+ # Will be triggered when a file change matched a watcher.
79
+ #
80
+ # @param [Array<String>] paths the changes files or paths
81
+ # @return [Boolean] Whether the run_on_change action was successful or not
82
+ #
53
83
  def run_on_change(paths)
54
84
  true
55
85
  end
56
86
 
87
+ # Will be triggered when a file deletion matched a watcher.
88
+ #
89
+ # @param [Array<String>] paths the deleted files or paths
90
+ # @return [Boolean] Whether the run_on_deletion action was successful or not
91
+ #
92
+ def run_on_deletion(paths)
93
+ true
94
+ end
95
+
57
96
  end
97
+
58
98
  end
data/lib/guard/hook.rb CHANGED
@@ -1,51 +1,82 @@
1
1
  module Guard
2
+
3
+ # Guard has a hook mechanism that allows you to insert callbacks for individual Guards.
4
+ # By default, each of the Guard instance methods has a "_begin" and an "_end" hook.
5
+ # For example, the Guard::Guard#start method has a :start_begin hook that is runs immediately
6
+ # before Guard::Guard#start, and a :start_end hook that runs immediately after Guard::Guard#start.
7
+ #
8
+ # Read more about [hooks and callbacks on the wiki](https://github.com/guard/guard/wiki/Hooks-and-callbacks).
9
+ #
2
10
  module Hook
3
11
 
12
+ # The Hook module gets included.
13
+ #
14
+ # @param [Class] base the class that includes the module
15
+ #
4
16
  def self.included(base)
5
17
  base.send :include, InstanceMethods
6
18
  end
7
19
 
20
+ # Instance methods that gets included in the base class.
21
+ #
8
22
  module InstanceMethods
9
- # When +event+ is a Symbol, #hook will generate a hook name
10
- # by concatenating the method name from where #hook is called
23
+
24
+ # When event is a Symbol, {#hook} will generate a hook name
25
+ # by concatenating the method name from where {#hook} is called
11
26
  # with the given Symbol.
12
- # Example:
27
+ #
28
+ # @example Add a hook with a Symbol
29
+ #
13
30
  # def run_all
14
31
  # hook :foo
15
32
  # end
16
- # Here, when #run_all is called, #hook will notify callbacks
33
+ #
34
+ # Here, when {Guard::Guard#run_all} is called, {#hook} will notify callbacks
17
35
  # registered for the "run_all_foo" event.
18
36
  #
19
- # When +event+ is a String, #hook will directly turn the String
37
+ # When event is a String, {#hook} will directly turn the String
20
38
  # into a Symbol.
21
- # Example:
39
+ #
40
+ # @example Add a hook with a String
41
+ #
22
42
  # def run_all
23
43
  # hook "foo_bar"
24
44
  # end
25
- # Here, when #run_all is called, #hook will notify callbacks
45
+ #
46
+ # When {Guard::Guard#run_all} is called, {#hook} will notify callbacks
26
47
  # registered for the "foo_bar" event.
27
48
  #
28
- # +args+ parameter is passed as is to the callbacks registered
29
- # for the given event.
49
+ # @param [Symbol, String] event the name of the Guard event
50
+ # @param [Array] args the parameters are passed as is to the callbacks registered for the given event.
51
+ #
30
52
  def hook(event, *args)
31
53
  hook_name = if event.is_a? Symbol
32
- calling_method = caller[0][/`([^']*)'/, 1]
33
- "#{calling_method}_#{event}"
34
- else
35
- event
36
- end.to_sym
54
+ calling_method = caller[0][/`([^']*)'/, 1]
55
+ "#{ calling_method }_#{ event }"
56
+ else
57
+ event
58
+ end.to_sym
37
59
 
38
- UI.debug "Hook :#{hook_name} executed for #{self.class}"
60
+ UI.debug "Hook :#{ hook_name } executed for #{ self.class }"
39
61
 
40
62
  Hook.notify(self.class, hook_name, *args)
41
63
  end
42
64
  end
43
65
 
44
66
  class << self
67
+
68
+ # Get all callbacks.
69
+ #
45
70
  def callbacks
46
71
  @callbacks ||= Hash.new { |hash, key| hash[key] = [] }
47
72
  end
48
73
 
74
+ # Add a callback.
75
+ #
76
+ # @param [Block] listener the listener to notify
77
+ # @param [Guard::Guard] guard_class the Guard class to add the callback
78
+ # @param [Array<Symbol>] events the events to register
79
+ #
49
80
  def add_callback(listener, guard_class, events)
50
81
  _events = events.is_a?(Array) ? events : [events]
51
82
  _events.each do |event|
@@ -53,19 +84,34 @@ module Guard
53
84
  end
54
85
  end
55
86
 
87
+ # Checks if a callback has been registered.
88
+ #
89
+ # @param [Block] listener the listener to notify
90
+ # @param [Guard::Guard] guard_class the Guard class to add the callback
91
+ # @param [Symbol] event the event to look for
92
+ #
56
93
  def has_callback?(listener, guard_class, event)
57
94
  callbacks[[guard_class, event]].include?(listener)
58
95
  end
59
96
 
97
+ # Notify a callback.
98
+ #
99
+ # @param [Guard::Guard] guard_class the Guard class to add the callback
100
+ # @param [Symbol] event the event to trigger
101
+ # @param [Array] args the arguments for the listener
102
+ #
60
103
  def notify(guard_class, event, *args)
61
104
  callbacks[[guard_class, event]].each do |listener|
62
105
  listener.call(guard_class, event, *args)
63
106
  end
64
107
  end
65
108
 
109
+ # Reset all callbacks.
110
+ #
66
111
  def reset_callbacks!
67
112
  @callbacks = nil
68
113
  end
114
+
69
115
  end
70
116
 
71
117
  end
@@ -1,39 +1,77 @@
1
1
  module Guard
2
+
3
+ # The interactor reads user input and triggers
4
+ # specific action upon them unless its locked.
5
+ #
6
+ # Currently the following actions are implemented:
7
+ #
8
+ # - stop, quit, exit, s, q, e => Exit Guard
9
+ # - reload, r, z => Reload Guard
10
+ # - pause, p => Pause Guard
11
+ # - Everything else => Run all
12
+ #
2
13
  class Interactor
3
14
 
15
+ class LockException < Exception; end
16
+ class UnlockException < Exception; end
17
+
4
18
  attr_reader :locked
5
19
 
20
+ # Initialize the interactor in unlocked state.
21
+ #
6
22
  def initialize
7
23
  @locked = false
8
24
  end
9
25
 
26
+ # Start the interactor in its own thread.
27
+ #
10
28
  def start
11
29
  return if ENV["GUARD_ENV"] == 'test'
12
- Thread.new do
30
+
31
+ @thread = Thread.new do
13
32
  loop do
14
- if (entry = $stdin.gets) && !@locked
15
- entry.gsub! /\n/, ''
16
- case entry
17
- when 'stop', 'quit', 'exit', 's', 'q', 'e'
18
- ::Guard.stop
19
- when 'reload', 'r', 'z'
20
- ::Guard.reload
21
- when 'pause', 'p'
22
- ::Guard.pause
23
- else
24
- ::Guard.run_all
33
+ begin
34
+ if !@locked && (entry = $stdin.gets)
35
+ entry.gsub! /\n/, ''
36
+ case entry
37
+ when 'stop', 'quit', 'exit', 's', 'q', 'e'
38
+ ::Guard.stop
39
+ when 'reload', 'r', 'z'
40
+ ::Guard::Dsl.reevaluate_guardfile
41
+ ::Guard.reload
42
+ when 'pause', 'p'
43
+ ::Guard.pause
44
+ else
45
+ ::Guard.run_all
46
+ end
25
47
  end
48
+ rescue LockException
49
+ lock
50
+ rescue UnlockException
51
+ unlock
26
52
  end
27
53
  end
28
54
  end
29
55
  end
30
56
 
57
+ # Lock the interactor.
58
+ #
31
59
  def lock
32
- @locked = true
60
+ if !@thread || @thread == Thread.current
61
+ @locked = true
62
+ else
63
+ @thread.raise(LockException)
64
+ end
33
65
  end
34
66
 
67
+ # Unlock the interactor.
68
+ #
35
69
  def unlock
36
- @locked = false
70
+ if !@thread || @thread == Thread.current
71
+ @locked = false
72
+ else
73
+ @thread.raise(UnlockException)
74
+ end
37
75
  end
38
76
 
39
77
  end
@@ -8,40 +8,64 @@ module Guard
8
8
  autoload :Windows, 'guard/listeners/windows'
9
9
  autoload :Polling, 'guard/listeners/polling'
10
10
 
11
+ # The Listener is the base class for all listener
12
+ # implementations.
13
+ #
14
+ # @abstract
15
+ #
11
16
  class Listener
12
17
 
13
- DefaultIgnorePaths = %w[. .. .bundle .git log tmp vendor]
18
+ # Default paths that gets ignored by the listener
19
+ DEFAULT_IGNORE_PATHS = %w[. .. .bundle .git log tmp vendor]
20
+
14
21
  attr_accessor :changed_files
15
22
  attr_reader :directory, :ignore_paths, :locked
16
23
 
17
- def self.select_and_init(*a)
24
+ # Select the appropriate listener implementation for the
25
+ # current OS and initializes it.
26
+ #
27
+ # @param [Array] args the arguments for the listener
28
+ # @return [Guard::Listener] the chosen listener
29
+ #
30
+ def self.select_and_init(*args)
18
31
  if mac? && Darwin.usable?
19
- Darwin.new(*a)
32
+ Darwin.new(*args)
20
33
  elsif linux? && Linux.usable?
21
- Linux.new(*a)
34
+ Linux.new(*args)
22
35
  elsif windows? && Windows.usable?
23
- Windows.new(*a)
36
+ Windows.new(*args)
24
37
  else
25
- UI.info "Using polling (Please help us to support your system better than that.)"
26
- Polling.new(*a)
38
+ UI.info 'Using polling (Please help us to support your system better than that).'
39
+ Polling.new(*args)
27
40
  end
28
41
  end
29
42
 
43
+ # Initialize the listener.
44
+ #
45
+ # @param [String] directory the root directory to listen to
46
+ # @option options [Boolean] relativize_paths use only relative paths
47
+ # @option options [Array<String>] ignore_paths the paths to ignore by the listener
48
+ #
30
49
  def initialize(directory = Dir.pwd, options = {})
31
- @directory = directory.to_s
32
- @sha1_checksums_hash = {}
33
- @relativize_paths = options.fetch(:relativize_paths, true)
34
- @changed_files = []
35
- @locked = false
36
- @ignore_paths = DefaultIgnorePaths
37
- @ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
50
+ @directory = directory.to_s
51
+ @sha1_checksums_hash = {}
52
+ @file_timestamp_hash = {}
53
+ @relativize_paths = options.fetch(:relativize_paths, true)
54
+ @changed_files = []
55
+ @locked = false
56
+ @ignore_paths = DEFAULT_IGNORE_PATHS
57
+ @ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
58
+ @watch_all_modifications = options.fetch(:watch_all_modifications, false)
38
59
 
39
60
  update_last_event
40
61
  start_reactor
41
62
  end
42
63
 
64
+ # Start the listener thread.
65
+ #
43
66
  def start_reactor
44
67
  return if ENV["GUARD_ENV"] == 'test'
68
+
45
69
  Thread.new do
46
70
  loop do
47
71
  if @changed_files != [] && !@locked
@@ -55,75 +79,146 @@ module Guard
55
79
  end
56
80
  end
57
81
 
82
+ # Start watching the root directory.
83
+ #
58
84
  def start
59
85
  watch(@directory)
86
+ timestamp_files
60
87
  end
61
88
 
89
+ # Stop listening for events.
90
+ #
62
91
  def stop
63
92
  end
64
93
 
94
+ # Lock the listener to ignore change events.
95
+ #
65
96
  def lock
66
97
  @locked = true
67
98
  end
68
99
 
100
+ # Unlock the listener to listen again to change events.
101
+ #
69
102
  def unlock
70
103
  @locked = false
71
104
  end
72
105
 
106
+ # Clear the list of changed files.
107
+ #
73
108
  def clear_changed_files
74
109
  @changed_files.clear
75
110
  end
76
111
 
112
+ # Store a listener callback.
113
+ #
114
+ # @param [Block] callback the callback to store
115
+ #
77
116
  def on_change(&callback)
78
117
  @callback = callback
79
118
  end
80
119
 
120
+ # Updates the timestamp of the last event.
121
+ #
81
122
  def update_last_event
82
123
  @last_event = Time.now
83
124
  end
84
125
 
126
+ # Get the modified files.
127
+ #
128
+ # If the `:watch_all_modifications` option is true, then moved and
129
+ # deleted files are also reported, but prefixed by an exclamation point.
130
+ #
131
+ # @example Deleted or moved file
132
+ # !/home/user/dir/file.rb
133
+ #
134
+ # @param [Array<String>] dirs the watched directories
135
+ # @param [Hash] options the listener options
136
+ # @option options [Symbol] all whether to files in sub directories
137
+ # @return [Array<String>] paths of files that have been modified
138
+ #
85
139
  def modified_files(dirs, options = {})
86
140
  last_event = @last_event
141
+ files = []
142
+ if @watch_all_modifications
143
+ deleted_files = @file_timestamp_hash.collect do |path, ts|
144
+ unless File.exists?(path)
145
+ @sha1_checksums_hash.delete(path)
146
+ @file_timestamp_hash.delete(path)
147
+ "!#{path}"
148
+ end
149
+ end
150
+ files.concat(deleted_files.compact)
151
+ end
87
152
  update_last_event
88
- files = potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) }
89
- relativize_paths(files)
90
- end
153
+ files.concat(potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) })
91
154
 
92
- def worker
93
- raise NotImplementedError, "should respond to #watch"
155
+ relativize_paths(files)
94
156
  end
95
157
 
96
- # register a directory to watch. must be implemented by the subclasses
158
+ # Register a directory to watch.
159
+ # Must be implemented by the subclasses.
160
+ #
161
+ # @param [String] directory the directory to watch
162
+ #
97
163
  def watch(directory)
98
164
  raise NotImplementedError, "do whatever you want here, given the directory as only argument"
99
165
  end
100
166
 
167
+ # Get all files that are in the watched directory.
168
+ #
169
+ # @return [Array<String>] the list of files
170
+ #
101
171
  def all_files
102
172
  potentially_modified_files([@directory], :all => true)
103
173
  end
104
174
 
105
- # scopes all given paths to the current #directory
175
+ # Scopes all given paths to the current directory.
176
+ #
177
+ # @param [Array<String>] paths the paths to change
178
+ # @return [Array<String>] all paths now relative to the current dir
179
+ #
106
180
  def relativize_paths(paths)
107
181
  return paths unless relativize_paths?
108
182
  paths.map do |path|
109
- path.gsub(%r{^#{@directory}/}, '')
183
+ path.gsub(%r{^(!)?#{ @directory }/},'\1')
110
184
  end
111
185
  end
112
186
 
187
+ # Use paths relative to the current directory.
188
+ #
189
+ # @return [Boolean] whether to use relative or absolute paths
190
+ #
113
191
  def relativize_paths?
114
192
  !!@relativize_paths
115
193
  end
116
194
 
117
- # return children of the passed dirs that are not in the ignore_paths list
195
+ # Populate initial timestamp file hash to watch for deleted or moved files.
196
+ #
197
+ def timestamp_files
198
+ all_files.each {|path| set_file_timestamp_hash(path, file_timestamp(path)) } if @watch_all_modifications
199
+ end
200
+
201
+ # Removes the ignored paths from the directory list.
202
+ #
203
+ # @param [Array<String>] dirs the directory to listen to
204
+ # @param [Array<String>] ignore_paths the paths to ignore
205
+ # @return children of the passed dirs that are not in the ignore_paths list
206
+ #
118
207
  def exclude_ignored_paths(dirs, ignore_paths = self.ignore_paths)
119
208
  Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path|
120
209
  ignore_paths.include?(File.basename(path))
121
210
  end
122
211
  end
123
212
 
124
- private
213
+ private
125
214
 
126
- def potentially_modified_files(dirs, options={})
215
+ # Gets a list of files that are in the modified directories.
216
+ #
217
+ # @param [Array<String>] dirs the list of directories
218
+ # @param [Hash] options the find file option
219
+ # @option options [Symbol] all whether to files in sub directories
220
+ #
221
+ def potentially_modified_files(dirs, options = {})
127
222
  paths = exclude_ignored_paths(dirs)
128
223
 
129
224
  if options[:all]
@@ -131,7 +226,7 @@ module Guard
131
226
  if File.file?(path)
132
227
  array << path
133
228
  else
134
- array += Dir.glob("#{path}/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) }
229
+ array += Dir.glob("#{ path }/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) }
135
230
  end
136
231
  array
137
232
  end
@@ -140,9 +235,17 @@ module Guard
140
235
  end
141
236
  end
142
237
 
238
+ # Test if the file content has changed.
239
+ #
143
240
  # Depending on the filesystem, mtime/ctime is probably only precise to the second, so round
144
241
  # both values down to the second for the comparison.
242
+ #
145
243
  # ctime is used only on == comparison to always catches Rails 3.1 Assets pipelined on Mac OSX
244
+ #
245
+ # @param [String] path the file path
246
+ # @param [Time] last_event the time of the last event
247
+ # @return [Boolean] Whether the file content has changed or not.
248
+ #
146
249
  def file_modified?(path, last_event)
147
250
  ctime = File.ctime(path).to_i
148
251
  mtime = File.mtime(path).to_i
@@ -151,6 +254,12 @@ module Guard
151
254
  elsif mtime > last_event.to_i
152
255
  set_sha1_checksums_hash(path, sha1_checksum(path))
153
256
  true
257
+ elsif @watch_all_modifications
258
+ ts = file_timestamp(path)
259
+ if ts != @file_timestamp_hash[path]
260
+ set_file_timestamp_hash(path, ts)
261
+ true
262
+ end
154
263
  else
155
264
  false
156
265
  end
@@ -158,6 +267,12 @@ module Guard
158
267
  false
159
268
  end
160
269
 
270
+ # Tests if the file content has been modified by
271
+ # comparing the SHA1 checksum.
272
+ #
273
+ # @param [String] path the file path
274
+ # @param [String] sha1_checksum the checksum of the file
275
+ #
161
276
  def file_content_modified?(path, sha1_checksum)
162
277
  if @sha1_checksums_hash[path] != sha1_checksum
163
278
  set_sha1_checksums_hash(path, sha1_checksum)
@@ -167,22 +282,62 @@ module Guard
167
282
  end
168
283
  end
169
284
 
285
+ # Set save a files current timestamp
286
+ #
287
+ # @param [String] path the file path
288
+ # @param [Int] file_timestamp the files modified timestamp
289
+ #
290
+ def set_file_timestamp_hash(path, file_timestamp)
291
+ @file_timestamp_hash[path] = file_timestamp
292
+ end
293
+
294
+ # Set the current checksum of a file.
295
+ #
296
+ # @param [String] path the file path
297
+ # @param [String] sha1_checksum the checksum of the file
298
+ #
170
299
  def set_sha1_checksums_hash(path, sha1_checksum)
171
300
  @sha1_checksums_hash[path] = sha1_checksum
172
301
  end
173
302
 
303
+ # Gets a files modified timestamp
304
+ #
305
+ # @path [String] path the file path
306
+ # @return [Int] file modified timestamp
307
+ #
308
+ def file_timestamp(path)
309
+ File.mtime(path).to_i
310
+ end
311
+
312
+ # Calculates the SHA1 checksum of a file.
313
+ #
314
+ # @param [String] path the path to the file
315
+ # @return [String] the SHA1 checksum
316
+ #
174
317
  def sha1_checksum(path)
175
318
  Digest::SHA1.file(path).to_s
176
319
  end
177
320
 
321
+ # Test if the OS is Mac OS X.
322
+ #
323
+ # @return [Boolean] Whether the OS is Mac OS X
324
+ #
178
325
  def self.mac?
179
326
  RbConfig::CONFIG['target_os'] =~ /darwin/i
180
327
  end
181
328
 
329
+ # Test if the OS is Linux.
330
+ #
331
+ # @return [Boolean] Whether the OS is Linux
332
+ #
182
333
  def self.linux?
183
334
  RbConfig::CONFIG['target_os'] =~ /linux/i
184
335
  end
185
336
 
337
+ # Test if the OS is Windows.
338
+ #
339
+ # @return [Boolean] Whether the OS is Windows
340
+ #
186
341
  def self.windows?
187
342
  RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
188
343
  end