ionfish-stylish 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +8 -8
- data/VERSION.yml +2 -2
- data/lib/stylish/core.rb +195 -51
- data/lib/stylish/generate.rb +139 -6
- data/lib/stylish/image.rb +32 -0
- data/lib/stylish/tree.rb +12 -13
- data/lib/stylish.rb +1 -0
- data/test/background_test.rb +23 -16
- data/test/declaration_test.rb +12 -8
- data/test/declarations_test.rb +4 -2
- data/test/generate_test.rb +29 -0
- data/test/image_test.rb +17 -0
- data/test/rule_test.rb +2 -2
- data/test/stylesheet_test.rb +1 -1
- data/test/tree_test.rb +9 -5
- metadata +5 -2
data/README.md
CHANGED
@@ -9,17 +9,17 @@ Creating stylesheets
|
|
9
9
|
--------------------
|
10
10
|
|
11
11
|
style = Stylish.generate do
|
12
|
-
rule ".header", background
|
12
|
+
rule ".header", :background => {:color => "teal", :image => "header.png"}
|
13
13
|
rule ".content" do
|
14
|
-
|
15
|
-
|
14
|
+
h2 :font_size => "2em"
|
15
|
+
p :margin => "0 0 1em 0"
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
Calling the stylesheet's `to_s` method would produce the following
|
20
20
|
<abbr title="Cascading Stylesheets">CSS</abbr> code.
|
21
21
|
|
22
|
-
.header {background-color
|
22
|
+
.header {background-color:teal; background-image:url('header.png');}
|
23
23
|
.content h2 {font-size:2em;}
|
24
24
|
.content p {margin:0 0 1em 0;}
|
25
25
|
|
@@ -34,15 +34,15 @@ test suite passes under Ruby 1.9; the usual caveats about this apply.
|
|
34
34
|
Future considerations
|
35
35
|
---------------------
|
36
36
|
|
37
|
-
*
|
38
|
-
|
37
|
+
* Add a Border class to complement Background.
|
38
|
+
* Add better support for CSS3 properties to Background.
|
39
39
|
* Add a native mapping construct to allow symbol lookup for alternate
|
40
40
|
stylesheets.
|
41
41
|
* Change stylesheet generation to a two-step process where the tree structure
|
42
42
|
is generated in the first step and symbols (for example, those employed by
|
43
43
|
a mapping construct) are evaluated the second.
|
44
|
-
* Fundamental objects like percentages
|
45
|
-
|
44
|
+
* Fundamental objects like percentages need their own classes rather than
|
45
|
+
being dealt with in an ad-hoc manner by higher-level objects.
|
46
46
|
* Add a parser so CSS can be read as well as written.
|
47
47
|
|
48
48
|
|
data/VERSION.yml
CHANGED
data/lib/stylish/core.rb
CHANGED
@@ -18,14 +18,49 @@ module Stylish
|
|
18
18
|
:col, :tbody, :thead, :tfoot, :tr, :td, :th, :form,
|
19
19
|
:fieldset, :label, :input, :button, :select, :datalist,
|
20
20
|
:optgroup, :option, :textarea, :output, :details, :datagrid,
|
21
|
-
:command, :bb, :menu, :legend, :div
|
21
|
+
:command, :bb, :menu, :legend, :div, :h1, :h2, :h3, :h4,
|
22
|
+
:h5, :h6]
|
22
23
|
|
24
|
+
# Rule objects represent CSS rules, and serialise to them. They possess one
|
25
|
+
# or more selectors, and zero or more declarations. In addition to their
|
26
|
+
# importance as the major building-blocks of stylesheets, they act as the
|
27
|
+
# leaves of Stylish's stylesheet trees.
|
28
|
+
#
|
29
|
+
# Their serialisation is controllable to some extent by altering their
|
30
|
+
# format attribute; this should never make them lose information when they
|
31
|
+
# are serialised.
|
32
|
+
#
|
33
|
+
# E.g., by changing its format to "%s {\n %s\n}" a rule that would
|
34
|
+
# normally be serialised thus:
|
35
|
+
#
|
36
|
+
# body {font-size:81%;}
|
37
|
+
#
|
38
|
+
# Would instead be serialised like this:
|
39
|
+
#
|
40
|
+
# body {
|
41
|
+
# font-size:81%;
|
42
|
+
# }
|
43
|
+
#
|
23
44
|
class Rule
|
24
45
|
include Formattable, Tree::Leaf
|
25
46
|
|
26
47
|
attr_reader :selectors, :declarations
|
27
48
|
|
28
|
-
|
49
|
+
# Every Rule must have at least one selector, but may have any number of
|
50
|
+
# declarations. Empty rules are often used in stylesheets to indicate
|
51
|
+
# particular combinations of selectors which may be used to produce
|
52
|
+
# particular effects.
|
53
|
+
#
|
54
|
+
# In Stylish, of course, a Rule's declarations may be amended after its
|
55
|
+
# creation:
|
56
|
+
#
|
57
|
+
# rule = Stylish::Rule.new([Stylish::Selector.new("body")])
|
58
|
+
# rule.declarations << Stylish::Declaration.new("font-weight", "bold")
|
59
|
+
# rule.to_s # => "body {font-weight:bold;}"
|
60
|
+
#
|
61
|
+
# This makes Rule objects a very flexible foundation for the higher-level
|
62
|
+
# data structures and APIs in Stylish.
|
63
|
+
def initialize(selectors, declarations)
|
29
64
|
accept_format(/^\s*%s\s*\{\s*%s\s*\}\s*$/m, "%s {%s}")
|
30
65
|
|
31
66
|
@selectors = selectors.inject(Selectors.new) do |ss, s|
|
@@ -39,17 +74,62 @@ module Stylish
|
|
39
74
|
|
40
75
|
# Serialise the rule to valid CSS code.
|
41
76
|
def to_s(scope = "")
|
42
|
-
selectors
|
43
|
-
(scope.empty? ? "" : scope + " ") + selector.to_s
|
44
|
-
end
|
45
|
-
|
46
|
-
sprintf(@format, selectors.join, @declarations.join)
|
77
|
+
sprintf(@format, selectors.join(scope), @declarations.join)
|
47
78
|
end
|
48
79
|
end
|
49
80
|
|
81
|
+
# Comment objects form the other concrete leaves of selector trees, and allow
|
82
|
+
# stylesheets to be annotated at any point (outside Rules). Their
|
83
|
+
# serialisation format follows the well-known JavaDoc and PHPDoc style, with
|
84
|
+
# a header, several lines of notes, and key-value information.
|
85
|
+
#
|
86
|
+
# This format is not amendable; those desirous of their own formatting would
|
87
|
+
# be better served by creating their own Comment class, such as the following
|
88
|
+
# more basic one:
|
89
|
+
#
|
90
|
+
# module Stylish
|
91
|
+
# module Extensions
|
92
|
+
#
|
93
|
+
# class Comment
|
94
|
+
# include Tree::Leaf
|
95
|
+
#
|
96
|
+
# attr_accessor :content
|
97
|
+
#
|
98
|
+
# def initialize(content)
|
99
|
+
# @content = content
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# def to_s(scope = "")
|
103
|
+
# "/* " + content.to_s + " */"
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# end
|
108
|
+
# end
|
109
|
+
#
|
50
110
|
class Comment
|
111
|
+
include Tree::Leaf
|
112
|
+
|
51
113
|
attr_reader :header, :lines, :metadata
|
52
114
|
|
115
|
+
# Each Comment can have a header, additional lines of text content (each
|
116
|
+
# provided as its own argument), and key-value metadata passed in as a Ruby
|
117
|
+
# Hash object.
|
118
|
+
#
|
119
|
+
# comment = Comment.new("My wonderful comment",
|
120
|
+
# "It has several lines of insightful notes,",
|
121
|
+
# "filled with wisdom and the knowledge of ages.",
|
122
|
+
# {:author => "Some Egotist"})
|
123
|
+
#
|
124
|
+
# comment.to_s # => /**
|
125
|
+
# # * My wonderful comment
|
126
|
+
# # *
|
127
|
+
# # * It has several lines of insightful notes,
|
128
|
+
# # * filled with wisdom and the knowledge of ages.
|
129
|
+
# # *
|
130
|
+
# # * @author Some Egotist
|
131
|
+
# # */
|
132
|
+
#
|
53
133
|
def initialize(*args)
|
54
134
|
@lines, @metadata = [], {}
|
55
135
|
|
@@ -61,83 +141,129 @@ module Stylish
|
|
61
141
|
@header = arg
|
62
142
|
end
|
63
143
|
elsif arg.is_a? Hash
|
64
|
-
@metadata.merge!
|
144
|
+
@metadata.merge! arg
|
65
145
|
end
|
66
146
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
147
|
+
end
|
148
|
+
|
149
|
+
# As Comment objects are the leaves of selector trees, they must implement
|
150
|
+
# the serialisation API of those trees, and thus the #to_s method has a
|
151
|
+
# scope argument, which is in practice discarded when the serialisation of
|
152
|
+
# the comment occurs.
|
153
|
+
def to_s(scope = "")
|
154
|
+
if @lines.empty? && @metadata.empty?
|
155
|
+
sprintf("/**\n * %s\n */", @header)
|
156
|
+
else
|
157
|
+
header = sprintf(" * %s", @header) unless @header.nil?
|
158
|
+
lines = @lines.map {|l| ' * ' + l }.join("\n") unless @lines.empty?
|
159
|
+
metadata = @metadata.to_a.map {|name, value|
|
160
|
+
sprintf(" * @%s %s", name.to_s, value.to_s)
|
161
|
+
}.join("\n") unless @metadata.empty?
|
162
|
+
|
163
|
+
sprintf("/**\n%s\n */", [
|
164
|
+
header || nil,
|
165
|
+
lines || nil,
|
166
|
+
metadata || nil
|
167
|
+
].compact.join("\n *\n"))
|
84
168
|
end
|
85
169
|
end
|
86
170
|
end
|
87
171
|
|
172
|
+
# Selector objects are just string containers, which when serialised are
|
173
|
+
# passed the scope in which they are situated. The Selector class is one of
|
174
|
+
# Stylish's most basic units, and are generally used only by internal APIs
|
175
|
+
# when constructing Rule objects, rather than by end users (although nothing
|
176
|
+
# prevents this; it is merely inconvenient to do so).
|
88
177
|
class Selector
|
89
178
|
|
179
|
+
# Selectors are immutable once created; the value of a given Selector must
|
180
|
+
# be set when the object is created.
|
90
181
|
def initialize(str)
|
91
182
|
@selector = str.to_s
|
92
183
|
end
|
93
184
|
|
94
|
-
|
95
|
-
|
185
|
+
# Each Rule possesses one or more Selectors. Rules are often placed in
|
186
|
+
# selector trees, and thus when serialised a Selector must be made aware
|
187
|
+
# of the context or scope in which it is being serialised.
|
188
|
+
#
|
189
|
+
# Selector.new("p").to_s("body") # => "body p"
|
190
|
+
#
|
191
|
+
# The Selector class is also used internally by the Tree::SelectorScope
|
192
|
+
# class, to store its scope value.
|
193
|
+
def to_s(scope = "")
|
194
|
+
(scope.empty? ? "" : scope + " ") + @selector.to_s
|
96
195
|
end
|
97
196
|
end
|
98
197
|
|
198
|
+
# Selectors objects are simply used to group Selector objects for more
|
199
|
+
# convenient storage and serialisation.
|
99
200
|
class Selectors < Array
|
100
201
|
include Formattable
|
101
202
|
|
203
|
+
# Since a group of Selectors is just a specialised kind of array, all that
|
204
|
+
# is done in its initialiser, regardless of arguments, is to set the
|
205
|
+
# default serialisation format.
|
102
206
|
def initialize(*args)
|
103
207
|
accept_format(/^\s*,\s*$/m, ", ")
|
104
208
|
super
|
105
209
|
end
|
106
210
|
|
107
|
-
|
108
|
-
|
211
|
+
# The join method overrides the superclass' method in order to always use a
|
212
|
+
# specific separator, and so that the scope that the selectors are being
|
213
|
+
# used in can be passed through when Rules etc. are serialised.
|
214
|
+
def join(scope = "")
|
215
|
+
self.inject("") do |ss, s|
|
216
|
+
(ss.empty? ? "" : ss + self.format) + s.to_s(scope)
|
217
|
+
end
|
109
218
|
end
|
110
219
|
|
111
|
-
|
112
|
-
|
220
|
+
# The to_s method alternative way of calling the join method.
|
221
|
+
def to_s(scope = "")
|
222
|
+
self.join(scope)
|
113
223
|
end
|
114
224
|
end
|
115
225
|
|
226
|
+
# Each Rule may have one or more Declaration objects, and usually has more
|
227
|
+
# than one. In a sense, declarations are the business end of stylesheets:
|
228
|
+
# they are where the set of elements specified by a rule's selectors are
|
229
|
+
# given their various attributes.
|
116
230
|
class Declaration
|
117
231
|
include Formattable
|
118
232
|
|
119
233
|
attr_accessor :value
|
120
|
-
|
121
|
-
|
234
|
+
|
235
|
+
# Each Declaration has a property name and a value.
|
236
|
+
def initialize(name, value)
|
122
237
|
accept_format(/^\s*%s\s*:\s*%s;\s*$/m, "%s:%s;")
|
123
|
-
self.value =
|
124
|
-
self.
|
238
|
+
self.value = value
|
239
|
+
self.name = name
|
125
240
|
end
|
126
241
|
|
127
|
-
|
128
|
-
|
242
|
+
# The property name of the Declaration.
|
243
|
+
def name
|
244
|
+
@property_name
|
129
245
|
end
|
130
246
|
|
131
|
-
|
132
|
-
|
247
|
+
# Property names are CSS identifiers.
|
248
|
+
def name=(name)
|
249
|
+
@property_name = name.to_s
|
133
250
|
end
|
134
251
|
|
135
|
-
|
136
|
-
|
252
|
+
# The value of the Declaration's property.
|
253
|
+
def value=(value)
|
254
|
+
@value = value
|
137
255
|
end
|
138
256
|
|
257
|
+
# Serialising a declaration produces a name-value pair separated by a colon
|
258
|
+
# and terminating with a semicolon, for instance
|
259
|
+
#
|
260
|
+
# declaration = Declaration.new("font-style", "italic")
|
261
|
+
# declaration.to_s # => "font-style:italic;"
|
262
|
+
#
|
263
|
+
# Since the formatting can be adjusted via the #format= accessor, the exact
|
264
|
+
# spacing of the declaration can be controlled if desired.
|
139
265
|
def to_s
|
140
|
-
sprintf(@format, @
|
266
|
+
sprintf(@format, @property_name.to_s, @value.to_s)
|
141
267
|
end
|
142
268
|
end
|
143
269
|
|
@@ -171,6 +297,24 @@ module Stylish
|
|
171
297
|
end
|
172
298
|
end
|
173
299
|
|
300
|
+
# The Background class is a specialised kind of Declaration, geared towards
|
301
|
+
# dealing with the oddities of the background family of declarations, which
|
302
|
+
# can exist in both long- and shorthand forms.
|
303
|
+
#
|
304
|
+
# For example, these longhand background declarations
|
305
|
+
#
|
306
|
+
# background-color: #999;
|
307
|
+
# background-image: url('bg.png');
|
308
|
+
# background-repeat: repeat-x;
|
309
|
+
#
|
310
|
+
# could be compressed into a single shorthand declaration
|
311
|
+
#
|
312
|
+
# background: #999 url('bg.png') repeat-x;
|
313
|
+
#
|
314
|
+
# The Background class allows for easy conversion between these forms. It
|
315
|
+
# defaults to the longhand versions, allowing rules with stronger selector
|
316
|
+
# weighting to only override specific parts of other rules' background
|
317
|
+
# declarations.
|
174
318
|
class Background < Declaration
|
175
319
|
attr_reader :color,
|
176
320
|
:image,
|
@@ -192,7 +336,7 @@ module Stylish
|
|
192
336
|
HORIZONTAL_POSITIONS = ["left", "center", "right"]
|
193
337
|
VERTICAL_POSITIONS = ["top", "center", "bottom"]
|
194
338
|
|
195
|
-
# Create a new Background object
|
339
|
+
# Create a new Background object with the specified properties.
|
196
340
|
def initialize(options)
|
197
341
|
accept_format(/^\s*%s\s*:\s*%s;\s*$/m, "%s:%s;")
|
198
342
|
self.value = options
|
@@ -206,7 +350,7 @@ module Stylish
|
|
206
350
|
|
207
351
|
# Set the background image.
|
208
352
|
def image=(path)
|
209
|
-
@image = path if path.is_a?(String)
|
353
|
+
@image = Image.new(path) if path.is_a?(String)
|
210
354
|
end
|
211
355
|
|
212
356
|
# Set the background repeat.
|
@@ -228,11 +372,11 @@ module Stylish
|
|
228
372
|
@attachment = val if ATTACHMENT_VALUES.include?(val)
|
229
373
|
end
|
230
374
|
|
231
|
-
# Set this to true to generate a
|
375
|
+
# Set this to true to generate a shorthand declaration, e.g.
|
232
376
|
#
|
233
377
|
# background:#ccc url('bg.png') no-repeat 0 0;
|
234
378
|
#
|
235
|
-
# As opposed to the
|
379
|
+
# As opposed to the longhand version:
|
236
380
|
#
|
237
381
|
# background-color:#ccc; background-image:url('bg.png');
|
238
382
|
# background-repeat:no-repeat; background-position:0 0;
|
@@ -241,19 +385,19 @@ module Stylish
|
|
241
385
|
@compressed = val == true || nil
|
242
386
|
end
|
243
387
|
|
244
|
-
# Override Declaration#
|
388
|
+
# Override Declaration#name, since it's not compatible with the
|
245
389
|
# internals of this class.
|
246
|
-
def
|
390
|
+
def name
|
247
391
|
PROPERTIES.reject {|n, p| p.nil? }.map {|n, p|
|
248
392
|
value = self.send(n)
|
249
393
|
p.to_s unless value.nil?
|
250
394
|
}.compact
|
251
395
|
end
|
252
396
|
|
253
|
-
# Override Declaration#
|
397
|
+
# Override Declaration#name=, since it's not compatible with the
|
254
398
|
# internals of this class.
|
255
|
-
def
|
256
|
-
raise NoMethodError, "
|
399
|
+
def name=(val)
|
400
|
+
raise NoMethodError, "name= is not defined for Background."
|
257
401
|
end
|
258
402
|
|
259
403
|
# Override Declaration#value, since it's not compatible with the internals
|
data/lib/stylish/generate.rb
CHANGED
@@ -1,13 +1,88 @@
|
|
1
1
|
module Stylish
|
2
2
|
|
3
|
+
# The generate method is the starting point for the stylesheet generation
|
4
|
+
# DSL. The method should be passed a block, and the various DSL methods then
|
5
|
+
# called within that context, e.g. as follows:
|
6
|
+
#
|
7
|
+
# style = Stylish.generate do
|
8
|
+
# body :margin => "1em"
|
9
|
+
# rule ".error" do
|
10
|
+
# p :color => "#f00"
|
11
|
+
# em :font_weight => "bold"
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# When serialised, the generated stylesheet would look like this:
|
16
|
+
#
|
17
|
+
# body {margin:1em;}
|
18
|
+
# .error p {color:#f00;}
|
19
|
+
# .error em {font-weight:bold;}
|
20
|
+
#
|
21
|
+
# Further examples can be found in the example/ directory and in the
|
22
|
+
# +GenerateTest+ class.
|
23
|
+
#
|
24
|
+
# * example/tarski.rb
|
25
|
+
# * test/generate_test.rb
|
26
|
+
#
|
27
|
+
# The options argument is currently unused.
|
3
28
|
def self.generate(options = {}, &block)
|
4
29
|
dsl = Generate::Description.new
|
5
30
|
dsl.instance_eval(&block)
|
6
31
|
dsl.node
|
7
32
|
end
|
8
33
|
|
34
|
+
# The +Generate+ module is a general namespace for the stylesheet generation
|
35
|
+
# DSL components. It contains various modules and classes which together are
|
36
|
+
# used to generate the intermediate data-structures of selector trees, and
|
37
|
+
# ultimately CSS code.
|
9
38
|
module Generate
|
10
39
|
|
40
|
+
# The +parse_declarations+ method deals with three things: turning the
|
41
|
+
# hashes passed to the +Description#rule+ method into +Declarations+
|
42
|
+
# objects; renaming property names to use dashes rather than underscores
|
43
|
+
# (since the former are correct CSS but are invalid Ruby, at least when
|
44
|
+
# quotation marks are not used to delimit the symbol); and handling special
|
45
|
+
# cases.
|
46
|
+
#
|
47
|
+
# There are currently two main special cases: colours and backgrounds. Each
|
48
|
+
# of these property types have their own +Stylish+ classes, and thus to
|
49
|
+
# create a rich datastructure which takes advantage of these powerful
|
50
|
+
# classes the declarations passed to the stylesheet generation DSL need to
|
51
|
+
# be parsed with this in mind.
|
52
|
+
def self.parse_declarations(declarations)
|
53
|
+
declarations.to_a.inject(Declarations.new) do |ds, declaration|
|
54
|
+
key, value = declaration
|
55
|
+
key = key.to_s.sub("_", "-").to_sym
|
56
|
+
|
57
|
+
if key == :background
|
58
|
+
declaration = Background.new(value)
|
59
|
+
elsif key == :color
|
60
|
+
declaration = Declaration.new("color", Color.new(value))
|
61
|
+
else
|
62
|
+
declaration = Declaration.new(key, value)
|
63
|
+
end
|
64
|
+
|
65
|
+
ds << declaration
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Often the selector associated with a call to the rule method would simply
|
70
|
+
# be a single HTML element name. This has been factored out by adding
|
71
|
+
# methods to the Description DSL class corresponding to all the HTML
|
72
|
+
# elements. Consider the following (contrived) example.
|
73
|
+
#
|
74
|
+
# Stylish.generate do
|
75
|
+
# body do
|
76
|
+
# div do
|
77
|
+
# a :font_weight => bold
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# Which would then serialise to the following:
|
83
|
+
#
|
84
|
+
# body div a {font-weight:bold;}
|
85
|
+
#
|
11
86
|
module ElementMethods
|
12
87
|
HTML_ELEMENTS.each do |element|
|
13
88
|
next if self.respond_to?(element)
|
@@ -20,40 +95,98 @@ module Stylish
|
|
20
95
|
end
|
21
96
|
end
|
22
97
|
|
98
|
+
# Description objects are the core of the stylesheet generation DSL. Blocks
|
99
|
+
# passed into the +Stylish#generate_ method are executed in the context of
|
100
|
+
# a +Description+ object. All the DSL methods, +rule+, +comment+, and all
|
101
|
+
# the HTML element methods are all methods on instances of the
|
102
|
+
# +Description+ class.
|
23
103
|
class Description
|
24
104
|
include ElementMethods
|
25
105
|
|
26
|
-
|
106
|
+
attr_reader :node
|
27
107
|
|
108
|
+
# +Description+ instances are associated with a particular node in a
|
109
|
+
# selector tree; if no node is assigned to them on creation, they
|
110
|
+
# associate with a new root, i.e. a +Stylesheet+ object.
|
28
111
|
def initialize(context = nil)
|
29
112
|
@node = context || Stylesheet.new
|
30
113
|
end
|
31
114
|
|
115
|
+
# The +rule+ method is the most general and powerful part of the DSL. It
|
116
|
+
# can be used to add a single +Rule+, with attendant declarations, or to
|
117
|
+
# create a selector namespace within which further rules or namespaces
|
118
|
+
# can be added.
|
119
|
+
#
|
120
|
+
# The nested structure created is precisely that of a selector tree; the
|
121
|
+
# rule method adds +Rule+ leaves and +SelectorScope+ nodes to the tree,
|
122
|
+
# whose root is a +Stylesheet+ object.
|
123
|
+
#
|
124
|
+
# Either a set of declarations or a block must be passed to the method;
|
125
|
+
# failing to do so will result in an early return which creates no
|
126
|
+
# additional objects. The following example demonstrates the various ways
|
127
|
+
# in which the method can be used:
|
128
|
+
#
|
129
|
+
# Stylish.generate do
|
130
|
+
# rule ".section", :margin_bottom => "10px"
|
131
|
+
#
|
132
|
+
# rule "form" do
|
133
|
+
# rule ".notice", :color => "#00f"
|
134
|
+
# rule "input[type=submit]", :font_weight => "normal"
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# rule "body", :padding => "0.5em" do
|
138
|
+
# rule "div", :margin => "2px"
|
139
|
+
# end
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# This would produce a stylesheet with the following rules:
|
143
|
+
#
|
144
|
+
# .section {margin-bottom:10px;}
|
145
|
+
#
|
146
|
+
# form .notice {color:#00f;}
|
147
|
+
# form input[type=submit] {font-weight:normal;}
|
148
|
+
#
|
149
|
+
# body {padding:0.5em;}
|
150
|
+
# body div {margin:2px;}
|
151
|
+
#
|
152
|
+
# Usefully, a call to #rule which passes in both declarations and a block
|
153
|
+
# will produce a single +Rule+ with the declarations attached, then
|
154
|
+
# create a new +SelectorScope+ node with the same selectors and execute
|
155
|
+
# the block in that context.
|
156
|
+
#
|
157
|
+
# If several selectors and a block are passed to +rule+, new
|
158
|
+
# +SelectorScope+ nodes will be created for each selector and the block
|
159
|
+
# will be executed in all the contexts created, not just one.
|
32
160
|
def rule(selectors, declarations = nil, &block)
|
33
161
|
return unless declarations || block
|
34
162
|
|
35
163
|
selectors = [selectors] unless selectors.is_a?(Array)
|
36
164
|
selectors.map! {|s| Selector.new(s) }
|
37
165
|
|
38
|
-
declarations = declarations
|
39
|
-
Declaration.new(p.to_s.sub("_", "-"), v)
|
40
|
-
end
|
166
|
+
declarations = Generate.parse_declarations(declarations)
|
41
167
|
|
42
168
|
unless block
|
43
169
|
@node << Rule.new(selectors, declarations)
|
44
170
|
else
|
45
171
|
selectors.each do |selector|
|
46
172
|
unless declarations.empty?
|
47
|
-
@node << Rule.new(selector, declarations)
|
173
|
+
@node << Rule.new([selector], declarations)
|
48
174
|
end
|
49
175
|
|
50
|
-
new_node = Tree::SelectorScope.new(selector
|
176
|
+
new_node = Tree::SelectorScope.new(selector)
|
51
177
|
@node << new_node
|
52
178
|
|
53
179
|
self.class.new(new_node).instance_eval(&block)
|
54
180
|
end
|
55
181
|
end
|
56
182
|
end
|
183
|
+
|
184
|
+
# Adds a +Comment+ object to the current node. This method simply hands
|
185
|
+
# its arguments off to the +Comment+ initialiser, and hence implements
|
186
|
+
# its API precisely.
|
187
|
+
def comment(*args)
|
188
|
+
@node << Comment.new(*args)
|
189
|
+
end
|
57
190
|
end
|
58
191
|
|
59
192
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Stylish
|
2
|
+
|
3
|
+
# Instances of the Image class are used to represent paths to images,
|
4
|
+
# generally background images.
|
5
|
+
class Image
|
6
|
+
include Formattable
|
7
|
+
|
8
|
+
attr_accessor :path
|
9
|
+
|
10
|
+
# Image instances are serialised to URI values. The path to the image file
|
11
|
+
# can be surrounded by either single quotes, double quotes or neither;
|
12
|
+
# single quotes are the default in Stylish.
|
13
|
+
def initialize(path)
|
14
|
+
accept_format(/^url\(\s*('|")?%s\1\s*\)$/, "url('%s')")
|
15
|
+
@path = path
|
16
|
+
end
|
17
|
+
|
18
|
+
# Serialising Image objects to a string produces the URI values seen in
|
19
|
+
# background-image declarations, e.g.
|
20
|
+
#
|
21
|
+
# image = Image.new("test.png")
|
22
|
+
# image.to_s # => "url('test.png')"
|
23
|
+
#
|
24
|
+
# background = Stylish::Background.new(:image => "test.png")
|
25
|
+
# background.to_s # => "background-image:url('test.png');"
|
26
|
+
#
|
27
|
+
def to_s
|
28
|
+
sprintf(@format, path.to_s)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
data/lib/stylish/tree.rb
CHANGED
@@ -1,11 +1,5 @@
|
|
1
1
|
module Stylish
|
2
2
|
|
3
|
-
def self.generate(options = {}, &block)
|
4
|
-
dsl = Tree::Description.new
|
5
|
-
dsl.instance_eval(&block)
|
6
|
-
dsl.node
|
7
|
-
end
|
8
|
-
|
9
3
|
# The objects defined in the Tree module allow for the creation of nested
|
10
4
|
# trees of selector scopes. These intermediate data structures can be used to
|
11
5
|
# help factor out some of the repetitiveness of CSS code, and can be easily
|
@@ -51,7 +45,7 @@ module Stylish
|
|
51
45
|
def initialize(selector)
|
52
46
|
accept_format(/\s*/m, "\n")
|
53
47
|
|
54
|
-
@scope = selector
|
48
|
+
@scope = Selector.new(selector)
|
55
49
|
@nodes = []
|
56
50
|
end
|
57
51
|
|
@@ -92,7 +86,7 @@ module Stylish
|
|
92
86
|
# Recursively serialise the selector tree.
|
93
87
|
def to_s(scope = "")
|
94
88
|
return "" if @nodes.empty?
|
95
|
-
scope = scope.empty? ? @scope : scope + " " + @scope
|
89
|
+
scope = scope.empty? ? @scope.to_s : scope + " " + @scope.to_s
|
96
90
|
@nodes.map {|node| node.to_s(scope) }.join(@format)
|
97
91
|
end
|
98
92
|
|
@@ -106,17 +100,22 @@ module Stylish
|
|
106
100
|
leaves(Rule)
|
107
101
|
end
|
108
102
|
|
103
|
+
# Recursively return all the comments in the selector tree.
|
104
|
+
def comments
|
105
|
+
leaves(Comment)
|
106
|
+
end
|
107
|
+
|
109
108
|
# Recursively return all the leaves of any, or a given type in a selector
|
110
109
|
# tree.
|
111
110
|
def leaves(type = nil)
|
112
|
-
@nodes.inject([]) do |
|
111
|
+
@nodes.inject([]) do |leaves, node|
|
113
112
|
if node.leaf?
|
114
|
-
|
115
|
-
|
116
|
-
|
113
|
+
leaves << node if type.nil? || node.is_a?(type)
|
114
|
+
else
|
115
|
+
leaves.concat(node.leaves(type))
|
117
116
|
end
|
118
117
|
|
119
|
-
|
118
|
+
leaves
|
120
119
|
end
|
121
120
|
end
|
122
121
|
end
|
data/lib/stylish.rb
CHANGED
data/test/background_test.rb
CHANGED
@@ -11,19 +11,17 @@ class BackgroundTest < Test::Unit::TestCase
|
|
11
11
|
|
12
12
|
def test_valid_background_colors
|
13
13
|
assert_equal("#ccc", @composite.color.to_hex)
|
14
|
-
assert_equal("black", Stylish::Background.new(:color =>
|
14
|
+
assert_equal("black", Stylish::Background.new(:color => "black").color.to_s)
|
15
15
|
end
|
16
16
|
|
17
17
|
def test_background_transparencies
|
18
|
-
assert_equal([0, 0, 0, 0], Stylish::Background.new(:color =>
|
18
|
+
assert_equal([0, 0, 0, 0], Stylish::Background.new(:color => "transparent").color.value)
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_valid_background_images
|
22
|
-
assert_equal("images/test.png", @composite.image)
|
23
|
-
assert_equal("background.jpg",
|
24
|
-
|
25
|
-
assert_not_nil(Stylish::Background.new(:image => file))
|
26
|
-
end
|
22
|
+
assert_equal("images/test.png", @composite.image.path)
|
23
|
+
assert_equal("background.jpg",
|
24
|
+
Stylish::Background.new(:image => "background.jpg").image.path)
|
27
25
|
end
|
28
26
|
|
29
27
|
def test_valid_background_repeats
|
@@ -42,12 +40,13 @@ class BackgroundTest < Test::Unit::TestCase
|
|
42
40
|
|
43
41
|
def test_valid_background_attachments
|
44
42
|
assert_equal("scroll", @composite.attachment)
|
45
|
-
assert_equal("fixed",
|
43
|
+
assert_equal("fixed",
|
44
|
+
Stylish::Background.new(:attachment => "fixed").attachment)
|
46
45
|
end
|
47
46
|
|
48
47
|
def test_invalid_background_colors
|
49
48
|
assert_raise ArgumentError do
|
50
|
-
Stylish::Background.new(:color =>
|
49
|
+
Stylish::Background.new(:color => "sky-blue")
|
51
50
|
end
|
52
51
|
end
|
53
52
|
|
@@ -78,36 +77,44 @@ class BackgroundTest < Test::Unit::TestCase
|
|
78
77
|
|
79
78
|
def test_declaration_property
|
80
79
|
assert_equal(["background-color", "background-repeat"],
|
81
|
-
Stylish::Background.new(:color => "red", :repeat => "no-repeat").
|
80
|
+
Stylish::Background.new(:color => "red", :repeat => "no-repeat").name)
|
82
81
|
end
|
83
82
|
|
84
83
|
def test_declaration_property_assignment
|
85
|
-
background = Stylish::Background.new(:color => "red",
|
84
|
+
background = Stylish::Background.new(:color => "red",
|
85
|
+
:repeat => "no-repeat")
|
86
86
|
|
87
87
|
assert_raise(NoMethodError) do
|
88
|
-
background.
|
88
|
+
background.name = "display"
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
92
|
def test_declaration_value
|
93
|
-
background = Stylish::Background.new(:color => "red",
|
93
|
+
background = Stylish::Background.new(:color => "red",
|
94
|
+
:repeat => "no-repeat")
|
94
95
|
|
95
96
|
assert_equal("no-repeat", background.value[1])
|
96
97
|
assert_equal("red", background.value[0].to_s)
|
97
98
|
end
|
98
99
|
|
99
100
|
def test_declaration_value_assignment
|
100
|
-
background = Stylish::Background.new(:color => "red"
|
101
|
+
background = Stylish::Background.new(:color => "red")
|
101
102
|
background.value = {:image => "mondrian.jpg"}
|
102
103
|
|
103
|
-
assert_equal("mondrian.jpg", background.image)
|
104
|
+
assert_equal("mondrian.jpg", background.image.path)
|
104
105
|
end
|
105
106
|
|
106
107
|
def test_declaration_value_assignment_errors
|
107
|
-
background = Stylish::Background.new(:color => "red"
|
108
|
+
background = Stylish::Background.new(:color => "red")
|
108
109
|
|
109
110
|
assert_raise ArgumentError do
|
110
111
|
background.value = "mondrian.jpg"
|
111
112
|
end
|
112
113
|
end
|
114
|
+
|
115
|
+
def test_image_declaration_serialisation
|
116
|
+
background = Stylish::Background.new(:image => "test.png")
|
117
|
+
|
118
|
+
assert_equal("background-image:url('test.png');", background.to_s)
|
119
|
+
end
|
113
120
|
end
|
data/test/declaration_test.rb
CHANGED
@@ -2,16 +2,20 @@ require 'test/unit'
|
|
2
2
|
require './lib/stylish'
|
3
3
|
|
4
4
|
class DeclarationTest < Test::Unit::TestCase
|
5
|
-
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@declaration = Stylish::Declaration.new("color", Stylish::Color.new("000"))
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_naming
|
11
|
+
assert_equal("color", @declaration.name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_value
|
15
|
+
assert_equal("#000", @declaration.value.to_s)
|
11
16
|
end
|
12
17
|
|
13
18
|
def test_to_string
|
14
|
-
|
15
|
-
assert_equal("color:#000;", dec.to_s)
|
19
|
+
assert_equal("color:#000;", @declaration.to_s)
|
16
20
|
end
|
17
21
|
end
|
data/test/declarations_test.rb
CHANGED
@@ -10,10 +10,12 @@ class DeclarationsTest < Test::Unit::TestCase
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def test_join
|
13
|
-
assert_equal("border-color:red; background-color:blue;
|
13
|
+
assert_equal("border-color:red; background-color:blue; " +
|
14
|
+
"background-image:url('test.png');", @ds.join)
|
14
15
|
end
|
15
16
|
|
16
17
|
def test_to_string
|
17
|
-
assert_equal("border-color:red; background-color:blue;
|
18
|
+
assert_equal("border-color:red; background-color:blue; " +
|
19
|
+
"background-image:url('test.png');", @ds.to_s)
|
18
20
|
end
|
19
21
|
end
|
data/test/generate_test.rb
CHANGED
@@ -13,6 +13,15 @@ class GenerateTest < Test::Unit::TestCase
|
|
13
13
|
".unchecked {font-style:italic;}", style.to_s)
|
14
14
|
end
|
15
15
|
|
16
|
+
def test_compound_rules
|
17
|
+
style = Stylish.generate do
|
18
|
+
rule ["abbr", "acronym"], :margin_bottom => "2em"
|
19
|
+
end
|
20
|
+
|
21
|
+
assert_equal(1, style.rules.length)
|
22
|
+
assert_equal("abbr, acronym {margin-bottom:2em;}", style.rules.first.to_s)
|
23
|
+
end
|
24
|
+
|
16
25
|
def test_nested_rules
|
17
26
|
style = Stylish.generate do
|
18
27
|
rule "body" do
|
@@ -51,4 +60,24 @@ class GenerateTest < Test::Unit::TestCase
|
|
51
60
|
|
52
61
|
assert_equal("body div p {line-height:1.5;}", style.to_s)
|
53
62
|
end
|
63
|
+
|
64
|
+
def test_nested_declarations
|
65
|
+
style = Stylish.generate do
|
66
|
+
fieldset :background => {:color => [0, 0, 255], :image => "fieldset.png"}
|
67
|
+
end
|
68
|
+
|
69
|
+
assert_instance_of(Stylish::Background,
|
70
|
+
style.rules.first.declarations.first)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_comments
|
74
|
+
style = Stylish.generate do
|
75
|
+
comment "A glorious comment!"
|
76
|
+
comment "An inglorious comment.", "An additional note."
|
77
|
+
end
|
78
|
+
|
79
|
+
assert_equal(2, style.comments.length)
|
80
|
+
assert_equal("A glorious comment!", style.comments[0].header)
|
81
|
+
assert_equal("An additional note.", style.comments[1].lines[0])
|
82
|
+
end
|
54
83
|
end
|
data/test/image_test.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require './lib/stylish'
|
3
|
+
|
4
|
+
class ImageTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@image = Stylish::Image.new("test.png")
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_image_path
|
11
|
+
assert_equal("test.png", @image.path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_image_serialisation
|
15
|
+
assert_equal("url('test.png')", @image.to_s)
|
16
|
+
end
|
17
|
+
end
|
data/test/rule_test.rb
CHANGED
@@ -6,8 +6,8 @@ class RuleTest < Test::Unit::TestCase
|
|
6
6
|
def setup
|
7
7
|
@rule = Stylish::Rule.new([
|
8
8
|
Stylish::Selector.new("div .alert, body .error")],
|
9
|
-
Stylish::Declaration.new("font-style", "italic"),
|
10
|
-
Stylish::Declaration.new("font-weight", "bold"))
|
9
|
+
[Stylish::Declaration.new("font-style", "italic"),
|
10
|
+
Stylish::Declaration.new("font-weight", "bold")])
|
11
11
|
end
|
12
12
|
|
13
13
|
def test_rule_serialisation
|
data/test/stylesheet_test.rb
CHANGED
@@ -8,7 +8,7 @@ class StylesheetTest < Test::Unit::TestCase
|
|
8
8
|
@node = Stylish::Tree::SelectorScope.new("div")
|
9
9
|
@onde = Stylish::Tree::SelectorScope.new("span")
|
10
10
|
@rule = Stylish::Rule.new([Stylish::Selector.new("em")],
|
11
|
-
Stylish::Declaration.new("font-weight", "bold"))
|
11
|
+
[Stylish::Declaration.new("font-weight", "bold")])
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_node_addition
|
data/test/tree_test.rb
CHANGED
@@ -4,10 +4,12 @@ require './lib/stylish'
|
|
4
4
|
class TreeTest < Test::Unit::TestCase
|
5
5
|
|
6
6
|
def setup
|
7
|
-
@tree
|
8
|
-
@node
|
9
|
-
@rule
|
10
|
-
|
7
|
+
@tree = Stylish::Stylesheet.new
|
8
|
+
@node = Stylish::Tree::SelectorScope.new(".test")
|
9
|
+
@rule = Stylish::Rule.new([Stylish::Selector.new("p")],
|
10
|
+
[Stylish::Declaration.new("font-weight", "bold")])
|
11
|
+
@comment = Stylish::Comment.new("Comment header",
|
12
|
+
{:author => "Some Body"})
|
11
13
|
end
|
12
14
|
|
13
15
|
def test_appending
|
@@ -28,11 +30,13 @@ class TreeTest < Test::Unit::TestCase
|
|
28
30
|
def test_rules_collation
|
29
31
|
@node << @rule
|
30
32
|
@node << @rule
|
33
|
+
@node << @comment
|
31
34
|
@tree << @node
|
32
35
|
@tree << @node
|
33
36
|
|
37
|
+
assert_equal(6, @tree.leaves.length)
|
34
38
|
assert_equal(4, @tree.rules.length)
|
35
|
-
assert_equal(
|
39
|
+
assert_equal(2, @tree.comments.length)
|
36
40
|
end
|
37
41
|
|
38
42
|
def test_node_reader
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ionfish-stylish
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benedict Eastaugh
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-04-
|
12
|
+
date: 2009-04-13 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -30,6 +30,7 @@ files:
|
|
30
30
|
- lib/stylish/core.rb
|
31
31
|
- lib/stylish/formattable.rb
|
32
32
|
- lib/stylish/generate.rb
|
33
|
+
- lib/stylish/image.rb
|
33
34
|
- lib/stylish/stylesheet.rb
|
34
35
|
- lib/stylish/tree.rb
|
35
36
|
- test/background_test.rb
|
@@ -39,6 +40,7 @@ files:
|
|
39
40
|
- test/declarations_test.rb
|
40
41
|
- test/formattable_test.rb
|
41
42
|
- test/generate_test.rb
|
43
|
+
- test/image_test.rb
|
42
44
|
- test/rule_test.rb
|
43
45
|
- test/selector_test.rb
|
44
46
|
- test/selectors_test.rb
|
@@ -78,6 +80,7 @@ test_files:
|
|
78
80
|
- test/declarations_test.rb
|
79
81
|
- test/formattable_test.rb
|
80
82
|
- test/generate_test.rb
|
83
|
+
- test/image_test.rb
|
81
84
|
- test/rule_test.rb
|
82
85
|
- test/selector_test.rb
|
83
86
|
- test/selectors_test.rb
|