ratatui_ruby 1.4.0-x86_64-linux
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 +7 -0
- data/LICENSE +15 -0
- data/LICENSES/AGPL-3.0-or-later.txt +661 -0
- data/LICENSES/CC-BY-SA-4.0.txt +427 -0
- data/LICENSES/CC0-1.0.txt +121 -0
- data/LICENSES/LGPL-3.0-or-later.txt +304 -0
- data/LICENSES/MIT-0.txt +16 -0
- data/LICENSES/MIT.txt +21 -0
- data/REUSE.toml +42 -0
- data/exe/.gitkeep +0 -0
- data/ext/ratatui_ruby/.cargo/config.toml +13 -0
- data/ext/ratatui_ruby/.gitignore +4 -0
- data/ext/ratatui_ruby/Cargo.lock +1737 -0
- data/ext/ratatui_ruby/Cargo.toml +24 -0
- data/ext/ratatui_ruby/clippy.toml +7 -0
- data/ext/ratatui_ruby/extconf.rb +21 -0
- data/ext/ratatui_ruby/src/color.rs +82 -0
- data/ext/ratatui_ruby/src/errors.rs +28 -0
- data/ext/ratatui_ruby/src/events.rs +700 -0
- data/ext/ratatui_ruby/src/frame.rs +241 -0
- data/ext/ratatui_ruby/src/lib.rs +343 -0
- data/ext/ratatui_ruby/src/lib_header.rs +11 -0
- data/ext/ratatui_ruby/src/rendering.rs +158 -0
- data/ext/ratatui_ruby/src/string_width.rs +101 -0
- data/ext/ratatui_ruby/src/style.rs +469 -0
- data/ext/ratatui_ruby/src/terminal/capabilities.rs +46 -0
- data/ext/ratatui_ruby/src/terminal/init.rs +233 -0
- data/ext/ratatui_ruby/src/terminal/mod.rs +42 -0
- data/ext/ratatui_ruby/src/terminal/mutations.rs +158 -0
- data/ext/ratatui_ruby/src/terminal/queries.rs +231 -0
- data/ext/ratatui_ruby/src/terminal/query.rs +400 -0
- data/ext/ratatui_ruby/src/terminal/storage.rs +109 -0
- data/ext/ratatui_ruby/src/terminal/wrapper.rs +16 -0
- data/ext/ratatui_ruby/src/text.rs +225 -0
- data/ext/ratatui_ruby/src/widgets/barchart.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/block.rs +41 -0
- data/ext/ratatui_ruby/src/widgets/calendar.rs +84 -0
- data/ext/ratatui_ruby/src/widgets/canvas.rs +183 -0
- data/ext/ratatui_ruby/src/widgets/center.rs +79 -0
- data/ext/ratatui_ruby/src/widgets/chart.rs +222 -0
- data/ext/ratatui_ruby/src/widgets/clear.rs +39 -0
- data/ext/ratatui_ruby/src/widgets/cursor.rs +32 -0
- data/ext/ratatui_ruby/src/widgets/gauge.rs +65 -0
- data/ext/ratatui_ruby/src/widgets/layout.rs +379 -0
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +100 -0
- data/ext/ratatui_ruby/src/widgets/list.rs +378 -0
- data/ext/ratatui_ruby/src/widgets/list_state.rs +173 -0
- data/ext/ratatui_ruby/src/widgets/mod.rs +26 -0
- data/ext/ratatui_ruby/src/widgets/overlay.rs +24 -0
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +87 -0
- data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
- data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +55 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +214 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/sparkline.rs +127 -0
- data/ext/ratatui_ruby/src/widgets/table.rs +415 -0
- data/ext/ratatui_ruby/src/widgets/table_state.rs +203 -0
- data/ext/ratatui_ruby/src/widgets/tabs.rs +194 -0
- data/lib/ratatui_ruby/backend/window_size.rb +50 -0
- data/lib/ratatui_ruby/backend.rb +59 -0
- data/lib/ratatui_ruby/buffer/cell.rb +212 -0
- data/lib/ratatui_ruby/buffer.rb +149 -0
- data/lib/ratatui_ruby/cell.rb +208 -0
- data/lib/ratatui_ruby/debug.rb +215 -0
- data/lib/ratatui_ruby/draw.rb +63 -0
- data/lib/ratatui_ruby/event/focus_gained.rb +125 -0
- data/lib/ratatui_ruby/event/focus_lost.rb +127 -0
- data/lib/ratatui_ruby/event/key/character.rb +53 -0
- data/lib/ratatui_ruby/event/key/dwim.rb +301 -0
- data/lib/ratatui_ruby/event/key/media.rb +46 -0
- data/lib/ratatui_ruby/event/key/modifier.rb +107 -0
- data/lib/ratatui_ruby/event/key/navigation.rb +72 -0
- data/lib/ratatui_ruby/event/key/system.rb +47 -0
- data/lib/ratatui_ruby/event/key.rb +479 -0
- data/lib/ratatui_ruby/event/mouse.rb +291 -0
- data/lib/ratatui_ruby/event/none.rb +53 -0
- data/lib/ratatui_ruby/event/paste.rb +130 -0
- data/lib/ratatui_ruby/event/resize.rb +221 -0
- data/lib/ratatui_ruby/event/sync.rb +52 -0
- data/lib/ratatui_ruby/event.rb +163 -0
- data/lib/ratatui_ruby/frame.rb +257 -0
- data/lib/ratatui_ruby/labs/a11y.rb +182 -0
- data/lib/ratatui_ruby/labs/frame_a11y_capture.rb +50 -0
- data/lib/ratatui_ruby/labs.rb +47 -0
- data/lib/ratatui_ruby/layout/alignment.rb +91 -0
- data/lib/ratatui_ruby/layout/constraint.rb +337 -0
- data/lib/ratatui_ruby/layout/layout.rb +258 -0
- data/lib/ratatui_ruby/layout/position.rb +81 -0
- data/lib/ratatui_ruby/layout/rect.rb +733 -0
- data/lib/ratatui_ruby/layout/size.rb +62 -0
- data/lib/ratatui_ruby/layout.rb +29 -0
- data/lib/ratatui_ruby/list_state.rb +201 -0
- data/lib/ratatui_ruby/output_guard.rb +171 -0
- data/lib/ratatui_ruby/ratatui_ruby.so +0 -0
- data/lib/ratatui_ruby/scrollbar_state.rb +122 -0
- data/lib/ratatui_ruby/style/color.rb +149 -0
- data/lib/ratatui_ruby/style/style.rb +147 -0
- data/lib/ratatui_ruby/style.rb +19 -0
- data/lib/ratatui_ruby/symbols.rb +435 -0
- data/lib/ratatui_ruby/synthetic_events.rb +106 -0
- data/lib/ratatui_ruby/table_state.rb +251 -0
- data/lib/ratatui_ruby/terminal/capabilities.rb +316 -0
- data/lib/ratatui_ruby/terminal/viewport.rb +80 -0
- data/lib/ratatui_ruby/terminal.rb +66 -0
- data/lib/ratatui_ruby/terminal_lifecycle.rb +303 -0
- data/lib/ratatui_ruby/terminal_lifecycle.rb.bak +197 -0
- data/lib/ratatui_ruby/test_helper/event_injection.rb +241 -0
- data/lib/ratatui_ruby/test_helper/global_state.rb +111 -0
- data/lib/ratatui_ruby/test_helper/snapshot.rb +568 -0
- data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.ansi +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.txt +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.ansi +5 -0
- data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.txt +5 -0
- data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.ansi +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.txt +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/my_snapshot.txt +1 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.ansi +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.txt +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.ansi +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.txt +10 -0
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +449 -0
- data/lib/ratatui_ruby/test_helper/subprocess_timeout.rb +35 -0
- data/lib/ratatui_ruby/test_helper/terminal.rb +187 -0
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +86 -0
- data/lib/ratatui_ruby/test_helper.rb +115 -0
- data/lib/ratatui_ruby/text/line.rb +245 -0
- data/lib/ratatui_ruby/text/span.rb +158 -0
- data/lib/ratatui_ruby/text.rb +99 -0
- data/lib/ratatui_ruby/tui/buffer_factories.rb +22 -0
- data/lib/ratatui_ruby/tui/canvas_factories.rb +149 -0
- data/lib/ratatui_ruby/tui/core.rb +67 -0
- data/lib/ratatui_ruby/tui/layout_factories.rb +153 -0
- data/lib/ratatui_ruby/tui/state_factories.rb +77 -0
- data/lib/ratatui_ruby/tui/style_factories.rb +22 -0
- data/lib/ratatui_ruby/tui/text_factories.rb +86 -0
- data/lib/ratatui_ruby/tui/widget_factories.rb +272 -0
- data/lib/ratatui_ruby/tui.rb +106 -0
- data/lib/ratatui_ruby/version.rb +12 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +51 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +29 -0
- data/lib/ratatui_ruby/widgets/bar_chart.rb +308 -0
- data/lib/ratatui_ruby/widgets/block.rb +266 -0
- data/lib/ratatui_ruby/widgets/calendar.rb +88 -0
- data/lib/ratatui_ruby/widgets/canvas.rb +297 -0
- data/lib/ratatui_ruby/widgets/cell.rb +59 -0
- data/lib/ratatui_ruby/widgets/center.rb +71 -0
- data/lib/ratatui_ruby/widgets/chart.rb +172 -0
- data/lib/ratatui_ruby/widgets/clear.rb +66 -0
- data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
- data/lib/ratatui_ruby/widgets/cursor.rb +54 -0
- data/lib/ratatui_ruby/widgets/gauge.rb +146 -0
- data/lib/ratatui_ruby/widgets/line_gauge.rb +158 -0
- data/lib/ratatui_ruby/widgets/list.rb +252 -0
- data/lib/ratatui_ruby/widgets/list_item.rb +55 -0
- data/lib/ratatui_ruby/widgets/overlay.rb +55 -0
- data/lib/ratatui_ruby/widgets/paragraph.rb +113 -0
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +35 -0
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +40 -0
- data/lib/ratatui_ruby/widgets/row.rb +123 -0
- data/lib/ratatui_ruby/widgets/scrollbar.rb +147 -0
- data/lib/ratatui_ruby/widgets/shape/label.rb +80 -0
- data/lib/ratatui_ruby/widgets/sparkline.rb +153 -0
- data/lib/ratatui_ruby/widgets/table.rb +213 -0
- data/lib/ratatui_ruby/widgets/tabs.rb +91 -0
- data/lib/ratatui_ruby/widgets.rb +43 -0
- data/lib/ratatui_ruby.rb +555 -0
- data/sig/examples/app_all_events/app.rbs +11 -0
- data/sig/examples/app_all_events/model/app_model.rbs +23 -0
- data/sig/examples/app_all_events/model/event_entry.rbs +23 -0
- data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
- data/sig/examples/app_all_events/view/app_view.rbs +8 -0
- data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
- data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
- data/sig/examples/app_all_events/view/live_view.rbs +6 -0
- data/sig/examples/app_all_events/view/log_view.rbs +6 -0
- data/sig/examples/app_all_events/view.rbs +14 -0
- data/sig/examples/app_cli_rich_moments/app.rbs +12 -0
- data/sig/examples/app_color_picker/app.rbs +17 -0
- data/sig/examples/app_external_editor/app.rbs +12 -0
- data/sig/examples/app_login_form/app.rbs +11 -0
- data/sig/examples/app_stateful_interaction/app.rbs +39 -0
- data/sig/examples/verify_quickstart_dsl/app.rbs +17 -0
- data/sig/examples/verify_quickstart_lifecycle/app.rbs +17 -0
- data/sig/examples/verify_readme_usage/app.rbs +17 -0
- data/sig/examples/widget_block_demo/app.rbs +38 -0
- data/sig/examples/widget_box_demo/app.rbs +17 -0
- data/sig/examples/widget_calendar_demo/app.rbs +17 -0
- data/sig/examples/widget_cell_demo/app.rbs +17 -0
- data/sig/examples/widget_chart_demo/app.rbs +17 -0
- data/sig/examples/widget_gauge_demo/app.rbs +17 -0
- data/sig/examples/widget_layout_split/app.rbs +16 -0
- data/sig/examples/widget_line_gauge_demo/app.rbs +17 -0
- data/sig/examples/widget_list_demo/app.rbs +17 -0
- data/sig/examples/widget_map_demo/app.rbs +17 -0
- data/sig/examples/widget_popup_demo/app.rbs +17 -0
- data/sig/examples/widget_ratatui_logo_demo/app.rbs +17 -0
- data/sig/examples/widget_ratatui_mascot_demo/app.rbs +17 -0
- data/sig/examples/widget_rect/app.rbs +18 -0
- data/sig/examples/widget_render/app.rbs +16 -0
- data/sig/examples/widget_rich_text/app.rbs +17 -0
- data/sig/examples/widget_scroll_text/app.rbs +17 -0
- data/sig/examples/widget_scrollbar_demo/app.rbs +17 -0
- data/sig/examples/widget_sparkline_demo/app.rbs +16 -0
- data/sig/examples/widget_style_colors/app.rbs +20 -0
- data/sig/examples/widget_table_demo/app.rbs +17 -0
- data/sig/examples/widget_text_width/app.rbs +16 -0
- data/sig/generated/event_key_predicates.rbs +1348 -0
- data/sig/manifest.yaml +5 -0
- data/sig/patches/data.rbs +26 -0
- data/sig/patches/debugger__.rbs +8 -0
- data/sig/ratatui_ruby/backend/window_size.rbs +17 -0
- data/sig/ratatui_ruby/backend.rbs +12 -0
- data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
- data/sig/ratatui_ruby/buffer.rbs +18 -0
- data/sig/ratatui_ruby/cell.rbs +44 -0
- data/sig/ratatui_ruby/clear.rbs +18 -0
- data/sig/ratatui_ruby/constraint.rbs +26 -0
- data/sig/ratatui_ruby/debug.rbs +45 -0
- data/sig/ratatui_ruby/draw.rbs +30 -0
- data/sig/ratatui_ruby/event.rbs +249 -0
- data/sig/ratatui_ruby/frame.rbs +23 -0
- data/sig/ratatui_ruby/interfaces.rbs +25 -0
- data/sig/ratatui_ruby/labs.rbs +90 -0
- data/sig/ratatui_ruby/layout/alignment.rbs +26 -0
- data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
- data/sig/ratatui_ruby/layout/layout.rbs +45 -0
- data/sig/ratatui_ruby/layout/position.rbs +18 -0
- data/sig/ratatui_ruby/layout/rect.rbs +64 -0
- data/sig/ratatui_ruby/layout/size.rbs +18 -0
- data/sig/ratatui_ruby/list_state.rbs +23 -0
- data/sig/ratatui_ruby/output_guard.rbs +23 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +113 -0
- data/sig/ratatui_ruby/rect.rbs +17 -0
- data/sig/ratatui_ruby/scrollbar_state.rbs +24 -0
- data/sig/ratatui_ruby/session.rbs +93 -0
- data/sig/ratatui_ruby/style/color.rbs +22 -0
- data/sig/ratatui_ruby/style/style.rbs +29 -0
- data/sig/ratatui_ruby/symbols.rbs +141 -0
- data/sig/ratatui_ruby/synthetic_events.rbs +24 -0
- data/sig/ratatui_ruby/table_state.rbs +27 -0
- data/sig/ratatui_ruby/terminal/capabilities.rbs +38 -0
- data/sig/ratatui_ruby/terminal/viewport.rbs +33 -0
- data/sig/ratatui_ruby/terminal_lifecycle.rbs +39 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +22 -0
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +37 -0
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +77 -0
- data/sig/ratatui_ruby/test_helper/terminal.rbs +20 -0
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +32 -0
- data/sig/ratatui_ruby/test_helper.rbs +18 -0
- data/sig/ratatui_ruby/text/line.rbs +27 -0
- data/sig/ratatui_ruby/text/span.rbs +23 -0
- data/sig/ratatui_ruby/text.rbs +12 -0
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +16 -0
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +38 -0
- data/sig/ratatui_ruby/tui/core.rbs +23 -0
- data/sig/ratatui_ruby/tui/layout_factories.rbs +39 -0
- data/sig/ratatui_ruby/tui/state_factories.rbs +23 -0
- data/sig/ratatui_ruby/tui/style_factories.rbs +18 -0
- data/sig/ratatui_ruby/tui/text_factories.rbs +23 -0
- data/sig/ratatui_ruby/tui/widget_factories.rbs +138 -0
- data/sig/ratatui_ruby/tui.rbs +25 -0
- data/sig/ratatui_ruby/version.rbs +12 -0
- data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
- data/sig/ratatui_ruby/widgets/block.rbs +51 -0
- data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
- data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
- data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
- data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
- data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
- data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
- data/sig/ratatui_ruby/widgets/list.rbs +63 -0
- data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
- data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
- data/sig/ratatui_ruby/widgets/row.rbs +43 -0
- data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
- data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
- data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
- data/sig/ratatui_ruby/widgets/table.rbs +78 -0
- data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
- data/sig/ratatui_ruby/widgets.rbs +16 -0
- data/vendor/goodcop/base.yml +1047 -0
- metadata +729 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
require "fileutils"
|
|
9
|
+
|
|
10
|
+
module RatatuiRuby
|
|
11
|
+
module TestHelper
|
|
12
|
+
##
|
|
13
|
+
# Snapshot testing assertions for terminal UIs.
|
|
14
|
+
#
|
|
15
|
+
# Verifying every character of a TUI screen by hand is tedious. Snapshots let you
|
|
16
|
+
# capture the screen once and compare against it in future runs.
|
|
17
|
+
#
|
|
18
|
+
# This mixin provides <tt>assert_plain_snapshot</tt> for plain text,
|
|
19
|
+
# <tt>assert_rich_snapshot</tt> for styled ANSI output, and
|
|
20
|
+
# <tt>assert_snapshots</tt> (plural) for both. All auto-create snapshot
|
|
21
|
+
# files on first run.
|
|
22
|
+
#
|
|
23
|
+
# Use it to verify complex layouts, styles, and interactions without manual assertions.
|
|
24
|
+
#
|
|
25
|
+
# === Snapshot Files
|
|
26
|
+
#
|
|
27
|
+
# Snapshots live in a <tt>snapshots/</tt> subdirectory next to your test file:
|
|
28
|
+
#
|
|
29
|
+
#--
|
|
30
|
+
# SPDX-SnippetBegin
|
|
31
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
32
|
+
# SPDX-License-Identifier: MIT-0
|
|
33
|
+
#++
|
|
34
|
+
# test/examples/my_app/test_app.rb
|
|
35
|
+
# test/examples/my_app/snapshots/initial_render.txt
|
|
36
|
+
# test/examples/my_app/snapshots/initial_render.ansi
|
|
37
|
+
#
|
|
38
|
+
#--
|
|
39
|
+
# SPDX-SnippetEnd
|
|
40
|
+
#++
|
|
41
|
+
# === Creating and Updating Snapshots
|
|
42
|
+
#
|
|
43
|
+
# Run tests with <tt>UPDATE_SNAPSHOTS=1</tt> to create or refresh snapshots:
|
|
44
|
+
#
|
|
45
|
+
#--
|
|
46
|
+
# SPDX-SnippetBegin
|
|
47
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
48
|
+
# SPDX-License-Identifier: MIT-0
|
|
49
|
+
#++
|
|
50
|
+
# UPDATE_SNAPSHOTS=1 bundle exec rake test
|
|
51
|
+
#
|
|
52
|
+
#--
|
|
53
|
+
# SPDX-SnippetEnd
|
|
54
|
+
#++
|
|
55
|
+
# === Seeding Random Data
|
|
56
|
+
#
|
|
57
|
+
# Random data (scatter plots, generated content) breaks snapshot stability.
|
|
58
|
+
# Use a seeded <tt>Random</tt> instance instead of <tt>Kernel.rand</tt>:
|
|
59
|
+
#
|
|
60
|
+
#--
|
|
61
|
+
# SPDX-SnippetBegin
|
|
62
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
63
|
+
# SPDX-License-Identifier: MIT-0
|
|
64
|
+
#++
|
|
65
|
+
# class MyApp
|
|
66
|
+
# def initialize(seed: nil)
|
|
67
|
+
# @rng = seed ? Random.new(seed) : Random.new
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# def generate_data
|
|
71
|
+
# (0..20).map { @rng.rand(0.0..10.0) }
|
|
72
|
+
# end
|
|
73
|
+
# end
|
|
74
|
+
#
|
|
75
|
+
# # In your test
|
|
76
|
+
# def setup
|
|
77
|
+
# @app = MyApp.new(seed: 42)
|
|
78
|
+
# end
|
|
79
|
+
#
|
|
80
|
+
#--
|
|
81
|
+
# SPDX-SnippetEnd
|
|
82
|
+
#++
|
|
83
|
+
# For libraries like Faker, see their docs on deterministic random:
|
|
84
|
+
# https://github.com/faker-ruby/faker#deterministic-random
|
|
85
|
+
#
|
|
86
|
+
# === Normalization Blocks
|
|
87
|
+
#
|
|
88
|
+
# Mask dynamic content (timestamps, IDs) with a normalization block:
|
|
89
|
+
#
|
|
90
|
+
#--
|
|
91
|
+
# SPDX-SnippetBegin
|
|
92
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
93
|
+
# SPDX-License-Identifier: MIT-0
|
|
94
|
+
#++
|
|
95
|
+
# assert_snapshots("dashboard") do |lines|
|
|
96
|
+
# lines.map { |l| l.gsub(/\d{4}-\d{2}-\d{2}/, "YYYY-MM-DD") }
|
|
97
|
+
# end
|
|
98
|
+
#
|
|
99
|
+
#--
|
|
100
|
+
# SPDX-SnippetEnd
|
|
101
|
+
#++
|
|
102
|
+
#
|
|
103
|
+
# === Class-Wide Normalization
|
|
104
|
+
#
|
|
105
|
+
# When every test in a class faces the same dynamic content, override
|
|
106
|
+
# <tt>normalize_snapshots</tt> instead of repeating the block. Return a callable
|
|
107
|
+
# (or Array of callables) that transforms lines. The hook runs before any per-call
|
|
108
|
+
# block, so the two compose naturally. See <tt>normalize_snapshots</tt> for details.
|
|
109
|
+
module Snapshot
|
|
110
|
+
# Override this method to normalize all snapshots in a test class.
|
|
111
|
+
#
|
|
112
|
+
# Snapshot assertions compare screen content against stored files. Dynamic content
|
|
113
|
+
# (timestamps, temp paths, random IDs) breaks those comparisons. Passing a normalization
|
|
114
|
+
# block to each assertion fixes one test but creates duplication when every test in a
|
|
115
|
+
# class faces the same dynamic content.
|
|
116
|
+
#
|
|
117
|
+
# Override <tt>normalize_snapshots</tt> in your test class to define class-wide
|
|
118
|
+
# normalization. Accept an Array of Strings (lines) and return the transformed Array.
|
|
119
|
+
#
|
|
120
|
+
# The hook runs before any per-call normalization block, so the two compose naturally:
|
|
121
|
+
# the hook handles class-wide concerns and the block handles one-off masking.
|
|
122
|
+
#
|
|
123
|
+
# Returns <tt>lines</tt> unchanged by default (no normalization).
|
|
124
|
+
#
|
|
125
|
+
# === Example
|
|
126
|
+
#
|
|
127
|
+
#--
|
|
128
|
+
# SPDX-SnippetBegin
|
|
129
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
130
|
+
# SPDX-License-Identifier: MIT-0
|
|
131
|
+
#++
|
|
132
|
+
# class TestFileExplorer < Minitest::Test
|
|
133
|
+
# include RatatuiRuby::TestHelper
|
|
134
|
+
#
|
|
135
|
+
# private def normalize_snapshots(lines)
|
|
136
|
+
# lines.map { |l| l.gsub(Dir.pwd, "STABLE_PATH") }
|
|
137
|
+
# end
|
|
138
|
+
#
|
|
139
|
+
# def test_initial_render
|
|
140
|
+
# # normalize_snapshots runs automatically — no block needed
|
|
141
|
+
# assert_snapshots("initial_render")
|
|
142
|
+
# end
|
|
143
|
+
#
|
|
144
|
+
# def test_after_scroll
|
|
145
|
+
# # Per-call block composes with the hook (hook runs first)
|
|
146
|
+
# assert_snapshots("after_scroll") do |lines|
|
|
147
|
+
# lines.map { |l| l.gsub(/\d{2}:\d{2}/, "XX:XX") }
|
|
148
|
+
# end
|
|
149
|
+
# end
|
|
150
|
+
# end
|
|
151
|
+
#
|
|
152
|
+
#--
|
|
153
|
+
# SPDX-SnippetEnd
|
|
154
|
+
#++
|
|
155
|
+
private def normalize_snapshots(lines)
|
|
156
|
+
lines
|
|
157
|
+
end
|
|
158
|
+
##
|
|
159
|
+
# Asserts that the current screen content matches a stored plain text snapshot.
|
|
160
|
+
#
|
|
161
|
+
# Plain text snapshots capture layout but miss styling bugs: wrong colors, missing bold,
|
|
162
|
+
# invisible text on a matching background. *Prefer <tt>assert_snapshots</tt>* (plural) to catch
|
|
163
|
+
# styling regressions.
|
|
164
|
+
#
|
|
165
|
+
# Plain text snapshots are human-readable when viewed in any editor or diff tool. They
|
|
166
|
+
# pair well with rich snapshots for documentation. Use <tt>assert_snapshots</tt> to generate both.
|
|
167
|
+
#
|
|
168
|
+
#--
|
|
169
|
+
# SPDX-SnippetBegin
|
|
170
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
171
|
+
# SPDX-License-Identifier: MIT-0
|
|
172
|
+
#++
|
|
173
|
+
# assert_plain_snapshot("login_screen")
|
|
174
|
+
# # Compares against: test/snapshots/login_screen.txt
|
|
175
|
+
#
|
|
176
|
+
# # With normalization block
|
|
177
|
+
# assert_plain_snapshot("clock") do |actual|
|
|
178
|
+
# actual.map { |l| l.gsub(/\d{2}:\d{2}/, "XX:XX") }
|
|
179
|
+
# end
|
|
180
|
+
#
|
|
181
|
+
#--
|
|
182
|
+
# SPDX-SnippetEnd
|
|
183
|
+
#++
|
|
184
|
+
# [name] String name of the snapshot (without extension).
|
|
185
|
+
# [msg] String optional failure message.
|
|
186
|
+
def assert_plain_snapshot(name, msg = nil, snapshot_dir: nil, &)
|
|
187
|
+
# Get the path of the test file calling this method
|
|
188
|
+
snapshot_dir ||= File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
|
|
189
|
+
snapshot_path = File.join(snapshot_dir, "#{name}.txt")
|
|
190
|
+
|
|
191
|
+
assert_screen_matches(snapshot_path, msg, &)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
##
|
|
195
|
+
# Asserts that the current screen content matches the expected content.
|
|
196
|
+
#
|
|
197
|
+
# Users need to verify that the entire TUI screen looks exactly as expected.
|
|
198
|
+
# Manually checking every cell or line is tedious and error-prone.
|
|
199
|
+
#
|
|
200
|
+
# This helper compares the current buffer content against an expected string (file path)
|
|
201
|
+
# or array of strings. It supports automatic snapshot creation and updating via
|
|
202
|
+
# the +UPDATE_SNAPSHOTS+ environment variable.
|
|
203
|
+
#
|
|
204
|
+
# Use it to verify complex UI states, layouts, and renderings.
|
|
205
|
+
#
|
|
206
|
+
# == Usage
|
|
207
|
+
#
|
|
208
|
+
#--
|
|
209
|
+
# SPDX-SnippetBegin
|
|
210
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
211
|
+
# SPDX-License-Identifier: MIT-0
|
|
212
|
+
#++
|
|
213
|
+
# # Direct comparison
|
|
214
|
+
# assert_screen_matches(["Line 1", "Line 2"])
|
|
215
|
+
#
|
|
216
|
+
# # File comparison
|
|
217
|
+
# assert_screen_matches("test/snapshots/login.txt")
|
|
218
|
+
#
|
|
219
|
+
# # With normalization (e.g., masking dynamic data)
|
|
220
|
+
# assert_screen_matches("test/snapshots/dashboard.txt") do |lines|
|
|
221
|
+
# lines.map { |l| l.gsub(/User ID: \d+/, "User ID: XXX") }
|
|
222
|
+
# end
|
|
223
|
+
#
|
|
224
|
+
#--
|
|
225
|
+
# SPDX-SnippetEnd
|
|
226
|
+
#++
|
|
227
|
+
# [expected] String (file path) or Array<String> (content).
|
|
228
|
+
# [msg] String optional failure message.
|
|
229
|
+
#
|
|
230
|
+
# == Non-Determinism
|
|
231
|
+
#
|
|
232
|
+
# To prevent flaky tests, this assertion performs a "Flakiness Check" when creating or updating
|
|
233
|
+
# snapshots. It captures the screen content, immediately re-renders the buffer, and compares
|
|
234
|
+
# the two results.
|
|
235
|
+
#
|
|
236
|
+
# Ensure your render logic is deterministic by seeding random number generators and stubbing
|
|
237
|
+
# time where necessary.
|
|
238
|
+
def assert_screen_matches(expected, msg = nil)
|
|
239
|
+
actual_lines = normalize_snapshots(buffer_content)
|
|
240
|
+
|
|
241
|
+
if block_given?
|
|
242
|
+
actual_lines = yield(actual_lines)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
if expected.is_a?(String)
|
|
246
|
+
# Snapshot file mode
|
|
247
|
+
snapshot_path = expected
|
|
248
|
+
update_snapshots = ENV["UPDATE_SNAPSHOTS"] == "1" || ENV["UPDATE_SNAPSHOTS"] == "true"
|
|
249
|
+
|
|
250
|
+
if !File.exist?(snapshot_path) || update_snapshots
|
|
251
|
+
FileUtils.mkdir_p(File.dirname(snapshot_path))
|
|
252
|
+
|
|
253
|
+
content_to_write = "#{actual_lines.join("\n")}\n"
|
|
254
|
+
|
|
255
|
+
begin
|
|
256
|
+
# Delete old file first to avoid git index stale-read issues
|
|
257
|
+
FileUtils.rm_f(snapshot_path)
|
|
258
|
+
|
|
259
|
+
# Write with explicit mode to ensure clean write
|
|
260
|
+
File.write(snapshot_path, content_to_write, mode: "w")
|
|
261
|
+
rescue => e
|
|
262
|
+
warn "Failed to write snapshot #{snapshot_path}: #{e.message}"
|
|
263
|
+
raise
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
if update_snapshots
|
|
267
|
+
puts "Updated snapshot: #{snapshot_path}"
|
|
268
|
+
else
|
|
269
|
+
puts "Created snapshot: #{snapshot_path}"
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
end
|
|
273
|
+
expected_lines = File.readlines(snapshot_path, chomp: true)
|
|
274
|
+
else
|
|
275
|
+
# Direct comparison mode
|
|
276
|
+
expected_lines = expected
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
msg ||= "Screen content mismatch"
|
|
280
|
+
|
|
281
|
+
assert_equal expected_lines.size, actual_lines.size, "#{msg}: Line count mismatch"
|
|
282
|
+
|
|
283
|
+
expected_lines.each_with_index do |expected_line, i|
|
|
284
|
+
actual_line = actual_lines[i]
|
|
285
|
+
assert_equal expected_line, actual_line,
|
|
286
|
+
"#{msg}: Line #{i + 1} mismatch.\nExpected: #{expected_line.inspect}\nActual: #{actual_line.inspect}"
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
##
|
|
291
|
+
# Asserts that the current screen content (including colors and styles) matches a stored ANSI snapshot.
|
|
292
|
+
#
|
|
293
|
+
# TUIs communicate meaning through colors and styles. Rich snapshots capture everything:
|
|
294
|
+
# wrong colors, missing bold, invisible text on a matching background. *Prefer <tt>assert_snapshots</tt>*
|
|
295
|
+
# (plural) to also generate human-readable plain text files for documentation.
|
|
296
|
+
#
|
|
297
|
+
# The <tt>.ansi</tt> snapshot files contain ANSI escape codes. You can <tt>cat</tt> them in a terminal
|
|
298
|
+
# to see exactly what the screen looked like.
|
|
299
|
+
#
|
|
300
|
+
#--
|
|
301
|
+
# SPDX-SnippetBegin
|
|
302
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
303
|
+
# SPDX-License-Identifier: MIT-0
|
|
304
|
+
#++
|
|
305
|
+
# assert_rich_snapshot("login_screen")
|
|
306
|
+
# # Compares against: test/snapshots/login_screen.ansi
|
|
307
|
+
#
|
|
308
|
+
# # With normalization
|
|
309
|
+
# assert_rich_snapshot("log_view") do |lines|
|
|
310
|
+
# lines.map { |l| l.gsub(/\d{2}:\d{2}:\d{2}/, "HH:MM:SS") }
|
|
311
|
+
# end
|
|
312
|
+
#
|
|
313
|
+
#--
|
|
314
|
+
# SPDX-SnippetEnd
|
|
315
|
+
#++
|
|
316
|
+
# [name] String snapshot name.
|
|
317
|
+
# [msg] String optional failure message.
|
|
318
|
+
def assert_rich_snapshot(name, msg = nil, snapshot_dir: nil)
|
|
319
|
+
snapshot_dir ||= File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
|
|
320
|
+
snapshot_path = File.join(snapshot_dir, "#{name}.ansi")
|
|
321
|
+
|
|
322
|
+
actual_content = _render_buffer_with_ansi
|
|
323
|
+
|
|
324
|
+
lines = normalize_snapshots(actual_content.split("\n"))
|
|
325
|
+
if block_given?
|
|
326
|
+
lines = yield(lines)
|
|
327
|
+
end
|
|
328
|
+
actual_content = "#{lines.join("\n")}\n"
|
|
329
|
+
|
|
330
|
+
update_snapshots = ENV["UPDATE_SNAPSHOTS"] == "1" || ENV["UPDATE_SNAPSHOTS"] == "true"
|
|
331
|
+
|
|
332
|
+
if !File.exist?(snapshot_path) || update_snapshots
|
|
333
|
+
FileUtils.mkdir_p(File.dirname(snapshot_path))
|
|
334
|
+
|
|
335
|
+
begin
|
|
336
|
+
# Delete old file first to avoid git index stale-read issues
|
|
337
|
+
FileUtils.rm_f(snapshot_path)
|
|
338
|
+
|
|
339
|
+
# Write with explicit mode to ensure clean write
|
|
340
|
+
File.write(snapshot_path, actual_content, mode: "w")
|
|
341
|
+
rescue => e
|
|
342
|
+
warn "Failed to write rich snapshot #{snapshot_path}: #{e.message}"
|
|
343
|
+
raise
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
puts (update_snapshots ? "Updated" : "Created") + " rich snapshot: #{snapshot_path}"
|
|
347
|
+
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
expected_content = File.read(snapshot_path)
|
|
351
|
+
|
|
352
|
+
# Compare byte-for-byte first
|
|
353
|
+
if expected_content != actual_content
|
|
354
|
+
# Fallback to line-by-line diff for better error messages
|
|
355
|
+
expected_lines = expected_content.split("\n")
|
|
356
|
+
actual_lines = actual_content.split("\n")
|
|
357
|
+
|
|
358
|
+
assert_equal expected_lines.size, actual_lines.size, "#{msg}: Line count mismatch"
|
|
359
|
+
|
|
360
|
+
expected_lines.each_with_index do |exp, i|
|
|
361
|
+
act = actual_lines[i]
|
|
362
|
+
assert_equal exp, act, "#{msg}: Rich content mismatch at line #{i + 1}"
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
##
|
|
368
|
+
# Asserts both plain text and rich (ANSI-styled) snapshots match.
|
|
369
|
+
#
|
|
370
|
+
# This is the recommended snapshot assertion. It calls both <tt>assert_plain_snapshot</tt> and
|
|
371
|
+
# <tt>assert_rich_snapshot</tt> with the same name, generating <tt>.txt</tt> and <tt>.ansi</tt> files.
|
|
372
|
+
#
|
|
373
|
+
# Rich snapshots catch styling bugs that plain text misses. Plain text snapshots are
|
|
374
|
+
# human-readable in any editor or diff tool, making them valuable for documentation and
|
|
375
|
+
# code review. Together, they provide comprehensive coverage and discoverability.
|
|
376
|
+
#
|
|
377
|
+
#--
|
|
378
|
+
# SPDX-SnippetBegin
|
|
379
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
380
|
+
# SPDX-License-Identifier: MIT-0
|
|
381
|
+
#++
|
|
382
|
+
# assert_snapshots("login_screen")
|
|
383
|
+
# # Creates/compares: snapshots/login_screen.txt AND snapshots/login_screen.ansi
|
|
384
|
+
#
|
|
385
|
+
# # With normalization (masks dynamic content like timestamps)
|
|
386
|
+
# assert_snapshots("dashboard") do |lines|
|
|
387
|
+
# lines.map { |l| l.gsub(/\d{2}:\d{2}:\d{2}/, "HH:MM:SS") }
|
|
388
|
+
# end
|
|
389
|
+
#
|
|
390
|
+
#--
|
|
391
|
+
# SPDX-SnippetEnd
|
|
392
|
+
#++
|
|
393
|
+
# [name] String snapshot name (without extension).
|
|
394
|
+
# [msg] String optional failure message.
|
|
395
|
+
def assert_snapshots(name, msg = nil, &)
|
|
396
|
+
snapshot_dir = File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
|
|
397
|
+
assert_plain_snapshot(name, msg, snapshot_dir:, &)
|
|
398
|
+
assert_rich_snapshot(name, msg, snapshot_dir:, &)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
##
|
|
402
|
+
# Returns the current buffer content as an ANSI-encoded string.
|
|
403
|
+
#
|
|
404
|
+
# The rich snapshot assertion captures styled output. Sometimes you need the raw ANSI
|
|
405
|
+
# string for debugging, custom assertions, or programmatic inspection.
|
|
406
|
+
#
|
|
407
|
+
# This method renders the buffer with escape codes for colors and modifiers.
|
|
408
|
+
# You can `cat` the output to see exactly what the terminal would display.
|
|
409
|
+
#
|
|
410
|
+
# === Example
|
|
411
|
+
#
|
|
412
|
+
#--
|
|
413
|
+
# SPDX-SnippetBegin
|
|
414
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
415
|
+
# SPDX-License-Identifier: MIT-0
|
|
416
|
+
#++
|
|
417
|
+
# with_test_terminal(80, 25) do
|
|
418
|
+
# RatatuiRuby.run do |tui|
|
|
419
|
+
# tui.draw tui.paragraph(text: "Hello", block: tui.block(title: "Test"))
|
|
420
|
+
# break
|
|
421
|
+
# end
|
|
422
|
+
# ansi_output = render_rich_buffer
|
|
423
|
+
# puts ansi_output # Shows styled output with escape codes
|
|
424
|
+
# end
|
|
425
|
+
#
|
|
426
|
+
#--
|
|
427
|
+
# SPDX-SnippetEnd
|
|
428
|
+
#++
|
|
429
|
+
def render_rich_buffer
|
|
430
|
+
_render_buffer_with_ansi
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
private def _render_buffer_with_ansi
|
|
434
|
+
RatatuiRuby.get_buffer_content # Ensure buffer is fresh if needed
|
|
435
|
+
|
|
436
|
+
lines = buffer_content
|
|
437
|
+
height = lines.size
|
|
438
|
+
width = lines.first&.length || 0
|
|
439
|
+
|
|
440
|
+
output = String.new
|
|
441
|
+
|
|
442
|
+
(0...height).each do |y|
|
|
443
|
+
current_fg = nil
|
|
444
|
+
current_bg = nil
|
|
445
|
+
current_modifiers = []
|
|
446
|
+
|
|
447
|
+
# Reset at start of line
|
|
448
|
+
output << "\e[0m"
|
|
449
|
+
|
|
450
|
+
(0...width).each do |x|
|
|
451
|
+
cell = RatatuiRuby.get_cell_at(x, y)
|
|
452
|
+
char = cell.char || " "
|
|
453
|
+
|
|
454
|
+
# Check for changes
|
|
455
|
+
fg_changed = cell.fg != current_fg
|
|
456
|
+
bg_changed = cell.bg != current_bg
|
|
457
|
+
mod_changed = cell.modifiers != current_modifiers
|
|
458
|
+
|
|
459
|
+
if fg_changed || bg_changed || mod_changed
|
|
460
|
+
# If modifiers change, easiest is to reset and re-apply everything
|
|
461
|
+
# because removing a modifier (e.g. bold) requires reset usually.
|
|
462
|
+
if mod_changed
|
|
463
|
+
output << "\e[0m"
|
|
464
|
+
output << _ansi_for_modifiers(cell.modifiers)
|
|
465
|
+
# Force re-apply colors after reset
|
|
466
|
+
output << _ansi_for_color(cell.fg, :fg)
|
|
467
|
+
output << _ansi_for_color(cell.bg, :bg)
|
|
468
|
+
else
|
|
469
|
+
# Modifiers same, just update colors if needed
|
|
470
|
+
output << _ansi_for_color(cell.fg, :fg) if fg_changed
|
|
471
|
+
output << _ansi_for_color(cell.bg, :bg) if bg_changed
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
current_fg = cell.fg
|
|
475
|
+
current_bg = cell.bg
|
|
476
|
+
current_modifiers = cell.modifiers
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
output << char
|
|
480
|
+
rescue
|
|
481
|
+
output << " "
|
|
482
|
+
end
|
|
483
|
+
output << "\e[0m\n" # Reset at end of line
|
|
484
|
+
end
|
|
485
|
+
output
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
private def _ansi_for_color(color, layer)
|
|
489
|
+
return "" if color.nil?
|
|
490
|
+
|
|
491
|
+
base = (layer == :fg) ? 38 : 48
|
|
492
|
+
|
|
493
|
+
case color
|
|
494
|
+
when Symbol
|
|
495
|
+
if color.to_s.start_with?("indexed_")
|
|
496
|
+
# Extracted indexed color :indexed_5 -> 5
|
|
497
|
+
idx = color.to_s.split("_").last.to_i
|
|
498
|
+
"\e[#{base};5;#{idx}m"
|
|
499
|
+
else
|
|
500
|
+
# Named colors
|
|
501
|
+
_ansi_named_color(color, layer == :fg)
|
|
502
|
+
end
|
|
503
|
+
when String
|
|
504
|
+
if color.start_with?("#")
|
|
505
|
+
# Hex color: #RRGGBB -> r;g;b
|
|
506
|
+
r = color[1..2].to_i(16)
|
|
507
|
+
g = color[3..4].to_i(16)
|
|
508
|
+
b = color[5..6].to_i(16)
|
|
509
|
+
"\e[#{base};2;#{r};#{g};#{b}m"
|
|
510
|
+
else
|
|
511
|
+
""
|
|
512
|
+
end
|
|
513
|
+
else
|
|
514
|
+
""
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
private def _ansi_named_color(name, is_fg)
|
|
519
|
+
# Map symbol to standard ANSI code offset
|
|
520
|
+
# FG: 30-37 (dim), 90-97 (bright)
|
|
521
|
+
# BG: 40-47 (dim), 100-107 (bright)
|
|
522
|
+
|
|
523
|
+
offset = is_fg ? 30 : 40
|
|
524
|
+
|
|
525
|
+
case name
|
|
526
|
+
when :black then "\e[#{offset}m"
|
|
527
|
+
when :red then "\e[#{offset + 1}m"
|
|
528
|
+
when :green then "\e[#{offset + 2}m"
|
|
529
|
+
when :yellow then "\e[#{offset + 3}m"
|
|
530
|
+
when :blue then "\e[#{offset + 4}m"
|
|
531
|
+
when :magenta then "\e[#{offset + 5}m"
|
|
532
|
+
when :cyan then "\e[#{offset + 6}m"
|
|
533
|
+
when :gray then is_fg ? "\e[90m" : "\e[100m" # Dark gray usually
|
|
534
|
+
when :dark_gray then is_fg ? "\e[90m" : "\e[100m"
|
|
535
|
+
when :light_red then "\e[#{offset + 60 + 1}m"
|
|
536
|
+
when :light_green then "\e[#{offset + 60 + 2}m"
|
|
537
|
+
when :light_yellow then "\e[#{offset + 60 + 3}m"
|
|
538
|
+
when :light_blue then "\e[#{offset + 60 + 4}m"
|
|
539
|
+
when :light_magenta then "\e[#{offset + 60 + 5}m"
|
|
540
|
+
when :light_cyan then "\e[#{offset + 60 + 6}m"
|
|
541
|
+
when :white then "\e[#{offset + 60 + 7}m"
|
|
542
|
+
else ""
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
private def _ansi_for_modifiers(modifiers)
|
|
547
|
+
return "" if modifiers.nil? || modifiers.empty?
|
|
548
|
+
|
|
549
|
+
seq = []
|
|
550
|
+
seq << "1" if modifiers.include?(:bold)
|
|
551
|
+
seq << "2" if modifiers.include?(:dim)
|
|
552
|
+
seq << "3" if modifiers.include?(:italic)
|
|
553
|
+
seq << "4" if modifiers.include?(:underlined)
|
|
554
|
+
seq << "5" if modifiers.include?(:slow_blink)
|
|
555
|
+
seq << "6" if modifiers.include?(:rapid_blink)
|
|
556
|
+
seq << "7" if modifiers.include?(:reversed)
|
|
557
|
+
seq << "8" if modifiers.include?(:hidden)
|
|
558
|
+
seq << "9" if modifiers.include?(:crossed_out)
|
|
559
|
+
|
|
560
|
+
if seq.any?
|
|
561
|
+
"\e[#{seq.join(';')}m"
|
|
562
|
+
else
|
|
563
|
+
""
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[0m┌Aligned Chart─────────────────────────────────────────────────────────────────┐[0m
|
|
2
|
+
[0m│10│Value ┌──────┐│[0m
|
|
3
|
+
[0m│ │ │[32mTestDS││[0m
|
|
4
|
+
[0m│ │ [32m••└──────┘│[0m
|
|
5
|
+
[0m│ │ [32m•••• │[0m
|
|
6
|
+
[0m│ │ [32m•••• │[0m
|
|
7
|
+
[0m│ │ [32m•••• │[0m
|
|
8
|
+
[0m│ │ [32m•••• │[0m
|
|
9
|
+
[0m│ │ [32m•••• │[0m
|
|
10
|
+
[0m│ │ [32m•••• │[0m
|
|
11
|
+
[0m│ │ [32m••• │[0m
|
|
12
|
+
[0m│ 5│ [32m•••• │[0m
|
|
13
|
+
[0m│ │ [32m•••• │[0m
|
|
14
|
+
[0m│ │ [32m•••• │[0m
|
|
15
|
+
[0m│ │ [32m•••• │[0m
|
|
16
|
+
[0m│ │ [32m•••• │[0m
|
|
17
|
+
[0m│ │ [32m•••• │[0m
|
|
18
|
+
[0m│ │ [32m•••• │[0m
|
|
19
|
+
[0m│ │ [32m•••• │[0m
|
|
20
|
+
[0m│ │ [32m•••• │[0m
|
|
21
|
+
[0m│ 0│[32m•• Time│[0m
|
|
22
|
+
[0m│ └───────────────────────────────────────────────────────────────────────────│[0m
|
|
23
|
+
[0m│ 0 5 10│[0m
|
|
24
|
+
[0m└──────────────────────────────────────────────────────────────────────────────┘[0m
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
┌Aligned Chart─────────────────────────────────────────────────────────────────┐
|
|
2
|
+
│10│Value ┌──────┐│
|
|
3
|
+
│ │ │TestDS││
|
|
4
|
+
│ │ ••└──────┘│
|
|
5
|
+
│ │ •••• │
|
|
6
|
+
│ │ •••• │
|
|
7
|
+
│ │ •••• │
|
|
8
|
+
│ │ •••• │
|
|
9
|
+
│ │ •••• │
|
|
10
|
+
│ │ •••• │
|
|
11
|
+
│ │ ••• │
|
|
12
|
+
│ 5│ •••• │
|
|
13
|
+
│ │ •••• │
|
|
14
|
+
│ │ •••• │
|
|
15
|
+
│ │ •••• │
|
|
16
|
+
│ │ •••• │
|
|
17
|
+
│ │ •••• │
|
|
18
|
+
│ │ •••• │
|
|
19
|
+
│ │ •••• │
|
|
20
|
+
│ │ •••• │
|
|
21
|
+
│ 0│•• Time│
|
|
22
|
+
│ └───────────────────────────────────────────────────────────────────────────│
|
|
23
|
+
│ 0 5 10│
|
|
24
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[0m┌Test Chart────────────────────────────────────────────────────────────────────┐[0m
|
|
2
|
+
[0m│10│Value ┌──────┐│[0m
|
|
3
|
+
[0m│ │ │[31mTestDS││[0m
|
|
4
|
+
[0m│ │ [31m••└──────┘│[0m
|
|
5
|
+
[0m│ │ [31m•••• │[0m
|
|
6
|
+
[0m│ │ [31m•••• │[0m
|
|
7
|
+
[0m│ │ [31m•••• │[0m
|
|
8
|
+
[0m│ │ [31m•••• │[0m
|
|
9
|
+
[0m│ │ [31m•••• │[0m
|
|
10
|
+
[0m│ │ [31m•••• │[0m
|
|
11
|
+
[0m│ │ [31m••• │[0m
|
|
12
|
+
[0m│ │ [31m•••• │[0m
|
|
13
|
+
[0m│ │ [31m•••• │[0m
|
|
14
|
+
[0m│ │ [31m•••• │[0m
|
|
15
|
+
[0m│ │ [31m•••• │[0m
|
|
16
|
+
[0m│ │ [31m•••• │[0m
|
|
17
|
+
[0m│ │ [31m•••• │[0m
|
|
18
|
+
[0m│ │ [31m•••• │[0m
|
|
19
|
+
[0m│ │ [31m•••• │[0m
|
|
20
|
+
[0m│ │ [31m•••• │[0m
|
|
21
|
+
[0m│0 │[31m•• Time│[0m
|
|
22
|
+
[0m│ └───────────────────────────────────────────────────────────────────────────│[0m
|
|
23
|
+
[0m│ 0 10│[0m
|
|
24
|
+
[0m└──────────────────────────────────────────────────────────────────────────────┘[0m
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
┌Test Chart────────────────────────────────────────────────────────────────────┐
|
|
2
|
+
│10│Value ┌──────┐│
|
|
3
|
+
│ │ │TestDS││
|
|
4
|
+
│ │ ••└──────┘│
|
|
5
|
+
│ │ •••• │
|
|
6
|
+
│ │ •••• │
|
|
7
|
+
│ │ •••• │
|
|
8
|
+
│ │ •••• │
|
|
9
|
+
│ │ •••• │
|
|
10
|
+
│ │ •••• │
|
|
11
|
+
│ │ ••• │
|
|
12
|
+
│ │ •••• │
|
|
13
|
+
│ │ •••• │
|
|
14
|
+
│ │ •••• │
|
|
15
|
+
│ │ •••• │
|
|
16
|
+
│ │ •••• │
|
|
17
|
+
│ │ •••• │
|
|
18
|
+
│ │ •••• │
|
|
19
|
+
│ │ •••• │
|
|
20
|
+
│ │ •••• │
|
|
21
|
+
│0 │•• Time│
|
|
22
|
+
│ └───────────────────────────────────────────────────────────────────────────│
|
|
23
|
+
│ 0 10│
|
|
24
|
+
└──────────────────────────────────────────────────────────────────────────────┘
|