Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Writing Plugins

pretender supports external metric plugins: small programs that analyse source files and emit findings in JSON. pretender runs each plugin as a subprocess, collects its output, and merges the findings into pretender check results alongside the built-in metrics.


Install location

Place plugin manifests (.toml files) in the metrics directory. pretender searches the first directory found in this order:

  1. $PRETENDER_METRICS_DIR (if set)
  2. $XDG_CONFIG_HOME/pretender/metrics/ (if XDG_CONFIG_HOME is set)
  3. ~/.config/pretender/metrics/ (fallback)

Each .toml file in that directory is loaded as one plugin. Non-.toml files and files with parse errors are skipped — a warning is printed to stderr for each invalid file.


Plugin manifest format

A plugin manifest is a TOML file with the following fields:

FieldTypeRequiredDescription
namestringyesPlugin name shown in output (e.g. "ruff")
extensionsarray of stringsyesFile extensions this plugin applies to (e.g. [".py"]) — must include the leading dot
commandarray of stringsyesCommand and arguments to run. The token {files} is expanded to the list of matching file paths
parserstringyesOutput format: "json" (a JSON array) or "json-lines" (one JSON object per line)
[mapping]tableyesField mapping from the plugin’s JSON output to pretender’s finding schema (see below)

[mapping] sub-table

Each mapping value is a dot-separated path into the JSON object emitted by the plugin. For example, "location.row" navigates {"location": {"row": 5}}.

FieldTypeRequiredDescription
pathstringyesJSON path to the source file name
linestringyesJSON path to the line number (integer)
messagestringyesJSON path to the human-readable finding message
codestringnoJSON path to an optional finding code (e.g. "E501")

Finding schema

Each finding pretender emits from a plugin has these fields:

FieldTypeDescription
sourcestringThe plugin name
lineintegerLine number of the finding
messagestringHuman-readable description
codestring or nullOptional finding code

Worked example

The metrics/example/ directory in this repository contains a minimal, runnable plugin — a shell script that emits one JSON finding per violation and a TOML manifest wiring it to pretender. Copy the files and edit the path to install it locally.

Manifest — metrics/example/long-lines.toml

name       = "long-lines"
extensions = [".py", ".js", ".ts", ".rs"]
command    = ["metrics/example/long-lines.sh", "{files}"]
parser     = "json-lines"

[mapping]
path    = "file"
line    = "line"
message = "message"
code    = "code"

Script — metrics/example/long-lines.sh

The script checks for lines exceeding 100 characters and emits one JSON object per violation:

#!/usr/bin/env sh
for file in "$@"; do
  awk -v file="$file" 'length > 100 {
    printf "{\"file\":\"%s\",\"line\":%d,\"message\":\"line exceeds 100 chars (%d)\",\"code\":\"LL001\"}\n",
      file, NR, length
  }' "$file"
done

Installing the example

Use absolute paths in command — relative paths are resolved from wherever pretender check is run, which can vary. Copy both files to the metrics directory:

mkdir -p ~/.config/pretender/metrics
cp metrics/example/long-lines.sh ~/.config/pretender/metrics/
chmod +x ~/.config/pretender/metrics/long-lines.sh

# Update the command path in the manifest before copying
sed 's|metrics/example/long-lines.sh|/home/you/.config/pretender/metrics/long-lines.sh|' \
  metrics/example/long-lines.toml \
  > ~/.config/pretender/metrics/long-lines.toml

# Verify it appears in check output
pretender check src/

The finding will appear in human output as:

src/lib.rs:87  [long-lines] line exceeds 100 chars (112)  LL001

How findings are merged

After each pretender check run, pretender runs every applicable plugin against the files being checked. Findings from plugins are merged with built-in metric findings and displayed in the same output stream. The source field identifies which plugin produced each finding.

Plugins are always loaded from the metrics directory in addition to any built-in metric tools listed in [plugins].metrics in pretender.toml. See the Configuration reference for the [plugins] section.


Real-world examples

ruff (Python linter)

name       = "ruff"
extensions = [".py"]
command    = ["ruff", "check", "--output-format=json", "--select=E501", "{files}"]
parser     = "json"

[mapping]
path    = "filename"
line    = "location.row"
message = "message"
code    = "code"

ESLint (JavaScript / TypeScript)

ESLint’s JSON output nests messages inside per-file objects — one array entry per file, not per finding. pretender’s dot-path mapping does not support array indexing, so a thin wrapper script is required to flatten the output.

Requires: jq — install with your system package manager (brew install jq, apt install jq, etc.).

#!/usr/bin/env sh
# eslint-wrapper.sh
# Requires jq. Flattens ESLint JSON output to one JSON object per finding.
npx eslint --format=json "$@" | jq -c '
  .[] | .filePath as $f | .messages[] |
  {file: $f, line: .line, message: .message, code: .ruleId}
'
name       = "eslint"
extensions = [".js", ".jsx", ".ts", ".tsx"]
command    = ["/path/to/eslint-wrapper.sh", "{files}"]
parser     = "json-lines"

[mapping]
path    = "file"
line    = "line"
message = "message"
code    = "code"

Replace /path/to/eslint-wrapper.sh with the absolute path where you install the wrapper script.