sass 3.3.0 → 3.4.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.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +58 -50
  4. data/Rakefile +1 -4
  5. data/VERSION +1 -1
  6. data/VERSION_DATE +1 -1
  7. data/VERSION_NAME +1 -1
  8. data/bin/sass +1 -1
  9. data/bin/scss +1 -1
  10. data/lib/sass/cache_stores/filesystem.rb +6 -2
  11. data/lib/sass/css.rb +1 -3
  12. data/lib/sass/engine.rb +37 -46
  13. data/lib/sass/environment.rb +13 -17
  14. data/lib/sass/error.rb +6 -9
  15. data/lib/sass/exec/base.rb +187 -0
  16. data/lib/sass/exec/sass_convert.rb +264 -0
  17. data/lib/sass/exec/sass_scss.rb +424 -0
  18. data/lib/sass/exec.rb +5 -771
  19. data/lib/sass/features.rb +7 -0
  20. data/lib/sass/importers/base.rb +7 -2
  21. data/lib/sass/importers/filesystem.rb +9 -25
  22. data/lib/sass/importers.rb +0 -1
  23. data/lib/sass/media.rb +1 -4
  24. data/lib/sass/plugin/compiler.rb +200 -83
  25. data/lib/sass/plugin/staleness_checker.rb +1 -1
  26. data/lib/sass/plugin.rb +3 -3
  27. data/lib/sass/script/css_lexer.rb +1 -1
  28. data/lib/sass/script/functions.rb +622 -268
  29. data/lib/sass/script/lexer.rb +99 -34
  30. data/lib/sass/script/parser.rb +24 -23
  31. data/lib/sass/script/tree/funcall.rb +1 -1
  32. data/lib/sass/script/tree/interpolation.rb +20 -2
  33. data/lib/sass/script/tree/selector.rb +26 -0
  34. data/lib/sass/script/tree/string_interpolation.rb +1 -1
  35. data/lib/sass/script/tree.rb +1 -0
  36. data/lib/sass/script/value/base.rb +7 -5
  37. data/lib/sass/script/value/bool.rb +0 -5
  38. data/lib/sass/script/value/color.rb +39 -21
  39. data/lib/sass/script/value/helpers.rb +107 -0
  40. data/lib/sass/script/value/list.rb +0 -15
  41. data/lib/sass/script/value/null.rb +0 -5
  42. data/lib/sass/script/value/number.rb +62 -14
  43. data/lib/sass/script/value/string.rb +59 -11
  44. data/lib/sass/script/value.rb +0 -1
  45. data/lib/sass/scss/css_parser.rb +8 -2
  46. data/lib/sass/scss/parser.rb +190 -328
  47. data/lib/sass/scss/rx.rb +15 -6
  48. data/lib/sass/scss/static_parser.rb +298 -1
  49. data/lib/sass/selector/abstract_sequence.rb +28 -13
  50. data/lib/sass/selector/comma_sequence.rb +92 -13
  51. data/lib/sass/selector/pseudo.rb +256 -0
  52. data/lib/sass/selector/sequence.rb +94 -24
  53. data/lib/sass/selector/simple.rb +14 -25
  54. data/lib/sass/selector/simple_sequence.rb +97 -33
  55. data/lib/sass/selector.rb +57 -194
  56. data/lib/sass/shared.rb +1 -1
  57. data/lib/sass/source/map.rb +26 -12
  58. data/lib/sass/stack.rb +0 -6
  59. data/lib/sass/supports.rb +2 -3
  60. data/lib/sass/tree/at_root_node.rb +1 -0
  61. data/lib/sass/tree/charset_node.rb +1 -1
  62. data/lib/sass/tree/directive_node.rb +8 -2
  63. data/lib/sass/tree/error_node.rb +18 -0
  64. data/lib/sass/tree/extend_node.rb +1 -1
  65. data/lib/sass/tree/function_node.rb +4 -0
  66. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  67. data/lib/sass/tree/prop_node.rb +1 -1
  68. data/lib/sass/tree/rule_node.rb +12 -7
  69. data/lib/sass/tree/visitors/check_nesting.rb +38 -10
  70. data/lib/sass/tree/visitors/convert.rb +16 -18
  71. data/lib/sass/tree/visitors/cssize.rb +29 -29
  72. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  73. data/lib/sass/tree/visitors/perform.rb +45 -33
  74. data/lib/sass/tree/visitors/set_options.rb +14 -0
  75. data/lib/sass/tree/visitors/to_css.rb +15 -14
  76. data/lib/sass/util/subset_map.rb +1 -1
  77. data/lib/sass/util.rb +222 -99
  78. data/lib/sass/version.rb +5 -5
  79. data/lib/sass.rb +0 -5
  80. data/test/sass/cache_test.rb +62 -20
  81. data/test/sass/callbacks_test.rb +1 -1
  82. data/test/sass/compiler_test.rb +19 -10
  83. data/test/sass/conversion_test.rb +58 -1
  84. data/test/sass/css2sass_test.rb +23 -4
  85. data/test/sass/encoding_test.rb +219 -0
  86. data/test/sass/engine_test.rb +136 -199
  87. data/test/sass/exec_test.rb +2 -2
  88. data/test/sass/extend_test.rb +236 -19
  89. data/test/sass/functions_test.rb +295 -253
  90. data/test/sass/importer_test.rb +31 -21
  91. data/test/sass/logger_test.rb +1 -1
  92. data/test/sass/more_results/more_import.css +1 -1
  93. data/test/sass/plugin_test.rb +14 -13
  94. data/test/sass/results/compact.css +1 -1
  95. data/test/sass/results/complex.css +4 -4
  96. data/test/sass/results/expanded.css +1 -1
  97. data/test/sass/results/import.css +1 -1
  98. data/test/sass/results/import_charset_ibm866.css +2 -2
  99. data/test/sass/results/mixins.css +17 -17
  100. data/test/sass/results/nested.css +1 -1
  101. data/test/sass/results/parent_ref.css +2 -2
  102. data/test/sass/results/script.css +3 -3
  103. data/test/sass/results/scss_import.css +1 -1
  104. data/test/sass/script_conversion_test.rb +10 -7
  105. data/test/sass/script_test.rb +288 -74
  106. data/test/sass/scss/css_test.rb +141 -24
  107. data/test/sass/scss/rx_test.rb +4 -4
  108. data/test/sass/scss/scss_test.rb +457 -18
  109. data/test/sass/source_map_test.rb +115 -25
  110. data/test/sass/superselector_test.rb +191 -0
  111. data/test/sass/templates/scss_import.scss +2 -1
  112. data/test/sass/test_helper.rb +1 -1
  113. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  114. data/test/sass/util/normalized_map_test.rb +1 -1
  115. data/test/sass/util/subset_map_test.rb +2 -2
  116. data/test/sass/util_test.rb +31 -1
  117. data/test/sass/value_helpers_test.rb +5 -7
  118. data/test/test_helper.rb +2 -2
  119. data/vendor/listen/CHANGELOG.md +1 -228
  120. data/vendor/listen/Gemfile +5 -15
  121. data/vendor/listen/README.md +111 -77
  122. data/vendor/listen/Rakefile +0 -42
  123. data/vendor/listen/lib/listen/adapter.rb +195 -82
  124. data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
  125. data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
  126. data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
  127. data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
  128. data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
  129. data/vendor/listen/lib/listen/directory_record.rb +96 -61
  130. data/vendor/listen/lib/listen/listener.rb +135 -37
  131. data/vendor/listen/lib/listen/turnstile.rb +9 -5
  132. data/vendor/listen/lib/listen/version.rb +1 -1
  133. data/vendor/listen/lib/listen.rb +33 -19
  134. data/vendor/listen/listen.gemspec +6 -0
  135. data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
  136. data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
  137. data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
  138. data/vendor/listen/spec/listen/listener_spec.rb +128 -39
  139. data/vendor/listen/spec/listen_spec.rb +15 -21
  140. data/vendor/listen/spec/spec_helper.rb +4 -0
  141. data/vendor/listen/spec/support/adapter_helper.rb +52 -15
  142. data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
  143. data/vendor/listen/spec/support/listeners_helper.rb +30 -7
  144. metadata +25 -22
  145. data/ext/mkrf_conf.rb +0 -27
  146. data/lib/sass/importers/deprecated_path.rb +0 -51
  147. data/lib/sass/script/value/deprecated_false.rb +0 -55
  148. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  149. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  150. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  151. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
