The primary use of configlet is linting: checking if a track's configuration files are properly structured - both syntactically and semantically. Misconfigured tracks may not sync correctly, may look wrong on the website, or may present a suboptimal user experience, so configlet's guards play an important part in maintaining the integrity of Exercism.
The lint
command can be used to lint a track.
configlet [global-options] lint [command-options]
Global options:
-h, --help Show this help message and exit
--version Show this tool's version information and exit
-t, --track-dir <dir> Specify a track directory to use instead of the current directory
-v, --verbosity <verbosity> The verbosity of output. Allowed values: q[uiet], n[ormal], d[etailed]
These are the linting rules being checked.
The linter should check if all the required files are present. The non-exercise specific files that must be present are:
config.json
docs/ABOUT.md
docs/INSTALLATION.md
docs/LEARNING.md
docs/RESOURCES.md
docs/SNIPPET.txt
docs/TESTS.md
exercises/shared/.docs/help.md
exercises/shared/.docs/tests.md
The Concept Exercise specific files that must be present are:
exercises/concept/<slug>/.docs/hints.md
exercises/concept/<slug>/.docs/instructions.md
exercises/concept/<slug>/.docs/introduction.md
exercises/concept/<slug>/.meta/config.json
There will be a similar list for Practice Exercises, but we've not yet defined the spec for that.
Each concept listed in the config.json
should have the following files:
concepts/<slug>/about.md
concepts/<slug>/introduction.md
concepts/<slug>/links.json
The config.json
file should have the following checks:
"language"
key is required"language"
value must be a non-blank stringĀ¹ with length <= 255"slug"
key is required"slug"
value must be a kebab-case stringĀ² with length <= 255"active"
key is required"active"
value must be a boolean"blurb"
key is required"blurb"
value must be a non-blank stringĀ¹ with length <= 400"version"
key is required"version"
value must be the integer 3
"status.concept_exercises"
key is required"status.concept_exercises"
value must be a boolean"status.test_runner"
key is required"status.test_runner"
value must be a boolean"status.representer"
key is required"status.representer"
value must be a boolean"status.analyzer"
key is required"status.analyzer"
value must be a boolean"online_editor.indent_style"
key is required"online_editor.indent_style"
value must be the string space
or tab
"online_editor.indent_size"
key is required"online_editor.indent_size"
value must be an integer >= 0 and <= 8"online_editor.highlightjs_language"
key is optional"online_editor.highlightjs_language"
value must be a non-blank stringĀ¹"files"
key is optional"files"
value must be an object"files.solution"
key is optional"files.solution"
value must be an array"files.solution"
values must be valid patternsāµ"files.solution"
values must not have duplicates"files.test"
key is optional"files.test"
value must be an array"files.test"
values must be valid patternsāµ"files.test"
values must not have duplicates"files.example"
key is optional"files.example"
value must be an array"files.example"
values must be valid patternsāµ"files.example"
values must not have duplicates"files.exemplar"
key is optional"files.exemplar"
value must be an array"files.exemplar"
values must be valid patternsāµ"files.exemplar"
values must not have duplicates"files.editor"
key is optional"files.editor"
value must be an array"files.editor"
values must be valid patternsāµ"files.editor"
values must not have duplicates"files.invalidator"
key is optional"files.invalidator"
value must be an array"files.invalidator"
values must be valid patternsāµ"files.invalidator"
values must not have duplicates"files.solution"
, "files.test"
, "files.example"
, "files.exemplar"
, "files.editor"
or "files.invalidator"
array (no overlap)
d
or plsql
, the "files.solution"
and "files.test"
files can overlap"files.example
and "files.exemplar"
files can overlap"test_runner.average_run_time"
key is required if "status.test_runner"
is equal to true
"test_runner.average_run_time"
value must be an integer > 0"approaches.snippet_extension"
key is required if the track has one or more approaches"approaches.snippet_extension"
value must be a non-blank stringĀ¹"exercises"
key is required"exercises.concept"
key is required"exercises.concept"
value must be an array"exercises.concept[].slug"
key is required"exercises.concept[].slug"
value must be a kebab-case stringĀ² with length <= 255"exercises.concept[].slug"
value must be unique in "exercises.concept[].slug"
and may not exist in "exercises.practice[].slug"
"exercises.concept[].name"
key is required"exercises.concept[].name"
value must be a Title Case stringĀ³ with length <= 255"exercises.concept[].uuid"
key is required"exercises.concept[].uuid"
value must be a unique version 4 UUID stringā¶"exercises.concept[].uuid"
value for each exercise must never change"exercises.concept[].concepts"
key is required"exercises.concept[].concepts"
value must be a non-empty array of strings if "exercises.concept[].status"
is not equal to deprecated
"exercises.concept[].concepts"
value must be an empty array if "exercises.concept[].status"
is equal to deprecated
"exercises.concept[].concepts"
values must be kebab-case stringsĀ²"exercises.concept[].concepts"
values must not have duplicates"exercises.concept[].concepts"
values must not be in any other concept exercise's "concepts"
property"exercises.concept[].concepts"
values must match the "concepts.slug"
property of one of the concepts"exercises.concept[].prerequisites"
key is required"exercises.concept[].prerequisites"
value must be a non-empty array of strings if "exercises.concept[].status"
is not equal to deprecated
, except for exactly one exercise which is allowed to have an empty array as its value"exercises.concept[].prerequisites"
value must be an empty array if "exercises.concept[].status"
is equal to deprecated
"exercises.concept[].prerequisites"
values must be kebab-case stringsĀ²"exercises.concept[].prerequisites"
values must not have duplicates"exercises.concept[].prerequisites"
values must match any other concept exercise's "concepts"
property values"exercises.concept[].prerequisites"
values must not match any of the values in the exercise's "exercises.concept[].concepts"
property"exercises.concept[].prerequisites"
values must match the "concepts.slug"
property of one of the concepts"exercises.concept[].concepts"
and "exercises.concept[].prerequisites"
"exercises.concept[].status"
key is optional"exercises.concept[].status"
value must be the string wip
, beta
, active
or deprecated
"exercises.practice"
key is required"exercises.practice"
value must be an array"exercises.practice[].slug"
key is required"exercises.practice[].slug"
value must be a kebab-case stringĀ² with length <= 255"exercises.practice[].slug"
value must be unique in "exercises.practice[].slug"
and may not exist in "exercises.concept[].slug"
"exercises.practice[].slug"
value that is the string hello-world
"exercises.practice[].name"
key is required"exercises.practice[].name"
value must be a Title Case stringĀ³ with length <= 255"exercises.practice[].uuid"
key is required"exercises.practice[].uuid"
value must be a unique version 4 UUID stringā¶"exercises.practice[].uuid"
value for each exercise must never change"exercises.practice[].difficulty"
key is required"exercises.practice[].difficulty"
value must be an integer >= 1 and <= 10"exercises.practice[].practices"
key is required"exercises.practice[].practices"
value must be a non-empty array of strings if "exercises.practice[].status"
is not equal to deprecated
"exercises.practice[].practices"
value must be an empty array if "exercises.practice[].status"
is equal to deprecated
"exercises.practice[].practices"
values must be kebab-case stringsĀ²"exercises.practice[].practices"
values must not have duplicates"exercises.practice[].practices"
values must match the "concepts[].slug"
property of one of the concepts"exercises.practice[].practices"
values must refer to an individual concept's slug at most 10 times (across all exercises)"exercises.practice[].prerequisites"
key is required"exercises.practice[].prerequisites"
value must be a non-empty array of strings if "exercises.practice[].status"
is not equal to deprecated
"exercises.practice[].prerequisites"
value must be an empty array if "exercises.practice[].status"
is equal to deprecated
"exercises.practice[].prerequisites"
value must be an empty array if "exercises.practice[].slug"
is equal to hello-world
"exercises.practice[].prerequisites"
values must be kebab-case stringsĀ²"exercises.practice[].prerequisites"
values must not have duplicates"exercises.practice[].prerequisites"
values must match any concept exercise's "exercises.concept[].concepts"
values"exercises.practice[].prerequisites"
values must match the "concepts[].slug"
property of one of the concepts"exercises.practice[].status"
key is optional"exercises.practice[].status"
value must be the string wip
, beta
, active
or deprecated
"exercises.practice[].status"
value must, if "exercises.practice[].slug"
is equal to hello-world
, be either omitted or the string active
"exercises.foregone"
key is optional"exercises.foregone"
value must be an array"exercises.foregone"
values must be kebab-case stringsĀ²"exercises.foregone"
values must not have duplicates"exercises.foregone"
values must not match any of the concept or practice exercise slugs"concepts"
key is required"concepts"
value must be an array"concepts[].uuid"
key is required"concepts[].uuid"
value must be a unique version 4 UUID stringā¶"concepts[].uuid"
value for each concept must never change"concepts[].slug"
key is required"concepts[].slug"
value must be a kebab-case stringĀ² with length <= 255"concepts[].name"
key is required"concepts[].name"
value must be a Title Case stringĀ³ with length <= 255"concepts[].tags"
key is optional"concepts[].tags.all"
key is optional, unless "concepts[].tags.any"
is empty"concepts[].tags.all"
value must be an array"concepts[].tags.all"
values must be non-blank tagā· strings with length <= 255"concepts[].tags.all"
values must not have duplicates"concepts[].tags.any"
key is optional, unless "concepts[].tags.all"
is empty"concepts[].tags.any"
value must be an array"concepts[].tags.any"
values must be non-blank tagā· strings with length <= 255"concepts[].tags.any"
values must not have duplicates"concepts[].tags.not"
key is optional"concepts[].tags.not"
value must be an array"concepts[].tags.not"
values must be non-blank tagā· strings with length <= 255"concepts[].tags.not"
values must not have duplicates"concepts"
value must have a concept/<concepts.slug>/about.md
file. Linting rules for this file are specified below."concepts"
value must have a concept/<concepts.slug>/introduction.md
file. Linting rules for this file are specified below."concepts"
value must have a concept/<concepts.slug>/links.json
file. Linting rules for this file are specified below."key_features"
key is optional"key_features"
value must be an array with length = 6"key_features[].icon"
key is required"key_features[].icon"
value must use one of the pre-defined icon values
"key_features[].title"
key is required"key_features[].title"
value must be a Sentence Case stringā“ with length <= 25"key_features[].content"
key is required"key_features[].content"
value must be a non-blank stringĀ¹ with length <= 100"tags"
key is required"tags"
value must be an array of strings"tags"
values must not have duplicates"tags"
values must use one of the pre-defined tag values
"blurb"
key is required"blurb"
value must be a non-blank stringĀ¹ with length <= 350"source"
key is optional"source"
value must be a non-blank stringĀ¹"source_url"
key is optional"source_url"
value must be a URL"authors"
key is required"authors"
value must be a non-empty array"authors"
values must be non-blank stringsĀ¹"authors"
values must not have duplicates"authors"
values are treated case-insensitively"contributors"
key is optional"contributors"
value must be an array"contributors"
values must be non-blank stringsĀ¹"contributors"
values must not have duplicates"contributors"
values are treated case-insensitively"authors"
or "contributors"
array (no overlap)"files.solution"
key is required"files.solution"
value must be a non-empty array"files.solution"
values must not have duplicates"files.test"
key is required"files.test"
value must be a non-empty array"files.test"
values must not have duplicates"files.exemplar"
key is required"files.exemplar"
value must be a non-empty array"files.exemplar"
values must not have duplicates"files.editor"
key is optional"files.editor"
value must be an array"files.editor"
values must not have duplicates"files.invalidator"
key is optional"files.invalidator"
value must be an array"files.invalidator"
values must not have duplicates"files.solution"
must exist"files.test"
must exist"files.exemplar"
must exist"files.editor"
must exist"files.invalidator"
must exist"files.solution"
, "files.test"
, "files.exemplar"
or "files.invalidator"
array (no overlap)
d
or plsql
, the "files.solution"
and "files.test"
files can overlap"forked_from"
key is optional"forked_from"
value must be an array"forked_from"
values must be strings formatted as <track-slug>/<exercise-slug>
(e.g. fsharp/bird-watcher
)"forked_from"
values must refer to actually implemented exercises"forked_from"
values must not have duplicates"language_versions"
key is optional"language_versions"
value must be a string"representer.version"
key is optional"representer.version"
value must be an integer >= 1"icon"
key is optional"icon"
value must be a kebab-case stringĀ²## General
or ## X. <task>
where X
matches the task number heading in the instructions.md
## 1. Do X
"concepts.slug"
property of one of the concepts in the track's config.json
."blurb"
key is required"blurb"
value must be a non-blank stringĀ¹ with length <= 350"source"
key is optional"source"
value must be a non-blank stringĀ¹"source_url"
key is optional"source_url"
value must be a URL"authors"
key is optional"authors"
value must be an array"authors"
values must be non-blank stringsĀ¹"authors"
values must not have duplicates"authors"
values are treated case-insensitively"contributors"
key is optional"contributors"
value must be an array"contributors"
values must be non-blank stringsĀ¹"contributors"
values must not have duplicates"contributors"
values are treated case-insensitively"authors"
or "contributors"
array (no overlap)"files.solution"
key is required"files.solution"
value must be a non-empty array"files.solution"
values must not have duplicates"files.test"
key is required"files.test"
value must be a non-empty array"files.test"
values must not have duplicates"files.example"
key is required"files.example"
value must be a non-empty array"files.example"
values must not have duplicates"files.editor"
key is optional"files.editor"
value must be an array"files.editor"
values must not have duplicates"files.invalidator"
key is optional"files.invalidator"
value must be an array"files.invalidator"
values must not have duplicates"files.solution"
must exist"files.test"
must exist"files.example"
must exist"files.editor"
must exist"files.invalidator"
must exist"files.solution"
, "files.test"
, "files.example
or "files.invalidator"
array (no overlap)
d
or plsql
, the "files.solution"
and "files.test"
files can overlap"language_versions"
key is optional"language_versions"
value must be a string"test_runner"
key is optional"test_runner"
value must be a boolean"representer.version"
key is optional"representer.version"
value must be an integer >= 1"icon"
key is optional"icon"
value must be a kebab-case stringĀ²introduction.md
or a sibling directory"introduction.authors"
key is optional"introduction.authors"
value must be an array"introduction.authors"
values must be non-blank stringsĀ¹"introduction.authors"
values must not have duplicates"introduction.authors"
values are treated case-insensitively"introduction.authors"
array is non-empty, there must be a non-empty introduction.md
file"introduction.contributors"
key is optional"introduction.contributors"
value must be an array"introduction.contributors"
values must be non-blank stringsĀ¹"introduction.contributors"
values must not have duplicates"introduction.contributors"
values are treated case-insensitively"introduction.contributors"
array is non-empty, there must be a non-empty introduction.md
file"introduction.authors"
or "introduction.contributors"
array (no overlap)"approaches"
key is optional, unless there is a sibling directory present (which contains the approach' files)"approaches"
value must be an array of objects"approaches[].uuid"
key is required"approaches[].uuid"
value must be a unique version 4 UUID stringā¶"approaches[].uuid"
value for each concept must never change"approaches[].slug"
key is required"approaches[].slug"
value must be a kebab-case stringĀ² with length <= 255"approaches[].slug"
value must have a corresponding non-empty <slug>/content.md
file"approaches[].slug"
value must have a corresponding non-empty <slug>/snippet.txt
file"approaches[].title"
key is required"approaches[].title"
value must be a Title Case stringĀ³ with length <= 255"approaches[].blurb"
key is required"approaches[].blurb"
value must be a non-blank stringĀ¹ with length <= 350"approaches[].authors"
key is required"approaches[].authors"
value must be a non-empty array"approaches[].authors"
values must be non-blank stringsĀ¹"approaches[].authors"
values must not have duplicates"approaches[].authors"
values are treated case-insensitively"approaches[].contributors"
key is optional"approaches[].contributors"
value must be an array"approaches[].contributors"
values must be non-blank stringsĀ¹"approaches[].contributors"
values must not have duplicates"approaches[].contributors"
values are treated case-insensitively"approaches[].authors"
or "approaches[].contributors"
array (no overlap)"approaches[].tags"
key is optional"approaches[].tags.all"
key is optional, unless "approaches[].tags.any"
is empty"approaches[].tags.all"
value must be an array"approaches[].tags.all"
values must be non-blank tagā· strings with length <= 255"approaches[].tags.all"
values must not have duplicates"approaches[].tags.any"
key is optional, unless "approaches[].tags.all"
is empty"approaches[].tags.any"
value must be an array"approaches[].tags.any"
values must be non-blank tagā· strings with length <= 255"approaches[].tags.any"
values must not have duplicates"approaches[].tags.not"
key is optional"approaches[].tags.not"
value must be an array"approaches[].tags.not"
values must be non-blank tagā· strings with length <= 255"approaches[].tags.not"
values must not have duplicates"approaches[].slug"
entry exists in the .approaches/config.json
file"approaches[].slug"
entry exists in the .approaches/config.json
fileapproaches.snippet_extension
value in the track's config.json
file"articles"
key is optional, unless there is a sibling directory present (which contains the article' files)"articles"
value must be an array of objects"articles[].uuid"
key is required"articles[].uuid"
value must be a unique version 4 UUID stringā¶"articles[].uuid"
value for each concept must never change"articles[].slug"
key is required"articles[].slug"
value must be a kebab-case stringĀ² with length <= 255"articles[].slug"
value must have a corresponding non-empty <slug>/content.md
file"articles[].slug"
value must have a corresponding non-empty <slug>/snippet.md
file"articles[].title"
key is required"articles[].title"
value must be a Title Case stringĀ³ with length <= 255"articles[].blurb"
key is required"articles[].blurb"
value must be a non-blank stringĀ¹ with length <= 350"articles[].authors"
key is required"articles[].authors"
value must be a non-empty array"articles[].authors"
values must be non-blank stringsĀ¹"articles[].authors"
values must not have duplicates"articles[].authors"
values are treated case-insensitively"articles[].contributors"
key is optional"articles[].contributors"
value must be an array"articles[].contributors"
values must be non-blank stringsĀ¹"articles[].contributors"
values must not have duplicates"articles[].contributors"
values are treated case-insensitively"articles[].authors"
or "articles[].contributors"
array (no overlap)"articles[].slug"
entry exists in the .articles/config.json
file"articles[].slug"
entry exists in the .articles/config.json
file"[].url"
property is required"[].url"
value must be an URL"[].description"
property is required"[].description"
value must be a non-blank stringĀ¹"[].icon_url"
property is optional"[].icon_url"
value must be an URL"blurb"
key is required"blurb"
value must be a non-blank stringĀ¹ with length <= 350"authors"
key is required"authors"
value must be an array"authors"
values must be non-blank stringsĀ¹"authors"
values must not have duplicates"authors"
values are treated case-insensitively"contributors"
key is optional"contributors"
value must be an array"contributors"
values must be non-blank stringsĀ¹"contributors"
values must not have duplicates"contributors"
values are treated case-insensitively"authors"
or "contributors"
array (no overlap)Non-blank string: a string that contains at least one non-whitespace character.
kebab-case string: a string that contains only characters in the range [a-z0-9]
, optionally separated by dashes (e.g. "two-fer"). It must match the regular expression: ^[a-z0-9]+(-[a-z0-9]+)*$
Title Case string: a non-blank string that follows the below guidelines from the Chicago Manual of Style:
Sentence Case string: a non-blank string that follows the below guidelines:
Valid files
pattern: A non-blank stringĀ¹ that specifies a location of a file used in an exercise, relative to the exercise's directory. A pattern may use one of the following placeholders:
%{kebab_slug}
: the kebab-case
exercise slug (e.g. bit-manipulation
)%{snake_slug}
: the snake_case
exercise slug (e.g. bit_manipulation
)%{camel_slug}
: the camelCase
exercise slug (e.g. bitManipulation
)%{pascal_slug}
: the PascalCase
exercise slug (e.g. BitManipulation
)Unique version 4 UUID string: A string that satisfies all of these conditions:
config.json
file of a given Exercism trackconfig.json
file of any other Exercism trackcanonical-data.json
file in https://github.com/exercism/problem-specifications^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
For example, the below UUID has the correct form:
d334ffe3-657e-4725-a950-290b284b6d9f
You can run configlet uuid
to generate a suitable UUID.
Valid analyzer tag
: A non-blank stringĀ¹ that is formatted as <category>:<thing>
.
The <category>
value must be one of:
paradigm
(e.g. paradigm:functional
)technique
(e.g. technique:recursion
)construct
(e.g. construct:bitwise-and
)uses
(e.g. uses:DateTime.add_seconds
)The <thing>
value must a non-blank stringĀ¹.