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