@@ -4,17 +4,12 @@ module Listen
4
4
  # Listener implementation for Linux `inotify`.
5
5
  #
6
6
  class Linux < Adapter
7
- extend DependencyManager
8
-
9
- # Declare the adapter's dependencies
10
- dependency 'rb-inotify', '~> 0.9'
11
-
12
7
  # Watched inotify events
13
8
  #
14
9
  # @see http://www.tin.org/bin/man.cgi?section=7&topic=inotify
15
10
  # @see https://github.com/nex3/rb-inotify/blob/master/lib/rb-inotify/notifier.rb#L99-L177
16
11
  #
17
- EVENTS = %w[recursive attrib create delete move close_write]
12
+ EVENTS = [:recursive, :attrib, :create, :delete, :move, :close_write]
18
13
 
19
14
  # The message to show when the limit of inotify watchers is not enough
20
15
  #
@@ -25,63 +20,31 @@ module Listen
25
20
  for information on how to solve this issue.
26
21
  EOS
27
22
 
28
- # Initializes the Adapter. See {Listen::Adapter#initialize} for more info.
23
+ def self.target_os_regex; /linux/i; end
24
+ def self.adapter_gem; 'rb-inotify'; end
25
+
26
+ # Initializes the Adapter.
27
+ #
28
+ # @see Listen::Adapter#initialize
29
29
  #
