simple_style_sheet 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog ADDED
@@ -0,0 +1,2 @@
1
+ 0.0.1 (February 21, 2012)
2
+ Initial release.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jacek Mikrut
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,144 @@
1
+ SimpleStyleSheet
2
+ ================
3
+
4
+ SimpleStyleSheet is a Ruby gem that **parses a CSS-like Hash style sheet** and then **allows searching for property values of given HTML-like tags**. Tag and property names, as well as their meaning, are up to the gem user.
5
+
6
+ Usage and details
7
+ -----------------
8
+
9
+ ### 1. Style sheet preparation
10
+
11
+ The style sheet is just a CSS-like Hash object. Its keys are either property names or selectors, and values are either property values or nested Hash objects.
12
+
13
+ ##### Property names
14
+
15
+ Property names can be objects of any class and their meaning is up to the user. Most often, they would be String or Symbol instances with content that is some formatting-related property name, like "background-color".
16
+
17
+ ##### Property values
18
+
19
+ The values can also be any objects, except for Hash objects, which are interpreted as nested style sheet definitions.
20
+
21
+ ##### Selectors
22
+
23
+ A selector is a String object that contains one or more custom **tag names**, **ids** and **class names**. For example: `#content message.success`.
24
+
25
+ ##### A style sheet example
26
+
27
+ ```ruby
28
+ style_sheet = {
29
+ "foreground-color" => :white,
30
+ "background-color" => :blue,
31
+
32
+ "#content message.success" => {
33
+ "background-color" => :green,
34
+
35
+ "number" => {
36
+ "background-color" => :inherit
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### 2. Creating a style sheet handler instance
43
+
44
+ Then the style sheet should be passed to the handler:
45
+
46
+ ```ruby
47
+ style_sheet_handler = SimpleStyleSheet::Handler.new(style_sheet)
48
+ ```
49
+
50
+ ### 3. Tag objects
51
+
52
+ Tag objects represent tags in a HTML-like structure. As a tag, any object can be used, provided that it responds to :name, :id, :class_names and :parent messages. For example:
53
+
54
+ ```ruby
55
+ Tag = Struct.new(:name, :id, :class_names, :parent)
56
+
57
+ # Tag objects corresponding to the structure:
58
+ # <section id="content"><message class="success">...</message></section>
59
+ content_tag = Tag.new("section", "content", [], nil)
60
+ message_tag = Tag.new("message", nil, ["success"], content_tag)
61
+ ```
62
+
63
+ ### 4. Getting property value for a given tag
64
+
65
+ Now, a property value for a given tag can be obtained:
66
+
67
+ ```ruby
68
+ style_sheet_handler.value_for(message_tag, "background-color")
69
+ => :green
70
+
71
+ style_sheet_handler.value_for(message_tag, "foreground-color")
72
+ => :white
73
+ ```
74
+
75
+ The #value_for method looks in the style sheet for selectors that match a given tag. If more than one matching selector is found, the one with highest specificity is used (more details below). Then the value it points to for given property name is returned.
76
+
77
+ ### 5. Getting top-level property value
78
+
79
+ The #value_for method, when called without the tag argument, returns top-level property value (defined in the style sheet without a selector), which may be interpreted as default one or as a value for text not included in any tags. For example:
80
+
81
+ ```ruby
82
+ style_sheet_handler.value_for("background-color")
83
+ => :blue
84
+ ```
85
+
86
+ Selector specificity
87
+ --------------------
88
+
89
+ In order to return a property value for a given tag, search for the matching selector of highest specificity is performed.
90
+
91
+ The specificity of each selector is calculated according to rules similar to the standard CSS rules.
92
+
93
+ The specificity is four numbers: **a,b,c,d**.
94
+
95
+ **The rules respected by this gem are**:
96
+
97
+ * **a** is always 0;
98
+ * **b** is the number of ID attributes in the selector;
99
+ * **c** is the number of class names in the selector;
100
+ * **d** is the number of tag names in the selector.
101
+
102
+ For example, a selector `tag#id1.class1 #id2.class2.class3` has specificity equal to `0,2,3,1`.
103
+
104
+ Two selector specificities are compared by succesively comparing their corresponding numbers, from left to right.
105
+
106
+ Property name translator
107
+ ------------------------
108
+
109
+ A property name translator allows using multiple names (or aliases) for a single property name. For example, property named "background-color" can have aliases "background", "bg-color" etc.
110
+
111
+ The translator, that is supposed to be provided by the gem user, should respond to :translate method and return the translation for a given property name, for example:
112
+
113
+ ```ruby
114
+ property_name_translator.translate("bg-color")
115
+ => "background-color"
116
+ ```
117
+
118
+ In order to use the translator, it should be passed as the second argument to SimpleStyleSheet::Handler.new method:
119
+
120
+ ```ruby
121
+ style_sheet_handler = SimpleStyleSheet::Handler.new(style_sheet, property_name_translator)
122
+ ```
123
+
124
+ Installation
125
+ ------------
126
+
127
+ As a Ruby gem, SimpleStyleSheet can be installed either by running
128
+
129
+ ```bash
130
+ gem install simple_style_sheet
131
+ ```
132
+
133
+ or adding
134
+
135
+ ```ruby
136
+ gem "simple_style_sheet"
137
+ ```
138
+
139
+ to the Gemfile and then invoking `bundle install`.
140
+
141
+ License
142
+ -------
143
+
144
+ License is included in the LICENSE file.
@@ -0,0 +1,52 @@
1
+ module SimpleStyleSheet
2
+ class Handler
3
+
4
+ def initialize(style_sheet_hash, property_name_translator=nil)
5
+ @map = {}
6
+ @property_name_translator = property_name_translator
7
+ populate(style_sheet_hash)
8
+ end
9
+
10
+ def value_for(tag=nil, property_name)
11
+ found = if tag
12
+ (@map[final_property_name(property_name)] || [])
13
+ .select { |data| data[:selector].match?(tag) }
14
+ .max_by { |data| data[:selector].specificity }
15
+
16
+ else
17
+ (@map[final_property_name(property_name)] || [])
18
+ .select { |data| data[:selector].empty? }
19
+ .last
20
+
21
+ end
22
+
23
+ found && found[:value]
24
+ end
25
+
26
+ protected
27
+
28
+ def populate(style_sheet_hash, selector=new_selector)
29
+
30
+ style_sheet_hash.each do |key, value|
31
+
32
+ case value
33
+ when Hash
34
+ populate(value, selector + key)
35
+ else
36
+ property_name = final_property_name(key)
37
+
38
+ @map[property_name] ||= []
39
+ @map[property_name] << { :value => value, :selector => selector }
40
+ end
41
+ end
42
+ end
43
+
44
+ def new_selector
45
+ Selector.new
46
+ end
47
+
48
+ def final_property_name(property_name)
49
+ @property_name_translator ? @property_name_translator.translate(property_name) : property_name
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,63 @@
1
+ module SimpleStyleSheet
2
+ class Selector
3
+
4
+ def initialize(string=nil)
5
+ @segments = []
6
+ @specificity = SelectorSpecificity.new
7
+ concat(string) unless string.nil?
8
+ end
9
+
10
+ attr_reader :specificity
11
+
12
+ def concat(string)
13
+ string.scan(/[\w.#]+/).map { |s| SelectorSegment.new(s) }.each do |segment|
14
+ @segments << segment
15
+ @specificity += segment.specificity
16
+ end
17
+ self
18
+ end
19
+
20
+ def +(string)
21
+ duplicate.concat(string)
22
+ end
23
+
24
+ def match?(tag)
25
+ return true if @segments.none?
26
+ return false unless @segments.last.match?(tag)
27
+
28
+ index = @segments.size - 2
29
+ current_tag = tag
30
+
31
+ while index >= 0 && current_tag = current_tag.parent
32
+ if @segments[index].match?(current_tag)
33
+ index -= 1
34
+ next
35
+ end
36
+ end
37
+ index == -1
38
+ end
39
+
40
+ def empty?
41
+ @segments.none?
42
+ end
43
+
44
+ def to_s
45
+ @segments.map { |segment| segment.to_s }.join(" ")
46
+ end
47
+
48
+ def inspect
49
+ "#<#{self.class} #{to_s.inspect}>"
50
+ end
51
+
52
+ def ==(other)
53
+ to_s == other.to_s
54
+ end
55
+
56
+ def duplicate
57
+ d = dup
58
+ d.instance_variable_set( "@segments", @segments.dup)
59
+ d.instance_variable_set("@specificity", @specificity.dup)
60
+ d
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,38 @@
1
+ module SimpleStyleSheet
2
+ class SelectorSegment
3
+
4
+ def initialize(string)
5
+ @tag_name = string.lstrip.slice(/^\w+/)
6
+ @id = string.slice(/(?<=\#)\w+/)
7
+ @class_names = string.scan(/(?<=\.)\w+/)
8
+ end
9
+
10
+ attr_reader :tag_name, :id, :class_names
11
+
12
+ def specificity
13
+ @specificity ||= SelectorSpecificity.new(
14
+ 0,
15
+ id ? 1 : 0,
16
+ class_names.count,
17
+ tag_name ? 1 : 0
18
+ )
19
+ end
20
+
21
+ def match?(tag)
22
+ return false if tag_name && tag_name != tag.name
23
+ return false if id && id != tag.id
24
+ return false if (class_names - tag.class_names).any?
25
+ true
26
+ end
27
+
28
+ def to_s
29
+ "#{tag_name}" +
30
+ (id ? "##{id}" : "") +
31
+ class_names.map { |class_name| ".#{class_name}" }.join
32
+ end
33
+
34
+ def inspect
35
+ "#<#{self.class} #{to_s.inspect}>"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ module SimpleStyleSheet
2
+ class SelectorSpecificity
3
+
4
+ include Comparable
5
+
6
+ def initialize(a=0, b=0, c=0, d=0)
7
+ @a, @b, @c, @d = a, b, c, d
8
+ end
9
+
10
+ attr_accessor :a, :b, :c, :d
11
+
12
+ def <=>(other)
13
+ [:a, :b, :c, :d].each do |name|
14
+ result = send(name) <=> other.send(name)
15
+ return result unless result == 0
16
+ end
17
+ 0
18
+ end
19
+
20
+ def +(other)
21
+ self.class.new(a + other.a, b + other.b, c + other.c, d + other.d)
22
+ end
23
+
24
+ def to_s
25
+ "#{a},#{b},#{c},#{d}"
26
+ end
27
+
28
+ def inspect
29
+ "#<#{self.class} #{to_s.inspect}>"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleStyleSheet
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,9 @@
1
+ require "simple_style_sheet/version"
2
+
3
+ require "simple_style_sheet/selector_specificity"
4
+ require "simple_style_sheet/selector_segment"
5
+ require "simple_style_sheet/selector"
6
+ require "simple_style_sheet/handler"
7
+
8
+ module SimpleStyleSheet
9
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_style_sheet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jacek Mikrut
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-21 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &74598880 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *74598880
25
+ description: Parses a CSS-like Hash style sheet and allows searching for property
26
+ values of HTML-like tags. Tag and property names, as well as their meaning, are
27
+ up to the gem user.
28
+ email: jacekmikrut.software@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/simple_style_sheet.rb
34
+ - lib/simple_style_sheet/handler.rb
35
+ - lib/simple_style_sheet/version.rb
36
+ - lib/simple_style_sheet/selector.rb
37
+ - lib/simple_style_sheet/selector_segment.rb
38
+ - lib/simple_style_sheet/selector_specificity.rb
39
+ - README.md
40
+ - LICENSE
41
+ - Changelog
42
+ homepage: http://github.com/jacekmikrut/simple_style_sheet
43
+ licenses: []
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 1.8.12
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Parses a CSS-like Hash style sheet and allows searching for property values
66
+ of HTML-like tags.
67
+ test_files: []