dcss 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +3 -0
- data/MIT-LICENSE +20 -0
- data/README +202 -0
- data/Rakefile +245 -0
- data/TODO +4 -0
- data/bin/dcss +4 -0
- data/bin/test +11 -0
- data/lib/dcss.rb +155 -0
- data/lib/lexer.rb +228 -0
- data/lib/rcss.original.rb +120 -0
- data/test/data/complex_descendants.in.rcss +32 -0
- data/test/data/complex_descendants.out.css +17 -0
- data/test/data/with_comments.in.rcss +9 -0
- data/test/data/with_comments.out.css +4 -0
- data/test/units.dcss +185 -0
- metadata +68 -0
data/CHANGES
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2005 Lukasz 'Bragi Ragnarson' Piestrzeniewicz
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
= DCSS - CSS extensions that make writing stylesheets less verbose
|
2
|
+
|
3
|
+
Information for version 0.3.0
|
4
|
+
|
5
|
+
Main features:
|
6
|
+
|
7
|
+
* Create rich CSS files using:
|
8
|
+
|
9
|
+
* ERB templates
|
10
|
+
|
11
|
+
* Server-side constants
|
12
|
+
|
13
|
+
* Server-side classes (preliminary)
|
14
|
+
|
15
|
+
* Process DCSS files off-line using command line executable
|
16
|
+
|
17
|
+
* Also available as a rails plugin
|
18
|
+
|
19
|
+
=== ERB templates
|
20
|
+
|
21
|
+
Example
|
22
|
+
|
23
|
+
<% template_color = "#000"
|
24
|
+
template_background = "#fff"
|
25
|
+
template_highlight = "#fa3"
|
26
|
+
%>
|
27
|
+
body {
|
28
|
+
color: <%= template_color %>;
|
29
|
+
background-color: <%= template_background %>;
|
30
|
+
}
|
31
|
+
|
32
|
+
h1 {
|
33
|
+
color: <%= template_background %>;
|
34
|
+
background-color: <%= template_highlight %>;
|
35
|
+
}
|
36
|
+
|
37
|
+
=== Server-side Constants
|
38
|
+
|
39
|
+
Server-side Constants were originally developed by Shaun Inman in PHP. SSC
|
40
|
+
extends CSS syntax in a simple way. SCC is cross-platform and easy to use.
|
41
|
+
|
42
|
+
DCSS uses the constants implementation from RCSS[http://rcss.rubyforge.org/] which also allows constants to include spaces and commas.
|
43
|
+
|
44
|
+
Example
|
45
|
+
|
46
|
+
@server constants {
|
47
|
+
template_color: #000;
|
48
|
+
template_background: #fff;
|
49
|
+
template_highlight: #fa3;
|
50
|
+
|
51
|
+
// Rcss specific (note spaces and commas)
|
52
|
+
template_font: "Trebuchet MS", "Bitstream Vera Sans", helvetica, sans-serif;
|
53
|
+
}
|
54
|
+
|
55
|
+
body {
|
56
|
+
color: template_color;
|
57
|
+
background-color: template_background;
|
58
|
+
font-family: template_font;
|
59
|
+
}
|
60
|
+
|
61
|
+
h1 {
|
62
|
+
color: template_background;
|
63
|
+
background-color: template_highlight;
|
64
|
+
font-family: template_font;
|
65
|
+
}
|
66
|
+
|
67
|
+
There might be several constants/variables blocks in the same file. It does not
|
68
|
+
matter where in CSS file they appear, their contents are still applied to whole
|
69
|
+
file.
|
70
|
+
|
71
|
+
When a constant is redefined only later occurrence is taken into account so:
|
72
|
+
|
73
|
+
template_color: #fff;
|
74
|
+
...
|
75
|
+
template_color: #000;
|
76
|
+
|
77
|
+
...will result in <code>template_color</code> having value <code>#000</code> in all places.
|
78
|
+
|
79
|
+
To use constant's value simply type it's name anywhere in the code:
|
80
|
+
|
81
|
+
body {
|
82
|
+
color: constant_name;
|
83
|
+
}
|
84
|
+
|
85
|
+
*Note* that since anything can be constant's value it is also possible to use it
|
86
|
+
to hold attribute or even selector:
|
87
|
+
|
88
|
+
@server constants {
|
89
|
+
body: #fff;
|
90
|
+
}
|
91
|
+
|
92
|
+
body {
|
93
|
+
color: body;
|
94
|
+
}
|
95
|
+
|
96
|
+
This will be evaluated to
|
97
|
+
|
98
|
+
#fff {
|
99
|
+
color: #fff;
|
100
|
+
}
|
101
|
+
|
102
|
+
..which probably is not what one might expect. For that reason avoid using CSS
|
103
|
+
selector and attribute names as constant names.
|
104
|
+
|
105
|
+
=== Nested Descendant Selectors
|
106
|
+
|
107
|
+
DCSS introduces the following <tt>{{ }}</tt> syntax:
|
108
|
+
|
109
|
+
#someid { color: blue } {{
|
110
|
+
a { color: green; }
|
111
|
+
}}
|
112
|
+
|
113
|
+
Which renders to:
|
114
|
+
|
115
|
+
#someid { color: blue }
|
116
|
+
#someid a { color: green; }
|
117
|
+
|
118
|
+
Leaving out the parent properties is ok:
|
119
|
+
|
120
|
+
#someid {{
|
121
|
+
a { color: green; }
|
122
|
+
}}
|
123
|
+
|
124
|
+
becomes:
|
125
|
+
|
126
|
+
#someid a { color: green; }
|
127
|
+
|
128
|
+
Nested and comma separated selectors also work:
|
129
|
+
|
130
|
+
body.home, #specialpage { margin: 10px auto; width: 700px; } {{
|
131
|
+
h1 { font-family: Arial, sans-serif; }
|
132
|
+
a, a:visited { color: blue; }
|
133
|
+
p > span { font-size: 1.2em; }
|
134
|
+
#someid { background: red; } {{
|
135
|
+
h1 { font-family: Georgia, serif; }
|
136
|
+
}}
|
137
|
+
}}
|
138
|
+
|
139
|
+
renders to:
|
140
|
+
|
141
|
+
body.home, #specialpage { margin: 10px auto; width: 700px; }
|
142
|
+
body.home h1, #specialpage h1 { font-family: Arial, sans-serif; }
|
143
|
+
body.home a, body.home a:visited, #specialpage a, #specialpage a:visited { color: blue; }
|
144
|
+
body.home p > span, #specialpage p > span { font-size: 1.2em; }
|
145
|
+
body.home #someid, #specialpage #someid { background: red; }
|
146
|
+
body.home #someid h1, #specialpage #someid h1 { font-family: Georgia, serif; }
|
147
|
+
|
148
|
+
== Installation
|
149
|
+
|
150
|
+
=== Using RubyGems
|
151
|
+
|
152
|
+
Download and install DCSS with the following command (won't work until I rubyforge account is created, see below):
|
153
|
+
|
154
|
+
gem install --remote dcss
|
155
|
+
|
156
|
+
=== Manual
|
157
|
+
|
158
|
+
Download the gem file from {my website}[http://myles.id.au] while I wait for my rubyforge account to get activated. After that it will be available at the {project page}[http://rubyforge.org/projects/dcss/]. Install it with following command
|
159
|
+
|
160
|
+
gem install dcss-0.3.0.gem
|
161
|
+
|
162
|
+
== Usage
|
163
|
+
|
164
|
+
=== Command line
|
165
|
+
|
166
|
+
NAME
|
167
|
+
dcss - process rich CSS files
|
168
|
+
|
169
|
+
SYNOPSIS
|
170
|
+
dcss [DCSSFile]
|
171
|
+
|
172
|
+
DESCRIPTION
|
173
|
+
Reads and processes DCSS files. Processed file is printed to standard
|
174
|
+
output. If no file is given standard input is processed instead.
|
175
|
+
|
176
|
+
EXAMPLE
|
177
|
+
dcss default.dcss > default.css
|
178
|
+
|
179
|
+
=== Rails
|
180
|
+
|
181
|
+
Run
|
182
|
+
|
183
|
+
./script/install http://svn.ducknewmedia.com.au/public/rails_plugins/dcss
|
184
|
+
|
185
|
+
|
186
|
+
== Credits
|
187
|
+
|
188
|
+
[<b>Shaun Inman</b>] For the original PHP version of CSS-SSC[http://www.shauninman.com/post/heap/2005/08/09/css_constants]
|
189
|
+
[<b>Lukasz 'Bragi Ragnason' Piestrzeniewicz</b>] For RCSS[http://rcss.rubyforge.org/]
|
190
|
+
|
191
|
+
== License
|
192
|
+
|
193
|
+
DCSS is available under an MIT-style license.
|
194
|
+
|
195
|
+
:include: MIT-LICENSE
|
196
|
+
|
197
|
+
== Warranty
|
198
|
+
|
199
|
+
This software is provided "as is" and without any express or
|
200
|
+
implied warranties, including, without limitation, the implied
|
201
|
+
warranties of merchantibility and fitness for a particular
|
202
|
+
purpose.
|
data/Rakefile
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
Gem::manage_gems
|
4
|
+
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rake/packagetask'
|
7
|
+
require 'rake/gempackagetask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
|
10
|
+
require File.dirname(__FILE__) + '/lib/dcss'
|
11
|
+
|
12
|
+
PROJECT_SUMMARY = "DCSS - CSS extensions that make writing stylesheets less verbose"
|
13
|
+
|
14
|
+
# need to set up rubyforge project
|
15
|
+
PKG_NAME = "dcss"
|
16
|
+
PKG_VERSION = DuckCSS::VERSION
|
17
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
18
|
+
RUBY_FORGE_PROJECT = "dcss"
|
19
|
+
RUBY_FORGE_USER = "mylesb"
|
20
|
+
PKG_FILES = FileList[
|
21
|
+
'README',
|
22
|
+
'CHANGES',
|
23
|
+
'TODO',
|
24
|
+
'MIT-LICENSE',
|
25
|
+
'Rakefile',
|
26
|
+
'lib/**/*.rb',
|
27
|
+
'bin/**/*',
|
28
|
+
'test/**/*'
|
29
|
+
]
|
30
|
+
|
31
|
+
desc "Builds everything"
|
32
|
+
task :default => [:test, :gem, :rdoc]
|
33
|
+
|
34
|
+
task :gem => :test
|
35
|
+
|
36
|
+
rd = Rake::RDocTask.new("rdoc") do |rdoc|
|
37
|
+
rdoc.rdoc_dir = 'html'
|
38
|
+
rdoc.title = PROJECT_SUMMARY
|
39
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
40
|
+
rdoc.rdoc_files.include('README', 'MIT-LICENSE', 'TODO', 'CHANGES')
|
41
|
+
rdoc.rdoc_files.include('lib/dcss.rb')
|
42
|
+
end
|
43
|
+
|
44
|
+
spec = Gem::Specification.new do |s|
|
45
|
+
s.name = PKG_NAME
|
46
|
+
s.version = PKG_VERSION
|
47
|
+
s.platform = Gem::Platform::RUBY
|
48
|
+
s.summary = PROJECT_SUMMARY
|
49
|
+
s.files = PKG_FILES.to_a
|
50
|
+
s.executables << 'dcss'
|
51
|
+
s.require_path = 'lib'
|
52
|
+
s.autorequire = 'dcss'
|
53
|
+
s.author = "Myles Byrne, Bragi Ragnarson"
|
54
|
+
s.email = "myles@ducknewmedia.com.au, bragi.ragnarson@gmail.com"
|
55
|
+
s.rubyforge_project = RUBY_FORGE_PROJECT
|
56
|
+
s.homepage = "http://myles.id.au/more/#DCSS" # needs its own page
|
57
|
+
s.has_rdoc = true
|
58
|
+
s.rdoc_options <<
|
59
|
+
'--title' << PROJECT_SUMMARY <<
|
60
|
+
'--main' << 'README' <<
|
61
|
+
'--line-numbers'
|
62
|
+
s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
def run_test(testfile)
|
67
|
+
testname = testfile.gsub(/test\/data\/(.*)\.in\.rcss/, '\1')
|
68
|
+
rcss = DuckCSS::CSSParser.new(File.read(testfile)).to_css
|
69
|
+
css = File.read(testfile.gsub(/\.in\.rcss/ , ".out.css"))
|
70
|
+
if rcss.strip == css.strip # damn newlines
|
71
|
+
puts "Test \"#{testname}\" passed"
|
72
|
+
else
|
73
|
+
puts "Test \"#{testname}\" failed"
|
74
|
+
puts "--- RESULT ---"
|
75
|
+
puts rcss
|
76
|
+
puts "--- EXPECTED ---"
|
77
|
+
puts css
|
78
|
+
puts "---"
|
79
|
+
raise
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "Run all tests - use only=test1 as an argument to run just test1"
|
84
|
+
task :test do |t|
|
85
|
+
puts "Testing"
|
86
|
+
if ENV.include?('only')
|
87
|
+
run_test "test/data/#{ENV['only']}.in.rcss"
|
88
|
+
else
|
89
|
+
# units
|
90
|
+
tests = File.read('test/units.dcss')\
|
91
|
+
.split('/* ----------------------------------------------')[1..-1]\
|
92
|
+
.map do |test|
|
93
|
+
ret = { :title => test.slice!(/.*\*\//)[0..-3] }
|
94
|
+
ret[:input], ret[:expected] = test.split('/* -- expected: */').map {|s| s.strip}
|
95
|
+
ret
|
96
|
+
end\
|
97
|
+
.each do |test|
|
98
|
+
result = DuckCSS::CSSParser.new(test[:input]).to_css
|
99
|
+
if result == test[:expected]
|
100
|
+
puts "#{test[:title]} - passed"
|
101
|
+
else
|
102
|
+
puts "#{test[:title]} - failed"
|
103
|
+
puts "--- RESULT ---"
|
104
|
+
puts result
|
105
|
+
puts "--- EXPECTED ---"
|
106
|
+
puts test[:expected]
|
107
|
+
puts "---"
|
108
|
+
raise
|
109
|
+
end
|
110
|
+
end
|
111
|
+
# whole files
|
112
|
+
Dir[ 'test/data/*.in.rcss' ].each do |testfile|
|
113
|
+
run_test testfile
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
119
|
+
pkg.need_zip = true
|
120
|
+
pkg.need_tar_gz = true
|
121
|
+
pkg.need_tar_bz2 = true
|
122
|
+
end
|
123
|
+
#
|
124
|
+
# desc "Publish the API documentation"
|
125
|
+
# task :pdoc => [:rdoc] do
|
126
|
+
# Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# desc 'Publish the gem and API docs'
|
130
|
+
# task :publish => [:pdoc, :rubyforge_upload]
|
131
|
+
#
|
132
|
+
# desc "Publish the release files to RubyForge."
|
133
|
+
# task :rubyforge_upload => :package do
|
134
|
+
# files = %w(gem tgz).map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
|
135
|
+
#
|
136
|
+
# if RUBY_FORGE_PROJECT then
|
137
|
+
# require 'net/http'
|
138
|
+
# require 'open-uri'
|
139
|
+
#
|
140
|
+
# project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
|
141
|
+
# project_data = open(project_uri) { |data| data.read }
|
142
|
+
# group_id = project_data[/[?&]group_id=(\d+)/, 1]
|
143
|
+
# raise "Couldn't get group id" unless group_id
|
144
|
+
#
|
145
|
+
# # This echos password to shell which is a bit sucky
|
146
|
+
# if ENV["RUBY_FORGE_PASSWORD"]
|
147
|
+
# password = ENV["RUBY_FORGE_PASSWORD"]
|
148
|
+
# else
|
149
|
+
# print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
|
150
|
+
# password = STDIN.gets.chomp
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
154
|
+
# data = [
|
155
|
+
# "login=1",
|
156
|
+
# "form_loginname=#{RUBY_FORGE_USER}",
|
157
|
+
# "form_pw=#{password}"
|
158
|
+
# ].join("&")
|
159
|
+
# http.post("/account/login.php", data)
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# cookie = login_response["set-cookie"]
|
163
|
+
# raise "Login failed" unless cookie
|
164
|
+
# headers = { "Cookie" => cookie }
|
165
|
+
#
|
166
|
+
# release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
|
167
|
+
# release_data = open(release_uri, headers) { |data| data.read }
|
168
|
+
# package_id = release_data[/[?&]package_id=(\d+)/, 1]
|
169
|
+
# raise "Couldn't get package id" unless package_id
|
170
|
+
#
|
171
|
+
# first_file = true
|
172
|
+
# release_id = ""
|
173
|
+
#
|
174
|
+
# files.each do |filename|
|
175
|
+
# basename = File.basename(filename)
|
176
|
+
# file_ext = File.extname(filename)
|
177
|
+
# file_data = File.open(filename, "rb") { |file| file.read }
|
178
|
+
#
|
179
|
+
# puts "Releasing #{basename}..."
|
180
|
+
#
|
181
|
+
# release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
182
|
+
# release_date = Time.now.strftime("%Y-%m-%d %H:%M")
|
183
|
+
# type_map = {
|
184
|
+
# ".zip" => "3000",
|
185
|
+
# ".tgz" => "3110",
|
186
|
+
# ".gz" => "3110",
|
187
|
+
# ".gem" => "1400"
|
188
|
+
# }; type_map.default = "9999"
|
189
|
+
# type = type_map[file_ext]
|
190
|
+
# boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
|
191
|
+
#
|
192
|
+
# query_hash = if first_file then
|
193
|
+
# {
|
194
|
+
# "group_id" => group_id,
|
195
|
+
# "package_id" => package_id,
|
196
|
+
# "release_name" => PKG_FILE_NAME,
|
197
|
+
# "release_date" => release_date,
|
198
|
+
# "type_id" => type,
|
199
|
+
# "processor_id" => "8000", # Any
|
200
|
+
# "release_notes" => "",
|
201
|
+
# "release_changes" => "",
|
202
|
+
# "preformatted" => "1",
|
203
|
+
# "submit" => "1"
|
204
|
+
# }
|
205
|
+
# else
|
206
|
+
# {
|
207
|
+
# "group_id" => group_id,
|
208
|
+
# "release_id" => release_id,
|
209
|
+
# "package_id" => package_id,
|
210
|
+
# "step2" => "1",
|
211
|
+
# "type_id" => type,
|
212
|
+
# "processor_id" => "8000", # Any
|
213
|
+
# "submit" => "Add This File"
|
214
|
+
# }
|
215
|
+
# end
|
216
|
+
#
|
217
|
+
# query = "?" + query_hash.map do |(name, value)|
|
218
|
+
# [name, URI.encode(value)].join("=")
|
219
|
+
# end.join("&")
|
220
|
+
#
|
221
|
+
# data = [
|
222
|
+
# "--" + boundary,
|
223
|
+
# "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
|
224
|
+
# "Content-Type: application/octet-stream",
|
225
|
+
# "Content-Transfer-Encoding: binary",
|
226
|
+
# "", file_data, ""
|
227
|
+
# ].join("\x0D\x0A")
|
228
|
+
#
|
229
|
+
# release_headers = headers.merge(
|
230
|
+
# "Content-Type" => "multipart/form-data; boundary=#{boundary}"
|
231
|
+
# )
|
232
|
+
#
|
233
|
+
# target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
|
234
|
+
# http.post(target + query, data, release_headers)
|
235
|
+
# end
|
236
|
+
#
|
237
|
+
# if first_file then
|
238
|
+
# release_id = release_response.body[/release_id=(\d+)/, 1]
|
239
|
+
# raise("Couldn't get release id") unless release_id
|
240
|
+
# end
|
241
|
+
#
|
242
|
+
# first_file = false
|
243
|
+
# end
|
244
|
+
# end
|
245
|
+
#end
|
data/TODO
ADDED
data/bin/dcss
ADDED
data/bin/test
ADDED
data/lib/dcss.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
$: << File.dirname(__FILE__) # adds the directory of this file to the load path
|
2
|
+
|
3
|
+
require 'lexer'
|
4
|
+
require 'erb'
|
5
|
+
require 'rubygems'
|
6
|
+
|
7
|
+
module DuckCSS #:nodoc:
|
8
|
+
|
9
|
+
VERSION = "0.3"
|
10
|
+
|
11
|
+
# Implements DCSS in Ruby.
|
12
|
+
class CSSParser < String
|
13
|
+
|
14
|
+
SERVER_CONSTANTS = /@server\s+(?:variables|constants)\s*\{\s*([^\}]+)\s*\}\s*/m
|
15
|
+
SERVER_CONSTANT = /([^:\s]+)\s*:\s*([^;]+)\s*;/m
|
16
|
+
|
17
|
+
def self.parse
|
18
|
+
new(self).to_css
|
19
|
+
end
|
20
|
+
|
21
|
+
# Generates CSS from DCSS contents
|
22
|
+
#
|
23
|
+
# When binding is provided it is used to evaluate ERB template
|
24
|
+
#
|
25
|
+
# template = <<-TXT
|
26
|
+
# @server constants { my_color: #fff; }
|
27
|
+
# body { color: my_color; }
|
28
|
+
# TXT
|
29
|
+
# dcss = DCSS::CSSParser.new(template)
|
30
|
+
# dcss.to_css
|
31
|
+
# #=> "body { color: #fff; }
|
32
|
+
def to_css(binding=nil) # :nodoc:
|
33
|
+
process_erb(binding)
|
34
|
+
process_constants
|
35
|
+
process_descendant_selectors
|
36
|
+
@dcss
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def process_erb(binding=nil)
|
42
|
+
@dcss = ERB.new(self.dup, nil, '-').result(binding)
|
43
|
+
end
|
44
|
+
|
45
|
+
def process_constants
|
46
|
+
constants = {}
|
47
|
+
# 1. Find and collect all server-side constants
|
48
|
+
@dcss.gsub!(SERVER_CONSTANTS) do
|
49
|
+
$1.scan(SERVER_CONSTANT) do
|
50
|
+
constants[$1]=$2
|
51
|
+
end
|
52
|
+
""
|
53
|
+
end
|
54
|
+
# 2. Substitute constants with values
|
55
|
+
@dcss.gsub!(/[^:;\s]+/) do
|
56
|
+
constants[$&] or $&
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Here's an untransformed CSS file:
|
61
|
+
#
|
62
|
+
# #notes { color: red; } {{
|
63
|
+
# p { font-size: 2em; }
|
64
|
+
# }}
|
65
|
+
#
|
66
|
+
# #footer { color: blue } {{
|
67
|
+
# a { font-size: 1em; }
|
68
|
+
# }}
|
69
|
+
#
|
70
|
+
# Split into a token and element array it looks like this:
|
71
|
+
#
|
72
|
+
# tokens = "sp{sp}sp{sp}"
|
73
|
+
# elements = [ '#notes', '{ color: red; }', '{{', 'p',
|
74
|
+
# '{ font-size: 2em; }', '}}', '#footer', '{ color: blue }',
|
75
|
+
# '{{', 'a', '{ font-size: 1em;}', '}}' ]
|
76
|
+
#
|
77
|
+
#
|
78
|
+
# Note that tokens is both a string and an array because an array is just
|
79
|
+
# a string of characters. So tokens[0] corresponds to the first selector
|
80
|
+
# token (represented by the 's' character) and also the first selector in
|
81
|
+
# the elements array:
|
82
|
+
#
|
83
|
+
# >> tokens[0]
|
84
|
+
# => 115
|
85
|
+
# >> tokens[0].chr
|
86
|
+
# => "s"
|
87
|
+
# >> elements[0]
|
88
|
+
# => "#notes"
|
89
|
+
#
|
90
|
+
# Lined up they look like:
|
91
|
+
#
|
92
|
+
# s p { s ...
|
93
|
+
# ['#notes', '{ color: red; }', '{{', 'p' ...
|
94
|
+
#
|
95
|
+
# Now we can run quick regexes on the tokens string to figure what elements
|
96
|
+
# need modification. Once an element has been modified the token is
|
97
|
+
# changed to 'X' so the same element is not modified again on an
|
98
|
+
# additional pass of the tokens string.
|
99
|
+
#
|
100
|
+
# When every transformation is complete, we just combine the elements
|
101
|
+
# array into one large string and discard the tokens array.
|
102
|
+
#
|
103
|
+
def process_descendant_selectors
|
104
|
+
tokenmap = [
|
105
|
+
[ /\s+/, ?\s ], # skip whitespace
|
106
|
+
[ /\/\*.*\*\//, ?\s ], # skip comments
|
107
|
+
[ /\/\*.*?\*\//m, ?\s ], # skip multiline comments (i don't know why I need to do this)
|
108
|
+
[ /[^\{\}]+/, ?s ], # selector e.g. #somediv a > span
|
109
|
+
[ /\{([^\{\}]*)\}/, ?p ], # properties e.g. { color: blue, background: green; }
|
110
|
+
[ /\{\{/, ?{ ], # descendant open {{
|
111
|
+
[ /\}\}/, ?} ] # descendant close }}
|
112
|
+
].map { |a| [ /\A#{a[0]}/, a[1] ] } # all tokens anchored at start (\A) of scanned string
|
113
|
+
|
114
|
+
tokens, elements = Lexer.new(tokenmap).scan(@dcss)
|
115
|
+
|
116
|
+
descendant_selector = /(s) (p){0,1} (\{) ([spX<>]+) (\})/mx
|
117
|
+
# 1 2 3 4 5
|
118
|
+
|
119
|
+
while tokens =~ descendant_selector
|
120
|
+
match = tokens.match(descendant_selector)
|
121
|
+
parent_scope = elements[match.begin(1)]
|
122
|
+
start_children, end_children = match.offset(4)
|
123
|
+
(start_children...end_children).each do |pos|
|
124
|
+
if tokens[pos] == ?s
|
125
|
+
elements[pos] = parent_scope.split(',').map do |p_scope|
|
126
|
+
elements[pos].split(',').map { |selector| "#{p_scope.strip} #{selector.strip}" }.join(', ')
|
127
|
+
end.join(', ')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
tokens[match.begin(3)], tokens[match.begin(5)] = ?< , ?> # mark the two { } symbols as processed
|
131
|
+
elements[match.begin(3)] = elements[match.begin(5)] = ''
|
132
|
+
# clear out parent selectors with no properties of their own. Eg. #somdiv {{ p { ... } }}
|
133
|
+
if match.begin(2).nil?
|
134
|
+
tokens[match.begin(1)] = tokens[match.begin(3)] = tokens[match.begin(5)] = ?X
|
135
|
+
elements[match.begin(1)] = ''
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# formatting ... could be prettier
|
140
|
+
tabsize = 0
|
141
|
+
tokens.size.times do |pos|
|
142
|
+
case tokens[pos]
|
143
|
+
when ?< : tabsize += 1
|
144
|
+
when ?> : tabsize -= 1
|
145
|
+
when ?p : elements[pos] = " #{elements[pos].strip}\n"
|
146
|
+
when ?s : elements[pos] = (0...tabsize).map { " " }.to_s + elements[pos].strip
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
@dcss = elements.to_s.strip
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
data/lib/lexer.rb
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
# Source: http://rubyforge.org/frs/?group_id=127&release_id=282
|
2
|
+
|
3
|
+
# Little Lexer, a very small very simple lexical scanner and parser class
|
4
|
+
#
|
5
|
+
# I wrote this in the middle of the night because the idea was sooo cute.
|
6
|
+
#
|
7
|
+
# Copyright (C) 2004 John Carter
|
8
|
+
|
9
|
+
# This library is free software; you can redistribute it and/or modify it
|
10
|
+
# under the terms of the GNU Lesser General Public License as published by
|
11
|
+
# the Free Software Foundation; either version 2.1 of the License, or (at
|
12
|
+
# your option) any later version.
|
13
|
+
|
14
|
+
# This library is distributed in the hope that it will be useful, but
|
15
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
17
|
+
# Lesser General Public License for more details.
|
18
|
+
|
19
|
+
# You should have received a copy of the GNU Lesser General Public
|
20
|
+
# License along with this library; if not, write to the Free Software
|
21
|
+
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
|
22
|
+
# USA.
|
23
|
+
|
24
|
+
class LexerJammed < Exception
|
25
|
+
end
|
26
|
+
|
27
|
+
# A tiny very very simple Lexer/Parser class.
|
28
|
+
#
|
29
|
+
# Feed it an array of [regex,char] pairs and it
|
30
|
+
# scans an input string returning a string of char's matching
|
31
|
+
# the tokens found.
|
32
|
+
|
33
|
+
# What makes this such a very very very cute idea (apart from the fact
|
34
|
+
# it so very small) is... Traditionally Lexical scanners such as flex
|
35
|
+
# yield a stream of tokens id's (usully an int or an enum) and the
|
36
|
+
# token (usually a string). I have chosen (this is the smart bit) to
|
37
|
+
# make each token id a character (which limits the number of possible
|
38
|
+
# token id's a bit, but certainly not for any practical use), and not
|
39
|
+
# return a stream of token id's and tokens, but an array.
|
40
|
+
|
41
|
+
# Now an array of token id's is just an array of char which is just a
|
42
|
+
# string!
|
43
|
+
|
44
|
+
# WAKE UP! This is VERY cute! Thus the output of the lexical scanner
|
45
|
+
# is just a string which can be used as the input to a lexical scanner.
|
46
|
+
|
47
|
+
# Thus we can use the very same class as both a lexical scanner _AND_
|
48
|
+
# a parser!
|
49
|
+
|
50
|
+
# I _told_ you it was cute.
|
51
|
+
|
52
|
+
# See the tests for examples.
|
53
|
+
#
|
54
|
+
class Lexer
|
55
|
+
|
56
|
+
# Constructs a Lexer that can be used and reused
|
57
|
+
# to scan a string for tokens.
|
58
|
+
#
|
59
|
+
# Parameters
|
60
|
+
# regex_to_char is an enumeration of [regex,char] pairs.
|
61
|
+
# The 'next_token' method tries to match each
|
62
|
+
# regex in order, if one matches, the matching
|
63
|
+
# token and corresponding char is returned.
|
64
|
+
# skip_white_space - If true, token that corresponds to ?\s (space)
|
65
|
+
# will be skipped.
|
66
|
+
#
|
67
|
+
def initialize(regex_to_char,skip_white_space = true)
|
68
|
+
@skip_white_space = skip_white_space
|
69
|
+
@regex_to_char = regex_to_char
|
70
|
+
end
|
71
|
+
|
72
|
+
# Scans a string for tokens
|
73
|
+
#
|
74
|
+
# Parameters
|
75
|
+
# string - String to scan for tokens
|
76
|
+
# string_token_list - token list output by first pass.
|
77
|
+
#
|
78
|
+
# Returns a string of token id's and a token list.
|
79
|
+
#
|
80
|
+
# If string_token_list is nil, (the default) then...
|
81
|
+
# The input string is scanned and broken up into tokens as
|
82
|
+
# follows. Each regex in the regex to char list is tried in order
|
83
|
+
# until either one matches, in which case the corresponding token
|
84
|
+
# id char is appended to the output string, and the token is
|
85
|
+
# appended to the token list. The token is removed from the front
|
86
|
+
# of the string and the it then tries again until all tokens have
|
87
|
+
# been found or no regex matches. If no regex in the regex to
|
88
|
+
# char list matches, then the Lexer jam's and throws a LexerJammed
|
89
|
+
# exception
|
90
|
+
|
91
|
+
# If the input string is the output string of a another Lexer
|
92
|
+
# instance's scan and the string_token_list is the matching token
|
93
|
+
# list, then the output token list is a list of pairs. The first
|
94
|
+
# element in each pair is a subarray of the string_token_list that
|
95
|
+
# matches the token matched. The token matched is in the second
|
96
|
+
# element of the pair. Thus a parser can work make to the original
|
97
|
+
# token's found by the lexer.
|
98
|
+
|
99
|
+
def scan(string,string_token_list=nil)
|
100
|
+
result_string = ''
|
101
|
+
token_list = []
|
102
|
+
|
103
|
+
if string_token_list
|
104
|
+
next_token(string) do |t,token, tail|
|
105
|
+
result_string << t
|
106
|
+
token_list << [string_token_list[0...tail], string[0...tail]]
|
107
|
+
string = string[tail..-1]
|
108
|
+
string_token_list = string_token_list[tail..-1]
|
109
|
+
end
|
110
|
+
else
|
111
|
+
next_token(string) do |t,token, tail|
|
112
|
+
result_string << t
|
113
|
+
token_list << token
|
114
|
+
end
|
115
|
+
end
|
116
|
+
return result_string, token_list
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# Private method for scan that finds the next token.
|
122
|
+
#
|
123
|
+
# Parameters
|
124
|
+
# string - input string to be scanned.
|
125
|
+
# block - yield's the token id char, token and offset of the end of the token.
|
126
|
+
#
|
127
|
+
# Throws LexerJammed if fails, details holds string it failed on.
|
128
|
+
#
|
129
|
+
# Returns void
|
130
|
+
def next_token( string)
|
131
|
+
match_data = nil
|
132
|
+
while string != ''
|
133
|
+
failed = true
|
134
|
+
@regex_to_char.each do |regex,char|
|
135
|
+
match_data = regex.match(string)
|
136
|
+
next unless match_data
|
137
|
+
token = match_data[0]
|
138
|
+
yield char,token, match_data.end(0) unless char == ?\s && @skip_white_space
|
139
|
+
string = match_data.post_match
|
140
|
+
failed = false
|
141
|
+
break
|
142
|
+
end
|
143
|
+
raise LexerJammed, string if failed
|
144
|
+
end
|
145
|
+
|
146
|
+
rescue RegexpError => details
|
147
|
+
raise RegexpError, "Choked while '#{@scanner}' was trying to match '#{string}' : #{details}"
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
if $0 == __FILE__ then
|
153
|
+
require 'test/unit'
|
154
|
+
require 'DiGraphTest'
|
155
|
+
require 'DiGraphParser'
|
156
|
+
require 'HtmlParser'
|
157
|
+
require 'CParser'
|
158
|
+
require 'pp'
|
159
|
+
|
160
|
+
class TC_TestLex < Test::Unit::TestCase
|
161
|
+
def test_own
|
162
|
+
lexer, tag_lexer = HtmlParser::create_parser
|
163
|
+
|
164
|
+
result_string, token_list = lexer.scan( '<!DOCTYPE html
|
165
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head><!-- Vignette V6 Sat Jan 31 01:00:06 2004 -->
|
166
|
+
<html>
|
167
|
+
<script foo="fah">
|
168
|
+
<!-- if a < b -->
|
169
|
+
</script>
|
170
|
+
<!-- comment -->
|
171
|
+
</body>')
|
172
|
+
|
173
|
+
assert_equal( "dsscfsfsfcfefcfe", result_string)
|
174
|
+
html_tag = token_list[1]
|
175
|
+
rtag,ttag = tag_lexer.scan( html_tag)
|
176
|
+
|
177
|
+
assert_equal( '<ii=ti=ti=t>', rtag)
|
178
|
+
assert_equal( 'html', ttag[1])
|
179
|
+
end
|
180
|
+
|
181
|
+
include DiGraphTest
|
182
|
+
|
183
|
+
def test_lex
|
184
|
+
lexer, parser = DiGraphParser::create_parser
|
185
|
+
string,list = lexer.scan(example_digraph)
|
186
|
+
# pp string
|
187
|
+
# pp list
|
188
|
+
assert_equal( "di{i[i=i];i[i=i];i>i;i>i;i>i;i>i;i>i;i>i;i>i;i>i;};", string)
|
189
|
+
assert_equal( 'digraph', list[0])
|
190
|
+
|
191
|
+
p_string, p_list = parser.scan(string,list)
|
192
|
+
assert_equal( 'hnneeeeeeeet', p_string)
|
193
|
+
assert_equal( 'di{', p_list[0][1])
|
194
|
+
end
|
195
|
+
|
196
|
+
def test_c
|
197
|
+
clexer = CParser::create_parser
|
198
|
+
src = " foo
|
199
|
+
dang //
|
200
|
+
//blast
|
201
|
+
/**/
|
202
|
+
/***/
|
203
|
+
/* */
|
204
|
+
/* * */
|
205
|
+
/* goo
|
206
|
+
**/ /* bah /* * ** / * /
|
207
|
+
test //gah
|
208
|
+
*/
|
209
|
+
"
|
210
|
+
pp src
|
211
|
+
string,list = clexer.scan(src)
|
212
|
+
assert_equal( 'dppndndcdcdcdcd', string)
|
213
|
+
assert_equal( "//\n", list[1])
|
214
|
+
pp string
|
215
|
+
pp list
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_fail
|
219
|
+
lexer, tag_lexer = HtmlParser::create_parser
|
220
|
+
|
221
|
+
result_string, token_list = tag_lexer.scan( '<f%%k>')
|
222
|
+
assert( false)
|
223
|
+
rescue LexerJammed
|
224
|
+
assert(true)
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
module Rcss #:nodoc:
|
6
|
+
|
7
|
+
VERSION = "0.3.1"
|
8
|
+
|
9
|
+
# Implements CSS-SSC in Ruby.
|
10
|
+
#
|
11
|
+
# Main features:
|
12
|
+
#
|
13
|
+
# * Create rich CSS files using:
|
14
|
+
#
|
15
|
+
# * ERB templates
|
16
|
+
#
|
17
|
+
# * Server-side constants
|
18
|
+
#
|
19
|
+
# * Server-side classes (preliminary)
|
20
|
+
#
|
21
|
+
# * Simply add Rcss files to any Rails application
|
22
|
+
#
|
23
|
+
# * Process Rcss files off-line using command line executable
|
24
|
+
#
|
25
|
+
class Ssc < String
|
26
|
+
|
27
|
+
@@SERVER_CONSTANTS = /@server\s+(?:variables|constants)\s*\{\s*([^\}]+)\s*\}\s*/m
|
28
|
+
|
29
|
+
@@SERVER_CONSTANT = /([^:\s]+)\s*:\s*([^;]+)\s*;/m
|
30
|
+
|
31
|
+
@@SERVER_CLASS = /@server\s+(\&\w?[-_\w\d]*)\s+\{([^\}]*)\}/m
|
32
|
+
|
33
|
+
@@EXTENDS_ATTRIBUTE = /extends\s*:\s*(\&\w?[-_\w\d]*);/m
|
34
|
+
|
35
|
+
def initialize(string) # :nodoc:
|
36
|
+
super(string)
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse(text, binding) # :nodoc:
|
40
|
+
classes = Hash.new
|
41
|
+
constants = Hash.new
|
42
|
+
|
43
|
+
# Apply ERB as it has the highest priority
|
44
|
+
rcss = ERB.new(text, nil, '-').result(binding)
|
45
|
+
|
46
|
+
# Apply SSC in two steps:
|
47
|
+
|
48
|
+
# 1. Find and collect all server-side constants
|
49
|
+
rcss.gsub!(@@SERVER_CONSTANTS) do
|
50
|
+
$1.scan(@@SERVER_CONSTANT) do
|
51
|
+
constants[$1]=$2
|
52
|
+
end
|
53
|
+
""
|
54
|
+
end
|
55
|
+
|
56
|
+
# 2. Substitute constants with values
|
57
|
+
rcss.gsub!(/[^:;\s]+/) do
|
58
|
+
constants[$&] or $&
|
59
|
+
end
|
60
|
+
|
61
|
+
# Apply server-side classes
|
62
|
+
rcss.gsub!(@@SERVER_CLASS) do
|
63
|
+
class_name = $1
|
64
|
+
body = $2
|
65
|
+
body.gsub!(@@EXTENDS_ATTRIBUTE) { classes[$1] }
|
66
|
+
classes[class_name] = body.strip
|
67
|
+
""
|
68
|
+
end
|
69
|
+
rcss.gsub!(@@EXTENDS_ATTRIBUTE) { classes[$1] }
|
70
|
+
|
71
|
+
rcss
|
72
|
+
end
|
73
|
+
|
74
|
+
# Generates CSS from Rcss contents
|
75
|
+
#
|
76
|
+
# When binding is provided it is used to evaluate ERB template
|
77
|
+
#
|
78
|
+
# template = <<-TXT
|
79
|
+
# @server constants { my_color: #fff; }
|
80
|
+
# body { color: my_color; }
|
81
|
+
# TXT
|
82
|
+
# rcss = Rcss::Ssc.new(template)
|
83
|
+
# rcss.to_css
|
84
|
+
# #=> "body { color: #fff; }
|
85
|
+
#
|
86
|
+
def to_css(binding=nil)
|
87
|
+
parse(self.dup, binding)
|
88
|
+
end
|
89
|
+
|
90
|
+
private :parse
|
91
|
+
end
|
92
|
+
|
93
|
+
# Rails handler for Rcss files
|
94
|
+
class RcssHandler
|
95
|
+
include ERB::Util
|
96
|
+
|
97
|
+
def initialize(action_view) #:nodoc:
|
98
|
+
@action_view = action_view
|
99
|
+
end
|
100
|
+
|
101
|
+
# Renders given template using local_assigns for evaluation
|
102
|
+
#
|
103
|
+
# Modifies response headers:
|
104
|
+
#
|
105
|
+
# Content-Type: text/css
|
106
|
+
#
|
107
|
+
def render(template, local_assigns)
|
108
|
+
@action_view.controller.headers["Content-Type"] = 'text/css'
|
109
|
+
b = binding
|
110
|
+
|
111
|
+
local_assigns.stringify_keys!
|
112
|
+
local_assigns.each { |key, value| eval "#{key} = local_assigns[\"#{key}\"]", b }
|
113
|
+
Ssc.new(template).to_css(b)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
if Object.const_defined? "ActionView"
|
119
|
+
ActionView::Base::register_template_handler 'rcss', Rcss::RcssHandler
|
120
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
body.page { width: 400px; }
|
2
|
+
|
3
|
+
#somediv { background: green; }
|
4
|
+
|
5
|
+
body.home, #specialpage { margin: 10px auto; width: 700px; } {{
|
6
|
+
h1 { font-family: Arial, sans-serif; }
|
7
|
+
a, a:visited { color: blue; }
|
8
|
+
p > span { font-size: 1.2em; }
|
9
|
+
#someid { background: red; } {{
|
10
|
+
h1 { font-family: Georgia, serif; }
|
11
|
+
}}
|
12
|
+
}}
|
13
|
+
|
14
|
+
body.home #someotherdiv { color: orange; }
|
15
|
+
|
16
|
+
#header { background: white; } {{
|
17
|
+
h2 { color: yellow; }
|
18
|
+
}}
|
19
|
+
|
20
|
+
#header, #footer { } {{
|
21
|
+
p { line-height: 1.5; }
|
22
|
+
}}
|
23
|
+
|
24
|
+
#header,
|
25
|
+
#footer {{
|
26
|
+
p {{
|
27
|
+
li {
|
28
|
+
line-height: 1.5;
|
29
|
+
color: orange;
|
30
|
+
}
|
31
|
+
}}
|
32
|
+
}}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
body.page { width: 400px; }
|
2
|
+
#somediv { background: green; }
|
3
|
+
body.home, #specialpage { margin: 10px auto; width: 700px; }
|
4
|
+
body.home h1, #specialpage h1 { font-family: Arial, sans-serif; }
|
5
|
+
body.home a, body.home a:visited, #specialpage a, #specialpage a:visited { color: blue; }
|
6
|
+
body.home p > span, #specialpage p > span { font-size: 1.2em; }
|
7
|
+
body.home #someid, #specialpage #someid { background: red; }
|
8
|
+
body.home #someid h1, #specialpage #someid h1 { font-family: Georgia, serif; }
|
9
|
+
body.home #someotherdiv { color: orange; }
|
10
|
+
#header { background: white; }
|
11
|
+
#header h2 { color: yellow; }
|
12
|
+
#header, #footer { }
|
13
|
+
#header p, #footer p { line-height: 1.5; }
|
14
|
+
#header p li, #footer p li {
|
15
|
+
line-height: 1.5;
|
16
|
+
color: orange;
|
17
|
+
}
|
data/test/units.dcss
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
/* ----------------------------------------------
|
2
|
+
should leave normal css alone */
|
3
|
+
|
4
|
+
#someselector { someproperty: somevalue; }
|
5
|
+
|
6
|
+
/* -- expected: */
|
7
|
+
|
8
|
+
#someselector { someproperty: somevalue; }
|
9
|
+
|
10
|
+
/* ----------------------------------------------
|
11
|
+
should leave normal multi-line css alone */
|
12
|
+
|
13
|
+
#someselector,
|
14
|
+
#anotherselector {
|
15
|
+
someproperty: somevalue;
|
16
|
+
}
|
17
|
+
|
18
|
+
/* -- expected: */
|
19
|
+
|
20
|
+
#someselector,
|
21
|
+
#anotherselector {
|
22
|
+
someproperty: somevalue;
|
23
|
+
}
|
24
|
+
|
25
|
+
/* ----------------------------------------------
|
26
|
+
should render erb */
|
27
|
+
|
28
|
+
#someselector { someproperty: <%= "somevalue" %>; }
|
29
|
+
|
30
|
+
/* -- expected: */
|
31
|
+
|
32
|
+
#someselector { someproperty: somevalue; }
|
33
|
+
|
34
|
+
/* ----------------------------------------------
|
35
|
+
should render erb */
|
36
|
+
|
37
|
+
#someselector { someproperty: <%= "somevalue" %>; }
|
38
|
+
|
39
|
+
/* -- expected: */
|
40
|
+
|
41
|
+
#someselector { someproperty: somevalue; }
|
42
|
+
|
43
|
+
/* ----------------------------------------------
|
44
|
+
should not screw up when one constant contains the name of another */
|
45
|
+
|
46
|
+
@server constants { gray: #ccc; lightgray: #eee;}
|
47
|
+
#someselector { color: gray; background: lightgray; }
|
48
|
+
|
49
|
+
/* -- expected: */
|
50
|
+
|
51
|
+
#someselector { color: #ccc; background: #eee; }
|
52
|
+
|
53
|
+
/* ----------------------------------------------
|
54
|
+
should convert constants */
|
55
|
+
|
56
|
+
@server constants { gray: #ccc; }
|
57
|
+
#someselector { color: gray; }
|
58
|
+
|
59
|
+
/* -- expected: */
|
60
|
+
|
61
|
+
#someselector { color: #ccc; }
|
62
|
+
|
63
|
+
/* ----------------------------------------------
|
64
|
+
should handle single level nesting */
|
65
|
+
|
66
|
+
#someselector { color: red; } {{
|
67
|
+
#anotherselector { color: blue; }
|
68
|
+
}}
|
69
|
+
|
70
|
+
/* -- expected: */
|
71
|
+
|
72
|
+
#someselector { color: red; }
|
73
|
+
#someselector #anotherselector { color: blue; }
|
74
|
+
|
75
|
+
/* ----------------------------------------------
|
76
|
+
should do the right thing with commas on the parent selector */
|
77
|
+
|
78
|
+
#someselector, #select2 { color: red; } {{
|
79
|
+
#anotherselector { color: blue; }
|
80
|
+
}}
|
81
|
+
|
82
|
+
/* -- expected: */
|
83
|
+
|
84
|
+
#someselector, #select2 { color: red; }
|
85
|
+
#someselector #anotherselector, #select2 #anotherselector { color: blue; }
|
86
|
+
|
87
|
+
/* ----------------------------------------------
|
88
|
+
should do the right thing with commas on the child selector */
|
89
|
+
|
90
|
+
#someselector { color: red; } {{
|
91
|
+
#anotherselector, #select2 { color: blue; }
|
92
|
+
}}
|
93
|
+
|
94
|
+
/* -- expected: */
|
95
|
+
|
96
|
+
#someselector { color: red; }
|
97
|
+
#someselector #anotherselector, #someselector #select2 { color: blue; }
|
98
|
+
|
99
|
+
/* ----------------------------------------------
|
100
|
+
commas everywhere! */
|
101
|
+
|
102
|
+
#someselector, #select2 { color: red; } {{
|
103
|
+
#anotherselector, #select3 { color: blue; }
|
104
|
+
}}
|
105
|
+
|
106
|
+
/* -- expected: */
|
107
|
+
|
108
|
+
#someselector, #select2 { color: red; }
|
109
|
+
#someselector #anotherselector, #someselector #select3, #select2 #anotherselector, #select2 #select3 { color: blue; }
|
110
|
+
|
111
|
+
/* ----------------------------------------------
|
112
|
+
should do the right thing with empty parent selectors (right thing may change) */
|
113
|
+
|
114
|
+
#someselector {} {{
|
115
|
+
#anotherselector, #select3 { color: blue; }
|
116
|
+
}}
|
117
|
+
|
118
|
+
/* -- expected: */
|
119
|
+
|
120
|
+
#someselector {}
|
121
|
+
#someselector #anotherselector, #someselector #select3 { color: blue; }
|
122
|
+
|
123
|
+
/* ----------------------------------------------
|
124
|
+
should allow parent selectors without properties (i.e. they are used to specify scope only) */
|
125
|
+
|
126
|
+
#someselector {{
|
127
|
+
#anotherselector, #select3 { color: blue; }
|
128
|
+
}}
|
129
|
+
|
130
|
+
/* -- expected: */
|
131
|
+
|
132
|
+
#someselector #anotherselector, #someselector #select3 { color: blue; }
|
133
|
+
|
134
|
+
/* ----------------------------------------------
|
135
|
+
should allow comma separated parent selectors without properties */
|
136
|
+
|
137
|
+
#someselector, #select2 {{
|
138
|
+
#anotherselector, #select3 { color: blue; }
|
139
|
+
}}
|
140
|
+
|
141
|
+
/* -- expected: */
|
142
|
+
|
143
|
+
#someselector #anotherselector, #someselector #select3, #select2 #anotherselector, #select2 #select3 { color: blue; }
|
144
|
+
|
145
|
+
/* ----------------------------------------------
|
146
|
+
should strip comments outside of property lists only */
|
147
|
+
|
148
|
+
/* some comment */
|
149
|
+
#someselector {{ /* some comment */
|
150
|
+
#anotherselector, #select3 { color: blue; /* some comment */ }
|
151
|
+
/* some comment */}}
|
152
|
+
|
153
|
+
/* -- expected: */
|
154
|
+
|
155
|
+
#someselector #anotherselector, #someselector #select3 { color: blue; /* some comment */ }
|
156
|
+
|
157
|
+
/* ----------------------------------------------
|
158
|
+
should strip multiline comments */
|
159
|
+
|
160
|
+
/* multiline
|
161
|
+
comment */
|
162
|
+
#someselector {{
|
163
|
+
#anotherselector, #select3 { color: blue; }
|
164
|
+
}}
|
165
|
+
|
166
|
+
/* -- expected: */
|
167
|
+
|
168
|
+
#someselector #anotherselector, #someselector #select3 { color: blue; }
|
169
|
+
|
170
|
+
/* ----------------------------------------------
|
171
|
+
should not be greedy when stripping multiline comments */
|
172
|
+
|
173
|
+
/* multiline
|
174
|
+
comment */
|
175
|
+
a { color: yellow; }
|
176
|
+
/* another
|
177
|
+
comment */
|
178
|
+
#someselector {{
|
179
|
+
#anotherselector, #select3 { color: blue; }
|
180
|
+
}}
|
181
|
+
|
182
|
+
/* -- expected: */
|
183
|
+
|
184
|
+
a { color: yellow; }
|
185
|
+
#someselector #anotherselector, #someselector #select3 { color: blue; }
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: dcss
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.3"
|
7
|
+
date: 2006-12-12 00:00:00 +11:00
|
8
|
+
summary: DCSS - CSS extensions that make writing stylesheets less verbose
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: myles@ducknewmedia.com.au, bragi.ragnarson@gmail.com
|
12
|
+
homepage: http://myles.id.au/more/#DCSS
|
13
|
+
rubyforge_project: dcss
|
14
|
+
description:
|
15
|
+
autorequire: dcss
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Myles Byrne, Bragi Ragnarson
|
31
|
+
files:
|
32
|
+
- README
|
33
|
+
- CHANGES
|
34
|
+
- TODO
|
35
|
+
- MIT-LICENSE
|
36
|
+
- Rakefile
|
37
|
+
- lib/dcss.rb
|
38
|
+
- lib/lexer.rb
|
39
|
+
- lib/rcss.original.rb
|
40
|
+
- bin/dcss
|
41
|
+
- bin/test
|
42
|
+
- test/data
|
43
|
+
- test/units.dcss
|
44
|
+
- test/data/complex_descendants.in.rcss
|
45
|
+
- test/data/complex_descendants.out.css
|
46
|
+
- test/data/with_comments.in.rcss
|
47
|
+
- test/data/with_comments.out.css
|
48
|
+
test_files: []
|
49
|
+
|
50
|
+
rdoc_options:
|
51
|
+
- --title
|
52
|
+
- DCSS - CSS extensions that make writing stylesheets less verbose
|
53
|
+
- --main
|
54
|
+
- README
|
55
|
+
- --line-numbers
|
56
|
+
extra_rdoc_files:
|
57
|
+
- README
|
58
|
+
- MIT-LICENSE
|
59
|
+
- TODO
|
60
|
+
- CHANGES
|
61
|
+
executables:
|
62
|
+
- dcss
|
63
|
+
extensions: []
|
64
|
+
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
dependencies: []
|
68
|
+
|