30
30
  def initialize(directories, options = {}, &callback)
31
31
  super
32
- @worker = init_worker
33
32
  rescue Errno::ENOSPC
34
33
  abort(INOTIFY_LIMIT_MESSAGE)
35
34
  end
36
35
 
37
- # Starts the adapter.
38
- #
39
- # @param [Boolean] blocking whether or not to block the current thread after starting
40
- #
41
- def start(blocking = true)
42
- @mutex.synchronize do
43
- return if @stop == false
44
- super
45
- end
46
-
47
- @worker_thread = Thread.new { @worker.run }
48
- @poll_thread = Thread.new { poll_changed_dirs } if @report_changes
49
-
50
- @worker_thread.join if blocking
51
- end
52
-
53
- # Stops the adapter.
54
- #
55
- def stop
56
- @mutex.synchronize do
57
- return if @stop == true
58
- super
59
- end
60
-
61
- @worker.stop
62
- Thread.kill(@worker_thread) if @worker_thread
63
- @poll_thread.join if @poll_thread
64
- end
65
-
66
- # Checks if the adapter is usable on the current OS.
67
- #
68
- # @return [Boolean] whether usable or not
69
- #
70
- def self.usable?
71
- return false unless RbConfig::CONFIG['target_os'] =~ /linux/i
72
- super
73
- end
74
-
75
- private
36
+ private
76
37
 
77
38
  # Initializes a INotify worker and adds a watcher for
78
39
  # each directory passed to the adapter.
79
40
  #
80
41
  # @return [INotify::Notifier] initialized worker
81
42
  #
82
- def init_worker
43
+ # @see Listen::Adapter#initialize_worker
44
+ #
45
+ def initialize_worker
83
46
  callback = lambda do |event|
