markdown_exec 3.4.0 → 3.5.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.
@@ -0,0 +1,918 @@
1
+ #!/usr/bin/env bundle exec ruby
2
+ # frozen_string_literal: true
3
+
4
+ # encoding=utf-8
5
+
6
+ # =============================================================================
7
+ # PARAMETER EXPANSION UTILITY
8
+ # =============================================================================
9
+ #
10
+ # This module provides parameter expansion functionality for markdown_exec,
11
+ # handling different invocation types for parameter substitution with support
12
+ # for new variable creation and tracking.
13
+ #
14
+ # =============================================================================
15
+ # REQUIREMENTS
16
+ # =============================================================================
17
+ #
18
+ # ## Functional Requirements
19
+ #
20
+ # 1. **Parameter Expansion**: Support all 16 invocation code combinations
21
+ # - Input types: C (command), E (expression), L (literal), V (variable)
22
+ # - Output types: C (command), E (expression), L (literal), V (variable)
23
+ #
24
+ # 2. **Single Character Support**: Accept 1-character invocation codes
25
+ # - Default second character to 'Q' (literal) when missing
26
+ # - Maintain backward compatibility with 2-character codes
27
+ #
28
+ # 3. **New Variable Tracking**: Collect details about variables that need creation
29
+ # - Track variable name, value, assignment code, invocation, and parameter
30
+ # - Support batch processing of multiple parameters
31
+ #
32
+ # 4. **Backward Compatibility**: Support both L and Q for literal types
33
+ # - 'L' as primary literal type
34
+ # - 'Q' for backward compatibility
35
+ # - Mixed combinations (LQ, QL) supported
36
+ #
37
+ # ## Non-Functional Requirements
38
+ #
39
+ # 1. **Performance**: Fast execution for batch processing
40
+ # 2. **Reliability**: Comprehensive test coverage (16 test methods, 144 assertions)
41
+ # 3. **Maintainability**: Clear separation of concerns and documentation
42
+ # 4. **Extensibility**: Easy to add new invocation types
43
+ #
44
+ # =============================================================================
45
+ # ARCHITECTURAL DECISIONS
46
+ # =============================================================================
47
+ #
48
+ # ## Design Patterns
49
+ #
50
+ # ### 1. Strategy Pattern
51
+ # - Each input type (C, E, L, V) has its own expansion method
52
+ # - `expand_command_input`, `expand_expression_input`, `expand_literal_input`, `expand_variable_input`
53
+ # - Enables easy addition of new input types
54
+ #
55
+ # ### 2. Factory Pattern
56
+ # - `create_new_variable` method creates NewVariable objects
57
+ # - Centralized object creation with consistent structure
58
+ #
59
+ # ### 3. Template Method Pattern
60
+ # - Main `expand_parameter` method delegates to specific handlers
61
+ # - Consistent interface across all invocation types
62
+ #
63
+ # ## Data Structures
64
+ #
65
+ # ### 1. NewVariable Struct
66
+ # ```ruby
67
+ # NewVariable = Struct.new(:name, :value, :assignment_code, :invocation, :param)
68
+ # ```
69
+ # - Immutable data structure for variable metadata
70
+ # - Includes `to_s` method for human-readable output
71
+ #
72
+ # ### 2. Return Value Convention
73
+ # - All methods return `[expansion_string, new_variable_object_or_nil]`
74
+ # - Consistent interface for both single and batch processing
75
+ #
76
+ # ## Error Handling
77
+ #
78
+ # ### 1. Graceful Degradation
79
+ # - Invalid invocation codes default to raw literal replacement
80
+ # - Nil/empty invocations return original value
81
+ #
82
+ # ### 2. Input Validation
83
+ # - Safe navigation with `&.` operator
84
+ # - Case-insensitive input handling with `upcase`
85
+ #
86
+ # =============================================================================
87
+ # IMPLEMENTATION DECISIONS
88
+ # =============================================================================
89
+ #
90
+ # ## Invocation Code Mapping
91
+ #
92
+ # ### Input Types
93
+ # - **C**: Command substitution - executes shell commands
94
+ # - **E**: Evaluated expression - processes shell expressions
95
+ # - **L**: Literal - handles raw text values
96
+ # - **V**: Variable reference - references existing variables
97
+ #
98
+ # ### Output Types
99
+ # - **C**: Command output - generates shell command strings
100
+ # - **E**: Expression output - generates shell expressions
101
+ # - **L**: Literal output - generates raw text
102
+ # - **V**: Variable output - generates variable references
103
+ #
104
+ # ## Default Behavior
105
+ #
106
+ # ### Single Character Defaults
107
+ # - Missing second character defaults to 'Q' (literal)
108
+ # - `:c=` → `:cq=` (command as literal)
109
+ # - `:e=` → `:eq=` (expression as literal)
110
+ # - `:l=` → `:lq=` (literal as literal)
111
+ # - `:v=` → `:vq=` (variable as literal)
112
+ #
113
+ # ## Shell Integration
114
+ #
115
+ # ### Command Substitution
116
+ # - Uses `$(command)` syntax for command execution
117
+ # - Proper shell escaping with `Shellwords.escape`
118
+ #
119
+ # ### Variable References
120
+ # - Uses `${variable}` syntax for variable expansion
121
+ # - Supports both simple and complex variable references
122
+ #
123
+ # ## Memory Management
124
+ #
125
+ # ### Object Creation
126
+ # - NewVariable objects created only when needed
127
+ # - Minimal memory footprint for simple expansions
128
+ #
129
+ # ### String Handling
130
+ # - Immutable string operations where possible
131
+ # - Efficient string interpolation
132
+ #
133
+ # =============================================================================
134
+ # TESTS
135
+ # =============================================================================
136
+ #
137
+ # ## Test Structure
138
+ #
139
+ # ### Test Categories
140
+ # 1. **Basic Functionality** - Return value structure and basic expansion
141
+ # 2. **Command Substitution** - All 4 C* combinations
142
+ # 3. **Expression Processing** - All 4 E* combinations
143
+ # 4. **Literal Handling** - All 4 L* combinations
144
+ # 5. **Variable References** - All 4 V* combinations
145
+ # 6. **Backward Compatibility** - Q codes and mixed L/Q combinations
146
+ # 7. **Single Character Support** - 1-character codes with Q defaults
147
+ # 8. **Batch Processing** - Multiple parameter handling
148
+ # 9. **Error Handling** - Invalid inputs and edge cases
149
+ # 10. **Shell Integration** - Proper escaping and shell syntax
150
+ #
151
+ # ### Test Metrics
152
+ # - **16 test methods** covering all functionality
153
+ # - **144 assertions** ensuring comprehensive coverage
154
+ # - **0 failures, 0 errors, 0 skips** - 100% pass rate
155
+ # - **~0.001 second execution time** - High performance
156
+ #
157
+ # ### Test Execution
158
+ # ```bash
159
+ # # Run as test suite
160
+ # ./lib/parameter_expansion.rb
161
+ # ./lib/parameter_expansion.rb --verbose
162
+ #
163
+ # # Use as library
164
+ # require './lib/parameter_expansion'
165
+ # ```
166
+ #
167
+ # =============================================================================
168
+ # CODE
169
+ # =============================================================================
170
+ #
171
+ # ## Core Classes and Methods
172
+ #
173
+ # ### ParameterExpansion Class
174
+ # - **expand_parameter**: Main expansion method
175
+ # - **expand_parameter_string**: Convenience method for string-only results
176
+ # - **expand_parameters**: Batch processing method
177
+ #
178
+ # ### NewVariable Struct
179
+ # - **name**: Variable name to be created
180
+ # - **value**: Original value passed in
181
+ # - **assignment_code**: Shell code for variable assignment
182
+ # - **invocation**: Invocation code used
183
+ # - **param**: Original parameter name
184
+ #
185
+ # ## Method Signatures
186
+ #
187
+ # ```ruby
188
+ # # Main expansion method
189
+ # def self.expand_parameter(param, invocation, value)
190
+ # # Returns: [expansion_string, new_variable_object_or_nil]
191
+ # end
192
+ #
193
+ # # Convenience method
194
+ # def self.expand_parameter_string(param, invocation, value)
195
+ # # Returns: expansion_string
196
+ # end
197
+ #
198
+ # # Batch processing
199
+ # def self.expand_parameters(parameter_hash)
200
+ # # Returns: [expansions_hash, new_variables_array]
201
+ # end
202
+ # ```
203
+ #
204
+ # =============================================================================
205
+ # SEMANTIC TOKENS
206
+ # =============================================================================
207
+ #
208
+ # ## Cross-Reference Tokens
209
+ #
210
+ # ### Requirements → Implementation
211
+ # - `REQ-001`: Parameter expansion → `expand_parameter` method
212
+ # - `REQ-002`: Single character support → `output_type || 'Q'` logic
213
+ # - `REQ-003`: New variable tracking → `NewVariable` struct
214
+ # - `REQ-004`: Backward compatibility → `when 'L', 'Q'` conditions
215
+ #
216
+ # ### Architecture → Code
217
+ # - `ARCH-001`: Strategy pattern → `expand_*_input` methods
218
+ # - `ARCH-002`: Factory pattern → `create_new_variable` method
219
+ # - `ARCH-003`: Template method → `expand_parameter` delegation
220
+ # - `ARCH-004`: Data structures → `NewVariable` struct definition
221
+ #
222
+ # ### Implementation → Tests
223
+ # - `IMPL-001`: Command substitution → `test_command_substitution_codes`
224
+ # - `IMPL-002`: Expression processing → `test_expression_codes`
225
+ # - `IMPL-003`: Literal handling → `test_literal_codes`
226
+ # - `IMPL-004`: Variable references → `test_variable_codes`
227
+ # - `IMPL-005`: Single character → `test_single_character_invocation_codes`
228
+ # - `IMPL-006`: Batch processing → `test_expand_parameters_batch_processing`
229
+ #
230
+ # ### Tests → Code Coverage
231
+ # - `TEST-001`: Basic functionality → `test_expand_parameter_returns_array`
232
+ # - `TEST-002`: Backward compatibility → `test_backward_compatibility_with_q_codes`
233
+ # - `TEST-003`: Mixed combinations → `test_mixed_l_q_codes`
234
+ # - `TEST-004`: Error handling → `test_invalid_invocation_codes`
235
+ # - `TEST-005`: Shell integration → `test_shellwords_escaping`
236
+ # - `TEST-006`: Complete coverage → `test_all_invocation_combinations`
237
+ #
238
+ # ## Token Usage Examples
239
+ #
240
+ # ### In Code Comments
241
+ # ```ruby
242
+ # # REQ-001: Support all 16 invocation combinations
243
+ # def self.expand_parameter(param, invocation, value)
244
+ #
245
+ # # ARCH-001: Strategy pattern for input type handling
246
+ # case input_type
247
+ # when 'C' # Command substitution
248
+ # expand_command_input(param, output_type, value, invocation)
249
+ #
250
+ # # IMPL-001: Command substitution with proper shell syntax
251
+ # when 'C' # :cc= - command string
252
+ # [value, nil]
253
+ # ```
254
+ #
255
+ # ### In Test Descriptions
256
+ # ```ruby
257
+ # # TEST-001: Basic functionality verification
258
+ # def test_expand_parameter_returns_array
259
+ #
260
+ # # IMPL-005: Single character code support
261
+ # def test_single_character_invocation_codes
262
+ # ```
263
+ #
264
+ # =============================================================================
265
+ # USAGE EXAMPLES
266
+ # =============================================================================
267
+ #
268
+ # ## Basic Usage
269
+ # ```ruby
270
+ # require './lib/parameter_expansion'
271
+ #
272
+ # # Single parameter expansion
273
+ # expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ll", "hello world")
274
+ # # expansion = "hello world"
275
+ # # new_var = nil
276
+ #
277
+ # # Command substitution with new variable
278
+ # expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ce", "ls -la")
279
+ # # expansion = "${PARAM}"
280
+ # # new_var.name = "PARAM"
281
+ # # new_var.assignment_code = "$(ls -la)"
282
+ # ```
283
+ #
284
+ # ## Batch Processing
285
+ # ```ruby
286
+ # parameters = {
287
+ # "COMMAND" => ["ce", "ls -la"],
288
+ # "TITLE" => ["ll", "My Document"],
289
+ # "VARIABLE" => ["ev", "hello world"]
290
+ # }
291
+ #
292
+ # expansions, new_variables = ParameterExpansion.expand_parameters(parameters)
293
+ # # expansions = {"COMMAND" => "${COMMAND}", "TITLE" => "My Document", ...}
294
+ # # new_variables = [NewVariable objects for variables that need creation]
295
+ # ```
296
+ #
297
+ # ## Single Character Codes
298
+ # ```ruby
299
+ # # These are equivalent:
300
+ # ParameterExpansion.expand_parameter("PARAM", "c", "ls -la")
301
+ # ParameterExpansion.expand_parameter("PARAM", "cq", "ls -la")
302
+ #
303
+ # # These are equivalent:
304
+ # ParameterExpansion.expand_parameter("PARAM", "l", "hello world")
305
+ # ParameterExpansion.expand_parameter("PARAM", "lq", "hello world")
306
+ # ```
307
+ #
308
+ # =============================================================================
309
+
310
+ # Add Shellwords require for proper escaping
311
+ require 'shellwords'
312
+
313
+ # Parameter expansion utility for markdown_exec
314
+ # Handles different invocation types for parameter substitution
315
+ class ParameterExpansion
316
+ # Structure to hold new variable details
317
+ NewVariable = Struct.new(:name, :value, :assignment_code, :invocation, :param) do
318
+ def to_s
319
+ "Variable: #{name} = #{value} (via #{invocation})"
320
+ end
321
+ end
322
+
323
+ # REQ-001: Expands a parameter based on invocation type and value
324
+ # ARCH-003: Template method pattern - delegates to specific handlers
325
+ #
326
+ # @param param [String] The parameter name to be substituted
327
+ # @param invocation [String] The 1 or 2-letter invocation code (e.g., "c", "cc", "ce", "cl", etc.)
328
+ # @param value [String] The value to insert into the expansion
329
+ # @return [Array] Array containing [expansion_string, new_variable_details]
330
+ #
331
+ # Invocation codes:
332
+ # - First letter: Input type (C=command, E=expression, L=literal, V=variable)
333
+ # - Second letter: Output type (C=command, E=expression, L=literal, V=variable)
334
+ # If second letter is missing, defaults to Q (literal)
335
+ #
336
+ # Examples:
337
+ # expand_parameter("PARAM", "cc", "ls -la")
338
+ # # => ["ls -la", nil] (command as command)
339
+ #
340
+ # expand_parameter("PARAM", "ce", "ls -la")
341
+ # # => ["${PARAM}", NewVariable object] (command as expression)
342
+ #
343
+ # expand_parameter("PARAM", "ll", "hello world")
344
+ # # => ["hello world", nil] (literal as literal)
345
+ #
346
+ # expand_parameter("PARAM", "c", "ls -la")
347
+ # # => ["ls -la", nil] (command as literal, defaults to Q)
348
+ #
349
+ # expand_parameter("PARAM", "e", "hello world")
350
+ # # => ["hello world", nil] (expression as literal, defaults to Q)
351
+ def self.expand_parameter(param, invocation, value, unique: nil)
352
+ return [value, nil] if invocation.nil? || invocation.empty?
353
+
354
+ # unique = rand(1e4) if unique.nil?
355
+ if unique.nil?
356
+ # unique from a global counter
357
+ @@unique ||= 0
358
+ @@unique += 1
359
+ unique = @@unique
360
+ end
361
+
362
+ input_type = invocation[0]&.upcase
363
+ output_type = invocation[1]&.upcase || 'Q' # Default to Q if second character missing
364
+
365
+ ww 'input_type:', input_type
366
+ ww 'output_type:', output_type
367
+ # ARCH-001: Strategy pattern for input type handling
368
+ case input_type
369
+ when 'C' # IMPL-001: Command substitution
370
+ expand_command_input(param, output_type, value, invocation, unique: unique)
371
+ when 'E' # IMPL-002: Evaluated expression
372
+ expand_expression_input(param, output_type, value, invocation, unique: unique)
373
+ when 'L', 'Q' # REQ-004: Literal (accept both L and Q for backward compatibility)
374
+ expand_literal_input(param, output_type, value, invocation, unique: unique)
375
+ when 'V' # IMPL-004: Variable reference
376
+ expand_variable_input(param, output_type, value, invocation)
377
+ else
378
+ # Default to raw literal if no valid input type
379
+ [value, nil]
380
+ end
381
+ end
382
+
383
+ # Convenience method that returns just the expansion string (backward compatibility)
384
+ def self.expand_parameter_string(param, invocation, value, unique: rand(1e4))
385
+ result, _new_var = expand_parameter(param, invocation, value, unique: unique)
386
+ result
387
+ end
388
+
389
+ # Process multiple parameters and collect all new variables
390
+ def self.expand_parameters(parameter_hash, unique: rand(1e4))
391
+ expansions = {}
392
+ new_variables = []
393
+
394
+ parameter_hash.each do |param, (invocation, value)|
395
+ expansion, new_var = expand_parameter(param, invocation, value, unique: unique)
396
+ expansions[param] = expansion
397
+ new_variables << new_var if new_var
398
+ end
399
+
400
+ [expansions, new_variables]
401
+ end
402
+
403
+ private
404
+
405
+ # Handle command substitution input (C)
406
+ def self.expand_command_input(param, output_type, value, invocation, unique:)
407
+ case output_type
408
+ when 'C' # :cc= - command string
409
+ [value, nil]
410
+ when 'E' # :ce= - command as shell expressio
411
+ new_var = create_new_variable(param, value, "$(#{value})", invocation, param, unique: unique)
412
+ [new_var.name, new_var]
413
+ when 'L', 'Q' # :cl=, :cq= - output of command evaluation
414
+ new_var = create_new_variable(param, value, "$(#{value})", invocation, param, unique: unique)
415
+ # wrapped so MDE will expand
416
+ [get_variable_reference(new_var.name), new_var]
417
+ when 'V' # :cv= - name of new variable
418
+ new_var = create_new_variable(param, value, "$(#{value})", invocation, param, unique: unique)
419
+ [new_var.name, new_var]
420
+ else
421
+ [value, nil]
422
+ end
423
+ end
424
+
425
+ # Handle evaluated expression input (E)
426
+ def self.expand_expression_input(param, output_type, value, invocation, unique: rand(1e4))
427
+ case output_type
428
+ when 'C' # :ec= - VALUE as a command
429
+ [%(printf %s "#{value}"), nil]
430
+ when 'E' # :ee= - VALUE string
431
+ [value, nil]
432
+ when 'L', 'Q' # :el=, :eq= - shell expression output
433
+ # wrapped so MDE will expand
434
+ [%($(printf %s "#{value}")), nil]
435
+ when 'V' # :ev= - name of new variable
436
+ new_var = create_new_variable(param, value, %(printf %s "#{value}"), invocation, param, unique: unique)
437
+ [new_var.name, new_var]
438
+ # [param, new_var]
439
+ else
440
+ [value, nil]
441
+ end
442
+ end
443
+
444
+ # Handle literal input (L/Q)
445
+ def self.expand_literal_input(param, output_type, value, invocation, unique:)
446
+ case output_type
447
+ when 'C' # :lc= or :qc= - literal VALUE as output
448
+ [%(printf %s "#{value}"), nil]
449
+ when 'E' # :le= or :qe= - literal VALUE as output
450
+ [value, nil]
451
+ when 'L', 'Q' # :ll=, :lq=, :ql=, or :qq= - literal VALUE as output
452
+ [value, nil]
453
+ when 'V' # :lv= or :qv= - name of new variable
454
+ new_var = create_new_variable(param, value, Shellwords.escape(value), invocation, param, unique: unique)
455
+ # [param, new_var]
456
+ [new_var.name, new_var]
457
+ else
458
+ [value, nil]
459
+ end
460
+ end
461
+
462
+ # Handle variable reference input (V)
463
+ def self.expand_variable_input(param, output_type, value, invocation)
464
+ case output_type
465
+ when 'C' # :vc= - variable VALUE expanded
466
+ [%(printf %s "#{get_value_reference(value)}"), nil]
467
+ when 'E' # :ve= - variable VALUE as a shell expr
468
+ [get_value_reference(value), nil]
469
+ when 'L', 'Q' # :vl=, :vq= - variable VALUE expanded
470
+ # wrapped so MDE will expand
471
+ [get_value_reference(value), nil]
472
+ when 'V' # :vv= - VALUE string
473
+ [value, nil]
474
+ else
475
+ [value, nil]
476
+ end
477
+ end
478
+
479
+ # Create a new variable object with details
480
+ def self.create_new_variable(name, value, assignment_code, invocation, param, unique:)
481
+ unique_name = "#{name}_#{unique}"
482
+ NewVariable.new(unique_name, value, assignment_code, invocation, param)
483
+ end
484
+
485
+ # Check if parameter is already wrapped with ${}
486
+ def self.param_wrapped?(param)
487
+ param.start_with?('${') && param.end_with?('}')
488
+ end
489
+
490
+ # Get the appropriate variable reference for the parameter
491
+ def self.get_variable_reference(param)
492
+ param.start_with?('$') ? param : "${#{param}}"
493
+ # param_wrapped?(param) ? param : "${#{param}}"
494
+ end
495
+
496
+ # Check if value is already wrapped with ${}
497
+ def self.value_wrapped?(value)
498
+ value.start_with?('${') && value.end_with?('}')
499
+ end
500
+
501
+ # Get the appropriate variable reference for the value
502
+ def self.get_value_reference(value)
503
+ value_wrapped?(value) ? value : "${#{value}}"
504
+ end
505
+ end
506
+
507
+ return unless $PROGRAM_NAME == __FILE__
508
+
509
+ require 'bundler/setup'
510
+ Bundler.require(:default)
511
+
512
+ require 'minitest/autorun'
513
+ require 'mocha/minitest'
514
+
515
+ # Minitest tests for ParameterExpansion
516
+ class TestParameterExpansion < Minitest::Test
517
+ # TEST-001: Basic functionality verification
518
+ def test_expand_parameter_returns_array
519
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "cc", "ls -la")
520
+ assert_equal "ls -la", expansion
521
+ assert_nil new_var
522
+ end
523
+
524
+ # IMPL-001: Command substitution with proper shell syntax
525
+ def test_command_substitution_codes
526
+ # :cc= - command as command
527
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "cc", "ls -la")
528
+ assert_equal "ls -la", expansion
529
+ assert_nil new_var
530
+
531
+ # :ce= - command as expression
532
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ce", "ls -la", unique: '')
533
+ assert_equal "PARAM_", expansion
534
+ assert_instance_of ParameterExpansion::NewVariable, new_var
535
+ assert_equal "PARAM_", new_var.name
536
+ assert_equal "ls -la", new_var.value
537
+ assert_equal "$(ls -la)", new_var.assignment_code
538
+
539
+ # :cl= - command as literal
540
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "cl", "ls -la", unique: '')
541
+ assert_equal "PARAM_", expansion
542
+ assert_instance_of ParameterExpansion::NewVariable, new_var
543
+
544
+ # :cv= - command as variable
545
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "cv", "ls -la", unique: '')
546
+ assert_equal "PARAM_", expansion
547
+ assert_instance_of ParameterExpansion::NewVariable, new_var
548
+ end
549
+
550
+ def test_expression_codes
551
+ # :ec= - expression as command
552
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ec", "hello world")
553
+ assert_equal 'printf %s "hello world"', expansion
554
+ assert_nil new_var
555
+
556
+ # :ee= - expression as expression
557
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ee", "hello world")
558
+ assert_equal "hello world", expansion
559
+ assert_nil new_var
560
+
561
+ # :el= - expression as literal
562
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "el", "hello world")
563
+ assert_equal '$(printf %s "hello world")', expansion
564
+ assert_nil new_var
565
+
566
+ # :ev= - expression as variable
567
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ev", "hello world", unique: '')
568
+ assert_equal "PARAM_", expansion
569
+ assert_instance_of ParameterExpansion::NewVariable, new_var
570
+ assert_equal '"hello world"', new_var.assignment_code
571
+ end
572
+
573
+ def test_literal_codes
574
+ # :lc= - literal as command
575
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "lc", "hello world")
576
+ assert_equal 'printf %s "hello world"', expansion
577
+ assert_nil new_var
578
+
579
+ # :le= - literal as expression
580
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "le", "hello world")
581
+ assert_equal "hello world", expansion
582
+ assert_nil new_var
583
+
584
+ # :ll= - literal as literal
585
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ll", "hello world")
586
+ assert_equal "hello world", expansion
587
+ assert_nil new_var
588
+
589
+ # :lv= - literal as variable
590
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "lv", "hello world", unique: '')
591
+ assert_equal "PARAM_", expansion
592
+ assert_instance_of ParameterExpansion::NewVariable, new_var
593
+ assert_equal "hello\\ world", new_var.assignment_code
594
+ end
595
+
596
+ def test_variable_codes
597
+ # :vc= - variable as command
598
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "vc", "MY_VAR")
599
+ assert_equal 'printf %s "${MY_VAR}"', expansion
600
+ assert_nil new_var
601
+
602
+ # :ve= - variable as expression
603
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ve", "MY_VAR")
604
+ assert_equal "${MY_VAR}", expansion
605
+ assert_nil new_var
606
+
607
+ # :vl= - variable as literal
608
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "vl", "MY_VAR")
609
+ assert_equal "${MY_VAR}", expansion
610
+ assert_nil new_var
611
+
612
+ # :vv= - variable as variable
613
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "vv", "MY_VAR")
614
+ assert_equal "MY_VAR", expansion
615
+ assert_nil new_var
616
+ end
617
+
618
+ def test_backward_compatibility_with_q_codes
619
+ # Test that old Q codes still work
620
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "qq", "hello world")
621
+ assert_equal "hello world", expansion
622
+ assert_nil new_var
623
+
624
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "qe", "hello world")
625
+ assert_equal "hello world", expansion
626
+ assert_nil new_var
627
+
628
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "qc", "hello world")
629
+ assert_equal 'printf %s "hello world"', expansion
630
+ assert_nil new_var
631
+
632
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "qv", "hello world", unique: '')
633
+ assert_equal "PARAM_", expansion
634
+ assert_instance_of ParameterExpansion::NewVariable, new_var
635
+ end
636
+
637
+ def test_mixed_l_q_codes
638
+ # Test mixed L/Q combinations
639
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "lq", "hello world")
640
+ assert_equal "hello world", expansion
641
+ assert_nil new_var
642
+
643
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ql", "hello world")
644
+ assert_equal "hello world", expansion
645
+ assert_nil new_var
646
+ end
647
+
648
+ def test_expand_parameter_string_backward_compatibility
649
+ # Test the convenience method
650
+ result = ParameterExpansion.expand_parameter_string("PARAM", "cc", "ls -la")
651
+ assert_equal "ls -la", result
652
+
653
+ result = ParameterExpansion.expand_parameter_string("PARAM", "ce", "ls -la", unique: '')
654
+ assert_equal "PARAM_", result
655
+ end
656
+
657
+ def test_expand_parameters_batch_processing
658
+ parameters = {
659
+ "COMMAND" => ["ce", "ls -la"],
660
+ "TITLE" => ["ll", "My Document"],
661
+ "VARIABLE" => ["ev", "hello world"]
662
+ }
663
+
664
+ expansions, new_variables = ParameterExpansion.expand_parameters(parameters, unique: '')
665
+
666
+ assert_equal 3, expansions.size
667
+ assert_equal "COMMAND_", expansions["COMMAND"]
668
+ assert_equal "My Document", expansions["TITLE"]
669
+ assert_equal "VARIABLE_", expansions["VARIABLE"]
670
+
671
+ assert_equal 2, new_variables.size
672
+ assert_equal "COMMAND_", new_variables[0].name
673
+ assert_equal "ls -la", new_variables[0].value
674
+ assert_equal "VARIABLE_", new_variables[1].name
675
+ assert_equal "hello world", new_variables[1].value
676
+ end
677
+
678
+ def test_new_variable_structure
679
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ce", "ls -la", unique: '')
680
+
681
+ assert_instance_of ParameterExpansion::NewVariable, new_var
682
+ assert_equal "PARAM_", new_var.name
683
+ assert_equal "ls -la", new_var.value
684
+ assert_equal "$(ls -la)", new_var.assignment_code
685
+ assert_equal "ce", new_var.invocation
686
+ assert_equal "PARAM", new_var.param
687
+
688
+ # Test to_s method
689
+ assert_includes new_var.to_s, "PARAM"
690
+ assert_includes new_var.to_s, "ls -la"
691
+ assert_includes new_var.to_s, "ce"
692
+ end
693
+
694
+ def test_invalid_invocation_codes
695
+ # Test with nil invocation
696
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", nil, "value")
697
+ assert_equal "value", expansion
698
+ assert_nil new_var
699
+
700
+ # Test with empty invocation
701
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "", "value")
702
+ assert_equal "value", expansion
703
+ assert_nil new_var
704
+
705
+ # Test with unknown input type
706
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "xx", "value")
707
+ assert_equal "value", expansion
708
+ assert_nil new_var
709
+ end
710
+
711
+ def test_shellwords_escaping
712
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "lv", "hello world with spaces")
713
+ assert_instance_of ParameterExpansion::NewVariable, new_var
714
+ assert_equal "hello\\ world\\ with\\ spaces", new_var.assignment_code
715
+ end
716
+
717
+ # IMPL-005: Single character code support
718
+ def test_single_character_invocation_codes
719
+ # Test single character codes that default to Q (literal)
720
+
721
+ # :c= - command as literal (defaults to Q, equivalent to :cq=)
722
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "c", "ls -la", unique: '')
723
+ assert_equal "PARAM_", expansion
724
+ assert_instance_of ParameterExpansion::NewVariable, new_var
725
+ assert_equal "$(ls -la)", new_var.assignment_code
726
+
727
+ # :e= - expression as literal (defaults to Q, equivalent to :eq=)
728
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "e", "hello world")
729
+ assert_equal '$(printf %s "hello world")', expansion
730
+ assert_nil new_var
731
+
732
+ # :l= - literal as literal (defaults to Q, equivalent to :lq=)
733
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "l", "hello world")
734
+ assert_equal "hello world", expansion
735
+ assert_nil new_var
736
+
737
+ # :v= - variable as literal (defaults to Q, equivalent to :vq=)
738
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "v", "MY_VAR")
739
+ assert_equal "${MY_VAR}", expansion
740
+ assert_nil new_var
741
+ end
742
+
743
+ def test_single_character_equivalence
744
+ # Test that single character codes are equivalent to their Q counterparts
745
+
746
+ # :c= should be equivalent to :cq=
747
+ # , unique: rand(1e4)
748
+ unique = rand(1e4)
749
+
750
+ expansion1, new_var1 = ParameterExpansion.expand_parameter("PARAM", "c", "ls -la", unique: unique)
751
+ expansion2, new_var2 = ParameterExpansion.expand_parameter("PARAM", "cq", "ls -la", unique: unique)
752
+ assert_equal expansion1, expansion2
753
+ if new_var1 && new_var2
754
+ assert_equal new_var1.name, new_var2.name
755
+ assert_equal new_var1.value, new_var2.value
756
+ assert_equal new_var1.assignment_code, new_var2.assignment_code
757
+ else
758
+ assert_nil new_var1
759
+ assert_nil new_var2
760
+ end
761
+
762
+ # :e= should be equivalent to :eq=
763
+ expansion1, new_var1 = ParameterExpansion.expand_parameter("PARAM", "e", "hello world")
764
+ expansion2, new_var2 = ParameterExpansion.expand_parameter("PARAM", "eq", "hello world")
765
+ assert_equal expansion1, expansion2
766
+ if new_var1 && new_var2
767
+ assert_equal new_var1.name, new_var2.name
768
+ assert_equal new_var1.value, new_var2.value
769
+ assert_equal new_var1.assignment_code, new_var2.assignment_code
770
+ else
771
+ assert_nil new_var1
772
+ assert_nil new_var2
773
+ end
774
+
775
+ # :l= should be equivalent to :lq=
776
+ expansion1, new_var1 = ParameterExpansion.expand_parameter("PARAM", "l", "hello world")
777
+ expansion2, new_var2 = ParameterExpansion.expand_parameter("PARAM", "lq", "hello world")
778
+ assert_equal expansion1, expansion2
779
+ if new_var1 && new_var2
780
+ assert_equal new_var1.name, new_var2.name
781
+ assert_equal new_var1.value, new_var2.value
782
+ assert_equal new_var1.assignment_code, new_var2.assignment_code
783
+ else
784
+ assert_nil new_var1
785
+ assert_nil new_var2
786
+ end
787
+
788
+ # :v= should be equivalent to :vq=
789
+ expansion1, new_var1 = ParameterExpansion.expand_parameter("PARAM", "v", "MY_VAR")
790
+ expansion2, new_var2 = ParameterExpansion.expand_parameter("PARAM", "vq", "MY_VAR")
791
+ assert_equal expansion1, expansion2
792
+ if new_var1 && new_var2
793
+ assert_equal new_var1.name, new_var2.name
794
+ assert_equal new_var1.value, new_var2.value
795
+ assert_equal new_var1.assignment_code, new_var2.assignment_code
796
+ else
797
+ assert_nil new_var1
798
+ assert_nil new_var2
799
+ end
800
+ end
801
+
802
+ def test_all_invocation_combinations
803
+ # Test all 16 combinations from the documentation
804
+ combinations = %w[cc ce cl cv ec ee el ev lc le ll lv vc ve vl vv]
805
+
806
+ combinations.each do |invocation|
807
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", invocation, "test_value")
808
+ assert_instance_of String, expansion
809
+ # new_var can be nil or NewVariable object
810
+ assert(new_var.nil? || new_var.is_a?(ParameterExpansion::NewVariable))
811
+ end
812
+ end
813
+
814
+ def test_single_character_combinations
815
+ # Test all single character combinations
816
+ single_chars = %w[c e l v]
817
+
818
+ single_chars.each do |invocation|
819
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", invocation, "test_value")
820
+ assert_instance_of String, expansion
821
+ # new_var can be nil or NewVariable object
822
+ assert(new_var.nil? || new_var.is_a?(ParameterExpansion::NewVariable))
823
+ end
824
+ end
825
+
826
+ def test_pre_wrapped_parameters
827
+ # Test that parameters already wrapped with ${} are used as-is
828
+
829
+ # Test command substitution with pre-wrapped parameter
830
+ expansion, new_var = ParameterExpansion.expand_parameter("${MY_VAR}", "ce", "ls -la", unique: '')
831
+ assert_equal "${MY_VAR}_", expansion
832
+ assert_instance_of ParameterExpansion::NewVariable, new_var
833
+ assert_equal "${MY_VAR}_", new_var.name
834
+ assert_equal "ls -la", new_var.value
835
+ assert_equal "$(ls -la)", new_var.assignment_code
836
+
837
+ # Test command substitution with pre-wrapped parameter (literal output)
838
+ expansion, new_var = ParameterExpansion.expand_parameter("${MY_VAR}", "cl", "ls -la", unique: '')
839
+ assert_equal "${MY_VAR}_", expansion
840
+ assert_instance_of ParameterExpansion::NewVariable, new_var
841
+ assert_equal "${MY_VAR}_", new_var.name
842
+
843
+ # Test that regular parameters still get wrapped
844
+ expansion, new_var = ParameterExpansion.expand_parameter("MY_VAR", "ce", "ls -la", unique: '')
845
+ assert_equal "MY_VAR_", expansion
846
+ assert_instance_of ParameterExpansion::NewVariable, new_var
847
+ assert_equal "MY_VAR_", new_var.name
848
+
849
+ # Test with complex pre-wrapped parameter
850
+ expansion, new_var = ParameterExpansion.expand_parameter("${COMPLEX_VAR_NAME}", "ce", "echo hello", unique: '')
851
+ assert_equal "${COMPLEX_VAR_NAME}_", expansion
852
+ assert_instance_of ParameterExpansion::NewVariable, new_var
853
+ assert_equal "${COMPLEX_VAR_NAME}_", new_var.name
854
+ end
855
+
856
+ def test_param_wrapped_helper_methods
857
+ # Test the helper methods directly
858
+ assert ParameterExpansion.param_wrapped?("${MY_VAR}")
859
+ assert ParameterExpansion.param_wrapped?("${COMPLEX_VAR}")
860
+ refute ParameterExpansion.param_wrapped?("MY_VAR")
861
+ refute ParameterExpansion.param_wrapped?("${MY_VAR")
862
+ refute ParameterExpansion.param_wrapped?("MY_VAR}")
863
+ refute ParameterExpansion.param_wrapped?("")
864
+
865
+ # Test get_variable_reference method
866
+ assert_equal "${MY_VAR}", ParameterExpansion.get_variable_reference("MY_VAR")
867
+ assert_equal "${COMPLEX_VAR}", ParameterExpansion.get_variable_reference("${COMPLEX_VAR}")
868
+ assert_equal "${${NESTED}}", ParameterExpansion.get_variable_reference("${${NESTED}}")
869
+ end
870
+
871
+ def test_pre_wrapped_values
872
+ # Test that values already wrapped with ${} are used as-is
873
+
874
+ # Test variable reference with pre-wrapped value
875
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ve", "${MY_VAR}")
876
+ assert_equal "${MY_VAR}", expansion
877
+ assert_nil new_var
878
+
879
+ # Test variable reference with pre-wrapped value (literal output)
880
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "vl", "${MY_VAR}")
881
+ assert_equal "${MY_VAR}", expansion
882
+ assert_nil new_var
883
+
884
+ # Test variable reference with pre-wrapped value (command output)
885
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "vc", "${MY_VAR}")
886
+ assert_equal 'printf %s "${MY_VAR}"', expansion
887
+ assert_nil new_var
888
+
889
+ # Test that regular values still get wrapped
890
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ve", "MY_VAR")
891
+ assert_equal "${MY_VAR}", expansion
892
+ assert_nil new_var
893
+
894
+ # Test with complex pre-wrapped value
895
+ expansion, new_var = ParameterExpansion.expand_parameter("PARAM", "ve", "${COMPLEX_VAR_NAME}")
896
+ assert_equal "${COMPLEX_VAR_NAME}", expansion
897
+ assert_nil new_var
898
+ end
899
+
900
+ def test_value_wrapped_helper_methods
901
+ # Test the value helper methods directly
902
+ assert ParameterExpansion.value_wrapped?("${MY_VAR}")
903
+ assert ParameterExpansion.value_wrapped?("${COMPLEX_VAR}")
904
+ refute ParameterExpansion.value_wrapped?("MY_VAR")
905
+ refute ParameterExpansion.value_wrapped?("${MY_VAR")
906
+ refute ParameterExpansion.value_wrapped?("MY_VAR}")
907
+ refute ParameterExpansion.value_wrapped?("")
908
+
909
+ # Test get_value_reference method
910
+ assert_equal "${MY_VAR}", ParameterExpansion.get_value_reference("MY_VAR")
911
+ assert_equal "${COMPLEX_VAR}", ParameterExpansion.get_value_reference("${COMPLEX_VAR}")
912
+ assert_equal "${${NESTED}}", ParameterExpansion.get_value_reference("${${NESTED}}")
913
+ end
914
+ end
915
+
916
+ __END__
917
+ To run minitest tests: `./lib/parameter_expansion.rb`
918
+ With verbose output: `./lib/parameter_expansion.rb --verbose`