guard 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +23 -1
- data/README.md +173 -226
- data/bin/guard +1 -1
- data/lib/guard.rb +243 -45
- data/lib/guard/cli.rb +135 -46
- data/lib/guard/dsl.rb +260 -38
- data/lib/guard/dsl_describer.rb +36 -4
- data/lib/guard/group.rb +22 -0
- data/lib/guard/guard.rb +53 -13
- data/lib/guard/hook.rb +61 -15
- data/lib/guard/interactor.rb +52 -14
- data/lib/guard/listener.rb +181 -26
- data/lib/guard/listeners/darwin.rb +26 -7
- data/lib/guard/listeners/linux.rb +32 -8
- data/lib/guard/listeners/polling.rb +23 -5
- data/lib/guard/listeners/windows.rb +25 -6
- data/lib/guard/notifier.rb +78 -3
- data/lib/guard/ui.rb +125 -47
- data/lib/guard/version.rb +4 -1
- data/lib/guard/watcher.rb +48 -4
- data/man/guard.1 +1 -1
- data/man/guard.1.html +1 -1
- metadata +36 -13
data/lib/guard/group.rb
ADDED
@@ -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)
|
22
|
+
@group = options[:group] ? options.delete(:group).to_sym : :default
|
9
23
|
@watchers, @options = watchers, options
|
10
24
|
end
|
11
25
|
|
12
|
-
# Guardfile template
|
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
|
-
#
|
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
|
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
|
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
|
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
|
-
|
10
|
-
#
|
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
|
-
#
|
27
|
+
#
|
28
|
+
# @example Add a hook with a Symbol
|
29
|
+
#
|
13
30
|
# def run_all
|
14
31
|
# hook :foo
|
15
32
|
# end
|
16
|
-
#
|
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
|
37
|
+
# When event is a String, {#hook} will directly turn the String
|
20
38
|
# into a Symbol.
|
21
|
-
#
|
39
|
+
#
|
40
|
+
# @example Add a hook with a String
|
41
|
+
#
|
22
42
|
# def run_all
|
23
43
|
# hook "foo_bar"
|
24
44
|
# end
|
25
|
-
#
|
45
|
+
#
|
46
|
+
# When {Guard::Guard#run_all} is called, {#hook} will notify callbacks
|
26
47
|
# registered for the "foo_bar" event.
|
27
48
|
#
|
28
|
-
#
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
data/lib/guard/interactor.rb
CHANGED
@@ -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
|
-
|
30
|
+
|
31
|
+
@thread = Thread.new do
|
13
32
|
loop do
|
14
|
-
|
15
|
-
entry
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
data/lib/guard/listener.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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(*
|
32
|
+
Darwin.new(*args)
|
20
33
|
elsif linux? && Linux.usable?
|
21
|
-
Linux.new(*
|
34
|
+
Linux.new(*args)
|
22
35
|
elsif windows? && Windows.usable?
|
23
|
-
Windows.new(*
|
36
|
+
Windows.new(*args)
|
24
37
|
else
|
25
|
-
UI.info
|
26
|
-
Polling.new(*
|
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
|
32
|
-
@sha1_checksums_hash
|
33
|
-
@
|
34
|
-
@
|
35
|
-
@
|
36
|
-
@
|
37
|
-
@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
|
89
|
-
relativize_paths(files)
|
90
|
-
end
|
153
|
+
files.concat(potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) })
|
91
154
|
|
92
|
-
|
93
|
-
raise NotImplementedError, "should respond to #watch"
|
155
|
+
relativize_paths(files)
|
94
156
|
end
|
95
157
|
|
96
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|
-
#
|
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
|
-
|
213
|
+
private
|
125
214
|
|
126
|
-
|
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
|