cascading_rubies 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +137 -0
- data/bin/rcss +37 -0
- data/example/block_example_1.rcss +7 -0
- data/example/block_example_2.rcss +7 -0
- data/example/example.rcss +39 -0
- data/example/no_block_example.rcss +6 -0
- data/lib/blankslate.rb +113 -0
- data/lib/cascading_rubies.rb +144 -0
- data/test/test_cascading_rubies.rb +138 -0
- metadata +63 -0
data/README.rdoc
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
= Cascading Rubies
|
2
|
+
|
3
|
+
Ruby DSL for generating CSS.
|
4
|
+
|
5
|
+
I wrote this small DSL because I like the concept of Sass -- just not
|
6
|
+
all the nuances of the syntax. I wondered how closely a Ruby DSL itself could
|
7
|
+
resemble final CSS output. This is the fruit of my experimentation.
|
8
|
+
|
9
|
+
I originally was going to call this RCSS, but that name is already taken.
|
10
|
+
|
11
|
+
Please note: This is very early beta, and the syntax may change. Also, this is
|
12
|
+
my very first DSL, and could very well be the biggest kludge you have ever seen.
|
13
|
+
But it works, and there's good test coverage, so hey.
|
14
|
+
|
15
|
+
== Syntax
|
16
|
+
|
17
|
+
header {
|
18
|
+
background_color '#eee'
|
19
|
+
margin_bottom '10px'
|
20
|
+
nav {
|
21
|
+
border '1px solid #000'
|
22
|
+
a(:link, :active, :visited) { color 'blue' }
|
23
|
+
a(:hover) { color 'red' }
|
24
|
+
}
|
25
|
+
div.search {
|
26
|
+
float 'right'
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
Output:
|
31
|
+
|
32
|
+
#header { background-color: #eee; margin-bottom: 10px; }
|
33
|
+
#header #nav { border: 1px solid #000; }
|
34
|
+
#header #nav a:link, #header #nav a:active, #header #nav a:visited { color: blue; }
|
35
|
+
#header #nav a:hover { color: red; }
|
36
|
+
#header div.search { float: right; }
|
37
|
+
|
38
|
+
Since the syntax is just Ruby, you can add comments, variables, and do just about anything
|
39
|
+
you can in plain Ruby.
|
40
|
+
|
41
|
+
=== Tags/IDs
|
42
|
+
|
43
|
+
Tags are determined from the array #CascadingRubies::TAGS. If the name is present there, it is
|
44
|
+
assumed to be a tag -- otherwise the name is output as a selector <tt>#id</tt>.
|
45
|
+
|
46
|
+
=== Classes
|
47
|
+
|
48
|
+
A leading underscore is used to denote a class selector, e.g. <tt>_search { }</tt> becomes
|
49
|
+
<tt>.search { }</tt> -- or you can put a tag in front of it like so: <tt>div.search { }</tt>.
|
50
|
+
|
51
|
+
=== Comma-Separated Selectors
|
52
|
+
|
53
|
+
To output a comma-separated list of selectors, chain selectors together with semicolons
|
54
|
+
or newlines, like so: <tt>li; a { }</tt>.
|
55
|
+
|
56
|
+
=== Examples
|
57
|
+
|
58
|
+
See files in example directory for more help.
|
59
|
+
|
60
|
+
== Installation
|
61
|
+
|
62
|
+
sudo gem install cascading_rubies -s http://gemcutter.org
|
63
|
+
|
64
|
+
== Usage
|
65
|
+
|
66
|
+
The gem includes a binary called +rcss+. Run it without args for usage details.
|
67
|
+
Here are some examples:
|
68
|
+
|
69
|
+
rcss base.rcss nav.rcss # print both rendered css files to screen
|
70
|
+
rcss -w base.rcss nav.rcss # write rendered base.css and nav.css
|
71
|
+
rcss base.rcss nav.rcss > all.css # write to single file
|
72
|
+
rcss -w stylesheets # render all .rcss files in directory
|
73
|
+
|
74
|
+
To use the library from within your own code:
|
75
|
+
|
76
|
+
require 'rubygems'
|
77
|
+
require 'cascading_rubies'
|
78
|
+
|
79
|
+
css = CascadingRubies.open(path_to_file)
|
80
|
+
# or...
|
81
|
+
css = CascadingRubies.new do
|
82
|
+
# css here
|
83
|
+
end
|
84
|
+
|
85
|
+
rendered = css.to_s
|
86
|
+
|
87
|
+
== Shortcomings
|
88
|
+
|
89
|
+
There are lots, no doubt. Here are few I thought up just now:
|
90
|
+
|
91
|
+
* CascadingRubies#s is the method that builds selectors, so you can't specify an id #s directly
|
92
|
+
in the DSL. Do this instead: <tt>s('#s') { ... }</tt>
|
93
|
+
* And there are a couple other names you can't use as ids (unless you use +s+):
|
94
|
+
* to\_s
|
95
|
+
* instance\_eval
|
96
|
+
* class
|
97
|
+
* inspect
|
98
|
+
* (others?)
|
99
|
+
* You can't build a selector like div#links without using the raw selector method:
|
100
|
+
<tt>s('div#links') { ... }</tt>
|
101
|
+
|
102
|
+
== Feedback
|
103
|
+
|
104
|
+
I'd love to hear from you if you have suggestions for improvement, bug fixes,
|
105
|
+
or whatever. Email me at tim@timmorgan.org or fork the project and send a
|
106
|
+
pull request.
|
107
|
+
|
108
|
+
To run the tests, do this:
|
109
|
+
ruby test/test_cascading_rubies.rb
|
110
|
+
|
111
|
+
The library has been tested with Ruby 1.8.6, Ruby 1.8.7 and Ruby 1.9.1p0.
|
112
|
+
If you find problems with your Ruby of choice, please let me know.
|
113
|
+
|
114
|
+
== License
|
115
|
+
|
116
|
+
The MIT License
|
117
|
+
|
118
|
+
Copyright (c) 2009 Tim Morgan
|
119
|
+
|
120
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
121
|
+
of this software and associated documentation files (the "Software"), to deal
|
122
|
+
in the Software without restriction, including without limitation the rights
|
123
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
124
|
+
copies of the Software, and to permit persons to whom the Software is
|
125
|
+
furnished to do so, subject to the following conditions:
|
126
|
+
|
127
|
+
The above copyright notice and this permission notice shall be included in
|
128
|
+
all copies or substantial portions of the Software.
|
129
|
+
|
130
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
131
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
132
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
133
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
134
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
135
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
136
|
+
THE SOFTWARE.
|
137
|
+
|
data/bin/rcss
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
require 'rubygems'
|
5
|
+
require 'cascading_rubies'
|
6
|
+
|
7
|
+
WRITE_OUT = ARGV.delete('-w') || ARGV.delete('--write')
|
8
|
+
|
9
|
+
def handle(path)
|
10
|
+
css = CascadingRubies.open(path)
|
11
|
+
if WRITE_OUT
|
12
|
+
File.open(path.sub(/\.[^\.]+$/, '.css'), 'w') { |f| f.write(css) }
|
13
|
+
else
|
14
|
+
puts css
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
ARGV.each do |path|
|
19
|
+
if File.directory?(path)
|
20
|
+
Dir[path + '/*.rcss'].each { |p| handle(p) }
|
21
|
+
elsif File.exist?(path)
|
22
|
+
handle(path)
|
23
|
+
else
|
24
|
+
puts "File or path not found: #{path}"
|
25
|
+
exit(1)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
unless ARGV.any?
|
30
|
+
puts 'Builds CSS from Ruby -- see http://github.com/seven1m/cascading_rubies'
|
31
|
+
puts 'Usage: rcss [-w] path1 [path2 [...]]'
|
32
|
+
puts 'path can be one or more files or a directory of .rcss files'
|
33
|
+
puts
|
34
|
+
puts 'Options:'
|
35
|
+
puts ' -w --write write .css file(s) at same path'
|
36
|
+
puts
|
37
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Wrap the code in a block like this, calling the "CascadingRubies.css" class method.
|
2
|
+
# This and the other block example should easily allow code outside the block
|
3
|
+
# to reference Kernel level methods, require additional code, etc.
|
4
|
+
|
5
|
+
CascadingRubies.css do
|
6
|
+
nav { color :red }
|
7
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# To see output: ruby bin/rcss example/example.rcss
|
2
|
+
|
3
|
+
require 'rubygems'; require 'sass'; Color = Sass::Constant::Color
|
4
|
+
|
5
|
+
bg_color = Color.parse('#eee')
|
6
|
+
footer_bg_color = bg_color.minus(Color.parse('#222'))
|
7
|
+
|
8
|
+
CascadingRubies.css do
|
9
|
+
|
10
|
+
header {
|
11
|
+
border '1px solid black'
|
12
|
+
background_color bg_color
|
13
|
+
padding '10px'
|
14
|
+
_search {
|
15
|
+
float :right
|
16
|
+
margin_right '20px'
|
17
|
+
a { text_decoration 'none' }
|
18
|
+
}
|
19
|
+
nav do |n|
|
20
|
+
n.margin '10px'
|
21
|
+
end
|
22
|
+
}
|
23
|
+
|
24
|
+
s('body>#content') {
|
25
|
+
margin '10px'
|
26
|
+
}
|
27
|
+
|
28
|
+
div.bordered {
|
29
|
+
border '1px solid #ccc'
|
30
|
+
}
|
31
|
+
|
32
|
+
a(:visited) { color 'purple' }
|
33
|
+
a(:link, :active) { color 'blue' }
|
34
|
+
|
35
|
+
footer {
|
36
|
+
background_color footer_bg_color
|
37
|
+
}
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
# Code in an rcss file does not strictly have to be wrapped in a block.
|
2
|
+
# Unlike the two block examples, the entire file is run through instance_eval,
|
3
|
+
# which makes using external libraries and other code a bit more difficult,
|
4
|
+
# but allows for a super clean file.
|
5
|
+
|
6
|
+
nav { color :red }
|
data/lib/blankslate.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#--
|
3
|
+
# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
|
4
|
+
# All rights reserved.
|
5
|
+
|
6
|
+
# Permission is granted for use, copying, modification, distribution,
|
7
|
+
# and distribution of modified versions of this work as long as the
|
8
|
+
# above copyright notice is included.
|
9
|
+
#++
|
10
|
+
|
11
|
+
######################################################################
|
12
|
+
# BlankSlate provides an abstract base class with no predefined
|
13
|
+
# methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
|
14
|
+
# BlankSlate is useful as a base class when writing classes that
|
15
|
+
# depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
|
16
|
+
#
|
17
|
+
class BlankSlate #:nodoc:
|
18
|
+
class << self
|
19
|
+
|
20
|
+
# Hide the method named +name+ in the BlankSlate class. Don't
|
21
|
+
# hide +instance_eval+ or any method beginning with "__".
|
22
|
+
def hide(name)
|
23
|
+
if instance_methods.include?(name.to_s) and
|
24
|
+
name !~ /^(__|instance_eval)/
|
25
|
+
@hidden_methods ||= {}
|
26
|
+
@hidden_methods[name.to_sym] = instance_method(name)
|
27
|
+
undef_method name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_hidden_method(name)
|
32
|
+
@hidden_methods ||= {}
|
33
|
+
@hidden_methods[name] || superclass.find_hidden_method(name)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Redefine a previously hidden method so that it may be called on a blank
|
37
|
+
# slate object.
|
38
|
+
def reveal(name)
|
39
|
+
bound_method = nil
|
40
|
+
unbound_method = find_hidden_method(name)
|
41
|
+
fail "Don't know how to reveal method '#{name}'" unless unbound_method
|
42
|
+
define_method(name) do |*args|
|
43
|
+
bound_method ||= unbound_method.bind(self)
|
44
|
+
bound_method.call(*args)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
instance_methods.each { |m| hide(m) }
|
50
|
+
end
|
51
|
+
|
52
|
+
######################################################################
|
53
|
+
# Since Ruby is very dynamic, methods added to the ancestors of
|
54
|
+
# BlankSlate <em>after BlankSlate is defined</em> will show up in the
|
55
|
+
# list of available BlankSlate methods. We handle this by defining a
|
56
|
+
# hook in the Object and Kernel classes that will hide any method
|
57
|
+
# defined after BlankSlate has been loaded.
|
58
|
+
#
|
59
|
+
module Kernel
|
60
|
+
class << self
|
61
|
+
alias_method :blank_slate_method_added, :method_added
|
62
|
+
|
63
|
+
# Detect method additions to Kernel and remove them in the
|
64
|
+
# BlankSlate class.
|
65
|
+
def method_added(name)
|
66
|
+
result = blank_slate_method_added(name)
|
67
|
+
return result if self != Kernel
|
68
|
+
BlankSlate.hide(name)
|
69
|
+
result
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
######################################################################
|
75
|
+
# Same as above, except in Object.
|
76
|
+
#
|
77
|
+
class Object
|
78
|
+
class << self
|
79
|
+
alias_method :blank_slate_method_added, :method_added
|
80
|
+
|
81
|
+
# Detect method additions to Object and remove them in the
|
82
|
+
# BlankSlate class.
|
83
|
+
def method_added(name)
|
84
|
+
result = blank_slate_method_added(name)
|
85
|
+
return result if self != Object
|
86
|
+
BlankSlate.hide(name)
|
87
|
+
result
|
88
|
+
end
|
89
|
+
|
90
|
+
def find_hidden_method(name)
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
######################################################################
|
97
|
+
# Also, modules included into Object need to be scanned and have their
|
98
|
+
# instance methods removed from blank slate. In theory, modules
|
99
|
+
# included into Kernel would have to be removed as well, but a
|
100
|
+
# "feature" of Ruby prevents late includes into modules from being
|
101
|
+
# exposed in the first place.
|
102
|
+
#
|
103
|
+
class Module
|
104
|
+
alias blankslate_original_append_features append_features
|
105
|
+
def append_features(mod)
|
106
|
+
result = blankslate_original_append_features(mod)
|
107
|
+
return result if mod != Object
|
108
|
+
instance_methods.each do |name|
|
109
|
+
BlankSlate.hide(name)
|
110
|
+
end
|
111
|
+
result
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# Ruby DSL for generating CSS.
|
2
|
+
# Copyright (c) 2009 Tim Morgan
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + '/blankslate'
|
5
|
+
|
6
|
+
class CascadingRubies < BlankSlate #:doc:
|
7
|
+
|
8
|
+
[:class, :respond_to?, :inspect].each { |m| reveal(m) rescue nil }
|
9
|
+
undef_method(:p)
|
10
|
+
|
11
|
+
# List of tags taken from http://www.w3schools.com/tags/default.asp
|
12
|
+
TAGS = %w(a abbr acronym address applet area b base basefont bdo big blockquote body br button caption center cite code col colgroup dd del dir div dfn dl dt em fieldset font form frame frameset h1 h2 h3 h4 h5 h6 head hr html i iframe img input ins isindex kbd label legend li link map meta noframes noscript object ol optgroup option p param pre q samp script select small span strike strong style sub sup table tbody td textarea tfoot th thead title tr tt u ul var xmp)
|
13
|
+
|
14
|
+
attr_reader :__selectors, :__css, :__context_name, :__has_children #:nodoc:
|
15
|
+
|
16
|
+
# Creates a CascadingRubies object. Normally, you should not pass in any arguments,
|
17
|
+
# except a block of style definitions. The block can optionally take a single argument --
|
18
|
+
# if so, the newly created object is yielded to the block, and instance_eval is not used.
|
19
|
+
#
|
20
|
+
# Each block in the style definitions is effectively going to trigger a new object to
|
21
|
+
# be created, so the block argument is optional <em>for each block</em>, and the determination to
|
22
|
+
# use instance_eval is subsequently <em>per block</em>:
|
23
|
+
# CascadingRubies.new do |css|
|
24
|
+
# css.header do |header|
|
25
|
+
# header.background_color '#ccc'
|
26
|
+
# end
|
27
|
+
# css.p {
|
28
|
+
# margin_bottom '15px'
|
29
|
+
# }
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# If you do not pass a block (or even if you do), you can execute additional style definitions
|
33
|
+
# by either calling methods directly on the object:
|
34
|
+
# obj = CascadingRubies.new
|
35
|
+
# obj.nav { margin '10px'; a { color :red } }
|
36
|
+
# or you can instance_eval another block of code:
|
37
|
+
# obj.instance_eval do
|
38
|
+
# p { margin_bottom '15px' }
|
39
|
+
# end
|
40
|
+
def initialize(context_name=nil, selectors=[], classed=false, &dsl_code)
|
41
|
+
@__context_name = context_name
|
42
|
+
@__css = ''
|
43
|
+
@__selectors = selectors
|
44
|
+
@__classed = classed # if true, method_missing calls are css class selectors -- not id selectors
|
45
|
+
if dsl_code.nil?
|
46
|
+
# do nothing
|
47
|
+
elsif dsl_code.arity == 1
|
48
|
+
dsl_code[self]
|
49
|
+
else
|
50
|
+
instance_eval(&dsl_code)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Create a new style "selector" without going through method_missing.
|
55
|
+
# Use this to create style definitions that cannot be created with the DSL directly, e.g.
|
56
|
+
# * <tt>s('##s') { color :blue }</tt> => <tt>##s { color: blue; }</tt>
|
57
|
+
# * <tt>s('div##search') { margin 0 }</tt> => <tt>div##search { margin: 0; }</tt>
|
58
|
+
def s(selector, *args, &dsl_code)
|
59
|
+
if args.any? and dsl_code.nil? # css rule
|
60
|
+
@__css << "#{selector}: #{args.join(' ')}; "
|
61
|
+
else
|
62
|
+
if args.any? and dsl_code # pseudo-class selector
|
63
|
+
selector_name = args.map do |arg|
|
64
|
+
[[@__context_name, selector].compact.join(' '), ':' + arg.to_s].join('')
|
65
|
+
end.join(', ')
|
66
|
+
else # regular selector
|
67
|
+
selector_name = [@__context_name, selector].compact.join(@__classed ? '' : ' ')
|
68
|
+
end
|
69
|
+
if @__bare and not @__bare.__has_children # e.g. li; a { }
|
70
|
+
selector_name = "#{@__bare.__context_name}, #{selector_name}"
|
71
|
+
end
|
72
|
+
if dsl_code
|
73
|
+
compiled = self.class.new(selector_name, &dsl_code)
|
74
|
+
@__selectors << "#{selector_name} { #{compiled.__css}}" unless compiled.__css.empty?
|
75
|
+
@__selectors += compiled.__selectors
|
76
|
+
@__bare = nil
|
77
|
+
else # e.g. div.something
|
78
|
+
@__bare = self.class.new(selector_name, @__selectors, true, &dsl_code)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def method_missing(method_name, *args, &dsl_code) #:nodoc:
|
84
|
+
@__has_children = true
|
85
|
+
selector = dsl_code ? __method_name_to_selector(method_name) : __dashify(method_name)
|
86
|
+
s(selector, *args, &dsl_code)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the complete CSS style definitions as a string.
|
90
|
+
def to_s
|
91
|
+
@__selectors.join("\n")
|
92
|
+
end
|
93
|
+
|
94
|
+
# Creates a CascadingRubies object, then opens the file and executes the style
|
95
|
+
# definitions in the context of the object. Returns the newly created object.
|
96
|
+
def self.open(path)
|
97
|
+
if (code = File.read(path)) =~ /^css do|CascadingRubies\.css do/
|
98
|
+
eval(code)
|
99
|
+
else
|
100
|
+
css(code)
|
101
|
+
end
|
102
|
+
@obj
|
103
|
+
end
|
104
|
+
|
105
|
+
# Wraps the creation of a CascadingRubies object and doing the instance_eval execution
|
106
|
+
# in a neat package for use inside rcss files. An rcss file can use the optional block
|
107
|
+
# syntax to wrap style definitions, allowing code to be run outside the instance_eval
|
108
|
+
# confines. Use this method in an .rcss file like so:
|
109
|
+
# css do
|
110
|
+
# a { color :red }
|
111
|
+
# end
|
112
|
+
# or:
|
113
|
+
# CascadingRubies.css do
|
114
|
+
# a { color :red }
|
115
|
+
# end
|
116
|
+
def self.css(code=nil, &block)
|
117
|
+
@obj = self.new
|
118
|
+
if code
|
119
|
+
@obj.instance_eval(code)
|
120
|
+
else
|
121
|
+
@obj.instance_eval(&block)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def __method_name_to_selector(method_name)
|
128
|
+
selector = method_name.to_s
|
129
|
+
if selector.sub!(/^_/, '.')
|
130
|
+
__dashify(selector)
|
131
|
+
elsif TAGS.include?(selector)
|
132
|
+
__dashify(selector)
|
133
|
+
elsif @__classed
|
134
|
+
'.' + __dashify(selector)
|
135
|
+
else
|
136
|
+
'#' + __dashify(selector)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def __dashify(name)
|
141
|
+
name.to_s.gsub(/_/, '-').downcase
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
$: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
2
|
+
|
3
|
+
require 'cascading_rubies'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
class TestCascadingRubies < Test::Unit::TestCase
|
7
|
+
|
8
|
+
# selectors
|
9
|
+
|
10
|
+
def test_id_selector
|
11
|
+
assert_produces "#nav { color: red; }", "nav { color :red }"
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_class_selector
|
15
|
+
assert_produces ".links { color: red; }", "_links { color :red }"
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_class_selector_with_tag
|
19
|
+
assert_produces "div.links { color: red; }", "div.links { color :red }"
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_tag_selector
|
23
|
+
assert_produces "div { color: red; }", "div { color :red }"
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_raw_selector
|
27
|
+
assert_produces "body>#content { color: red; }", "s('body>#content') { color :red }"
|
28
|
+
assert_produces "#s { color: red; }", "s('#s') { color :red }"
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_pseudo_class_selector
|
32
|
+
assert_produces "a:link { color: red; }", "a(:link) { color :red }"
|
33
|
+
assert_produces "a:hover, a:active { color: red; }", "a(:hover, :active) { color :red }"
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_multiple_comma_separated_selectors
|
37
|
+
assert_produces "#nav li, #nav a { color: red; }", "nav { li; a { color :red } }" # double
|
38
|
+
assert_produces "#nav li, #nav div, #nav a { color: red; }", "nav { li; div; a { color :red } }" # triple
|
39
|
+
output = <<-CSS
|
40
|
+
#nav li, #nav a:link, #nav a:visited { color: red; }
|
41
|
+
#nav a { color: green; }
|
42
|
+
CSS
|
43
|
+
code = <<-CSS
|
44
|
+
nav {
|
45
|
+
li; a(:link, :visited) {
|
46
|
+
color :red
|
47
|
+
}
|
48
|
+
a {
|
49
|
+
color :green
|
50
|
+
}
|
51
|
+
}
|
52
|
+
CSS
|
53
|
+
assert_produces output.gsub(/^\s+/, '').chomp, code # following selector isn't affected
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_empty_selectors_not_printed
|
57
|
+
assert_produces "#nav li { color: red; }", "nav { li { color :red } }"
|
58
|
+
end
|
59
|
+
|
60
|
+
# nesting
|
61
|
+
|
62
|
+
def test_nested_selectors
|
63
|
+
output = <<-CSS
|
64
|
+
#header { color: red; }
|
65
|
+
#header #nav { color: blue; }
|
66
|
+
#header #nav a { color: green; }
|
67
|
+
CSS
|
68
|
+
code = <<-CSS
|
69
|
+
header {
|
70
|
+
color :red
|
71
|
+
nav {
|
72
|
+
color :blue
|
73
|
+
a {
|
74
|
+
color :green
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
CSS
|
79
|
+
assert_produces output.gsub(/^\s+/, '').chomp, code
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_nested_pseudo_class_selector
|
83
|
+
output = <<-CSS
|
84
|
+
#header { color: red; }
|
85
|
+
#header a:link { color: blue; }
|
86
|
+
CSS
|
87
|
+
code = <<-CSS
|
88
|
+
header {
|
89
|
+
color :red
|
90
|
+
a(:link) {
|
91
|
+
color :blue
|
92
|
+
}
|
93
|
+
}
|
94
|
+
CSS
|
95
|
+
assert_produces output.gsub(/^\s+/, '').chomp, code
|
96
|
+
end
|
97
|
+
|
98
|
+
# rules
|
99
|
+
|
100
|
+
def test_underscores_replaced_with_dashes
|
101
|
+
assert_produces "a { text-decoration: none; }", "a { text_decoration 'none' }"
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_symbols_as_values
|
105
|
+
assert_produces "a { text-decoration: none; }", "a { text_decoration :none }"
|
106
|
+
end
|
107
|
+
|
108
|
+
# misc
|
109
|
+
|
110
|
+
def test_use_p_tag
|
111
|
+
assert_produces "p { color: red; }", "p { color :red }"
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_puts
|
115
|
+
assert_produces "a { color: red; }", "a { color :red }"
|
116
|
+
assert_nothing_raised do
|
117
|
+
puts @css
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_examples
|
122
|
+
Dir[File.dirname(__FILE__) + '/../example/*.rcss'].each do |path|
|
123
|
+
assert_nothing_raised do
|
124
|
+
@css = CascadingRubies.open(path)
|
125
|
+
end
|
126
|
+
assert @css.to_s != ''
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def assert_produces(output, code)
|
133
|
+
@css = CascadingRubies.new
|
134
|
+
@css.instance_eval(code)
|
135
|
+
assert_equal output, @css.to_s
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cascading_rubies
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tim Morgan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-21 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: tim@timmorgan.org
|
18
|
+
executables:
|
19
|
+
- rcss
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- README.rdoc
|
26
|
+
- lib/cascading_rubies.rb
|
27
|
+
- lib/blankslate.rb
|
28
|
+
- bin/rcss
|
29
|
+
- test/test_cascading_rubies.rb
|
30
|
+
- example/example.rcss
|
31
|
+
- example/block_example_1.rcss
|
32
|
+
- example/block_example_2.rcss
|
33
|
+
- example/no_block_example.rcss
|
34
|
+
has_rdoc: true
|
35
|
+
homepage: http://github.com/seven1m/cascading_rubies
|
36
|
+
licenses: []
|
37
|
+
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
requirements: []
|
56
|
+
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.3.5
|
59
|
+
signing_key:
|
60
|
+
specification_version: 3
|
61
|
+
summary: Ruby DSL for generating CSS.
|
62
|
+
test_files: []
|
63
|
+
|