aliddle-sass 1.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 (238) hide show
  1. data/.yardopts +11 -0
  2. data/CONTRIBUTING +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +201 -0
  5. data/Rakefile +347 -0
  6. data/VERSION +1 -0
  7. data/VERSION_NAME +1 -0
  8. data/bin/sass +9 -0
  9. data/bin/sass-convert +8 -0
  10. data/bin/scss +9 -0
  11. data/extra/update_watch.rb +13 -0
  12. data/init.rb +18 -0
  13. data/lib/sass.rb +95 -0
  14. data/lib/sass/cache_stores.rb +15 -0
  15. data/lib/sass/cache_stores/base.rb +88 -0
  16. data/lib/sass/cache_stores/chain.rb +33 -0
  17. data/lib/sass/cache_stores/filesystem.rb +60 -0
  18. data/lib/sass/cache_stores/memory.rb +47 -0
  19. data/lib/sass/cache_stores/null.rb +25 -0
  20. data/lib/sass/callbacks.rb +66 -0
  21. data/lib/sass/css.rb +409 -0
  22. data/lib/sass/engine.rb +928 -0
  23. data/lib/sass/environment.rb +101 -0
  24. data/lib/sass/error.rb +201 -0
  25. data/lib/sass/exec.rb +707 -0
  26. data/lib/sass/importers.rb +22 -0
  27. data/lib/sass/importers/base.rb +139 -0
  28. data/lib/sass/importers/filesystem.rb +190 -0
  29. data/lib/sass/logger.rb +15 -0
  30. data/lib/sass/logger/base.rb +32 -0
  31. data/lib/sass/logger/log_level.rb +49 -0
  32. data/lib/sass/media.rb +213 -0
  33. data/lib/sass/plugin.rb +132 -0
  34. data/lib/sass/plugin/compiler.rb +406 -0
  35. data/lib/sass/plugin/configuration.rb +123 -0
  36. data/lib/sass/plugin/generic.rb +15 -0
  37. data/lib/sass/plugin/merb.rb +48 -0
  38. data/lib/sass/plugin/rack.rb +60 -0
  39. data/lib/sass/plugin/rails.rb +47 -0
  40. data/lib/sass/plugin/staleness_checker.rb +183 -0
  41. data/lib/sass/railtie.rb +9 -0
  42. data/lib/sass/repl.rb +57 -0
  43. data/lib/sass/root.rb +7 -0
  44. data/lib/sass/script.rb +39 -0
  45. data/lib/sass/script/arg_list.rb +52 -0
  46. data/lib/sass/script/bool.rb +18 -0
  47. data/lib/sass/script/color.rb +606 -0
  48. data/lib/sass/script/css_lexer.rb +29 -0
  49. data/lib/sass/script/css_parser.rb +31 -0
  50. data/lib/sass/script/funcall.rb +237 -0
  51. data/lib/sass/script/functions.rb +1543 -0
  52. data/lib/sass/script/interpolation.rb +79 -0
  53. data/lib/sass/script/lexer.rb +348 -0
  54. data/lib/sass/script/list.rb +85 -0
  55. data/lib/sass/script/literal.rb +221 -0
  56. data/lib/sass/script/node.rb +99 -0
  57. data/lib/sass/script/null.rb +37 -0
  58. data/lib/sass/script/number.rb +453 -0
  59. data/lib/sass/script/operation.rb +110 -0
  60. data/lib/sass/script/parser.rb +495 -0
  61. data/lib/sass/script/string.rb +51 -0
  62. data/lib/sass/script/string_interpolation.rb +103 -0
  63. data/lib/sass/script/unary_operation.rb +69 -0
  64. data/lib/sass/script/variable.rb +58 -0
  65. data/lib/sass/scss.rb +16 -0
  66. data/lib/sass/scss/css_parser.rb +36 -0
  67. data/lib/sass/scss/parser.rb +1179 -0
  68. data/lib/sass/scss/rx.rb +133 -0
  69. data/lib/sass/scss/script_lexer.rb +15 -0
  70. data/lib/sass/scss/script_parser.rb +25 -0
  71. data/lib/sass/scss/static_parser.rb +54 -0
  72. data/lib/sass/selector.rb +452 -0
  73. data/lib/sass/selector/abstract_sequence.rb +94 -0
  74. data/lib/sass/selector/comma_sequence.rb +92 -0
  75. data/lib/sass/selector/sequence.rb +507 -0
  76. data/lib/sass/selector/simple.rb +119 -0
  77. data/lib/sass/selector/simple_sequence.rb +212 -0
  78. data/lib/sass/shared.rb +76 -0
  79. data/lib/sass/supports.rb +229 -0
  80. data/lib/sass/tree/charset_node.rb +22 -0
  81. data/lib/sass/tree/comment_node.rb +82 -0
  82. data/lib/sass/tree/content_node.rb +9 -0
  83. data/lib/sass/tree/css_import_node.rb +60 -0
  84. data/lib/sass/tree/debug_node.rb +18 -0
  85. data/lib/sass/tree/directive_node.rb +42 -0
  86. data/lib/sass/tree/each_node.rb +24 -0
  87. data/lib/sass/tree/extend_node.rb +36 -0
  88. data/lib/sass/tree/for_node.rb +36 -0
  89. data/lib/sass/tree/function_node.rb +34 -0
  90. data/lib/sass/tree/if_node.rb +52 -0
  91. data/lib/sass/tree/import_node.rb +75 -0
  92. data/lib/sass/tree/media_node.rb +58 -0
  93. data/lib/sass/tree/mixin_def_node.rb +38 -0
  94. data/lib/sass/tree/mixin_node.rb +39 -0
  95. data/lib/sass/tree/node.rb +196 -0
  96. data/lib/sass/tree/prop_node.rb +152 -0
  97. data/lib/sass/tree/return_node.rb +18 -0
  98. data/lib/sass/tree/root_node.rb +28 -0
  99. data/lib/sass/tree/rule_node.rb +132 -0
  100. data/lib/sass/tree/supports_node.rb +51 -0
  101. data/lib/sass/tree/trace_node.rb +32 -0
  102. data/lib/sass/tree/variable_node.rb +30 -0
  103. data/lib/sass/tree/visitors/base.rb +75 -0
  104. data/lib/sass/tree/visitors/check_nesting.rb +147 -0
  105. data/lib/sass/tree/visitors/convert.rb +316 -0
  106. data/lib/sass/tree/visitors/cssize.rb +229 -0
  107. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  108. data/lib/sass/tree/visitors/extend.rb +68 -0
  109. data/lib/sass/tree/visitors/perform.rb +446 -0
  110. data/lib/sass/tree/visitors/set_options.rb +125 -0
  111. data/lib/sass/tree/visitors/to_css.rb +230 -0
  112. data/lib/sass/tree/warn_node.rb +18 -0
  113. data/lib/sass/tree/while_node.rb +18 -0
  114. data/lib/sass/util.rb +906 -0
  115. data/lib/sass/util/multibyte_string_scanner.rb +155 -0
  116. data/lib/sass/util/subset_map.rb +109 -0
  117. data/lib/sass/util/test.rb +10 -0
  118. data/lib/sass/version.rb +126 -0
  119. data/rails/init.rb +1 -0
  120. data/test/Gemfile +3 -0
  121. data/test/Gemfile.lock +10 -0
  122. data/test/sass/cache_test.rb +89 -0
  123. data/test/sass/callbacks_test.rb +61 -0
  124. data/test/sass/conversion_test.rb +1760 -0
  125. data/test/sass/css2sass_test.rb +439 -0
  126. data/test/sass/data/hsl-rgb.txt +319 -0
  127. data/test/sass/engine_test.rb +3243 -0
  128. data/test/sass/exec_test.rb +86 -0
  129. data/test/sass/extend_test.rb +1461 -0
  130. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  131. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  132. data/test/sass/functions_test.rb +1139 -0
  133. data/test/sass/importer_test.rb +192 -0
  134. data/test/sass/logger_test.rb +58 -0
  135. data/test/sass/mock_importer.rb +49 -0
  136. data/test/sass/more_results/more1.css +9 -0
  137. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  138. data/test/sass/more_results/more_import.css +29 -0
  139. data/test/sass/more_templates/_more_partial.sass +2 -0
  140. data/test/sass/more_templates/more1.sass +23 -0
  141. data/test/sass/more_templates/more_import.sass +11 -0
  142. data/test/sass/plugin_test.rb +550 -0
  143. data/test/sass/results/alt.css +4 -0
  144. data/test/sass/results/basic.css +9 -0
  145. data/test/sass/results/cached_import_option.css +3 -0
  146. data/test/sass/results/compact.css +5 -0
  147. data/test/sass/results/complex.css +86 -0
  148. data/test/sass/results/compressed.css +1 -0
  149. data/test/sass/results/expanded.css +19 -0
  150. data/test/sass/results/filename_fn.css +3 -0
  151. data/test/sass/results/if.css +3 -0
  152. data/test/sass/results/import.css +31 -0
  153. data/test/sass/results/import_charset.css +5 -0
  154. data/test/sass/results/import_charset_1_8.css +5 -0
  155. data/test/sass/results/import_charset_ibm866.css +5 -0
  156. data/test/sass/results/import_content.css +1 -0
  157. data/test/sass/results/line_numbers.css +49 -0
  158. data/test/sass/results/mixins.css +95 -0
  159. data/test/sass/results/multiline.css +24 -0
  160. data/test/sass/results/nested.css +22 -0
  161. data/test/sass/results/options.css +1 -0
  162. data/test/sass/results/parent_ref.css +13 -0
  163. data/test/sass/results/script.css +16 -0
  164. data/test/sass/results/scss_import.css +31 -0
  165. data/test/sass/results/scss_importee.css +2 -0
  166. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  167. data/test/sass/results/subdir/subdir.css +3 -0
  168. data/test/sass/results/units.css +11 -0
  169. data/test/sass/results/warn.css +0 -0
  170. data/test/sass/results/warn_imported.css +0 -0
  171. data/test/sass/script_conversion_test.rb +299 -0
  172. data/test/sass/script_test.rb +591 -0
  173. data/test/sass/scss/css_test.rb +1093 -0
  174. data/test/sass/scss/rx_test.rb +156 -0
  175. data/test/sass/scss/scss_test.rb +2043 -0
  176. data/test/sass/scss/test_helper.rb +37 -0
  177. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  178. data/test/sass/templates/_double_import_loop2.sass +1 -0
  179. data/test/sass/templates/_filename_fn_import.scss +11 -0
  180. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  181. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  182. data/test/sass/templates/_imported_content.sass +3 -0
  183. data/test/sass/templates/_partial.sass +2 -0
  184. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  185. data/test/sass/templates/alt.sass +16 -0
  186. data/test/sass/templates/basic.sass +23 -0
  187. data/test/sass/templates/bork1.sass +2 -0
  188. data/test/sass/templates/bork2.sass +2 -0
  189. data/test/sass/templates/bork3.sass +2 -0
  190. data/test/sass/templates/bork4.sass +2 -0
  191. data/test/sass/templates/bork5.sass +3 -0
  192. data/test/sass/templates/cached_import_option.scss +3 -0
  193. data/test/sass/templates/compact.sass +17 -0
  194. data/test/sass/templates/complex.sass +305 -0
  195. data/test/sass/templates/compressed.sass +15 -0
  196. data/test/sass/templates/double_import_loop1.sass +1 -0
  197. data/test/sass/templates/expanded.sass +17 -0
  198. data/test/sass/templates/filename_fn.scss +18 -0
  199. data/test/sass/templates/if.sass +11 -0
  200. data/test/sass/templates/import.sass +12 -0
  201. data/test/sass/templates/import_charset.sass +9 -0
  202. data/test/sass/templates/import_charset_1_8.sass +6 -0
  203. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  204. data/test/sass/templates/import_content.sass +4 -0
  205. data/test/sass/templates/importee.less +2 -0
  206. data/test/sass/templates/importee.sass +19 -0
  207. data/test/sass/templates/line_numbers.sass +13 -0
  208. data/test/sass/templates/mixin_bork.sass +5 -0
  209. data/test/sass/templates/mixins.sass +76 -0
  210. data/test/sass/templates/multiline.sass +20 -0
  211. data/test/sass/templates/nested.sass +25 -0
  212. data/test/sass/templates/nested_bork1.sass +2 -0
  213. data/test/sass/templates/nested_bork2.sass +2 -0
  214. data/test/sass/templates/nested_bork3.sass +2 -0
  215. data/test/sass/templates/nested_bork4.sass +2 -0
  216. data/test/sass/templates/nested_import.sass +2 -0
  217. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  218. data/test/sass/templates/options.sass +2 -0
  219. data/test/sass/templates/parent_ref.sass +25 -0
  220. data/test/sass/templates/same_name_different_ext.sass +2 -0
  221. data/test/sass/templates/same_name_different_ext.scss +1 -0
  222. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  223. data/test/sass/templates/script.sass +101 -0
  224. data/test/sass/templates/scss_import.scss +11 -0
  225. data/test/sass/templates/scss_importee.scss +1 -0
  226. data/test/sass/templates/single_import_loop.sass +1 -0
  227. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  228. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  229. data/test/sass/templates/subdir/subdir.sass +6 -0
  230. data/test/sass/templates/units.sass +11 -0
  231. data/test/sass/templates/warn.sass +3 -0
  232. data/test/sass/templates/warn_imported.sass +4 -0
  233. data/test/sass/test_helper.rb +8 -0
  234. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  235. data/test/sass/util/subset_map_test.rb +91 -0
  236. data/test/sass/util_test.rb +313 -0
  237. data/test/test_helper.rb +80 -0
  238. metadata +348 -0
