prism-merge 1.1.1 → 1.1.2
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +20 -1
- data/README.md +29 -2
- data/lib/prism/merge/smart_merger.rb +61 -4
- data/lib/prism/merge/version.rb +1 -1
- data/sig/prism/merge.rbs +15 -2
- data.tar.gz.sig +0 -0
- metadata +4 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a67c080bc59cb7303a0f2d3da1c3e5518cf57f20bc1e5b0a43e8e003db155209
|
|
4
|
+
data.tar.gz: 9ddc4c529df3107414dc7c325f230d9224a30d3b828053a4ef76f9c38d05ce1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a88190811477660b57af41696a567ee899a76f04b918aa98d8e4c0ea45d5b0dca9b3eceb2abb8564dbb3912abdc35bf89b4a3736000b17d583d869e2f688fbd
|
|
7
|
+
data.tar.gz: 4cd9b298503649db46812dda1cfd2a04f37c0db776ff5dcc684ebe199fd81f6e469c13ed27ac375c7d23c6ae9e14a00afa2db5c8f8698a94ed080db595d8526d
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,23 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [1.1.2] - 2025-12-04
|
|
34
|
+
|
|
35
|
+
- TAG: [v1.1.2][1.1.2t]
|
|
36
|
+
- COVERAGE: 96.66% -- 868/898 lines in 9 files
|
|
37
|
+
- BRANCH COVERAGE: 82.84% -- 338/408 branches in 9 files
|
|
38
|
+
- 100.00% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `body_has_mergeable_statements?` private method to check if a block body contains statements that can be signature-matched
|
|
43
|
+
- `mergeable_statement?` private method to determine if a node type can generate signatures for merging
|
|
44
|
+
- `max_recursion_depth` option (defaults to `Float::INFINITY`) as a safety valve for edge cases
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **Fixed infinite recursion** when merging `CallNode` blocks (like `git_source`) that have matching signatures but non-mergeable body content (e.g., just string literals). The fix detects when a block body contains only literals/expressions with no signature-matchable statements and treats the node atomically instead of recursing.
|
|
49
|
+
|
|
33
50
|
## [1.1.1] - 2025-12-04
|
|
34
51
|
|
|
35
52
|
- TAG: [v1.1.1][1.1.1t]
|
|
@@ -154,7 +171,9 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
154
171
|
|
|
155
172
|
- Initial release
|
|
156
173
|
|
|
157
|
-
[Unreleased]: https://github.com/kettle-rb/prism-merge/compare/v1.1.
|
|
174
|
+
[Unreleased]: https://github.com/kettle-rb/prism-merge/compare/v1.1.2...HEAD
|
|
175
|
+
[1.1.2]: https://github.com/kettle-rb/prism-merge/compare/v1.1.1...v1.1.2
|
|
176
|
+
[1.1.2t]: https://github.com/kettle-rb/prism-merge/releases/tag/v1.1.2
|
|
158
177
|
[1.1.1]: https://github.com/kettle-rb/prism-merge/compare/v1.1.0...v1.1.1
|
|
159
178
|
[1.1.1t]: https://github.com/kettle-rb/prism-merge/releases/tag/v1.1.1
|
|
160
179
|
[1.1.0]: https://github.com/kettle-rb/prism-merge/compare/v1.0.3...v1.1.0
|
data/README.md
CHANGED
|
@@ -298,6 +298,33 @@ merger = Prism::Merge::SmartMerger.new(
|
|
|
298
298
|
# Result: Existing configs keep destination values, new configs added from template
|
|
299
299
|
```
|
|
300
300
|
|
|
301
|
+
### Recursion Depth Limit
|
|
302
|
+
|
|
303
|
+
Prism::Merge automatically detects when block bodies contain only literals or simple expressions (no mergeable statements) and treats them atomically. However, as a safety valve for edge cases, you can limit recursion depth:
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
# Limit recursive merging to 3 levels deep
|
|
307
|
+
merger = Prism::Merge::SmartMerger.new(
|
|
308
|
+
template,
|
|
309
|
+
destination,
|
|
310
|
+
max_recursion_depth: 3,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Disable recursive merging entirely (treat all nodes atomically)
|
|
314
|
+
merger = Prism::Merge::SmartMerger.new(
|
|
315
|
+
template,
|
|
316
|
+
destination,
|
|
317
|
+
max_recursion_depth: 0,
|
|
318
|
+
)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**When to use:**
|
|
322
|
+
|
|
323
|
+
- **`Float::INFINITY`** (default) - Normal operation, recursion terminates naturally based on content analysis.
|
|
324
|
+
- NOTE: If you get `stack level too deep (SystemStackError)`, please file a [bug](https://github.com/kettle-rb/prism-merge/issues)!
|
|
325
|
+
- **Finite value** - Safety valve if you encounter edge cases with unexpected deep recursion
|
|
326
|
+
- **`0`** - Disable recursive merging entirely; all matching nodes are treated atomically
|
|
327
|
+
|
|
301
328
|
### Custom Signature Generator
|
|
302
329
|
|
|
303
330
|
By default, Prism::Merge uses intelligent structural signatures to match nodes. The signature determines how nodes are matched between template and destination files.
|
|
@@ -332,7 +359,7 @@ The following node types support **recursive body merging**, where nested conten
|
|
|
332
359
|
- `ClassNode` - class bodies are recursively merged
|
|
333
360
|
- `ModuleNode` - module bodies are recursively merged
|
|
334
361
|
- `SingletonClassNode` - singleton class bodies are recursively merged
|
|
335
|
-
- `CallNode` with block - block bodies are recursively merged (e.g., `
|
|
362
|
+
- `CallNode` with block - block bodies are recursively merged **only when the body contains mergeable statements** (e.g., `describe do ... end` with nested `it` blocks). Blocks containing only literals or simple expressions (like `git_source(:github) { |repo| "https://..." }`) are treated atomically.
|
|
336
363
|
- `BeginNode` - begin/rescue/ensure blocks are recursively merged
|
|
337
364
|
|
|
338
365
|
#### Custom Signature Generator
|
|
@@ -1107,7 +1134,7 @@ Thanks for RTFM. ☺️
|
|
|
1107
1134
|
[📌gitmoji]: https://gitmoji.dev
|
|
1108
1135
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
1109
1136
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1110
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.
|
|
1137
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.898-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1111
1138
|
[🔐security]: SECURITY.md
|
|
1112
1139
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1113
1140
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -93,6 +93,12 @@ module Prism
|
|
|
93
93
|
# Default: "prism-merge" (looks for # prism-merge:freeze / # prism-merge:unfreeze)
|
|
94
94
|
# Freeze blocks preserve destination content unchanged during merge.
|
|
95
95
|
#
|
|
96
|
+
# @param max_recursion_depth [Integer, Float] Maximum depth for recursive body merging.
|
|
97
|
+
# Default: Float::INFINITY (no limit). This is a safety valve that users can set
|
|
98
|
+
# if they encounter edge cases. Normal merging terminates naturally based on
|
|
99
|
+
# body content analysis (blocks with non-mergeable content like literals are
|
|
100
|
+
# not recursed into).
|
|
101
|
+
#
|
|
96
102
|
# @raise [TemplateParseError] If template has syntax errors
|
|
97
103
|
# @raise [DestinationParseError] If destination has syntax errors
|
|
98
104
|
#
|
|
@@ -131,12 +137,14 @@ module Prism
|
|
|
131
137
|
# destination,
|
|
132
138
|
# signature_generator: sig_gen
|
|
133
139
|
# )
|
|
134
|
-
def initialize(template_content, dest_content, signature_generator: nil, signature_match_preference: :destination, add_template_only_nodes: false, freeze_token: FileAnalysis::DEFAULT_FREEZE_TOKEN)
|
|
140
|
+
def initialize(template_content, dest_content, signature_generator: nil, signature_match_preference: :destination, add_template_only_nodes: false, freeze_token: FileAnalysis::DEFAULT_FREEZE_TOKEN, max_recursion_depth: Float::INFINITY, current_depth: 0)
|
|
135
141
|
@template_content = template_content
|
|
136
142
|
@dest_content = dest_content
|
|
137
143
|
@signature_match_preference = signature_match_preference
|
|
138
144
|
@add_template_only_nodes = add_template_only_nodes
|
|
139
145
|
@freeze_token = freeze_token
|
|
146
|
+
@max_recursion_depth = max_recursion_depth
|
|
147
|
+
@current_depth = current_depth
|
|
140
148
|
@template_analysis = FileAnalysis.new(template_content, signature_generator: signature_generator, freeze_token: freeze_token)
|
|
141
149
|
@dest_analysis = FileAnalysis.new(dest_content, signature_generator: signature_generator, freeze_token: freeze_token)
|
|
142
150
|
@aligner = FileAligner.new(@template_analysis, @dest_analysis)
|
|
@@ -415,9 +423,14 @@ module Prism
|
|
|
415
423
|
# - Conditional nodes (if/unless) - treated as atomic units
|
|
416
424
|
# - Classes/modules/blocks containing freeze blocks - frozen content would be lost
|
|
417
425
|
# - Nodes of different types
|
|
426
|
+
# - Blocks whose body contains only literals/expressions with no mergeable statements
|
|
427
|
+
# - When max_recursion_depth has been reached (safety valve)
|
|
418
428
|
def should_merge_recursively?(template_node, dest_node)
|
|
419
429
|
return false unless template_node && dest_node
|
|
420
430
|
|
|
431
|
+
# Safety valve: stop recursion if max depth reached
|
|
432
|
+
return false if @current_depth >= @max_recursion_depth
|
|
433
|
+
|
|
421
434
|
# Both nodes must be the same type
|
|
422
435
|
return false unless template_node.class == dest_node.class
|
|
423
436
|
|
|
@@ -427,8 +440,10 @@ module Prism
|
|
|
427
440
|
# Class/module definitions - merge their body contents
|
|
428
441
|
true
|
|
429
442
|
when Prism::CallNode
|
|
430
|
-
# Only merge if both have blocks
|
|
431
|
-
template_node.block && dest_node.block
|
|
443
|
+
# Only merge if both have blocks with mergeable content
|
|
444
|
+
template_node.block && dest_node.block &&
|
|
445
|
+
body_has_mergeable_statements?(template_node.block.body) &&
|
|
446
|
+
body_has_mergeable_statements?(dest_node.block.body)
|
|
432
447
|
when Prism::BeginNode
|
|
433
448
|
# begin/rescue/ensure blocks - merge statements
|
|
434
449
|
template_node.statements && dest_node.statements
|
|
@@ -456,6 +471,45 @@ module Prism
|
|
|
456
471
|
true
|
|
457
472
|
end
|
|
458
473
|
|
|
474
|
+
# Check if a body (StatementsNode) contains statements that could be merged.
|
|
475
|
+
#
|
|
476
|
+
# Mergeable statements are those that can generate signatures and be
|
|
477
|
+
# independently matched between template and destination. This includes
|
|
478
|
+
# method definitions, class/module definitions, method calls, assignments, etc.
|
|
479
|
+
#
|
|
480
|
+
# Bodies containing only literals (strings, numbers, arrays, hashes) or
|
|
481
|
+
# simple expressions should not be recursively merged as there's nothing
|
|
482
|
+
# to align - they should be treated atomically.
|
|
483
|
+
#
|
|
484
|
+
# @param body [Prism::StatementsNode, nil] The body to check
|
|
485
|
+
# @return [Boolean] true if the body contains mergeable statements
|
|
486
|
+
# @api private
|
|
487
|
+
def body_has_mergeable_statements?(body)
|
|
488
|
+
return false unless body.is_a?(Prism::StatementsNode)
|
|
489
|
+
return false if body.body.empty?
|
|
490
|
+
|
|
491
|
+
body.body.any? { |stmt| mergeable_statement?(stmt) }
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Check if a statement is mergeable (can generate a signature).
|
|
495
|
+
#
|
|
496
|
+
# @param node [Prism::Node] The node to check
|
|
497
|
+
# @return [Boolean] true if this node type can be merged
|
|
498
|
+
# @api private
|
|
499
|
+
def mergeable_statement?(node)
|
|
500
|
+
case node
|
|
501
|
+
when Prism::CallNode, Prism::DefNode, Prism::ClassNode, Prism::ModuleNode,
|
|
502
|
+
Prism::SingletonClassNode, Prism::ConstantWriteNode, Prism::ConstantPathWriteNode,
|
|
503
|
+
Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode,
|
|
504
|
+
Prism::ClassVariableWriteNode, Prism::GlobalVariableWriteNode,
|
|
505
|
+
Prism::MultiWriteNode, Prism::IfNode, Prism::UnlessNode, Prism::CaseNode,
|
|
506
|
+
Prism::BeginNode
|
|
507
|
+
true
|
|
508
|
+
else
|
|
509
|
+
false
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
|
|
459
513
|
# Check if a node's body contains freeze block markers.
|
|
460
514
|
#
|
|
461
515
|
# @param node [Prism::Node] The node to check
|
|
@@ -512,6 +566,7 @@ module Prism
|
|
|
512
566
|
# @note The nested merger is configured with:
|
|
513
567
|
# - Same signature_generator, signature_match_preference, and add_template_only_nodes
|
|
514
568
|
# - freeze_token: nil (freeze blocks not processed in nested context)
|
|
569
|
+
# - Incremented current_depth to track recursion level
|
|
515
570
|
#
|
|
516
571
|
# @api private
|
|
517
572
|
def merge_node_body_recursively(template_node, dest_node, anchor)
|
|
@@ -519,7 +574,7 @@ module Prism
|
|
|
519
574
|
template_body = extract_node_body(template_node, @template_analysis)
|
|
520
575
|
dest_body = extract_node_body(dest_node, @dest_analysis)
|
|
521
576
|
|
|
522
|
-
# Recursively merge the bodies
|
|
577
|
+
# Recursively merge the bodies with incremented depth
|
|
523
578
|
body_merger = SmartMerger.new(
|
|
524
579
|
template_body,
|
|
525
580
|
dest_body,
|
|
@@ -527,6 +582,8 @@ module Prism
|
|
|
527
582
|
signature_match_preference: @signature_match_preference,
|
|
528
583
|
add_template_only_nodes: @add_template_only_nodes,
|
|
529
584
|
freeze_token: nil, # Don't process freeze blocks in nested context
|
|
585
|
+
max_recursion_depth: @max_recursion_depth,
|
|
586
|
+
current_depth: @current_depth + 1,
|
|
530
587
|
)
|
|
531
588
|
merged_body = body_merger.merge.rstrip
|
|
532
589
|
|
data/lib/prism/merge/version.rb
CHANGED
data/sig/prism/merge.rbs
CHANGED
|
@@ -323,7 +323,10 @@ module Prism
|
|
|
323
323
|
String dest_content,
|
|
324
324
|
?signature_generator: (^(untyped) -> Array[untyped])?,
|
|
325
325
|
?signature_match_preference: Symbol,
|
|
326
|
-
?add_template_only_nodes: bool
|
|
326
|
+
?add_template_only_nodes: bool,
|
|
327
|
+
?freeze_token: String?,
|
|
328
|
+
?max_recursion_depth: (Integer | Float),
|
|
329
|
+
?current_depth: Integer
|
|
327
330
|
) -> void
|
|
328
331
|
|
|
329
332
|
def merge: () -> String
|
|
@@ -353,9 +356,19 @@ module Prism
|
|
|
353
356
|
def find_node_at_line: (FileAnalysis analysis, Integer line_num) -> untyped?
|
|
354
357
|
|
|
355
358
|
# Determine if two matching nodes should be recursively merged
|
|
356
|
-
# Returns true for ClassNode, ModuleNode, SingletonClassNode, CallNode
|
|
359
|
+
# Returns true for ClassNode, ModuleNode, SingletonClassNode, and CallNode/BeginNode
|
|
360
|
+
# with blocks that contain mergeable statements.
|
|
361
|
+
# Returns false if max_recursion_depth has been reached (safety valve).
|
|
357
362
|
def should_merge_recursively?: (untyped? template_node, untyped? dest_node) -> bool
|
|
358
363
|
|
|
364
|
+
# Check if a body (StatementsNode) contains statements that could be merged
|
|
365
|
+
# Returns true if body contains CallNode, DefNode, ClassNode, assignments, etc.
|
|
366
|
+
def body_has_mergeable_statements?: (untyped? body) -> bool
|
|
367
|
+
|
|
368
|
+
# Check if a statement is mergeable (can generate a signature)
|
|
369
|
+
# Returns true for CallNode, DefNode, ClassNode, ModuleNode, assignments, conditionals, etc.
|
|
370
|
+
def mergeable_statement?: (untyped node) -> bool
|
|
371
|
+
|
|
359
372
|
# Check if a node's body contains freeze block markers
|
|
360
373
|
def node_contains_freeze_blocks?: (untyped node) -> bool
|
|
361
374
|
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: prism-merge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -265,10 +265,10 @@ licenses:
|
|
|
265
265
|
- MIT
|
|
266
266
|
metadata:
|
|
267
267
|
homepage_uri: https://prism-merge.galtzo.com/
|
|
268
|
-
source_code_uri: https://github.com/kettle-rb/prism-merge/tree/v1.1.
|
|
269
|
-
changelog_uri: https://github.com/kettle-rb/prism-merge/blob/v1.1.
|
|
268
|
+
source_code_uri: https://github.com/kettle-rb/prism-merge/tree/v1.1.2
|
|
269
|
+
changelog_uri: https://github.com/kettle-rb/prism-merge/blob/v1.1.2/CHANGELOG.md
|
|
270
270
|
bug_tracker_uri: https://github.com/kettle-rb/prism-merge/issues
|
|
271
|
-
documentation_uri: https://www.rubydoc.info/gems/prism-merge/1.1.
|
|
271
|
+
documentation_uri: https://www.rubydoc.info/gems/prism-merge/1.1.2
|
|
272
272
|
funding_uri: https://github.com/sponsors/pboling
|
|
273
273
|
wiki_uri: https://github.com/kettle-rb/prism-merge/wiki
|
|
274
274
|
news_uri: https://www.railsbling.com/tags/prism-merge
|
metadata.gz.sig
CHANGED
|
Binary file
|