dcss 0.3

Sign up to get free protection for your applications and to get access to all the features.
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
+