dcss 0.3
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.
- 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
|
+
|