Module: RemLint::Extractors
- Defined in:
- lib/remlint/extractors.rb
Overview
Turns a file on disk into zero or more Source runs of Remind code.
A linter that only globs *.rem misses most real Remind code: the shipped
examples/ansitext and examples/astro are shell scripts that pipe Remind
in through heredocs, and astro alone holds four of them. Linting such a
file as if it were Remind produces nothing but noise about the shell.
So extraction is its own layer, mirroring how RuboCop pulls Ruby out of ERB and Haml: each extractor either recognises the file and returns its Remind runs, or returns nil and lets the next one look.
Constant Summary collapse
- HEREDOC_OPENER =
remind ... <<'EOF'/<<-EOF/<< "EOF", with the flags and redirections in between ignored. The quoting around the delimiter is what stops the shell interpolating$Latitudebefore Remind sees it; both quoted and bare forms open a heredoc, so both are matched. /\bremind\b[^\n<]*<<(?<dash>-?)\s*(?<quote>['"]?)(?<delimiter>\w+)\k<quote>/- REM_SHEBANG =
A file Remind is meant to read directly, either by extension or because it runs itself through Remind.
/\A#![^\n]*\bremind\b/- ALL =
Ordered most-specific-first: a
.remfile is taken whole, and only a file that is not itself Remind gets searched for embedded heredocs. [method(:rem_file), method(:shell_heredoc)].freeze
Class Method Summary collapse
-
.extract(path, text) ⇒ Object
The first extractor that recognises the file wins.
-
.find_terminator(lines, from, delimiter, allow_indent) ⇒ Object
<<-lets the closing delimiter be indented; plain<<demands it sit at column zero. -
.rem_file(path, text) ⇒ Object
A plain Remind file: the whole text, no offset.
- .scan_heredocs(path, text) ⇒ Object
-
.shell_heredoc(path, text) ⇒ Object
Remind embedded in shell heredocs.
- .terminator?(line, delimiter, allow_indent) ⇒ Boolean
Class Method Details
.extract(path, text) ⇒ Object
The first extractor that recognises the file wins.
51 52 53 |
# File 'lib/remlint/extractors.rb', line 51 def extract(path, text) ALL.lazy.filter_map { |extractor| extractor.call(path, text) }.first || [] end |
.find_terminator(lines, from, delimiter, allow_indent) ⇒ Object
<<- lets the closing delimiter be indented; plain << demands it sit
at column zero. An unterminated heredoc runs to end of file, which is
what the shell would do too.
91 92 93 94 95 96 97 98 99 |
# File 'lib/remlint/extractors.rb', line 91 def find_terminator(lines, from, delimiter, allow_indent) cursor = from while cursor < lines.length && !terminator?(lines[cursor], delimiter, allow_indent) cursor += 1 end cursor end |
.rem_file(path, text) ⇒ Object
A plain Remind file: the whole text, no offset.
30 31 32 33 34 |
# File 'lib/remlint/extractors.rb', line 30 def rem_file(path, text) if File.extname(path).casecmp?(".rem") || text.match?(REM_SHEBANG) [Source.new(path: path, text: text)] end end |
.scan_heredocs(path, text) ⇒ Object
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 |
# File 'lib/remlint/extractors.rb', line 55 def scan_heredocs(path, text) lines = text.lines sources = [] index = 0 while index < lines.length match = lines[index].match(HEREDOC_OPENER) if match body_start = index + 1 body_end = find_terminator( lines, body_start, match[:delimiter], match[:dash] == "-", ) sources << Source.new( path: path, text: lines[body_start...body_end].join, line_offset: body_start, description: "heredoc at line #{index + 1}", ) index = body_end end index += 1 end sources end |
.shell_heredoc(path, text) ⇒ Object
Remind embedded in shell heredocs. Returns nil rather than an empty array when the file holds none, so the extractor chain keeps looking.
38 39 40 41 42 43 44 |
# File 'lib/remlint/extractors.rb', line 38 def shell_heredoc(path, text) sources = scan_heredocs(path, text) unless sources.empty? sources end end |
.terminator?(line, delimiter, allow_indent) ⇒ Boolean
101 102 103 104 105 106 107 108 109 |
# File 'lib/remlint/extractors.rb', line 101 def terminator?(line, delimiter, allow_indent) body = line.chomp if allow_indent body.sub(/\A\t+/, "") == delimiter else body == delimiter end end |