sass 3.7.4 → 4.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (257) hide show
  1. checksums.yaml +13 -5
  2. data/.yardopts +1 -1
  3. data/CODE_OF_CONDUCT.md +1 -1
  4. data/CONTRIBUTING.md +1 -146
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +25 -39
  7. data/Rakefile +274 -0
  8. data/VERSION +1 -1
  9. data/VERSION_DATE +1 -1
  10. data/lib/sass.rb +3 -3
  11. data/lib/sass/cache_stores/filesystem.rb +2 -2
  12. data/lib/sass/cache_stores/memory.rb +5 -4
  13. data/lib/sass/callbacks.rb +2 -2
  14. data/lib/sass/css.rb +12 -12
  15. data/lib/sass/engine.rb +44 -62
  16. data/lib/sass/environment.rb +7 -35
  17. data/lib/sass/error.rb +14 -14
  18. data/lib/sass/exec/base.rb +14 -3
  19. data/lib/sass/exec/sass_convert.rb +6 -20
  20. data/lib/sass/exec/sass_scss.rb +29 -5
  21. data/lib/sass/features.rb +2 -3
  22. data/lib/sass/importers/filesystem.rb +6 -11
  23. data/lib/sass/logger.rb +3 -8
  24. data/lib/sass/logger/base.rb +2 -19
  25. data/lib/sass/plugin.rb +2 -3
  26. data/lib/sass/plugin/compiler.rb +67 -48
  27. data/lib/sass/plugin/configuration.rb +3 -3
  28. data/lib/sass/plugin/merb.rb +1 -1
  29. data/lib/sass/plugin/rack.rb +3 -3
  30. data/lib/sass/plugin/staleness_checker.rb +3 -3
  31. data/lib/sass/railtie.rb +1 -1
  32. data/lib/sass/script.rb +3 -3
  33. data/lib/sass/script/css_parser.rb +15 -5
  34. data/lib/sass/script/functions.rb +121 -337
  35. data/lib/sass/script/lexer.rb +36 -102
  36. data/lib/sass/script/parser.rb +153 -529
  37. data/lib/sass/script/tree/funcall.rb +34 -42
  38. data/lib/sass/script/tree/interpolation.rb +26 -171
  39. data/lib/sass/script/tree/list_literal.rb +8 -23
  40. data/lib/sass/script/tree/map_literal.rb +2 -2
  41. data/lib/sass/script/tree/node.rb +3 -3
  42. data/lib/sass/script/tree/operation.rb +16 -43
  43. data/lib/sass/script/tree/string_interpolation.rb +43 -64
  44. data/lib/sass/script/tree/variable.rb +1 -1
  45. data/lib/sass/script/value.rb +0 -2
  46. data/lib/sass/script/value/arg_list.rb +1 -1
  47. data/lib/sass/script/value/base.rb +9 -27
  48. data/lib/sass/script/value/color.rb +18 -26
  49. data/lib/sass/script/value/helpers.rb +18 -44
  50. data/lib/sass/script/value/list.rb +14 -35
  51. data/lib/sass/script/value/map.rb +2 -2
  52. data/lib/sass/script/value/number.rb +16 -26
  53. data/lib/sass/script/value/string.rb +1 -30
  54. data/lib/sass/scss.rb +2 -0
  55. data/lib/sass/scss/css_parser.rb +3 -7
  56. data/lib/sass/scss/parser.rb +78 -196
  57. data/lib/sass/scss/rx.rb +14 -7
  58. data/lib/sass/scss/script_lexer.rb +15 -0
  59. data/lib/sass/scss/script_parser.rb +25 -0
  60. data/lib/sass/scss/static_parser.rb +55 -38
  61. data/lib/sass/selector.rb +10 -7
  62. data/lib/sass/selector/abstract_sequence.rb +12 -15
  63. data/lib/sass/selector/comma_sequence.rb +6 -24
  64. data/lib/sass/selector/pseudo.rb +6 -19
  65. data/lib/sass/selector/sequence.rb +16 -14
  66. data/lib/sass/selector/simple.rb +7 -9
  67. data/lib/sass/selector/simple_sequence.rb +12 -16
  68. data/lib/sass/shared.rb +1 -1
  69. data/lib/sass/source/map.rb +9 -7
  70. data/lib/sass/source/position.rb +4 -4
  71. data/lib/sass/stack.rb +3 -23
  72. data/lib/sass/tree/charset_node.rb +1 -1
  73. data/lib/sass/tree/comment_node.rb +1 -1
  74. data/lib/sass/tree/function_node.rb +3 -2
  75. data/lib/sass/tree/node.rb +3 -5
  76. data/lib/sass/tree/prop_node.rb +58 -49
  77. data/lib/sass/tree/rule_node.rb +8 -15
  78. data/lib/sass/tree/visitors/check_nesting.rb +23 -19
  79. data/lib/sass/tree/visitors/convert.rb +13 -15
  80. data/lib/sass/tree/visitors/cssize.rb +15 -4
  81. data/lib/sass/tree/visitors/deep_copy.rb +2 -2
  82. data/lib/sass/tree/visitors/extend.rb +14 -10
  83. data/lib/sass/tree/visitors/perform.rb +18 -29
  84. data/lib/sass/tree/visitors/set_options.rb +2 -2
  85. data/lib/sass/tree/visitors/to_css.rb +47 -77
  86. data/lib/sass/util.rb +311 -98
  87. data/lib/sass/util/cross_platform_random.rb +19 -0
  88. data/lib/sass/util/multibyte_string_scanner.rb +133 -127
  89. data/lib/sass/util/normalized_map.rb +8 -1
  90. data/lib/sass/util/ordered_hash.rb +192 -0
  91. data/lib/sass/version.rb +6 -2
  92. data/test/sass/cache_test.rb +131 -0
  93. data/test/sass/callbacks_test.rb +61 -0
  94. data/test/sass/compiler_test.rb +236 -0
  95. data/test/sass/conversion_test.rb +2171 -0
  96. data/test/sass/css2sass_test.rb +526 -0
  97. data/test/sass/data/hsl-rgb.txt +319 -0
  98. data/test/sass/encoding_test.rb +219 -0
  99. data/test/sass/engine_test.rb +3400 -0
  100. data/test/sass/exec_test.rb +86 -0
  101. data/test/sass/extend_test.rb +1719 -0
  102. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  103. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  104. data/test/sass/functions_test.rb +1984 -0
  105. data/test/sass/importer_test.rb +421 -0
  106. data/test/sass/logger_test.rb +58 -0
  107. data/test/sass/mock_importer.rb +49 -0
  108. data/test/sass/more_results/more1.css +9 -0
  109. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  110. data/test/sass/more_results/more_import.css +29 -0
  111. data/test/sass/more_templates/_more_partial.sass +2 -0
  112. data/test/sass/more_templates/more1.sass +23 -0
  113. data/test/sass/more_templates/more_import.sass +11 -0
  114. data/test/sass/plugin_test.rb +556 -0
  115. data/test/sass/results/alt.css +4 -0
  116. data/test/sass/results/basic.css +9 -0
  117. data/test/sass/results/cached_import_option.css +3 -0
  118. data/test/sass/results/compact.css +5 -0
  119. data/test/sass/results/complex.css +86 -0
  120. data/test/sass/results/compressed.css +1 -0
  121. data/test/sass/results/expanded.css +19 -0
  122. data/test/sass/results/filename_fn.css +3 -0
  123. data/test/sass/results/if.css +3 -0
  124. data/test/sass/results/import.css +31 -0
  125. data/test/sass/results/import_charset.css +5 -0
  126. data/test/sass/results/import_charset_1_8.css +5 -0
  127. data/test/sass/results/import_charset_ibm866.css +5 -0
  128. data/test/sass/results/import_content.css +1 -0
  129. data/test/sass/results/line_numbers.css +49 -0
  130. data/test/sass/results/mixins.css +95 -0
  131. data/test/sass/results/multiline.css +24 -0
  132. data/test/sass/results/nested.css +22 -0
  133. data/test/sass/results/options.css +1 -0
  134. data/test/sass/results/parent_ref.css +13 -0
  135. data/test/sass/results/script.css +16 -0
  136. data/test/sass/results/scss_import.css +31 -0
  137. data/test/sass/results/scss_importee.css +2 -0
  138. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  139. data/test/sass/results/subdir/subdir.css +3 -0
  140. data/test/sass/results/units.css +11 -0
  141. data/test/sass/results/warn.css +0 -0
  142. data/test/sass/results/warn_imported.css +0 -0
  143. data/test/sass/script_conversion_test.rb +306 -0
  144. data/test/sass/script_test.rb +1206 -0
  145. data/test/sass/scss/css_test.rb +1281 -0
  146. data/test/sass/scss/rx_test.rb +160 -0
  147. data/test/sass/scss/scss_test.rb +4147 -0
  148. data/test/sass/scss/test_helper.rb +37 -0
  149. data/test/sass/source_map_test.rb +1055 -0
  150. data/test/sass/superselector_test.rb +210 -0
  151. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  152. data/test/sass/templates/_double_import_loop2.sass +1 -0
  153. data/test/sass/templates/_filename_fn_import.scss +11 -0
  154. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  155. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  156. data/test/sass/templates/_imported_content.sass +3 -0
  157. data/test/sass/templates/_partial.sass +2 -0
  158. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  159. data/test/sass/templates/alt.sass +16 -0
  160. data/test/sass/templates/basic.sass +23 -0
  161. data/test/sass/templates/bork1.sass +2 -0
  162. data/test/sass/templates/bork2.sass +2 -0
  163. data/test/sass/templates/bork3.sass +2 -0
  164. data/test/sass/templates/bork4.sass +2 -0
  165. data/test/sass/templates/bork5.sass +3 -0
  166. data/test/sass/templates/cached_import_option.scss +3 -0
  167. data/test/sass/templates/compact.sass +17 -0
  168. data/test/sass/templates/complex.sass +305 -0
  169. data/test/sass/templates/compressed.sass +15 -0
  170. data/test/sass/templates/double_import_loop1.sass +1 -0
  171. data/test/sass/templates/expanded.sass +17 -0
  172. data/test/sass/templates/filename_fn.scss +18 -0
  173. data/test/sass/templates/if.sass +11 -0
  174. data/test/sass/templates/import.sass +12 -0
  175. data/test/sass/templates/import_charset.sass +9 -0
  176. data/test/sass/templates/import_charset_1_8.sass +6 -0
  177. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  178. data/test/sass/templates/import_content.sass +4 -0
  179. data/test/sass/templates/importee.less +2 -0
  180. data/test/sass/templates/importee.sass +19 -0
  181. data/test/sass/templates/line_numbers.sass +13 -0
  182. data/test/sass/templates/mixin_bork.sass +5 -0
  183. data/test/sass/templates/mixins.sass +76 -0
  184. data/test/sass/templates/multiline.sass +20 -0
  185. data/test/sass/templates/nested.sass +25 -0
  186. data/test/sass/templates/nested_bork1.sass +2 -0
  187. data/test/sass/templates/nested_bork2.sass +2 -0
  188. data/test/sass/templates/nested_bork3.sass +2 -0
  189. data/test/sass/templates/nested_bork4.sass +2 -0
  190. data/test/sass/templates/nested_import.sass +2 -0
  191. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  192. data/test/sass/templates/options.sass +2 -0
  193. data/test/sass/templates/parent_ref.sass +25 -0
  194. data/test/sass/templates/same_name_different_ext.sass +2 -0
  195. data/test/sass/templates/same_name_different_ext.scss +1 -0
  196. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  197. data/test/sass/templates/script.sass +101 -0
  198. data/test/sass/templates/scss_import.scss +12 -0
  199. data/test/sass/templates/scss_importee.scss +1 -0
  200. data/test/sass/templates/single_import_loop.sass +1 -0
  201. data/test/sass/templates/subdir/import_up1.scss +1 -0
  202. data/test/sass/templates/subdir/import_up2.scss +1 -0
  203. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  204. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  205. data/test/sass/templates/subdir/subdir.sass +6 -0
  206. data/test/sass/templates/units.sass +11 -0
  207. data/test/sass/templates/warn.sass +3 -0
  208. data/test/sass/templates/warn_imported.sass +4 -0
  209. data/test/sass/test_helper.rb +8 -0
  210. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  211. data/test/sass/util/normalized_map_test.rb +51 -0
  212. data/test/sass/util/subset_map_test.rb +91 -0
  213. data/test/sass/util_test.rb +438 -0
  214. data/test/sass/value_helpers_test.rb +179 -0
  215. data/test/test_helper.rb +110 -0
  216. data/vendor/listen/CHANGELOG.md +1 -0
  217. data/vendor/listen/CONTRIBUTING.md +38 -0
  218. data/vendor/listen/Gemfile +20 -0
  219. data/vendor/listen/Guardfile +8 -0
  220. data/vendor/listen/LICENSE +20 -0
  221. data/vendor/listen/README.md +349 -0
  222. data/vendor/listen/Rakefile +5 -0
  223. data/vendor/listen/Vagrantfile +96 -0
  224. data/vendor/listen/lib/listen.rb +54 -0
  225. data/vendor/listen/lib/listen/adapter.rb +327 -0
  226. data/vendor/listen/lib/listen/adapters/bsd.rb +75 -0
  227. data/vendor/listen/lib/listen/adapters/darwin.rb +48 -0
  228. data/vendor/listen/lib/listen/adapters/linux.rb +81 -0
  229. data/vendor/listen/lib/listen/adapters/polling.rb +58 -0
  230. data/vendor/listen/lib/listen/adapters/windows.rb +91 -0
  231. data/vendor/listen/lib/listen/directory_record.rb +406 -0
  232. data/vendor/listen/lib/listen/listener.rb +323 -0
  233. data/vendor/listen/lib/listen/turnstile.rb +32 -0
  234. data/vendor/listen/lib/listen/version.rb +3 -0
  235. data/vendor/listen/listen.gemspec +28 -0
  236. data/vendor/listen/spec/listen/adapter_spec.rb +149 -0
  237. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +36 -0
  238. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +37 -0
  239. data/vendor/listen/spec/listen/adapters/linux_spec.rb +47 -0
  240. data/vendor/listen/spec/listen/adapters/polling_spec.rb +68 -0
  241. data/vendor/listen/spec/listen/adapters/windows_spec.rb +30 -0
  242. data/vendor/listen/spec/listen/directory_record_spec.rb +1250 -0
  243. data/vendor/listen/spec/listen/listener_spec.rb +258 -0
  244. data/vendor/listen/spec/listen/turnstile_spec.rb +56 -0
  245. data/vendor/listen/spec/listen_spec.rb +67 -0
  246. data/vendor/listen/spec/spec_helper.rb +25 -0
  247. data/vendor/listen/spec/support/adapter_helper.rb +666 -0
  248. data/vendor/listen/spec/support/directory_record_helper.rb +57 -0
  249. data/vendor/listen/spec/support/fixtures_helper.rb +29 -0
  250. data/vendor/listen/spec/support/listeners_helper.rb +179 -0
  251. data/vendor/listen/spec/support/platform_helper.rb +15 -0
  252. metadata +217 -76
  253. data/extra/sass-spec-ref.sh +0 -40
  254. data/lib/sass/deprecation.rb +0 -55
  255. data/lib/sass/logger/delayed.rb +0 -50
  256. data/lib/sass/script/value/callable.rb +0 -25
  257. data/lib/sass/script/value/function.rb +0 -19
