configlet的主要用途是代码检查:检查某个 track 的配置文件结构是否正确,既要语法正确,也要语义正确。配置有误的 track 可能无法正确同步,可能在网站上显示异常,也可能给用户带来欠佳的体验,因此 configlet 的这些把关检查对维护 Exercism 的完整性起着重要作用。
可以用 lint 命令对某个 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]
以下就是 configlet 会检查的各项规则。
代码检查器应检查所有必需文件是否都已存在。必须存在的非练习专属文件有:
config.jsondocs/ABOUT.mddocs/INSTALLATION.mddocs/LEARNING.mddocs/RESOURCES.mddocs/SNIPPET.txtdocs/TESTS.mdexercises/shared/.docs/help.mdexercises/shared/.docs/tests.md必须存在的概念练习专属文件有:
exercises/concept/<slug>/.docs/hints.mdexercises/concept/<slug>/.docs/instructions.mdexercises/concept/<slug>/.docs/introduction.mdexercises/concept/<slug>/.meta/config.json实践练习也会有类似的清单,但我们还没有定义相应的规范。
config.json 中列出的每个概念都应有以下文件:
concepts/<slug>/about.mdconcepts/<slug>/introduction.mdconcepts/<slug>/links.jsonconfig.json 文件需要满足以下检查:
"language"键是必需的"language"的值必须是长度 <= 255 的非空白字符串¹"slug"键是必需的"slug"的值必须是长度 <= 255 的 kebab-case 字符串²"active"键是必需的"active"的值必须是布尔值"blurb"键是必需的"blurb"的值必须是长度 <= 400 的非空白字符串¹"version"键是必需的"version"的值必须是整数 3
"status.concept_exercises"键是必需的"status.concept_exercises"的值必须是布尔值"status.test_runner"键是必需的"status.test_runner"的值必须是布尔值"status.representer"键是必需的"status.representer"的值必须是布尔值"status.analyzer"键是必需的"status.analyzer"的值必须是布尔值"online_editor.indent_style"键是必需的"online_editor.indent_style"的值必须是字符串 space 或 tab
"online_editor.indent_size"键是必需的"online_editor.indent_size"的值必须是 >= 0 且 <= 8 的整数"online_editor.highlightjs_language"键是可选的"online_editor.highlightjs_language"的值必须是非空白字符串¹"files"键是可选的"files"的值必须是对象"files.solution"键是可选的"files.solution"的值必须是数组"files.solution"的值必须是有效模式⁵"files.solution"的值不能重复"files.test"键是可选的"files.test"的值必须是数组"files.test"的值必须是有效模式⁵"files.test"的值不能重复"files.example"键是可选的"files.example"的值必须是数组"files.example"的值必须是有效模式⁵"files.example"的值不能重复"files.exemplar"键是可选的"files.exemplar"的值必须是数组"files.exemplar"的值必须是有效模式⁵"files.exemplar"的值不能重复"files.editor"键是可选的"files.editor"的值必须是数组"files.editor"的值必须是有效模式⁵"files.editor"的值不能重复"files.invalidator"键是可选的"files.invalidator"的值必须是数组"files.invalidator"的值必须是有效模式⁵"files.invalidator"的值不能重复"files.solution"、"files.test"、"files.example"、"files.exemplar"、"files.editor" 或 "files.invalidator" 数组中的一个里(不能重叠)
d 或 plsql,"files.solution" 和 "files.test" 文件_可以_重叠"files.example 和 "files.exemplar" 文件_可以_重叠"status.test_runner" 等于 true,则 "test_runner.average_run_time"键是必需的"test_runner.average_run_time"的值必须是 > 0 的整数"approaches.snippet_extension"键是必需的"approaches.snippet_extension"的值必须是非空白字符串¹"exercises"键是必需的"exercises.concept"键是必需的"exercises.concept"的值必须是数组"exercises.concept[].slug"键是必需的"exercises.concept[].slug"的值必须是长度 <= 255 的 kebab-case 字符串²"exercises.concept[].slug"的值在 "exercises.concept[].slug" 中必须唯一,且不能出现在 "exercises.practice[].slug" 中"exercises.concept[].name"键是必需的"exercises.concept[].name"的值必须是长度 <= 255 的标题式大写字符串³"exercises.concept[].uuid"键是必需的"exercises.concept[].uuid"的值必须是唯一的版本 4 UUID 字符串⁶"exercises.concept[].uuid" 值都绝不能改变"exercises.concept[].concepts"键是必需的"exercises.concept[].status" 不等于 deprecated,则 "exercises.concept[].concepts"的值必须是由字符串组成的非空数组"exercises.concept[].status" 等于 deprecated,则 "exercises.concept[].concepts"的值必须是空数组"exercises.concept[].concepts"的值必须是 kebab-case 字符串²"exercises.concept[].concepts"的值不能重复"exercises.concept[].concepts"的值不能出现在其他任何概念练习的 "concepts" 属性中"exercises.concept[].concepts"的值必须与其中某个概念的 "concepts.slug" 属性匹配"exercises.concept[].prerequisites"键是必需的"exercises.concept[].status" 不等于 deprecated,则 "exercises.concept[].prerequisites"的值必须是由字符串组成的非空数组,不过恰好有一个练习_可以_以空数组作为它的值"exercises.concept[].status" 等于 deprecated,则 "exercises.concept[].prerequisites"的值必须是空数组"exercises.concept[].prerequisites"的值必须是 kebab-case 字符串²"exercises.concept[].prerequisites"的值不能重复"exercises.concept[].prerequisites"的值必须与任何其他概念练习的 "concepts" 属性值匹配"exercises.concept[].prerequisites"的值不能与该练习 "exercises.concept[].concepts" 属性中的任何值相同"exercises.concept[].prerequisites"的值必须与其中某个概念的 "concepts.slug" 属性匹配"exercises.concept[].concepts" 和 "exercises.concept[].prerequisites" 之间不能存在任何环路"exercises.concept[].status"键是可选的"exercises.concept[].status"的值必须是字符串 wip、beta、active 或 deprecated
"exercises.practice"键是必需的"exercises.practice"的值必须是数组"exercises.practice[].slug"键是必需的"exercises.practice[].slug"的值必须是长度 <= 255 的 kebab-case 字符串²"exercises.practice[].slug"的值在 "exercises.practice[].slug" 中必须唯一,且不能出现在 "exercises.concept[].slug" 中"exercises.practice[].slug" 的值是字符串 hello-world
"exercises.practice[].name"键是必需的"exercises.practice[].name"的值必须是长度 <= 255 的标题式大写字符串³"exercises.practice[].uuid"键是必需的"exercises.practice[].uuid"的值必须是唯一的版本 4 UUID 字符串⁶"exercises.practice[].uuid" 值都绝不能改变"exercises.practice[].difficulty"键是必需的"exercises.practice[].difficulty"的值必须是 >= 1 且 <= 10 的整数"exercises.practice[].practices"键是必需的"exercises.practice[].status" 不等于 deprecated,则 "exercises.practice[].practices"的值必须是由字符串组成的非空数组"exercises.practice[].status" 等于 deprecated,则 "exercises.practice[].practices"的值必须是空数组"exercises.practice[].practices"的值必须是 kebab-case 字符串²"exercises.practice[].practices"的值不能重复"exercises.practice[].practices"的值必须与其中某个概念的 "concepts[].slug" 属性匹配"exercises.practice[].practices"的值在全部练习中最多只能引用某个概念的 slug 10 次"exercises.practice[].prerequisites"键是必需的"exercises.practice[].status" 不等于 deprecated,则 "exercises.practice[].prerequisites"的值必须是由字符串组成的非空数组"exercises.practice[].status" 等于 deprecated,则 "exercises.practice[].prerequisites"的值必须是空数组"exercises.practice[].slug" 等于 hello-world,则 "exercises.practice[].prerequisites"的值必须是空数组"exercises.practice[].prerequisites"的值必须是 kebab-case 字符串²"exercises.practice[].prerequisites"的值不能重复"exercises.practice[].prerequisites"的值必须与任何概念练习的 "exercises.concept[].concepts" 值匹配"exercises.practice[].prerequisites"的值必须与其中某个概念的 "concepts[].slug" 属性匹配"exercises.practice[].status"键是可选的"exercises.practice[].status"的值必须是字符串 wip、beta、active 或 deprecated
"exercises.practice[].slug" 等于 hello-world,则 "exercises.practice[].status"的值必须省略,或者必须是字符串 active
"exercises.foregone"键是可选的"exercises.foregone"的值必须是数组"exercises.foregone"的值必须是 kebab-case 字符串²"exercises.foregone"的值不能重复"exercises.foregone"的值不能与任何概念练习或实践练习的 slug 相同"concepts"键是必需的"concepts"的值必须是数组"concepts[].uuid"键是必需的"concepts[].uuid"的值必须是唯一的版本 4 UUID 字符串⁶"concepts[].uuid" 值都绝不能改变"concepts[].slug"键是必需的"concepts[].slug"的值必须是长度 <= 255 的 kebab-case 字符串²"concepts[].name"键是必需的"concepts[].name"的值必须是长度 <= 255 的标题式大写字符串³"concepts[].tags"键是可选的"concepts[].tags.all"键是可选的,除非 "concepts[].tags.any" 为空"concepts[].tags.all"的值必须是数组"concepts[].tags.all"的值必须是长度 <= 255 的非空白 tag⁷ 字符串"concepts[].tags.all"的值不能重复"concepts[].tags.any"键是可选的,除非 "concepts[].tags.all" 为空"concepts[].tags.any"的值必须是数组"concepts[].tags.any"的值必须是长度 <= 255 的非空白 tag⁷ 字符串"concepts[].tags.any"的值不能重复"concepts[].tags.not"键是可选的"concepts[].tags.not"的值必须是数组"concepts[].tags.not"的值必须是长度 <= 255 的非空白 tag⁷ 字符串"concepts[].tags.not"的值不能重复"concepts" 值都必须有 concept/<concepts.slug>/about.md 文件。该文件的检查规则在下面说明。"concepts" 值都必须有 concept/<concepts.slug>/introduction.md 文件。该文件的检查规则在下面说明。"concepts" 值都必须有 concept/<concepts.slug>/links.json 文件。该文件的检查规则在下面说明。"key_features"键是可选的"key_features"的值必须是长度为 6 的数组"key_features[].icon"键是必需的"key_features[].icon"的值必须使用某个预定义的图标值
"key_features[].title"键是必需的"key_features[].title"的值必须是长度 <= 25 的句首大写字符串⁴"key_features[].content"键是必需的"key_features[].content"的值必须是长度 <= 100 的非空白字符串¹"tags"键是必需的"tags"的值必须是字符串数组"tags"的值不能重复"tags"的值必须使用某个预定义的 tag 值
"blurb"键是必需的"blurb"的值必须是长度 <= 350 的非空白字符串¹"source"键是可选的"source"的值必须是非空白字符串¹"source_url"键是可选的"source_url"的值必须是 URL"authors"键是必需的"authors"的值必须是非空数组"authors"的值必须是非空白字符串¹"authors"的值不能重复"authors"的值按不区分大小写处理"contributors"键是可选的"contributors"的值必须是数组"contributors"的值必须是非空白字符串¹"contributors"的值不能重复"contributors"的值按不区分大小写处理"authors" 或 "contributors" 数组中的一个里(不能重叠)"files.solution"键是必需的"files.solution"的值必须是非空数组"files.solution"的值不能重复"files.test"键是必需的"files.test"的值必须是非空数组"files.test"的值不能重复"files.exemplar"键是必需的"files.exemplar"的值必须是非空数组"files.exemplar"的值不能重复"files.editor"键是可选的"files.editor"的值必须是数组"files.editor"的值不能重复"files.invalidator"键是可选的"files.invalidator"的值必须是数组"files.invalidator"的值不能重复"files.solution" 中列出的文件必须存在"files.test" 中列出的文件必须存在"files.exemplar" 中列出的文件必须存在"files.editor" 中列出的文件必须存在"files.invalidator" 中列出的文件必须存在"files.solution"、"files.test"、"files.exemplar" 或 "files.invalidator" 数组中的一个里(不能重叠)
d 或 plsql,"files.solution" 和 "files.test" 文件_可以_重叠"forked_from"键是可选的"forked_from"的值必须是数组"forked_from"的值必须是格式为 <track-slug>/<exercise-slug> 的字符串(例如 fsharp/bird-watcher)"forked_from"的值必须指向实际已实现的练习"forked_from"的值不能重复"language_versions"键是可选的"language_versions"的值必须是字符串"representer.version"键是可选的"representer.version"的值必须是 >= 1 的整数"icon"键是可选的"icon"的值必须是 kebab-case 字符串²## General 或 ## X. <task>,其中 X 与 instructions.md 中的任务编号标题一致## 1. Do X
config.json 中某个概念的 "concepts.slug" 属性匹配。"blurb"键是必需的"blurb"的值必须是长度 <= 350 的非空白字符串¹"source"键是可选的"source"的值必须是非空白字符串¹"source_url"键是可选的"source_url"的值必须是 URL"authors"键是可选的"authors"的值必须是数组"authors"的值必须是非空白字符串¹"authors"的值不能重复"authors"的值按不区分大小写处理"contributors"键是可选的"contributors"的值必须是数组"contributors"的值必须是非空白字符串¹"contributors"的值不能重复"contributors"的值按不区分大小写处理"authors" 或 "contributors" 数组中的一个里(不能重叠)"files.solution"键是必需的"files.solution"的值必须是非空数组"files.solution"的值不能重复"files.test"键是必需的"files.test"的值必须是非空数组"files.test"的值不能重复"files.example"键是必需的"files.example"的值必须是非空数组"files.example"的值不能重复"files.editor"键是可选的"files.editor"的值必须是数组"files.editor"的值不能重复"files.invalidator"键是可选的"files.invalidator"的值必须是数组"files.invalidator"的值不能重复"files.solution" 中列出的文件必须存在"files.test" 中列出的文件必须存在"files.example" 中列出的文件必须存在"files.editor" 中列出的文件必须存在"files.invalidator" 中列出的文件必须存在"files.solution"、"files.test"、"files.example 或 "files.invalidator" 数组中的一个里(不能重叠)
d 或 plsql,"files.solution" 和 "files.test" 文件_可以_重叠"language_versions"键是可选的"language_versions"的值必须是字符串"test_runner"键是可选的"test_runner"的值必须是布尔值"representer.version"键是可选的"representer.version"的值必须是 >= 1 的整数"icon"键是可选的"icon"的值必须是 kebab-case 字符串²introduction.md 或同级目录"introduction.authors"键是可选的"introduction.authors"的值必须是数组"introduction.authors"的值必须是非空白字符串¹"introduction.authors"的值不能重复"introduction.authors"的值按不区分大小写处理"introduction.authors" 数组非空,则必须有非空的 introduction.md 文件"introduction.contributors"键是可选的"introduction.contributors"的值必须是数组"introduction.contributors"的值必须是非空白字符串¹"introduction.contributors"的值不能重复"introduction.contributors"的值按不区分大小写处理"introduction.contributors" 数组非空,则必须有非空的 introduction.md 文件"introduction.authors" 或 "introduction.contributors" 数组中的一个里(不能重叠)"approaches"键是可选的,除非存在同级目录(里面存放的是各个思路的文件)"approaches"的值必须是对象数组"approaches[].uuid"键是必需的"approaches[].uuid"的值必须是唯一的版本 4 UUID 字符串⁶"approaches[].uuid" 值都绝不能改变"approaches[].slug"键是必需的"approaches[].slug"的值必须是长度 <= 255 的 kebab-case 字符串²"approaches[].slug"的值必须有对应的非空 <slug>/content.md 文件"approaches[].slug"的值必须有对应的非空 <slug>/snippet.txt 文件"approaches[].title"键是必需的"approaches[].title"的值必须是长度 <= 255 的标题式大写字符串³"approaches[].blurb"键是必需的"approaches[].blurb"的值必须是长度 <= 350 的非空白字符串¹"approaches[].authors"键是必需的"approaches[].authors"的值必须是非空数组"approaches[].authors"的值必须是非空白字符串¹"approaches[].authors"的值不能重复"approaches[].authors"的值按不区分大小写处理"approaches[].contributors"键是可选的"approaches[].contributors"的值必须是数组"approaches[].contributors"的值必须是非空白字符串¹"approaches[].contributors"的值不能重复"approaches[].contributors"的值按不区分大小写处理"approaches[].authors" 或 "approaches[].contributors" 数组中的一个里(不能重叠)"approaches[].tags"键是可选的"approaches[].tags.all"键是可选的,除非 "approaches[].tags.any" 为空"approaches[].tags.all"的值必须是数组"approaches[].tags.all"的值必须是长度 <= 255 的非空白 tag⁷ 字符串"approaches[].tags.all"的值不能重复"approaches[].tags.any"键是可选的,除非 "approaches[].tags.all" 为空"approaches[].tags.any"的值必须是数组"approaches[].tags.any"的值必须是长度 <= 255 的非空白 tag⁷ 字符串"approaches[].tags.any"的值不能重复"approaches[].tags.not"键是可选的"approaches[].tags.not"的值必须是数组"approaches[].tags.not"的值必须是长度 <= 255 的非空白 tag⁷ 字符串"approaches[].tags.not"的值不能重复.approaches/config.json 文件中存在匹配的 "approaches[].slug" 条目,则该文件是必需的.approaches/config.json 文件中存在匹配的 "approaches[].slug" 条目,则该文件是必需的config.json 文件中的 approaches.snippet_extension 值"articles"键是可选的,除非存在同级目录(里面存放的是各篇文章的文件)"articles"的值必须是对象数组"articles[].uuid"键是必需的"articles[].uuid"的值必须是唯一的版本 4 UUID 字符串⁶"articles[].uuid" 值都绝不能改变"articles[].slug"键是必需的"articles[].slug"的值必须是长度 <= 255 的 kebab-case 字符串²"articles[].slug"的值必须有对应的非空 <slug>/content.md 文件"articles[].slug"的值必须有对应的非空 <slug>/snippet.md 文件"articles[].title"键是必需的"articles[].title"的值必须是长度 <= 255 的标题式大写字符串³"articles[].blurb"键是必需的"articles[].blurb"的值必须是长度 <= 350 的非空白字符串¹"articles[].authors"键是必需的"articles[].authors"的值必须是非空数组"articles[].authors"的值必须是非空白字符串¹"articles[].authors"的值不能重复"articles[].authors"的值按不区分大小写处理"articles[].contributors"键是可选的"articles[].contributors"的值必须是数组"articles[].contributors"的值必须是非空白字符串¹"articles[].contributors"的值不能重复"articles[].contributors"的值按不区分大小写处理"articles[].authors" 或 "articles[].contributors" 数组中的一个里(不能重叠).articles/config.json 文件中存在匹配的 "articles[].slug" 条目,则该文件是必需的.articles/config.json 文件中存在匹配的 "articles[].slug" 条目,则该文件是必需的"[].url"属性是必需的"[].url"的值必须是 URL"[].description"属性是必需的"[].description"的值必须是非空白字符串¹"[].icon_url"属性是可选的"[].icon_url"的值必须是 URL"blurb"键是必需的"blurb"的值必须是长度 <= 350 的非空白字符串¹"authors"键是必需的"authors"的值必须是数组"authors"的值必须是非空白字符串¹"authors"的值不能重复"authors"的值按不区分大小写处理"contributors"键是可选的"contributors"的值必须是数组"contributors"的值必须是非空白字符串¹"contributors"的值不能重复"contributors"的值按不区分大小写处理"authors" 或 "contributors" 数组中的一个里(不能重叠)非空白字符串:至少包含一个非空白字符的字符串。
kebab-case 字符串:只包含 [a-z0-9] 范围内的字符、可选地用短横线分隔的字符串(例如 “two-fer”)。它必须匹配正则表达式:^[a-z0-9]+(-[a-z0-9]+)*$
标题式大写字符串:遵循下面这些《芝加哥手册》中的指南的非空白字符串:
句首大写字符串:遵循下面这些指南的非空白字符串:
有效的 files 模式:一个非空白字符串¹,用来指定练习中某个文件相对于练习目录的位置。模式可以使用以下占位符之一:
%{kebab_slug}:kebab-case 形式的练习 slug(例如 bit-manipulation)%{snake_slug}:snake_case 形式的练习 slug(例如 bit_manipulation)%{camel_slug}:camelCase 形式的练习 slug(例如 bitManipulation)%{pascal_slug}:PascalCase 形式的练习 slug(例如 BitManipulation)唯一的版本 4 UUID 字符串:满足以下所有条件的字符串:
config.json 文件中只出现一次config.json 文件中canonical-data.json 文件中^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
例如,下面这个 UUID 格式正确:
d334ffe3-657e-4725-a950-290b284b6d9f
你可以运行 configlet uuid 生成一个合适的 UUID。
有效的分析器 tag:格式为 <category>:<thing> 的非空白字符串¹。
<category>的值必须是以下之一:
paradigm(例如 paradigm:functional)technique(例如 technique:recursion)construct(例如 construct:bitwise-and)uses(例如 uses:DateTime.add_seconds)<thing>的值必须是非空白字符串¹。