Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 373
- Log:
Initial import of Radiant 0.9.1, which is now packaged as a gem. This is an
import of the tagged 0.9.1 source checked out from GitHub, which isn't quite
the same as the gem distribution - but it doesn't seem to be available in an
archived form and the installed gem already has modifications, so this is
the closest I can get.
- Author:
- rool
- Date:
- Mon Mar 21 13:40:05 +0000 2011
- Size:
- 7444 Bytes
1 | require 'sass' |
2 | require 'rbconfig' |
3 | |
4 | module Sass |
5 | # This module handles the compilation of Sass files. |
6 | # It provides global options and checks whether CSS files |
7 | # need to be updated. |
8 | # |
9 | # This module is used as the primary interface with Sass |
10 | # when it's used as a plugin for various frameworks. |
11 | # All Rack-enabled frameworks are supported out of the box. |
12 | # The plugin is {file:SASS_REFERENCE.md#rails_merb_plugin automatically activated for Rails and Merb}. |
13 | # Other frameworks must enable it explicitly; see {Sass::Plugin::Rack}. |
14 | module Plugin |
15 | extend self |
16 | |
17 | @options = { |
18 | :css_location => './public/stylesheets', |
19 | :always_update => false, |
20 | :always_check => true, |
21 | :full_exception => true |
22 | } |
23 | @checked_for_updates = false |
24 | |
25 | # Whether or not Sass has **ever** checked if the stylesheets need to be updated |
26 | # (in this Ruby instance). |
27 | # |
28 | # @return [Boolean] |
29 | attr_reader :checked_for_updates |
30 | |
31 | # An options hash. |
32 | # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}. |
33 | # |
34 | # @return [{Symbol => Object}] |
35 | attr_reader :options |
36 | |
37 | # Sets the options hash. |
38 | # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}. |
39 | # |
40 | # @param value [{Symbol => Object}] The options hash |
41 | def options=(value) |
42 | @options.merge!(value) |
43 | end |
44 | |
45 | # Non-destructively modifies \{#options} so that default values are properly set. |
46 | # |
47 | # @param additional_options [{Symbol => Object}] An options hash with which to merge \{#options} |
48 | # @return [{Symbol => Object}] The modified options hash |
49 | def engine_options(additional_options = {}) |
50 | opts = options.dup.merge(additional_options) |
51 | opts[:load_paths] = load_paths(opts) |
52 | opts |
53 | end |
54 | |
55 | # Same as \{#update\_stylesheets}, but respects \{#checked\_for\_updates} |
56 | # and the {file:SASS_REFERENCE.md#always_update-option `:always_update`} |
57 | # and {file:SASS_REFERENCE.md#always_check-option `:always_check`} options. |
58 | # |
59 | # @see #update_stylesheets |
60 | def check_for_updates |
61 | return unless !Sass::Plugin.checked_for_updates || |
62 | Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check] |
63 | update_stylesheets |
64 | end |
65 | |
66 | # Updates out-of-date stylesheets. |
67 | # |
68 | # Checks each Sass file in {file:SASS_REFERENCE.md#template_location-option `:template_location`} |
69 | # to see if it's been modified more recently than the corresponding CSS file |
70 | # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}. |
71 | # If it has, it updates the CSS file. |
72 | def update_stylesheets |
73 | return if options[:never_update] |
74 | |
75 | @checked_for_updates = true |
76 | template_locations.zip(css_locations).each do |template_location, css_location| |
77 | |
78 | Dir.glob(File.join(template_location, "**", "*.sass")).each do |file| |
79 | # Get the relative path to the file with no extension |
80 | name = file.sub(template_location.sub(/\/*$/, '/'), "")[0...-5] |
81 | |
82 | if !forbid_update?(name) && (options[:always_update] || stylesheet_needs_update?(name, template_location, css_location)) |
83 | update_stylesheet(name, template_location, css_location) |
84 | end |
85 | end |
86 | end |
87 | end |
88 | |
89 | private |
90 | |
91 | def update_stylesheet(name, template_location, css_location) |
92 | css = css_filename(name, css_location) |
93 | File.delete(css) if File.exists?(css) |
94 | |
95 | filename = template_filename(name, template_location) |
96 | result = begin |
97 | Sass::Files.tree_for(filename, engine_options(:css_filename => css, :filename => filename)).render |
98 | rescue Exception => e |
99 | raise e unless options[:full_exception] |
100 | exception_string(e) |
101 | end |
102 | |
103 | # Create any directories that might be necessary |
104 | mkpath(css_location, name) |
105 | |
106 | # Finally, write the file |
107 | flag = 'w' |
108 | flag = 'wb' if RbConfig::CONFIG['host_os'] =~ /mswin|windows/i && options[:unix_newlines] |
109 | File.open(css, flag) {|file| file.print(result)} |
110 | end |
111 | |
112 | # Create any successive directories required to be able to write a file to: File.join(base,name) |
113 | def mkpath(base, name) |
114 | dirs = [base] |
115 | name.split(File::SEPARATOR)[0...-1].each { |dir| dirs << File.join(dirs[-1],dir) } |
116 | dirs.each { |dir| Dir.mkdir(dir) unless File.exist?(dir) } |
117 | end |
118 | |
119 | def load_paths(opts = options) |
120 | (opts[:load_paths] || []) + template_locations |
121 | end |
122 | |
123 | def template_locations |
124 | location = (options[:template_location] || File.join(options[:css_location],'sass')) |
125 | if location.is_a?(String) |
126 | [location] |
127 | else |
128 | location.to_a.map { |l| l.first } |
129 | end |
130 | end |
131 | |
132 | def css_locations |
133 | if options[:template_location] && !options[:template_location].is_a?(String) |
134 | options[:template_location].to_a.map { |l| l.last } |
135 | else |
136 | [options[:css_location]] |
137 | end |
138 | end |
139 | |
140 | def exception_string(e) |
141 | e_string = "#{e.class}: #{e.message}" |
142 | |
143 | if e.is_a? Sass::SyntaxError |
144 | e_string << "\non line #{e.sass_line}" |
145 | |
146 | if e.sass_filename |
147 | e_string << " of #{e.sass_filename}" |
148 | |
149 | if File.exists?(e.sass_filename) |
150 | e_string << "\n\n" |
151 | |
152 | min = [e.sass_line - 5, 0].max |
153 | begin |
154 | File.read(e.sass_filename).rstrip.split("\n")[ |
155 | min .. e.sass_line + 5 |
156 | ].each_with_index do |line, i| |
157 | e_string << "#{min + i + 1}: #{line}\n" |
158 | end |
159 | rescue |
160 | e_string << "Couldn't read sass file: #{e.sass_filename}" |
161 | end |
162 | end |
163 | end |
164 | end |
165 | <<END |
166 | /* |
167 | #{e_string} |
168 | |
169 | Backtrace:\n#{e.backtrace.join("\n")} |
170 | */ |
171 | body:before { |
172 | white-space: pre; |
173 | font-family: monospace; |
174 | content: "#{e_string.gsub('"', '\"').gsub("\n", '\\A ')}"; } |
175 | END |
176 | # Fix an emacs syntax-highlighting hiccup: ' |
177 | end |
178 | |
179 | def template_filename(name, path) |
180 | "#{path}/#{name}.sass" |
181 | end |
182 | |
183 | def css_filename(name, path) |
184 | "#{path}/#{name}.css" |
185 | end |
186 | |
187 | def forbid_update?(name) |
188 | name.sub(/^.*\//, '')[0] == ?_ |
189 | end |
190 | |
191 | def stylesheet_needs_update?(name, template_path, css_path) |
192 | css_file = css_filename(name, css_path) |
193 | template_file = template_filename(name, template_path) |
194 | exact_stylesheet_needs_update?(css_file, template_file) |
195 | end |
196 | |
197 | def exact_stylesheet_needs_update?(css_file, template_file) |
198 | return true unless File.exists?(css_file) |
199 | |
200 | css_mtime = File.mtime(css_file) |
201 | File.mtime(template_file) > css_mtime || |
202 | dependencies(template_file).any?(&dependency_updated?(css_mtime)) |
203 | end |
204 | |
205 | def dependency_updated?(css_mtime) |
206 | lambda do |dep| |
207 | begin |
208 | File.mtime(dep) > css_mtime || |
209 | dependencies(dep).any?(&dependency_updated?(css_mtime)) |
210 | rescue Sass::SyntaxError |
211 | # If there's an error finding depenencies, default to recompiling. |
212 | true |
213 | end |
214 | end |
215 | end |
216 | |
217 | def dependencies(filename) |
218 | File.readlines(filename).grep(/^@import /).map do |line| |
219 | line[8..-1].split(',').map do |inc| |
220 | Sass::Files.find_file_to_import(inc.strip, [File.dirname(filename)] + load_paths) |
221 | end |
222 | end.flatten.grep(/\.sass$/) |
223 | end |
224 | end |
225 | end |
226 | |
227 | require 'sass/plugin/rails' if defined?(ActionController) |
228 | require 'sass/plugin/merb' if defined?(Merb::Plugins) |