guard 0.7.0 → 0.8.0

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