ratatui_ruby-devtools 0.1.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 (90) hide show
  1. checksums.yaml +7 -0
  2. data/.builds/ruby-4.0.yml +38 -0
  3. data/.pre-commit-config.yaml +16 -0
  4. data/.rubocop.yml +8 -0
  5. data/AGENTS.md +72 -0
  6. data/CHANGELOG.md +23 -0
  7. data/LICENSE +661 -0
  8. data/LICENSES/AGPL-3.0-or-later.txt +661 -0
  9. data/LICENSES/CC-BY-SA-4.0.txt +427 -0
  10. data/LICENSES/CC0-1.0.txt +121 -0
  11. data/LICENSES/MIT-0.txt +16 -0
  12. data/LICENSES/MIT.txt +18 -0
  13. data/README.md +199 -0
  14. data/REUSE.toml +18 -0
  15. data/Rakefile +13 -0
  16. data/bin/agent_rake +13 -0
  17. data/bin/announce +13 -0
  18. data/bin/console +14 -0
  19. data/bin/consolidate_md +13 -0
  20. data/bin/hbs +13 -0
  21. data/bin/setup +17 -0
  22. data/doc/contributors/documentation_style.md +121 -0
  23. data/doc/custom.css +22 -0
  24. data/exe/agent_rake +96 -0
  25. data/exe/announce +1120 -0
  26. data/exe/consolidate_md +246 -0
  27. data/exe/hbs +670 -0
  28. data/exe/scaffold +662 -0
  29. data/lib/ratatui_ruby/devtools/tasks/autodoc/examples.rb +133 -0
  30. data/lib/ratatui_ruby/devtools/tasks/autodoc/member.rb +116 -0
  31. data/lib/ratatui_ruby/devtools/tasks/autodoc/name.rb +33 -0
  32. data/lib/ratatui_ruby/devtools/tasks/autodoc.rake +21 -0
  33. data/lib/ratatui_ruby/devtools/tasks/bump/cargo_lockfile.rb +38 -0
  34. data/lib/ratatui_ruby/devtools/tasks/bump/changelog.rb +67 -0
  35. data/lib/ratatui_ruby/devtools/tasks/bump/header.rb +43 -0
  36. data/lib/ratatui_ruby/devtools/tasks/bump/history.rb +50 -0
  37. data/lib/ratatui_ruby/devtools/tasks/bump/links.rb +78 -0
  38. data/lib/ratatui_ruby/devtools/tasks/bump/manifest.rb +63 -0
  39. data/lib/ratatui_ruby/devtools/tasks/bump/ruby_gem.rb +77 -0
  40. data/lib/ratatui_ruby/devtools/tasks/bump/sem_ver.rb +63 -0
  41. data/lib/ratatui_ruby/devtools/tasks/bump/unreleased_section.rb +75 -0
  42. data/lib/ratatui_ruby/devtools/tasks/bump.rake +80 -0
  43. data/lib/ratatui_ruby/devtools/tasks/cargo.rake +47 -0
  44. data/lib/ratatui_ruby/devtools/tasks/doc.rake +887 -0
  45. data/lib/ratatui_ruby/devtools/tasks/example_viewer.html.erb +172 -0
  46. data/lib/ratatui_ruby/devtools/tasks/license/headers_md.rb +276 -0
  47. data/lib/ratatui_ruby/devtools/tasks/license/headers_rb.rb +236 -0
  48. data/lib/ratatui_ruby/devtools/tasks/license/license_utils.rb +143 -0
  49. data/lib/ratatui_ruby/devtools/tasks/license/snippets_md.rb +353 -0
  50. data/lib/ratatui_ruby/devtools/tasks/license/snippets_rdoc.rb +186 -0
  51. data/lib/ratatui_ruby/devtools/tasks/license.rake +91 -0
  52. data/lib/ratatui_ruby/devtools/tasks/lint.rake +84 -0
  53. data/lib/ratatui_ruby/devtools/tasks/rdoc_config.rb +45 -0
  54. data/lib/ratatui_ruby/devtools/tasks/resources/build.yml.erb +54 -0
  55. data/lib/ratatui_ruby/devtools/tasks/resources/rubies.yml +7 -0
  56. data/lib/ratatui_ruby/devtools/tasks/reuse.rake +104 -0
  57. data/lib/ratatui_ruby/devtools/tasks/sourcehut.rake +94 -0
  58. data/lib/ratatui_ruby/devtools/tasks/test.rake +18 -0
  59. data/lib/ratatui_ruby/devtools/templates/.builds/ruby.yml.erb +47 -0
  60. data/lib/ratatui_ruby/devtools/templates/.gitignore.erb +18 -0
  61. data/lib/ratatui_ruby/devtools/templates/.pre-commit-config.yaml.erb +16 -0
  62. data/lib/ratatui_ruby/devtools/templates/.rubocop.yml.erb +8 -0
  63. data/lib/ratatui_ruby/devtools/templates/AGENTS.md.erb +65 -0
  64. data/lib/ratatui_ruby/devtools/templates/CHANGELOG.md.erb +18 -0
  65. data/lib/ratatui_ruby/devtools/templates/Gemfile.erb +32 -0
  66. data/lib/ratatui_ruby/devtools/templates/README.md.erb +127 -0
  67. data/lib/ratatui_ruby/devtools/templates/REUSE.toml.erb +33 -0
  68. data/lib/ratatui_ruby/devtools/templates/Rakefile.erb +29 -0
  69. data/lib/ratatui_ruby/devtools/templates/bin/console.erb +18 -0
  70. data/lib/ratatui_ruby/devtools/templates/bin/setup.erb +24 -0
  71. data/lib/ratatui_ruby/devtools/templates/doc/concepts/application_architecture.md.erb +16 -0
  72. data/lib/ratatui_ruby/devtools/templates/doc/concepts/application_testing.md.erb +49 -0
  73. data/lib/ratatui_ruby/devtools/templates/doc/custom.css.erb +24 -0
  74. data/lib/ratatui_ruby/devtools/templates/doc/getting_started/quickstart.md.erb +56 -0
  75. data/lib/ratatui_ruby/devtools/templates/doc/images/.gitkeep +0 -0
  76. data/lib/ratatui_ruby/devtools/templates/doc/index.md.erb +25 -0
  77. data/lib/ratatui_ruby/devtools/templates/exe/.gitkeep +0 -0
  78. data/lib/ratatui_ruby/devtools/templates/gemspec.erb +58 -0
  79. data/lib/ratatui_ruby/devtools/templates/mise.toml.erb +12 -0
  80. data/lib/ratatui_ruby/devtools/templates/tasks/example_viewer.html.erb +174 -0
  81. data/lib/ratatui_ruby/devtools/templates/tasks/resources/build.yml.erb +62 -0
  82. data/lib/ratatui_ruby/devtools/templates/tasks/resources/index.html.erb +46 -0
  83. data/lib/ratatui_ruby/devtools/templates/tasks/resources/rubies.yml.erb +9 -0
  84. data/lib/ratatui_ruby/devtools/templates/vendor/goodcop/base.yml +1047 -0
  85. data/lib/ratatui_ruby/devtools/version.rb +13 -0
  86. data/lib/ratatui_ruby/devtools.rb +137 -0
  87. data/mise.toml +7 -0
  88. data/sig/ratatui_ruby/devtools.rbs +15 -0
  89. data/vendor/goodcop/base.yml +1047 -0
  90. metadata +252 -0
