ratatui_ruby 0.3.1 → 0.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.
- checksums.yaml +4 -4
- data/.builds/ruby-3.2.yml +14 -12
- data/.builds/ruby-3.3.yml +14 -12
- data/.builds/ruby-3.4.yml +14 -12
- data/.builds/ruby-4.0.0.yml +14 -12
- data/AGENTS.md +89 -132
- data/CHANGELOG.md +223 -1
- data/README.md +23 -16
- data/REUSE.toml +20 -0
- data/doc/application_architecture.md +176 -0
- data/doc/application_testing.md +17 -10
- data/doc/contributors/design/ruby_frontend.md +11 -7
- data/doc/contributors/developing_examples.md +261 -0
- data/doc/contributors/documentation_style.md +104 -0
- data/doc/contributors/dwim_dx.md +366 -0
- data/doc/contributors/index.md +2 -0
- data/doc/custom.css +14 -0
- data/doc/event_handling.md +125 -0
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_analytics.png +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_custom_widget.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_map_demo.png +0 -0
- data/doc/images/app_mouse_events.png +0 -0
- data/doc/images/app_table_select.png +0 -0
- data/doc/images/verify_quickstart_dsl.png +0 -0
- data/doc/images/verify_quickstart_layout.png +0 -0
- data/doc/images/verify_quickstart_lifecycle.png +0 -0
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_barchart_demo.png +0 -0
- data/doc/images/widget_block_padding.png +0 -0
- data/doc/images/widget_block_titles.png +0 -0
- data/doc/images/widget_box_demo.png +0 -0
- data/doc/images/widget_calendar_demo.png +0 -0
- data/doc/images/widget_cell_demo.png +0 -0
- data/doc/images/widget_chart_demo.png +0 -0
- data/doc/images/widget_gauge_demo.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge_demo.png +0 -0
- data/doc/images/widget_list_demo.png +0 -0
- data/doc/images/widget_list_styles.png +0 -0
- data/doc/images/widget_popup_demo.png +0 -0
- data/doc/images/widget_ratatui_logo_demo.png +0 -0
- data/doc/images/widget_ratatui_mascot_demo.png +0 -0
- data/doc/images/widget_rect.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_rich_text.png +0 -0
- data/doc/images/widget_scroll_text.png +0 -0
- data/doc/images/widget_scrollbar_demo.png +0 -0
- data/doc/images/widget_sparkline_demo.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table_flex.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/index.md +1 -0
- data/doc/interactive_design.md +116 -0
- data/doc/quickstart.md +186 -84
- data/examples/app_all_events/README.md +81 -0
- data/examples/app_all_events/app.rb +93 -0
- data/examples/app_all_events/model/event_color_cycle.rb +41 -0
- data/examples/app_all_events/model/event_entry.rb +75 -0
- data/examples/app_all_events/model/events.rb +180 -0
- data/examples/app_all_events/model/highlight.rb +57 -0
- data/examples/app_all_events/model/timestamp.rb +54 -0
- data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
- data/examples/app_all_events/view/app_view.rb +78 -0
- data/examples/app_all_events/view/controls_view.rb +50 -0
- data/examples/app_all_events/view/counts_view.rb +55 -0
- data/examples/app_all_events/view/live_view.rb +69 -0
- data/examples/app_all_events/view/log_view.rb +60 -0
- data/{lib/ratatui_ruby/output.rb → examples/app_all_events/view.rb} +1 -1
- data/examples/app_all_events/view_state.rb +42 -0
- data/examples/app_color_picker/README.md +94 -0
- data/examples/app_color_picker/app.rb +112 -0
- data/examples/app_color_picker/clipboard.rb +84 -0
- data/examples/app_color_picker/color.rb +191 -0
- data/examples/app_color_picker/copy_dialog.rb +170 -0
- data/examples/app_color_picker/harmony.rb +56 -0
- data/examples/app_color_picker/input.rb +142 -0
- data/examples/app_color_picker/palette.rb +80 -0
- data/examples/app_color_picker/scene.rb +201 -0
- data/examples/app_login_form/app.rb +108 -0
- data/examples/app_map_demo/app.rb +93 -0
- data/examples/app_table_select/app.rb +201 -0
- data/examples/verify_quickstart_dsl/app.rb +45 -0
- data/examples/verify_quickstart_layout/app.rb +69 -0
- data/examples/verify_quickstart_lifecycle/app.rb +48 -0
- data/examples/verify_readme_usage/app.rb +34 -0
- data/examples/widget_barchart_demo/app.rb +238 -0
- data/examples/widget_block_padding/app.rb +67 -0
- data/examples/widget_block_titles/app.rb +69 -0
- data/examples/widget_box_demo/app.rb +250 -0
- data/examples/widget_calendar_demo/app.rb +109 -0
- data/examples/widget_cell_demo/app.rb +104 -0
- data/examples/widget_chart_demo/app.rb +213 -0
- data/examples/widget_gauge_demo/app.rb +212 -0
- data/examples/widget_layout_split/app.rb +246 -0
- data/examples/widget_line_gauge_demo/app.rb +217 -0
- data/examples/widget_list_demo/app.rb +382 -0
- data/examples/widget_list_styles/app.rb +141 -0
- data/examples/widget_popup_demo/app.rb +104 -0
- data/examples/widget_ratatui_logo_demo/app.rb +103 -0
- data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
- data/examples/widget_rect/app.rb +205 -0
- data/examples/widget_render/app.rb +184 -0
- data/examples/widget_rich_text/app.rb +137 -0
- data/examples/widget_scroll_text/app.rb +108 -0
- data/examples/widget_scrollbar_demo/app.rb +153 -0
- data/examples/widget_sparkline_demo/app.rb +274 -0
- data/examples/widget_style_colors/app.rb +102 -0
- data/examples/widget_table_flex/app.rb +95 -0
- data/examples/widget_tabs_demo/app.rb +167 -0
- data/ext/ratatui_ruby/Cargo.lock +889 -115
- data/ext/ratatui_ruby/Cargo.toml +4 -3
- data/ext/ratatui_ruby/clippy.toml +7 -0
- data/ext/ratatui_ruby/extconf.rb +7 -0
- data/ext/ratatui_ruby/src/events.rs +293 -219
- data/ext/ratatui_ruby/src/frame.rs +115 -0
- data/ext/ratatui_ruby/src/lib.rs +105 -24
- data/ext/ratatui_ruby/src/rendering.rs +94 -10
- data/ext/ratatui_ruby/src/style.rs +357 -93
- data/ext/ratatui_ruby/src/terminal.rs +121 -31
- data/ext/ratatui_ruby/src/text.rs +178 -0
- data/ext/ratatui_ruby/src/widgets/barchart.rs +99 -24
- data/ext/ratatui_ruby/src/widgets/block.rs +32 -3
- data/ext/ratatui_ruby/src/widgets/calendar.rs +45 -44
- data/ext/ratatui_ruby/src/widgets/canvas.rs +44 -9
- data/ext/ratatui_ruby/src/widgets/chart.rs +79 -27
- data/ext/ratatui_ruby/src/widgets/clear.rs +3 -1
- data/ext/ratatui_ruby/src/widgets/gauge.rs +11 -4
- data/ext/ratatui_ruby/src/widgets/layout.rs +223 -15
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +92 -0
- data/ext/ratatui_ruby/src/widgets/list.rs +114 -11
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
- data/ext/ratatui_ruby/src/widgets/overlay.rs +4 -2
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +35 -13
- data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
- data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +51 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +61 -7
- data/ext/ratatui_ruby/src/widgets/sparkline.rs +73 -6
- data/ext/ratatui_ruby/src/widgets/table.rs +177 -64
- data/ext/ratatui_ruby/src/widgets/tabs.rs +105 -5
- data/lib/ratatui_ruby/cell.rb +166 -0
- data/lib/ratatui_ruby/event/focus_gained.rb +49 -0
- data/lib/ratatui_ruby/event/focus_lost.rb +50 -0
- data/lib/ratatui_ruby/event/key.rb +211 -0
- data/lib/ratatui_ruby/event/mouse.rb +124 -0
- data/lib/ratatui_ruby/event/none.rb +43 -0
- data/lib/ratatui_ruby/event/paste.rb +71 -0
- data/lib/ratatui_ruby/event/resize.rb +80 -0
- data/lib/ratatui_ruby/event.rb +131 -0
- data/lib/ratatui_ruby/frame.rb +87 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +45 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +23 -0
- data/lib/ratatui_ruby/schema/bar_chart.rb +226 -17
- data/lib/ratatui_ruby/schema/block.rb +178 -11
- data/lib/ratatui_ruby/schema/calendar.rb +70 -14
- data/lib/ratatui_ruby/schema/canvas.rb +213 -46
- data/lib/ratatui_ruby/schema/center.rb +46 -8
- data/lib/ratatui_ruby/schema/chart.rb +134 -32
- data/lib/ratatui_ruby/schema/clear.rb +22 -53
- data/lib/ratatui_ruby/schema/constraint.rb +72 -12
- data/lib/ratatui_ruby/schema/cursor.rb +23 -5
- data/lib/ratatui_ruby/schema/draw.rb +53 -0
- data/lib/ratatui_ruby/schema/gauge.rb +56 -12
- data/lib/ratatui_ruby/schema/layout.rb +91 -9
- data/lib/ratatui_ruby/schema/line_gauge.rb +78 -0
- data/lib/ratatui_ruby/schema/list.rb +92 -16
- data/lib/ratatui_ruby/schema/overlay.rb +29 -3
- data/lib/ratatui_ruby/schema/paragraph.rb +82 -25
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +29 -0
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +34 -0
- data/lib/ratatui_ruby/schema/rect.rb +59 -10
- data/lib/ratatui_ruby/schema/scrollbar.rb +127 -19
- data/lib/ratatui_ruby/schema/shape/label.rb +66 -0
- data/lib/ratatui_ruby/schema/sparkline.rb +120 -12
- data/lib/ratatui_ruby/schema/style.rb +39 -11
- data/lib/ratatui_ruby/schema/table.rb +109 -18
- data/lib/ratatui_ruby/schema/tabs.rb +71 -10
- data/lib/ratatui_ruby/schema/text.rb +90 -0
- data/lib/ratatui_ruby/session/autodoc.rb +417 -0
- data/lib/ratatui_ruby/session.rb +163 -0
- data/lib/ratatui_ruby/test_helper.rb +322 -13
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +184 -38
- data/sig/examples/app_all_events/app.rbs +11 -0
- data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
- data/sig/examples/app_all_events/model/events.rbs +15 -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 +8 -0
- data/sig/examples/app_all_events/view_state.rbs +15 -0
- data/sig/examples/app_color_picker/app.rbs +12 -0
- data/sig/examples/app_login_form/app.rbs +11 -0
- data/sig/examples/app_map_demo/app.rbs +11 -0
- data/sig/examples/app_table_select/app.rbs +11 -0
- data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
- data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
- data/sig/examples/verify_readme_usage/app.rbs +11 -0
- data/sig/examples/widget_block_padding/app.rbs +11 -0
- data/sig/examples/widget_block_titles/app.rbs +11 -0
- data/sig/examples/widget_box_demo/app.rbs +11 -0
- data/sig/examples/widget_calendar_demo/app.rbs +11 -0
- data/sig/examples/widget_cell_demo/app.rbs +11 -0
- data/sig/examples/widget_chart_demo/app.rbs +11 -0
- data/sig/examples/widget_gauge_demo/app.rbs +11 -0
- data/sig/examples/widget_layout_split/app.rbs +10 -0
- data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
- data/sig/examples/widget_list_demo/app.rbs +12 -0
- data/sig/examples/widget_list_styles/app.rbs +11 -0
- data/sig/examples/widget_popup_demo/app.rbs +11 -0
- data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
- data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
- data/sig/examples/widget_rect/app.rbs +12 -0
- data/sig/examples/widget_render/app.rbs +10 -0
- data/sig/examples/widget_rich_text/app.rbs +11 -0
- data/sig/examples/widget_scroll_text/app.rbs +11 -0
- data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
- data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
- data/sig/examples/widget_style_colors/app.rbs +14 -0
- data/sig/examples/widget_table_flex/app.rbs +11 -0
- data/sig/ratatui_ruby/event.rbs +69 -0
- data/sig/ratatui_ruby/frame.rbs +9 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -3
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +16 -0
- data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +13 -0
- data/sig/ratatui_ruby/schema/bar_chart.rbs +20 -2
- data/sig/ratatui_ruby/schema/block.rbs +5 -4
- data/sig/ratatui_ruby/schema/calendar.rbs +6 -2
- data/sig/ratatui_ruby/schema/canvas.rbs +52 -39
- data/sig/ratatui_ruby/schema/center.rbs +3 -3
- data/sig/ratatui_ruby/schema/chart.rbs +8 -5
- data/sig/ratatui_ruby/schema/constraint.rbs +8 -5
- data/sig/ratatui_ruby/schema/cursor.rbs +1 -1
- data/sig/ratatui_ruby/schema/draw.rbs +27 -0
- data/sig/ratatui_ruby/schema/gauge.rbs +4 -2
- data/sig/ratatui_ruby/schema/layout.rbs +11 -1
- data/sig/ratatui_ruby/schema/line_gauge.rbs +16 -0
- data/sig/ratatui_ruby/schema/list.rbs +5 -1
- data/sig/ratatui_ruby/schema/paragraph.rbs +4 -1
- data/sig/ratatui_ruby/schema/ratatui_logo.rbs +8 -0
- data/sig/ratatui_ruby/{buffer.rbs → schema/ratatui_mascot.rbs} +4 -3
- data/sig/ratatui_ruby/schema/rect.rbs +2 -1
- data/sig/ratatui_ruby/schema/scrollbar.rbs +18 -2
- data/sig/ratatui_ruby/schema/sparkline.rbs +6 -2
- data/sig/ratatui_ruby/schema/table.rbs +8 -1
- data/sig/ratatui_ruby/schema/tabs.rbs +5 -1
- data/sig/ratatui_ruby/schema/text.rbs +22 -0
- data/sig/ratatui_ruby/session.rbs +94 -0
- data/tasks/autodoc/inventory.rb +61 -0
- data/tasks/autodoc/member.rb +56 -0
- data/tasks/autodoc/name.rb +19 -0
- data/tasks/autodoc/notice.rb +26 -0
- data/tasks/autodoc/rbs.rb +38 -0
- data/tasks/autodoc/rdoc.rb +45 -0
- data/tasks/autodoc.rake +47 -0
- data/tasks/bump/history.rb +2 -2
- data/tasks/doc.rake +600 -6
- data/tasks/example_viewer.html.erb +172 -0
- data/tasks/lint.rake +8 -4
- data/tasks/resources/build.yml.erb +13 -11
- data/tasks/resources/index.html.erb +6 -0
- data/tasks/sourcehut.rake +4 -4
- data/tasks/terminal_preview/app_screenshot.rb +33 -0
- data/tasks/terminal_preview/crash_report.rb +52 -0
- data/tasks/terminal_preview/example_app.rb +25 -0
- data/tasks/terminal_preview/launcher_script.rb +46 -0
- data/tasks/terminal_preview/preview_collection.rb +58 -0
- data/tasks/terminal_preview/preview_timing.rb +22 -0
- data/tasks/terminal_preview/safety_confirmation.rb +56 -0
- data/tasks/terminal_preview/saved_screenshot.rb +53 -0
- data/tasks/terminal_preview/system_appearance.rb +11 -0
- data/tasks/terminal_preview/terminal_window.rb +136 -0
- data/tasks/terminal_preview/window_id.rb +14 -0
- data/tasks/terminal_preview.rake +28 -0
- data/tasks/test.rake +2 -2
- data/tasks/website/index_page.rb +3 -3
- data/tasks/website/version.rb +10 -10
- data/tasks/website/version_menu.rb +10 -12
- data/tasks/website/versioned_documentation.rb +49 -17
- data/tasks/website/website.rb +6 -8
- data/tasks/website.rake +4 -4
- metadata +206 -54
- data/LICENSES/BSD-2-Clause.txt +0 -9
- data/doc/images/examples-analytics.rb.png +0 -0
- data/doc/images/examples-box_demo.rb.png +0 -0
- data/doc/images/examples-calendar_demo.rb.png +0 -0
- data/doc/images/examples-chart_demo.rb.png +0 -0
- data/doc/images/examples-custom_widget.rb.png +0 -0
- data/doc/images/examples-dashboard.rb.png +0 -0
- data/doc/images/examples-list_styles.rb.png +0 -0
- data/doc/images/examples-login_form.rb.png +0 -0
- data/doc/images/examples-map_demo.rb.png +0 -0
- data/doc/images/examples-mouse_events.rb.png +0 -0
- data/doc/images/examples-popup_demo.rb.gif +0 -0
- data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
- data/doc/images/examples-scroll_text.rb.png +0 -0
- data/doc/images/examples-scrollbar_demo.rb.png +0 -0
- data/doc/images/examples-stock_ticker.rb.png +0 -0
- data/doc/images/examples-system_monitor.rb.png +0 -0
- data/doc/images/examples-table_select.rb.png +0 -0
- data/examples/analytics.rb +0 -88
- data/examples/box_demo.rb +0 -71
- data/examples/calendar_demo.rb +0 -55
- data/examples/chart_demo.rb +0 -84
- data/examples/custom_widget.rb +0 -43
- data/examples/dashboard.rb +0 -72
- data/examples/list_styles.rb +0 -66
- data/examples/login_form.rb +0 -115
- data/examples/map_demo.rb +0 -58
- data/examples/mouse_events.rb +0 -95
- data/examples/popup_demo.rb +0 -105
- data/examples/quickstart_dsl.rb +0 -30
- data/examples/quickstart_lifecycle.rb +0 -40
- data/examples/readme_usage.rb +0 -21
- data/examples/scroll_text.rb +0 -74
- data/examples/scrollbar_demo.rb +0 -75
- data/examples/stock_ticker.rb +0 -93
- data/examples/system_monitor.rb +0 -94
- data/examples/table_select.rb +0 -70
- data/examples/test_analytics.rb +0 -65
- data/examples/test_box_demo.rb +0 -38
- data/examples/test_calendar_demo.rb +0 -66
- data/examples/test_dashboard.rb +0 -38
- data/examples/test_list_styles.rb +0 -61
- data/examples/test_login_form.rb +0 -63
- data/examples/test_map_demo.rb +0 -100
- data/examples/test_popup_demo.rb +0 -62
- data/examples/test_scroll_text.rb +0 -130
- data/examples/test_stock_ticker.rb +0 -39
- data/examples/test_system_monitor.rb +0 -40
- data/examples/test_table_select.rb +0 -37
- data/ext/ratatui_ruby/src/buffer.rs +0 -54
- data/lib/ratatui_ruby/dsl.rb +0 -64
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<%#
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
%>
|
|
5
|
+
<!DOCTYPE html>
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="UTF-8">
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
10
|
+
<title><%= page_title %> - Example Viewer</title>
|
|
11
|
+
|
|
12
|
+
<script>
|
|
13
|
+
var rdoc_rel_prefix = "<%= doc_root_link.sub('index.html', '') %>";
|
|
14
|
+
var index_rel_prefix = "<%= '../' * (relative_path.split('/').size - 1) %>";
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_navigation.js" defer></script>
|
|
18
|
+
<script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_data.js" defer></script>
|
|
19
|
+
<script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_ranker.js" defer></script>
|
|
20
|
+
<script src="<%= '../' * (relative_path.split('/').size - 1) %>js/search_controller.js" defer></script>
|
|
21
|
+
<script src="<%= '../' * (relative_path.split('/').size - 1) %>js/aliki.js" defer></script>
|
|
22
|
+
|
|
23
|
+
<link href="<%= doc_root_link.sub('index.html', '') %>css/rdoc.css" rel="stylesheet">
|
|
24
|
+
<link href="<%= doc_root_link.sub('index.html', '') %>custom.css" rel="stylesheet">
|
|
25
|
+
</head>
|
|
26
|
+
<body class="file<%= ' has-toc' unless toc_items.empty? %>">
|
|
27
|
+
<%= icons_svg %>
|
|
28
|
+
<header class="top-navbar">
|
|
29
|
+
<div class="navbar-brand">
|
|
30
|
+
Example Viewer
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Desktop search bar -->
|
|
34
|
+
<div class="navbar-search navbar-search-desktop" role="search">
|
|
35
|
+
<form action="#" method="get" accept-charset="utf-8">
|
|
36
|
+
<input id="search-field" role="combobox" aria-label="Search"
|
|
37
|
+
aria-autocomplete="list" aria-controls="search-results-desktop"
|
|
38
|
+
type="text" name="search" placeholder="Search (/) examples..."
|
|
39
|
+
spellcheck="false" autocomplete="off"
|
|
40
|
+
title="Type to search, Up and Down to navigate, Enter to load">
|
|
41
|
+
<ul id="search-results-desktop" aria-label="Search Results"
|
|
42
|
+
aria-busy="false" aria-expanded="false"
|
|
43
|
+
aria-atomic="false" class="initially-hidden search-results"></ul>
|
|
44
|
+
</form>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<!-- Mobile search icon button -->
|
|
48
|
+
<button id="search-toggle" class="navbar-search-mobile" aria-label="Open search" type="button">
|
|
49
|
+
<span aria-hidden="true">🔍</span>
|
|
50
|
+
</button>
|
|
51
|
+
|
|
52
|
+
<button id="theme-toggle" class="theme-toggle" aria-label="Switch to dark mode" type="button" onclick="cycleColorMode()">
|
|
53
|
+
<span class="theme-toggle-icon" aria-hidden="true">🌙</span>
|
|
54
|
+
</button>
|
|
55
|
+
</header>
|
|
56
|
+
|
|
57
|
+
<!-- Search Modal (Mobile) -->
|
|
58
|
+
<div id="search-modal" class="search-modal" hidden aria-modal="true" role="dialog" aria-label="Search">
|
|
59
|
+
<div class="search-modal-backdrop"></div>
|
|
60
|
+
<div class="search-modal-content">
|
|
61
|
+
<div class="search-modal-header">
|
|
62
|
+
<form class="search-modal-form" action="#" method="get" accept-charset="utf-8">
|
|
63
|
+
<span class="search-modal-icon" aria-hidden="true">🔍</span>
|
|
64
|
+
<input id="search-field-mobile" role="combobox" aria-label="Search"
|
|
65
|
+
aria-autocomplete="list" aria-controls="search-results-mobile"
|
|
66
|
+
type="text" name="search" placeholder="Search examples"
|
|
67
|
+
spellcheck="false" autocomplete="off">
|
|
68
|
+
<button type="button" class="search-modal-close" aria-label="Close search" id="search-modal-close">
|
|
69
|
+
<span aria-hidden="true">esc</span>
|
|
70
|
+
</button>
|
|
71
|
+
</form>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="search-modal-body">
|
|
74
|
+
<ul id="search-results-mobile" aria-label="Search Results"
|
|
75
|
+
aria-busy="false" aria-expanded="false"
|
|
76
|
+
aria-atomic="false" class="search-results search-modal-results initially-hidden"></ul>
|
|
77
|
+
<div class="search-modal-empty">
|
|
78
|
+
<p>No recent searches</p>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<nav id="navigation" role="navigation">
|
|
85
|
+
<div id="fileindex-section" class="nav-section">
|
|
86
|
+
<details class="nav-section-collapsible" open>
|
|
87
|
+
<summary class="nav-section-header">
|
|
88
|
+
<span class="nav-section-icon">
|
|
89
|
+
<svg><use href="#icon-file"></use></svg>
|
|
90
|
+
</span>
|
|
91
|
+
<span class="nav-section-title">Examples</span>
|
|
92
|
+
<span class="nav-section-chevron">
|
|
93
|
+
<svg><use href="#icon-chevron"></use></svg>
|
|
94
|
+
</span>
|
|
95
|
+
</summary>
|
|
96
|
+
<ul class="nav-list">
|
|
97
|
+
<li><a href="<%= doc_root_link %>">← Back to Docs</a></li>
|
|
98
|
+
</ul>
|
|
99
|
+
</details>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="nav-section">
|
|
102
|
+
<details class="nav-section-collapsible" open>
|
|
103
|
+
<summary class="nav-section-header">
|
|
104
|
+
<span class="nav-section-icon">
|
|
105
|
+
<svg><use href="#icon-layers"></use></svg>
|
|
106
|
+
</span>
|
|
107
|
+
<span class="nav-section-title">Files</span>
|
|
108
|
+
<span class="nav-section-chevron">
|
|
109
|
+
<svg><use href="#icon-chevron"></use></svg>
|
|
110
|
+
</span>
|
|
111
|
+
</summary>
|
|
112
|
+
<ul class="link-list nav-list">
|
|
113
|
+
<%= render_tree(tree_data, relative_path, current_file_html) %>
|
|
114
|
+
</ul>
|
|
115
|
+
</details>
|
|
116
|
+
</div>
|
|
117
|
+
</nav>
|
|
118
|
+
|
|
119
|
+
<main role="main">
|
|
120
|
+
<div class="breadcrumb">
|
|
121
|
+
<%= breadcrumb_path %>
|
|
122
|
+
</div>
|
|
123
|
+
<%= file_header_html %>
|
|
124
|
+
<div class="content">
|
|
125
|
+
<%= file_content_html %>
|
|
126
|
+
</div>
|
|
127
|
+
</main>
|
|
128
|
+
|
|
129
|
+
<% unless toc_items.empty? %>
|
|
130
|
+
<aside class="table-of-contents" role="complementary" aria-label="Table of Contents">
|
|
131
|
+
<div class="toc-sticky">
|
|
132
|
+
<div class="toc-list">
|
|
133
|
+
<h3>On This Page</h3>
|
|
134
|
+
<%= render_toc(toc_items) %>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</aside>
|
|
138
|
+
<% end %>
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
<script>
|
|
142
|
+
const modes = ['auto', 'light', 'dark'];
|
|
143
|
+
const icons = { auto: '🌓', light: '☀️', dark: '🌙' };
|
|
144
|
+
|
|
145
|
+
function setColorMode(mode) {
|
|
146
|
+
if (mode === 'auto') {
|
|
147
|
+
document.documentElement.removeAttribute('data-theme');
|
|
148
|
+
const systemTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
|
|
149
|
+
document.documentElement.setAttribute('data-theme', systemTheme);
|
|
150
|
+
} else {
|
|
151
|
+
document.documentElement.setAttribute('data-theme', mode);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const icon = icons[mode];
|
|
155
|
+
const toggle = document.getElementById('theme-toggle');
|
|
156
|
+
toggle.querySelector('.theme-toggle-icon').textContent = icon;
|
|
157
|
+
|
|
158
|
+
localStorage.setItem('rdoc-theme', mode);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function cycleColorMode() {
|
|
162
|
+
const current = localStorage.getItem('rdoc-theme') || 'auto';
|
|
163
|
+
const currentIndex = modes.indexOf(current);
|
|
164
|
+
const nextMode = modes[(currentIndex + 1) % modes.length];
|
|
165
|
+
setColorMode(nextMode);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const savedMode = localStorage.getItem('rdoc-theme') || 'auto';
|
|
169
|
+
setColorMode(savedMode);
|
|
170
|
+
</script>
|
|
171
|
+
</body>
|
|
172
|
+
</html>
|
data/tasks/lint.rake
CHANGED
|
@@ -9,9 +9,9 @@ require "inch/rake"
|
|
|
9
9
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
# Run rubycritic in a shell to prevent it from exiting the rake process
|
|
13
|
+
task :rubycritic do
|
|
14
|
+
sh "bundle exec rubycritic --no-browser exe lib sig"
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
Inch::Rake::Suggest.new("doc:suggest", "exe/**/*.rb", "lib/**/*.rb", "sig/**/*.rbs") do |suggest|
|
|
@@ -39,7 +39,11 @@ end
|
|
|
39
39
|
task(:reuse) { Rake::Task["reuse:lint"].invoke }
|
|
40
40
|
|
|
41
41
|
namespace :lint do
|
|
42
|
-
task
|
|
42
|
+
task :safe_rdoc_coverage do
|
|
43
|
+
sh "bundle exec rake rdoc:coverage"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
task docs: %w[autodoc rubycritic safe_rdoc_coverage reuse:lint]
|
|
43
47
|
task code: %w[rubocop rubycritic cargo:fmt cargo:clippy cargo:test]
|
|
44
48
|
task licenses: %w[reuse:lint]
|
|
45
49
|
task all: %w[docs code licenses]
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
2
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
|
-
image:
|
|
4
|
+
image: archlinux
|
|
5
5
|
packages:
|
|
6
6
|
- bash
|
|
7
|
-
-
|
|
7
|
+
- base-devel
|
|
8
8
|
- curl
|
|
9
|
-
- openssl
|
|
10
|
-
-
|
|
11
|
-
- zlib
|
|
12
|
-
- readline
|
|
13
|
-
- gdbm
|
|
14
|
-
- ncurses
|
|
15
|
-
- libffi
|
|
16
|
-
- clang
|
|
9
|
+
- openssl
|
|
10
|
+
- libyaml
|
|
11
|
+
- zlib
|
|
12
|
+
- readline
|
|
13
|
+
- gdbm
|
|
14
|
+
- ncurses
|
|
15
|
+
- libffi
|
|
16
|
+
- clang
|
|
17
17
|
- git
|
|
18
18
|
artifacts:
|
|
19
19
|
- ratatui_ruby/pkg/<%= gem_filename %>
|
|
@@ -24,8 +24,10 @@ tasks:
|
|
|
24
24
|
curl https://mise.jdx.dev/install.sh | sh
|
|
25
25
|
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.buildenv
|
|
26
26
|
echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
|
|
27
|
+
echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
|
|
28
|
+
echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
|
|
29
|
+
echo 'export BINDGEN_EXTRA_CLANG_ARGS="-include stdbool.h"' >> ~/.buildenv
|
|
27
30
|
. ~/.buildenv
|
|
28
|
-
export RUSTFLAGS="-C target-feature=-crt-static"
|
|
29
31
|
export CI="true"
|
|
30
32
|
cd ratatui_ruby
|
|
31
33
|
sed -i 's/ruby = .*/ruby = "<%= ruby_version %>"/' mise.toml
|
data/tasks/sourcehut.rake
CHANGED
|
@@ -9,18 +9,18 @@ task :sourcehut do
|
|
|
9
9
|
require "yaml"
|
|
10
10
|
|
|
11
11
|
spec = Gem::Specification.load("ratatui_ruby.gemspec")
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
# Read version directly from file to ensure we get the latest version
|
|
14
14
|
# even if it was just bumped in the same Rake execution
|
|
15
15
|
version_content = File.read("lib/ratatui_ruby/version.rb")
|
|
16
16
|
version = version_content.match(/VERSION = "(.+?)"/)[1]
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
gem_filename = "#{spec.name}-#{version}.gem"
|
|
19
19
|
|
|
20
20
|
rubies = YAML.load_file("tasks/resources/rubies.yml")
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
bundler_version = File.read("Gemfile.lock").match(/BUNDLED WITH\n\s+([\d.]+)/)[1]
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
template = File.read("tasks/resources/build.yml.erb")
|
|
25
25
|
erb = ERB.new(template, trim_mode: "-")
|
|
26
26
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
require "tmpdir"
|
|
7
|
+
require_relative "launcher_script"
|
|
8
|
+
require_relative "terminal_window"
|
|
9
|
+
require_relative "crash_report"
|
|
10
|
+
|
|
11
|
+
class AppScreenshot < Data.define(:app, :output_path)
|
|
12
|
+
def capture
|
|
13
|
+
print " 📸 #{app}..."
|
|
14
|
+
|
|
15
|
+
LauncherScript.new(app.app_path, Dir.pwd).run do |launcher|
|
|
16
|
+
TerminalWindow.new(launcher.path, launcher.pid_file).open do |window|
|
|
17
|
+
take_snapshot(window.window_id)
|
|
18
|
+
puts " done."
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
rescue => e
|
|
23
|
+
puts " FAILED"
|
|
24
|
+
puts
|
|
25
|
+
puts CrashReport.new(app, e, "Program crashed before screenshot could be taken:")
|
|
26
|
+
puts
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private def take_snapshot(window_id)
|
|
31
|
+
system("screencapture -l #{window_id} -o -x '#{output_path}'")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
class CrashReport < Data.define(:app, :error, :preamble)
|
|
7
|
+
def self.new(app, error, preamble = nil)
|
|
8
|
+
# Allow preamble to be optional while Data.define requires all fields
|
|
9
|
+
super(app:, error:, preamble:)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_s
|
|
13
|
+
output = error.message.strip
|
|
14
|
+
formatted_error = output.split("\n").map { |line| format_line(line) }.join("\n")
|
|
15
|
+
preamble_section = preamble ? <<~PREAMBLE.chomp : ""
|
|
16
|
+
#{box_top}
|
|
17
|
+
#{format_line(preamble)}
|
|
18
|
+
#{box_bottom}
|
|
19
|
+
PREAMBLE
|
|
20
|
+
|
|
21
|
+
<<~TEXT
|
|
22
|
+
#{preamble_section}
|
|
23
|
+
#{border_top(app.to_s)}
|
|
24
|
+
#{formatted_error}
|
|
25
|
+
#{box_bottom}
|
|
26
|
+
TEXT
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private def box_top
|
|
30
|
+
"┌#{'─' * (width - 2)}┐"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private def box_bottom
|
|
34
|
+
"└#{'─' * (width - 2)}┘"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private def border_top(title)
|
|
38
|
+
left = "┌─ #{title} "
|
|
39
|
+
right = "┐"
|
|
40
|
+
dashes = "─" * (width - left.length - right.length)
|
|
41
|
+
left + dashes + right
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private def format_line(line)
|
|
45
|
+
truncated = line[0...(width - 4)]
|
|
46
|
+
"│ #{truncated.ljust(width - 4)} │"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private def width
|
|
50
|
+
80
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
class ExampleApp < Data.define(:directory)
|
|
7
|
+
def self.all
|
|
8
|
+
examples_dir = File.expand_path("../../examples", __dir__)
|
|
9
|
+
Dir.glob("#{examples_dir}/*/app.rb").map do |path|
|
|
10
|
+
new(File.basename(File.dirname(path)))
|
|
11
|
+
end.sort_by(&:directory)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def app_path
|
|
15
|
+
"examples/#{directory}/app.rb"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def screenshot_filename
|
|
19
|
+
"#{directory}.png"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_s
|
|
23
|
+
directory
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
require "fileutils"
|
|
7
|
+
require "tmpdir"
|
|
8
|
+
|
|
9
|
+
class LauncherScript < Data.define(:app_path, :repo_root)
|
|
10
|
+
def initialize(app_path:, repo_root:)
|
|
11
|
+
super
|
|
12
|
+
write
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
yield self
|
|
17
|
+
ensure
|
|
18
|
+
cleanup
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def path
|
|
22
|
+
File.join(Dir.tmpdir, "preview_launcher.sh")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def pid_file
|
|
26
|
+
File.join(Dir.tmpdir, "preview_launcher.pid")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private def cleanup
|
|
30
|
+
FileUtils.rm_f(path)
|
|
31
|
+
FileUtils.rm_f(pid_file)
|
|
32
|
+
rescue Errno::ENOENT
|
|
33
|
+
# Already deleted
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private def write
|
|
37
|
+
File.open(path, "w") do |f|
|
|
38
|
+
f.puts "#!/bin/zsh"
|
|
39
|
+
f.puts "cd '#{repo_root}'"
|
|
40
|
+
f.puts "clear"
|
|
41
|
+
f.puts "echo $$ > '#{pid_file}'"
|
|
42
|
+
f.puts "exec bundle exec ruby '#{app_path}'"
|
|
43
|
+
end
|
|
44
|
+
FileUtils.chmod(0o755, path)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
require "fileutils"
|
|
7
|
+
require_relative "example_app"
|
|
8
|
+
require_relative "app_screenshot"
|
|
9
|
+
require_relative "crash_report"
|
|
10
|
+
require_relative "preview_timing"
|
|
11
|
+
require_relative "safety_confirmation"
|
|
12
|
+
require_relative "saved_screenshot"
|
|
13
|
+
|
|
14
|
+
class PreviewCollection
|
|
15
|
+
def initialize(output_dir)
|
|
16
|
+
@output_dir = output_dir
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def generate
|
|
20
|
+
abort "Error: This task requires macOS." unless RUBY_PLATFORM.match?(/darwin/)
|
|
21
|
+
|
|
22
|
+
apps = ExampleApp.all
|
|
23
|
+
stale_count = count_stale_apps(apps)
|
|
24
|
+
|
|
25
|
+
if stale_count.zero?
|
|
26
|
+
puts "\n✨ All #{apps.count} screenshots are up to date."
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
SafetyConfirmation.new(stale_count, apps.count).request
|
|
31
|
+
|
|
32
|
+
puts "\nHere we go!"
|
|
33
|
+
failures = apps.count { |app| !capture_app(app) }
|
|
34
|
+
|
|
35
|
+
if failures.zero?
|
|
36
|
+
puts "\n✨ All captures complete. Check doc/images/."
|
|
37
|
+
else
|
|
38
|
+
abort "\n❌ #{failures} capture(s) failed."
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private def count_stale_apps(apps)
|
|
43
|
+
apps.count { |app| SavedScreenshot.for(app, @output_dir).stale? }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private def capture_app(app)
|
|
47
|
+
saved = SavedScreenshot.for(app, @output_dir)
|
|
48
|
+
|
|
49
|
+
if saved.stale?
|
|
50
|
+
success = AppScreenshot.new(app, saved.path).capture
|
|
51
|
+
sleep PreviewTiming.between_captures
|
|
52
|
+
success
|
|
53
|
+
else
|
|
54
|
+
puts " ⏭️ #{app} (unchanged)"
|
|
55
|
+
true
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
class PreviewTiming
|
|
7
|
+
def self.window_startup
|
|
8
|
+
1.5
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.between_captures
|
|
12
|
+
0.2
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.close_delay
|
|
16
|
+
1.0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.total
|
|
20
|
+
window_startup + close_delay + between_captures
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
require_relative "preview_timing"
|
|
7
|
+
require_relative "system_appearance"
|
|
8
|
+
|
|
9
|
+
class SafetyConfirmation
|
|
10
|
+
def initialize(stale_count, total_count)
|
|
11
|
+
@stale_count = stale_count
|
|
12
|
+
@total_count = total_count
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def request
|
|
16
|
+
print_warning
|
|
17
|
+
wait_for_user
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private def print_warning
|
|
21
|
+
unchanged_count = @total_count - @stale_count
|
|
22
|
+
puts "\n#{'=' * 60}"
|
|
23
|
+
puts " 📸 NATIVE TERMINAL CAPTURE 📸"
|
|
24
|
+
puts "=" * 60
|
|
25
|
+
puts "This task will:"
|
|
26
|
+
puts " 1. Take control of your mouse/keyboard focus"
|
|
27
|
+
puts " 2. Rapidly open and close Terminal windows"
|
|
28
|
+
puts " 3. Capture #{@stale_count} screenshots (#{unchanged_count} unchanged)"
|
|
29
|
+
puts
|
|
30
|
+
puts "Before starting, be sure Terminal.app has the following permissions"
|
|
31
|
+
puts "in System Settings.app -> Privacy & Security:"
|
|
32
|
+
puts " - Screen & System Audio Recording"
|
|
33
|
+
puts " - Automation -> System Events"
|
|
34
|
+
puts
|
|
35
|
+
puts "⚠️ PLEASE DO NOT TOUCH YOUR COMPUTER WHILE THIS RUNS."
|
|
36
|
+
min_time = (@stale_count * PreviewTiming.total).to_i
|
|
37
|
+
max_time = (@stale_count * (PreviewTiming.total + PreviewTiming.close_delay)).to_i
|
|
38
|
+
puts " (Estimated time: #{min_time}-#{max_time} seconds)"
|
|
39
|
+
puts
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private def wait_for_user
|
|
43
|
+
loop do
|
|
44
|
+
print "Continue? [Y/n]: "
|
|
45
|
+
response = $stdin.gets.strip.downcase
|
|
46
|
+
|
|
47
|
+
if response.empty? || response == "y"
|
|
48
|
+
return if SystemAppearance.dark?
|
|
49
|
+
puts "⚠️ Dark Mode is not enabled. Please enable it in System Settings or Control Center before proceeding."
|
|
50
|
+
puts
|
|
51
|
+
elsif response == "n"
|
|
52
|
+
abort "Cancelled."
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
require "time"
|
|
7
|
+
|
|
8
|
+
class SavedScreenshot < Data.define(:app, :path)
|
|
9
|
+
def self.for(app, output_dir)
|
|
10
|
+
new(app, File.join(output_dir, app.screenshot_filename))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def stale?
|
|
14
|
+
return true unless exists?
|
|
15
|
+
|
|
16
|
+
app_last_modified > screenshot_last_commit_time
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private def exists?
|
|
20
|
+
File.exist?(path)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private def app_last_modified
|
|
24
|
+
# If the file has staged or unstaged changes, it's definitely stale
|
|
25
|
+
return Time.now.to_i if changed?
|
|
26
|
+
|
|
27
|
+
# Otherwise, compare against the last git commit time
|
|
28
|
+
app_last_commit_time
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private def changed?
|
|
32
|
+
system("git diff HEAD --quiet #{app.app_path} 2>/dev/null")
|
|
33
|
+
!$?.success?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private def app_last_commit_time
|
|
37
|
+
output = `git log -1 --format=%cI "#{app.app_path}" 2>/dev/null`.strip
|
|
38
|
+
return 0 if output.empty?
|
|
39
|
+
|
|
40
|
+
Time.iso8601(output).to_i
|
|
41
|
+
rescue
|
|
42
|
+
0
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private def screenshot_last_commit_time
|
|
46
|
+
output = `git log -1 --format=%cI "#{path}" 2>/dev/null`.strip
|
|
47
|
+
return Time.now.to_i if output.empty?
|
|
48
|
+
|
|
49
|
+
Time.iso8601(output).to_i
|
|
50
|
+
rescue
|
|
51
|
+
Time.now.to_i
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
class SystemAppearance
|
|
7
|
+
def self.dark?
|
|
8
|
+
result = `osascript -e 'tell application "System Events" to tell appearance preferences to get dark mode'`.strip
|
|
9
|
+
result == "true"
|
|
10
|
+
end
|
|
11
|
+
end
|