nokolexbor 0.6.4 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3615acd25f6233c5701c91d25ef6c94a73adf2de8433e2e1b6b3aa215f0581ab
4
- data.tar.gz: ad3e9f8134a7f26924b2283a2ac10a8dfae2f5684dd650be5903faf069f595ef
3
+ metadata.gz: fb6fea4008f68e39df01fbc3920634bd747f6c6e58605876b4600b6037745d4a
4
+ data.tar.gz: 38f67c6e320e95d35a82db68f34b08e7103fa8d56e52fe6e59ebdeb266701c16
5
5
  SHA512:
6
- metadata.gz: 2d84f341a6b3e1c4dcc8501ab9772e4376bc8c9acf2441753253c331ca381ff161c95cbb93f32568aada6ac03e3bedb01343bbc507d5201c468884f286008228
7
- data.tar.gz: f8a85acb4fdd063714316490289bae356a731931aa8c4202a80f677b70d2f02258f69991e0d996cfaadf80dbcad8c505c61b4a6a91456514bbc972e0d7861148
6
+ metadata.gz: a44ba9da124b17e58ae2abc8c6e99cfab22edbdcdff5b07e9636838f28098a4716aa6f50cf7282c1f15fcbb0630b7bd5ef814b54cb7886dd75b16a864e496405
7
+ data.tar.gz: 40fcdc69521d2f985be5fa62addf3f4fd03ac577eeb66878f496dbc684fc3925ceddb9fa2f74a870188431c672b16394ccc6c58303caae6c6718af8e978bc967
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Nokolexbor
6
+ # A DSL for programmatically building HTML documents.
7
+ #
8
+ # == No-arg block (instance_eval style) – tag names are called bare:
9
+ #
10
+ # Nokolexbor do
11
+ # body do
12
+ # h1 'Hello world'
13
+ # p 'This little p'
14
+ # ul do
15
+ # li 'Go to market'
16
+ # li 'Go to bed'
17
+ # end
18
+ # end
19
+ # end
20
+ #
21
+ # == Block-parameter style – tag names are called on the builder argument:
22
+ #
23
+ # Nokolexbor::Builder.new do |b|
24
+ # b.body do
25
+ # b.h1 'Hello world'
26
+ # end
27
+ # end
28
+ #
29
+ # == Building into an existing node:
30
+ #
31
+ # Nokolexbor::Builder.with(existing_node) do
32
+ # span 'injected'
33
+ # end
34
+ #
35
+ class Builder
36
+ # The Document used to create new nodes
37
+ attr_accessor :doc
38
+
39
+ # The node that currently receives new children
40
+ attr_accessor :parent
41
+
42
+ # Arity of the outermost block (drives instance_eval vs yield dispatch)
43
+ attr_accessor :arity
44
+
45
+ # A context object for use when the block has no arguments
46
+ attr_accessor :context
47
+
48
+ # Build into an existing +root+ node.
49
+ def self.with(root, &block)
50
+ new(root, &block)
51
+ end
52
+
53
+ # @param root [Node, nil]
54
+ # When given, new nodes are appended under +root+ and +root.document+
55
+ # is used as the owning document. When omitted a fresh {Document} and
56
+ # a {DocumentFragment} container are created.
57
+ def initialize(root = nil, &block)
58
+ if root
59
+ @doc = root.document
60
+ @parent = root
61
+ else
62
+ @doc = Document.new
63
+ @parent = DocumentFragment.new(@doc)
64
+ end
65
+
66
+ @context = nil
67
+ @arity = nil
68
+
69
+ return unless block
70
+
71
+ @arity = block.arity
72
+ if @arity <= 0
73
+ # Capture outer self so that outer methods / ivars remain accessible
74
+ # via method_missing delegation while the builder acts as self.
75
+ @context = eval("self", block.binding)
76
+ instance_eval(&block)
77
+ else
78
+ yield self
79
+ end
80
+ end
81
+
82
+ # Insert a Text node containing +string+.
83
+ def text(string)
84
+ insert(@doc.create_text_node(string))
85
+ end
86
+
87
+ # Insert a CDATA node containing +string+.
88
+ def cdata(string)
89
+ insert(@doc.create_cdata(string))
90
+ end
91
+
92
+ # Insert a Comment node containing +string+.
93
+ def comment(string)
94
+ insert(@doc.create_comment(string))
95
+ end
96
+
97
+ # Append raw HTML (parsed into nodes) under the current parent.
98
+ def <<(string)
99
+ @doc.fragment(string).children.each { |x| insert(x) }
100
+ end
101
+
102
+ # Serialize the built content to an HTML string.
103
+ def to_html
104
+ @parent.to_html
105
+ end
106
+ alias_method :to_s, :to_html
107
+
108
+ # Ruby's Kernel#p, Kernel#pp, etc. are defined on Object and therefore on
109
+ # Builder itself. They must be overridden so they fall through to
110
+ # method_missing and create the corresponding HTML element instead of
111
+ # printing values to stdout.
112
+ def p(*args, &block) # :nodoc:
113
+ method_missing(:p, *args, &block)
114
+ end
115
+
116
+ def method_missing(method, *args, &block) # :nodoc:
117
+ # Only delegate to the outer context for methods the user defined there.
118
+ # Standard Object / Kernel methods (like :p, :pp, :puts …) must not be
119
+ # forwarded to @context because all objects respond to them, which would
120
+ # bypass element creation entirely.
121
+ if @context && !OBJECT_INSTANCE_METHODS.include?(method) && @context.respond_to?(method)
122
+ @context.send(method, *args, &block)
123
+ else
124
+ node = @doc.create_element(method.to_s.sub(/[_!]$/, ""), *args)
125
+ insert(node, &block)
126
+ end
127
+ end
128
+
129
+ def respond_to_missing?(method, include_private = false) # :nodoc:
130
+ (@context && !OBJECT_INSTANCE_METHODS.include?(method) && @context.respond_to?(method)) || super
131
+ end
132
+
133
+ # Methods defined on plain Object that should never be forwarded to @context
134
+ # as HTML element creation fallbacks.
135
+ OBJECT_INSTANCE_METHODS = Object.instance_methods.to_set.freeze
136
+
137
+ private
138
+
139
+ # Add +node+ as a child of @parent; if a block is given, push @parent
140
+ # to +node+ for the duration of that block, then restore it.
141
+ # Returns a {NodeBuilder} for the inserted node so attributes / classes
142
+ # can be chained: <tt>div.container.hero!</tt>
143
+ def insert(node, &block)
144
+ node = @parent.add_child(node)
145
+ if block
146
+ old_parent = @parent
147
+ @parent = node
148
+ @arity ||= block.arity
149
+ begin
150
+ if @arity <= 0
151
+ instance_eval(&block)
152
+ else
153
+ yield(self)
154
+ end
155
+ ensure
156
+ @parent = old_parent
157
+ end
158
+ end
159
+ NodeBuilder.new(node, self)
160
+ end
161
+
162
+ # Wraps a built node and exposes a fluent API for setting classes and ids:
163
+ #
164
+ # div.container # => <div class="container">
165
+ # div.box.highlight # => <div class="box highlight">
166
+ # div.thing! # => <div id="thing">
167
+ # div.container.hero! # => <div class="container" id="hero">
168
+ #
169
+ class NodeBuilder # :nodoc:
170
+ def initialize(node, doc_builder)
171
+ @node = node
172
+ @doc_builder = doc_builder
173
+ end
174
+
175
+ def []=(k, v)
176
+ @node[k] = v
177
+ end
178
+
179
+ def [](k)
180
+ @node[k]
181
+ end
182
+
183
+ def method_missing(method, *args, &block)
184
+ opts = args.last.is_a?(Hash) ? args.pop : {}
185
+
186
+ case method.to_s
187
+ when /^(.*)!$/
188
+ @node["id"] = Regexp.last_match(1)
189
+ @node.content = args.first if args.first
190
+ when /^(.*)=$/
191
+ @node[Regexp.last_match(1)] = args.first
192
+ else
193
+ @node["class"] =
194
+ ((@node["class"] || "").split(/\s/) + [method.to_s]).join(" ")
195
+ @node.content = args.first if args.first
196
+ end
197
+
198
+ opts.each do |k, v|
199
+ @node[k.to_s] = ((@node[k.to_s] || "").split(/\s/) + [v.to_s]).join(" ")
200
+ end
201
+
202
+ if block
203
+ old_parent = @doc_builder.parent
204
+ @doc_builder.parent = @node
205
+ arity = @doc_builder.arity || block.arity
206
+ value = if arity <= 0
207
+ @doc_builder.instance_eval(&block)
208
+ else
209
+ yield(@doc_builder)
210
+ end
211
+ @doc_builder.parent = old_parent
212
+ return value
213
+ end
214
+
215
+ self
216
+ end
217
+
218
+ def respond_to_missing?(_method, _include_private = false) # :nodoc:
219
+ true
220
+ end
221
+ end
222
+ end
223
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nokolexbor
4
- VERSION = '0.6.4'
4
+ VERSION = '0.7.0'
5
5
  end