@@ -0,0 +1,91 @@
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
+ namespace :license do
9
+ namespace :headers do
10
+ desc "Ensure markdown files have correct CC-BY-SA-4.0 headers"
11
+ task :md, [:files] do |_t, args|
12
+ files = args[:files] || ""
13
+ ruby "tasks/license/headers_md.rb #{files}"
14
+ end
15
+
16
+ desc "Ensure Ruby files have correct AGPL-3.0-or-later headers"
17
+ task :rb, [:files] do |_t, args|
18
+ files = args[:files] || ""
19
+ ruby "tasks/license/headers_rb.rb #{files}"
20
+ end
21
+
22
+ desc "Ensure all files have correct license headers"
23
+ task :all do
24
+ Rake::Task["license:headers:md"].invoke
25
+ Rake::Task["license:headers:rb"].invoke
26
+ end
27
+ end
28
+
29
+ namespace :snippets do
30
+ desc "Add MIT-0 SPDX snippet headers to markdown fenced code blocks"
31
+ task :md, [:files] do |_t, args|
32
+ files = args[:files] || ""
33
+ ruby "tasks/license/snippets_md.rb #{files}"
34
+ end
35
+
36
+ desc "Add MIT-0 SPDX snippet headers to RDoc code examples in Ruby files"
37
+ task :rdoc, [:files] do |_t, args|
38
+ files = args[:files] || "lib/"
39
+ ruby "tasks/license/snippets_rdoc.rb #{files}"
40
+ end
41
+
42
+ desc "Add MIT-0 SPDX snippet headers to all code examples"
43
+ task :all do
44
+ Rake::Task["license:snippets:md"].invoke
45
+ Rake::Task["license:snippets:rdoc"].invoke
46
+ end
47
+ end
48
+
49
+ desc "Run all license tasks (headers + snippets)"
50
+ task all: ["headers:all", "snippets:all"]
51
+
52
+ desc "Run license tasks on changed files only (staged + unstaged)"
53
+ task :new do
54
+ # Get changed .md and .rb files (staged and unstaged)
55
+ changed_md = `git diff --name-only --diff-filter=ACMR HEAD -- '*.md' 2>/dev/null`.split("\n")
56
+ staged_md = `git diff --name-only --cached --diff-filter=ACMR -- '*.md' 2>/dev/null`.split("\n")
57
+ changed_rb = `git diff --name-only --diff-filter=ACMR HEAD -- '*.rb' 2>/dev/null`.split("\n")
58
+ staged_rb = `git diff --name-only --cached --diff-filter=ACMR -- '*.rb' 2>/dev/null`.split("\n")
59
+
60
+ # Also get untracked new files
61
+ untracked = `git ls-files --others --exclude-standard`.split("\n")
62
+ untracked_md = untracked.select { |f| f.end_with?(".md") }
63
+ untracked_rb = untracked.select { |f| f.end_with?(".rb") }
64
+
65
+ md_files = (changed_md + staged_md + untracked_md).uniq.join(" ")
66
+ rb_files = (changed_rb + staged_rb + untracked_rb).uniq
67
+
68
+ # Filter rb files to only lib/
69
+ lib_rb_files = rb_files.select { |f| f.start_with?("lib/") }.join(" ")
70
+
71
+ if md_files.empty? && lib_rb_files.empty?
72
+ puts "No changed .md or lib/*.rb files to process"
73
+ else
74
+ unless md_files.empty?
75
+ puts "Processing #{md_files.split.count} changed .md file(s)..."
76
+ Rake::Task["license:headers:md"].invoke(md_files)
77
+ Rake::Task["license:headers:md"].reenable
78
+ Rake::Task["license:snippets:md"].invoke(md_files)
79
+ Rake::Task["license:snippets:md"].reenable
80
+ end
81
+
82
+ unless lib_rb_files.empty?
83
+ puts "Processing #{lib_rb_files.split.count} changed lib/*.rb file(s)..."
84
+ Rake::Task["license:headers:rb"].invoke(lib_rb_files)
85
+ Rake::Task["license:headers:rb"].reenable
86
+ Rake::Task["license:snippets:rdoc"].invoke(lib_rb_files)
87
+ Rake::Task["license:snippets:rdoc"].reenable
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,84 @@
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 "rubocop/rake_task"
9
+ require "rubycritic/rake_task"
10
+ require "inch/rake"
11
+
12
+ RuboCop::RakeTask.new
13
+
14
+ # Run rubycritic in a shell to prevent it from exiting the rake process
15
+ task :rubycritic do
16
+ paths = []
17
+ paths << "exe" if Dir.exist?("exe")
18
+ paths << "lib" if Dir.exist?("lib")
19
+ paths << "sig" if Dir.exist?("sig")
20
+ sh "bundle exec rubycritic --no-browser #{paths.join(' ')}" unless paths.empty?
21
+ end
22
+
23
+ Inch::Rake::Suggest.new("doc:suggest", "exe/**/*.rb", "lib/**/*.rb", "sig/**/*.rbs") do |suggest|
24
+ suggest.args << ""
25
+ end
26
+
27
+ namespace :lint do
28
+ task :safe_rdoc_coverage do
29
+ if Rake::Task.task_defined?("rdoc:coverage")
30
+ sh "bundle exec rake rdoc:coverage"
31
+ else
32
+ puts "rdoc:coverage task not defined, skipping"
33
+ end
34
+ end
35
+
36
+ # Build dynamic task lists based on what's available
37
+ docs_tasks = %w[rubycritic reuse:lint]
38
+ docs_tasks.unshift("autodoc") if Rake::Task.task_defined?("autodoc")
39
+ docs_tasks << "safe_rdoc_coverage" if Rake::Task.task_defined?("rdoc:coverage")
40
+
41
+ code_tasks = %w[rubocop rubycritic]
42
+ code_tasks += %w[cargo:fmt cargo:clippy cargo:test] if Dir.exist?("ext")
43
+
44
+ task docs: docs_tasks
45
+ task code: code_tasks
46
+ task licenses: %w[reuse:lint]
47
+ task all: %w[docs code licenses]
48
+
49
+ namespace :fix do
50
+ desc "Auto-fix RuboCop offenses (most aggressive)"
51
+ task :rubocop do
52
+ sh "bundle exec rubocop --autocorrect-all"
53
+ end
54
+
55
+ if Dir.exist?("ext")
56
+ desc "Auto-fix Clippy warnings (most aggressive: --fix --allow-dirty --allow-staged)"
57
+ task :clippy do
58
+ ext_dirs = Dir.glob("ext/*").select { |d| File.directory?(d) && File.exist?("#{d}/Cargo.toml") }
59
+ ext_dirs.each do |dir|
60
+ sh "cd #{dir} && cargo clippy --fix --allow-dirty --allow-staged"
61
+ end
62
+ end
63
+ end
64
+
65
+ desc "Add SPDX headers and normalize Ruby file structure"
66
+ task reuse: %w[reuse:fix reuse:normalize_ruby]
67
+
68
+ # Build dynamic fix:all task
69
+ fix_all_tasks = %w[lint:fix:rubocop lint:fix:reuse]
70
+ fix_all_tasks.insert(1, "lint:fix:clippy") if Dir.exist?("ext")
71
+
72
+ desc "Run all auto-fix tasks"
73
+ task all: fix_all_tasks
74
+ end
75
+ end
76
+
77
+ desc "Run all lint auto-fix tasks"
78
+ task("lint:fix") { Rake::Task["lint:fix:all"].invoke }
79
+
80
+ # Aliases for convenience
81
+ task "rubocop:autocorrect_all" => "lint:fix:rubocop"
82
+
83
+ desc "Run all lint tasks"
84
+ task(:lint) { Rake::Task["lint:all"].invoke }
@@ -0,0 +1,45 @@
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
+ # Configuration for RDoc documentation generation.
9
+ #
10
+ # RDoc generates API documentation from source files. Configuring which files
11
+ # to include requires glob patterns. Large files slow generation. Verification
12
+ # examples clutter the output.
13
+ #
14
+ # This module defines the file list, size limits, and main page. Use it to
15
+ # configure RDoc::Task consistently across ecosystem gems.
16
+ module RDocConfig
17
+ # Maximum file size to include in documentation. Files larger than this are
18
+ # skipped to avoid performance issues. 100KB handles most source files while
19
+ # excluding chat logs and similar large artifacts.
20
+ MAX_FILE_SIZE = 100_000
21
+
22
+ # Files to include in RDoc generation.
23
+ #
24
+ # Includes markdown docs, readme files, library source, and executables.
25
+ # Excludes large files and verification examples.
26
+ RDOC_FILES = Dir.glob(%w[
27
+ doc/**/*.md
28
+ examples/**/*.md
29
+ *.md
30
+ *.rdoc
31
+ lib/**/*.rb
32
+ exe/**/*
33
+ ]).reject { |f|
34
+ # Skip large files
35
+ if File.size(f) > MAX_FILE_SIZE
36
+ warn "RDoc: skipping #{f} (#{File.size(f) / 1024}KB > #{MAX_FILE_SIZE / 1024}KB limit)"
37
+ next true
38
+ end
39
+ # Skip verification examples (internal testing, not user-facing)
40
+ f.start_with?("examples/verify_")
41
+ }.freeze
42
+
43
+ # The main page for RDoc output.
44
+ MAIN = "README.md"
45
+ end
@@ -0,0 +1,54 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ image: archlinux
5
+ packages:
6
+ - bash
7
+ - base-devel
8
+ - curl
9
+ - openssl
10
+ - libyaml
11
+ - zlib
12
+ - readline
13
+ - gdbm
14
+ - ncurses
15
+ - libffi
16
+ - clang
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
+ 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
30
+ . ~/.buildenv
31
+ export CI="true"
32
+ cd ratatui_ruby
33
+ sed -i 's/ruby = .*/ruby = "<%= ruby_version %>"/' mise.toml
34
+ mise install
35
+ mise x -- pip install reuse
36
+ mise x -- gem install bundler:<%= bundler_version %>
37
+ mise reshim
38
+ mise x -- bundle config set --local frozen 'true'
39
+ mise x -- bundle install
40
+ mise x -- bundle exec rake compile
41
+ - test: |
42
+ . ~/.buildenv
43
+ cd ratatui_ruby
44
+ echo "Testing Ruby <%= ruby_version %>"
45
+ mise x -- bundle exec rake test
46
+ - lint: |
47
+ . ~/.buildenv
48
+ cd ratatui_ruby
49
+ echo "Linting Ruby <%= ruby_version %>"
50
+ mise x -- bundle exec rake lint
51
+ - package: |
52
+ . ~/.buildenv
53
+ cd ratatui_ruby
54
+ mise x -- bundle exec rake build
@@ -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,104 @@
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
+ namespace :reuse do
9
+ desc "Run the REUSE Tool to confirm REUSE compliance"
10
+ task :lint do
11
+ sh "reuse lint"
12
+ end
13
+
14
+ desc "Add SPDX headers to files missing them (per AGENTS.md standards)"
15
+ task :fix do
16
+ copyright = ENV.fetch("REUSE_COPYRIGHT", "Kerrick Long <me@kerricklong.com>")
17
+
18
+ # Code files: AGPL-3.0-or-later
19
+ code_extensions = %w[rb rs rake gemspec rbs toml yml yaml json lock].freeze
20
+ code_license = ENV.fetch("REUSE_CODE_LICENSE", "AGPL-3.0-or-later")
21
+
22
+ # Documentation files: CC-BY-SA-4.0
23
+ doc_extensions = %w[md txt].freeze
24
+ doc_license = ENV.fetch("REUSE_DOC_LICENSE", "CC-BY-SA-4.0")
25
+
26
+ # Find files missing headers (listed after "no copyright and licensing" message)
27
+ puts "Checking for files missing REUSE headers..."
28
+ output = `reuse lint 2>&1`
29
+ in_missing_section = false
30
+ missing_files = output.lines.filter_map do |line|
31
+ in_missing_section = true if line.include?("no copyright and licensing")
32
+ in_missing_section = false if line.start_with?("# ") && !line.include?("copyright")
33
+ next unless in_missing_section
34
+
35
+ line.match(/^\* (.+)/)&.[](1)
36
+ end
37
+
38
+ if missing_files.empty?
39
+ puts "All files have REUSE headers!"
40
+ else
41
+ missing_files.each do |file|
42
+ ext = File.extname(file).delete(".")
43
+ license = if code_extensions.include?(ext)
44
+ code_license
45
+ elsif doc_extensions.include?(ext)
46
+ doc_license
47
+ else
48
+ puts " Skipping #{file} (unknown extension: .#{ext})"
49
+ next
50
+ end
51
+
52
+ puts " Annotating #{file} with #{license}"
53
+ sh "reuse annotate --license #{license} --copyright '#{copyright}' --skip-existing '#{file}'", verbose: false
54
+ end
55
+ end
56
+ end
57
+
58
+ desc "Normalize Ruby files: frozen_string_literal at top, SPDX in #--/#++ block"
59
+ task :normalize_ruby do
60
+ ruby_extensions = %w[rb rake gemspec].freeze
61
+ ruby_files = Dir.glob("**/*.{#{ruby_extensions.join(',')}}")
62
+ .reject { |f| f.start_with?("vendor/", "tmp/", ".") }
63
+
64
+ fixed_count = 0
65
+ ruby_files.each do |file|
66
+ content = File.read(file)
67
+ original = content.dup
68
+
69
+ # Skip if no SPDX header
70
+ next unless content.match?(/# SPDX-/)
71
+
72
+ # Extract components
73
+ frozen = content.match?(/^# frozen_string_literal: true/)
74
+ spdx_match = content.match(/(# SPDX-FileCopyrightText:[^\n]+\n(?:#[^\n]*\n)*# SPDX-License-Identifier:[^\n]+\n)/m)
75
+ next unless spdx_match
76
+
77
+ spdx_block = spdx_match[1]
78
+
79
+ # Remove existing frozen_string_literal and SPDX block (and any #--/#++)
80
+ cleaned = content
81
+ .sub(/^# frozen_string_literal: true\n+/, "")
82
+ .sub(/^#--\s*\n/, "")
83
+ .sub(spdx_block, "")
84
+ .sub(/^#\+\+\s*\n/, "")
85
+ .sub(/\A\n+/, "") # Remove leading blank lines
86
+
87
+ # Rebuild file in correct order: frozen, blank, #--, SPDX, #++, rest
88
+ new_content = ""
89
+ new_content += "# frozen_string_literal: true\n\n" if frozen
90
+ new_content += "#--\n#{spdx_block}#++\n\n"
91
+ new_content += cleaned.sub(/\A\n+/, "") # Ensure no double blank lines
92
+
93
+ next unless new_content != original
94
+
95
+ File.write(file, new_content)
96
+ puts " Normalized #{file}"
97
+ fixed_count += 1
98
+ end
99
+
100
+ puts fixed_count.zero? ? "All Ruby files properly normalized!" : "Fixed #{fixed_count} files."
101
+ end
102
+ end
103
+
104
+ task(:reuse) { Rake::Task["reuse:lint"].invoke }
@@ -0,0 +1,94 @@
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
+ desc "Generate SourceHut build manifests from template"
9
+ task sourcehut: "sourcehut:build"
10
+
11
+ namespace :sourcehut do
12
+ desc "Build SourceHut manifests"
13
+ task build: "sourcehut:build:manifest"
14
+
15
+ namespace :build do
16
+ desc "Generate SourceHut build manifests from template"
17
+ task :manifest do
18
+ require "erb"
19
+ require "yaml"
20
+
21
+ gem_name = RatatuiRuby::Devtools.gem_name
22
+ gemspec_file = RatatuiRuby::Devtools.gemspec_file
23
+ version_file = RatatuiRuby::Devtools.version_file
24
+
25
+ # Detect if this is a Rust extension gem
26
+ has_rust = File.exist?("ext/#{gem_name}/Cargo.toml") ||
27
+ File.exist?("ext/#{gem_name.tr('-', '_')}/Cargo.toml")
28
+
29
+ spec = Gem::Specification.load(gemspec_file)
30
+
31
+ # Read version directly from file to ensure we get the latest version
32
+ # even if it was just bumped in the same Rake execution
33
+ version_content = File.read(version_file)
34
+ version = version_content.match(/VERSION = "(.+?)"/)[1]
35
+
36
+ gem_filename = "#{spec.name}-#{version}.gem"
37
+
38
+ rubies = YAML.load_file("tasks/resources/rubies.yml")
39
+
40
+ bundler_version = File.read("Gemfile.lock").match(/BUNDLED WITH\n\s+([\d.]+)/)[1]
41
+
42
+ template = File.read("tasks/resources/build.yml.erb")
43
+ erb = ERB.new(template, trim_mode: "-")
44
+
45
+ FileUtils.mkdir_p ".builds"
46
+
47
+ # Remove old generated files to ensure a clean state
48
+ Dir.glob(".builds/*.yml").each { |f| File.delete(f) }
49
+
50
+ rubies.each do |ruby_version|
51
+ filename = ".builds/ruby-#{ruby_version}.yml"
52
+ puts "Generating #{filename}..."
53
+ content = erb.result_with_hash(gem_name:, has_rust:, ruby_version:, gem_filename:, bundler_version:)
54
+ File.write(filename, content)
55
+ end
56
+ end
57
+ end
58
+
59
+ desc "Update stable branch to match release and set as default"
60
+ task :update_stable do
61
+ version_file = RatatuiRuby::Devtools.version_file
62
+
63
+ # Read version to determine tag
64
+ version_content = File.read(version_file)
65
+ version = version_content.match(/VERSION = "(.+?)"/)[1]
66
+ tag_name = "v#{version}"
67
+
68
+ # Verify that the version file matches the actual git tag
69
+ # This prevents updating stable to the wrong version if the release failed
70
+ latest_tag = `git describe --tags --abbrev=0`.strip
71
+ if latest_tag != tag_name
72
+ abort "Fatal: Version mismatch! '#{version_file}' says #{tag_name}, " \
73
+ "but the latest git tag is #{latest_tag}."
74
+ end
75
+
76
+ puts "Updating stable branch to point to #{tag_name}..."
77
+ # Resolve the tag to a commit hash (peel annotated tags)
78
+ # This renders a commit SHA that can be pushed to a branch head
79
+ commit_sha = `git rev-parse #{tag_name}^{}`.strip
80
+
81
+ # Update local stable branch to match
82
+ sh "git branch -f stable #{commit_sha}"
83
+
84
+ # Push the commit to remote stable branch
85
+ # This creates 'stable' if it doesn't exist, or fast-forwards it.
86
+ sh "git push origin #{commit_sha}:stable"
87
+ end
88
+ end
89
+
90
+ if Rake::Task.task_defined?("release")
91
+ Rake::Task["release"].enhance do
92
+ Rake::Task["sourcehut:update_stable"].invoke
93
+ end
94
+ end
@@ -0,0 +1,18 @@
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
+ # Minitest task for pure Ruby gems.
9
+ #
10
+ # Consumer gems need test running. This provides a simple Minitest::TestTask
11
+ # that discovers tests in the standard locations. Gems with Rust extensions
12
+ # should define their own test task that includes cargo:test.
13
+
14
+ require "minitest/test_task"
15
+
16
+ Minitest::TestTask.create(:test) do |t|
17
+ t.test_globs = ["test/**/*_test.rb", "test/**/test_*.rb"]
18
+ end
@@ -0,0 +1,47 @@
1
+ # REUSE-IgnoreStart
2
+ # SPDX-FileCopyrightText: <%= Time.now.year %> <%= copyright_holder %>
3
+ # SPDX-License-Identifier: <%= license %>
4
+ # REUSE-IgnoreEnd
5
+
6
+ image: archlinux
7
+ packages:
8
+ - bash
9
+ - base-devel
10
+ - curl
11
+ - openssl
12
+ - libyaml
13
+ - zlib
14
+ - readline
15
+ - gdbm
16
+ - ncurses
17
+ - libffi
18
+ - git
19
+ sources:
20
+ - https://git.sr.ht/~kerrick/<%= gem_name %>
21
+ tasks:
22
+ - setup: |
23
+ curl https://mise.jdx.dev/install.sh | sh
24
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.buildenv
25
+ echo 'eval "$($HOME/.local/bin/mise activate bash)"' >> ~/.buildenv
26
+ echo 'export LANG="en_US.UTF-8"' >> ~/.buildenv
27
+ echo 'export LC_ALL="en_US.UTF-8"' >> ~/.buildenv
28
+ . ~/.buildenv
29
+ export CI="true"
30
+ cd <%= gem_name %>
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
35
+ mise reshim
36
+ mise x -- bundle config set --local frozen 'true'
37
+ mise x -- bundle install
38
+ - test: |
39
+ . ~/.buildenv
40
+ cd <%= gem_name %>
41
+ echo "Testing Ruby <%= ruby_version %>"
42
+ mise x -- bundle exec rake test
43
+ - lint: |
44
+ . ~/.buildenv
45
+ cd <%= gem_name %>
46
+ echo "Linting Ruby <%= ruby_version %>"
47
+ mise x -- bundle exec rake lint
@@ -0,0 +1,18 @@
1
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ **/.idea/
5
+ **/.DS_Store
6
+ /.bundle/
7
+ /.yardoc/
8
+ /_yardoc/
9
+ /coverage/
10
+ /pkg/
11
+ /spec/reports/
12
+ /tmp/
13
+ <%- if has_rust -%>
14
+ /ext/<%= gem_name %>/target/
15
+ *.bundle
16
+ *.dylib
17
+ *.so
18
+ <%- end -%>
@@ -0,0 +1,16 @@
1
+ # SPDX-FileCopyrightText: 2020 Free Software Foundation Europe e.V.
2
+ # SPDX-License-Identifier: CC0-1.0
3
+ repos:
4
+ - repo: local
5
+ hooks:
6
+ - id: bundle-check
7
+ name: Check Gemfile.lock
8
+ entry: bundle check
9
+ language: system
10
+ files: (Gemfile|Gemfile\.lock|<%= gem_name %>\.gemspec)
11
+ pass_filenames: false
12
+ - id: rake
13
+ name: rake
14
+ entry: bundle exec rake
15
+ language: system
16
+ pass_filenames: false
@@ -0,0 +1,8 @@
1
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ inherit_from:
5
+ - ./vendor/goodcop/base.yml
6
+
7
+ AllCops:
8
+ TargetRubyVersion: "3.2"
@@ -0,0 +1,65 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # AGENTS.md
7
+
8
+ ## Project Identity
9
+
10
+ Project Name: <%= gem_name %>
11
+
12
+ Description: Part of the RatatuiRuby ecosystem.
13
+
14
+ ## 1. Standards
15
+
16
+ ### STRICT REQUIREMENTS
17
+
18
+ - Every file MUST begin with an SPDX-compliant header. Use `<%= license %>` for code; `CC-BY-SA-4.0` for documentation. `reuse annotate` can help you generate the header. **For Ruby files**, wrap SPDX comments in `#--` / `#++` to hide them from RDoc output.
19
+ - Every line of Ruby MUST be covered by tests that would stand up to mutation testing.
20
+ - Tests must be meaningful and verify specific behavior or rendering output; simply verifying that code "doesn't crash" is insufficient and unacceptable.
21
+ - **Pre-commit:** Use `agent_rake` to ensure commit-readiness. See Tools for detailed instructions.
22
+ - **Git Pager:** ALWAYS set `PAGER=cat` for ALL `git` commands (e.g., `PAGER=cat git diff`). This is mandatory.
23
+
24
+ ### Tools
25
+
26
+ - **NEVER** run `bundle exec rake` directly. **NEVER** run `bundle exec ruby -Ilib:test ...` directly.
27
+ - **ALWAYS use `agent_rake`** (provided by the `ratatui_ruby-devtools` gem) for running tests, linting, or checking compilation.
28
+ - **Usage:**
29
+ - Runs default task (compile + test + lint): `agent_rake`
30
+ - Runs specific task: `agent_rake test:ruby` (for example)
31
+ - **Setup:** `bin/setup` must handle Bundler dependencies.
32
+ - **Git:** ALWAYS set `PAGER=cat` with `git`. **THIS IS CRITICAL!**
33
+
34
+ ### Ruby Standards
35
+
36
+ - Use `Data.define` for all value objects (Ruby 3.2+).
37
+ - Define types in `.rbs` files. Don't use `untyped` just because it's easy; be comprehensive and accurate.
38
+ - Every public Ruby class/method must be documented for humans in RDoc.
39
+
40
+ ## 2. Committing
41
+
42
+ - Who commits: DON'T stage (DON'T `git add`) unless explicitly instructed. DON'T commit unless explicitly instructed. DO suggest a commit message when you finish.
43
+ - **Format:**
44
+ - Format: Use [Conventional Commits](https://www.conventionalcommits.org/).
45
+ - Body: Explanation if necessary (wrap at 72 chars).
46
+ - **DON'T list the files changed or the edits made in the body.**
47
+ - **DON'T use markdown syntax** (no backticks, no bolding, no lists, no links).
48
+
49
+ ## 3. Definition of Done (DoD)
50
+
51
+ Before considering a task complete:
52
+
53
+ 1. **Default Rake Task Passes:** Run `agent_rake` (no args). Confirm it passes with ZERO errors.
54
+ 2. **Documentation Updated:** If public APIs changed, update relevant docs.
55
+ 3. **Changelog Updated:** If public APIs changed, update CHANGELOG.md's **Unreleased** section.
56
+ 4. **Commit Message Suggested:** Include a suggested commit message block.
57
+
58
+ ### Type Conventions
59
+
60
+ - `lib/`, `ext/`, `sig/`: Use `feat`, `fix`, `refactor`, `perf` as appropriate.
61
+ - `bin/`, `tasks/`, `.builds/`, CI/CD: Use `chore` for tooling internal to developing this gem. Use `feat`/`fix` for user-facing executables or changes that affect downstream users.
62
+ - `examples/`: Always `docs` (documentation by example).
63
+ - `test/`: Use `test` for new/changed tests, or match the type of the code being tested.
64
+ - `doc/`: Always `docs`.
65
+
@@ -0,0 +1,18 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Changelog
7
+
8
+ All notable changes to this project will be documented in this file.
9
+
10
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
11
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
12
+
13
+
14
+ ## [Unreleased]
15
+
16
+ ### Added
17
+
18
+ - Initial release of <%= gem_name %>