tui-td 0.2.9 → 0.2.11

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.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockNesting, Metrics/ParameterLists, Metrics/ClassLength, Metrics/CollectionLiteralLength
4
+
3
5
  require "chunky_png"
4
6
  require_relative "ansi_utils"
5
7
  require_relative "cairo_renderer"
@@ -200,7 +202,7 @@ module TUITD
200
202
  "╧" => [true, false, true, true, :double],
201
203
  "╨" => [true, false, true, true, :double],
202
204
  "╪" => [true, true, true, true, :double],
203
- "╫" => [true, true, true, true, :double]
205
+ "╫" => [true, true, true, true, :double],
204
206
  }.freeze
205
207
 
206
208
  private_constant :FONT
@@ -219,8 +221,10 @@ module TUITD
219
221
 
220
222
  @grid.each_with_index do |row, ri|
221
223
  next unless row
224
+
222
225
  row.each_with_index do |cell, ci|
223
226
  next unless cell
227
+
224
228
  render_cell(image, ri, ci, cell)
225
229
  end
226
230
  end
@@ -256,15 +260,15 @@ module TUITD
256
260
  end
257
261
 
258
262
  char_ord = char.ord
259
- if char_ord == 10095 # '❯'
263
+ if char_ord == 10_095 # '❯'
260
264
  draw_chevron(image, px, py, fg_rgb)
261
265
  draw_underline(image, px, py, CELL_W, fg_rgb) if underline
262
266
  return
263
- elsif char_ord == 9210 || char_ord == 9679 # '⏺' or '●'
267
+ elsif [9210, 9679].include?(char_ord) # '⏺' or '●'
264
268
  draw_circle(image, px, py, fg_rgb)
265
269
  draw_underline(image, px, py, CELL_W, fg_rgb) if underline
266
270
  return
267
- elsif char_ord >= 0x2800 && char_ord <= 0x28ff # Braille spinner
271
+ elsif char_ord.between?(0x2800, 0x28ff) # Braille spinner
268
272
  draw_braille(image, px, py, char, fg_rgb)
269
273
  draw_underline(image, px, py, CELL_W, fg_rgb) if underline
270
274
  return
@@ -399,7 +403,7 @@ module TUITD
399
403
 
400
404
  def glyph_rows(char)
401
405
  idx = (char.ord - 32) * 16
402
- return nil if idx < 0 || idx + 15 >= FONT.length
406
+ return nil if idx.negative? || idx + 15 >= FONT.length
403
407
 
404
408
  FONT[idx, 16]
405
409
  end
@@ -408,7 +412,7 @@ module TUITD
408
412
  color = ChunkyPNG::Color.rgb(*fg_rgb)
409
413
 
410
414
  rows.each_with_index do |byte, dy|
411
- next if byte == 0
415
+ next if byte.zero?
412
416
 
413
417
  slant = italic ? dy / 8 : 0
414
418
 
@@ -429,7 +433,7 @@ module TUITD
429
433
 
430
434
  def box_drawing?(char)
431
435
  char_ord = char.ord
432
- char_ord >= 0x2500 && char_ord <= 0x257F
436
+ char_ord.between?(0x2500, 0x257F)
433
437
  end
434
438
 
435
439
  def draw_box_character(image, px, py, char, fg_rgb)
@@ -438,10 +442,18 @@ module TUITD
438
442
  unless config
439
443
  char_ord = char.ord
440
444
  if [0x2500, 0x2501, 0x2504, 0x2505, 0x2508, 0x2509, 0x254c, 0x254d, 0x2550].include?(char_ord)
441
- style = [0x2501, 0x2505, 0x2509, 0x254d].include?(char_ord) ? :heavy : (char_ord == 0x2550 ? :double : :light)
445
+ style = if [0x2501, 0x2505, 0x2509, 0x254d].include?(char_ord)
446
+ :heavy
447
+ else
448
+ (char_ord == 0x2550 ? :double : :light)
449
+ end
442
450
  config = [false, false, true, true, style]
443
451
  elsif [0x2502, 0x2503, 0x2506, 0x2507, 0x250a, 0x250b, 0x254e, 0x254f, 0x2551].include?(char_ord)
