markdown_exec 3.3.0 → 3.4.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.
data/lib/ww.rb CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env -S bundle exec ruby
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # encoding=utf-8
@@ -12,16 +13,16 @@ DEPTH_ICON = '›'
12
13
  LOG_LEVELS = %i[debug info warn error fatal].freeze
13
14
 
14
15
  # is enabled
15
- def enable_debugging
16
+ private def enable_debugging
16
17
  ENV.fetch('WW', '0').to_i.positive?
17
18
  end
18
19
 
19
20
  # is enabled, not silent
20
- def env_show_attribution
21
+ private def env_show_attribution
21
22
  ENV['WW'] != '0'
22
23
  end
23
24
 
24
- def is_new_alg?
25
+ private def is_new_alg?
25
26
  # use the new algo only if env var is ALG is not empty
26
27
  !ENV.fetch('ALG', '').empty?
27
28
 
@@ -84,6 +85,11 @@ def wwa(*objs, **kwargs)
84
85
  exit 1
85
86
  end
86
87
 
88
+ # break into the debugger if enabled
89
+ def wwb
90
+ binding.irb if $debug
91
+ end
92
+
87
93
  # output the object and backtrace for the error
88
94
  # raise the error for the caller to handle
89
95
  def wwe(*objs, **kwargs)
@@ -136,9 +142,33 @@ def wwt(*objs, **kwargs)
136
142
  ))
137
143
  end
138
144
 
145
+ # enhanced expression wrapper with better context
146
+ # usage: wwx { some_expression } or wwx(expression)
147
+ def wwx(expression = nil, **kwargs, &block)
148
+ if block_given?
149
+ # Block form: wwx { some_expression }
150
+ result = block.call
151
+ return result unless $debug
152
+
153
+ # Capture the source location of the block
154
+ locations = kwargs[:locations] || caller_locations
155
+ ww0(result, **kwargs.merge(locations: locations, context: 'block'))
156
+ elsif expression
157
+ # Direct form: wwx(some_expression)
158
+ return expression unless $debug
159
+
160
+ locations = kwargs[:locations] || caller_locations
161
+ ww0(expression, **kwargs.merge(locations: locations, context: 'direct'))
162
+ else
163
+ # No arguments - just return nil
164
+ nil
165
+ end
166
+ end
167
+
139
168
  # output the formatted data and location