@@ -0,0 +1,101 @@
1
+ require 'set'
2
+
3
+ module Sass
4
+ # The lexical environment for SassScript.
5
+ # This keeps track of variable, mixin, and function 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_reader :options
22
+ attr_writer :caller
23
+ attr_writer :content
24
+
25
+ # @param options [{Symbol => Object}] The options hash. See
26
+ # {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
27
+ # @param parent [Environment] See \{#parent}
28
+ def initialize(parent = nil, options = nil)
29
+ @parent = parent
30
+ @options = options || (parent && parent.options) || {}
31
+ end
32
+
33
+ # The environment of the caller of this environment's mixin or function.
34
+ # @return {Environment?}
35
+ def caller
36
+ @caller || (@parent && @parent.caller)
37
+ end
38
+
39
+ # The content passed to this environmnet. This is naturally only set
40
+ # for mixin body environments with content passed in.
41
+ # @return {Environment?}
42
+ def content
43
+ @content || (@parent && @parent.content)
44
+ end
45
+
46
+ private
47
+
48
+ class << self
49
+ private
50
+ UNDERSCORE, DASH = '_', '-'
51
+
52
+ # Note: when updating this,
53
+ # update sass/yard/inherited_hash.rb as well.
54
+ def inherited_hash(name)
55
+ class_eval <<RUBY, __FILE__, __LINE__ + 1
56
+ def #{name}(name)
57
+ _#{name}(name.tr(UNDERSCORE, DASH))
58
+ end
59
+
60
+ def _#{name}(name)
61
+ (@#{name}s && @#{name}s[name]) || @parent && @parent._#{name}(name)
62
+ end
63
+ protected :_#{name}
64
+
65
+ def set_#{name}(name, value)
66
+ name = name.tr(UNDERSCORE, DASH)
67
+ @#{name}s[name] = value unless try_set_#{name}(name, value)
68
+ end
69
+
70
+ def try_set_#{name}(name, value)
71
+ @#{name}s ||= {}
72
+ if @#{name}s.include?(name)
73
+ @#{name}s[name] = value
74
+ true
75
+ elsif @parent
76
+ @parent.try_set_#{name}(name, value)
77
+ else
78
+ false
79
+ end
80
+ end
81
+ protected :try_set_#{name}
82
+
83
+ def set_local_#{name}(name, value)
84
+ @#{name}s ||= {}
85
+ @#{name}s[name.tr(UNDERSCORE, DASH)] = value
86
+ end
87
+ RUBY
88
+ end
89
+ end
90
+
91
+ # variable
92
+ # Script::Literal
93
+ inherited_hash :var
94
+ # mixin
95
+ # Sass::Callable
96
+ inherited_hash :mixin
97
+ # function
98
+ # Sass::Callable
99
+ inherited_hash :function
100
+ end
101
+ end
data/lib/sass/error.rb ADDED
@@ -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.gsub("*/", "*\\/")}
169
+
170
+ Backtrace:\n#{e.backtrace.join("\n").gsub("*/", "*\\/")}
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
data/lib/sass/exec.rb ADDED
@@ -0,0 +1,707 @@
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 ||= args.shift || $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
+ return if @options[:for_engine][:quiet]
127
+ printf color(color, "%11s %s\n"), name, arg
128
+ STDOUT.flush
129
+ end
130
+
131
+ # Same as \{Kernel.puts}, but doesn't print anything if the `--quiet` option is set.
132
+ #
133
+ # @param args [Array] Passed on to \{Kernel.puts}
134
+ def puts(*args)
135
+ return if @options[:for_engine][:quiet]
136
+ Kernel.puts(*args)
137
+ end
138
+
139
+ # Wraps the given string in terminal escapes
140
+ # causing it to have the given color.
141
+ # If terminal esapes aren't supported on this platform,
142
+ # just returns the string instead.
143
+ #
144
+ # @param color [Symbol] The name of the color to use.
145
+ # Can be `:red`, `:green`, or `:yellow`.
146
+ # @param str [String] The string to wrap in the given color.
147
+ # @return [String] The wrapped string.
148
+ def color(color, str)
149
+ raise "[BUG] Unrecognized color #{color}" unless COLORS[color]
150
+
151
+ # Almost any real Unix terminal will support color,
152
+ # so we just filter for Windows terms (which don't set TERM)
153
+ # and not-real terminals, which aren't ttys.
154
+ return str if ENV["TERM"].nil? || ENV["TERM"].empty? || !STDOUT.tty?
155
+ return "\e[#{COLORS[color]}m#{str}\e[0m"
156
+ end
157
+
158
+ def write_output(text, destination)
159
+ if destination.is_a?(String)
160
+ open_file(destination, 'w') {|file| file.write(text)}
161
+ else
162
+ destination.write(text)
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ def open_file(filename, flag = 'r')
169
+ return if filename.nil?
170
+ flag = 'wb' if @options[:unix_newlines] && flag == 'w'
171
+ file = File.open(filename, flag)
172
+ return file unless block_given?
173
+ yield file
174
+ file.close
175
+ end
176
+
177
+ def handle_load_error(err)
178
+ dep = err.message[/^no such file to load -- (.*)/, 1]
179
+ raise err if @options[:trace] || dep.nil? || dep.empty?
180
+ $stderr.puts <<MESSAGE
181
+ Required dependency #{dep} not found!
182
+ Run "gem install #{dep}" to get it.
183
+ Use --trace for backtrace.
184
+ MESSAGE
185
+ exit 1
186
+ end
187
+ end
188
+
189
+ # The `sass` executable.
190
+ class Sass < Generic
191
+ attr_reader :default_syntax
192
+
193
+ # @param args [Array<String>] The command-line arguments
194
+ def initialize(args)
195
+ super
196
+ @options[:for_engine] = {
197
+ :load_paths => ['.'] + (ENV['SASSPATH'] || '').split(File::PATH_SEPARATOR)
198
+ }
199
+ @default_syntax = :sass
200
+ end
201
+
202
+ protected
203
+
204
+ # Tells optparse how to parse the arguments.
205
+ #
206
+ # @param opts [OptionParser]
207
+ def set_opts(opts)
208
+ super
209
+
210
+ opts.banner = <<END
211
+ Usage: #{default_syntax} [options] [INPUT] [OUTPUT]
212
+
213
+ Description:
214
+ Converts SCSS or Sass files to CSS.
215
+
216
+ Options:
217
+ END
218
+
219
+ if @default_syntax == :sass
220
+ opts.on('--scss',
221
+ 'Use the CSS-superset SCSS syntax.') do
222
+ @options[:for_engine][:syntax] = :scss
223
+ end
224
+ else
225
+ opts.on('--sass',
226
+ 'Use the Indented syntax.') do
227
+ @options[:for_engine][:syntax] = :sass
228
+ end
229
+ end
230
+ opts.on('--watch', 'Watch files or directories for changes.',
231
+ 'The location of the generated CSS can be set using a colon:',
232
+ " #{@default_syntax} --watch input.#{@default_syntax}:output.css",
233
+ " #{@default_syntax} --watch input-dir:output-dir") do
234
+ @options[:watch] = true
235
+ end
236
+ opts.on('--update', 'Compile files or directories to CSS.',
237
+ 'Locations are set like --watch.') do
238
+ @options[:update] = true
239
+ end
240
+ opts.on('--stop-on-error', 'If a file fails to compile, exit immediately.',
241
+ 'Only meaningful for --watch and --update.') do
242
+ @options[:stop_on_error] = true
243
+ end
244
+ opts.on('--poll', 'Check for file changes manually, rather than relying on the OS.',
245
+ 'Only meaningful for --watch.') do
246
+ @options[:poll] = true
247
+ end
248
+ opts.on('-f', '--force', 'Recompile all Sass files, even if the CSS file is newer.',
249
+ 'Only meaningful for --update.') do
250
+ @options[:force] = true
251
+ end
252
+ opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
253
+ require 'stringio'
254
+ @options[:check_syntax] = true
255
+ @options[:output] = StringIO.new
256
+ end
257
+ opts.on('-t', '--style NAME',
258
+ 'Output style. Can be nested (default), compact, compressed, or expanded.') do |name|
259
+ @options[:for_engine][:style] = name.to_sym
260
+ end
261
+ opts.on('--precision NUMBER_OF_DIGITS', Integer,
262
+ 'How many digits of precision to use when outputting decimal numbers. Defaults to 3.') do |precision|
263
+ ::Sass::Script::Number.precision = precision
264
+ end
265
+ opts.on('-q', '--quiet', 'Silence warnings and status messages during compilation.') do
266
+ @options[:for_engine][:quiet] = true
267
+ end
268
+ opts.on('--compass', 'Make Compass imports available and load project configuration.') do
269
+ @options[:compass] = true
270
+ end
271
+ opts.on('-g', '--debug-info',
272
+ 'Emit extra information in the generated CSS that can be used by the FireSass Firebug plugin.') do
273
+ @options[:for_engine][:debug_info] = true
274
+ end
275
+ opts.on('-l', '--line-numbers', '--line-comments',
276
+ 'Emit comments in the generated CSS indicating the corresponding source line.') do
277
+ @options[:for_engine][:line_numbers] = true
278
+ end
279
+ opts.on('-i', '--interactive',
280
+ 'Run an interactive SassScript shell.') do
281
+ @options[:interactive] = true
282
+ end
283
+ opts.on('-I', '--load-path PATH', 'Add a sass import path.') do |path|
284
+ @options[:for_engine][:load_paths] << path
285
+ end
286
+ opts.on('-r', '--require LIB', 'Require a Ruby library before running Sass.') do |lib|
287
+ require lib
288
+ end
289
+ opts.on('--cache-location PATH', 'The path to put cached Sass files. Defaults to .sass-cache.') do |loc|
290
+ @options[:for_engine][:cache_location] = loc
291
+ end
292
+ opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
293
+ @options[:for_engine][:cache] = false
294
+ end
295
+
296
+ encoding_desc = if ::Sass::Util.ruby1_8?
297
+ 'Does not work in ruby 1.8.'
298
+ else
299
+ 'Specify the default encoding for Sass files.'
300
+ end
301
+ opts.on('-E encoding', encoding_desc) do |encoding|
302
+ if ::Sass::Util.ruby1_8?
303
+ $stderr.puts "Specifying the encoding is not supported in ruby 1.8."
304
+ exit 1
305
+ else
306
+ Encoding.default_external = encoding
307
+ end
308
+ end
309
+ end
310
+
311
+ # Processes the options set by the command-line arguments,
312
+ # and runs the Sass compiler appropriately.
313
+ def process_result
314
+ require 'sass'
315
+
316
+ if !@options[:update] && !@options[:watch] &&
317
+ @args.first && colon_path?(@args.first)
318
+ if @args.size == 1
319
+ @args = split_colon_path(@args.first)
320
+ else
321
+ @options[:update] = true
322
+ end
323
+ end
324
+ load_compass if @options[:compass]
325
+ return interactive if @options[:interactive]
326
+ return watch_or_update if @options[:watch] || @options[:update]
327
+ super
328
+ @options[:for_engine][:filename] = @options[:filename]
329
+ @options[:for_engine][:css_filename] = @options[:output] if @options[:output].is_a?(String)
330
+
331
+ begin
332
+ input = @options[:input]
333
+ output = @options[:output]
334
+
335
+ @options[:for_engine][:syntax] ||= :scss if input.is_a?(File) && input.path =~ /\.scss$/
336
+ @options[:for_engine][:syntax] ||= @default_syntax
337
+ engine =
338
+ if input.is_a?(File) && !@options[:check_syntax]
339
+ ::Sass::Engine.for_file(input.path, @options[:for_engine])
340
+ else
341
+ # We don't need to do any special handling of @options[:check_syntax] here,
342
+ # because the Sass syntax checking happens alongside evaluation
343
+ # and evaluation doesn't actually evaluate any code anyway.
344
+ ::Sass::Engine.new(input.read(), @options[:for_engine])
345
+ end
346
+
347
+ input.close() if input.is_a?(File)
348
+
349
+ write_output(engine.render, output)
350
+ rescue ::Sass::SyntaxError => e
351
+ raise e if @options[:trace]
352
+ raise e.sass_backtrace_str("standard input")
353
+ end
354
+ end
355
+
356
+ private
357
+
358
+ def load_compass
359
+ begin
360
+ require 'compass'
361
+ rescue LoadError
362
+ require 'rubygems'
363
+ begin
364
+ require 'compass'
365
+ rescue LoadError
366
+ puts "ERROR: Cannot load compass."
367
+ exit 1
368
+ end
369
+ end
370
+ Compass.add_project_configuration
371
+ Compass.configuration.project_path ||= Dir.pwd
372
+ @options[:for_engine][:load_paths] += Compass.configuration.sass_load_paths
373
+ end
374
+
375
+ def interactive
376
+ require 'sass/repl'
377
+ ::Sass::Repl.new(@options).run
378
+ end
379
+
380
+ def watch_or_update
381
+ require 'sass/plugin'
382
+ ::Sass::Plugin.options.merge! @options[:for_engine]
383
+ ::Sass::Plugin.options[:unix_newlines] = @options[:unix_newlines]
384
+ ::Sass::Plugin.options[:poll] = @options[:poll]
385
+
386
+ if @options[:force]
387
+ raise "The --force flag may only be used with --update." unless @options[:update]
388
+ ::Sass::Plugin.options[:always_update] = true
389
+ end
390
+
391
+ raise <<MSG if @args.empty?
392
+ What files should I watch? Did you mean something like:
393
+ #{@default_syntax} --watch input.#{@default_syntax}:output.css
394
+ #{@default_syntax} --watch input-dir:output-dir
395
+ MSG
396
+
397
+ if !colon_path?(@args[0]) && probably_dest_dir?(@args[1])
398
+ flag = @options[:update] ? "--update" : "--watch"
399
+ err =
400
+ if !File.exist?(@args[1])
401
+ "doesn't exist"
402
+ elsif @args[1] =~ /\.css$/
403
+ "is a CSS file"
404
+ end
405
+ raise <<MSG if err
406
+ File #{@args[1]} #{err}.
407
+ Did you mean: #{@default_syntax} #{flag} #{@args[0]}:#{@args[1]}
408
+ MSG
409
+ end
410
+
411
+ dirs, files = @args.map {|name| split_colon_path(name)}.
412
+ partition {|i, _| File.directory? i}
413
+ files.map! {|from, to| [from, to || from.gsub(/\.[^.]*?$/, '.css')]}
414
+ dirs.map! {|from, to| [from, to || from]}
415
+ ::Sass::Plugin.options[:template_location] = dirs
416
+
417
+ ::Sass::Plugin.on_updated_stylesheet do |_, css|
418
+ if File.exists? css
419
+ puts_action :overwrite, :yellow, css
420
+ else
421
+ puts_action :create, :green, css
422
+ end
423
+ end
424
+
425
+ had_error = false
426
+ ::Sass::Plugin.on_creating_directory {|dirname| puts_action :directory, :green, dirname}
427
+ ::Sass::Plugin.on_deleting_css {|filename| puts_action :delete, :yellow, filename}
428
+ ::Sass::Plugin.on_compilation_error do |error, _, _|
429
+ if error.is_a?(SystemCallError) && !@options[:stop_on_error]
430
+ had_error = true
431
+ puts_action :error, :red, error.message
432
+ STDOUT.flush
433
+ next
434
+ end
435
+
436
+ raise error unless error.is_a?(::Sass::SyntaxError) && !@options[:stop_on_error]
437
+ had_error = true
438
+ puts_action :error, :red, "#{error.sass_filename} (Line #{error.sass_line}: #{error.message})"
439
+ STDOUT.flush
440
+ end
441
+
442
+ if @options[:update]
443
+ ::Sass::Plugin.update_stylesheets(files)
444
+ exit 1 if had_error
445
+ return
446
+ end
447
+
448
+ puts ">>> Sass is watching for changes. Press Ctrl-C to stop."
449
+
450
+ ::Sass::Plugin.on_template_modified do |template|
451
+ puts ">>> Change detected to: #{template}"
452
+ STDOUT.flush
453
+ end
454
+ ::Sass::Plugin.on_template_created do |template|
455
+ puts ">>> New template detected: #{template}"
456
+ STDOUT.flush
457
+ end
458
+ ::Sass::Plugin.on_template_deleted do |template|
459
+ puts ">>> Deleted template detected: #{template}"
460
+ STDOUT.flush
461
+ end
462
+
463
+ ::Sass::Plugin.watch(files)
464
+ end
465
+
466
+ def colon_path?(path)
467
+ !split_colon_path(path)[1].nil?
468
+ end
469
+
470
+ def split_colon_path(path)
471
+ one, two = path.split(':', 2)
472
+ if one && two && ::Sass::Util.windows? &&
473
+ one =~ /\A[A-Za-z]\Z/ && two =~ /\A[\/\\]/
474
+ # If we're on Windows and we were passed a drive letter path,
475
+ # don't split on that colon.
476
+ one2, two = two.split(':', 2)
477
+ one = one + ':' + one2
478
+ end
479
+ return one, two
480
+ end
481
+
482
+ # Whether path is likely to be meant as the destination
483
+ # in a source:dest pair.
484
+ def probably_dest_dir?(path)
485
+ return false unless path
486
+ return false if colon_path?(path)
487
+ return ::Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty?
488
+ end
489
+ end
490
+
491
+ class Scss < Sass
492
+ # @param args [Array<String>] The command-line arguments
493
+ def initialize(args)
494
+ super
495
+ @default_syntax = :scss
496
+ end
497
+ end
498
+
499
+ # The `sass-convert` executable.
500
+ class SassConvert < Generic
501
+ # @param args [Array<String>] The command-line arguments
502
+ def initialize(args)
503
+ super
504
+ require 'sass'
505
+ @options[:for_tree] = {}
506
+ @options[:for_engine] = {:cache => false, :read_cache => true}
507
+ end
508
+
509
+ # Tells optparse how to parse the arguments.
510
+ #
511
+ # @param opts [OptionParser]
512
+ def set_opts(opts)
513
+ opts.banner = <<END
514
+ Usage: sass-convert [options] [INPUT] [OUTPUT]
515
+
516
+ Description:
517
+ Converts between CSS, Sass, and SCSS files.
518
+ E.g. converts from SCSS to Sass,
519
+ or converts from CSS to SCSS (adding appropriate nesting).
520
+
521
+ Options:
522
+ END
523
+
524
+ opts.on('-F', '--from FORMAT',
525
+ 'The format to convert from. Can be css, scss, sass.',
526
+ 'By default, this is inferred from the input filename.',
527
+ 'If there is none, defaults to css.') do |name|
528
+ @options[:from] = name.downcase.to_sym
529
+ raise "sass-convert no longer supports LessCSS." if @options[:from] == :less
530
+ unless [:css, :scss, :sass].include?(@options[:from])
531
+ raise "Unknown format for sass-convert --from: #{name}"
532
+ end
533
+ end
534
+
535
+ opts.on('-T', '--to FORMAT',
536
+ 'The format to convert to. Can be scss or sass.',
537
+ 'By default, this is inferred from the output filename.',
538
+ 'If there is none, defaults to sass.') do |name|
539
+ @options[:to] = name.downcase.to_sym
540
+ unless [:scss, :sass].include?(@options[:to])
541
+ raise "Unknown format for sass-convert --to: #{name}"
542
+ end
543
+ end
544
+
545
+ opts.on('-R', '--recursive',
546
+ 'Convert all the files in a directory. Requires --from and --to.') do
547
+ @options[:recursive] = true
548
+ end
549
+
550
+ opts.on('-i', '--in-place',
551
+ 'Convert a file to its own syntax.',
552
+ 'This can be used to update some deprecated syntax.') do
553
+ @options[:in_place] = true
554
+ end
555
+
556
+ opts.on('--dasherize', 'Convert underscores to dashes') do
557
+ @options[:for_tree][:dasherize] = true
558
+ end
559
+
560
+ opts.on('--indent NUM',
561
+ 'How many spaces to use for each level of indentation. Defaults to 2.',
562
+ '"t" means use hard tabs.') do |indent|
563
+
564
+ if indent == 't'
565
+ @options[:for_tree][:indent] = "\t"
566
+ else
567
+ @options[:for_tree][:indent] = " " * indent.to_i
568
+ end
569
+ end
570
+
571
+ opts.on('--old', 'Output the old-style ":prop val" property syntax.',
572
+ 'Only meaningful when generating Sass.') do
573
+ @options[:for_tree][:old] = true
574
+ end
575
+
576
+ opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
577
+ @options[:for_engine][:read_cache] = false
578
+ end
579
+
580
+ unless ::Sass::Util.ruby1_8?
581
+ opts.on('-E encoding', 'Specify the default encoding for Sass and CSS files.') do |encoding|
582
+ Encoding.default_external = encoding
583
+ end
584
+ end
585
+
586
+ super
587
+ end
588
+
589
+ # Processes the options set by the command-line arguments,
590
+ # and runs the CSS compiler appropriately.
591
+ def process_result
592
+ require 'sass'
593
+
594
+ if @options[:recursive]
595
+ process_directory
596
+ return
597
+ end
598
+
599
+ super
600
+ input = @options[:input]
601
+ raise "Error: '#{input.path}' is a directory (did you mean to use --recursive?)" if File.directory?(input)
602
+ output = @options[:output]
603
+ output = input if @options[:in_place]
604
+ process_file(input, output)
605
+ end
606
+
607
+ private
608
+
609
+ def process_directory
610
+ unless input = @options[:input] = @args.shift
611
+ raise "Error: directory required when using --recursive."
612
+ end
613
+
614
+ output = @options[:output] = @args.shift
615
+ raise "Error: --from required when using --recursive." unless @options[:from]
616
+ raise "Error: --to required when using --recursive." unless @options[:to]
617
+ raise "Error: '#{@options[:input]}' is not a directory" unless File.directory?(@options[:input])
618
+ if @options[:output] && File.exists?(@options[:output]) && !File.directory?(@options[:output])
619
+ raise "Error: '#{@options[:output]}' is not a directory"
620
+ end
621
+ @options[:output] ||= @options[:input]
622
+
623
+ if @options[:to] == @options[:from] && !@options[:in_place]
624
+ fmt = @options[:from]
625
+ raise "Error: converting from #{fmt} to #{fmt} without --in-place"
626
+ end
627
+
628
+ ext = @options[:from]
629
+ ::Sass::Util.glob("#{@options[:input]}/**/*.#{ext}") do |f|
630
+ output =
631
+ if @options[:in_place]
632
+ f
633
+ elsif @options[:output]
634
+ output_name = f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
635
+ output_name[0...@options[:input].size] = @options[:output]
636
+ output_name
637
+ else
638
+ f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
639
+ end
640
+
641
+ unless File.directory?(File.dirname(output))
642
+ puts_action :directory, :green, File.dirname(output)
643
+ FileUtils.mkdir_p(File.dirname(output))
644
+ end
645
+ puts_action :convert, :green, f
646
+ if File.exists?(output)
647
+ puts_action :overwrite, :yellow, output
648
+ else
649
+ puts_action :create, :green, output
650
+ end
651
+
652
+ input = open_file(f)
653
+ process_file(input, output)
654
+ end
655
+ end
656
+
657
+ def process_file(input, output)
658
+ if input.is_a?(File)
659
+ @options[:from] ||=
660
+ case input.path
661
+ when /\.scss$/; :scss
662
+ when /\.sass$/; :sass
663
+ when /\.less$/; raise "sass-convert no longer supports LessCSS."
664
+ when /\.css$/; :css
665
+ end
666
+ elsif @options[:in_place]
667
+ raise "Error: the --in-place option requires a filename."
668
+ end
669
+
670
+ if output.is_a?(File)
671
+ @options[:to] ||=
672
+ case output.path
673
+ when /\.scss$/; :scss
674
+ when /\.sass$/; :sass
675
+ end
676
+ end
677
+
678
+ @options[:from] ||= :css
679
+ @options[:to] ||= :sass
680
+ @options[:for_engine][:syntax] = @options[:from]
681
+
682
+ out =
683
+ ::Sass::Util.silence_sass_warnings do
684
+ if @options[:from] == :css
685
+ require 'sass/css'
686
+ ::Sass::CSS.new(input.read, @options[:for_tree]).render(@options[:to])
687
+ else
688
+ if input.is_a?(File)
689
+ ::Sass::Engine.for_file(input.path, @options[:for_engine])
690
+ else
691
+ ::Sass::Engine.new(input.read, @options[:for_engine])
692
+ end.to_tree.send("to_#{@options[:to]}", @options[:for_tree])
693
+ end
694
+ end
695
+
696
+ output = input.path if @options[:in_place]
697
+ write_output(out, output)
698
+ rescue ::Sass::SyntaxError => e
699
+ raise e if @options[:trace]
700
+ file = " of #{e.sass_filename}" if e.sass_filename
701
+ raise "Error on line #{e.sass_line}#{file}: #{e.message}\n Use --trace for backtrace"
702
+ rescue LoadError => err
703
+ handle_load_error(err)
704
+ end
705
+ end
706
+ end
707
+ end