444
- style = [0x2503, 0x2507, 0x250b, 0x254f].include?(char_ord) ? :heavy : (char_ord == 0x2551 ? :double : :light)
452
+ style = if [0x2503, 0x2507, 0x250b, 0x254f].include?(char_ord)
453
+ :heavy
454
+ else
455
+ (char_ord == 0x2551 ? :double : :light)
456
+ end
445
457
  config = [true, true, false, false, style]
446
458
  else
447
459
  config = [true, true, true, true, :light]
@@ -454,24 +466,31 @@ module TUITD
454
466
 
455
467
  color = ChunkyPNG::Color.rgb(*fg_rgb)
456
468
 
457
- if style == :double
469
+ case style
470
+ when :double
458
471
  if left
459
- (px..(cx + 2)).each { |x| image[x, py + 6] = color }
460
- (px..(cx + 2)).each { |x| image[x, py + 10] = color }
472
+ (px..(cx + 2)).each do |x|
473
+ image[x, py + 6] = color
474
+ image[x, py + 10] = color
475
+ end
461
476
  end
462
477
  if right
463
478
  ((cx - 2)..(px + 7)).each { |x| image[x, py + 6] = color }
464
479
  ((cx - 2)..(px + 10)).each { |x| image[x, py + 10] = color }
465
480
  end
466
481
  if up
467
- (py..(cy + 2)).each { |y| image[px + 2, y] = color }
468
- (py..(cy + 2)).each { |y| image[px + 6, y] = color }
482
+ (py..(cy + 2)).each do |y|
483
+ image[px + 2, y] = color
484
+ image[px + 6, y] = color
485
+ end
469
486
  end
470
487
  if down
471
- ((cy - 2)..(py + 15)).each { |y| image[px + 2, y] = color }
472
- ((cy - 2)..(py + 15)).each { |y| image[px + 6, y] = color }
488
+ ((cy - 2)..(py + 15)).each do |y|
489
+ image[px + 2, y] = color
490
+ image[px + 6, y] = color
491
+ end
473
492
  end
474
- elsif style == :heavy
493
+ when :heavy
475
494
  if left
476
495
  (px..cx).each do |x|
477
496
  image[x, cy - 1] = color
@@ -500,42 +519,34 @@ module TUITD
500
519
  image[cx + 1, y] = color
501
520
  end
502
521
  end
503
- elsif style == :light_rounded
522
+ when :light_rounded
504
523
  case char
505
524
  when "╭"
506
- (px + 5..px + 7).each { |x| image[x, py + 8] = color }
507
- (py + 10..py + 15).each { |y| image[px + 4, y] = color }
525
+ ((px + 5)..(px + 7)).each { |x| image[x, py + 8] = color }
526
+ ((py + 10)..(py + 15)).each { |y| image[px + 4, y] = color }
508
527
  image[px + 4, py + 9] = color
509
528
  image[px + 5, py + 9] = color
510
529
  when "╮"
511
- (px..px + 3).each { |x| image[x, py + 8] = color }
512
- (py + 10..py + 15).each { |y| image[px + 4, y] = color }
530
+ (px..(px + 3)).each { |x| image[x, py + 8] = color }
531
+ ((py + 10)..(py + 15)).each { |y| image[px + 4, y] = color }
513
532
  image[px + 4, py + 9] = color
514
533
  image[px + 3, py + 9] = color
515
534
  when "╯"
516
- (px..px + 3).each { |x| image[x, py + 8] = color }
517
- (py..py + 6).each { |y| image[px + 4, y] = color }
535
+ (px..(px + 3)).each { |x| image[x, py + 8] = color }
536
+ (py..(py + 6)).each { |y| image[px + 4, y] = color }
518
537
  image[px + 4, py + 7] = color
519
538
  image[px + 3, py + 7] = color
520
539
  when "╰"
