From 94a96d902234fa2438d8c99a38cdfa7e48e151be Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 14 Dec 2022 09:26:19 +0100 Subject: [PATCH 1/6] fmt, lint: support `icon` key for practice exercise The spec previously specified the `icon` key only for a concept exercise, but a practice exercise config.json file can contain this key too [1]. [1] https://github.com/exercism/docs/commit/7e4d87703432 --- src/lint/practice_exercises.nim | 1 + src/sync/sync_common.nim | 7 +++---- src/types_exercise_config.nim | 3 ++- tests/test_fmt.nim | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/lint/practice_exercises.nim b/src/lint/practice_exercises.nim index f2795ea2..487e4f6e 100644 --- a/src/lint/practice_exercises.nim +++ b/src/lint/practice_exercises.nim @@ -38,6 +38,7 @@ proc isValidPracticeExerciseConfig(data: JsonNode; uniqueValues = true), hasValidFiles(data, path, exerciseDir), hasString(data, "language_versions", path, isRequired = false), + hasString(data, "icon", path, isRequired = false, checkIsKebab = true), hasBoolean(data, "test_runner", path, isRequired = false), hasValidRepresenter(data, path), ] diff --git a/src/sync/sync_common.nim b/src/sync/sync_common.nim index c84d843f..c98abb16 100644 --- a/src/sync/sync_common.nim +++ b/src/sync/sync_common.nim @@ -336,14 +336,14 @@ func keyOrderForFmt(e: ConceptExerciseConfig | when e is ConceptExerciseConfig: if e.forked_from.isSome() and e.forked_from.get().len > 0: result.add eckForkedFrom - if e.icon.len > 0: - result.add eckIcon when e is PracticeExerciseConfig: # Strips `"test_runner": true`. if e.test_runner.isSome() and not e.test_runner.get(): result.add eckTestRunner if e.representer.isSome(): result.add eckRepresenter + if e.icon.len > 0: + result.add eckIcon result.add eckBlurb if e.source.isSome(): result.add eckSource @@ -397,8 +397,7 @@ proc pretty*(e: ConceptExerciseConfig | PracticeExerciseConfig, when e is ConceptExerciseConfig: addValOrNull(forked_from, addArray) of eckIcon: - when e is ConceptExerciseConfig: - result.addString("icon", e.icon) + result.addString("icon", e.icon) of eckTestRunner: when e is PracticeExerciseConfig: addValOrNull(test_runner, addBool) diff --git a/src/types_exercise_config.nim b/src/types_exercise_config.nim index 237df4dd..3f8fa6e7 100644 --- a/src/types_exercise_config.nim +++ b/src/types_exercise_config.nim @@ -102,7 +102,7 @@ type language_versions*: string representer*: Option[Representer] forked_from*: Option[seq[string]] ## Allowed only for a Concept Exercise. - icon*: string ## Allowed only for a Concept Exercise. + icon*: string blurb*: string source*: Option[string] source_url*: Option[string] @@ -116,6 +116,7 @@ type language_versions*: string test_runner*: Option[bool] ## Allowed only for a Practice Exercise. representer*: Option[Representer] + icon*: string # The below fields are synced for a Practice Exercise that exists in the # `exercism/problem-specifications` repo. blurb*: string diff --git a/tests/test_fmt.nim b/tests/test_fmt.nim index e0318fb4..fc01e1e2 100644 --- a/tests/test_fmt.nim +++ b/tests/test_fmt.nim @@ -337,8 +337,6 @@ proc testFmt = let val = j["forked_from"] if val.kind == JNull or (val.kind == JArray and val.len == 0): delete(j, "forked_from") - if j["icon"].getStr().len == 0: - delete(j, "icon") when e is PracticeExerciseConfig: let val = j["test_runner"] # Strip `"test_runner": true` @@ -346,6 +344,8 @@ proc testFmt = delete(j, "test_runner") if j["representer"].kind != JObject or j["representer"].len == 0: delete(j, "representer") + if j["icon"].getStr().len == 0: + delete(j, "icon") for k in ["language_versions", "source", "source_url"]: if j[k].getStr().len == 0: delete(j, k) From 59f357123aa3fc64ce307d9149c740c82c0784d1 Mon Sep 17 00:00:00 2001 From: ee7 <45465154+ee7@users.noreply.github.com> Date: Thu, 15 Dec 2022 14:09:02 +0100 Subject: [PATCH 2/6] tests(fmt): test `icon` for practice exercise --- tests/test_fmt.nim | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_fmt.nim b/tests/test_fmt.nim index fc01e1e2..e1977d93 100644 --- a/tests/test_fmt.nim +++ b/tests/test_fmt.nim @@ -72,7 +72,7 @@ proc testFmt = test "omits optional keys that have an empty value - Concept Exercise": let p = ConceptExerciseConfig( originalKeyOrder: @[eckContributors, eckFiles, eckLanguageVersions, - eckForkedFrom, eckIcon, eckRepresenter, eckSource, + eckForkedFrom, eckRepresenter, eckIcon, eckSource, eckSourceUrl, eckCustom], contributors: some(newSeq[string]()), files: ConceptExerciseFiles( @@ -96,7 +96,8 @@ proc testFmt = test "omits optional keys that have an empty value - Practice Exercise": let p = PracticeExerciseConfig( originalKeyOrder: @[eckContributors, eckFiles, eckLanguageVersions, - eckRepresenter, eckSource, eckSourceUrl, eckCustom], + eckRepresenter, eckIcon, eckSource, eckSourceUrl, + eckCustom], contributors: some(newSeq[string]()), files: PracticeExerciseFiles( originalKeyOrder: @[fkEditor], @@ -148,8 +149,8 @@ proc testFmt = ), language_versions: ">=1.2.3", forked_from: some(@["bar/lovely-lasagna"]), - icon: "myicon", representer: some(Representer(version: 42)), + icon: "myicon", blurb: "Learn about the basics of Foo by following a lasagna recipe.", source: some("mysource"), source_url: some("https://example.com"), @@ -180,10 +181,10 @@ proc testFmt = "forked_from": [ "bar/lovely-lasagna" ], - "icon": "myicon", "representer": { "version": 42 }, + "icon": "myicon", "blurb": "Learn about the basics of Foo by following a lasagna recipe.", "source": "mysource", "source_url": "https://example.com", @@ -217,7 +218,7 @@ proc testFmt = test "populated config with random key order - Practice Exercise": var exerciseConfig = PracticeExerciseConfig( originalKeyOrder: @[eckAuthors, eckContributors, eckFiles, - eckLanguageVersions, eckTestRunner, + eckLanguageVersions, eckTestRunner, eckIcon, eckRepresenter, eckBlurb, eckSource, eckSourceUrl, eckCustom], authors: @["author1"], @@ -231,6 +232,7 @@ proc testFmt = ), language_versions: ">=1.2.3", test_runner: some(false), + icon: "myicon", representer: some(Representer(version: 42)), blurb: "Write a function that returns the earned points in a single toss of a Darts game.", source: some("Inspired by an exercise created by a professor Della Paolera in Argentina"), @@ -263,6 +265,7 @@ proc testFmt = "representer": { "version": 42 }, + "icon": "myicon", "blurb": "Write a function that returns the earned points in a single toss of a Darts game.", "source": "Inspired by an exercise created by a professor Della Paolera in Argentina", "source_url": "https://example.com", From 499a151cbb4bb63a085cebde8a4b1a821a9fe3b4 Mon Sep 17 00:00:00 2001 From: ee7 <45465154+ee7@users.noreply.github.com> Date: Thu, 15 Dec 2022 14:09:03 +0100 Subject: [PATCH 3/6] tests(sync): test `icon` for practice exercise --- tests/test_sync.nim | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_sync.nim b/tests/test_sync.nim index 84286970..0fb8d06c 100644 --- a/tests/test_sync.nim +++ b/tests/test_sync.nim @@ -222,8 +222,9 @@ proc testSyncCommon = test "populated Practice Exercise": let exerciseConfig = PracticeExerciseConfig( originalKeyOrder: @[eckAuthors, eckContributors, eckFiles, - eckLanguageVersions, eckTestRunner, eckRepresenter, - eckBlurb, eckSource, eckSourceUrl, eckCustom], + eckLanguageVersions, eckTestRunner, eckIcon, + eckRepresenter, eckBlurb, eckSource, eckSourceUrl, + eckCustom], authors: @["author1"], contributors: some(@["contributor1"]), files: PracticeExerciseFiles( @@ -235,6 +236,7 @@ proc testSyncCommon = ), language_versions: ">=1.2.3", test_runner: some(false), + icon: "myicon", representer: some(Representer(version: 42)), blurb: "Write a function that returns the earned points in a single toss of a Darts game.", source: some("Inspired by an exercise created by a professor Della Paolera in Argentina"), @@ -264,6 +266,7 @@ proc testSyncCommon = }, "language_versions": ">=1.2.3", "test_runner": false, + "icon": "myicon", "representer": { "version": 42 }, From a273070fcff8f0c69cfc8aacb811661e13387a7e Mon Sep 17 00:00:00 2001 From: ee7 <45465154+ee7@users.noreply.github.com> Date: Thu, 15 Dec 2022 16:29:01 +0100 Subject: [PATCH 4/6] README, lint, sync, types: make `icon` key ordering consistent Match the key ordering in the spec. --- README.md | 2 +- src/lint/practice_exercises.nim | 2 +- src/sync/sync_common.nim | 4 ++-- src/types_exercise_config.nim | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0b0d0e38..7264b907 100644 --- a/README.md +++ b/README.md @@ -329,8 +329,8 @@ The canonical key order for an exercise `.meta/config.json` file is: - [invalidator] - [language_versions] - [forked_from] (Concept Exercises only) -- [icon] (Concept Exercises only) - [test_runner] (Practice Exercises only) +- [icon] - blurb - [source] - [source_url] diff --git a/src/lint/practice_exercises.nim b/src/lint/practice_exercises.nim index 487e4f6e..c8377f15 100644 --- a/src/lint/practice_exercises.nim +++ b/src/lint/practice_exercises.nim @@ -38,9 +38,9 @@ proc isValidPracticeExerciseConfig(data: JsonNode; uniqueValues = true), hasValidFiles(data, path, exerciseDir), hasString(data, "language_versions", path, isRequired = false), - hasString(data, "icon", path, isRequired = false, checkIsKebab = true), hasBoolean(data, "test_runner", path, isRequired = false), hasValidRepresenter(data, path), + hasString(data, "icon", path, isRequired = false, checkIsKebab = true), ] result = allTrue(checks) diff --git a/src/sync/sync_common.nim b/src/sync/sync_common.nim index c98abb16..e721e702 100644 --- a/src/sync/sync_common.nim +++ b/src/sync/sync_common.nim @@ -396,14 +396,14 @@ proc pretty*(e: ConceptExerciseConfig | PracticeExerciseConfig, of eckForkedFrom: when e is ConceptExerciseConfig: addValOrNull(forked_from, addArray) - of eckIcon: - result.addString("icon", e.icon) of eckTestRunner: when e is PracticeExerciseConfig: addValOrNull(test_runner, addBool) of eckRepresenter: if e.representer.isSome(): result.addRepresenter(e.representer.get()); + of eckIcon: + result.addString("icon", e.icon) of eckBlurb: result.addString("blurb", e.blurb) of eckSource: diff --git a/src/types_exercise_config.nim b/src/types_exercise_config.nim index 3f8fa6e7..c83f4da0 100644 --- a/src/types_exercise_config.nim +++ b/src/types_exercise_config.nim @@ -19,9 +19,9 @@ type # case kind*: ExerciseKind # of ekConcept: # forked_from: Option[seq[string]] - # icon: string # of ekPractice: # test_runner: Option[bool] + # icon: string # blurb*: string # source*: string # source_url*: string @@ -59,9 +59,9 @@ type eckFiles = "files" eckLanguageVersions = "language_versions" eckForkedFrom = "forked_from" - eckIcon = "icon" eckTestRunner = "test_runner" eckRepresenter = "representer" + eckIcon = "icon" eckBlurb = "blurb" eckSource = "source" eckSourceUrl = "source_url" From 13df6c04b20493be969cab0b23aaccce2484894c Mon Sep 17 00:00:00 2001 From: ee7 <45465154+ee7@users.noreply.github.com> Date: Thu, 15 Dec 2022 16:29:02 +0100 Subject: [PATCH 5/6] README: add representer.version We should have added this in c7b14f7c8233. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7264b907..5b8158c0 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,8 @@ The canonical key order for an exercise `.meta/config.json` file is: - [language_versions] - [forked_from] (Concept Exercises only) - [test_runner] (Practice Exercises only) +- [representer] + - version - [icon] - blurb - [source] From 1c5be5824a719b2d7a61519742d466e8d243fee0 Mon Sep 17 00:00:00 2001 From: ee7 <45465154+ee7@users.noreply.github.com> Date: Thu, 15 Dec 2022 16:29:03 +0100 Subject: [PATCH 6/6] lint, types_exercise_config: use canonical key ordering --- src/lint/concept_exercises.nim | 2 +- src/types_exercise_config.nim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lint/concept_exercises.nim b/src/lint/concept_exercises.nim index c80f1cb2..f02493b7 100644 --- a/src/lint/concept_exercises.nim +++ b/src/lint/concept_exercises.nim @@ -36,9 +36,9 @@ proc isValidConceptExerciseConfig(data: JsonNode; hasArrayOfStrings(data, "contributors", path, isRequired = false, uniqueValues = true), hasValidFiles(data, path, exerciseDir), + hasString(data, "language_versions", path, isRequired = false), hasArrayOfStrings(data, "forked_from", path, isRequired = false, uniqueValues = true), - hasString(data, "language_versions", path, isRequired = false), hasValidRepresenter(data, path), hasString(data, "icon", path, isRequired = false, checkIsKebab = true), ] diff --git a/src/types_exercise_config.nim b/src/types_exercise_config.nim index c83f4da0..f7b16c69 100644 --- a/src/types_exercise_config.nim +++ b/src/types_exercise_config.nim @@ -100,8 +100,8 @@ type contributors*: Option[seq[string]] files*: ConceptExerciseFiles language_versions*: string - representer*: Option[Representer] forked_from*: Option[seq[string]] ## Allowed only for a Concept Exercise. + representer*: Option[Representer] icon*: string blurb*: string source*: Option[string]