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 ADDED
@@ -0,0 +1,3 @@
1
+ = Version 0.1
2
+ * Initial release
3
+ * Descendant selector {{ }}
@@ -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.
@@ -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
@@ -0,0 +1,4 @@
1
+ = DuckCSS TODO list
2
+
3
+ == Future development
4
+ * Implement Include - syntax should be: #somdiv { include: .someclass, .somotherclass; color: blue; }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/dcss'
4
+ puts DuckCSS::CSSParser.new( ARGF.read ).to_css
@@ -0,0 +1,11 @@
1
+ @server constants {
2
+ titles_font: "Trebuchet MS", Verdana, sans-serif;
3
+ link_color: #0A5F9E;
4
+ }
5
+
6
+ #menu { padding: 10px; } {{
7
+ #archives, #links { font-size: 0.9em; } {{
8
+ h2 { font-family: titles_font; }
9
+ a { color: link_color; }
10
+ }}
11
+ }}
@@ -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
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ /* comment here! */
2
+ body.home { margin: 10px auto; width: 700px; } {{
3
+ /* and here! */
4
+ h1 { font-family: Arial, sans-serif; }
5
+ }}
6
+ /* anohter one here */
7
+ p { line-height: 1; } {{
8
+ a { color: orange; }
9
+ }}
@@ -0,0 +1,4 @@
1
+ body.home { margin: 10px auto; width: 700px; }
2
+ body.home h1 { font-family: Arial, sans-serif; }
3
+ p { line-height: 1; }
4
+ p a { color: orange; }
@@ -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
+