ionfish-stylish 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,11 @@
1
+ === Version 0.1.5 (2009-05-03)
2
+
3
+ Improved background support
4
+ * Length, Position and Percentage classes
5
+ * accept_format now a class method
6
+ * Improved API and internals for multiple background properties
7
+
8
+
1
9
  === Version 0.1.4 (2009-04-25)
2
10
 
3
11
  Bug fixes
data/README.md CHANGED
@@ -23,7 +23,7 @@ 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.
26
+ A number of additional examples are available in the `example/` directory.
27
27
 
28
28
 
29
29
  Compatibility
@@ -37,9 +37,6 @@ Future considerations
37
37
  ---------------------
38
38
 
39
39
  * Add a Border class to complement Background.
40
- * Add better support for CSS3 properties to Background.
41
- * Fundamental objects like percentages need their own classes rather than
42
- being dealt with in an ad-hoc manner by higher-level objects.
43
40
  * Add a parser so CSS can be read as well as written.
44
41
 
45
42
 
@@ -59,9 +56,7 @@ Stylish treats CSS as object code--but it treats it nicely.
59
56
  Licence
60
57
  -------
61
58
 
62
- Copyright (c) 2008-2009, Benedict Eastaugh
63
-
64
- All rights reserved.
59
+ Copyright (c) 2008-2009, Benedict Eastaugh. All rights reserved.
65
60
 
66
61
  Redistribution and use in source and binary forms, with or without
67
62
  modification, are permitted provided that the following conditions are met:
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :minor: 1
3
- :patch: 4
3
+ :patch: 5
4
4
  :major: 0
data/lib/stylish/color.rb CHANGED
@@ -22,14 +22,15 @@ module Stylish #:nodoc:
22
22
  # RGB integer; a hexadecimal representation of an RGB color; a rgb()
23
23
  # representation of an RGB color; and an rgba() representation of an RGBA
24
24
  # color.
25
+ pct = Percentage::PCTSTR
25
26
  OPACITY = /([0-1]|0\.\d+)/
26
27
  RGB_INT = /(\d{1,2}|[1-2][0-5]{2})/
27
28
  HSL_INT = /(\d{1,2}|[1-2]\d{2}|3([0-5]\d|60))/
28
29
  HEX_COLOR = /^#?([\da-fA-F]{3}){1,2}$/
