ratatui_ruby 0.1.0 → 0.3.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.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +52 -0
  3. data/.builds/ruby-3.3.yml +52 -0
  4. data/.builds/ruby-3.4.yml +52 -0
  5. data/.builds/ruby-4.0.0.yml +53 -0
  6. data/.pre-commit-config.yaml +9 -2
  7. data/AGENTS.md +53 -5
  8. data/CHANGELOG.md +51 -1
  9. data/README.md +38 -18
  10. data/REUSE.toml +5 -0
  11. data/Rakefile +3 -100
  12. data/{docs → doc}/contributors/index.md +2 -1
  13. data/doc/custom.css +8 -0
  14. data/doc/images/examples-calendar_demo.rb.png +0 -0
  15. data/doc/images/examples-chart_demo.rb.png +0 -0
  16. data/doc/images/examples-custom_widget.rb.png +0 -0
  17. data/doc/images/examples-list_styles.rb.png +0 -0
  18. data/doc/images/examples-popup_demo.rb.gif +0 -0
  19. data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
  20. data/doc/images/examples-scroll_text.rb.png +0 -0
  21. data/doc/images/examples-stock_ticker.rb.png +0 -0
  22. data/doc/images/examples-table_select.rb.png +0 -0
  23. data/{docs → doc}/index.md +1 -1
  24. data/{docs → doc}/quickstart.md +81 -11
  25. data/examples/analytics.rb +2 -1
  26. data/examples/calendar_demo.rb +55 -0
  27. data/examples/chart_demo.rb +84 -0
  28. data/examples/custom_widget.rb +43 -0
  29. data/examples/list_styles.rb +66 -0
  30. data/examples/login_form.rb +2 -1
  31. data/examples/popup_demo.rb +105 -0
  32. data/examples/quickstart_dsl.rb +30 -0
  33. data/examples/quickstart_lifecycle.rb +40 -0
  34. data/examples/readme_usage.rb +21 -0
  35. data/examples/scroll_text.rb +74 -0
  36. data/examples/stock_ticker.rb +13 -5
  37. data/examples/system_monitor.rb +2 -1
  38. data/examples/table_select.rb +70 -0
  39. data/examples/test_calendar_demo.rb +66 -0
  40. data/examples/test_list_styles.rb +61 -0
  41. data/examples/test_popup_demo.rb +62 -0
  42. data/examples/test_scroll_text.rb +130 -0
  43. data/examples/test_table_select.rb +37 -0
  44. data/ext/ratatui_ruby/.cargo/config.toml +5 -0
  45. data/ext/ratatui_ruby/Cargo.lock +260 -50
  46. data/ext/ratatui_ruby/Cargo.toml +5 -4
  47. data/ext/ratatui_ruby/extconf.rb +1 -1
  48. data/ext/ratatui_ruby/src/buffer.rs +54 -0
  49. data/ext/ratatui_ruby/src/events.rs +115 -107
  50. data/ext/ratatui_ruby/src/lib.rs +15 -6
  51. data/ext/ratatui_ruby/src/rendering.rs +18 -1
  52. data/ext/ratatui_ruby/src/style.rs +2 -1
  53. data/ext/ratatui_ruby/src/terminal.rs +27 -24
  54. data/ext/ratatui_ruby/src/widgets/calendar.rs +82 -0
  55. data/ext/ratatui_ruby/src/widgets/canvas.rs +1 -2
  56. data/ext/ratatui_ruby/src/widgets/center.rs +0 -2
  57. data/ext/ratatui_ruby/src/widgets/chart.rs +260 -0
  58. data/ext/ratatui_ruby/src/widgets/clear.rs +37 -0
  59. data/ext/ratatui_ruby/src/widgets/cursor.rs +1 -1
  60. data/ext/ratatui_ruby/src/widgets/layout.rs +2 -1
  61. data/ext/ratatui_ruby/src/widgets/list.rs +44 -5
  62. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -1
  63. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  64. data/ext/ratatui_ruby/src/widgets/paragraph.rs +10 -0
  65. data/ext/ratatui_ruby/src/widgets/table.rs +25 -6
  66. data/ext/ratatui_ruby/src/widgets/tabs.rs +2 -1
  67. data/lib/ratatui_ruby/dsl.rb +64 -0
  68. data/lib/ratatui_ruby/schema/calendar.rb +26 -0
  69. data/lib/ratatui_ruby/schema/chart.rb +81 -0
  70. data/lib/ratatui_ruby/schema/clear.rb +83 -0
  71. data/lib/ratatui_ruby/schema/list.rb +8 -2
  72. data/lib/ratatui_ruby/schema/paragraph.rb +7 -4
  73. data/lib/ratatui_ruby/schema/rect.rb +24 -0
  74. data/lib/ratatui_ruby/schema/table.rb +8 -2
  75. data/lib/ratatui_ruby/version.rb +1 -1
  76. data/lib/ratatui_ruby.rb +24 -2
  77. data/mise.toml +8 -0
  78. data/sig/ratatui_ruby/buffer.rbs +11 -0
  79. data/sig/ratatui_ruby/schema/calendar.rbs +13 -0
  80. data/sig/ratatui_ruby/schema/{line_chart.rbs → chart.rbs} +20 -1
  81. data/sig/ratatui_ruby/schema/list.rbs +4 -1
  82. data/sig/ratatui_ruby/schema/rect.rbs +14 -0
  83. data/tasks/bump/cargo_lockfile.rb +19 -0
  84. data/tasks/bump/changelog.rb +37 -0
  85. data/tasks/bump/comparison_links.rb +41 -0
  86. data/tasks/bump/header.rb +30 -0
  87. data/tasks/bump/history.rb +30 -0
  88. data/tasks/bump/manifest.rb +31 -0
  89. data/tasks/bump/ruby_gem.rb +35 -0
  90. data/tasks/bump/sem_ver.rb +34 -0
  91. data/tasks/bump/unreleased_section.rb +38 -0
  92. data/tasks/bump.rake +49 -0
  93. data/tasks/doc.rake +25 -0
  94. data/tasks/extension.rake +12 -0
  95. data/tasks/lint.rake +49 -0
  96. data/tasks/rdoc_config.rb +15 -0
  97. data/tasks/resources/build.yml.erb +65 -0
  98. data/tasks/resources/index.html.erb +38 -0
  99. data/tasks/resources/rubies.yml +7 -0
  100. data/tasks/sourcehut.rake +38 -0
  101. data/tasks/test.rake +31 -0
  102. data/tasks/website/index_page.rb +28 -0
  103. data/tasks/website/version.rb +117 -0
  104. data/tasks/website/version_menu.rb +68 -0
  105. data/tasks/website/versioned_documentation.rb +49 -0
  106. data/tasks/website/website.rb +53 -0
  107. data/tasks/website.rake +26 -0
  108. metadata +119 -28
  109. data/.build.yml +0 -34
  110. data/.ruby-version +0 -1
  111. data/CODE_OF_CONDUCT.md +0 -30
  112. data/CONTRIBUTING.md +0 -40
  113. data/docs/images/examples-stock_ticker.rb.png +0 -0
  114. data/ext/ratatui_ruby/src/widgets/linechart.rs +0 -154
  115. data/lib/ratatui_ruby/schema/line_chart.rb +0 -41
  116. /data/{docs → doc}/application_testing.md +0 -0
  117. /data/{docs → doc}/contributors/design/ruby_frontend.md +0 -0
  118. /data/{docs → doc}/contributors/design/rust_backend.md +0 -0
  119. /data/{docs → doc}/contributors/design.md +0 -0
  120. /data/{docs → doc}/images/examples-analytics.rb.png +0 -0
  121. /data/{docs → doc}/images/examples-box_demo.rb.png +0 -0
  122. /data/{docs → doc}/images/examples-dashboard.rb.png +0 -0
  123. /data/{docs → doc}/images/examples-login_form.rb.png +0 -0
  124. /data/{docs → doc}/images/examples-map_demo.rb.png +0 -0
  125. /data/{docs → doc}/images/examples-mouse_events.rb.png +0 -0
  126. /data/{docs → doc}/images/examples-scrollbar_demo.rb.png +0 -0
  127. /data/{docs → doc}/images/examples-system_monitor.rb.png +0 -0
