bash-merge 1.0.2 → 2.0.1
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 +38 -1
- data/README.md +58 -30
- data/lib/bash/merge/conflict_resolver.rb +47 -42
- data/lib/bash/merge/emitter.rb +34 -74
- data/lib/bash/merge/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +12 -12
- 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: c67a335db3b9d46652ca59793d480c4087bd88d4233070f0a6b4036a2902f136
|
|
4
|
+
data.tar.gz: a685d8c63ead508e3583eab6373d9cff3277b39c423315a3ad6aad77a3b5c9d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b32ddf1683fa6fc4ac1e9cfeef550416b54cf7d552eea9707642dd6176be1d7cb2fa4c6c8074770beccc5debbb8c850f53d45c56c8bdaa1208649bae302be820
|
|
7
|
+
data.tar.gz: bde6435c6ace02b852880ff8c0517e2e79ff03417ce64ef0ee764f1a9c0395fe87b0bdf992e1bc6088971f42ffaf506d8448e98fbff84fb1bc204b17cce79949
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,39 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [2.0.1] - 2026-01-09
|
|
34
|
+
|
|
35
|
+
- TAG: [v2.0.1][2.0.1t]
|
|
36
|
+
- COVERAGE: 96.34% -- 500/519 lines in 11 files
|
|
37
|
+
- BRANCH COVERAGE: 76.54% -- 124/162 branches in 11 files
|
|
38
|
+
- 96.33% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- FFI backend isolation for test suite
|
|
43
|
+
- Added `bin/rspec-ffi` script to run FFI specs in isolation (before MRI backend loads)
|
|
44
|
+
- Added `spec/spec_ffi_helper.rb` for FFI-specific test configuration
|
|
45
|
+
- Updated Rakefile with `ffi_specs` and `remaining_specs` tasks
|
|
46
|
+
- The `:test` task now runs FFI specs first, then remaining specs
|
|
47
|
+
- Emitter-based output
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- ast-merge v3.1.0, adds Ast::Merge::EmitterBase
|
|
52
|
+
- tree_haver v4.0.4, adds error handling for FFI backend
|
|
53
|
+
- major spec refactor
|
|
54
|
+
|
|
55
|
+
## [2.0.0] - 2026-01-06
|
|
56
|
+
|
|
57
|
+
- TAG: [v2.0.0][2.0.0t]
|
|
58
|
+
- COVERAGE: 100.00% -- 109/109 lines in 2 files
|
|
59
|
+
- BRANCH COVERAGE: 100.00% -- 28/28 branches in 2 files
|
|
60
|
+
- 96.46% documented
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- ast-merge v3.0.0 compatibility (breaking changes)
|
|
65
|
+
|
|
33
66
|
## [1.0.2] - 2026-01-02
|
|
34
67
|
|
|
35
68
|
- TAG: [v1.0.2][1.0.2t]
|
|
@@ -74,7 +107,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
74
107
|
|
|
75
108
|
### Security
|
|
76
109
|
|
|
77
|
-
[Unreleased]: https://github.com/kettle-rb/bash-merge/compare/
|
|
110
|
+
[Unreleased]: https://github.com/kettle-rb/bash-merge/compare/v2.0.1...HEAD
|
|
111
|
+
[2.0.1]: https://github.com/kettle-rb/bash-merge/compare/v2.0.0...v2.0.1
|
|
112
|
+
[2.0.1t]: https://github.com/kettle-rb/bash-merge/releases/tag/v2.0.1
|
|
113
|
+
[2.0.0]: https://github.com/kettle-rb/bash-merge/compare/v1.0.2...v2.0.0
|
|
114
|
+
[2.0.0t]: https://github.com/kettle-rb/bash-merge/releases/tag/v2.0.0
|
|
78
115
|
[1.0.2]: https://github.com/kettle-rb/bash-merge/compare/v1.0.1...v1.0.2
|
|
79
116
|
[1.0.2t]: https://github.com/kettle-rb/bash-merge/releases/tag/v1.0.2
|
|
80
117
|
[1.0.1]: https://github.com/kettle-rb/bash-merge/compare/v1.0.0...v1.0.1
|
data/README.md
CHANGED
|
@@ -98,33 +98,56 @@ result = merger.merge
|
|
|
98
98
|
File.write("merged.sh", result.to_bash)
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
|
|
102
101
|
### The `*-merge` Gem Family
|
|
103
102
|
|
|
104
103
|
The `*-merge` gem family provides intelligent, AST-based merging for various file formats. At the foundation is [tree_haver][tree_haver], which provides a unified cross-Ruby parsing API that works seamlessly across MRI, JRuby, and TruffleRuby.
|
|
105
104
|
|
|
106
|
-
| Gem | Format
|
|
107
|
-
|
|
108
|
-
| [tree_haver][tree_haver] | Multi
|
|
109
|
-
| [ast-merge][ast-merge] | Text
|
|
110
|
-
| [
|
|
111
|
-
| [
|
|
112
|
-
| [
|
|
113
|
-
| [
|
|
114
|
-
| [
|
|
115
|
-
| [
|
|
116
|
-
| [
|
|
117
|
-
| [
|
|
118
|
-
| [
|
|
119
|
-
| [
|
|
120
|
-
| [
|
|
105
|
+
| Gem | Language<br>/ Format | Parser Backend(s) | Description |
|
|
106
|
+
|------------------------------------------|----------------------|-----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------|
|
|
107
|
+
| [tree_haver][tree_haver] | Multi | MRI C, Rust, FFI, Java, Prism, Psych, Commonmarker, Markly, Citrus | **Foundation**: Cross-Ruby adapter for parsing libraries (like Faraday for HTTP) |
|
|
108
|
+
| [ast-merge][ast-merge] | Text | internal | **Infrastructure**: Shared base classes and merge logic for all `*-merge` gems |
|
|
109
|
+
| [bash-merge][bash-merge] | Bash | [tree-sitter-bash][ts-bash] (via tree_haver) | Smart merge for Bash scripts |
|
|
110
|
+
| [commonmarker-merge][commonmarker-merge] | Markdown | [Commonmarker][commonmarker] (via tree_haver) | Smart merge for Markdown (CommonMark via comrak Rust) |
|
|
111
|
+
| [dotenv-merge][dotenv-merge] | Dotenv | internal | Smart merge for `.env` files |
|
|
112
|
+
| [json-merge][json-merge] | JSON | [tree-sitter-json][ts-json] (via tree_haver) | Smart merge for JSON files |
|
|
113
|
+
| [jsonc-merge][jsonc-merge] | JSONC | [tree-sitter-jsonc][ts-jsonc] (via tree_haver) | ⚠️ Proof of concept; Smart merge for JSON with Comments |
|
|
114
|
+
| [markdown-merge][markdown-merge] | Markdown | [Commonmarker][commonmarker] / [Markly][markly] (via tree_haver) | **Foundation**: Shared base for Markdown mergers with inner code block merging |
|
|
115
|
+
| [markly-merge][markly-merge] | Markdown | [Markly][markly] (via tree_haver) | Smart merge for Markdown (CommonMark via cmark-gfm C) |
|
|
116
|
+
| [prism-merge][prism-merge] | Ruby | [Prism][prism] (`prism` std lib gem) | Smart merge for Ruby source files |
|
|
117
|
+
| [psych-merge][psych-merge] | YAML | [Psych][psych] (`psych` std lib gem) | Smart merge for YAML files |
|
|
118
|
+
| [rbs-merge][rbs-merge] | RBS | [tree-sitter-bash][ts-rbs] (via tree_haver), [RBS][rbs] (`rbs` std lib gem) | Smart merge for Ruby type signatures |
|
|
119
|
+
| [toml-merge][toml-merge] | TOML | [Citrus + toml-rb][toml-rb] (default, via tree_haver), [tree-sitter-toml][ts-toml] (via tree_haver) | Smart merge for TOML files |
|
|
120
|
+
|
|
121
|
+
#### Backend Platform Compatibility
|
|
122
|
+
|
|
123
|
+
tree_haver supports multiple parsing backends, but not all backends work on all Ruby platforms:
|
|
124
|
+
|
|
125
|
+
| Platform 👉️<br> TreeHaver Backend 👇️ | MRI | JRuby | TruffleRuby | Notes |
|
|
126
|
+
|------------------------------------------------|:---:|:-----:|:-----------:|-----------------------------------------------------|
|
|
127
|
+
| **MRI** ([ruby_tree_sitter][ruby_tree_sitter]) | ✅ | ❌ | ❌ | C extension, MRI only |
|
|
128
|
+
| **Rust** ([tree_stump][tree_stump]) | ✅ | ❌ | ❌ | Rust extension via magnus/rb-sys, MRI only |
|
|
129
|
+
| **FFI** | ✅ | ✅ | ❌ | TruffleRuby's FFI doesn't support `STRUCT_BY_VALUE` |
|
|
130
|
+
| **Java** ([jtreesitter][jtreesitter]) | ❌ | ✅ | ❌ | JRuby only, requires grammar JARs |
|
|
131
|
+
| **Prism** | ✅ | ✅ | ✅ | Ruby parsing, stdlib in Ruby 3.4+ |
|
|
132
|
+
| **Psych** | ✅ | ✅ | ✅ | YAML parsing, stdlib |
|
|
133
|
+
| **Citrus** | ✅ | ✅ | ✅ | Pure Ruby, no native dependencies |
|
|
134
|
+
| **Commonmarker** | ✅ | ❌ | ❓ | Rust extension for Markdown |
|
|
135
|
+
| **Markly** | ✅ | ❌ | ❓ | C extension for Markdown |
|
|
136
|
+
|
|
137
|
+
**Legend**: ✅ = Works, ❌ = Does not work, ❓ = Untested
|
|
138
|
+
|
|
139
|
+
**Why some backends don't work on certain platforms**:
|
|
140
|
+
|
|
141
|
+
- **JRuby**: Runs on the JVM; cannot load native C/Rust extensions (`.so` files)
|
|
142
|
+
- **TruffleRuby**: Has C API emulation via Sulong/LLVM, but it doesn't expose all MRI internals that native extensions require (e.g., `RBasic.flags`, `rb_gc_writebarrier`)
|
|
143
|
+
- **FFI on TruffleRuby**: TruffleRuby's FFI implementation doesn't support returning structs by value, which tree-sitter's C API requires
|
|
121
144
|
|
|
122
145
|
**Example implementations** for the gem templating use case:
|
|
123
146
|
|
|
124
|
-
| Gem
|
|
125
|
-
|
|
126
|
-
| [kettle-dev]
|
|
127
|
-
| [kettle-jem]
|
|
147
|
+
| Gem | Purpose | Description |
|
|
148
|
+
|--------------------------|-----------------|-----------------------------------------------|
|
|
149
|
+
| [kettle-dev][kettle-dev] | Gem Development | Gem templating tool using `*-merge` gems |
|
|
150
|
+
| [kettle-jem][kettle-jem] | Gem Templating | Gem template library with smart merge support |
|
|
128
151
|
|
|
129
152
|
[tree_haver]: https://github.com/kettle-rb/tree_haver
|
|
130
153
|
[ast-merge]: https://github.com/kettle-rb/ast-merge
|
|
@@ -144,16 +167,18 @@ The `*-merge` gem family provides intelligent, AST-based merging for various fil
|
|
|
144
167
|
[prism]: https://github.com/ruby/prism
|
|
145
168
|
[psych]: https://github.com/ruby/psych
|
|
146
169
|
[ts-json]: https://github.com/tree-sitter/tree-sitter-json
|
|
170
|
+
[ts-jsonc]: https://gitlab.com/WhyNotHugo/tree-sitter-jsonc
|
|
147
171
|
[ts-bash]: https://github.com/tree-sitter/tree-sitter-bash
|
|
172
|
+
[ts-rbs]: https://github.com/joker1007/tree-sitter-rbs
|
|
148
173
|
[ts-toml]: https://github.com/tree-sitter-grammars/tree-sitter-toml
|
|
174
|
+
[dotenv]: https://github.com/bkeepers/dotenv
|
|
149
175
|
[rbs]: https://github.com/ruby/rbs
|
|
150
176
|
[toml-rb]: https://github.com/emancu/toml-rb
|
|
151
177
|
[markly]: https://github.com/ioquatix/markly
|
|
152
178
|
[commonmarker]: https://github.com/gjtorikian/commonmarker
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
[
|
|
156
|
-
[dotenv]: https://github.com/bkeepers/dotenv
|
|
179
|
+
[ruby_tree_sitter]: https://github.com/Faveod/ruby-tree-sitter
|
|
180
|
+
[tree_stump]: https://github.com/joker1007/tree_stump
|
|
181
|
+
[jtreesitter]: https://central.sonatype.com/artifact/io.github.tree-sitter/jtreesitter
|
|
157
182
|
|
|
158
183
|
## 💡 Info you can shake a stick at
|
|
159
184
|
|
|
@@ -206,12 +231,16 @@ The maintainers of this and thousands of other packages are working with Tidelif
|
|
|
206
231
|
[](https://tidelift.com/subscription/pkg/rubygems-bash-merge?utm_source=rubygems-bash-merge&utm_medium=referral&utm_campaign=readme)
|
|
207
232
|
|
|
208
233
|
- 💡Subscribe for support guarantees covering *all* your FLOSS dependencies
|
|
234
|
+
|
|
209
235
|
- 💡Tidelift is part of [Sonar](https://blog.tidelift.com/tidelift-joins-sonar)
|
|
236
|
+
|
|
210
237
|
- 💡Tidelift pays maintainers to maintain the software you depend on\!<br/>📊`@`Pointy Haired Boss: An [enterprise support](https://tidelift.com/subscription/pkg/rubygems-bash-merge?utm_source=rubygems-bash-merge&utm_medium=referral&utm_campaign=readme) subscription is "[never gonna let you down](https://www.youtube.com/watch?v=dQw4w9WgXcQ)", and *supports* open source maintainers
|
|
211
|
-
Alternatively:
|
|
238
|
+
Alternatively:
|
|
212
239
|
|
|
213
240
|
- [](https://discord.gg/3qme4XHNKN)
|
|
241
|
+
|
|
214
242
|
- [](https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share)
|
|
243
|
+
|
|
215
244
|
- [](https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github)
|
|
216
245
|
</details>
|
|
217
246
|
|
|
@@ -357,8 +386,7 @@ export TREE_SITTER_BASH_PATH="/path/to/libtree-sitter-bash.so"
|
|
|
357
386
|
If the gem can't find the parser, check for versioned files and either:
|
|
358
387
|
- Set `TREE_SITTER_BASH_PATH` to the full versioned path, or
|
|
359
388
|
- Create a symlink: `sudo ln -s /usr/lib64/libtree-sitter-bash.so.14 /usr/lib64/libtree-sitter-bash.so`
|
|
360
|
-
Add this to your shell profile (`.bashrc`, `.zshrc`, etc.) for persistence.
|
|
361
|
-
|
|
389
|
+
Add this to your shell profile (`.bashrc`, `.zshrc`, etc.) for persistence.
|
|
362
390
|
### 💎 Ruby Interface Gems
|
|
363
391
|
|
|
364
392
|
In addition to the tree-sitter parser library, you need a Ruby gem that provides
|
|
@@ -560,7 +588,7 @@ Support us with a monthly donation and help us continue our activities. \[[Becom
|
|
|
560
588
|
NOTE: [kettle-readme-backers](https://github.com/kettle-rb/bash-merge/blob/main/exe/kettle-readme-backers) updates this list every day, automatically.
|
|
561
589
|
|
|
562
590
|
<!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
|
|
563
|
-
No backers yet. Be the first
|
|
591
|
+
No backers yet. Be the first!
|
|
564
592
|
<!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
|
|
565
593
|
|
|
566
594
|
### Open Collective for Organizations
|
|
@@ -570,7 +598,7 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s
|
|
|
570
598
|
NOTE: [kettle-readme-backers](https://github.com/kettle-rb/bash-merge/blob/main/exe/kettle-readme-backers) updates this list every day, automatically.
|
|
571
599
|
|
|
572
600
|
<!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
|
|
573
|
-
No sponsors yet. Be the first
|
|
601
|
+
No sponsors yet. Be the first!
|
|
574
602
|
<!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
|
|
575
603
|
|
|
576
604
|
[kettle-readme-backers]: https://github.com/kettle-rb/bash-merge/blob/main/exe/kettle-readme-backers
|
|
@@ -875,7 +903,7 @@ Thanks for RTFM. ☺️
|
|
|
875
903
|
[📌gitmoji]: https://gitmoji.dev
|
|
876
904
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
877
905
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
878
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.
|
|
906
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.519-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
879
907
|
[🔐security]: SECURITY.md
|
|
880
908
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
881
909
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -9,9 +9,6 @@ module Bash
|
|
|
9
9
|
# resolver = ConflictResolver.new(template_analysis, dest_analysis)
|
|
10
10
|
# resolver.resolve(result)
|
|
11
11
|
class ConflictResolver < ::Ast::Merge::ConflictResolverBase
|
|
12
|
-
# Alias for backward compatibility with existing API
|
|
13
|
-
alias_method :preference, :preference
|
|
14
|
-
|
|
15
12
|
# Creates a new ConflictResolver
|
|
16
13
|
#
|
|
17
14
|
# @param template_analysis [FileAnalysis] Analyzed template file
|
|
@@ -33,6 +30,7 @@ module Bash
|
|
|
33
30
|
match_refiner: match_refiner,
|
|
34
31
|
**options
|
|
35
32
|
)
|
|
33
|
+
@emitter = Emitter.new
|
|
36
34
|
end
|
|
37
35
|
|
|
38
36
|
# Resolve conflicts and populate the result
|
|
@@ -43,6 +41,9 @@ module Bash
|
|
|
43
41
|
template_nodes = @template_analysis.nodes
|
|
44
42
|
dest_nodes = @dest_analysis.nodes
|
|
45
43
|
|
|
44
|
+
# Clear emitter for fresh merge
|
|
45
|
+
@emitter.clear
|
|
46
|
+
|
|
46
47
|
# Build signature maps
|
|
47
48
|
template_by_sig = build_signature_map(template_nodes, @template_analysis)
|
|
48
49
|
dest_by_sig = build_signature_map(dest_nodes, @dest_analysis)
|
|
@@ -51,17 +52,24 @@ module Bash
|
|
|
51
52
|
processed_template_sigs = ::Set.new
|
|
52
53
|
processed_dest_sigs = ::Set.new
|
|
53
54
|
|
|
54
|
-
# Process nodes
|
|
55
|
-
|
|
55
|
+
# Process nodes via emitter
|
|
56
|
+
merge_nodes_to_emitter(
|
|
56
57
|
template_nodes,
|
|
57
58
|
dest_nodes,
|
|
58
59
|
template_by_sig,
|
|
59
60
|
dest_by_sig,
|
|
60
61
|
processed_template_sigs,
|
|
61
62
|
processed_dest_sigs,
|
|
62
|
-
result,
|
|
63
63
|
)
|
|
64
64
|
|
|
65
|
+
# Transfer emitter output to result
|
|
66
|
+
emitted_content = @emitter.to_s
|
|
67
|
+
unless emitted_content.empty?
|
|
68
|
+
emitted_content.lines.each do |line|
|
|
69
|
+
result.add_line(line.chomp, decision: MergeResult::DECISION_MERGED, source: :merged)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
65
73
|
DebugLogger.debug("Conflict resolution complete", {
|
|
66
74
|
template_nodes: template_nodes.size,
|
|
67
75
|
dest_nodes: dest_nodes.size,
|
|
@@ -72,18 +80,14 @@ module Bash
|
|
|
72
80
|
|
|
73
81
|
private
|
|
74
82
|
|
|
75
|
-
def
|
|
76
|
-
# Determine the output order based on preference
|
|
77
|
-
# We'll iterate through destination nodes first (to preserve dest order for matches)
|
|
78
|
-
# then add any template-only nodes if configured
|
|
79
|
-
|
|
83
|
+
def merge_nodes_to_emitter(template_nodes, dest_nodes, template_by_sig, dest_by_sig, processed_template_sigs, processed_dest_sigs)
|
|
80
84
|
# First pass: Process destination nodes and find matches
|
|
81
85
|
dest_nodes.each do |dest_node|
|
|
82
86
|
dest_sig = @dest_analysis.generate_signature(dest_node)
|
|
83
87
|
|
|
84
88
|
# Freeze blocks from destination are always preserved
|
|
85
89
|
if freeze_node?(dest_node)
|
|
86
|
-
|
|
90
|
+
emit_freeze_block(dest_node)
|
|
87
91
|
processed_dest_sigs << dest_sig if dest_sig
|
|
88
92
|
next
|
|
89
93
|
end
|
|
@@ -93,18 +97,18 @@ module Bash
|
|
|
93
97
|
template_info = template_by_sig[dest_sig].first
|
|
94
98
|
template_node = template_info[:node]
|
|
95
99
|
|
|
96
|
-
# Decide which to
|
|
100
|
+
# Decide which to emit based on preference
|
|
97
101
|
if @preference == :destination
|
|
98
|
-
|
|
102
|
+
emit_node(dest_node, @dest_analysis)
|
|
99
103
|
else
|
|
100
|
-
|
|
104
|
+
emit_node(template_node, @template_analysis)
|
|
101
105
|
end
|
|
102
106
|
|
|
103
107
|
processed_dest_sigs << dest_sig
|
|
104
108
|
processed_template_sigs << dest_sig
|
|
105
109
|
else
|
|
106
110
|
# Destination-only node - always keep
|
|
107
|
-
|
|
111
|
+
emit_node(dest_node, @dest_analysis)
|
|
108
112
|
processed_dest_sigs << dest_sig if dest_sig
|
|
109
113
|
end
|
|
110
114
|
end
|
|
@@ -118,43 +122,44 @@ module Bash
|
|
|
118
122
|
# Skip if already processed (matched with dest)
|
|
119
123
|
next if template_sig && processed_template_sigs.include?(template_sig)
|
|
120
124
|
|
|
121
|
-
# Skip freeze blocks from template
|
|
125
|
+
# Skip freeze blocks from template
|
|
122
126
|
next if freeze_node?(template_node)
|
|
123
127
|
|
|
124
128
|
# Add template-only node
|
|
125
|
-
|
|
129
|
+
emit_node(template_node, @template_analysis)
|
|
126
130
|
processed_template_sigs << template_sig if template_sig
|
|
127
131
|
end
|
|
128
132
|
end
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
# Emit a single node to the emitter
|
|
135
|
+
# @param node [NodeWrapper] Node to emit
|
|
136
|
+
# @param analysis [FileAnalysis] Analysis for accessing source
|
|
137
|
+
def emit_node(node, analysis)
|
|
138
|
+
return if freeze_node?(node)
|
|
139
|
+
|
|
140
|
+
# Emit leading comments
|
|
141
|
+
if node.start_line
|
|
142
|
+
leading = analysis.comment_tracker.leading_comments_before(node.start_line)
|
|
143
|
+
leading.each do |comment|
|
|
144
|
+
@emitter.emit_tracked_comment(comment)
|
|
145
|
+
end
|
|
137
146
|
end
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def add_wrapper_to_result(wrapper, result, source, decision)
|
|
141
|
-
return unless wrapper.start_line && wrapper.end_line
|
|
142
147
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
# Emit the node content
|
|
149
|
+
if node.start_line && node.end_line
|
|
150
|
+
lines = []
|
|
151
|
+
(node.start_line..node.end_line).each do |line_num|
|
|
152
|
+
line = analysis.line_at(line_num)
|
|
153
|
+
lines << line if line
|
|
154
|
+
end
|
|
155
|
+
@emitter.emit_raw_lines(lines)
|
|
149
156
|
end
|
|
157
|
+
end
|
|
150
158
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
result.add_line(line.chomp, decision: decision, source: source, original_line: line_num)
|
|
157
|
-
end
|
|
159
|
+
# Emit a freeze block
|
|
160
|
+
# @param freeze_node [FreezeNode] Freeze block to emit
|
|
161
|
+
def emit_freeze_block(freeze_node)
|
|
162
|
+
@emitter.emit_raw_lines(freeze_node.lines)
|
|
158
163
|
end
|
|
159
164
|
end
|
|
160
165
|
end
|
data/lib/bash/merge/emitter.rb
CHANGED
|
@@ -6,27 +6,28 @@ module Bash
|
|
|
6
6
|
# This class provides utilities for emitting Bash while maintaining
|
|
7
7
|
# the original structure, comments, and style choices.
|
|
8
8
|
#
|
|
9
|
+
# Inherits common emitter functionality from Ast::Merge::EmitterBase.
|
|
10
|
+
#
|
|
9
11
|
# @example Basic usage
|
|
10
12
|
# emitter = Emitter.new
|
|
11
13
|
# emitter.emit_comment("This is a comment")
|
|
12
14
|
# emitter.emit_line("echo 'hello'")
|
|
13
|
-
class Emitter
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
attr_reader :indent_level
|
|
15
|
+
class Emitter < Ast::Merge::EmitterBase
|
|
16
|
+
# Initialize subclass-specific state
|
|
17
|
+
def initialize_subclass_state(**options)
|
|
18
|
+
# Bash doesn't need separator tracking like JSON
|
|
19
|
+
end
|
|
19
20
|
|
|
20
|
-
#
|
|
21
|
-
|
|
21
|
+
# Clear subclass-specific state
|
|
22
|
+
def clear_subclass_state
|
|
23
|
+
# Nothing to clear for Bash
|
|
24
|
+
end
|
|
22
25
|
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@lines
|
|
28
|
-
@indent_level = 0
|
|
29
|
-
@indent_size = indent_size
|
|
26
|
+
# Emit a tracked comment from CommentTracker
|
|
27
|
+
# @param comment [Hash] Comment with :text, :indent
|
|
28
|
+
def emit_tracked_comment(comment)
|
|
29
|
+
indent = " " * (comment[:indent] || 0)
|
|
30
|
+
@lines << "#{indent}# #{comment[:text]}"
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
# Emit a comment line
|
|
@@ -44,22 +45,6 @@ module Bash
|
|
|
44
45
|
end
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
# Emit leading comments
|
|
48
|
-
#
|
|
49
|
-
# @param comments [Array<Hash>] Comment hashes from CommentTracker
|
|
50
|
-
def emit_leading_comments(comments)
|
|
51
|
-
comments.each do |comment|
|
|
52
|
-
# Preserve original indentation from comment
|
|
53
|
-
indent = " " * (comment[:indent] || 0)
|
|
54
|
-
@lines << "#{indent}# #{comment[:text]}"
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Emit a blank line
|
|
59
|
-
def emit_blank_line
|
|
60
|
-
@lines << ""
|
|
61
|
-
end
|
|
62
|
-
|
|
63
48
|
# Emit a shebang line
|
|
64
49
|
#
|
|
65
50
|
# @param interpreter [String] Interpreter path (e.g., "/bin/bash")
|
|
@@ -85,12 +70,12 @@ module Bash
|
|
|
85
70
|
# @param name [String] Function name
|
|
86
71
|
def emit_function_start(name)
|
|
87
72
|
@lines << "#{current_indent}#{name}() {"
|
|
88
|
-
|
|
73
|
+
indent
|
|
89
74
|
end
|
|
90
75
|
|
|
91
76
|
# Emit a function definition end
|
|
92
77
|
def emit_function_end
|
|
93
|
-
|
|
78
|
+
dedent
|
|
94
79
|
@lines << "#{current_indent}}"
|
|
95
80
|
end
|
|
96
81
|
|
|
@@ -99,28 +84,28 @@ module Bash
|
|
|
99
84
|
# @param condition [String] Condition expression
|
|
100
85
|
def emit_if_start(condition)
|
|
101
86
|
@lines << "#{current_indent}if #{condition}; then"
|
|
102
|
-
|
|
87
|
+
indent
|
|
103
88
|
end
|
|
104
89
|
|
|
105
90
|
# Emit an elif clause
|
|
106
91
|
#
|
|
107
92
|
# @param condition [String] Condition expression
|
|
108
93
|
def emit_elif(condition)
|
|
109
|
-
|
|
94
|
+
dedent
|
|
110
95
|
@lines << "#{current_indent}elif #{condition}; then"
|
|
111
|
-
|
|
96
|
+
indent
|
|
112
97
|
end
|
|
113
98
|
|
|
114
99
|
# Emit an else clause
|
|
115
100
|
def emit_else
|
|
116
|
-
|
|
101
|
+
dedent
|
|
117
102
|
@lines << "#{current_indent}else"
|
|
118
|
-
|
|
103
|
+
indent
|
|
119
104
|
end
|
|
120
105
|
|
|
121
106
|
# Emit an if statement end
|
|
122
107
|
def emit_fi
|
|
123
|
-
|
|
108
|
+
dedent
|
|
124
109
|
@lines << "#{current_indent}fi"
|
|
125
110
|
end
|
|
126
111
|
|
|
@@ -130,12 +115,12 @@ module Bash
|
|
|
130
115
|
# @param items [String] Items to iterate over
|
|
131
116
|
def emit_for_start(var, items)
|
|
132
117
|
@lines << "#{current_indent}for #{var} in #{items}; do"
|
|
133
|
-
|
|
118
|
+
indent
|
|
134
119
|
end
|
|
135
120
|
|
|
136
|
-
# Emit a for loop end
|
|
121
|
+
# Emit a for/while loop end
|
|
137
122
|
def emit_done
|
|
138
|
-
|
|
123
|
+
dedent
|
|
139
124
|
@lines << "#{current_indent}done"
|
|
140
125
|
end
|
|
141
126
|
|
|
@@ -144,7 +129,7 @@ module Bash
|
|
|
144
129
|
# @param condition [String] Condition expression
|
|
145
130
|
def emit_while_start(condition)
|
|
146
131
|
@lines << "#{current_indent}while #{condition}; do"
|
|
147
|
-
|
|
132
|
+
indent
|
|
148
133
|
end
|
|
149
134
|
|
|
150
135
|
# Emit a case statement start
|
|
@@ -152,7 +137,7 @@ module Bash
|
|
|
152
137
|
# @param expression [String] Expression to match
|
|
153
138
|
def emit_case_start(expression)
|
|
154
139
|
@lines << "#{current_indent}case #{expression} in"
|
|
155
|
-
|
|
140
|
+
indent
|
|
156
141
|
end
|
|
157
142
|
|
|
158
143
|
# Emit a case pattern
|
|
@@ -160,18 +145,18 @@ module Bash
|
|
|
160
145
|
# @param pattern [String] Pattern to match
|
|
161
146
|
def emit_case_pattern(pattern)
|
|
162
147
|
@lines << "#{current_indent}#{pattern})"
|
|
163
|
-
|
|
148
|
+
indent
|
|
164
149
|
end
|
|
165
150
|
|
|
166
151
|
# Emit a case pattern terminator
|
|
167
152
|
def emit_case_pattern_end
|
|
168
|
-
|
|
153
|
+
dedent
|
|
169
154
|
@lines << "#{current_indent};;"
|
|
170
155
|
end
|
|
171
156
|
|
|
172
157
|
# Emit a case statement end
|
|
173
158
|
def emit_esac
|
|
174
|
-
|
|
159
|
+
dedent
|
|
175
160
|
@lines << "#{current_indent}esac"
|
|
176
161
|
end
|
|
177
162
|
|
|
@@ -182,36 +167,11 @@ module Bash
|
|
|
182
167
|
@lines << "#{current_indent}#{line}"
|
|
183
168
|
end
|
|
184
169
|
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
# @param raw_lines [Array<String>] Lines to emit as-is
|
|
188
|
-
def emit_raw_lines(raw_lines)
|
|
189
|
-
raw_lines.each { |line| @lines << line.chomp }
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
# Get the output as a single string
|
|
170
|
+
# Get the output as a Bash string
|
|
193
171
|
#
|
|
194
172
|
# @return [String]
|
|
195
173
|
def to_bash
|
|
196
|
-
|
|
197
|
-
content += "\n" unless content.empty? || content.end_with?("\n")
|
|
198
|
-
content
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
# Alias for consistency with other merge gems
|
|
202
|
-
# @return [String]
|
|
203
|
-
alias_method :to_s, :to_bash
|
|
204
|
-
|
|
205
|
-
# Clear the output
|
|
206
|
-
def clear
|
|
207
|
-
@lines = []
|
|
208
|
-
@indent_level = 0
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
private
|
|
212
|
-
|
|
213
|
-
def current_indent
|
|
214
|
-
" " * (@indent_level * @indent_size)
|
|
174
|
+
to_s
|
|
215
175
|
end
|
|
216
176
|
end
|
|
217
177
|
end
|
data/lib/bash/merge/version.rb
CHANGED
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bash-merge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -43,40 +43,40 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '
|
|
46
|
+
version: '4.0'
|
|
47
47
|
- - ">="
|
|
48
48
|
- !ruby/object:Gem::Version
|
|
49
|
-
version:
|
|
49
|
+
version: 4.0.4
|
|
50
50
|
type: :runtime
|
|
51
51
|
prerelease: false
|
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
|
53
53
|
requirements:
|
|
54
54
|
- - "~>"
|
|
55
55
|
- !ruby/object:Gem::Version
|
|
56
|
-
version: '
|
|
56
|
+
version: '4.0'
|
|
57
57
|
- - ">="
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
|
-
version:
|
|
59
|
+
version: 4.0.4
|
|
60
60
|
- !ruby/object:Gem::Dependency
|
|
61
61
|
name: ast-merge
|
|
62
62
|
requirement: !ruby/object:Gem::Requirement
|
|
63
63
|
requirements:
|
|
64
64
|
- - "~>"
|
|
65
65
|
- !ruby/object:Gem::Version
|
|
66
|
-
version: '
|
|
66
|
+
version: '3.1'
|
|
67
67
|
- - ">="
|
|
68
68
|
- !ruby/object:Gem::Version
|
|
69
|
-
version:
|
|
69
|
+
version: 3.1.0
|
|
70
70
|
type: :runtime
|
|
71
71
|
prerelease: false
|
|
72
72
|
version_requirements: !ruby/object:Gem::Requirement
|
|
73
73
|
requirements:
|
|
74
74
|
- - "~>"
|
|
75
75
|
- !ruby/object:Gem::Version
|
|
76
|
-
version: '
|
|
76
|
+
version: '3.1'
|
|
77
77
|
- - ">="
|
|
78
78
|
- !ruby/object:Gem::Version
|
|
79
|
-
version:
|
|
79
|
+
version: 3.1.0
|
|
80
80
|
- !ruby/object:Gem::Dependency
|
|
81
81
|
name: version_gem
|
|
82
82
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -315,10 +315,10 @@ licenses:
|
|
|
315
315
|
- MIT
|
|
316
316
|
metadata:
|
|
317
317
|
homepage_uri: https://bash-merge.galtzo.com/
|
|
318
|
-
source_code_uri: https://github.com/kettle-rb/bash-merge/tree/
|
|
319
|
-
changelog_uri: https://github.com/kettle-rb/bash-merge/blob/
|
|
318
|
+
source_code_uri: https://github.com/kettle-rb/bash-merge/tree/v2.0.1
|
|
319
|
+
changelog_uri: https://github.com/kettle-rb/bash-merge/blob/v2.0.1/CHANGELOG.md
|
|
320
320
|
bug_tracker_uri: https://github.com/kettle-rb/bash-merge/issues
|
|
321
|
-
documentation_uri: https://www.rubydoc.info/gems/bash-merge/
|
|
321
|
+
documentation_uri: https://www.rubydoc.info/gems/bash-merge/2.0.1
|
|
322
322
|
funding_uri: https://github.com/sponsors/pboling
|
|
323
323
|
wiki_uri: https://github.com/kettle-rb/bash-merge/wiki
|
|
324
324
|
news_uri: https://www.railsbling.com/tags/bash-merge
|
metadata.gz.sig
CHANGED
|
Binary file
|