29
- RGB_COLOR = /rgb\(\s*((#{RGB_INT}|#{PCT}),\s*){2}(#{RGB_INT}|#{PCT})\s*\)/
30
- RGBA_COLOR = /rgba\(\s*((#{RGB_INT}|#{PCT}),\s*){3}#{OPACITY}\s*\)/
31
- HSL_COLOR = /hsl\(\s*((#{HSL_INT}|#{PCT}),\s*){2}(#{HSL_INT}|#{PCT})\s*\)/
32
- HSLA_COLOR = /hsla\(\s*((#{HSL_INT}|#{PCT}),\s*){3}(#{OPACITY})\s*\)/
30
+ RGB_COLOR = /rgb\(\s*((#{RGB_INT}|#{pct}),\s*){2}(#{RGB_INT}|#{pct})\s*\)/
31
+ RGBA_COLOR = /rgba\(\s*((#{RGB_INT}|#{pct}),\s*){3}#{OPACITY}\s*\)/
32
+ HSL_COLOR = /hsl\(\s*((#{HSL_INT}|#{pct}),\s*){2}(#{HSL_INT}|#{pct})\s*\)/
33
+ HSLA_COLOR = /hsla\(\s*((#{HSL_INT}|#{pct}),\s*){3}(#{OPACITY})\s*\)/
33
34
 
34
35
  # Colors can be of several types: keywords, hexadecimal strings, RGB and
35
36
  # RGBA formats. The type of the color is set on initialisation, so that
@@ -253,7 +254,7 @@ module Stylish #:nodoc:
253
254
  rgb << v
254
255
  elsif v =~ /^#{RGB_INT}$/
255
256
  rgb << v.to_i
256
- elsif v =~ PERCENTAGE
257
+ elsif Percentage.match? v
257
258
  rgb << (v.chop.to_f * 256 / 100).round
258
259
  end
259
260
 
@@ -503,7 +504,7 @@ module Stylish #:nodoc:
503
504
  map do |value|
504
505
  if value =~ /^#{RGB_INT}$/
505
506
  value.to_i
506
- elsif value =~ PERCENTAGE
507
+ elsif Percentage.match? value
507
508
  (value.chop.to_f * 255 / 100).round
508
509
  else
509
510
  value.to_f
data/lib/stylish/core.rb CHANGED
@@ -1,9 +1,5 @@
1
1
  module Stylish
2
2
 
3
- # Regular expressions matching a percentage, and matching only a percentage.
4
- PCT = /-?(0\.)?\d+%/
5
- PERCENTAGE = /^#{PCT}$/
6
-
7
3
  # A list of all valid HTML5 elements. Used primarily in the stylesheet
8
4
  # generation DSL as method names.
9
5
  HTML_ELEMENTS = [:html, :head, :title, :base, :link, :meta, :style, :script,
@@ -21,6 +17,28 @@ module Stylish
21
17
  :command, :bb, :menu, :legend, :div, :h1, :h2, :h3, :h4,
22
18
  :h5, :h6]
23
19
 
20
+ class Stylesheet < Tree::SelectorScope
21
+ accept_format(/\s*/m, "\n")
22
+
23
+ # Stylesheets are pure aggregate objects; they can contain child nodes,
24
+ # but have no data of their own. Their initializer therefore accepts no
25
+ # arguments.
26
+ def initialize
27
+ @nodes = []
28
+ end
29
+
30
+ # Stylesheets are the roots of selector trees.
31
+ def root?
32
+ true
33
+ end
34
+
35
+ # Recursively serialise the tree to a stylesheet.
36
+ def to_s(symbols = {})
37
+ return "" if @nodes.empty?
38
+ @nodes.map {|node| node.to_s(symbols) }.join(self.class.format)
39
+ end
40
+ end
41
+
24
42
  # Rule objects represent CSS rules, and serialise to them. They possess one
25
43
  # or more selectors, and zero or more declarations. In addition to their
26
44
  # importance as the major building-blocks of stylesheets, they act as the
@@ -45,6 +63,7 @@ module Stylish
45
63
  include Formattable, Tree::Leaf
46
64
 
47
65
  attr_reader :selectors, :declarations
66
+ accept_format(/^\s*%s\s*\{\s*%s\s*\}\s*$/m, "%s {%s}")
48
67
 
49
68
  # Every Rule must have at least one selector, but may have any number of
50
69
  # declarations. Empty rules are often used in stylesheets to indicate
@@ -61,8 +80,6 @@ module Stylish
61
80
  # This makes Rule objects a very flexible foundation for the higher-level
62
81
  # data structures and APIs in Stylish.
63
82
  def initialize(selectors, declarations)
64
- accept_format(/^\s*%s\s*\{\s*%s\s*\}\s*$/m, "%s {%s}")
65
-
66
83
  @selectors = selectors.inject(Selectors.new) do |ss, s|
67
84
  ss << s
68
85
  end
@@ -74,7 +91,7 @@ module Stylish
74
91
 
75
92
  # Serialise the rule to valid CSS code.
76
93
  def to_s(symbols = {}, scope = "")
77
- sprintf(@format, selectors.join(symbols, scope),
94
+ sprintf(self.class.format, selectors.join(symbols, scope),
78
95
  @declarations.to_s(symbols))
79
96
  end
80
97
  end
@@ -201,20 +218,14 @@ module Stylish
201
218
  class Selectors < Array
202
219
  include Formattable
203
220
 
204
- # Since a group of Selectors is just a specialised kind of array, all that
205
- # is done in its initialiser, regardless of arguments, is to set the
206
- # default serialisation format.
207
- def initialize(*args)
208
- accept_format(/^\s*,\s*$/m, ", ")
209
- super
210
- end
221
+ accept_format(/^\s*,\s*$/m, ", ")
211
222
 
212
223
  # The join method overrides the superclass' method in order to always use a
213
224
  # specific separator, and so that the scope that the selectors are being
214
225
  # used in can be passed through when Rules etc. are serialised.
215
226
  def join(symbols = {}, scope = "")
216
227
  self.inject("") do |ss, s|
217
- (ss.empty? ? "" : ss + self.format) + s.to_s(symbols, scope)
228
+ (ss.empty? ? "" : ss + self.class.format) + s.to_s(symbols, scope)
218
229
  end
219
230
  end
220
231
 
@@ -232,10 +243,10 @@ module Stylish
232
243
  include Formattable
233
244
 
234
245
  attr_accessor :value
246
+ accept_format(/^\s*%s\s*:\s*%s;\s*$/m, "%s:%s;")
235
247
 
236
248
  # Each Declaration has a property name and a value.
237
249
  def initialize(name, value)
238
- accept_format(/^\s*%s\s*:\s*%s;\s*$/m, "%s:%s;")
239
250
  self.value = value
240
251
  self.name = name
241
252
  end
@@ -270,7 +281,7 @@ module Stylish
270
281
  value = @value.to_s
271
282
  end
272
283
 
273
- sprintf(@format, @property_name.to_s, value)
284
+ sprintf(self.class.format, @property_name.to_s, value)
274
285
  end
275
286
  end
276
287
 
@@ -280,18 +291,12 @@ module Stylish
280
291
  class Declarations < Array
281
292
  include Formattable
282
293
 
283
- # The allowed format is any string consisting only of whitespace
284
- # characters, including newline. The default format string is a single
285
- # space, which is probably the most common choice in hand-written CSS.
286
- def initialize(*args)
287
- accept_format(/^\s*$/m, " ")
288
- super
289
- end
294
+ accept_format(/^\s*$/m, " ")
290
295
 
291
296
  # The format attribute is always used as the separator when joining the
292
297
  # elements of a Declarations object.
293
298
  def join
294
- super(@format)
299
+ super(self.class.format)
295
300
  end
296
301
 
297
302
  # Returns a string by converting each element to a string, separated by the
@@ -300,9 +305,21 @@ module Stylish
300
305
  # CSS code.
301
306
  def to_s(symbols = {})
302
307
  self.inject("") do |a, o|
303
- a << (a.empty? ? "" : @format) << o.to_s(symbols)
308
+ a << (a.empty? ? "" : self.class.format) << o.to_s(symbols)
304
309
  end
305
310
  end
306
311
  end
307
312
 
313
+ # PropertyBundle objects are simple wrappers around property values with
314
+ # multiple values.
315
+ class PropertyBundle < Array
316
+ include Formattable
317
+
318
+ accept_format(/^\s*,\s*$/m, ", ")
319
+
320
+ def to_s
321
+ self.join(self.class.format)
322
+ end
323
+ end
324
+
308
325
  end
@@ -1,5 +1,34 @@
1
1
  module Stylish
2
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
+ accept_format(/^url\(\s*('|")?%s\1\s*\)$/, "url('%s')")
10
+
11
+ # Image instances are serialised to URI values. The path to the image file
12
+ # can be surrounded by either single quotes, double quotes or neither;
13
+ # single quotes are the default in Stylish.
14
+ def initialize(path)
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(self.class.format, path.to_s)
29
+ end
30
+ end
31
+
3
32
  # The Background class is a specialised kind of Declaration, geared towards
4
33
  # dealing with the oddities of the background family of declarations, which
5
34
  # can exist in both long- and shorthand forms.
@@ -19,15 +48,6 @@ module Stylish
19
48
  # weighting to only override specific parts of other rules' background
20
49
  # declarations.
21
50
  class Background < Declaration
22
- attr_reader :color,
23
- :image,
24
- :repeat,
25
- :position,
26
- :attachment,
27
- :origin,
28
- :break,
29
- :compressed
30
-
31
51
  PROPERTIES = [
32
52
  [:color, "background-color"],
33
53
  [:image, "background-image"],
@@ -41,105 +61,134 @@ module Stylish
41
61
  REPEAT_VALUES = ["repeat-x", "repeat-y", "repeat",
42
62
  "space", "round", "no-repeat"]
43
63
  ATTACHMENT_VALUES = ["scroll", "fixed", "local"]
44
- HORIZONTAL_POSITIONS = ["left", "center", "right"]
45
- VERTICAL_POSITIONS = ["top", "center", "bottom"]
46
64
  ORIGIN_VALUES = ["border-box", "padding-box", "content-box"]
47
65
  BREAK_VALUES = ["bounding-box", "each-box", "continuous"]
48
66
 
67
+ PROPERTIES.each do |name, value|
68
+ attr_reader name
69
+ end
70
+
49
71
  # Create a new Background object with the specified properties.
50
72
  def initialize(options)
51
- accept_format(/^\s*%s\s*:\s*%s;\s*$/m, "%s:%s;")
52
73
  self.value = options
53
74
  end
54
75
 
55
76
  # Input validation for colours is handled by the Color class, which will
56
77
  # raise an ArgumentError if the argument is an invalid colour value.
78
+ #
79
+ # coloured = Background.new :color => "545454"
80
+ # coloured.to_s # => "background-color:#545454;"
81
+ #
57
82
  def color=(val)
58
83
  @color = Color.new(val)
59
84
  end
60
85
 
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.
86
+ # Set the background image.
64
87
  #
65
88
  # background = Background.new :image => "sky.png", :compressed => true
66
89
  # background.to_s # => "background:url('sky.png');"
67
90
  #
68
- # background.image = ["ball.png", "grass.png"]
91
+ def image=(path)
92
+ @image = Image.new(path)
93
+ end
94
+
95
+ # As of CSS3, elements may have multiple background images.
96
+ #
97
+ # background.images = ["ball.png", "grass.png"]
69
98
  # background.to_s # => "background:url('ball.png'), url('grass.png');"
70
99
  #
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
100
+ def images=(paths)
101
+ @image = paths.inject(PropertyBundle.new) do |is, i|
102
+ is << Image.new(i)
81
103
  end
82
104
  end
83
105
 
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.
106
+ # Set the background repeat.
107
+ #
108
+ # nonrepeating = Background.new :repeat => "no-repeat"
109
+ # nonrepeating.to_s # => "background-repeat:no-repeat;"
110
+ #
111
+ def repeat=(repeat)
112
+ @repeat = repeat if REPEAT_VALUES.include? repeat
113
+ end
114
+
115
+ # As of CSS3, the background-repeat property may have multiple values.
87
116
  #
88
- # repeating = Background.new :repeat => ["repeat-x", "repeat-y"]
117
+ # repeating = Background.new :repeats => ["repeat-x", "repeat-y"]
89
118
  # repeating.to_s # => "background-repeat:repeat-x, repeat-y;"
90
119
  #
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
120
+ def repeats=(repeats)
121
+ @repeat = repeats.inject(PropertyBundle.new) do |rs, r|
122
+ rs << r if REPEAT_VALUES.include? r
123
+ rs
101
124
  end
102
125
  end
103
126
 
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]
127
+ # Positions have an x and a y value, and are handled by a specialised
128
+ # Position class. They should be passed an array of two values, e.g.
129
+ #
130
+ # positioned = Background.new :position => ["100%", 0]
131
+ # positioned.to_s # => "background-position:100% 0;"
132
+ #
133
+ # See the documentation for the Position class for further details on
134
+ # permitted position types.
135
+ def position=(position)
136
+ @position = Position.new(position[0], position[1])
137
+ end
138
+
139
+ # As of CSS3, elements may have multiple background images, and
140
+ # consequently they will need to be separately positioned as well.
141
+ #
142
+ # p = Background.new :positions => [["left", "top"], ["right", "top"]]
143
+ # p.to_s # => "background-position:left top, right top;"
144
+ #
145
+ def positions=(positions)
146
+ @position = positions.inject(PropertyBundle.new) do |ps, p|
147
+ ps << Position.new(p[0], p[1])
109
148
  end
110
149
  end
111
150
 
112
151
  # The background-attachment property takes a limited range of values, so
113
152
  # 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
153
+ #
154
+ # attached = Background.new :attachment => "fixed"
155
+ # attached.to_s # => "background-attachment:fixed;"
156
+ #
157
+ def attachment=(attachment)
158
+ @attachment = attachment if ATTACHMENT_VALUES.include? attachment
159
+ end
160
+
161
+ # As of CSS3 elements may have multiple background-attachment values.
162
+ #
163
+ # atts = Background.new :attachments => ["fixed", "scroll"]
164
+ # atts.to_s # => "background-attachment:fixed, scroll;"
165
+ #
166
+ def attachments=(attachments)
167
+ @attachment = attachments.inject(PropertyBundle.new) do |as, a|
168
+ as << a if ATTACHMENT_VALUES.include? a
169
+ as
124
170
  end
125
171
  end
126
172
 
127
173
  # The background-origin property specifies the background positioning area.
128
- # It is a CSS3 property which takes multiple values.
129
174
  #
130
- # original = Background.new :origin => ["padding-box", "content-box"]
131
- # original.to_s # => background-origin:padding-box, content-box;
175
+ # original = Background.new :origin => "content-box"
176
+ # original.to_s # => background-origin:content-box;
132
177
  #
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
178
+ def origin=(origin)
179
+ @origin = origin if ORIGIN_VALUES.include? origin
180
+ end
181
+
182
+ # The background-origin property is a CSS3 property and can take multiple
183
+ # values.
184
+ #
185
+ # origs = Background.new :origin => ["padding-box", "content-box"]
186
+ # origs.to_s # => background-origin:padding-box, content-box;
187
+ #
188
+ def origins=(origins)
189
+ @origin = origins.inject(PropertyBundle.new) do |os, o|
190
+ os << o if ORIGIN_VALUES.include? o
191
+ os
143
192
  end
144
193
  end
145
194
 
@@ -164,11 +213,11 @@ module Stylish
164
213
  # background-repeat:no-repeat; background-position:0 0;
165
214
  #
166
215
  def compressed=(val)
167
- @compressed = val == true || nil
216
+ @compressed = val == true
168
217
  end
169
218
 
170
- # Override Declaration#name, since it's not compatible with the
171
- # internals of this class.
219
+ # Override Declaration#name, since it's not compatible with the internals
220
+ # of this class.
172
221
  def name
173
222
  PROPERTIES.reject {|n, p| p.nil? }.map {|n, p|
174
223
  value = self.send(n)
@@ -176,8 +225,8 @@ module Stylish
176
225
  }.compact
177
226
  end
178
227
 
179
- # Override Declaration#name=, since it's not compatible with the
180
- # internals of this class.
228
+ # Override Declaration#name=, since it's not compatible with the internals
229
+ # of this class.
181
230
  def name=(val)
182
231
  raise NoMethodError, "name= is not defined for Background."
183
232
  end
@@ -199,8 +248,10 @@ module Stylish
199
248
  raise ArgumentError, "Argument must be a hash of background properties"
200
249
  end
201
250
 
202
- PROPERTIES.each do |name, property|
251
+ PROPERTIES.each do |name, value|
252
+ plural = (name.to_s + "s").to_sym
203
253
  self.send(:"#{name.to_s}=", options[name]) if options[name]
254
+ self.send(:"#{name.to_s}s=", options[plural]) if options[plural]
204
255
  end
205
256
  end
206
257
 
@@ -218,7 +269,9 @@ module Stylish
218
269
  if @compressed
219
270
  "background:#{self.value(true).map {|p, v| v }.compact.join(" ")};"
220
271
  else
221
- self.value(true).map {|p, v| sprintf(@format, p, v.to_s) }.join(" ")
272
+ self.value(true).map {|p, v|
273
+ sprintf(self.class.format, p, v.to_s)
274
+ }.join(" ")
222
275
  end
223
276
  end
224
277
  end
@@ -1,9 +1,9 @@
1
1
  module Stylish
2
2
 
3
3
  # The Formattable mixin gives an API for changing the output format of any of
4
- # Stylish's core objects, as long as the new output format matches the regex
5
- # provided in the object's initialize method. This is because in order to
6
- # serialise a valid CSS document, strings must be joined in the correct way.
4
+ # Stylish's core classes, as long as the new output format matches the regex
5
+ # provided when the class was declared. This is because in order to generate
6
+ # valid CSS, strings must be joined in the correct way.
7
7
  #
8
8
  # For example, a rule's selectors must be comma-separated and its
9
9
  # declarations must be wrapped in curly braces, while each declaration
@@ -11,56 +11,47 @@ module Stylish
11
11
  # terminating with a semicolon.
12
12
  #
13
13
  # In order to employ the Formattable mixin, it must be included into a class
14
- # and the #accept_format method must be called in the class's #initialize
15
- # method, setting the allowed format and the default format.
14
+ # and the accept_format method must be called with the allowed format (as a
15
+ # regular expression) and the default format (a string).
16
16
  #
17
17
  # class Stylesheet
18
18
  # include Formattable
19
- #
20
- # def initialize
21
- # accept_format(/\s*/m, "\n")
22
- # end
19
+ # accept_format(/\s*/m, "\n")
23
20
  # end
24
21
  #
25
- # If one then creates a new Stylesheet object, one can modify the way it's
26
- # formatted when serialised to CSS code.
27
- #
28
- # stylesheet = Stylesheet.new
29
- # stylesheet.format # => "\n"
30
- # stylesheet.format = "\n\n"
31
- # stylesheet.format # => "\n\n"
32
- #
33
22
  module Formattable
34
- attr_reader :format
35
23
 
36
- # Attribute writer that sets the object's format attribute. The argument
37
- # must match the regular expression passed to #accept_format in the
38
- # object's initialize method.
39
- def format=(format)
40
- if format_validates?(format)
41
- @format = format
42
- else
43
- raise ArgumentError, "Not an allowed format."
44
- end
24
+ def self.included(base)
25
+ base.extend(FormattableMethods)
45
26
  end
46
27
 
47
- private
48
-
49
- # Because this is a private method, it . Ruby's object system and
50
- # metaprogramming facilities means that these restrictions are evaded
51
- # without much difficulty; making this method private indicates intent
52
- # rather than providing a serious barrier to inadvisable behaviour.
53
- #
54
- # Within Stylish, #accept_format is used only in the initialize method of
55
- # the various core classes which may be serialised to CSS code.
56
- def accept_format(pattern, default)
57
- @format_pattern = pattern if pattern.is_a? Regexp
58
- self.format = default
59
- end
60
-
61
- # Checks whether a particular string matches the pattern acceptable formats
62
- def format_validates?(format)
63
- format =~ @format_pattern
28
+ module FormattableMethods
29
+ attr_reader :format
30
+
31
+ def format=(format)
32
+ if format_validates?(format)
33
+ @format = format
34
+ else
35
+ raise ArgumentError, "Not an allowed format."
36
+ end
37
+ end
38
+
39
+ def accept_format(pattern, default)
40
+ @format_pattern = pattern if pattern.is_a? Regexp
41
+ self.format = default
42
+ end
43
+
44
+ def format_validates?(format_string)
45
+ format_string =~ @format_pattern
46
+ end
47
+
48
+ def inherited(subclass)
49
+ ["format", "format_pattern"].each do |attribute|
50
+ instance_var = "@#{attribute}"
51
+ subclass.instance_variable_set(instance_var,
52
+ instance_variable_get(instance_var))
53
+ end
54
+ end
64
55
  end
65
56
  end
66
57
 
@@ -55,13 +55,13 @@ module Stylish
55
55
  key = key.to_s.sub("_", "-").to_sym
56
56
 
57
57
  if key == :background
58
- if value.any? {|k,v| v.is_a? Symbol }
58
+ if includes_symbols? value
59
59
  declaration = Variable.new(value, Background)
60
60
  else
61
61
  declaration = Background.new(value)
62
62
  end
63
63
  elsif key == :color
64
- if value.is_a? Symbol
64
+ if includes_symbols? value
65
65
  value = Variable.new(value, Color)
66
66
  else
67
67
  value = Color.new(value)
@@ -69,7 +69,7 @@ module Stylish
69
69
 
70
70
  declaration = Declaration.new("color", value)
71
71
  else
72
- value = Variable.new(value) if value.is_a? Symbol
72
+ value = Variable.new(value) if includes_symbols? value
73
73
  declaration = Declaration.new(key, value)
74
74
  end
75
75
 
@@ -77,6 +77,18 @@ module Stylish
77
77
  end
78
78
  end
79
79
 
80
+ def self.includes_symbols?(value)
81
+ return true if value.is_a? Symbol
82
+
83
+ if value.is_a? Array
84
+ return value.any? {|v| includes_symbols?(v) }
85
+ elsif value.is_a? Hash
86
+ return value.values.any? {|v| includes_symbols?(v) }
87
+ end
88
+
89
+ false
90
+ end
91
+
80
92
  # Variables are elements of a selector tree that haven't been assigned
81
93
  # values yet. When a tree that includes Variable objects is serialised, it
82
94
  # must be passed a symbol table so that the variables may be given values.
@@ -109,16 +121,36 @@ module Stylish
109
121
  # the tree is traversed. Nodes must then serialise themselves, and if
110
122
  # they contain Variables they must pass them the symbol table so that
111
123
  # they can be resolved to a given value.
112
- def to_s(symbols, scope = "")
113
- if @constructor.nil?
114
- symbols[@name]
115
- elsif @name.is_a? Symbol
116
- @constructor.new(symbols[@name]).to_s(symbols, scope)
124
+ def to_s(symbol_table, scope = "")
125
+ evald = eval(nil, symbol_table)
126
+
127
+ unless @constructor.nil?
128
+ @constructor.new(evald).to_s(symbol_table, scope)
129
+ else
130
+ evald
131
+ end
132
+ end
133
+
134
+ # Recursively replace symbols with values. This allows for symbol lookup
135
+ # within more complex nested structures of arrays and hashes, created by
136
+ # e.g. background declarations.
137
+ def eval(name_or_hash, symbol_table)
138
+ replaceable = name_or_hash || @name
139
+
140
+ if replaceable.is_a? Symbol
141
+ symbol_table[replaceable]
142
+ elsif replaceable.is_a?(Hash) || replaceable.is_a?(Array)
143
+ replaceable.to_a.inject(replaceable.class.new) do |acc, el|
144
+ if acc.is_a? Hash
145
+ acc[el[0]] = eval(el[1], symbol_table)
146
+ else
147
+ acc << eval(el, symbol_table)
148
+ end
149
+
150
+ acc
151
+ end
117
152
  else
118
- @constructor.new(@name.to_a.inject({}) {|a, e|
119
- a[e.first] = symbols[e.last]
120
- a
121
- }).to_s(symbols, scope)
153
+ replaceable
122
154
  end
123
155
  end
124
156
  end
@@ -0,0 +1,94 @@
1
+ module Stylish
2
+
3
+ class Percentage
4
+ PCTSTR = /-?(0\.)?\d+%/
5
+
6
+ def initialize(value)
7
+ value = value[0..-2]
8
+
9
+ if value =~ /^\d+$/
10
+ @number = value.to_i
11
+ else
12
+ @number = value.to_f
13
+ end
14
+ end
15
+
16
+ def to_s(symbols = {}, scope = "")
17
+ @number.to_s + "%"
18
+ end
19
+
20
+ def self.match?(value)
21
+ value =~ /^#{PCTSTR}$/
22
+ end
23
+ end
24
+
25
+ class Length
26
+ UNITS = [
27
+ # Relative length units
28
+ "em", "ex", "px",
29
+ # Absolute length units
30
+ "in", "cm", "mm", "pt", "pc"]
31
+
32
+ attr_reader :unit, :value
33
+
34
+ def initialize(value)
35
+ self.unit = value.match(/(#{UNITS * "|"})$/)[0]
36
+ self.value = value[0..-3]
37
+ end
38
+
39
+ def value=(value)
40
+ if value.is_a? Numeric
41
+ @value = value
42
+ elsif value =~ /^\d+$/
43
+ @value = value.to_i
44
+ elsif value =~ /^\d+\.\d+$/
45
+ @value = value.to_f
46
+ end
47
+ end
48
+
49
+ def unit=(unit)
50
+ @unit = unit if UNITS.include? unit
51
+ end
52
+
53
+ def to_s(symbols = {}, scope = "")
54
+ self.value.to_s + self.unit
55
+ end
56
+ end
57
+
58
+ class Position
59
+ HORIZONTAL = ["left", "center", "right"]
60
+ VERTICAL = ["top", "center", "bottom"]
61
+
62
+ attr_reader :x, :y
63
+
64
+ def initialize(xpos, ypos)
65
+ self.x = xpos || "center"
66
+ self.y = ypos || "center"
67
+ end
68
+
69
+ def x=(xpos)
70
+ if HORIZONTAL.include?(xpos) || xpos.to_i == 0
71
+ @x = xpos
72
+ elsif Percentage.match? xpos
73
+ @x = Percentage.new(xpos)
74
+ else
75
+ @x = Length.new(xpos)
76
+ end
77
+ end
78
+
79
+ def y=(ypos)
80
+ if VERTICAL.include?(ypos) || ypos.to_i == 0
81
+ @y = ypos
82
+ elsif Percentage.match? ypos
83
+ @y = Percentage.new(ypos)
84
+ else
85
+ @y = Length.new(ypos)
86
+ end
87
+ end
88
+
89
+ def to_s(symbols = {}, scope = "")
90
+ @x.to_s + " " + @y.to_s
91
+ end
92
+ end
93
+
94
+ end
data/lib/stylish/tree.rb CHANGED
@@ -41,14 +41,13 @@ module Stylish
41
41
  include Formattable, Node
42
42
 
43
43
  attr_reader :nodes
44
+ accept_format(/\s*/m, "\n")
44
45
 
45
46
  def initialize(selector)
46
- accept_format(/\s*/m, "\n")
47
-
48
47
  @nodes = []
49
48
  @scope = selector
50
49
  end
51
-
50
+
52
51
  # Return the child node at the given index.
53
52
  def [](index)
54
53
  @nodes[index]
@@ -89,7 +88,7 @@ module Stylish
89
88
 
90
89
  @nodes.map {|node|
91
90
  node.to_s(symbols, @scope.to_s(symbols, scope))
92
- }.join(@format)
91
+ }.join(self.class.format)
93
92
  end
94
93
 
95
94
  # Return the node's child nodes.
data/lib/stylish.rb CHANGED
@@ -8,10 +8,8 @@ module Stylish
8
8
  require STYLISH_PATH + 'formattable'
9
9
  require STYLISH_PATH + 'tree'
10
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'
11
+ require STYLISH_PATH + 'numeric'
12
+ require STYLISH_PATH + 'extended'
15
13
  require STYLISH_PATH + 'color'
16
14
  require STYLISH_PATH + 'generate'
17
15
  end
@@ -3,7 +3,7 @@ class BackgroundTest < Test::Unit::TestCase
3
3
  def setup
4
4
  @composite = Stylish::Background.new(:color => "#CCC",
5
5
  :image => "images/test.png", :repeat => "no-repeat",
6
- :position => "left", :attachment => "scroll")
6
+ :position => ["left", "top"], :attachment => "scroll")
7
7
  end
8
8
 
9
9
  def test_valid_background_colors
@@ -22,7 +22,7 @@ class BackgroundTest < Test::Unit::TestCase
22
22
  end
23
23
 
24
24
  def test_multiple_background_images
25
- bg = Stylish::Background.new :image =>
25
+ bg = Stylish::Background.new :images =>
26
26
  ["flower.png", "ball.png", "grass.png"]
27
27
 
28
28
  assert_equal(3, bg.image.length)
@@ -35,20 +35,27 @@ class BackgroundTest < Test::Unit::TestCase
35
35
  end
36
36
 
37
37
  def test_multiple_background_repeats
38
- bg = Stylish::Background.new :repeat => ["repeat-x", "repeat-y"]
38
+ bg = Stylish::Background.new :repeats => ["repeat-x", "repeat-y"]
39
39
 
40
40
  assert_equal(2, bg.repeat.length)
41
41
  assert_equal("background-repeat:repeat-x, repeat-y;", bg.to_s)
42
42
  end
43
43
 
44
44
  def test_valid_background_positions
45
- assert_equal(2, @composite.position.length)
46
- assert_equal("left", @composite.position[0])
47
- assert_equal("center", @composite.position[1])
45
+ assert_equal("left", @composite.position.x)
46
+ assert_equal("top", @composite.position.y)
48
47
  end
49
48
 
50
- def test_valid_compression
49
+ def test_position_serialisation
50
+ positioned = Stylish::Background.new({:position => ["100%", 0]})
51
+ assert_equal("background-position:100% 0;", positioned.to_s)
52
+ end
53
+
54
+ def test_compression
51
55
  assert(Stylish::Background.new(:compressed => true))
56
+ assert_equal(false, Stylish::Background.new(:compressed => "true").compressed)
57
+ assert_equal(false, Stylish::Background.new(:compressed => "false").compressed)
58
+ assert_nil(Stylish::Background.new(:compressed => nil).compressed)
52
59
  end
53
60
 
54
61
  def test_valid_background_attachments
@@ -58,7 +65,7 @@ class BackgroundTest < Test::Unit::TestCase
58
65
  end
59
66
 
60
67
  def test_multiple_attachments
61
- glued = Stylish::Background.new :attachment => ["local", "fixed"]
68
+ glued = Stylish::Background.new :attachments => ["local", "fixed"]
62
69
 
63
70
  assert_equal(2, glued.attachment.length)
64
71
  assert_equal("background-attachment:local, fixed;", glued.to_s)
@@ -71,7 +78,7 @@ class BackgroundTest < Test::Unit::TestCase
71
78
  end
72
79
 
73
80
  def test_origins
74
- original = Stylish::Background.new :origin => ["border-box", "padding-box"]
81
+ original = Stylish::Background.new :origins => ["border-box", "padding-box"]
75
82
 
76
83
  assert_equal(2, original.origin.length)
77
84
  assert_equal("background-origin:border-box, padding-box;", original.to_s)
@@ -83,31 +90,15 @@ class BackgroundTest < Test::Unit::TestCase
83
90
  assert_equal("background-break:bounding-box;", broken.to_s)
84
91
  end
85
92
 
86
- def test_invalid_image_values
87
- assert_nil(Stylish::Background.new(:image => []).image)
88
- assert_nil(Stylish::Background.new(:image => {}).image)
89
- end
90
-
91
93
  def test_invalid_background_repeats
92
94
  assert_nil(Stylish::Background.new(:repeat => 'maxim-gun').repeat)
93
95
  end
94
96
 
95
- def test_invalid_background_positions
96
- assert_nil(Stylish::Background.new(:position => "green ideas").position)
97
- assert_nil(Stylish::Background.new(:position => "top").position)
98
- end
99
-
100
97
  def test_invalid_background_attachments
101
98
  assert_nil(Stylish::Background.new(:attachment => "static").attachment)
102
99
  assert_nil(Stylish::Background.new(:attachment => "unhooked").attachment)
103
100
  end
104
101
 
105
- def test_invalid_compression
106
- assert_nil(Stylish::Background.new(:compressed => "true").compressed)
107
- assert_nil(Stylish::Background.new(:compressed => "false").compressed)
108
- assert_nil(Stylish::Background.new(:compressed => nil).compressed)
109
- end
110
-
111
102
  def test_declaration_property
112
103
  assert_equal(["background-color", "background-repeat"],
113
104
  Stylish::Background.new(:color => "red", :repeat => "no-repeat").name)
@@ -1,40 +1,21 @@
1
1
  class FormattableTest < Test::Unit::TestCase
2
2
 
3
- def setup
4
- @selectors = Stylish::Selectors.new
5
- @declaration = Stylish::Declaration.new("background-color", "#000")
6
- @background = Stylish::Background.new("color" => "red")
7
- end
8
-
9
3
  def test_reading_default_formats
10
- assert_equal(", ", @selectors.format)
11
- assert_equal("%s:%s;", @declaration.format)
12
- assert_equal("%s:%s;", @declaration.format)
4
+ assert_equal(", ", Stylish::Selectors.format)
5
+ assert_equal("%s:%s;", Stylish::Declaration.format)
6
+ assert_equal("%s:%s;", Stylish::Background.format)
13
7
  end
14
8
 
15
9
  def test_setting_allowed_formats
16
10
  assert_nothing_raised do
17
- @selectors.format = ",\n"
11
+ Stylish::Selectors.format = ",\n"
12
+ Stylish::Selectors.format = ", "
18
13
  end
19
14
  end
20
15
 
21
16
  def test_setting_disallowed_formats
22
17
  assert_raise ArgumentError do
23
- @selectors.format = "//"
24
- end
25
- end
26
-
27
- class ConfusedAboutFormatting
28
- include Stylish::Formattable
29
-
30
- def initialize
31
- accept_format(/^\s*$/, "///")
32
- end
33
- end
34
-
35
- def test_class_definition_with_disallowed_format
36
- assert_raise ArgumentError do
37
- ConfusedAboutFormatting.new
18
+ Stylish::Selectors.format = "//"
38
19
  end
39
20
  end
40
21
  end
@@ -77,4 +77,21 @@ class GenerateTest < Test::Unit::TestCase
77
77
  assert_equal("A glorious comment!", style.comments[0].header)
78
78
  assert_equal("An additional note.", style.comments[1].lines[0])
79
79
  end
80
+
81
+ def test_complex_background
82
+ style = Stylish.generate do
83
+ div :background => {
84
+ :images => ["tl.png", "tr.png", "br.png", "bl.png"],
85
+ :positions => [
86
+ ["left", "top"],
87
+ ["right", "top"],
88
+ ["right", "bottom"],
89
+ ["left", :pos]]}
90
+ end
91
+
92
+ assert_equal("div {background-image:url('tl.png'), url('tr.png'), " +
93
+ "url('br.png'), url('bl.png'); " +
94
+ "background-position:left top, right top, right bottom, left bottom;}",
95
+ style.to_s({:pos => "bottom"}))
96
+ end
80
97
  end
@@ -0,0 +1,20 @@
1
+ class LengthTest < Test::Unit::TestCase
2
+
3
+ def test_different_units
4
+ assert_equal("px", Stylish::Length.new("10px").unit)
5
+ end
6
+
7
+ def test_serialisation
8
+ length = Stylish::Length.new("5em")
9
+ assert_equal("5em", length.to_s)
10
+ end
11
+
12
+ def test_accessors
13
+ length = Stylish::Length.new("30mm")
14
+ length.unit = "px"
15
+ length.value = 50
16
+
17
+ assert_equal("px", length.unit)
18
+ assert_equal(50, length.value)
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ class PositionTest < Test::Unit::TestCase
2
+
3
+ def setup
4
+ @pos = Stylish::Position.new(0, 0)
5
+ end
6
+
7
+ def test_percentage_positions
8
+ percentage = Stylish::Position.new("5%", "10%")
9
+ assert_equal("5%", percentage.x.to_s)
10
+ assert_equal("10%", percentage.y.to_s)
11
+ end
12
+
13
+ def test_keyword_positions
14
+ keyed = Stylish::Position.new("left", "top")
15
+ assert_equal("left", keyed.x)
16
+ assert_equal("top", keyed.y)
17
+ end
18
+
19
+ def test_serialisation
20
+ assert_equal("0 0", @pos.to_s)
21
+ end
22
+ 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.4
4
+ version: 0.1.5
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-25 00:00:00 -07:00
12
+ date: 2009-05-03 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -27,13 +27,12 @@ files:
27
27
  - Rakefile
28
28
  - VERSION.yml
29
29
  - lib/stylish.rb
30
- - lib/stylish/background.rb
31
30
  - lib/stylish/color.rb
32
31
  - lib/stylish/core.rb
32
+ - lib/stylish/extended.rb
33
33
  - lib/stylish/formattable.rb
34
34
  - lib/stylish/generate.rb
35
- - lib/stylish/image.rb
36
- - lib/stylish/stylesheet.rb
35
+ - lib/stylish/numeric.rb
37
36
  - lib/stylish/tree.rb
38
37
  - test/background_test.rb
39
38
  - test/color_test.rb
@@ -43,6 +42,8 @@ files:
43
42
  - test/formattable_test.rb
44
43
  - test/generate_test.rb
45
44
  - test/image_test.rb
45
+ - test/length_test.rb
46
+ - test/position_test.rb
46
47
  - test/rule_test.rb
47
48
  - test/selector_test.rb
48
49
  - test/selectors_test.rb
@@ -84,6 +85,8 @@ test_files:
84
85
  - test/formattable_test.rb
85
86
  - test/generate_test.rb
86
87
  - test/image_test.rb
88
+ - test/length_test.rb
89
+ - test/position_test.rb
87
90
  - test/rule_test.rb
88
91
  - test/selector_test.rb
89
92
  - test/selectors_test.rb
data/lib/stylish/image.rb DELETED
@@ -1,32 +0,0 @@
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
@@ -1,25 +0,0 @@
1
- module Stylish
2
-
3
- class Stylesheet < Tree::SelectorScope
4
-
5
- # Stylesheets are pure aggregate objects; they can contain child nodes,
6
- # but have no data of their own. Their initializer therefore accepts no
7
- # arguments.
8
- def initialize
9
- accept_format(/\s*/m, "\n")
10
- @nodes = []
11
- end
12
-
13
- # Stylesheets are the roots of selector trees.
14
- def root?
15
- true
16
- end
17
-
18
- # Recursively serialise the tree to a stylesheet.
19
- def to_s(symbols = {})
20
- return "" if @nodes.empty?
21
- @nodes.map {|node| node.to_s(symbols) }.join(@format)
22
- end
23
- end
24
-
25
- end