ionfish-stylish 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,32 @@
1
+ === Version 0.1.3 (TODO)
2
+
3
+ Variables in the DSL
4
+ * Selector and property variables
5
+ * New descope example
6
+ * Limited CSS3 background support
7
+ * Cleaned up various things
8
+
9
+
10
+ === Version 0.1.2 (2009-04-12)
11
+
12
+ Documentation release
13
+ * Documented the stylesheet generation DSL
14
+ * Updated example code in the README
15
+ * Added an Image class to handle background images
16
+
17
+
18
+ === Version 0.1.1 (2009-04-12)
19
+
20
+ More DSL features
21
+ * Added the ability to append comments through the stylesheet generation DSL
22
+ * Fixed some parsing and selector serialisation problems
23
+ * Updated the main example file to work with the new DSL
24
+ * Completed the documentation of the core classes
25
+
26
+
27
+ === Version 0.1.0 (2009-04-10)
28
+
29
+ First major release
30
+ * New selector scope tree, which serialises to a valid stylesheet
31
+ * Stylesheet generation DSL based on the tree datastructure
32
+ * HSL(A) colour space support, complementing the existing RGB support
data/README.md CHANGED
@@ -23,6 +23,8 @@ Calling the stylesheet's `to_s` method would produce the following
23
23
  .content h2 {font-size:2em;}
24
24
  .content p {margin:0 0 1em 0;}
25
25
 
26
+ A number of additional examples are available in the example/ directory.
27
+
26
28
 
27
29
  Compatibility
28
30
  -------------
@@ -36,11 +38,6 @@ Future considerations
36
38
 
37
39
  * Add a Border class to complement Background.
38
40
  * Add better support for CSS3 properties to Background.
39
- * Add a native mapping construct to allow symbol lookup for alternate
40
- stylesheets.
41
- * Change stylesheet generation to a two-step process where the tree structure
42
- is generated in the first step and symbols (for example, those employed by
43
- a mapping construct) are evaluated the second.
44
41
  * Fundamental objects like percentages need their own classes rather than
45
42
  being dealt with in an ad-hoc manner by higher-level objects.
46
43
  * Add a parser so CSS can be read as well as written.
data/Rakefile CHANGED
@@ -11,14 +11,16 @@ begin
11
11
  s.authors = ["Benedict Eastaugh"]
12
12
  end
13
13
  rescue LoadError
14
- puts "Jeweler not available. Install it with: sudo gem install "
15
- + "technicalpickles-jeweler -s http://gems.github.com"
14
+ puts "Jeweler not available. Install it with: sudo gem install " +
15
+ "technicalpickles-jeweler -s http://gems.github.com"
16
16
  end
17
17
 
18
18
  task :default => :test
19
19
 
20
20
  desc "Run the Stylish test suite"
21
21
  task :test do
22
+ require 'test/unit'
23
+
22
24
  testdir = "test"
23
25
  Dir.foreach(testdir) do |f|
24
26
  path = "#{testdir}/#{f}"
@@ -28,7 +30,16 @@ task :test do
28
30
  end
29
31
  end
30
32
 
31
- desc "Run a Stylish example"
32
- task :example do
33
- require 'example/tarski'
33
+ namespace :example do
34
+
35
+ desc "An extended generator DSL example."
36
+ task :tarski do
37
+ require 'example/tarski'
38
+ end
39
+
40
+ desc "Extract base rules from a selector tree."
41
+ task :descope do
42
+ require 'example/descope'
43
+ end
34
44
  end
45
+
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :minor: 1
3
- :patch: 2
3
+ :patch: 3
4
4
  :major: 0
