haml 3.1.0.alpha.14 → 3.1.0.alpha.17

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (222) hide show
  1. data/EDGE_GEM_VERSION +1 -1
  2. data/VERSION +1 -1
  3. data/lib/haml.rb +3 -2
  4. data/lib/haml/exec.rb +0 -226
  5. data/lib/sass.rb +8 -0
  6. data/lib/sass/plugin.rb +8 -0
  7. data/lib/sass/rails2_shim.rb +9 -0
  8. data/lib/sass/rails3_shim.rb +16 -0
  9. data/vendor/sass/CONTRIBUTING +3 -0
  10. data/vendor/sass/MIT-LICENSE +20 -0
  11. data/vendor/sass/README.md +201 -0
  12. data/vendor/sass/Rakefile +363 -0
  13. data/vendor/sass/TODO +39 -0
  14. data/vendor/sass/VERSION +1 -0
  15. data/vendor/sass/VERSION_NAME +1 -0
  16. data/vendor/sass/bin/css2sass +13 -0
  17. data/vendor/sass/bin/sass +8 -0
  18. data/vendor/sass/bin/sass-convert +7 -0
  19. data/vendor/sass/doc-src/FAQ.md +35 -0
  20. data/vendor/sass/doc-src/INDENTED_SYNTAX.md +210 -0
  21. data/vendor/sass/doc-src/SASS_CHANGELOG.md +1878 -0
  22. data/vendor/sass/doc-src/SASS_REFERENCE.md +1713 -0
  23. data/vendor/sass/doc-src/SCSS_FOR_SASS_USERS.md +155 -0
  24. data/vendor/sass/ext/extconf.rb +10 -0
  25. data/vendor/sass/extra/update_watch.rb +13 -0
  26. data/vendor/sass/init.rb +18 -0
  27. data/vendor/sass/lib/sass.rb +71 -0
  28. data/vendor/sass/lib/sass/cache_store.rb +208 -0
  29. data/vendor/sass/lib/sass/callbacks.rb +66 -0
  30. data/vendor/sass/lib/sass/css.rb +294 -0
  31. data/vendor/sass/lib/sass/engine.rb +792 -0
  32. data/vendor/sass/lib/sass/environment.rb +143 -0
  33. data/vendor/sass/lib/sass/error.rb +201 -0
  34. data/vendor/sass/lib/sass/exec.rb +619 -0
  35. data/vendor/sass/lib/sass/importers.rb +22 -0
  36. data/vendor/sass/lib/sass/importers/base.rb +138 -0
  37. data/vendor/sass/lib/sass/importers/filesystem.rb +121 -0
  38. data/vendor/sass/lib/sass/less.rb +363 -0
  39. data/vendor/sass/lib/sass/plugin.rb +126 -0
  40. data/vendor/sass/lib/sass/plugin/compiler.rb +346 -0
  41. data/vendor/sass/lib/sass/plugin/configuration.rb +123 -0
  42. data/vendor/sass/lib/sass/plugin/generic.rb +15 -0
  43. data/vendor/sass/lib/sass/plugin/merb.rb +48 -0
  44. data/vendor/sass/lib/sass/plugin/rack.rb +47 -0
  45. data/vendor/sass/lib/sass/plugin/rails.rb +41 -0
  46. data/vendor/sass/lib/sass/plugin/staleness_checker.rb +145 -0
  47. data/vendor/sass/lib/sass/railtie.rb +8 -0
  48. data/vendor/sass/lib/sass/repl.rb +58 -0
  49. data/vendor/sass/lib/sass/root.rb +7 -0
  50. data/vendor/sass/lib/sass/script.rb +63 -0
  51. data/vendor/sass/lib/sass/script/bool.rb +18 -0
  52. data/vendor/sass/lib/sass/script/color.rb +491 -0
  53. data/vendor/sass/lib/sass/script/css_lexer.rb +29 -0
  54. data/vendor/sass/lib/sass/script/css_parser.rb +31 -0
  55. data/vendor/sass/lib/sass/script/funcall.rb +76 -0
  56. data/vendor/sass/lib/sass/script/functions.rb +852 -0
  57. data/vendor/sass/lib/sass/script/interpolation.rb +70 -0
  58. data/vendor/sass/lib/sass/script/lexer.rb +337 -0
  59. data/vendor/sass/lib/sass/script/literal.rb +236 -0
  60. data/vendor/sass/lib/sass/script/node.rb +112 -0
  61. data/vendor/sass/lib/sass/script/number.rb +423 -0
  62. data/vendor/sass/lib/sass/script/operation.rb +90 -0
  63. data/vendor/sass/lib/sass/script/parser.rb +392 -0
  64. data/vendor/sass/lib/sass/script/string.rb +67 -0
  65. data/vendor/sass/lib/sass/script/string_interpolation.rb +93 -0
  66. data/vendor/sass/lib/sass/script/unary_operation.rb +57 -0
  67. data/vendor/sass/lib/sass/script/variable.rb +48 -0
  68. data/vendor/sass/lib/sass/scss.rb +17 -0
  69. data/vendor/sass/lib/sass/scss/css_parser.rb +51 -0
  70. data/vendor/sass/lib/sass/scss/parser.rb +838 -0
  71. data/vendor/sass/lib/sass/scss/rx.rb +126 -0
  72. data/vendor/sass/lib/sass/scss/sass_parser.rb +11 -0
  73. data/vendor/sass/lib/sass/scss/script_lexer.rb +15 -0
  74. data/vendor/sass/lib/sass/scss/script_parser.rb +25 -0
  75. data/vendor/sass/lib/sass/scss/static_parser.rb +40 -0
  76. data/vendor/sass/lib/sass/selector.rb +361 -0
  77. data/vendor/sass/lib/sass/selector/abstract_sequence.rb +62 -0
  78. data/vendor/sass/lib/sass/selector/comma_sequence.rb +82 -0
  79. data/vendor/sass/lib/sass/selector/sequence.rb +236 -0
  80. data/vendor/sass/lib/sass/selector/simple.rb +113 -0
  81. data/vendor/sass/lib/sass/selector/simple_sequence.rb +135 -0
  82. data/vendor/sass/lib/sass/shared.rb +78 -0
  83. data/vendor/sass/lib/sass/tree/comment_node.rb +128 -0
  84. data/vendor/sass/lib/sass/tree/debug_node.rb +36 -0
  85. data/vendor/sass/lib/sass/tree/directive_node.rb +75 -0
  86. data/vendor/sass/lib/sass/tree/extend_node.rb +65 -0
  87. data/vendor/sass/lib/sass/tree/for_node.rb +67 -0
  88. data/vendor/sass/lib/sass/tree/if_node.rb +81 -0
  89. data/vendor/sass/lib/sass/tree/import_node.rb +124 -0
  90. data/vendor/sass/lib/sass/tree/mixin_def_node.rb +60 -0
  91. data/vendor/sass/lib/sass/tree/mixin_node.rb +123 -0
  92. data/vendor/sass/lib/sass/tree/node.rb +490 -0
  93. data/vendor/sass/lib/sass/tree/prop_node.rb +220 -0
  94. data/vendor/sass/lib/sass/tree/root_node.rb +125 -0
  95. data/vendor/sass/lib/sass/tree/rule_node.rb +273 -0
  96. data/vendor/sass/lib/sass/tree/variable_node.rb +39 -0
  97. data/vendor/sass/lib/sass/tree/warn_node.rb +42 -0
  98. data/vendor/sass/lib/sass/tree/while_node.rb +48 -0
  99. data/vendor/sass/lib/sass/util.rb +700 -0
  100. data/vendor/sass/lib/sass/util/subset_map.rb +101 -0
  101. data/vendor/sass/lib/sass/version.rb +109 -0
  102. data/vendor/sass/rails/init.rb +1 -0
  103. data/vendor/sass/sass.gemspec +32 -0
  104. data/vendor/sass/test/sass/cache_test.rb +74 -0
  105. data/vendor/sass/test/sass/callbacks_test.rb +61 -0
  106. data/vendor/sass/test/sass/conversion_test.rb +1210 -0
  107. data/vendor/sass/test/sass/css2sass_test.rb +364 -0
  108. data/vendor/sass/test/sass/data/hsl-rgb.txt +319 -0
  109. data/vendor/sass/test/sass/engine_test.rb +2305 -0
  110. data/vendor/sass/test/sass/extend_test.rb +1348 -0
  111. data/vendor/sass/test/sass/functions_test.rb +565 -0
  112. data/vendor/sass/test/sass/importer_test.rb +104 -0
  113. data/vendor/sass/test/sass/less_conversion_test.rb +632 -0
  114. data/vendor/sass/test/sass/mock_importer.rb +49 -0
  115. data/vendor/sass/test/sass/more_results/more1.css +9 -0
  116. data/vendor/sass/test/sass/more_results/more1_with_line_comments.css +26 -0
  117. data/vendor/sass/test/sass/more_results/more_import.css +29 -0
  118. data/vendor/sass/test/sass/more_templates/_more_partial.sass +2 -0
  119. data/vendor/sass/test/sass/more_templates/more1.sass +23 -0
  120. data/vendor/sass/test/sass/more_templates/more_import.sass +11 -0
  121. data/vendor/sass/test/sass/plugin_test.rb +430 -0
  122. data/vendor/sass/test/sass/results/alt.css +4 -0
  123. data/vendor/sass/test/sass/results/basic.css +9 -0
  124. data/vendor/sass/test/sass/results/compact.css +5 -0
  125. data/vendor/sass/test/sass/results/complex.css +86 -0
  126. data/vendor/sass/test/sass/results/compressed.css +1 -0
  127. data/vendor/sass/test/sass/results/expanded.css +19 -0
  128. data/vendor/sass/test/sass/results/import.css +31 -0
  129. data/vendor/sass/test/sass/results/line_numbers.css +49 -0
  130. data/vendor/sass/test/sass/results/mixins.css +95 -0
  131. data/vendor/sass/test/sass/results/multiline.css +24 -0
  132. data/vendor/sass/test/sass/results/nested.css +22 -0
  133. data/vendor/sass/test/sass/results/options.css +1 -0
  134. data/vendor/sass/test/sass/results/parent_ref.css +13 -0
  135. data/vendor/sass/test/sass/results/script.css +16 -0
  136. data/vendor/sass/test/sass/results/scss_import.css +31 -0
  137. data/vendor/sass/test/sass/results/scss_importee.css +2 -0
  138. data/vendor/sass/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  139. data/vendor/sass/test/sass/results/subdir/subdir.css +3 -0
  140. data/vendor/sass/test/sass/results/units.css +11 -0
  141. data/vendor/sass/test/sass/results/warn.css +0 -0
  142. data/vendor/sass/test/sass/results/warn_imported.css +0 -0
  143. data/vendor/sass/test/sass/script_conversion_test.rb +254 -0
  144. data/vendor/sass/test/sass/script_test.rb +470 -0
  145. data/vendor/sass/test/sass/scss/css_test.rb +897 -0
  146. data/vendor/sass/test/sass/scss/rx_test.rb +156 -0
  147. data/vendor/sass/test/sass/scss/scss_test.rb +1088 -0
  148. data/vendor/sass/test/sass/scss/test_helper.rb +37 -0
  149. data/vendor/sass/test/sass/templates/_partial.sass +2 -0
  150. data/vendor/sass/test/sass/templates/alt.sass +16 -0
  151. data/vendor/sass/test/sass/templates/basic.sass +23 -0
  152. data/vendor/sass/test/sass/templates/bork1.sass +2 -0
  153. data/vendor/sass/test/sass/templates/bork2.sass +2 -0
  154. data/vendor/sass/test/sass/templates/bork3.sass +2 -0
  155. data/vendor/sass/test/sass/templates/bork4.sass +2 -0
  156. data/vendor/sass/test/sass/templates/compact.sass +17 -0
  157. data/vendor/sass/test/sass/templates/complex.sass +305 -0
  158. data/vendor/sass/test/sass/templates/compressed.sass +15 -0
  159. data/vendor/sass/test/sass/templates/expanded.sass +17 -0
  160. data/vendor/sass/test/sass/templates/import.sass +12 -0
  161. data/vendor/sass/test/sass/templates/importee.less +2 -0
  162. data/vendor/sass/test/sass/templates/importee.sass +19 -0
  163. data/vendor/sass/test/sass/templates/line_numbers.sass +13 -0
  164. data/vendor/sass/test/sass/templates/mixin_bork.sass +5 -0
  165. data/vendor/sass/test/sass/templates/mixins.sass +76 -0
  166. data/vendor/sass/test/sass/templates/multiline.sass +20 -0
  167. data/vendor/sass/test/sass/templates/nested.sass +25 -0
  168. data/vendor/sass/test/sass/templates/nested_bork1.sass +2 -0
  169. data/vendor/sass/test/sass/templates/nested_bork2.sass +2 -0
  170. data/vendor/sass/test/sass/templates/nested_bork3.sass +2 -0
  171. data/vendor/sass/test/sass/templates/nested_bork4.sass +2 -0
  172. data/vendor/sass/test/sass/templates/nested_mixin_bork.sass +6 -0
  173. data/vendor/sass/test/sass/templates/options.sass +2 -0
  174. data/vendor/sass/test/sass/templates/parent_ref.sass +25 -0
  175. data/vendor/sass/test/sass/templates/script.sass +101 -0
  176. data/vendor/sass/test/sass/templates/scss_import.scss +11 -0
  177. data/vendor/sass/test/sass/templates/scss_importee.scss +1 -0
  178. data/vendor/sass/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  179. data/vendor/sass/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  180. data/vendor/sass/test/sass/templates/subdir/subdir.sass +6 -0
  181. data/vendor/sass/test/sass/templates/units.sass +11 -0
  182. data/vendor/sass/test/sass/templates/warn.sass +3 -0
  183. data/vendor/sass/test/sass/templates/warn_imported.sass +4 -0
  184. data/vendor/sass/test/sass/test_helper.rb +8 -0
  185. data/vendor/sass/test/sass/util/subset_map_test.rb +91 -0
  186. data/vendor/sass/test/sass/util_test.rb +275 -0
  187. data/vendor/sass/test/test_helper.rb +64 -0
  188. data/vendor/sass/vendor/fssm/LICENSE +20 -0
  189. data/vendor/sass/vendor/fssm/README.markdown +55 -0
  190. data/vendor/sass/vendor/fssm/Rakefile +59 -0
  191. data/vendor/sass/vendor/fssm/VERSION.yml +5 -0
  192. data/vendor/sass/vendor/fssm/example.rb +9 -0
  193. data/vendor/sass/vendor/fssm/fssm.gemspec +77 -0
  194. data/vendor/sass/vendor/fssm/lib/fssm.rb +33 -0
  195. data/vendor/sass/vendor/fssm/lib/fssm/backends/fsevents.rb +36 -0
  196. data/vendor/sass/vendor/fssm/lib/fssm/backends/inotify.rb +26 -0
  197. data/vendor/sass/vendor/fssm/lib/fssm/backends/polling.rb +25 -0
  198. data/vendor/sass/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +131 -0
  199. data/vendor/sass/vendor/fssm/lib/fssm/monitor.rb +26 -0
  200. data/vendor/sass/vendor/fssm/lib/fssm/path.rb +91 -0
  201. data/vendor/sass/vendor/fssm/lib/fssm/pathname.rb +502 -0
  202. data/vendor/sass/vendor/fssm/lib/fssm/state/directory.rb +57 -0
  203. data/vendor/sass/vendor/fssm/lib/fssm/state/file.rb +24 -0
  204. data/vendor/sass/vendor/fssm/lib/fssm/support.rb +63 -0
  205. data/vendor/sass/vendor/fssm/lib/fssm/tree.rb +176 -0
  206. data/vendor/sass/vendor/fssm/profile/prof-cache.rb +40 -0
  207. data/vendor/sass/vendor/fssm/profile/prof-fssm-pathname.html +1231 -0
  208. data/vendor/sass/vendor/fssm/profile/prof-pathname.rb +68 -0
  209. data/vendor/sass/vendor/fssm/profile/prof-plain-pathname.html +988 -0
  210. data/vendor/sass/vendor/fssm/profile/prof.html +2379 -0
  211. data/vendor/sass/vendor/fssm/spec/path_spec.rb +75 -0
  212. data/vendor/sass/vendor/fssm/spec/root/duck/quack.txt +0 -0
  213. data/vendor/sass/vendor/fssm/spec/root/file.css +0 -0
  214. data/vendor/sass/vendor/fssm/spec/root/file.rb +0 -0
  215. data/vendor/sass/vendor/fssm/spec/root/file.yml +0 -0
  216. data/vendor/sass/vendor/fssm/spec/root/moo/cow.txt +0 -0
  217. data/vendor/sass/vendor/fssm/spec/spec_helper.rb +14 -0
  218. data/vendor/sass/yard/callbacks.rb +29 -0
  219. data/vendor/sass/yard/default/fulldoc/html/css/common.sass +26 -0
  220. data/vendor/sass/yard/default/layout/html/footer.erb +12 -0
  221. data/vendor/sass/yard/inherited_hash.rb +41 -0
  222. metadata +219 -2
