Configuration

markright reads its configuration from the root of the repository: .markright.yaml, in YAML, or .markright.toml, in TOML, whichever the repository prefers; a root that holds both is an error. The root is the nearest directory, from the current one up, that holds one of those files or .git, so a command run in a subdirectory reads the same configuration; --root names it instead. --config names another file, read as TOML when its name ends in .toml and as YAML otherwise. Every repository that uses the tool keeps one, even when it sets nothing beyond the defaults, so what runs is visible where the documents are and a change of defaults in a new version shows up as a decision in the file.

The smallest file#

It turns every formatter and linter on.

YAML: .markright.yaml#

version: "1"

linters:
  default: all

formatters:
  default: all

TOML: .markright.toml#

version = "1"

[linters]
default = "all"

[formatters]
default = "all"

Both say the same: every key below has the same name and meaning in either language, and this guide shows YAML.

Rules of the file#

A file must declare version: "1"; any key it sets replaces only that value, and a list replaces the whole default list. Without the file every formatter and linter runs with the settings below. markright config verify checks a file without running anything; an unknown key, name or regular expression, a name both enabled and disabled, or an incomplete exclusion rule stops any command with exit code 2, and the message names the line of the file:

markright: .markright.yaml: line 5: unknown field `width`, expected one of max

Every key:

version: "1"

documents:
  exclude: []

output:
  formats:
    text:
      path: stdout
    checkstyle:
      path: markright.xml
    sarif:
      path: markright.sarif

linters:
  default: all
  enable: []
  disable: [line-length]
  settings:
    line-length:
      max: 120
    index:
      file: CONTENTS.md
    readme-links:
      file: README.md
      links: [CONTENTS.md, GLOSSARY.md, STANDARDS.md]
    alerts:
      types: [NOTE, TIP, IMPORTANT, WARNING, CAUTION]
    link-text:
      phrases: [click here, here, link, more, read more, this, this link]
    links:
      targets: []
    anchors:
      targets: []
  exclusions:
    warn-unused: true
    paths:
      - ^generated/
    paths-except: []
    rules:
      - path: ^docs/legacy\.md$
        linters: [line-length]
      - path-except: ^docs/
        text: "broken link: ../"

formatters:
  default: all
  enable: []
  disable: [tables]
  settings:
    list-marker:
      marker: "*"
    ordered-list-numbers:
      numbering: increasing
    emphasis-marker:
      marker: "*"
    fence-marker:
      marker: "`"
    horizontal-rule:
      style: "---"
  exclusions:
    warn-unused: true
    paths:
      - ^CHANGELOG\.md$
    paths-except: []

The same in TOML:

version = "1"

[documents]
exclude = []

[output.formats.text]
path = "stdout"

[output.formats.checkstyle]
path = "markright.xml"

[output.formats.sarif]
path = "markright.sarif"

[linters]
default = "all"
enable = []
disable = ["line-length"]

[linters.settings.line-length]
max = 120

[linters.settings.index]
file = "CONTENTS.md"

[linters.settings.readme-links]
file = "README.md"
links = ["CONTENTS.md", "GLOSSARY.md", "STANDARDS.md"]

[linters.settings.alerts]
types = ["NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"]

[linters.settings.link-text]
phrases = ["click here", "here", "link", "more", "read more", "this", "this link"]

[linters.settings.links]
targets = []

[linters.settings.anchors]
targets = []

[linters.exclusions]
warn-unused = true
paths = ['^generated/']
paths-except = []

[[linters.exclusions.rules]]
path = '^docs/legacy\.md$'
linters = ["line-length"]

[[linters.exclusions.rules]]
path-except = '^docs/'
text = "broken link: ../"

[formatters]
default = "all"
enable = []
disable = ["tables"]

[formatters.settings.list-marker]
marker = "*"

[formatters.settings.ordered-list-numbers]
numbering = "increasing"

[formatters.settings.emphasis-marker]
marker = "*"

[formatters.settings.fence-marker]
marker = "`"

[formatters.settings.horizontal-rule]
style = "---"

[formatters.exclusions]
warn-unused = true
paths = ['^CHANGELOG\.md$']
paths-except = []

Choosing what runs#

linters and formatters each choose their own set. default is all or none; enable adds names to it and disable removes them. markright linters and markright formatters list the names and what the configuration makes of each. The names are in Rules.

Settings#