@@ -0,0 +1,226 @@
1
+ module Stylish
2
+
3
+ # The Background class is a specialised kind of Declaration, geared towards
4
+ # dealing with the oddities of the background family of declarations, which
5
+ # can exist in both long- and shorthand forms.
6
+ #
7
+ # For example, these longhand background declarations
8
+ #
9
+ # background-color: #999;
10
+ # background-image: url('bg.png');
11
+ # background-repeat: repeat-x;
12
+ #
13
+ # could be compressed into a single shorthand declaration
14
+ #
15
+ # background: #999 url('bg.png') repeat-x;
16
+ #
17
+ # The Background class allows for easy conversion between these forms. It
18
+ # defaults to the longhand versions, allowing rules with stronger selector
19
+ # weighting to only override specific parts of other rules' background
20
+ # declarations.
21
+ class Background < Declaration
22
+ attr_reader :color,
23
+ :image,
24
+ :repeat,
25
+ :position,
26
+ :attachment,
27
+ :origin,
28
+ :break,
29
+ :compressed
30
+
31
+ PROPERTIES = [
32
+ [:color, "background-color"],
33
+ [:image, "background-image"],
34
+ [:repeat, "background-repeat"],
35
+ [:position, "background-position"],
36
+ [:attachment, "background-attachment"],
37
+ [:origin, "background-origin"],
38
+ [:break, "background-break"],
39
+ [:compressed]]
40
+
41
+ REPEAT_VALUES = ["repeat-x", "repeat-y", "repeat",
42
+ "space", "round", "no-repeat"]
43
+ ATTACHMENT_VALUES = ["scroll", "fixed", "local"]
44
+ HORIZONTAL_POSITIONS = ["left", "center", "right"]
45
+ VERTICAL_POSITIONS = ["top", "center", "bottom"]
46
+ ORIGIN_VALUES = ["border-box", "padding-box", "content-box"]
47
+ BREAK_VALUES = ["bounding-box", "each-box", "continuous"]
48
+
49
+ # Create a new Background object with the specified properties.
50
+ def initialize(options)
51
+ accept_format(/^\s*%s\s*:\s*%s;\s*$/m, "%s:%s;")
52
+ self.value = options
53
+ end
54
+
55
+ # Input validation for colours is handled by the Color class, which will
56
+ # raise an ArgumentError if the argument is an invalid colour value.
57
+ def color=(val)
58
+ @color = Color.new(val)
59
+ end
60
+
61
+ # Set the background image(s). As of CSS3, elements may have multiple
62
+ # background images, so this method attempts to provide a backwards-
63
+ # compatible solution.
64
+ #
65
+ # background = Background.new :image => "sky.png", :compressed => true
66
+ # background.to_s # => "background:url('sky.png');"
67
+ #
68
+ # background.image = ["ball.png", "grass.png"]
69
+ # background.to_s # => "background:url('ball.png'), url('grass.png');"
70
+ #
71
+ def image=(paths)
72
+ paths = [paths] if paths.is_a?(String)
73
+ @image = paths.inject([]) {|images, path| images << Image.new(path) }
74
+
75
+ if @image.length < 2
76
+ @image = @image.first
77
+ else
78
+ def @image.to_s
79
+ join(", ")
80
+ end
81
+ end
82
+ end
83
+
84
+ # Set the background repeat(s). As of CSS3, the background-repeat property
85
+ # may have multiple values, so this method provides a backwards-compatible
86
+ # solution.
87
+ #
88
+ # repeating = Background.new :repeat => ["repeat-x", "repeat-y"]
89
+ # repeating.to_s # => "background-repeat:repeat-x, repeat-y;"
90
+ #
91
+ def repeat=(repeats)
92
+ repeats = [repeats] if repeats.is_a? String
93
+ @repeat = repeats.find_all {|r| REPEAT_VALUES.include? r }
94
+
95
+ if @repeat.length < 2
96
+ @repeat = @repeat.first
97
+ else
98
+ def @repeat.to_s
99
+ join(", ")
100
+ end
101
+ end
102
+ end
103
+
104
+ # Only position keywords are currently handled, not percentages or lengths.
105
+ def position=(val)
106
+ xpos, ypos = val.split(/\s+/) << "center"
107
+ if HORIZONTAL_POSITIONS.include?(xpos) && VERTICAL_POSITIONS.include?(ypos)
108
+ @position = [xpos, ypos]
109
+ end
110
+ end
111
+
112
+ # The background-attachment property takes a limited range of values, so
113
+ # only a value within that range will be accepted.
114
+ def attachment=(attachments)
115
+ attachments = [attachments] if attachments.is_a? String
116
+ @attachment = attachments.find_all {|a| ATTACHMENT_VALUES.include? a }
117
+
118
+ if @attachment.length < 2
119
+ @attachment = @attachment.first
120
+ else
121
+ def @attachment.to_s
122
+ join(", ")
123
+ end
124
+ end
125
+ end
126
+
127
+ # The background-origin property specifies the background positioning area.
128
+ # It is a CSS3 property which takes multiple values.
129
+ #
130
+ # original = Background.new :origin => ["padding-box", "content-box"]
131
+ # original.to_s # => background-origin:padding-box, content-box;
132
+ #
133
+ def origin=(origins)
134
+ origins = [origins] if origins.is_a? String
135
+ @origin = origins.find_all {|o| ORIGIN_VALUES.include? o }
136
+
137
+ if @origin.length < 2
138
+ @origin = @origin.first
139
+ else
140
+ def @origin.to_s
141
+ join(", ")
142
+ end
143
+ end
144
+ end
145
+
146
+ # The background-break property, defined in CSS3, specifies how the
147
+ # background positioning area is derived when an element is broken into
148
+ # multiple boxes.
149
+ #
150
+ # broken = Background.new :break => "bounding-box"
151
+ # broken.to_s # => "background-break:bounding-box;"
152
+ #
153
+ def break=(value)
154
+ @break = value if BREAK_VALUES.include? value
155
+ end
156
+
157
+ # Set this to true to generate a shorthand declaration, e.g.
158
+ #
159
+ # background:#ccc url('bg.png') no-repeat 0 0;
160
+ #
161
+ # As opposed to the longhand version:
162
+ #
163
+ # background-color:#ccc; background-image:url('bg.png');
164
+ # background-repeat:no-repeat; background-position:0 0;
165
+ #
166
+ def compressed=(val)
167
+ @compressed = val == true || nil
168
+ end
169
+
170
+ # Override Declaration#name, since it's not compatible with the
171
+ # internals of this class.
172
+ def name
173
+ PROPERTIES.reject {|n, p| p.nil? }.map {|n, p|
174
+ value = self.send(n)
175
+ p.to_s unless value.nil?
176
+ }.compact
177
+ end
178
+
179
+ # Override Declaration#name=, since it's not compatible with the
180
+ # internals of this class.
181
+ def name=(val)
182
+ raise NoMethodError, "name= is not defined for Background."
183
+ end
184
+
185
+ # Override Declaration#value, since it's not compatible with the internals
186
+ # of this class.
187
+ def value(name_and_value = false)
188
+ PROPERTIES.reject {|n, p| p.nil? }.map {|n, p|
189
+ value = self.send(n)
190
+ next if value.nil?
191
+ name_and_value ? [p.to_s, value] : value
192
+ }.compact
193
+ end
194
+
195
+ # Override Declaration#value=, since it's not compatible with the internals
196
+ # of this class.
197
+ def value=(options)
198
+ unless options.is_a? Hash
199
+ raise ArgumentError, "Argument must be a hash of background properties"
200
+ end
201
+
202
+ PROPERTIES.each do |name, property|
203
+ self.send(:"#{name.to_s}=", options[name]) if options[name]
204
+ end
205
+ end
206
+
207
+ # Generate a string representation of a Background instance.
208
+ #
209
+ # There are two kinds of representation, each of which have slightly
210
+ # different CSS semantics. If compressed is set to true, this method will
211
+ # produce a shorthand CSS declaration such as the following:
212
+ #
213
+ # background: #fff url('bg.png') no-repeat 50% 0;
214
+ #
215
+ # Otherwise it will produce an unordered list of individual background
216
+ # declarations.
217
+ def to_s(symbols = {})
218
+ if @compressed
219
+ "background:#{self.value(true).map {|p, v| v }.compact.join(" ")};"
220
+ else
221
+ self.value(true).map {|p, v| sprintf(@format, p, v.to_s) }.join(" ")
222
+ end
223
+ end
224
+ end
225
+
226
+ end
data/lib/stylish/color.rb CHANGED
@@ -1,6 +1,3 @@
1
- require 'mathn'
2
- require 'rational'
3
-
4
1
  module Stylish #:nodoc:
5
2
 
6
3
  # The Color class is intended to eventually implement the entirety of the
@@ -214,7 +211,7 @@ module Stylish #:nodoc:
214
211
  [:red, :green, :blue].each do |color|
215
212
  reader = color
216
213
  writer = :"#{color}="
217
- color = :"@#{color.to_s}"
214
+ color = :"@#{color.to_s}"
218
215
 
219
216
  unless self.respond_to?(reader)
220
217
  self.send(:define_method, reader) do
@@ -246,11 +243,11 @@ module Stylish #:nodoc:
246
243
  # Attribute writer for the color's value. Uses the ColorStringParser inner
247
244
  # class to parse string values, and contains other logic to handle arrays.
248
245
  def value=(value)
249
- if value.is_a?(String) || value.is_a?(Symbol)
246
+ if value.is_a?(String)
250
247
  parser = ColorStringParser.new
251
248
  @type, @red, @green, @blue, @opacity = parser.parse(value)
252
249
  return unless @type.nil?
253
- elsif value.is_a?(Array) && (3..4).include?(value.length)
250
+ else
254
251
  rgb = value[0..2].inject([]) do |rgb, v|
255
252
  if v.is_a?(Integer) || v.is_a?(Float)
256
253
  rgb << v
@@ -263,14 +260,12 @@ module Stylish #:nodoc:
263
260
  rgb
264
261
  end
265
262
 
266
- if rgb.length == 3
267
- if value.length == 3
268
- @red, @green, @blue, @opacity = rgb << nil
269
- @type = :rgb and return
270
- elsif value.length == 4 and value[3].kind_of?(Numeric)
271
- @red, @green, @blue, @opacity = rgb << value[3].to_f
272
- @type = :rgba and return
273
- end
263
+ if value.length < 4
264
+ @red, @green, @blue, @opacity = rgb << nil
265
+ @type = :rgb and return
266
+ else
267
+ @red, @green, @blue, @opacity = rgb << value[3].to_f
268
+ @type = :rgba and return
274
269
  end
275
270
  end
276
271
 
@@ -289,10 +284,7 @@ module Stylish #:nodoc:
289
284
  # color.opacity # => 0.5
290
285
  #
291
286
  def opacity=(value)
292
- return unless value.is_a?(Integer) || value.is_a?(Float)
293
- return if value < 0 || value > 1
294
-
295
- @opacity = value
287
+ @opacity = value unless value < 0 || value > 1
296
288
  end
297
289
 
298
290
  # Returns a color keyword string if the RGB value of the color is equal to
@@ -381,7 +373,7 @@ module Stylish #:nodoc:
381
373
  # color.type = :rgb
382
374
  # color.to_s # => "rgb(0, 0, 0)"
383
375
  #
384
- def to_s
376
+ def to_s(symbols = {})
385
377
  return "inherit" if @type == :inherit
386
378
 
387
379
  self.send(:"to_#{self.type.to_s}")
@@ -415,7 +407,9 @@ module Stylish #:nodoc:
415
407
  huer.call(red - green, 240)