521
- (px + 5..px + 7).each { |x| image[x, py + 8] = color }
522
- (py..py + 6).each { |y| image[px + 4, y] = color }
540
+ ((px + 5)..(px + 7)).each { |x| image[x, py + 8] = color }
541
+ (py..(py + 6)).each { |y| image[px + 4, y] = color }
523
542
  image[px + 4, py + 7] = color
524
543
  image[px + 5, py + 7] = color
525
544
  end
526
545
  else # :light
527
- if left
528
- (px..cx).each { |x| image[x, cy] = color }
529
- end
530
- if right
531
- (cx..(px + 7)).each { |x| image[x, cy] = color }
532
- end
533
- if up
534
- (py..cy).each { |y| image[cx, y] = color }
535
- end
536
- if down
537
- (cy..(py + 15)).each { |y| image[cx, y] = color }
538
- end
546
+ (px..cx).each { |x| image[x, cy] = color } if left
547
+ (cx..(px + 7)).each { |x| image[x, cy] = color } if right
548
+ (py..cy).each { |y| image[cx, y] = color } if up
549
+ (cy..(py + 15)).each { |y| image[cx, y] = color } if down
539
550
  end
540
551
  end
541
552
 
@@ -551,7 +562,7 @@ module TUITD
551
562
  ri = cursor_info[:row] || cursor_info["row"] || 0
552
563
  ci = cursor_info[:col] || cursor_info["col"] || 0
553
564
 
554
- return if ri < 0 || ri >= @rows || ci < 0 || ci >= @cols
565
+ return if ri.negative? || ri >= @rows || ci.negative? || ci >= @cols
555
566
 
556
567
  style_val = @state[:cursor_style] || cursor_info[:style] || cursor_info["style"] || 1
557
568
 
@@ -568,6 +579,7 @@ module TUITD
568
579
  x = px + dx
569
580
  y = py + dy
570
581
  next if x >= image.width || y >= image.height
582
+
571
583
  original_color = image[x, y]
572
584
  r = 255 - ChunkyPNG::Color.r(original_color)
573
585
  g = 255 - ChunkyPNG::Color.g(original_color)
@@ -579,9 +591,11 @@ module TUITD
579
591
  2.times do |h_offset|
580
592
  y = py + CELL_H - 1 - h_offset
581
593
  next if y >= image.height
594
+
582
595
  CELL_W.times do |dx|
583
596
  x = px + dx
584
597
  next if x >= image.width
598
+
585
599
  image[x, y] = color
586
600
  end
587
601
  end
@@ -589,9 +603,11 @@ module TUITD
589
603
  2.times do |w_offset|
590
604
  x = px + w_offset
591
605
  next if x >= image.width
606
+
592
607
  CELL_H.times do |dy|
593
608
  y = py + dy
594
609
  next if y >= image.height
610
+
595
611
  image[x, y] = color
596
612
  end
597
613
  end
@@ -603,7 +619,7 @@ module TUITD
603
619
  (0..3).each do |i|
604
620
  image[px + 2 + i, py + 4 + i] = color
605
621
  image[px + 3 + i, py + 4 + i] = color # bold/thick chevron
606
-
622
+
607
623
  image[px + 5 - i, py + 8 + i] = color
608
624
  image[px + 6 - i, py + 8 + i] = color # bold/thick chevron
609
625
  end
@@ -623,6 +639,7 @@ module TUITD
623
639
  x = cx + dx
624
640
  y = cy + dy
625
641
  next if x < px || x >= px + CELL_W || y < py || y >= py + CELL_H
642
+
626
643
  image[x, y] = color
627
644
  end
628
645
  end
@@ -639,17 +656,18 @@ module TUITD
639
656
  [5, 6], # Dot 5
640
657
  [5, 9], # Dot 6
641
658
  [2, 12], # Dot 7
642
- [5, 12] # Dot 8
659
+ [5, 12], # Dot 8
643
660
  ]
644
661
  dot_coords.each_with_index do |(dx, dy), idx|