84
- if @paused || (
47
+ if paused || (
85
48
  # Event on root directory
86
49
  event.name == ""
87
50
  ) || (
@@ -89,24 +52,29 @@ module Listen
89
52
  # on the directories themselves too.
90
53
  #
91
54
  # @see http://linux.die.net/man/7/inotify
92
- event.flags.include?(:isdir) and event.flags & [:close, :modify] != []
55
+ event.flags.include?(:isdir) and (event.flags & [:close, :modify]).any?
93
56
  )
94
57
  # Skip all of these!
95
58
  next
96
59
  end
97
60
 
98
- @mutex.synchronize do
99
- @changed_dirs << File.dirname(event.absolute_name)
61
+ mutex.synchronize do
62
+ @changed_directories << File.dirname(event.absolute_name)
100
63
  end
101
64
  end
102
65
 
103
66
  INotify::Notifier.new.tap do |worker|
104
- @directories.each do |directory|
105
- worker.watch(directory, *EVENTS.map(&:to_sym), &callback)
106
- end
67
+ directories.each { |dir| worker.watch(dir, *EVENTS, &callback) }
107
68
  end
108
69
  end
109
70
 
71
+ # Starts the worker in a new thread.
72
+ #
73
+ # @see Listen::Adapter#start_worker
74
+ #
75
+ def start_worker
76
+ @worker_thread = Thread.new { worker.run }
77
+ end
110
78
  end
111
79
 
112
80
  end
@@ -1,66 +1,57 @@
1
1
  module Listen
2
2
  module Adapters
3
3
 
4
- # The default delay between checking for changes.
5
4
  DEFAULT_POLLING_LATENCY = 1.0
6
5
 
7
6
  # Polling Adapter that works cross-platform and
8
7
  # has no dependencies. This is the adapter that
9
8
  # uses the most CPU processing power and has higher
10
- # file IO that the other implementations.
9
+ # file IO than the other implementations.
11
10
  #
12
11
  class Polling < Adapter
13
- extend DependencyManager
12
+ private
14
13
 
15
- # Initialize the Adapter. See {Listen::Adapter#initialize} for more info.
14
+ # The default delay between checking for changes.
16
15
  #
17
- def initialize(directories, options = {}, &callback)
18
- @latency ||= DEFAULT_POLLING_LATENCY
19
- super
16
+ # @see Listen::Adapter#default_latency
17
+ #
18
+ def default_latency
19
+ 1.0
20
20
  end
21
21
 
22
- # Start the adapter.
22
+ # The thread on which the main thread should wait
23
+ # when the adapter has been started in blocking mode.
23
24
  #
24
- # @param [Boolean] blocking whether or not to block the current thread after starting
25
+ # @see Listen::Adapter#blocking_thread
25
26
  #
26
- def start(blocking = true)
27
- @mutex.synchronize do
28
- return if @stop == false
29
- super
30
- end
31
-
32
- @poll_thread = Thread.new { poll }
33
- @poll_thread.join if blocking
27
+ def blocking_thread
28
+ poller_thread
34
29
  end
35
30
 
36
- # Stop the adapter.
31
+ # @see Listen::Adapter#start_worker
37
32
  #
38
- def stop
39
- @mutex.synchronize do
40
- return if @stop == true
41
- super
42
- end
43
-
44
- @poll_thread.join
33
+ # @see Listen::Adapter#start_worker
34
+ #
35
+ def start_worker
36
+ # The polling adapter has no worker! Sad panda! :'(
45
37
  end
46
38
 
47
- private
48
-
49
39
  # Poll listener directory for file system changes.
50
40
  #
51
- def poll
52
- until @stop
53
- next if @paused
41
+ # @see Listen::Adapter#poll_changed_directories
42
+ #
43
+ def poll_changed_directories
44
+ until stopped
45
+ next if paused
54
46
 
55
47
  start = Time.now.to_f
56
- @callback.call(@directories.dup, :recursive => true)
57
- @turnstile.signal
58
- nap_time = @latency - (Time.now.to_f - start)
48
+ callback.call(directories.dup, :recursive => true)
49
+ turnstile.signal
50
+ nap_time = latency - (Time.now.to_f - start)
59
51
  sleep(nap_time) if nap_time > 0
60
52
  end
61
53
  rescue Interrupt
62
54
  end
63
-
64
55
  end
65
56
 
66
57
  end
@@ -1,4 +1,5 @@
1
1
  require 'set'
2
+ require 'rubygems'
2
3
 
3
4
  module Listen
4
5
  module Adapters
@@ -6,81 +7,84 @@ module Listen
6
7
  # Adapter implementation for Windows `wdm`.
7
8
  #
8
9
  class Windows < Adapter
9
- extend DependencyManager
10
10
 
11
- # Declare the adapter's dependencies
12
- dependency 'wdm', '~> 0.1'
11
+ BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
12
+ Please add the following to your Gemfile to avoid polling for changes:
13
+ require 'rbconfig'
14
+ gem 'wdm', '>= 0.1.0' if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
15
+ EOS
13
16
 
14
- # Initializes the Adapter. See {Listen::Adapter#initialize} for more info.
17
+ def self.target_os_regex; /mswin|mingw/i; end
18
+ def self.adapter_gem; 'wdm'; end
19
+
20
+ # Checks if the adapter is usable on target OS.
15
21
  #
16
- def initialize(directories, options = {}, &callback)
17
- super
18
- @worker = init_worker
22
+ # @return [Boolean] whether usable or not
23
+ #
24
+ def self.usable?
25
+ super if mri? && at_least_ruby_1_9?
19
26
  end
20
27
 
21
- # Starts the adapter.
28
+ # Load the adapter gem
22
29
  #
23
- # @param [Boolean] blocking whether or not to block the current thread after starting
30
+ # @return [Boolean] whether required or not
24
31
  #
25
- def start(blocking = true)
26
- @mutex.synchronize do
27
- return if @stop == false
28
- super
29
- end
30
-
31
- @worker_thread = Thread.new { @worker.run! }
32
-
33
- # Wait for the worker to start. This is needed to avoid a deadlock
34
- # when stopping immediately after starting.
35
- sleep 0.1
36
-
37
- @poll_thread = Thread.new { poll_changed_dirs } if @report_changes
38
-
39
- @worker_thread.join if blocking
32
+ def self.load_dependent_adapter
33
+ super
34
+ rescue Gem::LoadError
35
+ Kernel.warn BUNDLER_DECLARE_GEM
40
36
  end
41
37
 
42
- # Stops the adapter.
43
- #
44
- def stop
45
- @mutex.synchronize do
46
- return if @stop == true
47
- super
48
- end
38
+ private
49
39
 
50
- @worker.stop
51
- @worker_thread.join if @worker_thread
52
- @poll_thread.join if @poll_thread
40
+ # Checks if Ruby engine is MRI.
41
+ #
42
+ # @return [Boolean]
43
+ #
44
+ def self.mri?
45
+ defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
53
46
  end
54
47
 
55
- # Checks if the adapter is usable on the current OS.
48
+ # Checks if Ruby engine is MRI.
56
49
  #
57
- # @return [Boolean] whether usable or not
50
+ # @return [Boolean]
58
51
  #
59
- def self.usable?
60
- return false unless RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
61
- super
52
+ def self.at_least_ruby_1_9?
53
+ Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('1.9.2')
62
54
  end
63
55
 
64
- private
65
-
66
56
  # Initializes a WDM monitor and adds a watcher for
67
57
  # each directory passed to the adapter.
68
58
  #
69
59
  # @return [WDM::Monitor] initialized worker
70
60
  #
71
- def init_worker
61
+ # @see Listen::Adapter#initialize_worker
62
+ #
63
+ def initialize_worker
72
64
  callback = Proc.new do |change|
73
- next if @paused
74
- @mutex.synchronize do
75
- @changed_dirs << File.dirname(change.path)
65
+ next if paused
66
+
67
+ mutex.synchronize do
68
+ @changed_directories << File.dirname(change.path)
76
69
  end
77
70
  end
78
71
 
79
72
  WDM::Monitor.new.tap do |worker|
80
- @directories.each { |d| worker.watch_recursively(d, &callback) }
73
+ directories.each { |dir| worker.watch_recursively(dir, &callback) }
81
74
  end
82
75
  end
83
76
 
77
+ # Start the worker in a new thread and sleep 0.1 second.
78
+ #
79
+ # @see Listen::Adapter#start_worker
80
+ #
81
+ def start_worker
82
+ @worker_thread = Thread.new { worker.run! }
83
+ # Wait for the worker to start. This is needed to avoid a deadlock
84
+ # when stopping immediately after starting.
85
+ sleep 0.1
86
+ end
87
+
84
88
  end
85
89
 
86
90
  end
@@ -11,8 +11,10 @@ module Listen
11
11
  class DirectoryRecord
12
12
  attr_reader :directory, :paths, :sha1_checksums
13
13
 
14
- DEFAULT_IGNORED_DIRECTORIES = %w[.rbx .bundle .git .svn log tmp vendor]
14
+ # The default list of directories that get ignored by the listener.
15
+ DEFAULT_IGNORED_DIRECTORIES = %w[.rbx .bundle .git .svn bundle log tmp vendor]
15
16
 
17
+ # The default list of files that get ignored by the listener.
16
18
  DEFAULT_IGNORED_EXTENSIONS = %w[.DS_Store]
17
19
 
18
20
  # Defines the used precision based on the type of mtime returned by the
@@ -56,15 +58,14 @@ module Listen
56
58
  def initialize(directory)
57
59
  raise ArgumentError, "The path '#{directory}' is not a directory!" unless File.directory?(directory)
58
60
 
59
- @directory = directory
60
- @ignoring_patterns = Set.new
61
- @filtering_patterns = Set.new
62
- @sha1_checksums = Hash.new
61
+ @directory, @sha1_checksums = File.expand_path(directory), Hash.new
62
+ @ignoring_patterns, @filtering_patterns = Set.new, Set.new
63
63
 
64
64
  @ignoring_patterns.merge(DirectoryRecord.generate_default_ignoring_patterns)
65
65
  end
66
66
 
67
- # Returns the ignoring patterns in the record
67
+ # Returns the ignoring patterns in the record to know
68
+ # which paths should be ignored.
68
69
  #
69
70
  # @return [Array<Regexp>] the ignoring patterns
70
71
  #
@@ -72,7 +73,7 @@ module Listen
72
73
  @ignoring_patterns.to_a
73
74
  end
74
75
 
75
- # Returns the filtering patterns used in the record to know
76
+ # Returns the filtering patterns in the record to know
76
77
  # which paths should be stored.
77
78
  #
78
79
  # @return [Array<Regexp>] the filtering patterns
@@ -86,10 +87,10 @@ module Listen
86
87
  # @example Ignore some paths
87
88
  # ignore %r{^ignored/path/}, /man/
88
89
  #
89
- # @param [Regexp] regexp a pattern for ignoring paths
90
+ # @param [Regexp] regexps a list of patterns for ignoring paths
90
91
  #
91
92
  def ignore(*regexps)
92
- @ignoring_patterns.merge(regexps)
93
+ @ignoring_patterns.merge(regexps).reject! { |r| r.nil? }
93
94
  end
94
95
 
95
96
  # Replaces ignoring patterns in the record.
@@ -97,37 +98,37 @@ module Listen
97
98
  # @example Ignore only these paths
98
99
  # ignore! %r{^ignored/path/}, /man/
99
100
  #
100
- # @param [Regexp] regexp a pattern for ignoring paths
101
+ # @param [Regexp] regexps a list of patterns for ignoring paths
101
102
  #
102
103
  def ignore!(*regexps)
103
- @ignoring_patterns.replace(regexps)
104
+ @ignoring_patterns.replace(regexps).reject! { |r| r.nil? }
104
105
  end
105
106
 
106
- # Adds filtering patterns to the listener.
107
+ # Adds filtering patterns to the record.
107
108
  #
108
109
  # @example Filter some files
109
- # ignore /\.txt$/, /.*\.zip/
110
+ # filter /\.txt$/, /.*\.zip/
110
111
  #
111
- # @param [Regexp] regexp a pattern for filtering paths
112
+ # @param [Regexp] regexps a list of patterns for filtering files
112
113
  #
113
114
  def filter(*regexps)
114
- @filtering_patterns.merge(regexps)
115
+ @filtering_patterns.merge(regexps).reject! { |r| r.nil? }
115
116
  end
116
117
 
117
- # Replaces filtering patterns in the listener.
118
+ # Replaces filtering patterns in the record.
118
119
  #
119
120
  # @example Filter only these files
120
- # ignore /\.txt$/, /.*\.zip/
121
+ # filter! /\.txt$/, /.*\.zip/
121
122
  #
122
- # @param [Regexp] regexp a pattern for filtering paths
123
+ # @param [Regexp] regexps a list of patterns for filtering files
123
124
  #
124
125
  def filter!(*regexps)
125
- @filtering_patterns.replace(regexps)
126
+ @filtering_patterns.replace(regexps).reject! { |r| r.nil? }
126
127
  end
127
128
 
128
129
  # Returns whether a path should be ignored or not.
129
130
  #
130
- # @param [String] path the path to test.
131
+ # @param [String] path the path to test
131
132
  #
132
133
  # @return [Boolean]
133
134
  #
@@ -138,7 +139,7 @@ module Listen
138
139
 
139
140
  # Returns whether a path should be filtered or not.
140
141
  #
141
- # @param [String] path the path to test.
142
+ # @param [String] path the path to test
142
143
  #
143
144
  # @return [Boolean]
144
145
  #
@@ -159,9 +160,9 @@ module Listen
159
160
  end
160
161
 
161
162
  # Detects changes in the passed directories, updates
162
- # the record with the new changes and returns the changes
163
+ # the record with the new changes and returns the changes.
163
164
  #
164
- # @param [Array] directories the list of directories scan for changes
165
+ # @param [Array] directories the list of directories to scan for changes
165
166
  # @param [Hash] options
166
167
  # @option options [Boolean] recursive scan all sub-directories recursively
167
168
  # @option options [Boolean] relative_paths whether or not to use relative paths for changes
@@ -174,6 +175,7 @@ module Listen
174
175
 
175
176
  directories.each do |directory|
176
177
  next unless directory[@directory] # Path is or inside directory
178
+
177
179
  detect_modifications_and_removals(directory, options)
178
180
  detect_additions(directory, options)
179
181
  end
@@ -188,9 +190,15 @@ module Listen
188
190
  # @return [String] the relative path
189
191
  #
190
192
  def relative_to_base(path)
191
- return nil unless path[@directory]
192
- path = path.force_encoding("BINARY") if path.respond_to?(:force_encoding)
193
- path.sub(%r{^#{Regexp.quote(@directory)}#{File::SEPARATOR}?}, '')
193
+ path = path.dup
194
+ regexp = "\\A#{Regexp.quote directory}(#{File::SEPARATOR}|\\z)"
195
+ if path.respond_to?(:force_encoding)
196
+ path.force_encoding("BINARY")
197
+ regexp.force_encoding("BINARY")
198
+ end
199
+ if path.sub!(Regexp.new(regexp), '')
200
+ path
201
+ end
194
202
  end
195
203
 
196
204
  private
@@ -207,43 +215,69 @@ module Listen
207
215
  # @option options [Boolean] relative_paths whether or not to use relative paths for changes
208
216
  #
209
217
  def detect_modifications_and_removals(directory, options = {})
210
- @paths[directory].each do |basename, meta_data|
218
+ paths[directory].each do |basename, meta_data|
211
219
  path = File.join(directory, basename)
212
-
213
220
  case meta_data.type
214
221
  when 'Dir'
215
- if File.directory?(path)
216
- detect_modifications_and_removals(path, options) if options[:recursive]
217
- else
218
- detect_modifications_and_removals(path, { :recursive => true }.merge(options))
219
- @paths[directory].delete(basename)
220
- @paths.delete("#{directory}/#{basename}")
221
- end
222
+ detect_modification_or_removal_for_dir(path, options)
222
223
  when 'File'
223
- if File.exist?(path)
224
- new_mtime = mtime_of(path)
224
+ detect_modification_or_removal_for_file(path, meta_data, options)
225
+ end
226
+ end
227
+ end
225
228
 
226
- # First check if we are in the same second (to update checksums)
227
- # before checking the time difference
228
- if (meta_data.mtime.to_i == new_mtime.to_i && content_modified?(path)) || meta_data.mtime < new_mtime
229
- # Update the sha1 checksum of the file
230
- insert_sha1_checksum(path)
229
+ def detect_modification_or_removal_for_dir(path, options)
231
230
 
232
- # Update the meta data of the file
233
- meta_data.mtime = new_mtime
234
- @paths[directory][basename] = meta_data
231
+ # Directory still exists
232
+ if File.directory?(path)
233
+ detect_modifications_and_removals(path, options) if options[:recursive]
235
234
 
236
- @changes[:modified] << (options[:relative_paths] ? relative_to_base(path) : path)
237
- end
238
- else
239
- @paths[directory].delete(basename)
240
- @sha1_checksums.delete(path)
241
- @changes[:removed] << (options[:relative_paths] ? relative_to_base(path) : path)
242
- end
243
- end
235
+ # Directory has been removed
236
+ else
237
+ detect_modifications_and_removals(path, options)
238
+ @paths[File.dirname(path)].delete(File.basename(path))
239
+ @paths.delete("#{File.dirname(path)}/#{File.basename(path)}")
240
+ end
241
+ end
242
+
243
+ def detect_modification_or_removal_for_file(path, meta_data, options)
244
+ # File still exists
245
+ if File.exist?(path)
246
+ detect_modification(path, meta_data, options)
247
+
248
+ # File has been removed
249
+ else
250
+ removal_detected(path, meta_data, options)
251
+ end
252
+ end
253
+
254
+ def detect_modification(path, meta_data, options)
255
+ new_mtime = mtime_of(path)
256
+
257
+ # First check if we are in the same second (to update checksums)
258
+ # before checking the time difference
259
+ if (meta_data.mtime.to_i == new_mtime.to_i && content_modified?(path)) || meta_data.mtime < new_mtime
260
+ modification_detected(path, meta_data, new_mtime, options)
244
261
  end
245
262
  end
246
263
 
264
+ def modification_detected(path, meta_data, new_mtime, options)
265
+ # Update the sha1 checksum of the file
266
+ update_sha1_checksum(path)
267
+
268
+ # Update the meta data of the file
269
+ meta_data.mtime = new_mtime
270
+ @paths[File.dirname(path)][File.basename(path)] = meta_data
271
+
272
+ @changes[:modified] << (options[:relative_paths] ? relative_to_base(path) : path)
273
+ end
274
+
275
+ def removal_detected(path, meta_data, options)
276
+ @paths[File.dirname(path)].delete(File.basename(path))
277
+ @sha1_checksums.delete(path)
278
+ @changes[:removed] << (options[:relative_paths] ? relative_to_base(path) : path)
279
+ end
280
+
247
281
  # Detects additions in a directory.
248
282
  #
249
283
  # @param [String] directory the path to analyze
@@ -283,9 +317,10 @@ module Listen
283
317
  # @param [String] path the file path
284
318
  #
285
319
  def content_modified?(path)
320
+ return false unless File.ftype(path) == 'file'
286
321
  @sha1_checksum = sha1_checksum(path)
287
- if @sha1_checksums[path] == @sha1_checksum || !@sha1_checksums.key?(path)
288
- insert_sha1_checksum(path)
322
+ if sha1_checksums[path] == @sha1_checksum || !sha1_checksums.key?(path)
323
+ update_sha1_checksum(path)
289
324
  false
290
325
  else
291
326
  true
@@ -296,7 +331,7 @@ module Listen
296
331
  #
297
332
  # @param [String] path the SHA1-checksum path to insert in @sha1_checksums.
298
333
  #
299
- def insert_sha1_checksum(path)
334
+ def update_sha1_checksum(path)
300
335
  if @sha1_checksum ||= sha1_checksum(path)
301
336
  @sha1_checksums[path] = @sha1_checksum
302
337
  @sha1_checksum = nil
@@ -309,18 +344,18 @@ module Listen
309
344
  #
310
345
  def sha1_checksum(path)
311
346
  Digest::SHA1.file(path).to_s
312
- rescue Errno::EACCES, Errno::ENOENT, Errno::ENXIO, Errno::EOPNOTSUPP
347
+ rescue
313
348
  nil
314
349
  end
315
350
 
316
351
  # Traverses the base directory looking for paths that should
317
- # be stored; thus paths that are filters or not ignored.
352
+ # be stored; thus paths that are filtered or not ignored.
318
353
  #
319
354
  # @yield [path] an important path
320
355
  #
321
356
  def important_paths
322
- Find.find(@directory) do |path|
323
- next if path == @directory
357
+ Find.find(directory) do |path|
358
+ next if path == directory
324
359
 
325
360
  if File.directory?(path)
326
361
  # Add a trailing slash to directories when checking if a directory is
@@ -355,7 +390,7 @@ module Listen
355
390
  # @return [Boolean]
356
391
  #
357
392
  def existing_path?(path)
358
- @paths[File.dirname(path)][File.basename(path)] != nil
393
+ paths[File.dirname(path)][File.basename(path)] != nil
359
394
  end
360
395
 
361
396
  # Returns the modification time of a file based on the precision defined by the system