416
408
  end
417
409
 
418
- [hue].concat([saturation, lightness].map {|r| (r * 100).to_f.round.to_s + "%" })
410
+ [hue].concat([saturation, lightness].map {|r|
411
+ (r * 100).to_f.round.to_s + "%"
412
+ })
419
413
  end
420
414
 
421
415
  def hsla
data/lib/stylish/core.rb CHANGED
@@ -73,8 +73,9 @@ module Stylish
73
73
  end
74
74
 
75
75
  # Serialise the rule to valid CSS code.
76
- def to_s(scope = "")
77
- sprintf(@format, selectors.join(scope), @declarations.join)
76
+ def to_s(symbols = {}, scope = "")
77
+ sprintf(@format, selectors.join(symbols, scope),
78
+ @declarations.to_s(symbols))
78
79
  end
79
80
  end
80
81
 
@@ -150,7 +151,7 @@ module Stylish
150
151
  # the serialisation API of those trees, and thus the #to_s method has a
151
152
  # scope argument, which is in practice discarded when the serialisation of
152
153
  # the comment occurs.
153
- def to_s(scope = "")
154
+ def to_s(symbols = {}, scope = "")
154
155
  if @lines.empty? && @metadata.empty?
155
156
  sprintf("/**\n * %s\n */", @header)
156
157
  else
@@ -178,8 +179,8 @@ module Stylish
178
179
 
179
180
  # Selectors are immutable once created; the value of a given Selector must
180
181
  # be set when the object is created.
181
- def initialize(str)
182
- @selector = str.to_s
182
+ def initialize(selector)
183
+ @selector = selector
183
184
  end
184
185
 
185
186
  # Each Rule possesses one or more Selectors. Rules are often placed in
@@ -190,8 +191,9 @@ module Stylish
190
191
  #
191
192
  # The Selector class is also used internally by the Tree::SelectorScope
192
193
  # class, to store its scope value.
193
- def to_s(scope = "")
194
- (scope.empty? ? "" : scope + " ") + @selector.to_s
194
+ def to_s(symbols = {}, scope = "")
195
+ (scope.empty? ? "" : scope + " ") +
196
+ (@selector.is_a?(String) ? @selector.to_s : @selector.to_s(symbols))
195
197
  end
196
198
  end
197
199
 
@@ -211,15 +213,15 @@ module Stylish
211
213
  # The join method overrides the superclass' method in order to always use a
212
214
  # specific separator, and so that the scope that the selectors are being
213
215
  # used in can be passed through when Rules etc. are serialised.
214
- def join(scope = "")
216
+ def join(symbols = {}, scope = "")
215
217
  self.inject("") do |ss, s|
216
- (ss.empty? ? "" : ss + self.format) + s.to_s(scope)
218
+ (ss.empty? ? "" : ss + self.format) + s.to_s(symbols, scope)
217
219
  end
218
220
  end
219
221
 
220
222
  # The to_s method alternative way of calling the join method.
221
- def to_s(scope = "")
222
- self.join(scope)
223
+ def to_s(symbols = {}, scope = "")
224
+ self.join(symbols, scope)
223
225
  end
224
226
  end
225
227
 
@@ -262,12 +264,17 @@ module Stylish
262
264
  #
263
265
  # Since the formatting can be adjusted via the #format= accessor, the exact
264
266
  # spacing of the declaration can be controlled if desired.
265
- def to_s
266
- sprintf(@format, @property_name.to_s, @value.to_s)
267
+ def to_s(symbols = {})
268
+ if @value.is_a?(Generate::Variable) || @value.is_a?(Color)
269
+ value = @value.to_s(symbols)
270
+ else
271
+ value = @value.to_s
272
+ end
273
+
274
+ sprintf(@format, @property_name.to_s, value)
267
275
  end
268
276
  end
269
277
 
270
-
271
278
  # Declarations subclasses Array so that whenever #join is called, the
272
279
  # instance's format attribute will be used as the join string, rather than
273
280
  # the empty string.
@@ -292,151 +299,9 @@ module Stylish
292
299
  # format attribute. Assuming that its contents are indeed Declaration
293
300
  # objects, this will invoke their own #to_s method and generating correct
294
301
  # CSS code.