645
- if (mask & (1 << idx)) != 0
646
- 2.times do |ddy|
647
- 2.times do |ddx|
648
- x = px + dx + ddx
649
- y = py + dy + ddy
650
- next if x >= image.width || y >= image.height
651
- image[x, y] = color
652
- end
662
+ next unless mask.anybits?(1 << idx)
663
+
664
+ 2.times do |ddy|
665
+ 2.times do |ddx|
666
+ x = px + dx + ddx
667
+ y = py + dy + ddy
668
+ next if x >= image.width || y >= image.height
669
+
670
+ image[x, y] = color
653
671
  end
654
672
  end
655
673
  end
@@ -659,7 +677,7 @@ module TUITD
659
677
  color = ChunkyPNG::Color.rgb(*fg_rgb)
660
678
  (5..8).each do |dy|
661
679
  width = dy - 5
662
- (4 - width..4 + width).each do |dx|
680
+ ((4 - width)..(4 + width)).each do |dx|
663
681
  image[px + dx, py + dy] = color
664
682
  end
665
683
  end
@@ -669,7 +687,7 @@ module TUITD
669
687
  color = ChunkyPNG::Color.rgb(*fg_rgb)
670
688
  (5..8).each do |dy|
671
689
  width = 8 - dy
672
- (4 - width..4 + width).each do |dx|
690
+ ((4 - width)..(4 + width)).each do |dx|
673
691
  image[px + dx, py + dy] = color
674
692
  end
675
693
  end
@@ -874,6 +892,6 @@ module TUITD
874
892
  image[px + 3, py + 8] = color
875
893
  image[px + 4, py + 8] = color
876
894
  end
877
-
878
895
  end
879
896
  end
897
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockNesting, Metrics/ParameterLists, Metrics/ClassLength, Metrics/CollectionLiteralLength
data/lib/tui_td/state.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
4
+
3
5
  module TUITD
4
6
  # Represents the parsed state of a terminal screen.
5
7
  # Provides high-level query methods for AI consumption.
@@ -31,6 +33,7 @@ module TUITD
31
33
  # Get text at a specific position
32
34
  def text_at(row, col, length = @cols - col)
33
35
  return "" if row >= @rows || col >= @cols
36
+
34
37
  @grid[row][col, length].map { |c| c[:char] }.join
35
38
  end
36
39
 
@@ -51,16 +54,19 @@ module TUITD
51
54
  # Get the color at a specific cell
52
55
  def foreground_at(row, col)
53
56
  return nil if row >= @rows || col >= @cols
57
+
54
58
  @grid[row][col][:fg]
55
59
  end
56
60
 
57
61
  def background_at(row, col)
58
62
  return nil if row >= @rows || col >= @cols
63
+
59
64
  @grid[row][col][:bg]
60
65
  end
61
66
 
62
67
  def style_at(row, col)
63
68
  return nil if row >= @rows || col >= @cols
69
+
64
70
  cell = @grid[row][col]
65
71
  { bold: cell[:bold], italic: cell[:italic], underline: cell[:underline] }
66
72
  end
@@ -72,12 +78,12 @@ module TUITD
72
78
  c = cursor_info[:col] || cursor_info["col"] || 0
73
79
  styled_count = h.count { |hl| hl[:bold] || hl[:italic] || hl[:underline] || hl[:fg] || hl[:bg] }
74
80
 
75
- summary = +"Cursor at [#{r},#{c}]. "
76
- summary << "#{styled_count} styled row#{styled_count == 1 ? '' : 's'}"
81
+ summary = "Cursor at [#{r},#{c}]. "
82
+ summary << "#{styled_count} styled row#{"s" unless styled_count == 1}"
77
83
  fgs = h.flat_map { |hl| hl[:fg] }.compact.uniq
78
84
  bgs = h.flat_map { |hl| hl[:bg] }.compact.uniq
79
- summary << ", colors: fg=#{fgs.sort.join(',')}" unless fgs.empty?
80
- summary << ", bg=#{bgs.sort.join(',')}" unless bgs.empty?
85
+ summary << ", colors: fg=#{fgs.sort.join(",")}" unless fgs.empty?
86
+ summary << ", bg=#{bgs.sort.join(",")}" unless bgs.empty?
81
87
  summary << "."
82
88
 