@@ -0,0 +1,323 @@
1
+ require 'pathname'
2
+
3
+ module Listen
4
+ class Listener
5
+ attr_reader :directories, :directories_records, :block, :adapter, :adapter_options, :use_relative_paths
6
+
7
+ BLOCKING_PARAMETER_DEPRECATION_MESSAGE = <<-EOS.gsub(/^\s*/, '')
8
+ The blocking parameter of Listen::Listener#start is deprecated.\n
9
+ Please use Listen::Adapter#start for a non-blocking listener and Listen::Listener#start! for a blocking one.
10
+ EOS
11
+
12
+ RELATIVE_PATHS_WITH_MULTIPLE_DIRECTORIES_WARNING_MESSAGE = "The relative_paths option doesn't work when listening to multiple diretories."
13
+
14
+ # Initializes the directories listener.
15
+ #
16
+ # @param [String] directory the directories to listen to
17
+ # @param [Hash] options the listen options
18
+ # @option options [Regexp] ignore a pattern for ignoring paths
19
+ # @option options [Regexp] filter a pattern for filtering paths
20
+ # @option options [Float] latency the delay between checking for changes in seconds
21
+ # @option options [Boolean] relative_paths whether or not to use relative-paths in the callback
22
+ # @option options [Boolean] force_polling whether to force the polling adapter or not
23
+ # @option options [String, Boolean] polling_fallback_message to change polling fallback message or remove it
24
+ # @option options [Class] force_adapter force the use of this adapter class, skipping usual adapter selection
25
+ #
26
+ # @yield [modified, added, removed] the changed files
27
+ # @yieldparam [Array<String>] modified the list of modified files
28
+ # @yieldparam [Array<String>] added the list of added files
29
+ # @yieldparam [Array<String>] removed the list of removed files
30
+ #
31
+ def initialize(*args, &block)
32
+ options = args.last.is_a?(Hash) ? args.pop : {}
33
+ directories = args.flatten
34
+ initialize_directories_and_directories_records(directories)
35
+ initialize_relative_paths_usage(options)
36
+ @block = block
37
+
38
+ ignore(*options.delete(:ignore))
39
+ filter(*options.delete(:filter))
40
+
41
+ @adapter_options = options
42
+ end
43
+
44
+ # Starts the listener by initializing the adapter and building
45
+ # the directory record concurrently, then it starts the adapter to watch
46
+ # for changes. The current thread is not blocked after starting.
47
+ #
48
+ # @see Listen::Listener#start!
49
+ #
50
+ def start(deprecated_blocking = nil)
51
+ Kernel.warn "[Listen warning]:\n#{BLOCKING_PARAMETER_DEPRECATION_MESSAGE}" unless deprecated_blocking.nil?
52
+ setup
53
+ adapter.start
54
+ end
55
+
56
+ # Starts the listener by initializing the adapter and building
57
+ # the directory record concurrently, then it starts the adapter to watch
58
+ # for changes. The current thread is blocked after starting.
59
+ #
60
+ # @see Listen::Listener#start
61
+ #
62
+ # @since 1.0.0
63
+ #
64
+ def start!
65
+ setup
66
+ adapter.start!
67
+ end
68
+
69
+ # Stops the listener.
70
+ #
71
+ def stop
72
+ adapter && adapter.stop
73
+ end
74
+
75
+ # Pauses the listener.
76
+ #
77
+ # @return [Listen::Listener] the listener
78
+ #
79
+ def pause
80
+ adapter.pause
81
+ self
82
+ end
83
+
84
+ # Unpauses the listener.
85
+ #
86
+ # @return [Listen::Listener] the listener
87
+ #
88
+ def unpause
89
+ build_directories_records
90
+ adapter.unpause
91
+ self
92
+ end
93
+
94
+ # Returns whether the listener is paused or not.
95
+ #
96
+ # @return [Boolean] adapter paused status
97
+ #
98
+ def paused?
99
+ !!adapter && adapter.paused?
100
+ end
101
+
102
+ # Adds ignoring patterns to the listener.
103
+ #
104
+ # @param (see Listen::DirectoryRecord#ignore)
105
+ #
106
+ # @return [Listen::Listener] the listener
107
+ #
108
+ # @see Listen::DirectoryRecord#ignore
109
+ #
110
+ def ignore(*regexps)
111
+ directories_records.each { |r| r.ignore(*regexps) }
112
+ self
113
+ end
114
+
115
+ # Replaces ignoring patterns in the listener.
116
+ #
117
+ # @param (see Listen::DirectoryRecord#ignore!)
118
+ #
119
+ # @return [Listen::Listener] the listener
120
+ #
121
+ # @see Listen::DirectoryRecord#ignore!
122
+ #
123
+ def ignore!(*regexps)
124
+ directories_records.each { |r| r.ignore!(*regexps) }
125
+ self
126
+ end
127
+
128
+ # Adds filtering patterns to the listener.
129
+ #
130
+ # @param (see Listen::DirectoryRecord#filter)
131
+ #
132
+ # @return [Listen::Listener] the listener
133
+ #
134
+ # @see Listen::DirectoryRecord#filter
135
+ #
136
+ def filter(*regexps)
137
+ directories_records.each { |r| r.filter(*regexps) }
138
+ self
139
+ end
140
+
141
+ # Replaces filtering patterns in the listener.
142
+ #
143
+ # @param (see Listen::DirectoryRecord#filter!)
144
+ #
145
+ # @return [Listen::Listener] the listener
146
+ #
147
+ # @see Listen::DirectoryRecord#filter!
148
+ #
149
+ def filter!(*regexps)
150
+ directories_records.each { |r| r.filter!(*regexps) }
151
+ self
152
+ end
153
+
154
+ # Sets the latency for the adapter. This is a helper method
155
+ # to simplify changing the latency directly from the listener.
156
+ #
157
+ # @example Wait 0.5 seconds each time before checking changes
158
+ # latency 0.5
159
+ #
160
+ # @param [Float] seconds the amount of delay, in seconds
161
+ #
162
+ # @return [Listen::Listener] the listener
163
+ #
164
+ def latency(seconds)
165
+ @adapter_options[:latency] = seconds
166
+ self
167
+ end
168
+
169
+ # Sets whether the use of the polling adapter
170
+ # should be forced or not.
171
+ #
172
+ # @example Forcing the use of the polling adapter
173
+ # force_polling true
174
+ #
175
+ # @param [Boolean] value whether to force the polling adapter or not
176
+ #
177
+ # @return [Listen::Listener] the listener
178
+ #
179
+ def force_polling(value)
180
+ @adapter_options[:force_polling] = value
181
+ self
182
+ end
183
+
184
+ # Sets whether to force the use of a particular adapter, rather than
185
+ # going through usual adapter selection process on start.
186
+ #
187
+ # @example Force use of Linux polling
188
+ # force_adapter Listen::Adapters::Linux
189
+ #
190
+ # @param [Class] adapter class to use for file system event notification.
191
+ #
192
+ # @return [Listen::Listener] the listener
193
+ #
194
+ def force_adapter(adapter_class)
195
+ @adapter_options[:force_adapter] = adapter_class
196
+ self
197
+ end
198
+
199
+ # Sets whether the paths in the callback should be
200
+ # relative or absolute.
201
+ #
202
+ # @example Enabling relative paths in the callback
203
+ # relative_paths true
204
+ #
205
+ # @param [Boolean] value whether to enable relative paths in the callback or not
206
+ #
207
+ # @return [Listen::Listener] the listener
208
+ #
209
+ def relative_paths(value)
210
+ @use_relative_paths = value
211
+ self
212
+ end
213
+
214
+ # Defines a custom polling fallback message or disable it.
215
+ #
216
+ # @example Disabling the polling fallback message
217
+ # polling_fallback_message false
218
+ #
219
+ # @param [String, Boolean] value to change polling fallback message or remove it
220
+ #
221
+ # @return [Listen::Listener] the listener
222
+ #
223
+ def polling_fallback_message(value)
224
+ @adapter_options[:polling_fallback_message] = value
225
+ self
226
+ end
227
+
228
+ # Sets the callback that gets called on changes.
229
+ #
230
+ # @example Assign a callback to be called on changes
231
+ # callback = lambda { |modified, added, removed| ... }
232
+ # change &callback
233
+ #
234
+ # @param [Proc] block the callback proc
235
+ #
236
+ # @return [Listen::Listener] the listener
237
+ #
238
+ def change(&block) # modified, added, removed
239
+ @block = block
240
+ self
241
+ end
242
+
243
+ # Runs the callback passing it the changes if there are any.
244
+ #
245
+ # @param (see Listen::DirectoryRecord#fetch_changes)
246
+ #
247
+ # @see Listen::DirectoryRecord#fetch_changes
248
+ #
249
+ def on_change(directories, options = {})
250
+ changes = fetch_records_changes(directories, options)
251
+ unless changes.values.all? { |paths| paths.empty? }
252
+ block.call(changes[:modified], changes[:added], changes[:removed])
253
+ end
254
+ rescue => ex
255
+ Kernel.warn "[Listen warning]: Change block raise an execption: #{$!}"
256
+ Kernel.warn "Backtrace:\n\t#{ex.backtrace.join("\n\t")}"
257
+ end
258
+
259
+ private
260
+
261
+ # Initializes the directories to watch as well as the directories records.
262
+ #
263
+ # @see Listen::DirectoryRecord
264
+ #
265
+ def initialize_directories_and_directories_records(directories)
266
+ @directories = directories.map { |d| Pathname.new(d).realpath.to_s }
267
+ @directories_records = directories.map { |d| DirectoryRecord.new(d) }
268
+ end
269
+
270
+ # Initializes whether or not using relative paths.
271
+ #
272
+ def initialize_relative_paths_usage(options)
273
+ if directories.size > 1 && options[:relative_paths]
274
+ Kernel.warn "[Listen warning]: #{RELATIVE_PATHS_WITH_MULTIPLE_DIRECTORIES_WARNING_MESSAGE}"
275
+ end
276
+ @use_relative_paths = directories.one? && options.delete(:relative_paths) { false }
277
+ end
278
+
279
+ # Build the directory record concurrently and initialize the adapter.
280
+ #
281
+ def setup
282
+ t = Thread.new { build_directories_records }
283
+ @adapter = initialize_adapter
284
+ t.join
285
+ end
286
+
287
+ # Initializes an adapter passing it the callback and adapters' options.
288
+ #
289
+ def initialize_adapter
290
+ callback = lambda { |changed_directories, options| self.on_change(changed_directories, options) }
291
+ Adapter.select_and_initialize(directories, adapter_options, &callback)
292
+ end
293
+
294
+ # Build the watched directories' records.
295
+ #
296
+ def build_directories_records
297
+ directories_records.each { |r| r.build }
298
+ end
299
+
300
+ # Returns the sum of all the changes to the directories records
301
+ #
302
+ # @param (see Listen::DirectoryRecord#fetch_changes)
303
+ #
304
+ # @return [Hash] the changes
305
+ #
306
+ def fetch_records_changes(directories_to_search, options)
307
+ directories_records.inject({}) do |h, r|
308
+ # directory records skips paths outside their range, so passing the
309
+ # whole `directories` array is not a problem.
310
+ record_changes = r.fetch_changes(directories_to_search, options.merge(:relative_paths => use_relative_paths))
311
+
312
+ if h.empty?
313
+ h.merge!(record_changes)
314
+ else
315
+ h.each { |k, v| h[k] += record_changes[k] }
316
+ end
317
+
318
+ h
319
+ end
320
+ end
321
+
322
+ end
323
+ end
@@ -0,0 +1,32 @@
1
+ module Listen
2
+
3
+ # Allows two threads to wait on eachother.
4
+ #
5
+ # @note Only two threads can be used with this Turnstile
6
+ # because of the current implementation.
7
+ class Turnstile
8
+ attr_accessor :queue
9
+
10
+ # Initialize the turnstile.
11
+ #
12
+ def initialize
13
+ # Until Ruby offers semahpores, only queues can be used
14
+ # to implement a turnstile.
15
+ @queue = Queue.new
16
+ end
17
+
18
+ # Blocks the current thread until a signal is received.
19
+ #
20
+ def wait
21
+ queue.pop if queue.num_waiting == 0
22
+ end
23
+
24
+ # Unblocks the waiting thread if any.
25
+ #
26
+ def signal
27
+ queue.push(:dummy) if queue.num_waiting == 1
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,3 @@
1
+ module Listen
2
+ VERSION = '1.3.1'
3
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'listen/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'listen'
7
+ s.version = Listen::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Thibaud Guillaume-Gentil', 'Maher Sallam']
10
+ s.email = ['thibaud@thibaud.me', 'maher@sallam.me']
11
+ s.homepage = 'https://github.com/guard/listen'
12
+ s.license = 'MIT'
13
+ s.summary = 'Listen to file modifications'
14
+ s.description = 'The Listen gem listens to file modifications and notifies you about the changes. Works everywhere!'
15
+
16
+ s.required_rubygems_version = '>= 1.3.6'
17
+ s.rubyforge_project = 'listen'
18
+
19
+ s.add_dependency 'rb-fsevent', '>= 0.9.3'
20
+ s.add_dependency 'rb-inotify', '>= 0.9'
21
+ s.add_dependency 'rb-kqueue', '>= 0.2'
22
+
23
+ s.add_development_dependency 'bundler'
24
+ s.add_development_dependency 'rspec'
25
+
26
+ s.files = Dir.glob('{lib}/**/*') + %w[CHANGELOG.md LICENSE README.md]
27
+ s.require_path = 'lib'
28
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+
3
+ describe Listen::Adapter do
4
+ subject { described_class.new('dir') }
5
+
6
+ describe '#initialize' do
7
+ it 'sets the latency to the default one' do
8
+ subject.latency.should eq described_class::DEFAULT_LATENCY
9
+ end
10
+
11
+ it 'accepts a single directory to watch' do
12
+ described_class.new('dir').directories = %w{dir}
13
+ end
14
+
15
+ it 'accepts multiple directories to watch' do
16
+ described_class.new(%w{dir1 dir2}).directories.should eq %w{dir1 dir2}
17
+ end
18
+ end
19
+
20
+ describe ".select_and_initialize" do
21
+ before do
22
+ Listen::Adapter::OPTIMIZED_ADAPTERS.each do |adapter|
23
+ Listen::Adapters.const_get(adapter).stub(:usable_and_works?) { false }
24
+ end
25
+ end
26
+
27
+ context "with force_adapter option" do
28
+ it "returns an instance of the given adapter class" do
29
+ expected_adapter = Object.new
30
+ adapter_class = double('adapter_class')
31
+ options = {:force_adapter => adapter_class}
32
+
33
+ adapter_class.should_receive(:load_dependent_adapter)
34
+ adapter_class.should_receive(:new).with('dir', options) { expected_adapter }
35
+
36
+ adapter = described_class.select_and_initialize('dir', options)
37
+ adapter.should be(expected_adapter)
38
+ end
39
+ end
40
+
41
+ context "with no specific adapter usable" do
42
+ it "returns Listen::Adapters::Polling instance" do
43
+ Kernel.stub(:warn)
44
+ Listen::Adapters::Polling.should_receive(:new).with('dir', {})
45
+ described_class.select_and_initialize('dir')
46
+ end
47
+
48
+ it 'warns with the default polling fallback message' do
49
+ Kernel.should_receive(:warn).with(/#{Listen::Adapter::POLLING_FALLBACK_MESSAGE}/)
50
+ described_class.select_and_initialize('dir')
51
+ end
52
+
53
+ context "with custom polling_fallback_message option" do
54
+ it "warns with the custom polling fallback message" do
55
+ Kernel.should_receive(:warn).with(/custom/)
56
+ described_class.select_and_initialize('dir', :polling_fallback_message => 'custom')
57
+ end
58
+ end
59
+
60
+ context "with polling_fallback_message to false" do
61
+ it "doesn't warn with a polling fallback message" do
62
+ Kernel.should_not_receive(:warn)
63
+ described_class.select_and_initialize('dir', :polling_fallback_message => false)
64
+ end
65
+ end
66
+ end
67
+
68
+ Listen::Adapter::OPTIMIZED_ADAPTERS.each do |adapter|
69
+ adapter_class = Listen::Adapters.const_get(adapter)
70
+
71
+ context "on #{adapter}" do
72
+ before { adapter_class.stub(:usable_and_works?) { true } }
73
+
74
+ it "uses Listen::Adapters::#{adapter}" do
75
+ adapter_class.should_receive(:new).with('dir', {})
76
+ described_class.select_and_initialize('dir')
77
+ end
78
+
79
+ context 'when the use of the polling adapter is forced' do
80
+ it 'uses Listen::Adapters::Polling' do
81
+ Listen::Adapters::Polling.should_receive(:new).with('dir', {})
82
+ described_class.select_and_initialize('dir', :force_polling => true)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ describe '.load_dependend_adapter' do
90
+ after(:each) { described_class.instance_variable_set('@loaded', nil) }
91
+
92
+ it 'returns true (success) even if the adapter_gem has already been required' do
93
+ described_class.stub(:adapter_gem => 'already_loaded_gem')
94
+ described_class.stub(:require => false)
95
+
96
+ described_class.load_dependent_adapter.should be_true
97
+ end
98
+
99
+ it 'returns false (failure) if the adapter_gem cannot be required' do
100
+ described_class.stub(:adapter_gem => 'unloadable_gem')
101
+ described_class.stub(:require) do
102
+ raise LoadError.new('no such file to load -- unloadable_gem')
103
+ end
104
+
105
+ described_class.load_dependent_adapter.should be_false
106
+ end
107
+ end
108
+
109
+ Listen::Adapter::OPTIMIZED_ADAPTERS.each do |adapter|
110
+ adapter_class = Listen::Adapters.const_get(adapter)
111
+ if adapter_class.usable?
112
+ describe '.usable_and_works?' do
113
+ it 'checks if the adapter is usable' do
114
+ adapter_class.stub(:works?)
115
+ adapter_class.should_receive(:usable?)
116
+ adapter_class.usable_and_works?('dir')
117
+ end
118
+
119
+ context 'with one directory' do
120
+ it 'tests if that directory actually work' do
121
+ fixtures do |path|
122
+ adapter_class.should_receive(:works?).with(path, anything).and_return(true)
123
+ adapter_class.usable_and_works?(path)
124
+ end
125
+ end
126
+ end
127
+
128
+ context 'with multiple directories' do
129
+ it 'tests if each directory passed does actually work' do
130
+ fixtures(3) do |path1, path2, path3|
131
+ adapter_class.should_receive(:works?).exactly(3).times.with do |path, options|
132
+ [path1, path2, path3].include? path
133
+ end.and_return(true)
134
+ adapter_class.usable_and_works?([path1, path2, path3])
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ describe '.works?' do
141
+ it 'does work' do
142
+ fixtures do |path|
143
+ adapter_class.works?(path).should be_true
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end