ionfish-stylish 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|