140
169
  def ww0(*objs,
141
170
  category: $ww_category,
171
+ context: nil,
142
172
  full_backtrace: false,
143
173
  level: :debug,
144
174
  locations: caller_locations,
@@ -172,12 +202,13 @@ def ww0(*objs,
172
202
  # Add optional timestamp
173
203
  time_prefix = timestamp ? "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] " : ''
174
204
 
175
- # Add log level and category prefix
205
+ # Add log level, category, and context prefix
176
206
  level_prefix = "[#{level.to_s.upcase}]"
177
207
  category_prefix = category ? "[#{category}] " : ''
208
+ context_prefix = context ? "[#{context}] " : ''
178
209
 
179
210
  # Combine all parts into the final message
180
- header = "#{time_prefix}#{level_prefix} #{category_prefix}"
211
+ header = "#{time_prefix}#{level_prefix} #{category_prefix}#{context_prefix}"
181
212
  trace = backtrace + objs
182
213
  io = StringIO.new
183
214
  formatted_message = if single_line
@@ -197,21 +228,16 @@ def ww0(*objs,
197
228
  output.flush
198
229
 
199
230
  # Optionally log to a file
200
- return objs.size == 1 ? objs.first : objs unless log_file
201
-
202
- File.open(log_file, 'a') do |file|
203
- file.puts(formatted_message)
231
+ if log_file
232
+ File.open(log_file, 'a') do |file|
233
+ file.puts(formatted_message)
234
+ end
204
235
  end
205
236
 
206
- # return the last item in the list, as the label is usually first
237
+ # Always return the last item in the list, as the label is usually first
207
238
  objs.last
208
239
  end
209
240
 
210
- # break into the debugger if enabled
211
- def wwb
212
- binding.irb if $debug
213
- end
214
-
215
241
  class Array
216
242
  unless defined?(deref)
217
243
 
@@ -430,3 +456,455 @@ class Array
430
456
  []
431
457
  end
432
458
  end
459
+
460
+ return if $PROGRAM_NAME != __FILE__
461
+
462
+ require 'minitest/autorun'
463
+
464
+ class TestWwFunction < Minitest::Test
465
+ def setup
466
+ # Save original global state
467
+ @original_debug = $debug
468
+ @original_ww_log_file = $ww_log_file
469
+ @original_ww_output = $ww_output
470
+ @original_ww_category = $ww_category
471
+
472
+ # Redirect output to capture it - ensure it's writable
473
+ @output_buffer = StringIO.new
474
+ @output_buffer.sync = true
475
+ $ww_output = @output_buffer
476
+ $ww_category = nil
477
+ end
478
+
479
+ def teardown
480
+ # Restore original global state
481
+ $debug = @original_debug
482
+ $ww_log_file = @original_ww_log_file
483
+ $ww_output = @original_ww_output
484
+ $ww_category = @original_ww_category
485
+
486
+ # Clean up any test log files
487
+ Dir.glob('test*.log').each { |f| FileUtils.rm_f(f) }
488
+ end
489
+
490
+ # Core functionality tests
491
+ def test_ww_returns_last_item_with_debug_disabled
492
+ $debug = false
493
+ $ww_log_file = nil
494
+
495
+ # Test with single item
496
+ result = ww('single_item')
497
+ assert_equal 'single_item', result,
498
+ 'ww should return the single item when debug is disabled'
499
+
500
+ # Test with multiple items
501
+ result = ww('first', 'second', 'third')
502
+ assert_equal 'third', result,
503
+ 'ww should return the last item when debug is disabled'
504
+
505
+ # Test with various data types
506
+ result = ww(1, 'string', :symbol, [1, 2, 3])
507
+ assert_equal [1, 2, 3], result,
508
+ 'ww should return the last item regardless of type'
509
+
510
+ # Verify no output when debug is disabled
511
+ assert_empty @output_buffer.string,
512
+ 'No output should be generated when debug is disabled'
513
+ end
514
+
515
+ def test_ww_returns_last_item_with_debug_enabled_no_log_file
516
+ $debug = true
517
+ $ww_log_file = nil
518
+
519
+ # Test with single item
520
+ result = ww('single_item')
521
+ assert_equal 'single_item', result,
522
+ 'ww should return the single item when debug is enabled and no log file'
523
+
524
+ # Test with multiple items
525
+ result = ww('first', 'second', 'third')
526
+ assert_equal 'third', result,
527
+ 'ww should return the last item when debug is enabled and no log file'
528
+
529
+ # Test with mixed types
530
+ result = ww(42, 'hello', :world, { key: 'value' })
531
+ assert_equal({ key: 'value' }, result,
532
+ 'ww should return the last item even with hash')
533
+
534
+ # Verify output is generated when debug is enabled
535
+ refute_empty @output_buffer.string,
536
+ 'Output should be generated when debug is enabled'
537
+ end
538
+
539
+ def test_ww_returns_last_item_with_debug_enabled_with_log_file
540
+ $debug = true
541
+ $ww_log_file = 'test_ww.log'
542
+
543
+ begin
544
+ # Test with single item
545
+ result = ww('single_item')
546
+ assert_equal 'single_item', result,
547
+ 'ww should return the single item when debug is enabled with log file'
548
+
549
+ # Test with multiple items
550
+ result = ww('first', 'second', 'third')
551
+ assert_equal 'third', result,
552
+ 'ww should return the last item when debug is enabled with log file'
553
+
554
+ # Test with various data types
555
+ result = ww(1, 'string', :symbol, [1, 2, 3])
556
+ assert_equal [1, 2, 3], result,
557
+ 'ww should return the last item regardless of type with log file'
558
+
559
+ # Verify log file was created and contains content
560
+ assert File.exist?('test_ww.log'), 'Log file should be created'
561
+ refute_empty File.read('test_ww.log'), 'Log file should contain content'
562
+ ensure
563
+ # Clean up test log file
564
+ FileUtils.rm_f('test_ww.log')
565
+ end
566
+ end
567
+
568
+ def test_ww_with_named_options
569
+ $debug = false
570
+
571
+ # Test that named options don't affect the return value
572
+ result = ww('first', 'second', category: 'test', level: :info)
573
+ assert_equal 'second', result,
574
+ 'ww should return the last item even with named options'
575
+
576
+ # Test with single item and options
577
+ result = ww('only_item', timestamp: true, single_line: true)
578
+ assert_equal 'only_item', result,
579
+ 'ww should return the single item even with named options'
580
+
581
+ # Test with various option combinations
582
+ result = ww('a', 'b', 'c', category: 'testing', level: :warn,
583
+ timestamp: true, single_line: false)
584
+ assert_equal 'c', result,
585
+ 'ww should return the last item with complex options'
586
+ end
587
+
588
+ # Test all ww function variants
589
+ def test_wwr_function_returns_last_item
590
+ $debug = true
591
+ $ww_log_file = nil
592
+
593
+ # Test wwr with multiple items
594
+ result = wwr('first', 'second', 'third')
595
+ assert_equal 'third', result, 'wwr should return the last item'
596
+
597
+ # Test wwr with single item
598
+ result = wwr('only_item')
599
+ assert_equal 'only_item', result, 'wwr should return the single item'
600
+
601
+ # Test wwr with debug disabled
602
+ $debug = false
603
+ result = wwr('a', 'b', 'c')
604
+ assert_equal 'c', result,
605
+ 'wwr should return the last item even when debug is disabled'
606
+ end
607
+
608
+ def test_wwp_function_returns_last_item
609
+ $debug = true
610
+ $ww_log_file = nil
611
+
612
+ # Test wwp with multiple items
613
+ result = wwp('first', 'second', 'third')
614
+ assert_equal 'third', result, 'wwp should return the last item'
615
+
616
+ # Test wwp with single item
617
+ result = wwp('only_item')
618
+ assert_equal 'only_item', result, 'wwp should return the single item'
619
+
620
+ # Test wwp with debug disabled
621
+ $debug = false
622
+ result = wwp('a', 'b', 'c')
623
+ assert_equal 'c', result,
624
+ 'wwp should return the last item even when debug is disabled'
625
+ end
626
+
627
+ def test_wwt_function_returns_last_item
628
+ $debug = true
629
+ $ww_log_file = nil
630
+
631
+ # Test wwt with multiple items (first item is tag)
632
+ result = wwt(:test_tag, 'first', 'second', 'third')
633
+ assert_equal 'third', result, 'wwt should return the last item'
634
+
635
+ # Test wwt with single data item after tag
636
+ result = wwt(:tag, 'only_data_item')
637
+ assert_equal 'only_data_item', result,
638
+ 'wwt should return the single data item'
639
+
640
+ # Test wwt with skipped tags
641
+ result = wwt(:blocks, 'data')
642
+ assert_equal 'data', result, 'wwt should return data even for skipped tags'
643
+
644
+ # Test wwt with debug disabled
645
+ $debug = false
646
+ result = wwt(:any_tag, 'a', 'b', 'c')
647
+ assert_equal 'c', result,
648
+ 'wwt should return the last item even when debug is disabled'
649
+ end
650
+
651
+ def test_wwt_multiline_continuation
652
+ $debug = true
653
+ $ww_log_file = nil
654
+
655
+ # Test multiline continuation pattern
656
+ var1 = 'test_value'
657
+ result = wwt :tag1, \
658
+ var1
659
+ assert_equal 'test_value', result,
660
+ 'wwt should work with multiline continuation'
661
+
662
+ # Test with complex expression
663
+ complex_var = [1, 2, 3]
664
+ result = wwt(:processing, \
665
+ complex_var.map { |x| x * 2 })
666
+ assert_equal [2, 4, 6], result,
667
+ 'wwt should work with complex expressions in multiline'
668
+
669
+ # Test with debug disabled
670
+ $debug = false
671
+ simple_var = 'no_debug'
672
+ result = wwt :disabled, \
673
+ simple_var
674
+ assert_equal 'no_debug', result,
675
+ 'wwt should return correct value even with debug disabled'
676
+ end
677
+
678
+ def test_ww_multiline_continuation
679
+ $debug = true
680
+ $ww_log_file = nil
681
+
682
+ # Test ww with multiline continuation
683
+ var1 = 'test_value'
684
+ result = ww \
685
+ var1
686
+ assert_equal 'test_value', result,
687
+ 'ww should work with multiline continuation'
688
+
689
+ # Test with multiple items in multiline
690
+ result = ww 'prefix', \
691
+ 'value'
692
+ assert_equal 'value', result,
693
+ 'ww should work with multiple items in multiline'
694
+
695
+ # Test with debug disabled
696
+ $debug = false
697
+ result = ww \
698
+ 'no_debug_value'
699
+ assert_equal 'no_debug_value', result,
700
+ 'ww should return correct value with debug disabled'
701
+ end
702
+
703
+ def test_wwa_function_behavior
704
+ $debug = true
705
+
706
+ # Test that wwa exits (we can't easily test exit behavior in minitest)
707
+ # So we'll just verify it would call ww0 properly by testing the structure
708
+ # Note: wwa calls exit, so we can't test it directly without special handling
709
+ skip 'wwa exits the program, cannot test directly in minitest'
710
+ end
711
+
712
+ def test_wwe_function_behavior
713
+ $debug = true
714
+
715
+ # Test that wwe raises an error
716
+ assert_raises(StandardError) do
717
+ wwe('error message')
718
+ end
719
+
720
+ # Test that wwe raises with the first object as the error message
721
+ error = assert_raises(StandardError) do
722
+ wwe('custom error', 'additional', 'data')
723
+ end
724
+ assert_equal 'custom error', error.message
725
+ end
726
+
727
+ # Edge case tests
728
+ def test_edge_cases
729
+ $debug = false
730
+
731
+ # Test with nil values
732
+ result = ww(nil, 'not_nil')
733
+ assert_equal 'not_nil', result, 'ww should handle nil values correctly'
734
+
735
+ # Test with empty array as last item
736
+ result = ww('first', [])
737
+ assert_equal [], result,
738
+ 'ww should return empty array if it is the last item'
739
+
740
+ # Test with false as last item
741
+ result = ww('first', false)
742
+ assert_equal false, result, 'ww should return false if it is the last item'
743
+
744
+ # Test with zero as last item
745
+ result = ww('first', 0)
746
+ assert_equal 0, result, 'ww should return zero if it is the last item'
747
+
748
+ # Test with empty string as last item
749
+ result = ww('first', '')
750
+ assert_equal '', result,
751
+ 'ww should return empty string if it is the last item'
752
+ end
753
+
754
+ def test_complex_data_structures
755
+ $debug = false
756
+
757
+ # Test with nested arrays
758
+ nested = [1, [2, [3, 4]], 5]
759
+ result = ww('start', nested)
760
+ assert_equal nested, result, 'ww should handle nested arrays'
761
+
762
+ # Test with complex hash
763
+ complex_hash = {
764
+ users: [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }],
765
+ metadata: { created: Time.now, version: '1.0' }
766
+ }
767
+ result = ww('prefix', complex_hash)
768
+ assert_equal complex_hash, result, 'ww should handle complex hashes'
769
+
770
+ # Test with objects
771
+ string_obj = String.new('test')
772
+ result = ww('object', string_obj)
773
+ assert_equal string_obj, result, 'ww should handle object instances'
774
+ end
775
+
776
+ def test_options_validation
777
+ $debug = true
778
+ $ww_log_file = nil
779
+
780
+ # Test valid log levels
781
+ %i[debug info warn error fatal].each do |level|
782
+ result = ww('test', level: level)
783
+ assert_equal 'test', result, "ww should work with log level #{level}"
784
+ end
785
+
786
+ # Test invalid log level raises error
787
+ assert_raises(ArgumentError) do
788
+ ww('test', level: :invalid)
789
+ end
790
+ end
791
+
792
+ def test_output_formatting_options
793
+ $debug = true
794
+ $ww_log_file = nil
795
+
796
+ # Create fresh buffer for this test
797
+ fresh_buffer = StringIO.new
798
+ $ww_output = fresh_buffer
799
+
800
+ # Test single_line option
801
+ ww('test', 'data', single_line: true)
802
+ output = fresh_buffer.string
803
+ refute_includes output, "\n [",
804
+ 'single_line should format output on one line'
805
+
806
+ # Reset buffer
807
+ fresh_buffer = StringIO.new
808
+ $ww_output = fresh_buffer
809
+
810
+ # Test timestamp option
811
+ ww('test', timestamp: true)
812
+ output = fresh_buffer.string
813
+ assert_match(/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]/, output,
814
+ 'timestamp should be included in output')
815
+
816
+ # Reset buffer
817
+ fresh_buffer = StringIO.new
818
+ $ww_output = fresh_buffer
819
+
820
+ # Test category option
821
+ ww('test', category: 'MYCATEGORY')
822
+ output = fresh_buffer.string
823
+ assert_includes output, '[MYCATEGORY]',
824
+ 'category should be included in output'
825
+ end
826
+
827
+ def test_location_information
828
+ $debug = true
829
+ $ww_log_file = nil
830
+
831
+ # Create fresh buffer for this test
832
+ fresh_buffer = StringIO.new
833
+ $ww_output = fresh_buffer
834
+
835
+ # Test that location information is included
836
+ ww('location test')
837
+ output = fresh_buffer.string
838
+
839
+ # Should include file path and line number information
840
+ assert_includes output, 'lib/ww.rb', 'output should include filename'
841
+ assert_match(/\s:\s\d+\s:/, output,
842
+ 'output should include line number with format')
843
+ end
844
+
845
+ def test_full_backtrace_option
846
+ $debug = true
847
+ $ww_log_file = nil
848
+
849
+ # Create fresh buffer for this test
850
+ fresh_buffer = StringIO.new
851
+ $ww_output = fresh_buffer
852
+
853
+ # Test full_backtrace option
854
+ ww('test', full_backtrace: true)
855
+ output = fresh_buffer.string
856
+
857
+ # With full backtrace, we should see multiple stack levels
858
+ # (This is hard to test precisely since it depends on call stack depth)
859
+ refute_empty output, 'full_backtrace should produce output'
860
+ end
861
+
862
+ def test_multiple_consecutive_calls
863
+ $debug = false
864
+
865
+ # Test that multiple calls work correctly
866
+ results = []
867
+ results << ww('call1', 'result1')
868
+ results << ww('call2a', 'call2b', 'result2')
869
+ results << ww('call3a', 'call3b', 'call3c', 'result3')
870
+
871
+ assert_equal %w[result1 result2 result3], results,
872
+ 'Multiple consecutive calls should work correctly'
873
+ end
874
+
875
+ def test_return_value_consistency_across_debug_states
876
+ # Test the same call with debug enabled and disabled
877
+ test_args = %w[first second third]
878
+
879
+ # With debug disabled
880
+ $debug = false
881
+ $ww_log_file = nil
882
+ result_no_debug = ww(*test_args)
883
+
884
+ # With debug enabled, no log file
885
+ $debug = true
886
+ $ww_log_file = nil
887
+ result_debug_no_log = ww(*test_args)
888
+
889
+ # With debug enabled, with log file
890
+ $debug = true
891
+ $ww_log_file = 'consistency_test.log'
892
+ begin
893
+ result_debug_with_log = ww(*test_args)
894
+ ensure
895
+ FileUtils.rm_f('consistency_test.log')
896
+ end
897
+
898
+ # All should return the same value
899
+ assert_equal 'third', result_no_debug,
900
+ 'Should return last item with debug disabled'
901
+ assert_equal 'third', result_debug_no_log,
902
+ 'Should return last item with debug enabled, no log'
903
+ assert_equal 'third', result_debug_with_log,
904
+ 'Should return last item with debug enabled, with log'
905
+ assert_equal result_no_debug, result_debug_no_log,
906
+ 'Results should be consistent across debug states'
907
+ assert_equal result_no_debug, result_debug_with_log,
908
+ 'Results should be consistent across log file states'
909
+ end
910
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markdown_exec
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fareed Stevenson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-02 00:00:00.000000000 Z
11
+ date: 2025-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: clipboard
@@ -127,6 +127,7 @@ files:
127
127
  - bats/block-type-ux-formats.bats
128
128
  - bats/block-type-ux-hidden.bats
129
129
  - bats/block-type-ux-invalid.bats
130
+ - bats/block-type-ux-no-name.bats
130
131
  - bats/block-type-ux-readonly.bats
131
132
  - bats/block-type-ux-require-chained.bats
132
133
  - bats/block-type-ux-require.bats
@@ -143,6 +144,7 @@ files:
143
144
  - bats/fail.bats
144
145
  - bats/history.bats
145
146
  - bats/import-conflict.bats
147
+ - bats/import-directive-line-continuation.bats
146
148
  - bats/import-directive-parameter-symbols.bats
147
149
  - bats/import-duplicates.bats
148
150
  - bats/import-parameter-symbols.bats
@@ -192,6 +194,7 @@ files:
192
194
  - docs/dev/block-type-ux-formats.md
193
195
  - docs/dev/block-type-ux-hidden.md
194
196
  - docs/dev/block-type-ux-invalid.md
197
+ - docs/dev/block-type-ux-no-name.md
195
198
  - docs/dev/block-type-ux-readonly.md
196
199
  - docs/dev/block-type-ux-require-chained.md
197
200
  - docs/dev/block-type-ux-require.md
@@ -209,6 +212,7 @@ files:
209
212
  - docs/dev/hexdump_format.md
210
213
  - docs/dev/import-conflict-0.md
211
214
  - docs/dev/import-conflict-1.md
215
+ - docs/dev/import-directive-line-continuation.md
212
216
  - docs/dev/import-directive-parameter-symbols.md
213
217
  - docs/dev/import-duplicates-0.md
214
218
  - docs/dev/import-duplicates-1.md