Module: Suma::ExpressReformatter
- Defined in:
- lib/suma/express_reformatter.rb
Overview
Reformats EXPRESS source into suma's canonical form: comment blocks
(the (*"...\n*) remarks that EXPRESS uses for documentation) are
extracted from their inline positions and appended to the end of
the file, and excess blank lines are collapsed.
Pure content transformation — no I/O, no Thor, no filesystem. The CLI adapter handles reading from and writing to disk.
The transform is idempotent: reformatting an already-reformatted
document returns changed?: false.
Comment extraction uses String#index rather than a single m-mode
regex. The original /\(\*"(.*?)\n\*\)/m is correct functionally
but is polynomial-time on adversarial input (CodeQL
rb/polynomial-redos). Scanning for the start marker, then for
the next terminator, is unambiguously linear.
Defined Under Namespace
Classes: Result
Constant Summary collapse
- COMMENT_START =
'(*"'- COMMENT_END =
"\n*)"- BLANK_RUN_PATTERN =
/(\n\n+)/- NEWLINE_RUN_PATTERN =
/(\n+)/
Class Method Summary collapse
- .call(content) ⇒ Object
- .each_comment_range(content) ⇒ Object
- .extract_comments(content) ⇒ Object
- .strip_comments(content) ⇒ Object
Class Method Details
.call(content) ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/suma/express_reformatter.rb', line 34 def call(content) comments = extract_comments(content) return Result.new(content: content, changed?: false) if comments.empty? without_comments = strip_comments(content) new_comments = comments.map { |c| "#{COMMENT_START}#{c}#{COMMENT_END}" } .join("\n\n") new_content = "#{without_comments}\n\n#{new_comments}\n" new_content = new_content.gsub(BLANK_RUN_PATTERN, "\n\n") changed = normalised_compare(content, new_content) != 0 Result.new(content: new_content, changed?: changed) end |
.each_comment_range(content) ⇒ Object
74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/suma/express_reformatter.rb', line 74 def each_comment_range(content) return enum_for(:each_comment_range, content) unless block_given? pos = 0 while (start_idx = content.index(COMMENT_START, pos)) end_idx = content.index(COMMENT_END, start_idx + COMMENT_START.length) break unless end_idx yield pre_start: pos, start: start_idx, end_exclusive: end_idx + COMMENT_END.length pos = end_idx + COMMENT_END.length end end |
.extract_comments(content) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/suma/express_reformatter.rb', line 48 def extract_comments(content) comments = [] pos = 0 while (start_idx = content.index(COMMENT_START, pos)) end_idx = content.index(COMMENT_END, start_idx + COMMENT_START.length) break unless end_idx comments << content[(start_idx + COMMENT_START.length)...end_idx] pos = end_idx + COMMENT_END.length end comments end |
.strip_comments(content) ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/suma/express_reformatter.rb', line 61 def strip_comments(content) return content unless content.index(COMMENT_START, 0) stripped = +"" last_end = 0 each_comment_range(content) do |range| stripped << content[range[:pre_start]...range[:start]] last_end = range[:end_exclusive] end stripped << content[last_end..] stripped.force_encoding(content.encoding) end |