83
89
  {
@@ -119,3 +125,4 @@ module TUITD
119
125
  end
120
126
  end
121
127
  end
128
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength
4
+
3
5
  require "json"
4
6
 
5
7
  module TUITD
@@ -39,10 +41,6 @@ module TUITD
39
41
  @on_step = on_step
40
42
  rescue JSON::ParserError => e
41
43
  raise Error, "Invalid JSON: #{e.message}"
42
- @plan[:steps] = @plan[:steps].map { |s| s.transform_keys(&:to_sym) }
43
- @plan[:before_all] = @plan[:before_all]&.map { |s| s.transform_keys(&:to_sym) }
44
- @plan[:after_all] = @plan[:after_all]&.map { |s| s.transform_keys(&:to_sym) }
45
- @on_step = on_step
46
44
  end
47
45
 
48
46
  def run
@@ -55,7 +53,7 @@ module TUITD
55
53
  hooks = [
56
54
  { label: :before_all, steps: @plan[:before_all] || [] },
57
55
  { label: :main, steps: @plan[:steps] },
58
- { label: :after_all, steps: @plan[:after_all] || [] }
56
+ { label: :after_all, steps: @plan[:after_all] || [] },
59
57
  ]
60
58
 
61
59
  all_results = []
@@ -120,7 +118,8 @@ module TUITD
120
118
  if match
121
119
  Result.new(step: action, passed: true, message: "Style at [#{row},#{col}] matches #{expected}")
122
120
  else
123
- Result.new(step: action, passed: false, message: "Style at [#{row},#{col}] is #{actual}, expected #{expected}")
121
+ Result.new(step: action, passed: false,
122
+ message: "Style at [#{row},#{col}] is #{actual}, expected #{expected}",)
124
123
  end
125
124
 
126
125
  when "screenshot"
@@ -159,7 +158,6 @@ module TUITD
159
158
  else
160
159
  Result.new(step: action, passed: false, message: "Unknown action: #{action}")
161
160
  end
162
-
163
161
  rescue StandardError => e
164
162
  r = Result.new(step: action, passed: false, message: "#{e.class}: #{e.message}")
165
163
  end
@@ -167,23 +165,23 @@ module TUITD
167
165
  all_results << r
168
166
  all_passed &&= r.passed
169
167
 
170
- if @on_step
171
- state_data = nil
172
- begin
173
- state_data = driver.state_data if driver
174
- rescue StandardError
175
- # ignore — state retrieval is best-effort
176
- end
177
- @on_step.call(
178
- index: all_results.size - 1,
179
- total: total_steps,
180
- action: action,
181
- value: value,
182
- result: r,
183
- driver: driver,
184
- state_data: state_data
185
- )
168
+ next unless @on_step
169
+
170
+ state_data = nil
171
+ begin
172
+ state_data = driver.state_data if driver
173
+ rescue StandardError
174
+ # ignore — state retrieval is best-effort
186
175
  end
176
+ @on_step.call(
177
+ index: all_results.size - 1,
178
+ total: total_steps,
179
+ action: action,
180
+ value: value,
181
+ result: r,
182
+ driver: driver,
183
+ state_data: state_data,
184
+ )
187
185
  end
188
186
  end
189
187
 
@@ -192,7 +190,7 @@ module TUITD
192
190
  {
193
191
  name: @plan[:name] || "(unnamed)",
194
192
  passed: all_passed,
195
- results: all_results.map(&:to_h)
193
+ results: all_results.map(&:to_h),
196
194
  }
197
195
  end
198
196
 
@@ -261,8 +259,10 @@ module TUITD
261
259
  if actual == expected
262
260
  Result.new(step: step.keys.first.to_s, passed: true, message: "#{label} at [#{row},#{col}] is #{expected}")
263
261
  else
264
- Result.new(step: step.keys.first.to_s, passed: false, message: "#{label} at [#{row},#{col}] is #{actual}, expected #{expected}")
262
+ Result.new(step: step.keys.first.to_s, passed: false,
263
+ message: "#{label} at [#{row},#{col}] is #{actual}, expected #{expected}",)
265
264
  end
266
265
  end
267
266
  end
268
267
  end
268
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength