drnic-haml 2.3.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 (190) hide show
  1. data/.yardopts +5 -0
  2. data/CONTRIBUTING +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +347 -0
  5. data/REVISION +1 -0
  6. data/Rakefile +371 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/css2sass +7 -0
  10. data/bin/haml +9 -0
  11. data/bin/html2haml +7 -0
  12. data/bin/sass +8 -0
  13. data/extra/haml-mode.el +663 -0
  14. data/extra/sass-mode.el +205 -0
  15. data/extra/update_watch.rb +13 -0
  16. data/init.rb +8 -0
  17. data/lib/haml.rb +40 -0
  18. data/lib/haml/buffer.rb +307 -0
  19. data/lib/haml/engine.rb +301 -0
  20. data/lib/haml/error.rb +22 -0
  21. data/lib/haml/exec.rb +470 -0
  22. data/lib/haml/filters.rb +341 -0
  23. data/lib/haml/helpers.rb +560 -0
  24. data/lib/haml/helpers/action_view_extensions.rb +40 -0
  25. data/lib/haml/helpers/action_view_mods.rb +176 -0
  26. data/lib/haml/herb.rb +96 -0
  27. data/lib/haml/html.rb +308 -0
  28. data/lib/haml/precompiler.rb +997 -0
  29. data/lib/haml/shared.rb +78 -0
  30. data/lib/haml/template.rb +51 -0
  31. data/lib/haml/template/patch.rb +58 -0
  32. data/lib/haml/template/plugin.rb +71 -0
  33. data/lib/haml/util.rb +244 -0
  34. data/lib/haml/version.rb +64 -0
  35. data/lib/sass.rb +24 -0
  36. data/lib/sass/css.rb +423 -0
  37. data/lib/sass/engine.rb +491 -0
  38. data/lib/sass/environment.rb +79 -0
  39. data/lib/sass/error.rb +162 -0
  40. data/lib/sass/files.rb +133 -0
  41. data/lib/sass/plugin.rb +170 -0
  42. data/lib/sass/plugin/merb.rb +57 -0
  43. data/lib/sass/plugin/rails.rb +23 -0
  44. data/lib/sass/repl.rb +58 -0
  45. data/lib/sass/script.rb +55 -0
  46. data/lib/sass/script/bool.rb +17 -0
  47. data/lib/sass/script/color.rb +183 -0
  48. data/lib/sass/script/funcall.rb +50 -0
  49. data/lib/sass/script/functions.rb +199 -0
  50. data/lib/sass/script/lexer.rb +191 -0
  51. data/lib/sass/script/literal.rb +177 -0
  52. data/lib/sass/script/node.rb +14 -0
  53. data/lib/sass/script/number.rb +381 -0
  54. data/lib/sass/script/operation.rb +45 -0
  55. data/lib/sass/script/parser.rb +222 -0
  56. data/lib/sass/script/string.rb +12 -0
  57. data/lib/sass/script/unary_operation.rb +34 -0
  58. data/lib/sass/script/variable.rb +31 -0
  59. data/lib/sass/tree/comment_node.rb +84 -0
  60. data/lib/sass/tree/debug_node.rb +30 -0
  61. data/lib/sass/tree/directive_node.rb +70 -0
  62. data/lib/sass/tree/for_node.rb +48 -0
  63. data/lib/sass/tree/if_node.rb +54 -0
  64. data/lib/sass/tree/import_node.rb +69 -0
  65. data/lib/sass/tree/mixin_def_node.rb +29 -0
  66. data/lib/sass/tree/mixin_node.rb +48 -0
  67. data/lib/sass/tree/node.rb +252 -0
  68. data/lib/sass/tree/prop_node.rb +106 -0
  69. data/lib/sass/tree/root_node.rb +56 -0
  70. data/lib/sass/tree/rule_node.rb +220 -0
  71. data/lib/sass/tree/variable_node.rb +34 -0
  72. data/lib/sass/tree/while_node.rb +31 -0
  73. data/rails/init.rb +1 -0
  74. data/test/benchmark.rb +99 -0
  75. data/test/haml/engine_test.rb +1129 -0
  76. data/test/haml/helper_test.rb +282 -0
  77. data/test/haml/html2haml_test.rb +258 -0
  78. data/test/haml/markaby/standard.mab +52 -0
  79. data/test/haml/mocks/article.rb +6 -0
  80. data/test/haml/results/content_for_layout.xhtml +12 -0
  81. data/test/haml/results/eval_suppressed.xhtml +9 -0
  82. data/test/haml/results/filters.xhtml +62 -0
  83. data/test/haml/results/helpers.xhtml +93 -0
  84. data/test/haml/results/helpful.xhtml +10 -0
  85. data/test/haml/results/just_stuff.xhtml +68 -0
  86. data/test/haml/results/list.xhtml +12 -0
  87. data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
  88. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  89. data/test/haml/results/original_engine.xhtml +20 -0
  90. data/test/haml/results/partial_layout.xhtml +5 -0
  91. data/test/haml/results/partials.xhtml +21 -0
  92. data/test/haml/results/render_layout.xhtml +3 -0
  93. data/test/haml/results/silent_script.xhtml +74 -0
  94. data/test/haml/results/standard.xhtml +162 -0
  95. data/test/haml/results/tag_parsing.xhtml +23 -0
  96. data/test/haml/results/very_basic.xhtml +5 -0
  97. data/test/haml/results/whitespace_handling.xhtml +89 -0
  98. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  99. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  100. data/test/haml/rhtml/action_view.rhtml +62 -0
  101. data/test/haml/rhtml/standard.rhtml +54 -0
  102. data/test/haml/spec_test.rb +44 -0
  103. data/test/haml/template_test.rb +217 -0
  104. data/test/haml/templates/_av_partial_1.haml +9 -0
  105. data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
  106. data/test/haml/templates/_av_partial_2.haml +5 -0
  107. data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
  108. data/test/haml/templates/_layout.erb +3 -0
  109. data/test/haml/templates/_layout_for_partial.haml +3 -0
  110. data/test/haml/templates/_partial.haml +8 -0
  111. data/test/haml/templates/_text_area.haml +3 -0
  112. data/test/haml/templates/action_view.haml +47 -0
  113. data/test/haml/templates/action_view_ugly.haml +47 -0
  114. data/test/haml/templates/breakage.haml +8 -0
  115. data/test/haml/templates/content_for_layout.haml +8 -0
  116. data/test/haml/templates/eval_suppressed.haml +11 -0
  117. data/test/haml/templates/filters.haml +66 -0
  118. data/test/haml/templates/helpers.haml +95 -0
  119. data/test/haml/templates/helpful.haml +11 -0
  120. data/test/haml/templates/just_stuff.haml +83 -0
  121. data/test/haml/templates/list.haml +12 -0
  122. data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
  123. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  124. data/test/haml/templates/original_engine.haml +17 -0
  125. data/test/haml/templates/partial_layout.haml +3 -0
  126. data/test/haml/templates/partialize.haml +1 -0
  127. data/test/haml/templates/partials.haml +12 -0
  128. data/test/haml/templates/render_layout.haml +2 -0
  129. data/test/haml/templates/silent_script.haml +40 -0
  130. data/test/haml/templates/standard.haml +42 -0
  131. data/test/haml/templates/standard_ugly.haml +42 -0
  132. data/test/haml/templates/tag_parsing.haml +21 -0
  133. data/test/haml/templates/very_basic.haml +4 -0
  134. data/test/haml/templates/whitespace_handling.haml +87 -0
  135. data/test/haml/util_test.rb +92 -0
  136. data/test/linked_rails.rb +12 -0
  137. data/test/sass/css2sass_test.rb +294 -0
  138. data/test/sass/engine_test.rb +956 -0
  139. data/test/sass/functions_test.rb +126 -0
  140. data/test/sass/more_results/more1.css +9 -0
  141. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  142. data/test/sass/more_results/more_import.css +29 -0
  143. data/test/sass/more_templates/_more_partial.sass +2 -0
  144. data/test/sass/more_templates/more1.sass +23 -0
  145. data/test/sass/more_templates/more_import.sass +11 -0
  146. data/test/sass/plugin_test.rb +229 -0
  147. data/test/sass/results/alt.css +4 -0
  148. data/test/sass/results/basic.css +9 -0
  149. data/test/sass/results/compact.css +5 -0
  150. data/test/sass/results/complex.css +87 -0
  151. data/test/sass/results/compressed.css +1 -0
  152. data/test/sass/results/expanded.css +19 -0
  153. data/test/sass/results/import.css +29 -0
  154. data/test/sass/results/line_numbers.css +49 -0
  155. data/test/sass/results/mixins.css +95 -0
  156. data/test/sass/results/multiline.css +24 -0
  157. data/test/sass/results/nested.css +22 -0
  158. data/test/sass/results/parent_ref.css +13 -0
  159. data/test/sass/results/script.css +16 -0
  160. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  161. data/test/sass/results/subdir/subdir.css +3 -0
  162. data/test/sass/results/units.css +11 -0
  163. data/test/sass/script_test.rb +261 -0
  164. data/test/sass/templates/_partial.sass +2 -0
  165. data/test/sass/templates/alt.sass +16 -0
  166. data/test/sass/templates/basic.sass +23 -0
  167. data/test/sass/templates/bork1.sass +2 -0
  168. data/test/sass/templates/bork2.sass +2 -0
  169. data/test/sass/templates/bork3.sass +2 -0
  170. data/test/sass/templates/compact.sass +17 -0
  171. data/test/sass/templates/complex.sass +307 -0
  172. data/test/sass/templates/compressed.sass +15 -0
  173. data/test/sass/templates/expanded.sass +17 -0
  174. data/test/sass/templates/import.sass +11 -0
  175. data/test/sass/templates/importee.sass +19 -0
  176. data/test/sass/templates/line_numbers.sass +13 -0
  177. data/test/sass/templates/mixins.sass +76 -0
  178. data/test/sass/templates/multiline.sass +20 -0
  179. data/test/sass/templates/nested.sass +25 -0
  180. data/test/sass/templates/nested_bork1.sass +2 -0
  181. data/test/sass/templates/nested_bork2.sass +2 -0
  182. data/test/sass/templates/nested_bork3.sass +2 -0
  183. data/test/sass/templates/parent_ref.sass +25 -0
  184. data/test/sass/templates/script.sass +101 -0
  185. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  186. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  187. data/test/sass/templates/subdir/subdir.sass +6 -0
  188. data/test/sass/templates/units.sass +11 -0
  189. data/test/test_helper.rb +44 -0
  190. metadata +298 -0
@@ -0,0 +1,79 @@
1
+ module Sass
2
+ # The lexical environment for SassScript.
3
+ # This keeps track of variable and mixin definitions.
4
+ #
5
+ # A new environment is created for each level of Sass nesting.
6
+ # This allows variables to be lexically scoped.
7
+ # The new environment refers to the environment in the upper scope,
8
+ # so it has access to variables defined in enclosing scopes,
9
+ # but new variables are defined locally.
10
+ #
11
+ # Environment also keeps track of the {Engine} options
12
+ # so that they can be made available to {Sass::Script::Functions}.
13
+ class Environment
14
+ # The enclosing environment,
15
+ # or nil if this is the global environment.
16
+ #
17
+ # @return [Environment]
18
+ attr_reader :parent
19
+ attr_writer :options
20
+
21
+ # @param parent [Environment] See \{#parent}
22
+ def initialize(parent = nil)
23
+ @vars = {}
24
+ @mixins = {}
25
+ @parent = parent
26
+
27
+ set_var("important", Script::String.new("!important")) unless @parent
28
+ end
29
+
30
+ # The options hash.
31
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
32
+ #
33
+ # @return [Hash<Symbol, Object>]
34
+ def options
35
+ @options || (parent && parent.options) || {}
36
+ end
37
+
38
+ class << self
39
+ private
40
+
41
+ # Note: when updating this,
42
+ # update haml/yard/inherited_hash.rb as well.
43
+ def inherited_hash(name)
44
+ class_eval <<RUBY, __FILE__, __LINE__ + 1
45
+ def #{name}(name)
46
+ @#{name}s[name] || @parent && @parent.#{name}(name)
47
+ end
48
+
49
+ def set_#{name}(name, value)
50
+ @#{name}s[name] = value unless try_set_#{name}(name, value)
51
+ end
52
+
53
+ def try_set_#{name}(name, value)
54
+ if @#{name}s.include?(name)
55
+ @#{name}s[name] = value
56
+ true
57
+ elsif @parent
58
+ @parent.try_set_#{name}(name, value)
59
+ else
60
+ false
61
+ end
62
+ end
63
+ protected :try_set_#{name}
64
+
65
+ def set_local_#{name}(name, value)
66
+ @#{name}s[name] = value
67
+ end
68
+ RUBY
69
+ end
70
+ end
71
+
72
+ # variable
73
+ # Script::Literal
74
+ inherited_hash :var
75
+ # mixin
76
+ # Engine::Mixin
77
+ inherited_hash :mixin
78
+ end
79
+ end
@@ -0,0 +1,162 @@
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
+ # `:line`
35
+ # : The line of the file on which the error occurred. Never nil.
36
+ #
37
+ # This information is also included in standard backtrace format
38
+ # in the output of \{#backtrace}.
39
+ #
40
+ # @return [Aray<Hash<Symbol, Object>>]
41
+ attr_accessor :sass_backtrace
42
+
43
+ # The text of the template where this error was raised.
44
+ #
45
+ # @return [String]
46
+ attr_accessor :sass_template
47
+
48
+ # @param msg [String] The error message
49
+ # @param attrs [Hash<Symbol, Object>] The information in the backtrace entry.
50
+ # See \{#sass\_backtrace}
51
+ def initialize(msg, attrs = {})
52
+ @message = msg
53
+ @sass_backtrace = []
54
+ add_backtrace(attrs)
55
+ end
56
+
57
+ # The name of the file in which the exception was raised.
58
+ # This could be `nil` if no filename is available.
59
+ #
60
+ # @return [String]
61
+ def sass_filename
62
+ sass_backtrace.first[:filename]
63
+ end
64
+
65
+ # The line of the Sass template on which the error occurred.
66
+ #
67
+ # @return [Fixnum]
68
+ def sass_line
69
+ sass_backtrace.first[:line]
70
+ end
71
+
72
+ # Adds an entry to the exception's Sass backtrace.
73
+ #
74
+ # @param attrs [Hash<Symbol, Object>] The information in the backtrace entry.
75
+ # See \{#sass\_backtrace}
76
+ def add_backtrace(attrs)
77
+ sass_backtrace << attrs.reject {|k, v| v.nil?}
78
+ end
79
+
80
+ # Modify the top Sass backtrace entry (that is, the last one)
81
+ # to have the given attributes.
82
+ # If that entry already has one of the given attributes set,
83
+ # that takes precendence.
84
+ #
85
+ # @param attrs [Hash<Symbol, Object>] The information to add to the backtrace entry.
86
+ # See \{#sass\_backtrace}
87
+ def modify_backtrace(attrs)
88
+ sass_backtrace[-1] = attrs.reject {|k, v| v.nil?}.merge(sass_backtrace.last)
89
+ end
90
+
91
+ # @return [String] The error message
92
+ def to_s
93
+ @message
94
+ end
95
+
96
+ # Returns the standard exception backtrace,
97
+ # including the Sass backtrace.
98
+ #
99
+ # @return [Array<String>]
100
+ def backtrace
101
+ return nil if super.nil?
102
+ sass_backtrace.map {|h| "#{h[:filename] || "(sass)"}:#{h[:line]}"} + super
103
+ end
104
+
105
+ # Returns a string representation of the Sass backtrace.
106
+ #
107
+ # @param default_filename [String] The filename to use for unknown files
108
+ # @see #sass_backtrace
109
+ # @return [String]
110
+ def sass_backtrace_str(default_filename = "an unknown file")
111
+ "Syntax error: #{message}" +
112
+ Haml::Util.enum_with_index(sass_backtrace).map do |entry, i|
113
+ "\n #{i == 0 ? "on" : "from"} line #{entry[:line]}" +
114
+ " of #{entry[:filename] || default_filename}"
115
+ end.join
116
+ end
117
+
118
+ class << self
119
+ # Returns an error report for an exception in CSS format.
120
+ #
121
+ # @param e [Exception]
122
+ # @param full_exception [Boolean] The value of
123
+ # \{file:SASS\_REFERENCE.md#full_exception-option `options[:full_exception]`}
124
+ def exception_to_css(e, options)
125
+ return "/* Internal stylesheet error */" unless options[:full_exception]
126
+
127
+ header = header_string(e, options)
128
+
129
+ <<END
130
+ /*
131
+ #{header}
132
+
133
+ Backtrace:\n#{e.backtrace.join("\n")}
134
+ */
135
+ body:before {
136
+ white-space: pre;
137
+ font-family: monospace;
138
+ content: "#{header.gsub('"', '\"').gsub("\n", '\\A ')}"; }
139
+ END
140
+ end
141
+
142
+ private
143
+
144
+ def header_string(e, options)
145
+ return "#{e.class}: #{e.message}" unless e.is_a? Sass::SyntaxError
146
+
147
+ line_offset = options[:line] || 1
148
+ line_num = e.sass_line + 1 - line_offset
149
+ min = [line_num - 6, 0].max
150
+ section = e.sass_template.rstrip.split("\n")[min ... line_num + 5]
151
+ return e.sass_backtrace_str if section.nil? || section.empty?
152
+
153
+ e.sass_backtrace_str + "\n\n" + Haml::Util.enum_with_index(section).
154
+ map {|line, i| "#{line_offset + min + i}: #{line}"}.join("\n")
155
+ end
156
+ end
157
+ end
158
+
159
+ # The class for Sass errors that are raised due to invalid unit conversions
160
+ # in SassScript.
161
+ class UnitConversionError < SyntaxError; end
162
+ end
@@ -0,0 +1,133 @@
1
+ require 'digest/sha1'
2
+ require 'pathname'
3
+
4
+ module Sass
5
+ # This module contains various bits of functionality
6
+ # related to finding and caching Sass files.
7
+ module Files
8
+ extend self
9
+
10
+ # Returns the {Sass::Tree} for the given file,
11
+ # reading it from the Sass cache if possible.
12
+ #
13
+ # @param filename [String] The path to the Sass file
14
+ # @param options [Hash<Symbol, Object>] The options hash.
15
+ # Only the {file:SASS_REFERENCE.md#cache-option `:cache_location`} option is used
16
+ # @raise [Sass::SyntaxError] if there's an error in the document.
17
+ # The caller has responsibility for setting backtrace information, if necessary
18
+ def tree_for(filename, options)
19
+ options = Sass::Engine::DEFAULT_OPTIONS.merge(options)
20
+ text = File.read(filename)
21
+
22
+ if options[:cache]
23
+ compiled_filename = sassc_filename(filename, options)
24
+ sha = Digest::SHA1.hexdigest(text)
25
+
26
+ if root = try_to_read_sassc(filename, compiled_filename, sha)
27
+ root.options = options.merge(:filename => filename)
28
+ return root
29
+ end
30
+ end
31
+
32
+ engine = Sass::Engine.new(text, options.merge(:filename => filename))
33
+
34
+ root = engine.to_tree
35
+ try_to_write_sassc(root, compiled_filename, sha, options) if options[:cache]
36
+ root
37
+ end
38
+
39
+ # Find the full filename of a Sass or CSS file to import.
40
+ # This follows Sass's import rules:
41
+ # if the filename given ends in `".sass"` or `".css"`,
42
+ # it will try to find that type of file;
43
+ # otherwise, it will try to find the corresponding Sass file
44
+ # and fall back on CSS if it's not available.
45
+ #
46
+ # Any Sass filename returned will correspond to
47
+ # an actual Sass file on the filesystem.
48
+ # CSS filenames, however, may not;
49
+ # they're expected to be put through directly to the stylesheet
50
+ # as CSS `@import` statements.
51
+ #
52
+ # @param filename [String] The filename to search for
53
+ # @param load_paths [Array<String>] The set of filesystem paths
54
+ # to search for Sass files.
55
+ # @return [String] The filename of the imported file.
56
+ # This is an absolute path if the file is a `".sass"` file.
57
+ # @raise [Sass::SyntaxError] if `filename` ends in ``".sass"``
58
+ # and no corresponding Sass file could be found.
59
+ def find_file_to_import(filename, load_paths)
60
+ was_sass = false
61
+ original_filename = filename
62
+
63
+ if filename[-5..-1] == ".sass"
64
+ filename = filename[0...-5]
65
+ was_sass = true
66
+ elsif filename[-4..-1] == ".css"
67
+ return filename
68
+ end
69
+
70
+ new_filename = find_full_path("#{filename}.sass", load_paths)
71
+
72
+ return new_filename if new_filename
73
+ return filename + '.css' unless was_sass
74
+ raise SyntaxError.new("File to import not found or unreadable: #{original_filename}.")
75
+ end
76
+
77
+ private
78
+
79
+ def sassc_filename(filename, options)
80
+ File.join(options[:cache_location],
81
+ Digest::SHA1.hexdigest(File.dirname(File.expand_path(filename))),
82
+ File.basename(filename) + 'c')
83
+ end
84
+
85
+ def try_to_read_sassc(filename, compiled_filename, sha)
86
+ return unless File.readable?(compiled_filename)
87
+
88
+ File.open(compiled_filename, "rb") do |f|
89
+ return unless f.readline("\n").strip == Sass::VERSION
90
+ return unless f.readline("\n").strip == sha
91
+ return Marshal.load(f.read)
92
+ end
93
+ rescue TypeError, ArgumentError => e
94
+ warn "Warning. Error encountered while reading cache #{compiled_filename}: #{e}"
95
+ end
96
+
97
+ def try_to_write_sassc(root, compiled_filename, sha, options)
98
+ return unless File.writable?(File.dirname(options[:cache_location]))
99
+ return if File.exists?(options[:cache_location]) && !File.writable?(options[:cache_location])
100
+ return if File.exists?(File.dirname(compiled_filename)) && !File.writable?(File.dirname(compiled_filename))
101
+ return if File.exists?(compiled_filename) && !File.writable?(compiled_filename)
102
+ FileUtils.mkdir_p(File.dirname(compiled_filename))
103
+ File.open(compiled_filename, "wb") do |f|
104
+ f.write(Sass::VERSION)
105
+ f.write("\n")
106
+ f.write(sha)
107
+ f.write("\n")
108
+ f.write(Marshal.dump(root))
109
+ end
110
+ end
111
+
112
+ def find_full_path(filename, load_paths)
113
+ partial_name = File.join(File.dirname(filename), "_#{File.basename(filename)}")
114
+
115
+ if Pathname.new(filename).absolute?
116
+ [partial_name, filename].each do |name|
117
+ return name if File.readable?(name)
118
+ end
119
+ return nil
120
+ end
121
+
122
+ load_paths.each do |path|
123
+ [partial_name, filename].each do |name|
124
+ full_path = File.join(path, name)
125
+ if File.readable?(full_path)
126
+ return full_path
127
+ end
128
+ end
129
+ end
130
+ nil
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,170 @@
1
+ require 'sass/engine'
2
+
3
+ module Sass
4
+ # This module handles the compilation of Sass files.
5
+ # It provides global options and checks whether CSS files
6
+ # need to be updated.
7
+ #
8
+ # This module is used as the primary interface with Sass
9
+ # when it's used as a plugin for various frameworks.
10
+ # Currently Rails and Merb are supported out of the box.
11
+ module Plugin
12
+ include Haml::Util
13
+ extend self
14
+
15
+ @options = {
16
+ :css_location => './public/stylesheets',
17
+ :always_update => false,
18
+ :always_check => true,
19
+ :full_exception => true
20
+ }
21
+ @checked_for_updates = false
22
+
23
+ # Whether or not Sass has **ever** checked if the stylesheets need to be updated
24
+ # (in this Ruby instance).
25
+ #
26
+ # @return [Boolean]
27
+ attr_reader :checked_for_updates
28
+
29
+ # An options hash.
30
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
31
+ #
32
+ # @return [Hash<Symbol, Object>]
33
+ attr_reader :options
34
+
35
+ # Sets the options hash.
36
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
37
+ #
38
+ # @param value [Hash<Symbol, Object>] The options hash
39
+ def options=(value)
40
+ @options.merge!(value)
41
+ end
42
+
43
+ # Non-destructively modifies \{#options} so that default values are properly set.
44
+ #
45
+ # @param additional_options [Hash<Symbol, Object>] An options hash with which to merge \{#options}
46
+ # @return [Hash<Symbol, Object>] The modified options hash
47
+ def engine_options(additional_options = {})
48
+ opts = options.dup.merge(additional_options)
49
+ opts[:load_paths] = load_paths(opts)
50
+ opts
51
+ end
52
+
53
+ # Updates out-of-date stylesheets.
54
+ #
55
+ # Checks each Sass file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
56
+ # to see if it's been modified more recently than the corresponding CSS file
57
+ # in {file:SASS_REFERENCE.md#css_location-option} `:css_location`}.
58
+ # If it has, it updates the CSS file.
59
+ def update_stylesheets
60
+ return if options[:never_update]
61
+
62
+ @checked_for_updates = true
63
+ template_locations.zip(css_locations).each do |template_location, css_location|
64
+
65
+ Dir.glob(File.join(template_location, "**", "*.sass")).each do |file|
66
+ # Get the relative path to the file with no extension
67
+ name = file.sub(template_location + "/", "")[0...-5]
68
+
69
+ if !forbid_update?(name) && (options[:always_update] || stylesheet_needs_update?(name, template_location, css_location))
70
+ update_stylesheet(name, template_location, css_location)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def update_stylesheet(name, template_location, css_location)
79
+ css = css_filename(name, css_location)
80
+ File.delete(css) if File.exists?(css)
81
+
82
+ filename = template_filename(name, template_location)
83
+ result = begin
84
+ Sass::Files.tree_for(filename, engine_options(:css_filename => css, :filename => filename)).render
85
+ rescue Exception => e
86
+ Sass::SyntaxError.exception_to_css(e, options)
87
+ end
88
+
89
+ # Create any directories that might be necessary
90
+ mkpath(css_location, name)
91
+
92
+ # Finally, write the file
93
+ File.open(css, 'w') do |file|
94
+ file.print(result)
95
+ end
96
+ end
97
+
98
+ # Create any successive directories required to be able to write a file to: File.join(base,name)
99
+ def mkpath(base, name)
100
+ dirs = [base]
101
+ name.split(File::SEPARATOR)[0...-1].each { |dir| dirs << File.join(dirs[-1],dir) }
102
+ dirs.each { |dir| Dir.mkdir(dir) unless File.exist?(dir) }
103
+ end
104
+
105
+ def load_paths(opts = options)
106
+ (opts[:load_paths] || []) + template_locations
107
+ end
108
+
109
+ def template_locations
110
+ location = (options[:template_location] || File.join(options[:css_location],'sass'))
111
+ if location.is_a?(String)
112
+ [location]
113
+ else
114
+ location.to_a.map { |l| l.first }
115
+ end
116
+ end
117
+
118
+ def css_locations
119
+ if options[:template_location] && !options[:template_location].is_a?(String)
120
+ options[:template_location].to_a.map { |l| l.last }
121
+ else
122
+ [options[:css_location]]
123
+ end
124
+ end
125
+
126
+ def template_filename(name, path)
127
+ "#{path}/#{name}.sass"
128
+ end
129
+
130
+ def css_filename(name, path)
131
+ "#{path}/#{name}.css"
132
+ end
133
+
134
+ def forbid_update?(name)
135
+ name.sub(/^.*\//, '')[0] == ?_
136
+ end
137
+
138
+ def stylesheet_needs_update?(name, template_path, css_path)
139
+ css_file = css_filename(name, css_path)
140
+ template_file = template_filename(name, template_path)
141
+ exact_stylesheet_needs_update?(css_file, template_file)
142
+ end
143
+
144
+ def exact_stylesheet_needs_update?(css_file, template_file)
145
+ return true unless File.exists?(css_file)
146
+
147
+ css_mtime = File.mtime(css_file)
148
+ File.mtime(template_file) > css_mtime ||
149
+ dependencies(template_file).any?(&dependency_updated?(css_mtime))
150
+ end
151
+
152
+ def dependency_updated?(css_mtime)
153
+ lambda do |dep|
154
+ File.mtime(dep) > css_mtime ||
155
+ dependencies(dep).any?(&dependency_updated?(css_mtime))
156
+ end
157
+ end
158
+
159
+ def dependencies(filename)
160
+ File.readlines(filename).grep(/^@import /).map do |line|
161
+ line[8..-1].split(',').map do |inc|
162
+ Sass::Files.find_file_to_import(inc.strip, [File.dirname(filename)] + load_paths)
163
+ end
164
+ end.flatten.grep(/\.sass$/)
165
+ end
166
+ end
167
+ end
168
+
169
+ require 'sass/plugin/rails' if defined?(ActionController)
170
+ require 'sass/plugin/merb' if defined?(Merb::Plugins)