295
- def to_s
296
- self.join
297
- end
298
- end
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.
318
- class Background < Declaration
319
- attr_reader :color,
320
- :image,
321
- :repeat,
322
- :position,
323
- :attachment,
324
- :compressed
325
-
326
- PROPERTIES = [
327
- [:color, "background-color"],
328
- [:image, "background-image"],
329
- [:repeat, "background-repeat"],
330
- [:position, "background-position"],
331
- [:attachment, "background-attachment"],
332
- [:compressed]]
333
-
334
- REPEAT_VALUES = ["repeat", "repeat-x", "repeat-y", "no-repeat"]
335
- ATTACHMENT_VALUES = ["scroll", "fixed", "inherit"]
336
- HORIZONTAL_POSITIONS = ["left", "center", "right"]
337
- VERTICAL_POSITIONS = ["top", "center", "bottom"]
338
-
339
- # Create a new Background object with the specified properties.
340
- def initialize(options)
341
- accept_format(/^\s*%s\s*:\s*%s;\s*$/m, "%s:%s;")
342
- self.value = options
343
- end
344
-
345
- # Input validation for colours is handled by the Color class, which will
346
- # raise an ArgumentError if the argument is an invalid colour value.
347
- def color=(val)
348
- @color = Color.new(val)
349
- end
350
-
351
- # Set the background image.
352
- def image=(path)
353
- @image = Image.new(path) if path.is_a?(String)
354
- end
355
-
356
- # Set the background repeat.
357
- def repeat=(val)
358
- @repeat = val if REPEAT_VALUES.include?(val)
359
- end
360
-
361
- # Only position keywords are currently handled, not percentages or lengths.
362
- def position=(val)
363
- xpos, ypos = val.split(/\s+/) << "center"
364
- if HORIZONTAL_POSITIONS.include?(xpos) && VERTICAL_POSITIONS.include?(ypos)
365
- @position = [xpos, ypos]
366
- end
367
- end
368
-
369
- # The background-attachment property takes a limited range of values, so
370
- # only a value within that range will be accepted.
371
- def attachment=(val)
372
- @attachment = val if ATTACHMENT_VALUES.include?(val)
373
- end
374
-
375
- # Set this to true to generate a shorthand declaration, e.g.
376
- #
377
- # background:#ccc url('bg.png') no-repeat 0 0;
378
- #
379
- # As opposed to the longhand version:
380
- #
381
- # background-color:#ccc; background-image:url('bg.png');
382
- # background-repeat:no-repeat; background-position:0 0;
383
- #
384
- def compressed=(val)
385
- @compressed = val == true || nil
386
- end
387
-
388
- # Override Declaration#name, since it's not compatible with the
389
- # internals of this class.
390
- def name
391
- PROPERTIES.reject {|n, p| p.nil? }.map {|n, p|
392
- value = self.send(n)
393
- p.to_s unless value.nil?
394
- }.compact
395
- end
396
-
397
- # Override Declaration#name=, since it's not compatible with the
398
- # internals of this class.
399
- def name=(val)
400
- raise NoMethodError, "name= is not defined for Background."
401
- end
402
-
403
- # Override Declaration#value, since it's not compatible with the internals
404
- # of this class.
405
- def value(name_and_value = false)
406
- PROPERTIES.reject {|n, p| p.nil? }.map {|n, p|
407
- value = self.send(n)
408
- next if value.nil?
409
- name_and_value ? [p.to_s, value] : value
410
- }.compact
411
- end
412
-
413
- # Override Declaration#value=, since it's not compatible with the internals
414
- # of this class.
415
- def value=(options)
416
- unless options.is_a? Hash
417
- raise ArgumentError, "Argument must be a hash of background properties"
418
- end
419
-
420
- PROPERTIES.each do |name, property|
421
- self.send(:"#{name.to_s}=", options[name]) if options[name]
422
- end
423
- end
424
-
425
- # Generate a string representation of a Background instance.
426
- #
427
- # There are two kinds of representation, each of which have slightly
428
- # different CSS semantics. If compressed is set to true, this method will
429
- # produce a shorthand CSS declaration such as the following:
430
- #
431
- # background: #fff url('bg.png') no-repeat 50% 0;
432
- #
433
- # Otherwise it will produce an unordered list of individual background
434
- # declarations.
435
- def to_s
436
- if @compressed
437
- "background:#{self.value(true).map {|p, v| v }.compact.join(" ")};"
438
- else
439
- self.value(true).map {|p, v| sprintf(@format, p, v.to_s) }.join(" ")
302
+ def to_s(symbols = {})
303
+ self.inject("") do |a, o|
304
+ a << (a.empty? ? "" : @format) << o.to_s(symbols)
440
305
  end
441
306
  end
442
307
  end
@@ -55,10 +55,21 @@ module Stylish
55
55
  key = key.to_s.sub("_", "-").to_sym
56
56
 
57
57
  if key == :background
58
- declaration = Background.new(value)
58
+ if value.any? {|k,v| v.is_a? Symbol }
59
+ declaration = Variable.new(value, Background)
60
+ else
61
+ declaration = Background.new(value)
62
+ end
59
63
  elsif key == :color
60
- declaration = Declaration.new("color", Color.new(value))
64
+ if value.is_a? Symbol
65
+ value = Variable.new(value, Color)
66
+ else
67
+ value = Color.new(value)
68
+ end
69
+
70
+ declaration = Declaration.new("color", value)
61
71
  else
72
+ value = Variable.new(value) if value.is_a? Symbol
62
73
  declaration = Declaration.new(key, value)
63
74
  end
64
75
 
@@ -66,6 +77,52 @@ module Stylish
66
77
  end
67
78
  end
68
79
 