data/lib/nokolexbor.rb CHANGED
@@ -28,11 +28,23 @@ require 'nokolexbor/node_set'
28
28
  require 'nokolexbor/document_fragment'
29
29
  require 'nokolexbor/xpath'
30
30
  require 'nokolexbor/xpath_context'
31
+ require 'nokolexbor/builder'
31
32
 
32
33
  module Nokolexbor
33
34
  class << self
34
- def HTML(*args)
35
+ def parse(*args)
35
36
  Document.parse(*args)
36
37
  end
38
+
39
+ alias_method :HTML, :parse
40
+ end
41
+ end
42
+
43
+ # Parse an HTML document, or build one with the DSL when a block is given.
44
+ def Nokolexbor(string_or_io = nil, &block)
45
+ if block
46
+ Nokolexbor::Builder.new(&block).parent
47
+ else
48
+ Nokolexbor.parse(string_or_io)
37
49
  end
38
50
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nokolexbor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.4
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yicheng Zhou
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-03-23 00:00:00.000000000 Z
10
+ date: 2026-03-25 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rake-compiler
@@ -120,6 +119,7 @@ files:
120
119
  - ext/nokolexbor/xml_tree.c
121
120
  - ext/nokolexbor/xml_xpath.c
122
121
  - lib/nokolexbor.rb
122
+ - lib/nokolexbor/builder.rb
123
123
  - lib/nokolexbor/document.rb
124
124
  - lib/nokolexbor/document_fragment.rb
125
125
  - lib/nokolexbor/node.rb
@@ -523,7 +523,6 @@ licenses:
523
523
  - MIT
524
524
  metadata:
525
525
  msys2_mingw_dependencies: cmake
526
- post_install_message:
527
526
  rdoc_options: []
528
527
  require_paths:
529
528
  - lib
@@ -538,8 +537,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
538
537
  - !ruby/object:Gem::Version
539
538
  version: '0'
540
539
  requirements: []
541
- rubygems_version: 3.1.6
542
- signing_key:
540
+ rubygems_version: 4.0.6
543
541
  specification_version: 4
544
542
  summary: High-performance HTML5 parser, with support for both CSS selectors and XPath.
545
543
  test_files: []