ratatui_ruby 1.0.0 → 1.0.1
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/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/lib/ratatui_ruby/version.rb +1 -1
- metadata +1 -232
- data/.builds/ruby-3.2.yml +0 -54
- data/.builds/ruby-3.3.yml +0 -54
- data/.builds/ruby-3.4.yml +0 -54
- data/.builds/ruby-4.0.0.yml +0 -54
- data/.pre-commit-config.yaml +0 -16
- data/.rubocop.yml +0 -10
- data/AGENTS.md +0 -146
- data/CHANGELOG.md +0 -710
- data/README.md +0 -187
- data/README.rdoc +0 -302
- data/Rakefile +0 -11
- data/Steepfile +0 -49
- data/doc/concepts/application_architecture.md +0 -321
- data/doc/concepts/application_testing.md +0 -193
- data/doc/concepts/async.md +0 -190
- data/doc/concepts/custom_widgets.md +0 -247
- data/doc/concepts/debugging.md +0 -401
- data/doc/concepts/event_handling.md +0 -162
- data/doc/concepts/interactive_design.md +0 -146
- data/doc/contributors/auditing/parity.md +0 -239
- data/doc/contributors/design/ruby_frontend.md +0 -420
- data/doc/contributors/design/rust_backend.md +0 -422
- data/doc/contributors/design.md +0 -11
- data/doc/contributors/developing_examples.md +0 -400
- data/doc/contributors/documentation_style.md +0 -121
- data/doc/contributors/index.md +0 -21
- data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -375
- data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -206
- data/doc/contributors/todo/align/terminal.md +0 -647
- data/doc/contributors/todo/future_work.md +0 -169
- data/doc/contributors/upstream_requests/tab_rects.md +0 -173
- data/doc/contributors/upstream_requests/title_rects.md +0 -132
- data/doc/custom.css +0 -22
- data/doc/getting_started/quickstart.md +0 -291
- data/doc/getting_started/why.md +0 -93
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_cli_rich_moments.gif +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_debugging_showcase.gif +0 -0
- data/doc/images/app_debugging_showcase.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_stateful_interaction.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.png +0 -0
- data/doc/images/widget_block.png +0 -0
- data/doc/images/widget_box.png +0 -0
- data/doc/images/widget_calendar.png +0 -0
- data/doc/images/widget_canvas.png +0 -0
- data/doc/images/widget_cell.png +0 -0
- data/doc/images/widget_center.png +0 -0
- data/doc/images/widget_chart.png +0 -0
- data/doc/images/widget_gauge.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge.png +0 -0
- data/doc/images/widget_list.png +0 -0
- data/doc/images/widget_map.png +0 -0
- data/doc/images/widget_overlay.png +0 -0
- data/doc/images/widget_popup.png +0 -0
- data/doc/images/widget_ratatui_logo.png +0 -0
- data/doc/images/widget_ratatui_mascot.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.png +0 -0
- data/doc/images/widget_sparkline.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table.png +0 -0
- data/doc/images/widget_tabs.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/index.md +0 -39
- data/doc/troubleshooting/async.md +0 -4
- data/doc/troubleshooting/terminal_limitations.md +0 -131
- data/doc/troubleshooting/tui_output.md +0 -197
- data/examples/app_all_events/README.md +0 -114
- data/examples/app_all_events/app.rb +0 -98
- data/examples/app_all_events/model/app_model.rb +0 -159
- data/examples/app_all_events/model/event_color_cycle.rb +0 -43
- data/examples/app_all_events/model/event_entry.rb +0 -94
- data/examples/app_all_events/model/msg.rb +0 -39
- data/examples/app_all_events/model/timestamp.rb +0 -56
- data/examples/app_all_events/update.rb +0 -75
- data/examples/app_all_events/view/app_view.rb +0 -80
- data/examples/app_all_events/view/controls_view.rb +0 -54
- data/examples/app_all_events/view/counts_view.rb +0 -61
- data/examples/app_all_events/view/live_view.rb +0 -72
- data/examples/app_all_events/view/log_view.rb +0 -57
- data/examples/app_all_events/view.rb +0 -9
- data/examples/app_cli_rich_moments/README.md +0 -81
- data/examples/app_cli_rich_moments/app.rb +0 -189
- data/examples/app_color_picker/README.md +0 -156
- data/examples/app_color_picker/app.rb +0 -76
- data/examples/app_color_picker/clipboard.rb +0 -86
- data/examples/app_color_picker/color.rb +0 -193
- data/examples/app_color_picker/controls.rb +0 -92
- data/examples/app_color_picker/copy_dialog.rb +0 -168
- data/examples/app_color_picker/export_pane.rb +0 -128
- data/examples/app_color_picker/harmony.rb +0 -58
- data/examples/app_color_picker/input.rb +0 -176
- data/examples/app_color_picker/main_container.rb +0 -180
- data/examples/app_color_picker/palette.rb +0 -111
- data/examples/app_debugging_showcase/README.md +0 -119
- data/examples/app_debugging_showcase/app.rb +0 -318
- data/examples/app_login_form/README.md +0 -58
- data/examples/app_login_form/app.rb +0 -109
- data/examples/app_stateful_interaction/README.md +0 -35
- data/examples/app_stateful_interaction/app.rb +0 -328
- data/examples/timeout_demo.rb +0 -45
- data/examples/verify_quickstart_dsl/README.md +0 -55
- data/examples/verify_quickstart_dsl/app.rb +0 -49
- data/examples/verify_quickstart_layout/README.md +0 -77
- data/examples/verify_quickstart_layout/app.rb +0 -73
- data/examples/verify_quickstart_lifecycle/README.md +0 -68
- data/examples/verify_quickstart_lifecycle/app.rb +0 -62
- data/examples/verify_readme_usage/README.md +0 -49
- data/examples/verify_readme_usage/app.rb +0 -42
- data/examples/verify_website_managed/README.md +0 -48
- data/examples/verify_website_managed/app.rb +0 -36
- data/examples/verify_website_menu/README.md +0 -60
- data/examples/verify_website_menu/app.rb +0 -84
- data/examples/verify_website_spinner/README.md +0 -44
- data/examples/verify_website_spinner/app.rb +0 -34
- data/examples/widget_barchart/README.md +0 -58
- data/examples/widget_barchart/app.rb +0 -240
- data/examples/widget_block/README.md +0 -44
- data/examples/widget_block/app.rb +0 -258
- data/examples/widget_box/README.md +0 -54
- data/examples/widget_box/app.rb +0 -255
- data/examples/widget_calendar/README.md +0 -48
- data/examples/widget_calendar/app.rb +0 -115
- data/examples/widget_canvas/README.md +0 -31
- data/examples/widget_canvas/app.rb +0 -130
- data/examples/widget_cell/README.md +0 -45
- data/examples/widget_cell/app.rb +0 -112
- data/examples/widget_center/README.md +0 -33
- data/examples/widget_center/app.rb +0 -118
- data/examples/widget_chart/README.md +0 -50
- data/examples/widget_chart/app.rb +0 -220
- data/examples/widget_gauge/README.md +0 -50
- data/examples/widget_gauge/app.rb +0 -229
- data/examples/widget_layout_split/README.md +0 -53
- data/examples/widget_layout_split/app.rb +0 -260
- data/examples/widget_line_gauge/README.md +0 -50
- data/examples/widget_line_gauge/app.rb +0 -219
- data/examples/widget_list/README.md +0 -58
- data/examples/widget_list/app.rb +0 -384
- data/examples/widget_map/README.md +0 -48
- data/examples/widget_map/app.rb +0 -95
- data/examples/widget_overlay/README.md +0 -45
- data/examples/widget_overlay/app.rb +0 -250
- data/examples/widget_popup/README.md +0 -45
- data/examples/widget_popup/app.rb +0 -106
- data/examples/widget_ratatui_logo/README.md +0 -43
- data/examples/widget_ratatui_logo/app.rb +0 -104
- data/examples/widget_ratatui_mascot/README.md +0 -43
- data/examples/widget_ratatui_mascot/app.rb +0 -95
- data/examples/widget_rect/README.md +0 -53
- data/examples/widget_rect/app.rb +0 -222
- data/examples/widget_render/README.md +0 -46
- data/examples/widget_render/app.rb +0 -186
- data/examples/widget_render/app.rbs +0 -41
- data/examples/widget_rich_text/README.md +0 -44
- data/examples/widget_rich_text/app.rb +0 -193
- data/examples/widget_scroll_text/README.md +0 -46
- data/examples/widget_scroll_text/app.rb +0 -109
- data/examples/widget_scrollbar/README.md +0 -46
- data/examples/widget_scrollbar/app.rb +0 -155
- data/examples/widget_sparkline/README.md +0 -51
- data/examples/widget_sparkline/app.rb +0 -277
- data/examples/widget_style_colors/README.md +0 -43
- data/examples/widget_style_colors/app.rb +0 -83
- data/examples/widget_table/README.md +0 -57
- data/examples/widget_table/app.rb +0 -279
- data/examples/widget_tabs/README.md +0 -50
- data/examples/widget_tabs/app.rb +0 -183
- data/examples/widget_text_width/README.md +0 -44
- data/examples/widget_text_width/app.rb +0 -117
- data/migrate_to_buffer.rb +0 -145
- data/mise.toml +0 -8
- data/tasks/autodoc/examples.rb +0 -87
- data/tasks/autodoc/member.rb +0 -58
- data/tasks/autodoc/name.rb +0 -21
- data/tasks/autodoc.rake +0 -21
- data/tasks/bump/cargo_lockfile.rb +0 -21
- data/tasks/bump/changelog.rb +0 -47
- data/tasks/bump/header.rb +0 -32
- data/tasks/bump/history.rb +0 -32
- data/tasks/bump/links.rb +0 -69
- data/tasks/bump/manifest.rb +0 -33
- data/tasks/bump/ruby_gem.rb +0 -49
- data/tasks/bump/sem_ver.rb +0 -40
- data/tasks/bump/unreleased_section.rb +0 -56
- data/tasks/bump.rake +0 -51
- data/tasks/doc.rake +0 -887
- data/tasks/example_viewer.html.erb +0 -172
- data/tasks/extension.rake +0 -14
- data/tasks/license/headers_md.rb +0 -223
- data/tasks/license/headers_rb.rb +0 -210
- data/tasks/license/license_utils.rb +0 -130
- data/tasks/license/snippets_md.rb +0 -315
- data/tasks/license/snippets_rdoc.rb +0 -150
- data/tasks/license.rake +0 -91
- data/tasks/lint.rake +0 -170
- data/tasks/rdoc_config.rb +0 -29
- data/tasks/resources/build.yml.erb +0 -60
- data/tasks/resources/index.html.erb +0 -141
- data/tasks/resources/rubies.yml +0 -7
- data/tasks/sourcehut.rake +0 -110
- data/tasks/steep.rake +0 -11
- data/tasks/terminal_preview/app_screenshot.rb +0 -45
- data/tasks/terminal_preview/crash_report.rb +0 -54
- data/tasks/terminal_preview/example_app.rb +0 -27
- data/tasks/terminal_preview/launcher_script.rb +0 -48
- data/tasks/terminal_preview/preview_collection.rb +0 -60
- data/tasks/terminal_preview/preview_timing.rb +0 -24
- data/tasks/terminal_preview/safety_confirmation.rb +0 -58
- data/tasks/terminal_preview/saved_screenshot.rb +0 -56
- data/tasks/terminal_preview/system_appearance.rb +0 -13
- data/tasks/terminal_preview/terminal_window.rb +0 -138
- data/tasks/terminal_preview/window_id.rb +0 -16
- data/tasks/terminal_preview.rake +0 -30
- data/tasks/test.rake +0 -33
- data/tasks/website/index_page.rb +0 -30
- data/tasks/website/version.rb +0 -127
- data/tasks/website/version_menu.rb +0 -68
- data/tasks/website/versioned_documentation.rb +0 -83
- data/tasks/website/website.rb +0 -53
- data/tasks/website.rake +0 -28
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require "fileutils"
|
|
9
|
-
require "tmpdir"
|
|
10
|
-
|
|
11
|
-
class LauncherScript < Data.define(:app_path, :repo_root)
|
|
12
|
-
def initialize(app_path:, repo_root:)
|
|
13
|
-
super
|
|
14
|
-
write
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def run
|
|
18
|
-
yield self
|
|
19
|
-
ensure
|
|
20
|
-
cleanup
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def path
|
|
24
|
-
File.join(Dir.tmpdir, "preview_launcher.sh")
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def pid_file
|
|
28
|
-
File.join(Dir.tmpdir, "preview_launcher.pid")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private def cleanup
|
|
32
|
-
FileUtils.rm_f(path)
|
|
33
|
-
FileUtils.rm_f(pid_file)
|
|
34
|
-
rescue Errno::ENOENT
|
|
35
|
-
# Already deleted
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
private def write
|
|
39
|
-
File.open(path, "w") do |f|
|
|
40
|
-
f.puts "#!/bin/zsh"
|
|
41
|
-
f.puts "cd '#{repo_root}'"
|
|
42
|
-
f.puts "clear"
|
|
43
|
-
f.puts "echo $$ > '#{pid_file}'"
|
|
44
|
-
f.puts "exec bundle exec ruby '#{app_path}'"
|
|
45
|
-
end
|
|
46
|
-
FileUtils.chmod(0o755, path)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require "fileutils"
|
|
9
|
-
require_relative "example_app"
|
|
10
|
-
require_relative "app_screenshot"
|
|
11
|
-
require_relative "crash_report"
|
|
12
|
-
require_relative "preview_timing"
|
|
13
|
-
require_relative "safety_confirmation"
|
|
14
|
-
require_relative "saved_screenshot"
|
|
15
|
-
|
|
16
|
-
class PreviewCollection
|
|
17
|
-
def initialize(output_dir)
|
|
18
|
-
@output_dir = output_dir
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def generate
|
|
22
|
-
abort "Error: This task requires macOS." unless RUBY_PLATFORM.match?(/darwin/)
|
|
23
|
-
|
|
24
|
-
apps = ExampleApp.all
|
|
25
|
-
stale_count = count_stale_apps(apps)
|
|
26
|
-
|
|
27
|
-
if stale_count.zero?
|
|
28
|
-
puts "\n✨ All #{apps.count} screenshots are up to date."
|
|
29
|
-
return
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
SafetyConfirmation.new(stale_count, apps.count).request
|
|
33
|
-
|
|
34
|
-
puts "\nHere we go!"
|
|
35
|
-
failures = apps.count { |app| !capture_app(app) }
|
|
36
|
-
|
|
37
|
-
if failures.zero?
|
|
38
|
-
puts "\n✨ All captures complete. Check doc/images/."
|
|
39
|
-
else
|
|
40
|
-
abort "\n❌ #{failures} capture(s) failed."
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
private def count_stale_apps(apps)
|
|
45
|
-
apps.count { |app| SavedScreenshot.for(app, @output_dir).stale? }
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
private def capture_app(app)
|
|
49
|
-
saved = SavedScreenshot.for(app, @output_dir)
|
|
50
|
-
|
|
51
|
-
if saved.stale?
|
|
52
|
-
success = AppScreenshot.new(app, saved.path).capture
|
|
53
|
-
sleep PreviewTiming.between_captures
|
|
54
|
-
success
|
|
55
|
-
else
|
|
56
|
-
puts " ⏭️ #{app} (unchanged)"
|
|
57
|
-
true
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
class PreviewTiming
|
|
9
|
-
def self.window_startup
|
|
10
|
-
1.5
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def self.between_captures
|
|
14
|
-
0.2
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def self.close_delay
|
|
18
|
-
1.0
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def self.total
|
|
22
|
-
window_startup + close_delay + between_captures
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require_relative "preview_timing"
|
|
9
|
-
require_relative "system_appearance"
|
|
10
|
-
|
|
11
|
-
class SafetyConfirmation
|
|
12
|
-
def initialize(stale_count, total_count)
|
|
13
|
-
@stale_count = stale_count
|
|
14
|
-
@total_count = total_count
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def request
|
|
18
|
-
print_warning
|
|
19
|
-
wait_for_user
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private def print_warning
|
|
23
|
-
unchanged_count = @total_count - @stale_count
|
|
24
|
-
puts "\n#{'=' * 60}"
|
|
25
|
-
puts " 📸 NATIVE TERMINAL CAPTURE 📸"
|
|
26
|
-
puts "=" * 60
|
|
27
|
-
puts "This task will:"
|
|
28
|
-
puts " 1. Take control of your mouse/keyboard focus"
|
|
29
|
-
puts " 2. Rapidly open and close Terminal windows"
|
|
30
|
-
puts " 3. Capture #{@stale_count} screenshots (#{unchanged_count} unchanged)"
|
|
31
|
-
puts
|
|
32
|
-
puts "Before starting, be sure Terminal.app has the following permissions"
|
|
33
|
-
puts "in System Settings.app -> Privacy & Security:"
|
|
34
|
-
puts " - Screen & System Audio Recording"
|
|
35
|
-
puts " - Automation -> System Events"
|
|
36
|
-
puts
|
|
37
|
-
puts "⚠️ PLEASE DO NOT TOUCH YOUR COMPUTER WHILE THIS RUNS."
|
|
38
|
-
min_time = (@stale_count * PreviewTiming.total).to_i
|
|
39
|
-
max_time = (@stale_count * (PreviewTiming.total + PreviewTiming.close_delay)).to_i
|
|
40
|
-
puts " (Estimated time: #{min_time}-#{max_time} seconds)"
|
|
41
|
-
puts
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
private def wait_for_user
|
|
45
|
-
loop do
|
|
46
|
-
print "Continue? [Y/n]: "
|
|
47
|
-
response = $stdin.gets.strip.downcase
|
|
48
|
-
|
|
49
|
-
if response.empty? || response == "y"
|
|
50
|
-
return if SystemAppearance.dark?
|
|
51
|
-
puts "⚠️ Dark Mode is not enabled. Please enable it in System Settings or Control Center before proceeding."
|
|
52
|
-
puts
|
|
53
|
-
elsif response == "n"
|
|
54
|
-
abort "Cancelled."
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require "time"
|
|
9
|
-
|
|
10
|
-
class SavedScreenshot < Data.define(:app, :path)
|
|
11
|
-
def self.for(app, output_dir)
|
|
12
|
-
new(app, File.join(output_dir, app.screenshot_filename))
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def stale?
|
|
16
|
-
return true unless valid?
|
|
17
|
-
|
|
18
|
-
app_last_modified > screenshot_last_commit_time
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private def valid?
|
|
22
|
-
# File must exist and have content (not 0 bytes)
|
|
23
|
-
File.size?(path) || false
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
private def app_last_modified
|
|
27
|
-
# If the file has staged or unstaged changes, it's definitely stale
|
|
28
|
-
return Time.now.to_i if changed?
|
|
29
|
-
|
|
30
|
-
# Otherwise, compare against the last git commit time
|
|
31
|
-
app_last_commit_time
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private def changed?
|
|
35
|
-
system("git diff HEAD --quiet #{app.app_path} 2>/dev/null")
|
|
36
|
-
!$?.success?
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
private def app_last_commit_time
|
|
40
|
-
output = `git log -1 --format=%cI "#{app.app_path}" 2>/dev/null`.strip
|
|
41
|
-
return 0 if output.empty?
|
|
42
|
-
|
|
43
|
-
Time.iso8601(output).to_i
|
|
44
|
-
rescue
|
|
45
|
-
0
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
private def screenshot_last_commit_time
|
|
49
|
-
output = `git log -1 --format=%cI "#{path}" 2>/dev/null`.strip
|
|
50
|
-
return Time.now.to_i if output.empty?
|
|
51
|
-
|
|
52
|
-
Time.iso8601(output).to_i
|
|
53
|
-
rescue
|
|
54
|
-
Time.now.to_i
|
|
55
|
-
end
|
|
56
|
-
end
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
class SystemAppearance
|
|
9
|
-
def self.dark?
|
|
10
|
-
result = `osascript -e 'tell application "System Events" to tell appearance preferences to get dark mode'`.strip
|
|
11
|
-
result == "true"
|
|
12
|
-
end
|
|
13
|
-
end
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require_relative "window_id"
|
|
9
|
-
require_relative "preview_timing"
|
|
10
|
-
|
|
11
|
-
class TerminalWindow
|
|
12
|
-
CTRL_C = "ASCII character 3"
|
|
13
|
-
|
|
14
|
-
def initialize(launcher_script_path, pid_file)
|
|
15
|
-
@launcher_script_path = launcher_script_path
|
|
16
|
-
@pid_file = pid_file
|
|
17
|
-
@window_id = nil
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def open
|
|
21
|
-
setup_script = <<~APPLESCRIPT
|
|
22
|
-
tell application "Terminal"
|
|
23
|
-
set newTab to do script "#{@launcher_script_path}"
|
|
24
|
-
set currentWindow to window 1
|
|
25
|
-
|
|
26
|
-
set number of rows of currentWindow to 24
|
|
27
|
-
set number of columns of currentWindow to 80
|
|
28
|
-
set position of currentWindow to {100, 100}
|
|
29
|
-
set frontmost of currentWindow to true
|
|
30
|
-
|
|
31
|
-
return id of currentWindow
|
|
32
|
-
end tell
|
|
33
|
-
APPLESCRIPT
|
|
34
|
-
|
|
35
|
-
@window_id = WindowID.new(`osascript -e '#{setup_script}'`.strip)
|
|
36
|
-
wait_for_startup
|
|
37
|
-
yield self
|
|
38
|
-
ensure
|
|
39
|
-
close if @window_id
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def window_id
|
|
43
|
-
@window_id
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
private def close
|
|
47
|
-
try_graceful_shutdown
|
|
48
|
-
kill_process if process_still_alive?
|
|
49
|
-
|
|
50
|
-
delay_script = <<~APPLESCRIPT
|
|
51
|
-
tell application "Terminal"
|
|
52
|
-
delay #{PreviewTiming.close_delay}
|
|
53
|
-
|
|
54
|
-
try
|
|
55
|
-
close window id #{@window_id}
|
|
56
|
-
end try
|
|
57
|
-
end tell
|
|
58
|
-
APPLESCRIPT
|
|
59
|
-
|
|
60
|
-
system("osascript", "-e", delay_script, out: File::NULL, err: File::NULL)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
private def wait_for_startup
|
|
64
|
-
sleep PreviewTiming.window_startup
|
|
65
|
-
|
|
66
|
-
unless @window_id.valid?
|
|
67
|
-
raise "Failed to open terminal window"
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
unless process_running?
|
|
71
|
-
error_output = contents
|
|
72
|
-
raise error_output
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
private def try_graceful_shutdown
|
|
77
|
-
shutdown_script = <<~APPLESCRIPT
|
|
78
|
-
tell application "Terminal"
|
|
79
|
-
try
|
|
80
|
-
do script (#{CTRL_C}) in window id #{@window_id}
|
|
81
|
-
end try
|
|
82
|
-
end tell
|
|
83
|
-
APPLESCRIPT
|
|
84
|
-
|
|
85
|
-
system("osascript", "-e", shutdown_script, out: File::NULL, err: File::NULL)
|
|
86
|
-
sleep 0.2
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
private def process_still_alive?
|
|
90
|
-
return false unless @pid_file && File.exist?(@pid_file)
|
|
91
|
-
|
|
92
|
-
pid = File.read(@pid_file).strip.to_i
|
|
93
|
-
Process.kill(0, pid)
|
|
94
|
-
true
|
|
95
|
-
rescue Errno::ESRCH, Errno::ENOENT
|
|
96
|
-
false
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
private def kill_process
|
|
100
|
-
return unless @pid_file && File.exist?(@pid_file)
|
|
101
|
-
|
|
102
|
-
pid = File.read(@pid_file).strip.to_i
|
|
103
|
-
Process.kill("TERM", pid)
|
|
104
|
-
rescue Errno::ESRCH, Errno::ENOENT
|
|
105
|
-
# Process already gone or PID file doesn't exist
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
private def process_running?
|
|
109
|
-
check_script = <<~APPLESCRIPT
|
|
110
|
-
tell application "Terminal"
|
|
111
|
-
try
|
|
112
|
-
set theWindow to window id #{@window_id}
|
|
113
|
-
return busy of theWindow
|
|
114
|
-
on error
|
|
115
|
-
return false
|
|
116
|
-
end try
|
|
117
|
-
end tell
|
|
118
|
-
APPLESCRIPT
|
|
119
|
-
|
|
120
|
-
result = `osascript -e '#{check_script}'`.strip
|
|
121
|
-
result == "true"
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
private def contents
|
|
125
|
-
read_script = <<~APPLESCRIPT
|
|
126
|
-
tell application "Terminal"
|
|
127
|
-
try
|
|
128
|
-
set theWindow to window id #{@window_id}
|
|
129
|
-
return contents of selected tab of theWindow
|
|
130
|
-
on error
|
|
131
|
-
return ""
|
|
132
|
-
end try
|
|
133
|
-
end tell
|
|
134
|
-
APPLESCRIPT
|
|
135
|
-
|
|
136
|
-
`osascript -e '#{read_script}'`
|
|
137
|
-
end
|
|
138
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
class WindowID < Data.define(:value)
|
|
9
|
-
def valid?
|
|
10
|
-
!value.empty? && value.match?(/^\d+$/)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def to_s
|
|
14
|
-
value
|
|
15
|
-
end
|
|
16
|
-
end
|
data/tasks/terminal_preview.rake
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require "fileutils"
|
|
9
|
-
require_relative "terminal_preview/preview_collection"
|
|
10
|
-
require_relative "terminal_preview/example_app"
|
|
11
|
-
|
|
12
|
-
namespace :terminal_preview do
|
|
13
|
-
desc "Generate native PNG screenshots using Terminal.app"
|
|
14
|
-
task :generate do
|
|
15
|
-
img_dir = File.expand_path("../doc/images", __dir__)
|
|
16
|
-
FileUtils.mkdir_p(img_dir)
|
|
17
|
-
|
|
18
|
-
# Create empty placeholder files for any missing images that compile depends on.
|
|
19
|
-
# This prevents Rake from trying to build them as dependencies.
|
|
20
|
-
ExampleApp.all.each do |app|
|
|
21
|
-
image_path = File.join(img_dir, "#{app}.png")
|
|
22
|
-
FileUtils.touch(image_path) unless File.exist?(image_path)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
Rake::Task["compile"].invoke
|
|
26
|
-
|
|
27
|
-
collection = PreviewCollection.new(img_dir)
|
|
28
|
-
collection.generate
|
|
29
|
-
end
|
|
30
|
-
end
|
data/tasks/test.rake
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require "minitest/test_task"
|
|
9
|
-
|
|
10
|
-
namespace :cargo do
|
|
11
|
-
desc "Run cargo tests"
|
|
12
|
-
task :test do
|
|
13
|
-
sh "cd ext/ratatui_ruby && cargo test"
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Clear the default test task created by Minitest::TestTask if it exists
|
|
18
|
-
Rake::Task["test"].clear if Rake::Task.task_defined?("test")
|
|
19
|
-
|
|
20
|
-
desc "Run all tests (Ruby and Rust)"
|
|
21
|
-
task test: %w[compile test:ruby test:rust]
|
|
22
|
-
|
|
23
|
-
namespace :test do
|
|
24
|
-
desc "Run Rust tests"
|
|
25
|
-
task :rust do
|
|
26
|
-
Rake::Task["cargo:test"].invoke
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Create a specific Minitest task for Ruby tests
|
|
30
|
-
Minitest::TestTask.create(:ruby) do |t|
|
|
31
|
-
t.test_globs = ["test/**/*.rb", "examples/**/test_*.rb"]
|
|
32
|
-
end
|
|
33
|
-
end
|
data/tasks/website/index_page.rb
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require "erb"
|
|
9
|
-
|
|
10
|
-
class IndexPage
|
|
11
|
-
def initialize(versions)
|
|
12
|
-
@versions = versions
|
|
13
|
-
|
|
14
|
-
latest_version = @versions.find { |v| v.is_a?(Tagged) }
|
|
15
|
-
latest_version.is_latest = true if latest_version
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def publish_to(path, project_name:)
|
|
19
|
-
puts "Generating index page..."
|
|
20
|
-
|
|
21
|
-
template_path = File.expand_path("../resources/index.html.erb", __dir__)
|
|
22
|
-
template = File.read(template_path)
|
|
23
|
-
|
|
24
|
-
versions = @versions
|
|
25
|
-
# project_name is used in the ERB
|
|
26
|
-
html_content = ERB.new(template).result(binding)
|
|
27
|
-
|
|
28
|
-
File.write(path, html_content)
|
|
29
|
-
end
|
|
30
|
-
end
|
data/tasks/website/version.rb
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
#--
|
|
4
|
-
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
-
#++
|
|
7
|
-
|
|
8
|
-
require "rubygems"
|
|
9
|
-
require "fileutils"
|
|
10
|
-
|
|
11
|
-
class Version
|
|
12
|
-
def self.all
|
|
13
|
-
tags = `git tag`.split.grep(/^v\d/)
|
|
14
|
-
sorted_versions = tags.map { |t| Tagged.new(t) }
|
|
15
|
-
.sort_by(&:semver)
|
|
16
|
-
.reverse
|
|
17
|
-
|
|
18
|
-
# Keep only the latest patch for each minor version
|
|
19
|
-
# e.g., if we have v0.6.0, v0.6.1, v0.6.2, only keep v0.6.2
|
|
20
|
-
latest_per_minor = sorted_versions
|
|
21
|
-
.group_by { |v| v.semver.segments[0..1] } # group by [major, minor]
|
|
22
|
-
.values
|
|
23
|
-
.map(&:first) # take the first (highest patch) from each group
|
|
24
|
-
|
|
25
|
-
[Edge.new] + latest_per_minor
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def slug
|
|
29
|
-
raise NotImplementedError
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def name
|
|
33
|
-
raise NotImplementedError
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def type
|
|
37
|
-
raise NotImplementedError
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def checkout(globs:, &block)
|
|
41
|
-
raise NotImplementedError
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def latest?
|
|
45
|
-
false
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def edge?
|
|
49
|
-
false
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
class Edge < Version
|
|
54
|
-
def slug
|
|
55
|
-
"trunk"
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def name
|
|
59
|
-
"trunk"
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def type
|
|
63
|
-
:edge
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def edge?
|
|
67
|
-
true
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def checkout(globs:, &block)
|
|
71
|
-
Dir.mktmpdir do |path|
|
|
72
|
-
# Use git ls-files for accurate source list
|
|
73
|
-
files = `git ls-files`.split("\n").select do |f|
|
|
74
|
-
globs.any? { |glob| File.fnmatch(glob, f, File::FNM_PATHNAME) }
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
files.each do |file|
|
|
78
|
-
dest = File.join(path, file)
|
|
79
|
-
next unless File.exist?(file) # Skip files that are in the index but deleted in the working tree
|
|
80
|
-
FileUtils.mkdir_p(File.dirname(dest))
|
|
81
|
-
FileUtils.cp(file, dest)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
yield path
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
class Tagged < Version
|
|
90
|
-
attr_reader :tag
|
|
91
|
-
|
|
92
|
-
def initialize(tag)
|
|
93
|
-
@tag = tag
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def slug
|
|
97
|
-
segments = semver.segments
|
|
98
|
-
"v#{segments[0]}.#{segments[1]}"
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def name
|
|
102
|
-
@tag
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def type
|
|
106
|
-
:version
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def semver
|
|
110
|
-
Gem::Version.new(@tag.sub(/^v/, ""))
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
attr_accessor :is_latest
|
|
114
|
-
|
|
115
|
-
def latest?
|
|
116
|
-
@is_latest
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def checkout(globs:, &block)
|
|
120
|
-
Dir.mktmpdir do |path|
|
|
121
|
-
system("git archive #{@tag} | tar -x -C #{path}")
|
|
122
|
-
# We could enforce globs here too, but git archive is usually sufficient.
|
|
123
|
-
FileUtils.rm_rf("#{path}/ext")
|
|
124
|
-
yield path
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|