If you have a command or utility that you want to run pretty much as-is, but Trunk doesn't natively understand how to parse it, you can inject your own custom parser to translate its output into a format that Trunk does understand!
For example, let's say that we want to use grep
as a linter, but we want to add more context to the matches. We could define a custom linter like so:
lint:
definitions:
- name: todo-finder
files: [ALL]
commands:
- output: regex
# matches the parser run output
parse_regex: "((?P<path>.*):(?P<line>\\d+):(?P<col>\\d+):
\\[(?P<severity>.*)\\] (?P<message>.*) \\((?P<code>.*)\\))"
run: grep --with-filename --line-number --ignore-case todo ${target}
success_codes: [0, 1]
read_output_from: stdout
parser:
run:
"sed -E 's/(.*):([0-9]+):(.*)/\\1:\\2:0:
[error] Found todo in \"\\3\" (found-todo)/'"
The execution model that trunk
follows for a parser is that it will:
- execute the linter's
run
field, asserting that either:- the linter's exit code is in
success_codes
, or - the linter's exit code is not in
error_codes
;
- the linter's exit code is in
- execute
parser.run
,- with the
read_output_from
of the linter execution fed toparser.run
asstdin
, - assert that the exit code of the parser is 0, and then
- with the
- use
output
to determine how it should parse the parser'sstdout
.
Note that you can also set parser.runtime
to node
or python
so that you can write your parser in Javascript or Python instead, if you so prefer! You can find plenty of examples of python parsers in our plugins repo.
{% tabs %} {% tab title="node" %} Node
lint:
definitions:
- name: todo-finder-node
files: [ALL]
commands:
- output: parsable
# parse_regex matches the parser run output
parse_regex: "((?P<path>.*):(?P<line>\\d+):(?P<col>\\d+):
\\[(?P<severity>.*)\\] (?P<message>.*) \\((?P<code>.*)\\))"
run: grep --with-filename --line-number --ignore-case todo ${target}
success_codes: [0, 1]
read_output_from: stdout
parser:
runtime: node
run: ${workspace}/todo-finder-parser.js
#!/usr/bin/env node
'use strict';
let readline = require('readline');
let rl = readline.createInterface({ input: process.stdin });
rl.on('line', function(line){
let match = line.match(/(.*):([0-9]+):(.*)/);
if (match) {
let [_, path, line_number, line_contents] = match;
console.log(`${path}:${line_number}:0: [error]`
+` Found todo in "${line_contents}" (found-todo)`);
}
Remember to run chmod u+x todo-finder-parser.js
so that trunk
can run it!
{% endtab %}
{% tab title="python" %} Python
lint:
definitions:
- name: todo-finder-python
files: [ALL]
commands:
- output: parsable
# parse_regex matches the parser run output
parse_regex: "((?P<path>.*):(?P<line>\\d+):(?P<col>\\d+):
\\[(?P<severity>.*)\\] (?P<message>.*) \\((?P<code>.*)\\))"
run: grep --with-filename --line-number --ignore-case todo ${target}
success_codes: [0, 1]
read_output_from: stdout
parser:
runtime: python
run: ${workspace}/todo-finder-parser.js
#!/usr/bin/env python
import re, sys
for line in sys.stdin.readlines():
match = re.match("(.*):([0-9]+):(.*)", line)
if match:
path, line_number, line_contents = match.groups()
print(f"{path}:{line_number}:0: [error] "
"Found todo in \"{line_contents}\" (found-todo)")
Remember to run chmod u+x todo-finder-parser.py
so that trunk
can run it!
{% endtab %}
{% endtabs %}