Class: Toml::Merge::NodeWrapper
- Inherits:
-
Ast::Merge::NodeWrapperBase
- Object
- Ast::Merge::NodeWrapperBase
- Toml::Merge::NodeWrapper
- Defined in:
- lib/toml/merge/node_wrapper.rb
Overview
Wraps tree-sitter nodes with comment associations, line information, and signatures. This provides a unified interface for working with TOML AST nodes during merging.
Inherits common functionality from Ast::Merge::NodeWrapperBase:
- Source context (lines, source, comments)
- Line info extraction
- Basic methods: #type, #text, #signature
Adds TOML-specific functionality:
- Backend awareness for Citrus/tree-sitter normalization
- Type predicates using NodeTypeNormalizer
- Structural normalization for Citrus backend (pairs as siblings)
Instance Attribute Summary collapse
-
#backend ⇒ Symbol
readonly
The backend used for parsing.
-
#document_root ⇒ TreeHaver::Node?
readonly
The document root node for sibling lookups.
Class Method Summary collapse
-
.wrap(node, lines, source: nil, leading_comments: nil, inline_comment: nil, backend: :tree_sitter, comment_entries: nil, comment_tracker: nil) ⇒ NodeWrapper?
Wrap a tree-sitter node, returning nil for nil input.
Instance Method Summary collapse
-
#array? ⇒ Boolean
Check if this is a TOML array.
-
#array_of_tables? ⇒ Boolean
Check if this is a TOML array of tables Uses NodeTypeNormalizer for backend-agnostic type checking.
-
#boolean? ⇒ Boolean
Check if this is a TOML boolean.
-
#canonical_type ⇒ Symbol
Get the canonical (normalized) type for this node.
-
#closing_line ⇒ String?
Get the closing line for a container node For tables, this is the last line of content before the next table or EOF.
-
#comment? ⇒ Boolean
Check if this is a comment.
-
#container? ⇒ Boolean
Check if this node is a container (has mergeable children).
-
#content ⇒ String
Get the content for this node from source lines.
-
#datetime? ⇒ Boolean
Check if this is a datetime.
-
#document? ⇒ Boolean
Check if this is the document root.
-
#effective_end_line ⇒ Integer?
Get the effective end line for this node, accounting for Citrus backend.
-
#elements ⇒ Array<NodeWrapper>
Get array elements if this is an array.
-
#float? ⇒ Boolean
Check if this is a TOML float.
-
#initialize(node, lines:, source: nil, leading_comments: nil, inline_comment: nil, **options) ⇒ NodeWrapper
constructor
A new instance of NodeWrapper.
-
#inline_table? ⇒ Boolean
Check if this is a TOML inline table.
-
#integer? ⇒ Boolean
Check if this is a TOML integer.
-
#key_name ⇒ String?
Get the key name if this is a pair node.
-
#mergeable_children ⇒ Array<NodeWrapper>
Get mergeable children - the semantically meaningful children for tree merging For tables, returns pairs.
- #node_text(ts_node) ⇒ Object
- #normalize_toml_line_range! ⇒ Object
-
#opening_line ⇒ String?
Get the opening line for a table (the line with [table_name]).
-
#pair? ⇒ Boolean
Check if this is a key-value pair.
-
#pairs ⇒ Array<NodeWrapper>
Get key-value pairs from a table or inline_table.
-
#process_additional_options(options) ⇒ Object
Process TOML-specific options (backend, document_root).
-
#string? ⇒ Boolean
Check if this is a TOML string.
-
#table? ⇒ Boolean
Check if this is a TOML table (section).
-
#table_name ⇒ String?
Get the table name (header) if this is a table.
- #text ⇒ Object
-
#type?(type_name) ⇒ Boolean
Check if this node has a specific type (checks both raw and canonical).
-
#value_node ⇒ NodeWrapper?
Get the value node if this is a pair.
Constructor Details
#initialize(node, lines:, source: nil, leading_comments: nil, inline_comment: nil, **options) ⇒ NodeWrapper
Returns a new instance of NodeWrapper.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/toml/merge/node_wrapper.rb', line 54 def initialize(node, lines:, source: nil, leading_comments: nil, inline_comment: nil, **) @explicit_leading_comments = !leading_comments.nil? @explicit_inline_comment = !inline_comment.nil? super( node, lines: lines, source: source, leading_comments: leading_comments || [], inline_comment: inline_comment, **, ) normalize_toml_line_range! hydrate_comment_associations! end |
Instance Attribute Details
#backend ⇒ Symbol (readonly)
Returns The backend used for parsing.
72 73 74 |
# File 'lib/toml/merge/node_wrapper.rb', line 72 def backend @backend end |
#document_root ⇒ TreeHaver::Node? (readonly)
Returns The document root node for sibling lookups.
75 76 77 |
# File 'lib/toml/merge/node_wrapper.rb', line 75 def document_root @document_root end |
Class Method Details
.wrap(node, lines, source: nil, leading_comments: nil, inline_comment: nil, backend: :tree_sitter, comment_entries: nil, comment_tracker: nil) ⇒ NodeWrapper?
Wrap a tree-sitter node, returning nil for nil input.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/toml/merge/node_wrapper.rb', line 37 def wrap(node, lines, source: nil, leading_comments: nil, inline_comment: nil, backend: :tree_sitter, comment_entries: nil, comment_tracker: nil) return if node.nil? new( node, lines: lines, source: source, leading_comments: leading_comments, inline_comment: inline_comment, backend: backend, comment_entries: comment_entries, comment_tracker: comment_tracker ) end |
Instance Method Details
#array? ⇒ Boolean
Check if this is a TOML array
141 142 143 |
# File 'lib/toml/merge/node_wrapper.rb', line 141 def array? canonical_type == :array end |
#array_of_tables? ⇒ Boolean
Check if this is a TOML array of tables Uses NodeTypeNormalizer for backend-agnostic type checking.
129 130 131 |
# File 'lib/toml/merge/node_wrapper.rb', line 129 def array_of_tables? canonical_type == :array_of_tables end |
#boolean? ⇒ Boolean
Check if this is a TOML boolean
165 166 167 |
# File 'lib/toml/merge/node_wrapper.rb', line 165 def boolean? canonical_type == :boolean end |
#canonical_type ⇒ Symbol
Get the canonical (normalized) type for this node
106 107 108 109 110 |
# File 'lib/toml/merge/node_wrapper.rb', line 106 def canonical_type return parslet_element_canonical_type if parslet_element_node? NodeTypeNormalizer.canonical_type(@node.type, @backend) end |
#closing_line ⇒ String?
Get the closing line for a container node For tables, this is the last line of content before the next table or EOF
364 365 366 367 368 |
# File 'lib/toml/merge/node_wrapper.rb', line 364 def closing_line return unless container? && @end_line @lines[@end_line - 1] end |
#comment? ⇒ Boolean
Check if this is a comment
177 178 179 |
# File 'lib/toml/merge/node_wrapper.rb', line 177 def comment? canonical_type == :comment end |
#container? ⇒ Boolean
Check if this node is a container (has mergeable children)
348 349 350 |
# File 'lib/toml/merge/node_wrapper.rb', line 348 def container? table? || array_of_tables? || inline_table? || array? || document? end |
#content ⇒ String
Get the content for this node from source lines.
Handles structural differences between backends:
- Tree-sitter: table nodes include pairs, so start_line..end_line covers everything
- Citrus: table nodes only include header, so we extend to include associated pairs
377 378 379 380 381 382 383 384 385 |
# File 'lib/toml/merge/node_wrapper.rb', line 377 def content return '' unless @start_line # For tables with Citrus backend, extend end_line to include pairs effective_end = effective_end_line return '' unless effective_end (@start_line..effective_end).map { |ln| @lines[ln - 1] }.compact.join("\n") end |
#datetime? ⇒ Boolean
Check if this is a datetime
183 184 185 |
# File 'lib/toml/merge/node_wrapper.rb', line 183 def datetime? canonical_type == :datetime end |
#document? ⇒ Boolean
Check if this is the document root
189 190 191 |
# File 'lib/toml/merge/node_wrapper.rb', line 189 def document? canonical_type == :document end |
#effective_end_line ⇒ Integer?
Get the effective end line for this node, accounting for Citrus backend. For Citrus tables, this extends to the line before the next table.
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 |
# File 'lib/toml/merge/node_wrapper.rb', line 390 def effective_end_line return @end_line if !(table? || array_of_tables?) || @document_root.nil? # Check if we have pairs as children (tree-sitter structure). Some # backends expose table node spans that run through the next table # boundary, so use the semantic child range when it is available. child_pairs = collect_child_pairs return child_pairs.map(&:end_line).compact.max || @start_line if child_pairs.any? # Citrus structure: find the last pair that belongs to us sibling_pairs = collect_sibling_pairs_for_table return @end_line if sibling_pairs.empty? # Return the end line of the last pair sibling_pairs.map(&:end_line).compact.max || @end_line end |
#elements ⇒ Array<NodeWrapper>
Get array elements if this is an array
Handles structural differences between backends:
- Tree-sitter: values are direct children of array node
- Citrus: values are nested inside array_elements container
307 308 309 310 311 312 313 |
# File 'lib/toml/merge/node_wrapper.rb', line 307 def elements return [] unless array? result = [] collect_array_elements(@node, result) result end |
#float? ⇒ Boolean
Check if this is a TOML float
159 160 161 |
# File 'lib/toml/merge/node_wrapper.rb', line 159 def float? canonical_type == :float end |
#inline_table? ⇒ Boolean
Check if this is a TOML inline table
135 136 137 |
# File 'lib/toml/merge/node_wrapper.rb', line 135 def inline_table? canonical_type == :inline_table end |
#integer? ⇒ Boolean
Check if this is a TOML integer
153 154 155 |
# File 'lib/toml/merge/node_wrapper.rb', line 153 def integer? canonical_type == :integer end |
#key_name ⇒ String?
Get the key name if this is a pair node
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/toml/merge/node_wrapper.rb', line 211 def key_name return unless pair? # In TOML, pair has key children (bare_key, quoted_key, or dotted_key) @node.each do |child| child_canonical = NodeTypeNormalizer.canonical_type(child.type, @backend) next unless NodeTypeNormalizer.key_type?(child_canonical) key_text = node_text(child) # Remove surrounding quotes if present, and strip whitespace # (Citrus backend includes trailing space in key nodes) return key_text&.gsub(/\A["']|["']\z/, '')&.strip end nil end |
#mergeable_children ⇒ Array<NodeWrapper>
Get mergeable children - the semantically meaningful children for tree merging For tables, returns pairs. For arrays, returns elements. For other node types, returns empty array (leaf nodes).
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/toml/merge/node_wrapper.rb', line 319 def mergeable_children case canonical_type when :table, :inline_table, :array_of_tables pairs when :array elements when :document # Return top-level pairs and tables result = [] @node.each do |child| child_canonical = NodeTypeNormalizer.canonical_type(child.type, @backend) next if child_canonical == :comment result << NodeWrapper.new( child, lines: @lines, source: @source, backend: @backend, document_root: @document_root ) end result else [] end end |
#node_text(ts_node) ⇒ Object
87 88 89 90 91 92 93 94 |
# File 'lib/toml/merge/node_wrapper.rb', line 87 def node_text(ts_node) return '' unless ts_node.respond_to?(:start_byte) && ts_node.respond_to?(:end_byte) length = ts_node.end_byte - ts_node.start_byte return @source[ts_node.start_byte, length].to_s if @backend == :citrus super end |
#normalize_toml_line_range! ⇒ Object
96 97 98 99 100 101 102 |
# File 'lib/toml/merge/node_wrapper.rb', line 96 def normalize_toml_line_range! return unless pair? return unless @start_line && @end_line && @end_line > @start_line line_count = [node_text(@node).sub(/\n\z/, '').lines.count, 1].max @end_line = @start_line + line_count - 1 end |
#opening_line ⇒ String?
Get the opening line for a table (the line with [table_name])
354 355 356 357 358 359 |
# File 'lib/toml/merge/node_wrapper.rb', line 354 def opening_line return unless @start_line return unless table? || array_of_tables? @lines[@start_line - 1] end |
#pair? ⇒ Boolean
Check if this is a key-value pair
171 172 173 |
# File 'lib/toml/merge/node_wrapper.rb', line 171 def pair? canonical_type == :pair end |
#pairs ⇒ Array<NodeWrapper>
Get key-value pairs from a table or inline_table.
Handles structural differences between backends:
- Tree-sitter: pairs are children of the table node
- Citrus: pairs are siblings at document level (table only contains header)
For Citrus backend, when no pair children are found, we look for sibling pairs in the document that belong to this table (pairs after this table's header but before the next table).
286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/toml/merge/node_wrapper.rb', line 286 def pairs return [] unless table? || inline_table? || document? || array_of_tables? # First, try to find pairs as direct children (tree-sitter structure) result = collect_child_pairs return result if result.any? # For Citrus backend: pairs are siblings, not children # Look for pairs in document that belong to this table return [] if @document_root.nil? || !(table? || array_of_tables?) collect_sibling_pairs_for_table end |
#process_additional_options(options) ⇒ Object
Process TOML-specific options (backend, document_root)
79 80 81 82 83 84 85 |
# File 'lib/toml/merge/node_wrapper.rb', line 79 def () @backend = .fetch(:backend, :tree_sitter) @document_root = [:document_root] @comment_entries = Array([:comment_entries]) @comment_tracker = [:comment_tracker] @raw_text = [:raw_text] end |
#string? ⇒ Boolean
Check if this is a TOML string
147 148 149 |
# File 'lib/toml/merge/node_wrapper.rb', line 147 def string? canonical_type == :string end |
#table? ⇒ Boolean
Check if this is a TOML table (section)
122 123 124 |
# File 'lib/toml/merge/node_wrapper.rb', line 122 def table? canonical_type == :table end |
#table_name ⇒ String?
Get the table name (header) if this is a table
195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/toml/merge/node_wrapper.rb', line 195 def table_name return unless table? || array_of_tables? # Find the dotted_key or bare_key child that represents the table name table_name_container.each do |child| child_canonical = NodeTypeNormalizer.canonical_type(child.type, @backend) if NodeTypeNormalizer.key_type?(child_canonical) # Strip whitespace (Citrus backend includes trailing space in key nodes) return node_text(child)&.strip end end parslet_element_table_name end |
#text ⇒ Object
269 270 271 272 273 |
# File 'lib/toml/merge/node_wrapper.rb', line 269 def text return @raw_text if @raw_text super end |
#type?(type_name) ⇒ Boolean
Check if this node has a specific type (checks both raw and canonical)
115 116 117 118 |
# File 'lib/toml/merge/node_wrapper.rb', line 115 def type?(type_name) type_sym = type_name.to_sym @node.type.to_sym == type_sym || canonical_type == type_sym end |
#value_node ⇒ NodeWrapper?
Get the value node if this is a pair
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/toml/merge/node_wrapper.rb', line 229 def value_node return unless pair? @node.each do |child| child_canonical = NodeTypeNormalizer.canonical_type(child.type, @backend) # Skip keys, equals sign, whitespace, and unknown (Citrus uses these for delimiters) next if NodeTypeNormalizer.key_type?(child_canonical) next if %i[equals whitespace unknown space].include?(child_canonical) if @backend == :parslet && child_canonical == :value scalar_child = each_child(child).find do |grandchild| grandchild_canonical = NodeTypeNormalizer.canonical_type(grandchild.type, @backend) !%i[whitespace unknown space].include?(grandchild_canonical) end if scalar_child return NodeWrapper.new( scalar_child, lines: @lines, source: @source, backend: @backend, document_root: @document_root, comment_entries: @comment_entries, comment_tracker: comment_tracker, raw_text: raw_pair_value_text ) end end return NodeWrapper.new( child, lines: @lines, source: @source, backend: @backend, document_root: @document_root ) end nil end |