KeyMeaningDefault
formatters.settings.list-marker.markerthe marker of every item of an unordered list-
formatters.settings.ordered-list-numbers.numberinghow the items of an ordered list are numberedincreasing
formatters.settings.emphasis-marker.markerthe character of emphasis and strong emphasis*
formatters.settings.fence-marker.markerthe character of the fences of code blocks`
formatters.settings.horizontal-rule.stylehow a horizontal rule is written---
linters.settings.links.targetspaths a relative link is checked against, when any are set; a link to another path is not checkednone
linters.settings.anchors.targetsdocuments a link's fragment is checked against, when any are set; a fragment into another document is not checkednone
linters.settings.link-text.phrasesthe texts that say nothing about where a link leads, compared without case and without the punctuation around themclick here, here, link, more, read more, this, this link
linters.settings.alerts.typesallowed alert typesNOTE, TIP, IMPORTANT, WARNING, CAUTION
linters.settings.line-length.maxthe longest line allowed, in characters120
linters.settings.index.filethe document that must link every other oneCONTENTS.md
linters.settings.readme-links.filethe READMEREADME.md
linters.settings.readme-links.linksdocuments the README must link, when they existCONTENTS.md, GLOSSARY.md, STANDARDS.md

Paths in the settings are relative to the root.

Documents#

documents.exclude takes files out of the repository's documents altogether: no formatter or linter looks at them, index does not ask for them, and the anchors of a link into one are not checked, though the file it names must exist. It suits generated or vendored Markdown that is neither yours to fix nor a target your links rely on:

[documents]
exclude = ['^vendor/', '^CHANGELOG\.md$']

To keep a file as a link target while leaving it unformatted or unlinted, exclude it in formatters or linters below instead.

Links and anchors are checked against every document of the repository. linters.settings.links.targets and linters.settings.anchors.targets narrow that, for a repository that links to files it does not keep correct, such as a generated changelog whose headings change with every release:

[linters.settings.anchors]
targets = ['^docs/', '^README\.md$']

A link to a path no target matches is not checked; the document holding the link still is. These are settings of the file, not options of the command, so a document gets the same issues however markright is run.

Exclusions#

Each section skips paths on its own, so a file can be linted but never formatted, or the other way round.

  • paths: a matching document is excluded.
  • paths-except: when set, a document that matches none of these is excluded as well, so a section can be limited to a few directories.
  • formatters.exclusions: the formatters leave an excluded document as it is, and check does not report it as unformatted.
  • linters.exclusions: no linter reports an issue in an excluded document, and index does not ask for it.
  • linters.exclusions.rules: an issue is dropped when it matches every field a rule sets: path or path-except against the document, linters against the linter's name, text against the message. A rule sets at least two of those, and never both path and path-except, so it cannot silence a linter everywhere by accident; that is what disable is for.
  • warn-unused: check prints a warning to standard error for every path pattern that matches no document and every rule that drops no issue, so stale exclusions surface. It does not change the exit code.

Paths and texts are regular expressions, in the syntax of Rust's regex crate, matched anywhere in the path relative to the root, with / as the separator; ^ and $ anchor them. Build and dependency directories are skipped before any of this: Document discovery.

Output#

output.formats chooses what markright check writes, and where: stdout, stderr, or a file relative to the working directory. Without any format, it writes text to standard output.

FormatContent
textone issue per line, path:line: message (linter), then the count
checkstyleCheckstyle XML, for tools that read it, with markright.<linter> as source
sarifSARIF 2.1.0, with every formatter and linter as a rule, for tools that read static analysis results

markright fmt always prints the files it changed, as text.

Schema#

jsonschema/markright.jsonschema.json describes the file, so an editor completes and checks it. Point the editor at the schema of the version in use, for example with the YAML language server's modeline:

# yaml-language-server: $schema=https://raw.githubusercontent.com/vertyll/markright/v0.2.1/jsonschema/markright.jsonschema.json
version: "1"

or by mapping .markright.yaml to that address in the editor's settings. For .markright.toml, a first line of #:schema followed by the same address does the same for editors that use Taplo, the TOML language server.

The schema is generated from the types markright reads the file into, so an editor accepts exactly what markright does: Development.

In CI#

CI fetches the command of a pinned release, checks it against the release's SHA256SUMS, and fails the build on exit code 1:

version=v0.2.1
target=x86_64-unknown-linux-gnu
base=https://github.com/vertyll/markright/releases/download/$version
curl -fsSLO "$base/markright-$target.tar.gz"
curl -fsSL "$base/SHA256SUMS" | grep "markright-$target.tar.gz" | sha256sum -c -
tar -xzf "markright-$target.tar.gz"
./markright check

In a GitHub Actions workflow, those lines are the run of one step, after actions/checkout. To move to a newer release, raise version. Each release has an archive for every system it is built for, named after its target; in a container without glibc, such as Alpine, take a musl one. The list is in Development.

Settings

Appearance

ThemeColours of every page

Reading

Code blocksLines longer than the block
TablesCells wider than the page

Accessibility

Text sizeEvery page, in proportion
LinksLinks inside the text
MotionAnimations and smooth scrolling
ScrollbarsCode blocks, tables and other scrolling areas