80
+ # Variables are elements of a selector tree that haven't been assigned
81
+ # values yet. When a tree that includes Variable objects is serialised, it
82
+ # must be passed a symbol table so that the variables may be given values.
83
+ class Variable
84
+
85
+ # When Variable objects are initialised they may be given either a simple
86
+ # symbol, or a compound object (such as a hash) which contains symbols.
87
+ # When a compound object is given, a constructor must also be given.
88
+ #
89
+ # varbg = Variable.new({:image => :button, :color => :bright})
90
+ # varbg.to_s({:button => "button.png", :bright => "0f0"})
91
+ #
92
+ # Which would give the following:
93
+ #
94
+ # background-image:url('button.png'); background-color:#0f0;
95
+ #
96
+ # Constructors can also be given for simple values, e.g. when creating a
97
+ # Color.
98
+ #
99
+ # varc = Variable.new(:bright, Color)
100
+ # varc.to_s({:bright => "f00"}) # => "#f00"
101
+ #
102
+ def initialize(name_or_hash, constructor = nil)
103
+ @name = name_or_hash
104
+ @constructor = constructor
105
+ end
106
+
107
+ # The symbol table is given as an argument to the root element of a
108
+ # selector tree when it is serialised, and passed down to each node as
109
+ # the tree is traversed. Nodes must then serialise themselves, and if
110
+ # they contain Variables they must pass them the symbol table so that
111
+ # they can be resolved to a given value.
112
+ def to_s(symbols)
113
+ if @constructor.nil?
114
+ symbols[@name]
115
+ elsif @name.is_a? Symbol
116
+ @constructor.new(symbols[@name]).to_s
117
+ else
118
+ @constructor.new(@name.to_a.inject({}) {|a, e|
119
+ a[e.first] = symbols[e.last]
120
+ a
121
+ }).to_s
122
+ end
123
+ end
124
+ end
125
+
69
126
  # Often the selector associated with a call to the rule method would simply
70
127
  # be a single HTML element name. This has been factored out by adding
71
128
  # methods to the Description DSL class corresponding to all the HTML
@@ -161,7 +218,9 @@ module Stylish
161
218
  return unless declarations || block
162
219
 
163
220
  selectors = [selectors] unless selectors.is_a?(Array)
164
- selectors.map! {|s| Selector.new(s) }
221
+ selectors.map! do |s|
222
+ Selector.new(s.is_a?(Symbol) ? Variable.new(s) : s)
223
+ end
165
224
 
166
225
  declarations = Generate.parse_declarations(declarations)
167
226
 
@@ -16,9 +16,9 @@ module Stylish
16
16
  end
17
17
 
18
18
  # Recursively serialise the tree to a stylesheet.
19
- def to_s
19
+ def to_s(symbols = {})
20
20
  return "" if @nodes.empty?
21
- @nodes.map {|node| node.to_s }.join(@format)
21
+ @nodes.map {|node| node.to_s(symbols) }.join(@format)
22
22
  end
23
23
  end
24
24
 
data/lib/stylish/tree.rb CHANGED
@@ -84,10 +84,10 @@ module Stylish
84
84
  end
85
85
 
86
86
  # Recursively serialise the selector tree.
87
- def to_s(scope = "")
87
+ def to_s(symbols = {}, scope = "")
88
88
  return "" if @nodes.empty?
89
89
  scope = scope.empty? ? @scope.to_s : scope + " " + @scope.to_s
90
- @nodes.map {|node| node.to_s(scope) }.join(@format)
90
+ @nodes.map {|node| node.to_s(symbols, scope) }.join(@format)
91
91
  end
92
92
 
93
93
  # Return the node's child nodes.
data/lib/stylish.rb CHANGED
@@ -1,11 +1,17 @@
1
- $:.unshift File.dirname(__FILE__)
2
-
3
1
  require 'pathname'
2
+ require 'mathn'
3
+ require 'rational'
4
4
 
5
- require 'stylish/formattable'
6
- require 'stylish/tree'
7
- require 'stylish/core'
8
- require 'stylish/stylesheet'
9
- require 'stylish/image'
10
- require 'stylish/color'
11
- require 'stylish/generate'
5
+ module Stylish
6
+ STYLISH_PATH = File.expand_path(File.dirname(__FILE__)) + '/stylish/'
7
+
8
+ require STYLISH_PATH + 'formattable'
9
+ require STYLISH_PATH + 'tree'
10
+ require STYLISH_PATH + 'core'
11
+ require STYLISH_PATH + 'tree'
12
+ require STYLISH_PATH + 'stylesheet'
13
+ require STYLISH_PATH + 'image'
14
+ require STYLISH_PATH + 'background'
15
+ require STYLISH_PATH + 'color'
16
+ require STYLISH_PATH + 'generate'
17
+ end
@@ -1,6 +1,3 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class BackgroundTest < Test::Unit::TestCase
5
2
 
6
3
  def setup
@@ -24,10 +21,26 @@ class BackgroundTest < Test::Unit::TestCase
24
21
  Stylish::Background.new(:image => "background.jpg").image.path)
25
22
  end
26
23
 
24
+ def test_multiple_background_images
25
+ bg = Stylish::Background.new :image =>
26
+ ["flower.png", "ball.png", "grass.png"]
27
+
28
+ assert_equal(3, bg.image.length)
29
+ assert_equal("background-image:" +
30
+ "url('flower.png'), url('ball.png'), url('grass.png');", bg.to_s)
31
+ end
32
+
27
33
  def test_valid_background_repeats
