hexp 0.0.1 → 0.2.0
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/.travis.yml +12 -3
- data/Changelog.md +9 -0
- data/Gemfile +3 -5
- data/Gemfile.devtools +20 -18
- data/Gemfile.lock +97 -84
- data/Rakefile +16 -0
- data/config/flay.yml +2 -2
- data/config/flog.yml +1 -1
- data/config/reek.yml +42 -18
- data/config/rubocop.yml +31 -0
- data/config/yardstick.yml +39 -1
- data/examples/from_nokogiri.rb +77 -0
- data/examples/selector_rewriter_chaining.rb +14 -0
- data/examples/todo.rb +138 -0
- data/examples/widget.rb +64 -0
- data/hexp.gemspec +8 -3
- data/lib/hexp.rb +103 -2
- data/lib/hexp/builder.rb +256 -0
- data/lib/hexp/css_selector.rb +205 -0
- data/lib/hexp/css_selector/parser.rb +74 -0
- data/lib/hexp/css_selector/sass_parser.rb +22 -0
- data/lib/hexp/dom.rb +0 -2
- data/lib/hexp/dsl.rb +27 -0
- data/lib/hexp/errors.rb +21 -0
- data/lib/hexp/h.rb +5 -2
- data/lib/hexp/list.rb +67 -9
- data/lib/hexp/node.rb +197 -41
- data/lib/hexp/node/attributes.rb +176 -0
- data/lib/hexp/node/children.rb +44 -0
- data/lib/hexp/node/css_selection.rb +73 -0
- data/lib/hexp/node/domize.rb +52 -6
- data/lib/hexp/node/normalize.rb +19 -9
- data/lib/hexp/node/pp.rb +32 -0
- data/lib/hexp/node/rewriter.rb +52 -0
- data/lib/hexp/node/selector.rb +59 -0
- data/lib/hexp/nokogiri/equality.rb +61 -0
- data/lib/hexp/nokogiri/reader.rb +27 -0
- data/lib/hexp/sass/selector_parser.rb +4 -0
- data/lib/hexp/text_node.rb +129 -9
- data/lib/hexp/version.rb +1 -1
- data/notes +34 -0
- data/spec/shared_helper.rb +6 -0
- data/spec/spec_helper.rb +2 -6
- data/spec/unit/hexp/builder_spec.rb +101 -0
- data/spec/unit/hexp/css_selector/attribute_spec.rb +137 -0
- data/spec/unit/hexp/css_selector/class_spec.rb +15 -0
- data/spec/unit/hexp/css_selector/comma_sequence_spec.rb +20 -0
- data/spec/unit/hexp/css_selector/element_spec.rb +11 -0
- data/spec/unit/hexp/css_selector/parser_spec.rb +51 -0
- data/spec/unit/hexp/css_selector/simple_sequence_spec.rb +48 -0
- data/spec/unit/hexp/dsl_spec.rb +55 -0
- data/spec/unit/hexp/h_spec.rb +38 -0
- data/spec/unit/hexp/list_spec.rb +19 -0
- data/spec/unit/hexp/node/attr_spec.rb +55 -0
- data/spec/unit/hexp/node/attributes_spec.rb +125 -0
- data/spec/unit/hexp/node/children_spec.rb +33 -0
- data/spec/unit/hexp/node/class_spec.rb +37 -0
- data/spec/unit/hexp/node/css_selection_spec.rb +86 -0
- data/spec/unit/hexp/node/normalize_spec.rb +12 -6
- data/spec/unit/hexp/node/rewrite_spec.rb +67 -30
- data/spec/unit/hexp/node/selector_spec.rb +78 -0
- data/spec/unit/hexp/node/text_spec.rb +7 -0
- data/spec/unit/hexp/node/to_dom_spec.rb +1 -1
- data/spec/unit/hexp/nokogiri/reader_spec.rb +8 -0
- data/spec/unit/hexp/parse_spec.rb +23 -0
- data/spec/unit/hexp/text_node_spec.rb +25 -0
- data/spec/unit/hexp_spec.rb +33 -0
- metadata +129 -16
- data/lib/hexp/format_error.rb +0 -8
@@ -0,0 +1,74 @@
|
|
1
|
+
module Hexp
|
2
|
+
module CssSelector
|
3
|
+
# A parser of CSS selectors
|
4
|
+
#
|
5
|
+
# This is a wrapper around the SASS parser. This way we are isolated from
|
6
|
+
# changes in SASS. It also makes things easier should we decide to switch
|
7
|
+
# to a different parsing library or roll our own parser. We only use a
|
8
|
+
# fraction of the functionality of SASS so this might be worth it, although
|
9
|
+
# at this point I want to avoid reinventing that wheel.
|
10
|
+
#
|
11
|
+
# The classes that make up the parse tree largely mimic the ones from SASS,
|
12
|
+
# like CommaSequence, SimpleSequence, Class, Id, etc. By having them in our
|
13
|
+
# own namespace however we can easily add Hexp-specific helper functions.
|
14
|
+
#
|
15
|
+
class Parser
|
16
|
+
def initialize(selector)
|
17
|
+
@selector = selector.freeze
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse
|
21
|
+
rewrite_comma_sequence(SassParser.call(@selector))
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.call(selector)
|
25
|
+
new(selector).parse
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def rewrite_comma_sequence(comma_sequence)
|
31
|
+
CommaSequence.new(comma_sequence.members.map{|sequence| rewrite_sequence(sequence)})
|
32
|
+
end
|
33
|
+
|
34
|
+
def rewrite_sequence(sequence)
|
35
|
+
Sequence.new(sequence.members.map{|simple_sequence| rewrite_simple_sequence(simple_sequence)})
|
36
|
+
end
|
37
|
+
|
38
|
+
def rewrite_simple_sequence(simple_sequence)
|
39
|
+
SimpleSequence.new(simple_sequence.members.map{|simple| rewrite_simple(simple)})
|
40
|
+
end
|
41
|
+
|
42
|
+
def rewrite_simple(simple)
|
43
|
+
case simple
|
44
|
+
when ::Sass::Selector::Element # span
|
45
|
+
Element.new(simple.name.first)
|
46
|
+
when ::Sass::Selector::Class # .foo
|
47
|
+
Class.new(simple.name.first)
|
48
|
+
when ::Sass::Selector::Id # #main
|
49
|
+
Id.new(simple.name.first)
|
50
|
+
when ::Sass::Selector::Attribute # [href^="http://"]
|
51
|
+
raise "CSS attribute selector flags are curently ignored by Hexp (not implemented)" unless simple.flags.nil?
|
52
|
+
raise "CSS attribute namespaces are curently ignored by Hexp (not implemented)" unless simple.namespace.nil?
|
53
|
+
raise "CSS attribute operator #{simple.operator} not understood by Hexp" unless %w[= ~= ^=].include?(simple.operator) || simple.operator.nil?
|
54
|
+
Attribute.new(
|
55
|
+
simple.name.first,
|
56
|
+
simple.namespace,
|
57
|
+
simple.operator,
|
58
|
+
simple.value ? simple.value.first : nil,
|
59
|
+
simple.flags
|
60
|
+
)
|
61
|
+
else
|
62
|
+
raise "CSS selectors containing #{simple.class} are not implemented in Hexp"
|
63
|
+
end
|
64
|
+
|
65
|
+
# when ::Sass::Selector::Universal # *
|
66
|
+
# when ::Sass::Selector::Parent # & in Sass
|
67
|
+
# when ::Sass::Selector::Interpolation # #{} in Sass
|
68
|
+
# when ::Sass::Selector::Pseudo # :visited, ::first-line, :nth-child(2n+1)
|
69
|
+
# when ::Sass::Selector::SelectorPseudoClass # :not(.foo)
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Hexp
|
2
|
+
module CssSelector
|
3
|
+
# A CSS Parser that only knows how to parse CSS selectors
|
4
|
+
#
|
5
|
+
class SassParser < ::Sass::SCSS::CssParser
|
6
|
+
def initialize(selector)
|
7
|
+
super(selector, '')
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse
|
11
|
+
init_scanner!
|
12
|
+
result = selector_comma_sequence
|
13
|
+
raise "Invalid CSS selector : unconsumed input #{@scanner.rest}" unless @scanner.eos?
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.call(selector)
|
18
|
+
self.new(selector).parse
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/hexp/dom.rb
CHANGED
data/lib/hexp/dsl.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Hexp
|
2
|
+
module DSL
|
3
|
+
[ :tag,
|
4
|
+
:attributes,
|
5
|
+
:children,
|
6
|
+
:attr,
|
7
|
+
:rewrite,
|
8
|
+
:replace,
|
9
|
+
:select,
|
10
|
+
:to_html,
|
11
|
+
:class?,
|
12
|
+
:add_class,
|
13
|
+
:add_child,
|
14
|
+
:add,
|
15
|
+
:<<,
|
16
|
+
:process,
|
17
|
+
:%,
|
18
|
+
:text,
|
19
|
+
:remove_attr,
|
20
|
+
:set_attributes,
|
21
|
+
].each do |meth|
|
22
|
+
define_method meth do |*args, &blk|
|
23
|
+
to_hexp.public_send(meth, *args, &blk)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/hexp/errors.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Hexp
|
2
|
+
Error = Class.new(StandardError)
|
3
|
+
|
4
|
+
# Raised when trying to stick things inside a Hexp where they don't belong
|
5
|
+
#
|
6
|
+
class FormatError < Error
|
7
|
+
# Create a new FormatError
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
#
|
11
|
+
def initialize(msg = 'You have illegal contents in your Hexp')
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Raised by {Hexp.parse} when the input can't be converted to a {Hexp::Node}
|
17
|
+
#
|
18
|
+
class ParseError < Error
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/hexp/h.rb
CHANGED
data/lib/hexp/list.rb
CHANGED
@@ -1,25 +1,83 @@
|
|
1
1
|
module Hexp
|
2
2
|
# A list of nodes
|
3
3
|
#
|
4
|
-
|
5
|
-
# Hexp::List[
|
6
|
-
# Hexp::Node[:marquee, "Try Hexp for instanst satisfaction!"],
|
7
|
-
# Hexp::Node[:hr],
|
8
|
-
# ]
|
9
|
-
#
|
10
|
-
class List < SimpleDelegator
|
11
|
-
include Equalizer.new(:__getobj__)
|
4
|
+
class List < DelegateClass(Array)
|
12
5
|
|
6
|
+
# Create new Hexp::List
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# Hexp::List.new([H[:p], H[:div]])
|
10
|
+
#
|
11
|
+
# @param nodes [#to_ary] List of nodes
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
#
|
13
15
|
def initialize(nodes)
|
14
|
-
super
|
16
|
+
super nodes.to_ary.freeze
|
15
17
|
end
|
16
18
|
|
19
|
+
# Convenience constructor
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# Hexp::List[
|
23
|
+
# Hexp::Node[:marquee, "Try Hexp for instanst satisfaction!"],
|
24
|
+
# Hexp::Node[:hr],
|
25
|
+
# ]
|
26
|
+
#
|
27
|
+
# @param args [Array] individual nodes
|
28
|
+
#
|
29
|
+
# @return [Hexp::List]
|
30
|
+
# @api public
|
31
|
+
#
|
17
32
|
def self.[](*args)
|
18
33
|
new(args)
|
19
34
|
end
|
20
35
|
|
36
|
+
# String representation
|
37
|
+
#
|
38
|
+
# This delegates to the underlying array, so it's not obvious from the output
|
39
|
+
# that this is a wrapping class. This is convenient when inspecting nested
|
40
|
+
# hexps, but probably something we want to solve differently.
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
# @return string
|
44
|
+
#
|
21
45
|
def inspect
|
22
46
|
__getobj__.inspect
|
23
47
|
end
|
48
|
+
|
49
|
+
# Internal coercion to Array
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# Hexp::List[ H[:p], H[:span] ].to_ary #=> [H[:p], H[:span]]
|
53
|
+
#
|
54
|
+
# @return [Array<Hexp::Node>]
|
55
|
+
# @api public
|
56
|
+
#
|
57
|
+
def to_ary
|
58
|
+
__getobj__
|
59
|
+
end
|
60
|
+
|
61
|
+
# Value and type equality
|
62
|
+
#
|
63
|
+
# Hexp::List is mostly interchangeable with a plain Array, and so equality
|
64
|
+
# with `==` delegates to the underlying array, making `Hexp::List[] == []`
|
65
|
+
# true.
|
66
|
+
#
|
67
|
+
# If you want a stronger comparison, than this version will compare both
|
68
|
+
# the value (in this case : contents), and the type.
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# H[:div, [[:span]]].children == [H[:span]] #=> true
|
72
|
+
# H[:div, [[:span]]].children.eql? [H[:span]] #=> false
|
73
|
+
# H[:div, [[:span]]].children.eql? Hexp::List[H[:span]] #=> true
|
74
|
+
#
|
75
|
+
# @param other [Object] Object to compare with
|
76
|
+
# @api public
|
77
|
+
# @return [Boolean]
|
78
|
+
#
|
79
|
+
def eql?(other)
|
80
|
+
self == other && self.class == other.class
|
81
|
+
end
|
24
82
|
end
|
25
83
|
end
|
data/lib/hexp/node.rb
CHANGED
@@ -1,53 +1,162 @@
|
|
1
1
|
module Hexp
|
2
|
-
# A Hexp Node
|
2
|
+
# A Hexp Node, or simply 'a hexp'
|
3
3
|
class Node
|
4
4
|
include Equalizer.new(:tag, :attributes, :children)
|
5
5
|
extend Forwardable
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
include Hexp::Node::Attributes
|
8
|
+
include Hexp::Node::Children
|
9
9
|
|
10
|
-
#
|
10
|
+
# The HTML tag of this node
|
11
11
|
#
|
12
|
-
# @
|
13
|
-
#
|
12
|
+
# @example
|
13
|
+
# H[:p].tag #=> :p
|
14
|
+
#
|
15
|
+
# @return [Symbol]
|
16
|
+
# @api public
|
17
|
+
#
|
18
|
+
attr_reader :tag
|
19
|
+
|
20
|
+
# The attributes of this node
|
14
21
|
#
|
15
22
|
# @example
|
16
|
-
#
|
23
|
+
# H[:p, class: 'foo'].attributes #=> {'class' => 'foo'}
|
17
24
|
#
|
25
|
+
# @return [Hash<String, String>]
|
18
26
|
# @api public
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
27
|
+
#
|
28
|
+
attr_reader :attributes
|
29
|
+
|
30
|
+
# The child nodes of this node
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# H[:p, [ H[:span], 'hello' ]].children
|
34
|
+
# #=> Hexp::List[ H[:span], Hexp::TextNode["hello"] ]
|
35
|
+
#
|
36
|
+
# @return [Hexp::List]
|
37
|
+
# @api public
|
38
|
+
#
|
39
|
+
attr_reader :children
|
24
40
|
|
41
|
+
# Main entry point for creating literal hexps
|
42
|
+
#
|
43
|
+
# At the moment this just redirects to #new, and since Hexp::Node is aliased
|
44
|
+
# to H this provides a shorthand for the contructor,
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# H[:span, {attr: 'value'}].
|
48
|
+
#
|
49
|
+
# Note that while the H[] form is part of the public API and expected to
|
50
|
+
# remain, it's implementation might change. In particular H might become
|
51
|
+
# a class or module in its own right, so it is recommended to only use
|
52
|
+
# this method in its H[] form.
|
53
|
+
#
|
54
|
+
# @param args [Array] args a Hexp node components
|
55
|
+
# @return [Hexp::Node]
|
56
|
+
# @api public
|
57
|
+
#
|
25
58
|
def self.[](*args)
|
26
59
|
new(*args)
|
27
60
|
end
|
28
61
|
|
62
|
+
# Normalize the arguments
|
63
|
+
#
|
64
|
+
# @param args [Array] args a Hexp node components
|
65
|
+
# @return [Hexp::Node]
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
# Hexp::Node.new(:p, {'class' => 'foo'}, [[:b, "Hello, World!"]])
|
69
|
+
#
|
70
|
+
# @api public
|
71
|
+
#
|
72
|
+
def initialize(*args)
|
73
|
+
@tag, @attributes, @children = Normalize.new(args).call
|
74
|
+
end
|
75
|
+
|
76
|
+
# Standard hexp coercion protocol, return self
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# H[:p].to_hexp #=> H[:p]
|
80
|
+
#
|
81
|
+
# @return [Hexp::Node] self
|
82
|
+
# @api public
|
83
|
+
#
|
29
84
|
def to_hexp
|
30
85
|
self
|
31
86
|
end
|
32
87
|
|
33
|
-
|
34
|
-
|
88
|
+
# Serialize this node to HTML
|
89
|
+
#
|
90
|
+
# @example
|
91
|
+
# H[:html, [ H[:body, ["hello, world"] ] ]] .to_html
|
92
|
+
# # => "<html><body>hello, world</body></html>"
|
93
|
+
#
|
94
|
+
# @return [String]
|
95
|
+
# @api public
|
96
|
+
#
|
97
|
+
def to_html(options = {})
|
98
|
+
to_dom(options).to_html
|
35
99
|
end
|
36
100
|
|
37
|
-
|
38
|
-
|
101
|
+
# Convert this node into a Nokogiri Document
|
102
|
+
#
|
103
|
+
# @example
|
104
|
+
# H[:p].to_dom
|
105
|
+
# #=> #<Nokogiri::HTML::Document name="document"
|
106
|
+
# children=[#<Nokogiri::XML::DTD name="html">,
|
107
|
+
# #<Nokogiri::XML::Element name="p">]>
|
108
|
+
#
|
109
|
+
# @return [Nokogiri::HTML::Document]
|
110
|
+
# @api private
|
111
|
+
#
|
112
|
+
def to_dom(options = {})
|
113
|
+
Domize.new(self, options).call
|
39
114
|
end
|
40
115
|
|
116
|
+
# Return a string representation that is close to the literal form
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# H[:p, {class: 'foo'}].inspect #=> "H[:p, {\"class\"=>\"foo\"}]"
|
120
|
+
#
|
121
|
+
# @return [String]
|
122
|
+
# @api public
|
123
|
+
#
|
41
124
|
def inspect
|
42
|
-
self.class.inspect_name + [tag, attributes, children
|
125
|
+
self.class.inspect_name + [tag, attributes, children].compact.reject(&:empty?).inspect
|
43
126
|
end
|
44
127
|
|
128
|
+
# Pretty print, a multiline representation with indentation
|
129
|
+
#
|
130
|
+
# @example
|
131
|
+
# H[:p, [[:span], [:div]]].pp # => "H[:p, [\n H[:span],\n H[:div]]]"
|
132
|
+
#
|
133
|
+
# @return [String]
|
134
|
+
# @api public
|
135
|
+
#
|
45
136
|
def pp
|
46
137
|
self.class::PP.new(self).call
|
47
138
|
end
|
48
139
|
|
49
|
-
#
|
50
|
-
#
|
140
|
+
# Is this a text node? Returns false
|
141
|
+
#
|
142
|
+
# @example
|
143
|
+
# H[:p].text? #=> false
|
144
|
+
#
|
145
|
+
# @return [FalseClass]
|
146
|
+
# @api public
|
147
|
+
#
|
148
|
+
def text?
|
149
|
+
false
|
150
|
+
end
|
151
|
+
|
152
|
+
def set_tag(tag)
|
153
|
+
H[tag.to_sym, attributes, children]
|
154
|
+
end
|
155
|
+
|
156
|
+
# Rewrite a node tree
|
157
|
+
#
|
158
|
+
# Since nodes are immutable, this is the main entry point for deriving nodes
|
159
|
+
# from others.
|
51
160
|
#
|
52
161
|
# Rewrite will pass you each node in the tree, and expects something to replace
|
53
162
|
# it with. A single node, multiple nodes, or no nodes (remove it).
|
@@ -73,10 +182,12 @@ module Hexp
|
|
73
182
|
#
|
74
183
|
# Remove all script tags
|
75
184
|
#
|
185
|
+
# @example
|
76
186
|
# tree.rewrite{|node| [] if node.tag == :script }
|
77
187
|
#
|
78
188
|
# Wrap each <input> tag into a <p> tag
|
79
189
|
#
|
190
|
+
# @example
|
80
191
|
# tree.rewrite do |node|
|
81
192
|
# if node.tag == :input
|
82
193
|
# [ H[:p, [ child ] ]
|
@@ -85,32 +196,76 @@ module Hexp
|
|
85
196
|
#
|
86
197
|
# @param blk [Proc] The rewrite action
|
87
198
|
# @return [Hexp::Node] The rewritten tree
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
199
|
+
# @api public
|
200
|
+
#
|
201
|
+
def rewrite(css_selector = nil, &block)
|
202
|
+
return Rewriter.new(self, block) if css_selector.nil?
|
203
|
+
CssSelection.new(self, css_selector).rewrite(&block)
|
204
|
+
end
|
205
|
+
alias :replace :rewrite
|
206
|
+
|
207
|
+
def select(css_selector = nil, &block)
|
208
|
+
if css_selector
|
209
|
+
CssSelection.new(self, css_selector).each(&block)
|
210
|
+
else
|
211
|
+
Selector.new(self, block)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Run a number of processors on this node
|
216
|
+
#
|
217
|
+
# This is pure convenience, but it helps to conceptualize the "processor"
|
218
|
+
# idea of a component (be it a lambda or other object), that responds to
|
219
|
+
# call, and transform a {Hexp::Node} tree.
|
220
|
+
#
|
221
|
+
# @example
|
222
|
+
# hexp.process(
|
223
|
+
# ->(node) { node.replace('.section') {|node| H[:p, class: 'big', node]} },
|
224
|
+
# ->(node) { node.add_class 'foo' },
|
225
|
+
# InlineAssets.new
|
226
|
+
# )
|
227
|
+
#
|
228
|
+
# @param processors [Array<#call>]
|
229
|
+
# @return [Hexp::Node]
|
230
|
+
# @api public
|
231
|
+
#
|
232
|
+
def process(*processors)
|
233
|
+
processors.empty? ? self : processors.first.(self).process(*processors.drop(1))
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
|
238
|
+
# Set an attribute, used internally by #attr
|
239
|
+
#
|
240
|
+
# Setting an attribute to nil will delete it
|
241
|
+
#
|
242
|
+
# @param name [String|Symbol]
|
243
|
+
# @param value [String|NilClass]
|
244
|
+
# @return [Hexp::Node]
|
245
|
+
#
|
246
|
+
# @api private
|
247
|
+
#
|
248
|
+
def set_attr(name, value)
|
249
|
+
if value.nil?
|
250
|
+
new_attrs = {}
|
251
|
+
attributes.each do |nam,val|
|
252
|
+
new_attrs[nam] = val unless nam == name.to_s
|
109
253
|
end
|
110
|
-
|
254
|
+
else
|
255
|
+
new_attrs = attributes.merge(name.to_s => value.to_s)
|
256
|
+
end
|
257
|
+
self.class.new(self.tag, new_attrs, self.children)
|
111
258
|
end
|
112
259
|
|
113
260
|
class << self
|
261
|
+
|
262
|
+
# Returns the class name for use in creating inspection strings
|
263
|
+
#
|
264
|
+
# This will return "H" if H == Hexp::Node, or "Hexp::Node" otherwise.
|
265
|
+
#
|
266
|
+
# @return [String]
|
267
|
+
# @api private
|
268
|
+
#
|
114
269
|
def inspect_name
|
115
270
|
if defined?(H) && H == self
|
116
271
|
'H'
|
@@ -118,6 +273,7 @@ module Hexp
|
|
118
273
|
self.name
|
119
274
|
end
|
120
275
|
end
|
276
|
+
|
121
277
|
end
|
122
278
|
end
|
123
279
|
end
|