@@ -0,0 +1,143 @@
1
+ require 'set'
2
+
3
+ module Sass
4
+ # The lexical environment for SassScript.
5
+ # This keeps track of variable and mixin definitions.
6
+ #
7
+ # A new environment is created for each level of Sass nesting.
8
+ # This allows variables to be lexically scoped.
9
+ # The new environment refers to the environment in the upper scope,
10
+ # so it has access to variables defined in enclosing scopes,
11
+ # but new variables are defined locally.
12
+ #
13
+ # Environment also keeps track of the {Engine} options
14
+ # so that they can be made available to {Sass::Script::Functions}.
15
+ class Environment
16
+ # The enclosing environment,
17
+ # or nil if this is the global environment.
18
+ #
19
+ # @return [Environment]
20
+ attr_reader :parent
21
+ attr_writer :options
22
+
23
+ # @param parent [Environment] See \{#parent}
24
+ def initialize(parent = nil)
25
+ @vars = {}
26
+ @mixins = {}
27
+ @parent = parent
28
+ @stack = [] unless parent
29
+ @mixins_in_use = Set.new unless parent
30
+ set_var("important", Script::String.new("!important")) unless @parent
31
+ end
32
+
33
+ # The options hash.
34
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
35
+ #
36
+ # @return [{Symbol => Object}]
37
+ def options
38
+ @options || (parent && parent.options) || {}
39
+ end
40
+
41
+ # Push a new stack frame onto the mixin/include stack.
42
+ #
43
+ # @param frame_info [{Symbol => Object}]
44
+ # Frame information has the following keys:
45
+ #
46
+ # `:filename`
47
+ # : The name of the file in which the lexical scope changed.
48
+ #
49
+ # `:mixin`
50
+ # : The name of the mixin in which the lexical scope changed,
51
+ # or `nil` if it wasn't within in a mixin.
52
+ #
53
+ # `:line`
54
+ # : The line of the file on which the lexical scope changed. Never nil.
55
+ def push_frame(frame_info)
56
+ if stack.last && stack.last[:prepared]
57
+ stack.last.delete(:prepared)
58
+ stack.last.merge!(frame_info)
59
+ else
60
+ stack.push(frame_info)
61
+ end
62
+ mixins_in_use << stack.last[:mixin] if stack.last[:mixin] && !stack.last[:prepared]
63
+ end
64
+
65
+ # Like \{#push\_frame}, but next time a stack frame is pushed,
66
+ # it will be merged with this frame.
67
+ #
68
+ # @param frame_info [{Symbol => Object}] Same as for \{#push\_frame}.
69
+ def prepare_frame(frame_info)
70
+ push_frame(frame_info.merge(:prepared => true))
71
+ end
72
+
73
+ # Pop a stack frame from the mixin/include stack.
74
+ def pop_frame
75
+ stack.pop if stack.last && stack.last[:prepared]
76
+ popped = stack.pop
77
+ mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
78
+ end
79
+
80
+ # A list of stack frames in the mixin/include stack.
81
+ # The last element in the list is the most deeply-nested frame.
82
+ #
83
+ # @return [Array<{Symbol => Object}>] The stack frames,
84
+ # of the form passed to \{#push\_frame}.
85
+ def stack
86
+ @stack ||= @parent.stack
87
+ end
88
+
89
+ # A set of names of mixins currently present in the stack.
90
+ #
91
+ # @return [Set<String>] The mixin names.
92
+ def mixins_in_use
93
+ @mixins_in_use ||= @parent.mixins_in_use
94
+ end
95
+
96
+ class << self
97
+ private
98
+
99
+ # Note: when updating this,
100
+ # update sass/yard/inherited_hash.rb as well.
101
+ def inherited_hash(name)
102
+ class_eval <<RUBY, __FILE__, __LINE__ + 1
103
+ def #{name}(name)
104
+ _#{name}(name.gsub('_', '-'))
105
+ end
106
+
107
+ def _#{name}(name)
108
+ @#{name}s[name] || @parent && @parent._#{name}(name)
109
+ end
110
+ protected :_#{name}
111
+
112
+ def set_#{name}(name, value)
113
+ name = name.gsub('_', '-')
114
+ @#{name}s[name] = value unless try_set_#{name}(name, value)
115
+ end
116
+
117
+ def try_set_#{name}(name, value)
118
+ if @#{name}s.include?(name)
119
+ @#{name}s[name] = value
120
+ true
121
+ elsif @parent
122
+ @parent.try_set_#{name}(name, value)
123
+ else
124
+ false
125
+ end
126
+ end
127
+ protected :try_set_#{name}
128
+
129
+ def set_local_#{name}(name, value)
130
+ @#{name}s[name.gsub('_', '-')] = value
131
+ end
132
+ RUBY
133
+ end
134
+ end
135
+
136
+ # variable
137
+ # Script::Literal
138
+ inherited_hash :var
139
+ # mixin
140
+ # Engine::Mixin
141
+ inherited_hash :mixin
142
+ end
143
+ end
@@ -0,0 +1,201 @@
1
+ module Sass
2
+ # An exception class that keeps track of
3
+ # the line of the Sass template it was raised on
4
+ # and the Sass file that was being parsed (if applicable).
5
+ #
6
+ # All Sass errors are raised as {Sass::SyntaxError}s.
7
+ #
8
+ # When dealing with SyntaxErrors,
9
+ # it's important to provide filename and line number information.
10
+ # This will be used in various error reports to users, including backtraces;
11
+ # see \{#sass\_backtrace} for details.
12
+ #
13
+ # Some of this information is usually provided as part of the constructor.
14
+ # New backtrace entries can be added with \{#add\_backtrace},
15
+ # which is called when an exception is raised between files (e.g. with `@import`).
16
+ #
17
+ # Often, a chunk of code will all have similar backtrace information -
18
+ # the same filename or even line.
19
+ # It may also be useful to have a default line number set.
20
+ # In those situations, the default values can be used
21
+ # by omitting the information on the original exception,
22
+ # and then calling \{#modify\_backtrace} in a wrapper `rescue`.
23
+ # When doing this, be sure that all exceptions ultimately end up
24
+ # with the information filled in.
25
+ class SyntaxError < StandardError
26
+ # The backtrace of the error within Sass files.
27
+ # This is an array of hashes containing information for a single entry.
28
+ # The hashes have the following keys:
29
+ #
30
+ # `:filename`
31
+ # : The name of the file in which the exception was raised,
32
+ # or `nil` if no filename is available.
33
+ #
34
+ # `:mixin`
35
+ # : The name of the mixin in which the exception was raised,
36
+ # or `nil` if it wasn't raised in a mixin.
37
+ #
38
+ # `:line`
39
+ # : The line of the file on which the error occurred. Never nil.
40
+ #
41
+ # This information is also included in standard backtrace format
42
+ # in the output of \{#backtrace}.
43
+ #
44
+ # @return [Aray<{Symbol => Object>}]
45
+ attr_accessor :sass_backtrace
46
+
47
+ # The text of the template where this error was raised.
48
+ #
49
+ # @return [String]
50
+ attr_accessor :sass_template
51
+
52
+ # @param msg [String] The error message
53
+ # @param attrs [{Symbol => Object}] The information in the backtrace entry.
54
+ # See \{#sass\_backtrace}
55
+ def initialize(msg, attrs = {})
56
+ @message = msg
57
+ @sass_backtrace = []
58
+ add_backtrace(attrs)
59
+ end
60
+
61
+ # The name of the file in which the exception was raised.
62
+ # This could be `nil` if no filename is available.
63
+ #
64
+ # @return [String, nil]
65
+ def sass_filename
66
+ sass_backtrace.first[:filename]
67
+ end
68
+
69
+ # The name of the mixin in which the error occurred.
70
+ # This could be `nil` if the error occurred outside a mixin.
71
+ #
72
+ # @return [Fixnum]
73
+ def sass_mixin
74
+ sass_backtrace.first[:mixin]
75
+ end
76
+
77
+ # The line of the Sass template on which the error occurred.
78
+ #
79
+ # @return [Fixnum]
80
+ def sass_line
81
+ sass_backtrace.first[:line]
82
+ end
83
+
84
+ # Adds an entry to the exception's Sass backtrace.
85
+ #
86
+ # @param attrs [{Symbol => Object}] The information in the backtrace entry.
87
+ # See \{#sass\_backtrace}
88
+ def add_backtrace(attrs)
89
+ sass_backtrace << attrs.reject {|k, v| v.nil?}
90
+ end
91
+
92
+ # Modify the top Sass backtrace entries
93
+ # (that is, the most deeply nested ones)
94
+ # to have the given attributes.
95
+ #
96
+ # Specifically, this goes through the backtrace entries
97
+ # from most deeply nested to least,
98
+ # setting the given attributes for each entry.
99
+ # If an entry already has one of the given attributes set,
100
+ # the pre-existing attribute takes precedence
101
+ # and is not used for less deeply-nested entries
102
+ # (even if they don't have that attribute set).
103
+ #
104
+ # @param attrs [{Symbol => Object}] The information to add to the backtrace entry.
105
+ # See \{#sass\_backtrace}
106
+ def modify_backtrace(attrs)
107
+ attrs = attrs.reject {|k, v| v.nil?}
108
+ # Move backwards through the backtrace
109
+ (0...sass_backtrace.size).to_a.reverse.each do |i|
110
+ entry = sass_backtrace[i]
111
+ sass_backtrace[i] = attrs.merge(entry)
112
+ attrs.reject! {|k, v| entry.include?(k)}
113
+ break if attrs.empty?
114
+ end
115
+ end
116
+
117
+ # @return [String] The error message
118
+ def to_s
119
+ @message
120
+ end
121
+
122
+ # Returns the standard exception backtrace,
123
+ # including the Sass backtrace.
124
+ #
125
+ # @return [Array<String>]
126
+ def backtrace
127
+ return nil if super.nil?
128
+ return super if sass_backtrace.all? {|h| h.empty?}
129
+ sass_backtrace.map do |h|
130
+ "#{h[:filename] || "(sass)"}:#{h[:line]}" +
131
+ (h[:mixin] ? ":in `#{h[:mixin]}'" : "")
132
+ end + super
133
+ end
134
+
135
+ # Returns a string representation of the Sass backtrace.
136
+ #
137
+ # @param default_filename [String] The filename to use for unknown files
138
+ # @see #sass_backtrace
139
+ # @return [String]
140
+ def sass_backtrace_str(default_filename = "an unknown file")
141
+ lines = self.message.split("\n")
142
+ msg = lines[0] + lines[1..-1].
143
+ map {|l| "\n" + (" " * "Syntax error: ".size) + l}.join
144
+ "Syntax error: #{msg}" +
145
+ Sass::Util.enum_with_index(sass_backtrace).map do |entry, i|
146
+ "\n #{i == 0 ? "on" : "from"} line #{entry[:line]}" +
147
+ " of #{entry[:filename] || default_filename}" +
148
+ (entry[:mixin] ? ", in `#{entry[:mixin]}'" : "")
149
+ end.join
150
+ end
151
+
152
+ class << self
153
+ # Returns an error report for an exception in CSS format.
154
+ #
155
+ # @param e [Exception]
156
+ # @param options [{Symbol => Object}] The options passed to {Sass::Engine#initialize}
157
+ # @return [String] The error report
158
+ # @raise [Exception] `e`, if the
159
+ # {file:SASS_REFERENCE.md#full_exception-option `:full_exception`} option
160
+ # is set to false.
161
+ def exception_to_css(e, options)
162
+ raise e unless options[:full_exception]
163
+
164
+ header = header_string(e, options)
165
+
166
+ <<END
167
+ /*
168
+ #{header}
169
+
170
+ Backtrace:\n#{e.backtrace.join("\n")}
171
+ */
172
+ body:before {
173
+ white-space: pre;
174
+ font-family: monospace;
175
+ content: "#{header.gsub('"', '\"').gsub("\n", '\\A ')}"; }
176
+ END
177
+ end
178
+
179
+ private
180
+
181
+ def header_string(e, options)
182
+ unless e.is_a?(Sass::SyntaxError) && e.sass_line && e.sass_template
183
+ return "#{e.class}: #{e.message}"
184
+ end
185
+
186
+ line_offset = options[:line] || 1
187
+ line_num = e.sass_line + 1 - line_offset
188
+ min = [line_num - 6, 0].max
189
+ section = e.sass_template.rstrip.split("\n")[min ... line_num + 5]
190
+ return e.sass_backtrace_str if section.nil? || section.empty?
191
+
192
+ e.sass_backtrace_str + "\n\n" + Sass::Util.enum_with_index(section).
193
+ map {|line, i| "#{line_offset + min + i}: #{line}"}.join("\n")
194
+ end
195
+ end
196
+ end
197
+
198
+ # The class for Sass errors that are raised due to invalid unit conversions
199
+ # in SassScript.
200
+ class UnitConversionError < SyntaxError; end
201
+ end
@@ -0,0 +1,619 @@
1
+ require 'optparse'
2
+ require 'fileutils'
3
+
4
+ module Sass
5
+ # This module handles the various Sass executables (`sass` and `sass-convert`).
6
+ module Exec
7
+ # An abstract class that encapsulates the executable code for all three executables.
8
+ class Generic
9
+ # @param args [Array<String>] The command-line arguments
10
+ def initialize(args)
11
+ @args = args
12
+ @options = {}
13
+ end
14
+
15
+ # Parses the command-line arguments and runs the executable.
16
+ # Calls `Kernel#exit` at the end, so it never returns.
17
+ #
18
+ # @see #parse
19
+ def parse!
20
+ begin
21
+ parse
22
+ rescue Exception => e
23
+ raise e if @options[:trace] || e.is_a?(SystemExit)
24
+
25
+ $stderr.print "#{e.class}: " unless e.class == RuntimeError
26
+ $stderr.puts "#{e.message}"
27
+ $stderr.puts " Use --trace for backtrace."
28
+ exit 1
29
+ end
30
+ exit 0
31
+ end
32
+
33
+ # Parses the command-line arguments and runs the executable.
34
+ # This does not handle exceptions or exit the program.
35
+ #
36
+ # @see #parse!
37
+ def parse
38
+ @opts = OptionParser.new(&method(:set_opts))
39
+ @opts.parse!(@args)
40
+
41
+ process_result
42
+
43
+ @options
44
+ end
45
+
46
+ # @return [String] A description of the executable
47
+ def to_s
48
+ @opts.to_s
49
+ end
50
+
51
+ protected
52
+
53
+ # Finds the line of the source template
54
+ # on which an exception was raised.
55
+ #
56
+ # @param exception [Exception] The exception
57
+ # @return [String] The line number
58
+ def get_line(exception)
59
+ # SyntaxErrors have weird line reporting
60
+ # when there's trailing whitespace
61
+ return (exception.message.scan(/:(\d+)/).first || ["??"]).first if exception.is_a?(::SyntaxError)
62
+ (exception.backtrace[0].scan(/:(\d+)/).first || ["??"]).first
63
+ end
64
+
65
+ # Tells optparse how to parse the arguments
66
+ # available for all executables.
67
+ #
68
+ # This is meant to be overridden by subclasses
69
+ # so they can add their own options.
70
+ #
71
+ # @param opts [OptionParser]
72
+ def set_opts(opts)
73
+ opts.on('-s', '--stdin', :NONE, 'Read input from standard input instead of an input file') do
74
+ @options[:input] = $stdin
75
+ end
76
+
77
+ opts.on('--trace', :NONE, 'Show a full traceback on error') do
78
+ @options[:trace] = true
79
+ end
80
+
81
+ opts.on('--unix-newlines', 'Use Unix-style newlines in written files.') do
82
+ @options[:unix_newlines] = true if ::Sass::Util.windows?
83
+ end
84
+
85
+ opts.on_tail("-?", "-h", "--help", "Show this message") do
86
+ puts opts
87
+ exit
88
+ end
89
+
90
+ opts.on_tail("-v", "--version", "Print version") do
91
+ puts("Sass #{::Sass.version[:string]}")
92
+ exit
93
+ end
94
+ end
95
+
96
+ # Processes the options set by the command-line arguments.
97
+ # In particular, sets `@options[:input]` and `@options[:output]`
98
+ # to appropriate IO streams.
99
+ #
100
+ # This is meant to be overridden by subclasses
101
+ # so they can run their respective programs.
102
+ def process_result
103
+ input, output = @options[:input], @options[:output]
104
+ args = @args.dup
105
+ input ||=
106
+ begin
107
+ filename = args.shift
108
+ @options[:filename] = filename
109
+ open_file(filename) || $stdin
110
+ end
111
+ output ||= open_file(args.shift, 'w') || $stdout
112
+
113
+ @options[:input], @options[:output] = input, output
114
+ end
115
+
116
+ COLORS = { :red => 31, :green => 32, :yellow => 33 }
117
+
118
+ # Prints a status message about performing the given action,
119
+ # colored using the given color (via terminal escapes) if possible.
120
+ #
121
+ # @param name [#to_s] A short name for the action being performed.
122
+ # Shouldn't be longer than 11 characters.
123
+ # @param color [Symbol] The name of the color to use for this action.
124
+ # Can be `:red`, `:green`, or `:yellow`.
125
+ def puts_action(name, color, arg)
126
+ printf color(color, "%11s %s\n"), name, arg
127
+ end
128
+
129
+ # Wraps the given string in terminal escapes
130
+ # causing it to have the given color.
131
+ # If terminal esapes aren't supported on this platform,
132
+ # just returns the string instead.
133
+ #
134
+ # @param color [Symbol] The name of the color to use.
135
+ # Can be `:red`, `:green`, or `:yellow`.
136
+ # @param str [String] The string to wrap in the given color.
137
+ # @return [String] The wrapped string.
138
+ def color(color, str)
139
+ raise "[BUG] Unrecognized color #{color}" unless COLORS[color]
140
+
141
+ # Almost any real Unix terminal will support color,
142
+ # so we just filter for Windows terms (which don't set TERM)
143
+ # and not-real terminals, which aren't ttys.
144
+ return str if ENV["TERM"].nil? || ENV["TERM"].empty? || !STDOUT.tty?
145
+ return "\e[#{COLORS[color]}m#{str}\e[0m"
146
+ end
147
+
148
+ private
149
+
150
+ def open_file(filename, flag = 'r')
151
+ return if filename.nil?
152
+ flag = 'wb' if @options[:unix_newlines] && flag == 'w'
153
+ File.open(filename, flag)
154
+ end
155
+
156
+ def handle_load_error(err)
157
+ dep = err.message.scan(/^no such file to load -- (.*)/)[0]
158
+ raise err if @options[:trace] || dep.nil? || dep.empty?
159
+ $stderr.puts <<MESSAGE
160
+ Required dependency #{dep} not found!
161
+ Run "gem install #{dep}" to get it.
162
+ Use --trace for backtrace.
163
+ MESSAGE
164
+ exit 1
165
+ end
166
+ end
167
+
168
+ # The `sass` executable.
169
+ class Sass < Generic
170
+ # @param args [Array<String>] The command-line arguments
171
+ def initialize(args)
172
+ super
173
+ @options[:for_engine] = {
174
+ :load_paths => ['.'] + (ENV['SASSPATH'] || '').split(File::PATH_SEPARATOR)
175
+ }
176
+ end
177
+
178
+ protected
179
+
180
+ # Tells optparse how to parse the arguments.
181
+ #
182
+ # @param opts [OptionParser]
183
+ def set_opts(opts)
184
+ super
185
+
186
+ opts.banner = <<END
187
+ Usage: sass [options] [INPUT] [OUTPUT]
188
+
189
+ Description:
190
+ Converts SCSS or Sass files to CSS.
191
+
192
+ Options:
193
+ END
194
+
195
+ opts.on('--scss',
196
+ 'Use the CSS-superset SCSS syntax.') do
197
+ @options[:for_engine][:syntax] = :scss
198
+ end
199
+ opts.on('--watch', 'Watch files or directories for changes.',
200
+ 'The location of the generated CSS can be set using a colon:',
201
+ ' sass --watch input.sass:output.css',
202
+ ' sass --watch input-dir:output-dir') do
203
+ @options[:watch] = true
204
+ end
205
+ opts.on('--update', 'Compile files or directories to CSS.',
206
+ 'Locations are set like --watch.') do
207
+ @options[:update] = true
208
+ end
209
+ opts.on('--stop-on-error', 'If a file fails to compile, exit immediately.',
210
+ 'Only meaningful for --watch and --update.') do
211
+ @options[:stop_on_error] = true
212
+ end
213
+ opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
214
+ require 'stringio'
215
+ @options[:check_syntax] = true
216
+ @options[:output] = StringIO.new
217
+ end
218
+ opts.on('-t', '--style NAME',
219
+ 'Output style. Can be nested (default), compact, compressed, or expanded.') do |name|
220
+ @options[:for_engine][:style] = name.to_sym
221
+ end
222
+ opts.on('-q', '--quiet', 'Silence warnings during compilation.') do
223
+ @options[:for_engine][:quiet] = true
224
+ end
225
+ opts.on('-g', '--debug-info',
226
+ 'Emit extra information in the generated CSS that can be used by the FireSass Firebug plugin.') do
227
+ @options[:for_engine][:debug_info] = true
228
+ end
229
+ opts.on('-l', '--line-numbers', '--line-comments',
230
+ 'Emit comments in the generated CSS indicating the corresponding sass line.') do
231
+ @options[:for_engine][:line_numbers] = true
232
+ end
233
+ opts.on('-i', '--interactive',
234
+ 'Run an interactive SassScript shell.') do
235
+ @options[:interactive] = true
236
+ end
237
+ opts.on('-I', '--load-path PATH', 'Add a sass import path.') do |path|
238
+ @options[:for_engine][:load_paths] << path
239
+ end
240
+ opts.on('-r', '--require LIB', 'Require a Ruby library before running Sass.') do |lib|
241
+ require lib
242
+ end
243
+ opts.on('--cache-location PATH', 'The path to put cached Sass files. Defaults to .sass-cache.') do |loc|
244
+ @options[:for_engine][:cache_location] = loc
245
+ end
246
+ opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
247
+ @options[:for_engine][:cache] = false
248
+ end
249
+
250
+ unless ::Sass::Util.ruby1_8?
251
+ opts.on('-E encoding', 'Specify the default encoding for Sass files.') do |encoding|
252
+ Encoding.default_external = encoding
253
+ end
254
+ end
255
+ end
256
+
257
+ # Processes the options set by the command-line arguments,
258
+ # and runs the Sass compiler appropriately.
259
+ def process_result
260
+ require 'sass'
261
+
262
+ if !@options[:update] && !@options[:watch] &&
263
+ @args.first && colon_path?(@args.first)
264
+ if @args.size == 1
265
+ @args = split_colon_path(@args.first)
266
+ else
267
+ @options[:update] = true
268
+ end
269
+ end
270
+
271
+ return interactive if @options[:interactive]
272
+ return watch_or_update if @options[:watch] || @options[:update]
273
+ super
274
+ @options[:for_engine][:filename] = @options[:filename]
275
+
276
+ begin
277
+ input = @options[:input]
278
+ output = @options[:output]
279
+
280
+ @options[:for_engine][:syntax] ||= :scss if input.is_a?(File) && input.path =~ /\.scss$/
281
+ engine =
282
+ if input.is_a?(File) && !@options[:check_syntax]
283
+ ::Sass::Engine.for_file(input.path, @options[:for_engine])
284
+ else
285
+ # We don't need to do any special handling of @options[:check_syntax] here,
286
+ # because the Sass syntax checking happens alongside evaluation
287
+ # and evaluation doesn't actually evaluate any code anyway.
288
+ ::Sass::Engine.new(input.read(), @options[:for_engine])
289
+ end
290
+
291
+ input.close() if input.is_a?(File)
292
+
293
+ output.write(engine.render)
294
+ output.close() if output.is_a? File
295
+ rescue ::Sass::SyntaxError => e
296
+ raise e if @options[:trace]
297
+ raise e.sass_backtrace_str("standard input")
298
+ end
299
+ end
300
+
301
+ private
302
+
303
+ def interactive
304
+ require 'sass/repl'
305
+ ::Sass::Repl.new(@options).run
306
+ end
307
+
308
+ def watch_or_update
309
+ require 'sass/plugin'
310
+ ::Sass::Plugin.options.merge! @options[:for_engine]
311
+ ::Sass::Plugin.options[:unix_newlines] = @options[:unix_newlines]
312
+
313
+ raise <<MSG if @args.empty?
314
+ What files should I watch? Did you mean something like:
315
+ sass --watch input.sass:output.css
316
+ sass --watch input-dir:output-dir
317
+ MSG
318
+
319
+ if !colon_path?(@args[0]) && probably_dest_dir?(@args[1])
320
+ flag = @options[:update] ? "--update" : "--watch"
321
+ err =
322
+ if !File.exist?(@args[1])
323
+ "doesn't exist"
324
+ elsif @args[1] =~ /\.css$/
325
+ "is a CSS file"
326
+ end
327
+ raise <<MSG if err
328
+ File #{@args[1]} #{err}.
329
+ Did you mean: sass #{flag} #{@args[0]}:#{@args[1]}
330
+ MSG
331
+ end
332
+
333
+ dirs, files = @args.map {|name| split_colon_path(name)}.
334
+ partition {|i, _| File.directory? i}
335
+ files.map! {|from, to| [from, to || from.gsub(/\..*?$/, '.css')]}
336
+ dirs.map! {|from, to| [from, to || from]}
337
+ ::Sass::Plugin.options[:template_location] = dirs
338
+
339
+ ::Sass::Plugin.on_updating_stylesheet do |_, css|
340
+ if File.exists? css
341
+ puts_action :overwrite, :yellow, css
342
+ else
343
+ puts_action :create, :green, css
344
+ end
345
+ end
346
+
347
+ had_error = false
348
+ ::Sass::Plugin.on_creating_directory {|dirname| puts_action :directory, :green, dirname}
349
+ ::Sass::Plugin.on_deleting_css {|filename| puts_action :delete, :yellow, filename}
350
+ ::Sass::Plugin.on_compilation_error do |error, _, _|
351
+ raise error unless error.is_a?(::Sass::SyntaxError) && !@options[:stop_on_error]
352
+ had_error = true
353
+ puts_action :error, :red, "#{error.sass_filename} (Line #{error.sass_line}: #{error.message})"
354
+ end
355
+
356
+ if @options[:update]
357
+ ::Sass::Plugin.update_stylesheets(files)
358
+ exit 1 if had_error
359
+ return
360
+ end
361
+
362
+ puts ">>> Sass is watching for changes. Press Ctrl-C to stop."
363
+
364
+ ::Sass::Plugin.on_template_modified {|template| puts ">>> Change detected to: #{template}"}
365
+ ::Sass::Plugin.on_template_created {|template| puts ">>> New template detected: #{template}"}
366
+ ::Sass::Plugin.on_template_deleted {|template| puts ">>> Deleted template detected: #{template}"}
367
+
368
+ ::Sass::Plugin.watch(files)
369
+ end
370
+
371
+ def colon_path?(path)
372
+ !split_colon_path(path)[1].nil?
373
+ end
374
+
375
+ def split_colon_path(path)
376
+ one, two = path.split(':', 2)
377
+ if one && two && ::Sass::Util.windows? &&
378
+ one =~ /\A[A-Za-z]\Z/ && two =~ /\A[\/\\]/
379
+ # If we're on Windows and we were passed a drive letter path,
380
+ # don't split on that colon.
381
+ one2, two = two.split(':', 2)
382
+ one = one + ':' + one2
383
+ end
384
+ return one, two
385
+ end
386
+
387
+ # Whether path is likely to be meant as the destination
388
+ # in a source:dest pair.
389
+ def probably_dest_dir?(path)
390
+ return false unless path
391
+ return false if colon_path?(path)
392
+ return Dir.glob(File.join(path, "*.s[ca]ss")).empty?
393
+ end
394
+ end
395
+
396
+ # The `sass-convert` executable.
397
+ class SassConvert < Generic
398
+ # @param args [Array<String>] The command-line arguments
399
+ def initialize(args)
400
+ super
401
+ require 'sass'
402
+ @options[:for_tree] = {}
403
+ @options[:for_engine] = {:cache => false, :read_cache => true}
404
+ end
405
+
406
+ # Tells optparse how to parse the arguments.
407
+ #
408
+ # @param opts [OptionParser]
409
+ def set_opts(opts)
410
+ opts.banner = <<END
411
+ Usage: sass-convert [options] [INPUT] [OUTPUT]
412
+
413
+ Description:
414
+ Converts between CSS, Sass, and SCSS files.
415
+ E.g. converts from SCSS to Sass,
416
+ or converts from CSS to SCSS (adding appropriate nesting).
417
+
418
+ Options:
419
+ END
420
+
421
+ opts.on('-F', '--from FORMAT',
422
+ 'The format to convert from. Can be css, scss, sass, less, or sass2.',
423
+ 'sass2 is the same as sass, but updates more old syntax to new.',
424
+ 'By default, this is inferred from the input filename.',
425
+ 'If there is none, defaults to css.') do |name|
426
+ @options[:from] = name.downcase.to_sym
427
+ unless [:css, :scss, :sass, :less, :sass2].include?(@options[:from])
428
+ raise "Unknown format for sass-convert --from: #{name}"
429
+ end
430
+ try_less_note if @options[:from] == :less
431
+ end
432
+
433
+ opts.on('-T', '--to FORMAT',
434
+ 'The format to convert to. Can be scss or sass.',
435
+ 'By default, this is inferred from the output filename.',
436
+ 'If there is none, defaults to sass.') do |name|
437
+ @options[:to] = name.downcase.to_sym
438
+ unless [:scss, :sass].include?(@options[:to])
439
+ raise "Unknown format for sass-convert --to: #{name}"
440
+ end
441
+ end
442
+
443
+ opts.on('-R', '--recursive',
444
+ 'Convert all the files in a directory. Requires --from and --to.') do
445
+ @options[:recursive] = true
446
+ end
447
+
448
+ opts.on('-i', '--in-place',
449
+ 'Convert a file to its own syntax.',
450
+ 'This can be used to update some deprecated syntax.') do
451
+ @options[:in_place] = true
452
+ end
453
+
454
+ opts.on('--dasherize', 'Convert underscores to dashes') do
455
+ @options[:for_tree][:dasherize] = true
456
+ end
457
+
458
+ opts.on('--old', 'Output the old-style ":prop val" property syntax.',
459
+ 'Only meaningful when generating Sass.') do
460
+ @options[:for_tree][:old] = true
461
+ end
462
+
463
+ opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
464
+ @options[:for_engine][:read_cache] = false
465
+ end
466
+
467
+ unless ::Sass::Util.ruby1_8?
468
+ opts.on('-E encoding', 'Specify the default encoding for Sass and CSS files.') do |encoding|
469
+ Encoding.default_external = encoding
470
+ end
471
+ end
472
+
473
+ super
474
+ end
475
+
476
+ # Processes the options set by the command-line arguments,
477
+ # and runs the CSS compiler appropriately.
478
+ def process_result
479
+ require 'sass'
480
+
481
+ if @options[:recursive]
482
+ process_directory
483
+ return
484
+ end
485
+
486
+ super
487
+ input = @options[:input]
488
+ raise "Error: '#{input.path}' is a directory (did you mean to use --recursive?)" if File.directory?(input)
489
+ output = @options[:output]
490
+ output = input if @options[:in_place]
491
+ process_file(input, output)
492
+ end
493
+
494
+ private
495
+
496
+ def process_directory
497
+ unless input = @options[:input] = @args.shift
498
+ raise "Error: directory required when using --recursive."
499
+ end
500
+
501
+ output = @options[:output] = @args.shift
502
+ raise "Error: --from required when using --recursive." unless @options[:from]
503
+ raise "Error: --to required when using --recursive." unless @options[:to]
504
+ raise "Error: '#{@options[:input]}' is not a directory" unless File.directory?(@options[:input])
505
+ if @options[:output] && File.exists?(@options[:output]) && !File.directory?(@options[:output])
506
+ raise "Error: '#{@options[:output]}' is not a directory"
507
+ end
508
+ @options[:output] ||= @options[:input]
509
+
510
+ from = @options[:from]
511
+ from = :sass if from == :sass2
512
+ if @options[:to] == @options[:from] && !@options[:in_place]
513
+ fmt = @options[:from]
514
+ raise "Error: converting from #{fmt} to #{fmt} without --in-place"
515
+ end
516
+
517
+ ext = @options[:from]
518
+ ext = :sass if ext == :sass2
519
+ Dir.glob("#{@options[:input]}/**/*.#{ext}") do |f|
520
+ output =
521
+ if @options[:in_place]
522
+ f
523
+ elsif @options[:output]
524
+ output_name = f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
525
+ output_name[0...@options[:input].size] = @options[:output]
526
+ output_name
527
+ else
528
+ f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
529
+ end
530
+
531
+ unless File.directory?(File.dirname(output))
532
+ puts_action :directory, :green, File.dirname(output)
533
+ FileUtils.mkdir_p(File.dirname(output))
534
+ end
535
+ puts_action :convert, :green, f
536
+ if File.exists?(output)
537
+ puts_action :overwrite, :yellow, output
538
+ else
539
+ puts_action :create, :green, output
540
+ end
541
+
542
+ input = open_file(f)
543
+ output = @options[:in_place] ? input : open_file(output, "w")
544
+ process_file(input, output)
545
+ end
546
+ end
547
+
548
+ def process_file(input, output)
549
+ if input.is_a?(File)
550
+ @options[:from] ||=
551
+ case input.path
552
+ when /\.scss$/; :scss
553
+ when /\.sass$/; :sass
554
+ when /\.less$/; :less
555
+ when /\.css$/; :css
556
+ end
557
+ elsif @options[:in_place]
558
+ raise "Error: the --in-place option requires a filename."
559
+ end
560
+
561
+ if output.is_a?(File)
562
+ @options[:to] ||=
563
+ case output.path
564
+ when /\.scss$/; :scss
565
+ when /\.sass$/; :sass
566
+ end
567
+ end
568
+
569
+ if @options[:from] == :sass2
570
+ @options[:from] = :sass
571
+ @options[:for_engine][:sass2] = true
572
+ end
573
+
574
+ @options[:from] ||= :css
575
+ @options[:to] ||= :sass
576
+ @options[:for_engine][:syntax] = @options[:from]
577
+
578
+ out =
579
+ ::Sass::Util.silence_sass_warnings do
580
+ if @options[:from] == :css
581
+ require 'sass/css'
582
+ ::Sass::CSS.new(input.read, @options[:for_tree]).render(@options[:to])
583
+ elsif @options[:from] == :less
584
+ require 'sass/less'
585
+ try_less_note
586
+ input = input.read if input.is_a?(IO) && !input.is_a?(File) # Less is dumb
587
+ Less::Engine.new(input).to_tree.to_sass_tree.send("to_#{@options[:to]}", @options[:for_tree])
588
+ else
589
+ if input.is_a?(File)
590
+ ::Sass::Engine.for_file(input.path, @options[:for_engine])
591
+ else
592
+ ::Sass::Engine.new(input.read, @options[:for_engine])
593
+ end.to_tree.send("to_#{@options[:to]}", @options[:for_tree])
594
+ end
595
+ end
596
+
597
+ output = File.open(input.path, 'w') if @options[:in_place]
598
+ output.write(out)
599
+ rescue ::Sass::SyntaxError => e
600
+ raise e if @options[:trace]
601
+ file = " of #{e.sass_filename}" if e.sass_filename
602
+ raise "Error on line #{e.sass_line}#{file}: #{e.message}\n Use --trace for backtrace"
603
+ rescue LoadError => err
604
+ handle_load_error(err)
605
+ end
606
+
607
+ @@less_note_printed = false
608
+ def try_less_note
609
+ return if @@less_note_printed
610
+ @@less_note_printed = true
611
+ warn <<NOTE
612
+ * NOTE: Sass and Less are different languages, and they work differently.
613
+ * I'll do my best to translate, but some features -- especially mixins --
614
+ * should be checked by hand.
615
+ NOTE
616
+ end
617
+ end
618
+ end
619
+ end