28
34
  assert_equal('no-repeat', @composite.repeat)
29
35
  end
30
36
 
37
+ def test_multiple_background_repeats
38
+ bg = Stylish::Background.new :repeat => ["repeat-x", "repeat-y"]
39
+
40
+ assert_equal(2, bg.repeat.length)
41
+ assert_equal("background-repeat:repeat-x, repeat-y;", bg.to_s)
42
+ end
43
+
31
44
  def test_valid_background_positions
32
45
  assert_equal(2, @composite.position.length)
33
46
  assert_equal("left", @composite.position[0])
@@ -44,12 +57,32 @@ class BackgroundTest < Test::Unit::TestCase
44
57
  Stylish::Background.new(:attachment => "fixed").attachment)
45
58
  end
46
59
 
60
+ def test_multiple_attachments
61
+ glued = Stylish::Background.new :attachment => ["local", "fixed"]
62
+
63
+ assert_equal(2, glued.attachment.length)
64
+ assert_equal("background-attachment:local, fixed;", glued.to_s)
65
+ end
66
+
47
67
  def test_invalid_background_colors
48
68
  assert_raise ArgumentError do
49
69
  Stylish::Background.new(:color => "sky-blue")
50
70
  end
51
71
  end
52
72
 
73
+ def test_origins
74
+ original = Stylish::Background.new :origin => ["border-box", "padding-box"]
75
+
76
+ assert_equal(2, original.origin.length)
77
+ assert_equal("background-origin:border-box, padding-box;", original.to_s)
78
+ end
79
+
80
+ def test_breaks
81
+ broken = Stylish::Background.new :break => "bounding-box"
82
+
83
+ assert_equal("background-break:bounding-box;", broken.to_s)
84
+ end
85
+
53
86
  def test_invalid_image_values
54
87
  assert_nil(Stylish::Background.new(:image => []).image)
55
88
  assert_nil(Stylish::Background.new(:image => {}).image)
data/test/color_test.rb CHANGED
@@ -1,11 +1,8 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class ColorTest < Test::Unit::TestCase
5
2
 
6
3
  def setup
7
- @red = Stylish::Color.new(:red)
8
- @green = Stylish::Color.new(:green)
4
+ @red = Stylish::Color.new("red")
5
+ @green = Stylish::Color.new("green")
9
6
  @blue = Stylish::Color.new("#0000FF")
10
7
  @white = Stylish::Color.new("#FFF")
11
8
  @yellow = Stylish::Color.new([255, 255, 0])
@@ -68,8 +65,6 @@ class ColorTest < Test::Unit::TestCase
68
65
  end
69
66
 
70
67
  def test_case_insensitivity_of_keywords
71
- assert_equal([0, 128, 0, nil], Stylish::Color.new(:Green).value)
72
- assert_equal([0, 128, 0, nil], Stylish::Color.new(:GrEeN).value)
73
68
  assert_equal([0, 128, 0, nil], Stylish::Color.new("Green").value)
74
69
  assert_equal([0, 128, 0, nil], Stylish::Color.new("GrEeN").value)
75
70
  end
@@ -117,7 +112,7 @@ class ColorTest < Test::Unit::TestCase
117
112
 
118
113
  def test_keyword_to_string
119
114
  assert_equal("green", @green.to_s)
120
- assert_equal("yellow", Stylish::Color.new(:yellow).to_s)
115
+ assert_equal("yellow", Stylish::Color.new("yellow").to_s)
121
116
  end
122
117
 
123
118
  def test_rgb_to_string
@@ -148,8 +143,8 @@ class ColorTest < Test::Unit::TestCase
148
143
  end
149
144
 
150
145
  def test_inherit_and_transparent_to_hex
151
- assert_nil(Stylish::Color.new(:inherit).to_hex)
152
- assert_nil(Stylish::Color.new(:transparent).to_hex)
146
+ assert_nil(Stylish::Color.new("inherit").to_hex)
147
+ assert_nil(Stylish::Color.new("transparent").to_hex)
153
148
  end
154
149
 
155
150
  def test_hex_to_hex
@@ -160,8 +155,8 @@ class ColorTest < Test::Unit::TestCase
160
155
  end
161
156
 
162
157
  def test_keywords_to_hex
163
- assert_equal("#808080", Stylish::Color.new(:gray).to_hex)
164
- assert_equal("#800000", Stylish::Color.new(:maroon).to_hex)
158
+ assert_equal("#808080", Stylish::Color.new("gray").to_hex)
159
+ assert_equal("#800000", Stylish::Color.new("maroon").to_hex)
165
160
  end
166
161
 
167
162
  def test_rgb_to_hex
@@ -174,7 +169,7 @@ class ColorTest < Test::Unit::TestCase
174
169
  assert_equal("#fff", Stylish::Color.new([255, 255, 255]).to_hex)
175
170
  assert_equal("#fff", Stylish::Color.new("#ffffff").to_hex)
176
171
  assert_equal("#fb0", Stylish::Color.new("#ffbb00").to_hex)
177
- assert_equal("#0ff", Stylish::Color.new(:aqua).to_hex)
172
+ assert_equal("#0ff", Stylish::Color.new("aqua").to_hex)
178
173
  end
