Top Level Namespace
Defined Under Namespace
Modules: Autodoc, LicenseUtils, RDocConfig, RatatuiRuby Classes: CargoLockfile, Changelog, Header, History, Links, Manifest, RubyGem, SemVer, UnreleasedSection
Constant Summary collapse
- YOUR_NAME =
Your name for copyright headers.
"Kerrick Long"- YOUR_EMAIL =
Your email for copyright headers.
"[email protected]"- YOUR_IDENTIFIERS =
Identifiers used to match your contributions in git history.
[YOUR_NAME, YOUR_EMAIL].freeze
- YOUR_COPYRIGHT =
Full copyright string for headers.
"#{YOUR_NAME} <#{YOUR_EMAIL}>"- LICENSE =
The SPDX license identifier for code snippets.
"MIT-0"- COPYRIGHT_HOLDER =
The name for SPDX-FileCopyrightText in code snippets.
"Kerrick Long"- EXCLUDED_FILES =
Files to skip entirely (relative paths from repo root)
[ "doc/contributors/v1.0.0_blockers.md", "doc/contributors/upstream_requests/tab_rects.md", "doc/contributors/upstream_requests/title_rects.md", ].freeze
Instance Method Summary collapse
-
#find_code_blocks(lines) ⇒ Object
Identifies fenced code blocks in markdown content.
-
#find_md_files(paths) ⇒ Object
Finds markdown files to process.
-
#find_rb_files(paths) ⇒ Object
Finds Ruby files to process.
-
#find_rdoc_code_blocks(lines) ⇒ Object
Identifies RDoc code blocks in Ruby source files.
-
#find_snippet_end(lines, start_idx) ⇒ Object
Locates the SPDX-SnippetEnd marker for a snippet block.
-
#get_latest_git_year(file, start_line, end_line) ⇒ Object
Determines the latest edit year for a line range using git blame.
-
#get_non_code_line_ranges(lines) ⇒ Object
Calculates line ranges outside code blocks.
-
#is_already_wrapped?(lines, block_start, prefix) ⇒ Boolean
Checks if a code block already has SPDX snippet headers.
-
#is_our_snippet_header?(lines, idx) ⇒ Boolean
Checks if an existing SPDX snippet header matches our required format.
-
#license_for_file(filepath) ⇒ Object
Selects the appropriate license based on file location.
-
#parse_existing_header(lines) ⇒ Object
Extracts existing SPDX header from Ruby file content.
-
#process_file(filepath) ⇒ Object
Wraps RDoc code blocks in a Ruby file with SPDX snippet headers.
Instance Method Details
#find_code_blocks(lines) ⇒ Object
Identifies fenced code blocks in markdown content.
Copyright years come from git blame. Code blocks contain pasted content, not original prose. Blaming code block lines produces wrong contributors. Exclude these ranges when calculating copyright years.
- lines
-
Array of line strings from the file.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/ratatui_ruby/devtools/tasks/license/headers_md.rb', line 57 def find_code_blocks(lines) blocks = [] i = 0 while i < lines.length line = lines[i] if line =~ /^(````*)(\w*)$/ fence_marker = $1 fence_start = i re_end = /^#{Regexp.escape(fence_marker)}$/ j = i + 1 while j < lines.length if lines[j] =~ re_end blocks << { start: fence_start, end: j } i = j break end j += 1 end end i += 1 end blocks end |
#find_md_files(paths) ⇒ Object
Finds markdown files to process.
License automation runs on file sets. Users may specify paths or want all files. This handles both cases using git ls-files for tracking.
- paths
-
Explicit paths to process, or empty for all tracked .md files.
255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/ratatui_ruby/devtools/tasks/license/headers_md.rb', line 255 def find_md_files(paths) if paths.empty? `git ls-files '*.md'`.split("\n") else paths.flat_map do |path| if File.directory?(path) `git ls-files '#{path}/**/*.md'`.split("\n") else path end end end end |
#find_rb_files(paths) ⇒ Object
Finds Ruby files to process.
License automation runs on file sets. Users may specify paths or want all files. This handles both cases using git ls-files for tracking.
- paths
-
Explicit paths to process, or empty for all tracked .rb files.
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/ratatui_ruby/devtools/tasks/license/headers_rb.rb', line 203 def find_rb_files(paths) if paths.empty? # Process all relevant directories dirs = %w[lib ext test examples tasks bin sig] files = dirs.flat_map do |dir| # Include both root files and subdirectory files, for both .rb and .rbs %w[rb rbs].flat_map do |ext| root_files = `git ls-files '#{dir}/*.#{ext}' 2>/dev/null`.split("\n") sub_files = `git ls-files '#{dir}/**/*.#{ext}' 2>/dev/null`.split("\n") root_files + sub_files end end files.uniq else paths.flat_map do |path| if File.directory?(path) rb_files = `git ls-files '#{path}/**/*.rb'`.split("\n") rbs_files = `git ls-files '#{path}/**/*.rbs'`.split("\n") rb_files + rbs_files else path end end end end |
#find_rdoc_code_blocks(lines) ⇒ Object
Identifies RDoc code blocks in Ruby source files.
RDoc code examples are indented comment lines. They need MIT-0 licensing separate from the file. This scans for the indentation pattern that identifies code blocks.
- lines
-
Array of line strings from the file.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/ratatui_ruby/devtools/tasks/license/snippets_rdoc.rb', line 48 def find_rdoc_code_blocks(lines) # Find all RDoc code blocks (indented comment lines) # Returns array of {start:, end:, indent:} where indent is the comment prefix blocks = [] i = 0 while i < lines.length line = lines[i] # Check if this is an indented code line in a comment # Pattern: optional leading whitespace, #, then 3+ spaces (RDoc code indent) if line =~ /^(\s*)#( +)(\S.*)$/ prefix = $1 # leading whitespace before # block_start = i # Find the extent of this code block j = i while j < lines.length current = lines[j] # Code block continues if line is indented code OR empty comment line if current =~ /^#{Regexp.escape(prefix)}#( +|\s*$)/ j += 1 else break end end block_end = j - 1 # Only count as a block if it has actual code (not just empty lines) has_code = (block_start..block_end).any? { |k| lines[k] =~ /^#{Regexp.escape(prefix)}# +\S/ } if has_code && block_end > block_start blocks << { start: block_start, end: block_end, prefix: } end i = j else i += 1 end end blocks end |
#find_snippet_end(lines, start_idx) ⇒ Object
Locates the SPDX-SnippetEnd marker for a snippet block.
Snippet blocks have paired begin/end markers. Removing or replacing a block requires finding both. This scans forward from a start position.
- lines
-
Array of line strings.
- start_idx
-
Index to start searching from.
82 83 84 85 86 87 88 89 |
# File 'lib/ratatui_ruby/devtools/tasks/license/snippets_md.rb', line 82 def find_snippet_end(lines, start_idx) i = start_idx while i < lines.length return i if lines[i].include?("SPDX-SnippetEnd") i += 1 end nil end |
#get_latest_git_year(file, start_line, end_line) ⇒ Object
Determines the latest edit year for a line range using git blame.
Copyright years come from when code was last modified. Git blame provides per-line authorship. Extract and return the most recent year.
- file
-
Path to the file.
- start_line
-
First line number (1-indexed).
- end_line
-
Last line number (1-indexed).
45 46 47 48 49 50 |
# File 'lib/ratatui_ruby/devtools/tasks/license/snippets_md.rb', line 45 def get_latest_git_year(file, start_line, end_line) cmd = %W[git blame -L #{start_line},#{end_line} --date=short -- #{file}] output, _status = Open3.capture2(*cmd) years = output.scan(/(\d{4})-\d{2}-\d{2}/).flatten.map(&:to_i) years.empty? ? Date.today.year : years.max end |
#get_non_code_line_ranges(lines) ⇒ Object
Calculates line ranges outside code blocks.
Copyright years come from git blame. Blaming the entire file includes code blocks. This function returns only prose ranges for accurate year lookup.
- lines
-
Array of line strings from the file.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/ratatui_ruby/devtools/tasks/license/headers_md.rb', line 92 def get_non_code_line_ranges(lines) header_end = 0 if lines[0]&.include?("<!--") (0...(lines.length)).each do |i| if lines[i].include?("-->") header_end = i + 1 break end end end code_blocks = find_code_blocks(lines) non_code_ranges = [] current_line = header_end code_blocks.each do |block| if current_line < block[:start] non_code_ranges << [current_line + 1, block[:start]] end current_line = block[:end] + 1 end if current_line < lines.length non_code_ranges << [current_line + 1, lines.length] end non_code_ranges end |
#is_already_wrapped?(lines, block_start, prefix) ⇒ Boolean
Checks if a code block already has SPDX snippet headers.
Already-wrapped blocks should be skipped. Re-wrapping wastes time and creates noisy diffs. This checks for the #++ marker before a block.
- lines
-
Array of line strings.
- block_start
-
Index of the code block start.
- prefix
-
The indentation prefix for this block.
101 102 103 104 105 106 107 |
# File 'lib/ratatui_ruby/devtools/tasks/license/snippets_rdoc.rb', line 101 def is_already_wrapped?(lines, block_start, prefix) # Check if the line before the block is #++ (meaning it's already wrapped) return false if block_start < 1 prev_line = lines[block_start - 1] prev_line =~ /^#{Regexp.escape(prefix)}#\+\+\s*$/ end |
#is_our_snippet_header?(lines, idx) ⇒ Boolean
Checks if an existing SPDX snippet header matches our required format.
Already-correct snippets should be skipped. Re-wrapping wastes time and creates noisy diffs. This function validates existing headers.
- lines
-
Array of line strings.
- idx
-
Index of the SPDX-SnippetBegin line.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/ratatui_ruby/devtools/tasks/license/snippets_md.rb', line 59 def is_our_snippet_header?(lines, idx) # Check if the current SPDX-SnippetBegin block already has our copyright/license i = idx + 1 has_our_copyright = false has_mit0 = false while i < lines.length && !lines[i].include?("-->") line = lines[i] has_our_copyright = true if line.include?(COPYRIGHT_HOLDER) && line.include?("SPDX-FileCopyrightText") has_mit0 = true if line.include?("MIT-0") && line.include?("SPDX-License-Identifier") i += 1 end has_our_copyright && has_mit0 end |
#license_for_file(filepath) ⇒ Object
Selects the appropriate license based on file location.
Different parts of the codebase have different licenses. Library code is LGPL. Examples are MIT-0 or AGPL. This function routes files to their correct license by path pattern.
- filepath
-
Path to the Ruby file.
33 34 35 36 37 38 39 40 41 42 |
# File 'lib/ratatui_ruby/devtools/tasks/license/headers_rb.rb', line 33 def license_for_file(filepath) case filepath when %r{^(lib|sig/ratatui_ruby|ext|test)/} "LGPL-3.0-or-later" when %r{^(examples|sig/examples)/(widget_|verify_)} "MIT-0" else "AGPL-3.0-or-later" end end |
#parse_existing_header(lines) ⇒ Object
Extracts existing SPDX header from Ruby file content.
Files may already have headers. Updating requires parsing existing copyright holders and years. This extracts them for comparison and update.
- lines
-
Array of line strings from the file.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/ratatui_ruby/devtools/tasks/license/headers_md.rb', line 127 def parse_existing_header(lines) return nil unless lines[0]&.include?("<!--") header_end = nil copyrights = [] license = nil (0...(lines.length)).each do |i| line = lines[i] if line =~ /SPDX-FileCopyrightText:\s*(\d{4})\s+(.+)$/ copyrights << { year: $1.to_i, holder: $2.strip } # REUSE-IgnoreStart elsif line =~ /SPDX-License-Identifier:\s*(.+)$/ # REUSE-IgnoreEnd license = $1.strip end if line.include?("-->") header_end = i break end end return nil if header_end.nil? return nil if copyrights.empty? && license.nil? { end_line: header_end, copyrights:, license: } end |
#process_file(filepath) ⇒ Object
Wraps RDoc code blocks in a Ruby file with SPDX snippet headers.
Each code example needs MIT-0 licensing. Processing involves scanning for indented examples and inserting hidden SPDX headers. This function orchestrates that workflow.
- filepath
-
Path to the Ruby file.
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/ratatui_ruby/devtools/tasks/license/headers_md.rb', line 164 def process_file(filepath) content = File.read(filepath) lines = content.lines non_code_ranges = get_non_code_line_ranges(lines) # Get contributors from non-code lines for year lookups all_contributors = {} non_code_ranges.each do |start_line, end_line| range_contributors = LicenseUtils.get_contributors_for_lines(filepath, start_line, end_line) range_contributors.each do |contributor, year| all_contributors[contributor] = [all_contributors[contributor] || 0, year].max end end your_year = nil all_contributors.each do |contributor, year| if YOUR_IDENTIFIERS.any? { |id| contributor.include?(id) } your_year = [your_year || 0, year].max end end your_year ||= Date.today.year existing = parse_existing_header(lines) if existing # Only update years for EXISTING contributors needs_update = false updated_copyrights = [] existing[:copyrights].each do |c| git_year = nil all_contributors.each do |contributor, year| if c[:holder].split.any? { |word| contributor.include?(word) } git_year = [git_year || 0, year].max end end if git_year && git_year != c[:year] puts " Updated #{c[:holder].split.first}'s copyright year: #{c[:year]} -> #{git_year}" updated_copyrights << { year: git_year, holder: c[:holder] } needs_update = true else updated_copyrights << c end end # Check if YOUR year needs updating your_existing = updated_copyrights.find { |c| YOUR_IDENTIFIERS.any? { |id| c[:holder].include?(id) } } if your_existing.nil? puts " Adding your copyright" updated_copyrights << { year: your_year, holder: YOUR_COPYRIGHT } needs_update = true end if existing[:license] != LICENSE puts " Fixing license: #{existing[:license]} -> #{LICENSE}" needs_update = true end if needs_update # REUSE-IgnoreStart header_lines = ["<!--\n"] updated_copyrights.each do |c| header_lines << " SPDX-FileCopyrightText: #{c[:year]} #{c[:holder]}\n" end header_lines << " SPDX-License-Identifier: #{LICENSE}\n" header_lines << "-->\n" # REUSE-IgnoreEnd remaining = lines[(existing[:end_line] + 1)..] File.write(filepath, header_lines.join + remaining.join) puts "Updated: #{filepath}" end else # No header - add one with YOUR copyright only # REUSE-IgnoreStart header = "<!--\n SPDX-FileCopyrightText: #{your_year} #{YOUR_COPYRIGHT}\n SPDX-License-Identifier: #{LICENSE}\n-->\n" # REUSE-IgnoreEnd File.write(filepath, header + content) puts "Added header: #{filepath}" end end |