@@ -0,0 +1,30 @@
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
+ # History manages the versioned history of the changelog.
7
+ class History
8
+ # Extracts the history section from the given content, between unreleased and links.
9
+ def self.parse(content, header_length, unreleased_length, links_text)
10
+ start = header_length + unreleased_length
11
+ text = content[start...content.index(links_text)].strip + "\n"
12
+ new(text)
13
+ end
14
+
15
+ # Creates a new History from the given content.
16
+ def initialize(content)
17
+ @content = content.dup
18
+ end
19
+
20
+ # Adds a new versioned section to the history.
21
+ def add(section)
22
+ @content = "#{section}\n\n#{@content}".strip + "\n"
23
+ nil
24
+ end
25
+
26
+ # Returns the current state of the history as a string.
27
+ def to_s
28
+ @content
29
+ end
30
+ end
@@ -0,0 +1,31 @@
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
+ # Manifests hold a copy of the version number and should be changed manually.
7
+ # Use Regexp lookarounds in `pattern` to match the version number.
8
+ class Manifest < Data.define(:path, :pattern, :primary)
9
+ def read
10
+ File.read(path)
11
+ end
12
+
13
+ def initialize(path:, pattern:, primary: false)
14
+ super
15
+ end
16
+
17
+ def version
18
+ content = read
19
+ match = content.match(pattern)
20
+ raise "Version missing in manifest #{path}" unless match
21
+
22
+ SemVer.parse(match[0])
23
+ end
24
+
25
+ def write(version)
26
+ return unless File.exist?(path)
27
+
28
+ new_content = read.gsub(pattern, version.to_s)
29
+ File.write(path, new_content)
30
+ end
31
+ end
@@ -0,0 +1,35 @@
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 RubyGem
7
+ def initialize(manifests:, lockfile:, changelog:)
8
+ raise ArgumentError, "Must have exactly one primary manifest" unless manifests.count(&:primary) == 1
9
+ @manifests = manifests
10
+ @lockfile = lockfile
11
+ @changelog = changelog
12
+ end
13
+
14
+ def version
15
+ @manifests.find(&:primary).version
16
+ end
17
+
18
+ def bump(segment)
19
+ target = version.next(segment)
20
+
21
+ puts "Bumping #{segment}: #{version} -> #{target}"
22
+ @changelog.release(target)
23
+ @manifests.each { |manifest| manifest.write(target) }
24
+ @lockfile.refresh
25
+ end
26
+
27
+ def set(version_string)
28
+ target = SemVer.parse(version_string)
29
+
30
+ puts "Setting version: #{version} -> #{target}"
31
+ @changelog.release(target)
32
+ @manifests.each { |manifest| manifest.write(target) }
33
+ @lockfile.refresh
34
+ end
35
+ end
@@ -0,0 +1,34 @@
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
+ # See https://semver.org/spec/v2.0.0.html
7
+ class SemVer
8
+ SEGMENTS = [:major, :minor, :patch].freeze
9
+
10
+ def self.parse(string)
11
+ require "rubygems"
12
+ segments = Gem::Version.new(string).segments.fill(0, 3).first(3)
13
+ new(segments)
14
+ end
15
+
16
+ def initialize(segments)
17
+ @segments = segments
18
+ end
19
+
20
+ def next(segment)
21
+ index = SEGMENTS.index(segment)
22
+ raise ArgumentError, "Invalid segment: #{segment}" unless index
23
+
24
+ new_segments = @segments.dup
25
+ new_segments[index] += 1
26
+ new_segments.fill(0, (index + 1)..2)
27
+
28
+ SemVer.new(new_segments)
29
+ end
30
+
31
+ def to_s
32
+ @segments.join(".")
33
+ end
34
+ end
@@ -0,0 +1,38 @@
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 "date"
7
+
8
+ # UnreleasedSection manages the [Unreleased] section of the changelog.
9
+ class UnreleasedSection
10
+ PATTERN = /^(## \[Unreleased\].*?)(?=## \[\d)/m
11
+
12
+ # Extracts the unreleased section from the given content.
13
+ def self.parse(content)
14
+ match = content.match(PATTERN)
15
+ new(match[1].strip) if match
16
+ end
17
+
18
+ # Creates a new UnreleasedSection from the given unreleased content.
19
+ def initialize(content)
20
+ @content = content.dup
21
+ end
22
+
23
+ # Returns the unreleased content as a versioned section.
24
+ def as_version(new_version)
25
+ date = Date.today.iso8601
26
+ @content.sub(/^## \[Unreleased\]/, "## [#{new_version}] - #{date}")
27
+ end
28
+
29
+ # Returns a fresh unreleased section.
30
+ def self.fresh
31
+ new("## [Unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n### Removed")
32
+ end
33
+
34
+ # Returns the current state of the section as a string.
35
+ def to_s
36
+ @content
37
+ end
38
+ end
data/tasks/bump.rake ADDED
@@ -0,0 +1,49 @@
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 "rubygems"
7
+
8
+ require_relative "bump/sem_ver"
9
+ require_relative "bump/manifest"
10
+ require_relative "bump/cargo_lockfile"
11
+ require_relative "bump/ruby_gem"
12
+ require_relative "bump/changelog"
13
+
14
+ namespace :bump do
15
+ ratatuiRuby = RubyGem.new(
16
+ manifests: [
17
+ Manifest.new(
18
+ path: "lib/ratatui_ruby/version.rb",
19
+ pattern: /(?<=VERSION = ")[^"]+(?=")/,
20
+ primary: true
21
+ ),
22
+ Manifest.new(
23
+ path: "ext/ratatui_ruby/Cargo.toml",
24
+ pattern: /(?<=^version = ")[^"]+(?=")/,
25
+ primary: false
26
+ ),
27
+ ],
28
+ lockfile: CargoLockfile.new(
29
+ path: "ext/ratatui_ruby/Cargo.lock",
30
+ dir: "ext/ratatui_ruby",
31
+ name: "ratatui_ruby"
32
+ ),
33
+ changelog: Changelog.new
34
+ )
35
+
36
+ SemVer::SEGMENTS.each do |segment|
37
+ desc "Bump #{segment} version"
38
+ task segment do
39
+ ratatuiRuby.bump(segment)
40
+ Rake::Task["sourcehut"].invoke
41
+ end
42
+ end
43
+
44
+ desc "Set exact version (e.g. rake bump:exact[0.1.0])"
45
+ task :exact, [:version] do |_, args|
46
+ ratatuiRuby.set(args[:version])
47
+ Rake::Task["sourcehut"].invoke
48
+ end
49
+ end
data/tasks/doc.rake ADDED
@@ -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
+ require "rdoc/task"
7
+
8
+ require_relative "rdoc_config"
9
+
10
+ RDoc::Task.new do |rdoc|
11
+ rdoc.rdoc_dir = "tmp/rdoc"
12
+ rdoc.main = RDocConfig::MAIN
13
+ rdoc.rdoc_files.include(RDocConfig::RDOC_FILES)
14
+ rdoc.options << "--template-stylesheets=doc/custom.css"
15
+ end
16
+
17
+ task :copy_doc_images do
18
+ if Dir.exist?("doc/images")
19
+ FileUtils.mkdir_p "tmp/rdoc/doc/images"
20
+ FileUtils.cp_r Dir["doc/images/*.png"], "tmp/rdoc/doc/images"
21
+ end
22
+ end
23
+
24
+ Rake::Task[:rdoc].enhance [:copy_doc_images]
25
+ Rake::Task[:rerdoc].enhance [:copy_doc_images]
@@ -0,0 +1,12 @@
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 "rake/extensiontask"
7
+
8
+ spec = Gem::Specification.load("ratatui_ruby.gemspec")
9
+ Rake::ExtensionTask.new("ratatui_ruby", spec) do |ext|
10
+ ext.lib_dir = "lib/ratatui_ruby"
11
+ ext.ext_dir = "ext/ratatui_ruby"
12
+ end
data/tasks/lint.rake ADDED
@@ -0,0 +1,49 @@
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 "rubocop/rake_task"
7
+ require "rubycritic/rake_task"
8
+ require "inch/rake"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ RubyCritic::RakeTask.new do |task|
13
+ task.options = "--no-browser"
14
+ task.paths = FileList.new.include("exe/**/*.rb", "lib/**/*.rb", "sig/**/*.rbs")
15
+ end
16
+
17
+ Inch::Rake::Suggest.new("doc:suggest", "exe/**/*.rb", "lib/**/*.rb", "sig/**/*.rbs") do |suggest|
18
+ suggest.args << ""
19
+ end
20
+
21
+ namespace :cargo do
22
+ desc "Run cargo fmt"
23
+ task :fmt do
24
+ sh "cd ext/ratatui_ruby && cargo fmt --all -- --check"
25
+ end
26
+
27
+ desc "Run cargo clippy"
28
+ task :clippy do
29
+ sh "cd ext/ratatui_ruby && cargo clippy -- -D warnings"
30
+ end
31
+ end
32
+
33
+ namespace :reuse do
34
+ desc "Run the REUSE Tool to confirm REUSE compliance"
35
+ task :lint do
36
+ sh "reuse lint"
37
+ end
38
+ end
39
+ task(:reuse) { Rake::Task["reuse:lint"].invoke }
40
+
41
+ namespace :lint do
42
+ task docs: %w[rubycritic rdoc:coverage reuse:lint]
43
+ task code: %w[rubocop rubycritic cargo:fmt cargo:clippy cargo:test]
44
+ task licenses: %w[reuse:lint]
45
+ task all: %w[docs code licenses]
46
+ end
47
+
48
+ desc "Run all lint tasks"
49
+ task(:lint) { Rake::Task["lint:all"].invoke }
@@ -0,0 +1,15 @@
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
+ module RDocConfig
7
+ RDOC_FILES = %w[
8
+ **/*.md
9
+ **/*.rdoc
10
+ lib/**/*.rb
11
+ exe/**/*
12
+ ].freeze
13
+
14
+ MAIN = "README.md"
15
+ end
@@ -0,0 +1,65 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ image: alpine/edge
5
+ packages:
6
+ - bash
7
+ - build-base
8
+ - curl
9
+ - openssl-dev
10
+ - yaml-dev
11
+ - zlib-dev
12
+ - readline-dev
13
+ - gdbm-dev
14
+ - ncurses-dev
15
+ - libffi-dev
16
+ - clang-dev
17
+ - git
18
+ artifacts:
19
+ - ratatui_ruby/pkg/<%= gem_filename %>
20
+ sources:
21
+ - https://git.sr.ht/~kerrick/ratatui_ruby
22
+ tasks:
23
+ - setup: |
24
+ curl https://mise.jdx.dev/install.sh | sh
25
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.buildenv
26
+ echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
27
+ . ~/.buildenv
28
+ export RUSTFLAGS="-C target-feature=-crt-static"
29
+ export CI="true"
30
+ cd ratatui_ruby
31
+ sed -i 's/ruby = .*/ruby = "<%= ruby_version %>"/' mise.toml
32
+ mise install
33
+ mise x -- pip install reuse
34
+ mise x -- gem install bundler:<%= bundler_version %>
35
+ mise reshim
36
+ mise x -- bundle config set --local frozen 'true'
37
+ mise x -- bundle install
38
+ <% if ruby_version.start_with?("4.0") -%>
39
+ # We allow this to fail so we can see logs without stopping the CI pipeline
40
+ mise x -- bundle exec rake compile || echo "⚠️ Compilation failed (Allowed Failure)"
41
+ <% else -%>
42
+ mise x -- bundle exec rake compile
43
+ <% end -%>
44
+ - test: |
45
+ . ~/.buildenv
46
+ cd ratatui_ruby
47
+ echo "Testing Ruby <%= ruby_version %>"
48
+ <% if ruby_version.start_with?("4.0") -%>
49
+ mise x -- bundle exec rake test || true
50
+ <% else -%>
51
+ mise x -- bundle exec rake test
52
+ <% end -%>
53
+ - lint: |
54
+ . ~/.buildenv
55
+ cd ratatui_ruby
56
+ echo "Linting Ruby <%= ruby_version %>"
57
+ <% if ruby_version.start_with?("4.0") -%>
58
+ mise x -- bundle exec rake lint || true
59
+ <% else -%>
60
+ mise x -- bundle exec rake lint
61
+ <% end -%>
62
+ - package: |
63
+ . ~/.buildenv
64
+ cd ratatui_ruby
65
+ mise x -- bundle exec rake build
@@ -0,0 +1,38 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= project_name %> documentation</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <style>
7
+ :root { color-scheme: light dark; }
8
+ body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; line-height: 1.5; color: light-dark(#111, #eee); background: light-dark(#fff, #111); }
9
+ h1 { border-bottom: 2px solid light-dark(#eee, #333); padding-bottom: 0.5rem; }
10
+ ul { list-style: none; padding: 0; }
11
+ li { margin: 0.5rem 0; border: 1px solid light-dark(#ddd, #444); border-radius: 4px; }
12
+ a { display: block; padding: 1rem; text-decoration: none; color: light-dark(#0055aa, #44aaff); font-weight: bold; }
13
+ a:hover { background: light-dark(#f5f5f5, #222); }
14
+ .meta { font-weight: normal; color: light-dark(#666, #aaa); font-size: 0.9em; float: right; }
15
+ </style>
16
+ </head>
17
+ <body>
18
+ <h1><%= project_name %> documentation</h1>
19
+ <ul>
20
+ <% versions.each do |version| %>
21
+ <li>
22
+ <a href='<%= version.slug %>/index.html'>
23
+ <%= version.name %>
24
+ <span class='meta'>
25
+ <% if version.latest? %>
26
+ Latest
27
+ <% elsif version.edge? %>
28
+ Edge
29
+ <% else %>
30
+ Historical
31
+ <% end %>
32
+ </span>
33
+ </a>
34
+ </li>
35
+ <% end %>
36
+ </ul>
37
+ </body>
38
+ </html>
@@ -0,0 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ - "3.2"
5
+ - "3.3"
6
+ - "3.4"
7
+ - "4.0.0"
@@ -0,0 +1,38 @@
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
+ desc "Generate SourceHut build manifests from template"
7
+ task :sourcehut do
8
+ require "erb"
9
+ require "yaml"
10
+
11
+ spec = Gem::Specification.load("ratatui_ruby.gemspec")
12
+
13
+ # Read version directly from file to ensure we get the latest version
14
+ # even if it was just bumped in the same Rake execution
15
+ version_content = File.read("lib/ratatui_ruby/version.rb")
16
+ version = version_content.match(/VERSION = "(.+?)"/)[1]
17
+
18
+ gem_filename = "#{spec.name}-#{version}.gem"
19
+
20
+ rubies = YAML.load_file("tasks/resources/rubies.yml")
21
+
22
+ bundler_version = File.read("Gemfile.lock").match(/BUNDLED WITH\n\s+([\d.]+)/)[1]
23
+
24
+ template = File.read("tasks/resources/build.yml.erb")
25
+ erb = ERB.new(template, trim_mode: "-")
26
+
27
+ FileUtils.mkdir_p ".builds"
28
+
29
+ # Remove old generated files to ensure a clean state
30
+ Dir.glob(".builds/*.yml").each { |f| File.delete(f) }
31
+
32
+ rubies.each do |ruby_version|
33
+ filename = ".builds/ruby-#{ruby_version}.yml"
34
+ puts "Generating #{filename}..."
35
+ content = erb.result_with_hash(ruby_version:, gem_filename:, bundler_version:)
36
+ File.write(filename, content)
37
+ end
38
+ end
data/tasks/test.rake ADDED
@@ -0,0 +1,31 @@
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 "minitest/test_task"
7
+
8
+ namespace :cargo do
9
+ desc "Run cargo tests"
10
+ task :test do
11
+ sh "cd ext/ratatui_ruby && cargo test"
12
+ end
13
+ end
14
+
15
+ # Clear the default test task created by Minitest::TestTask if it exists
16
+ Rake::Task["test"].clear if Rake::Task.task_defined?("test")
17
+
18
+ desc "Run all tests (Ruby and Rust)"
19
+ task test: %w[test:ruby test:rust]
20
+
21
+ namespace :test do
22
+ desc "Run Rust tests"
23
+ task :rust do
24
+ Rake::Task["cargo:test"].invoke
25
+ end
26
+
27
+ # Create a specific Minitest task for Ruby tests
28
+ Minitest::TestTask.create(:ruby) do |t|
29
+ t.test_globs = ["test/**/test_*.rb", "examples/**/test_*.rb"]
30
+ end
31
+ end
@@ -0,0 +1,28 @@
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 "erb"
7
+
8
+ class IndexPage
9
+ def initialize(versions)
10
+ @versions = versions
11
+
12
+ latest_version = @versions.find { |v| v.is_a?(Tagged) }
13
+ latest_version.is_latest = true if latest_version
14
+ end
15
+
16
+ def publish_to(path, project_name:)
17
+ puts "Generating index page..."
18
+
19
+ template_path = File.expand_path("../resources/index.html.erb", __dir__)
20
+ template = File.read(template_path)
21
+
22
+ versions = @versions
23
+ # project_name is used in the ERB
24
+ html_content = ERB.new(template).result(binding)
25
+
26
+ File.write(path, html_content)
27
+ end
28
+ end
@@ -0,0 +1,117 @@
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 "rubygems"
7
+ require "fileutils"
8
+
9
+ class Version
10
+ def self.all
11
+ tags = `git tag`.split.grep(/^v\d/)
12
+ sorted_versions = tags.map { |t| Tagged.new(t) }
13
+ .sort_by { |v| v.semver }
14
+ .reverse
15
+
16
+ [Edge.new] + sorted_versions
17
+ end
18
+
19
+ def slug
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def name
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def type
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def checkout(globs:, &block)
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def latest?
36
+ false
37
+ end
38
+
39
+ def edge?
40
+ false
41
+ end
42
+ end
43
+
44
+ class Edge < Version
45
+ def slug
46
+ "main"
47
+ end
48
+
49
+ def name
50
+ "main"
51
+ end
52
+
53
+ def type
54
+ :edge
55
+ end
56
+
57
+ def edge?
58
+ true
59
+ end
60
+
61
+ def checkout(globs:, &block)
62
+ Dir.mktmpdir do |path|
63
+ # Use git ls-files for accurate source list
64
+ files = `git ls-files`.split("\n").select do |f|
65
+ globs.any? { |glob| File.fnmatch(glob, f, File::FNM_PATHNAME) }
66
+ end
67
+
68
+ files.each do |file|
69
+ dest = File.join(path, file)
70
+ next unless File.exist?(file) # Skip files that are in the index but deleted in the working tree
71
+ FileUtils.mkdir_p(File.dirname(dest))
72
+ FileUtils.cp(file, dest)
73
+ end
74
+
75
+ yield path
76
+ end
77
+ end
78
+ end
79
+
80
+ class Tagged < Version
81
+ attr_reader :tag
82
+
83
+ def initialize(tag)
84
+ @tag = tag
85
+ end
86
+
87
+ def slug
88
+ @tag
89
+ end
90
+
91
+ def name
92
+ @tag
93
+ end
94
+
95
+ def type
96
+ :version
97
+ end
98
+
99
+ def semver
100
+ Gem::Version.new(@tag.sub(/^v/, ""))
101
+ end
102
+
103
+ attr_accessor :is_latest
104
+
105
+ def latest?
106
+ @is_latest
107
+ end
108
+
109
+ def checkout(globs:, &block)
110
+ Dir.mktmpdir do |path|
111
+ system("git archive #{@tag} | tar -x -C #{path}")
112
+ # We could enforce globs here too, but git archive is usually sufficient.
113
+ FileUtils.rm_rf("#{path}/ext")
114
+ yield path
115
+ end
116
+ end
117
+ end