179
174
 
180
175
  def test_rgb_to_hsl
data/test/comment_test.rb CHANGED
@@ -1,6 +1,3 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class CommentTest < Test::Unit::TestCase
5
2
 
6
3
  def setup
@@ -1,6 +1,3 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class DeclarationTest < Test::Unit::TestCase
5
2
 
6
3
  def setup
@@ -1,12 +1,9 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class DeclarationsTest < Test::Unit::TestCase
5
2
 
6
3
  def setup
7
4
  @ds = Stylish::Declarations.new
8
5
  @ds << Stylish::Declaration.new("border-color", "red")
9
- @ds << Stylish::Background.new(:color => :blue, :image => "test.png")
6
+ @ds << Stylish::Background.new(:color => "blue", :image => "test.png")
10
7
  end
11
8
 
12
9
  def test_join
@@ -1,6 +1,3 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class FormattableTest < Test::Unit::TestCase
5
2
 
6
3
  def setup
@@ -1,6 +1,3 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class GenerateTest < Test::Unit::TestCase
5
2
 
6
3
  def test_simple_rules
data/test/image_test.rb CHANGED
@@ -1,6 +1,3 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class ImageTest < Test::Unit::TestCase
5
2
 
6
3
  def setup
data/test/rule_test.rb CHANGED
@@ -1,6 +1,3 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class RuleTest < Test::Unit::TestCase
5
2
 
6
3
  def setup
@@ -1,13 +1,10 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class SelectorTest < Test::Unit::TestCase
5
2
 
6
3
  def test_to_string
7
4
  s = Stylish::Selector.new(".test")
8
5
  assert_equal(".test", s.to_s)
9
6
 
10
- s = Stylish::Selector.new(:div)
7
+ s = Stylish::Selector.new("div")
11
8
  assert_equal("div", s.to_s)
12
9
  end
13
10
  end
@@ -1,6 +1,3 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class SelectorsTest < Test::Unit::TestCase
5
2
 
6
3
  def setup
@@ -1,6 +1,3 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class StylesheetTest < Test::Unit::TestCase
5
2
 
6
3
  def setup
data/test/tree_test.rb CHANGED
@@ -1,6 +1,3 @@
1
- require 'test/unit'
2
- require './lib/stylish'
3
-
4
1
  class TreeTest < Test::Unit::TestCase
5
2
 
6
3
  def setup
@@ -0,0 +1,46 @@
1
+ class VariableTest < Test::Unit::TestCase
2
+
3
+ def setup
4
+ @style = Stylish.generate do
5
+ div :font_weight => :weighty
6
+ end
7
+ end
8
+
9
+ def test_variable_inclusion
10
+ assert_instance_of(Stylish::Generate::Variable,
11
+ @style.rules.first.declarations.first.value)
12
+ end
13
+
14
+ def test_variable_serialisation
15
+ assert_equal("div {font-weight:bold;}",
16
+ @style.to_s({:weighty => "bold"}))
17
+ end
18
+
19
+ def test_selector_variables
20
+ style = Stylish.generate do
21
+ rule :some_selector, :line_height => 1.5
22
+ end
23
+
24
+ assert_equal("body p {line-height:1.5;}",
25
+ style.to_s({:some_selector => "body p"}))
26
+ end
27
+
28
+ def test_color_variables
29
+ style = Stylish.generate do
30
+ body :color => :bright_as_a_button
31
+ end
32
+
33
+ assert_equal("body {color:#57b5cc;}",
34
+ style.to_s({:bright_as_a_button => "57b5cc"}))
35
+ end
36
+
37
+ def test_background_variables
38
+ style = Stylish.generate do
39
+ body :background => {:color => :dark, :image => :buttonish}
40
+ end
41
+
42
+ assert_equal(
43
+ "body {background-color:#000; background-image:url('button.png');}",
44
+ style.to_s({:dark => "000", :buttonish => "button.png"}))
45
+ end
46
+ end
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.2
4
+ version: 0.1.3
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-13 00:00:00 -07:00
12
+ date: 2009-04-24 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -22,10 +22,12 @@ extensions: []
22
22
  extra_rdoc_files:
23
23
  - README.md
24
24
  files:
25
+ - History.txt
25
26
  - README.md
26
27
  - Rakefile
27
28
  - VERSION.yml
28
29
  - lib/stylish.rb
30
+ - lib/stylish/background.rb
29
31
  - lib/stylish/color.rb
30
32
  - lib/stylish/core.rb
31
33
  - lib/stylish/formattable.rb
@@ -46,6 +48,7 @@ files:
46
48
  - test/selectors_test.rb
47
49
  - test/stylesheet_test.rb
48
50
  - test/tree_test.rb
51
+ - test/variable_test.rb
49
52
  has_rdoc: true
50
53
  homepage: http://github.com/ionfish/stylish
51
54
  post_install_message:
@@ -86,3 +89,4 @@ test_files:
86
89
  - test/selectors_test.rb
87
90
  - test/stylesheet_test.rb
88
91
  - test/tree_test.rb
92
+ - test/variable_test.rb