diff --git a/src/fmt/fmt.nim b/src/fmt/fmt.nim index b3638b64..84566926 100644 --- a/src/fmt/fmt.nim +++ b/src/fmt/fmt.nim @@ -1,5 +1,5 @@ import std/[os, strformat, strutils] -import ".."/[cli, logger, sync/sync_common, sync/sync_filepaths, sync/sync] +import ".."/[cli, logger, sync/sync_common, sync/sync_filepaths, sync/sync, types_track_config] type PathAndFormattedExerciseConfig = object diff --git a/src/helpers.nim b/src/helpers.nim index 9c420a57..9900bc59 100644 --- a/src/helpers.nim +++ b/src/helpers.nim @@ -1,4 +1,4 @@ -import std/[algorithm, os, strformat, strscans, terminal] +import std/[algorithm, os, strformat, strscans, strutils, terminal] import "."/cli template withDir*(dir: string; body: untyped): untyped = @@ -91,3 +91,24 @@ func tidyJsonyMessage*(jsonyMsg, fileContents: string): string = &"({line}, {col}): {jsonyMsgStart}" else: &": {jsonyMsg}" + +proc tidyJsonyErrorMsg*(trackConfigContents: string): string = + let jsonyMsg = getCurrentExceptionMsg() + let details = tidyJsonyMessage(jsonyMsg, trackConfigContents) + const bugNotice = """ + -------------------------------------------------------------------------------- + THIS IS A CONFIGLET BUG. PLEASE REPORT IT. + + The JSON parsing error above should not occur - it indicates a bug in configlet! + + If you are seeing this, please open an issue in this repo: + https://github.com/exercism/configlet + + Please include: + - a copy of the error message above + - the contents of the track `config.json` file at the time `configlet lint` ran + + Thank you. + -------------------------------------------------------------------------------- + """.unindent() + result = &"JSON parsing error:\nconfig.json{details}\n\n{bugNotice}" diff --git a/src/info/info.nim b/src/info/info.nim index 49f27d4f..80cdc73a 100644 --- a/src/info/info.nim +++ b/src/info/info.nim @@ -1,6 +1,6 @@ import std/[algorithm, os, sequtils, sets, strformat, strutils, terminal] import pkg/jsony -import ".."/[cli, lint/track_config] +import ".."/[cli, types_track_config] type ProbSpecsExercises = object @@ -90,7 +90,7 @@ proc conceptsInfo(practiceExercises: seq[PracticeExercise], func getSlugs(practiceExercises: seq[PracticeExercise]): HashSet[string] = result = initHashSet[string](practiceExercises.len) for practiceExercise in practiceExercises: - result.incl practiceExercise.slug + result.incl $practiceExercise.slug proc unimplementedProbSpecsExercises(practiceExercises: seq[PracticeExercise], foregone: HashSet[string], diff --git a/src/lint/track_config.nim b/src/lint/track_config.nim index f3ac3351..f63c811b 100644 --- a/src/lint/track_config.nim +++ b/src/lint/track_config.nim @@ -1,6 +1,5 @@ import std/[json, os, sets, strformat, strutils, tables] -import pkg/jsony -import ".."/[cli, helpers] +import ".."/[helpers, types_track_config] import "."/validators proc hasValidStatus(data: JsonNode; path: Path): bool = @@ -297,89 +296,6 @@ proc satisfiesFirstPass(data: JsonNode; path: Path): bool = ] result = allTrue(checks) -type - Status* = enum - sMissing = "missing" - sWip = "wip" - sBeta = "beta" - sActive = "active" - sDeprecated = "deprecated" - - # We can use a `HashSet` for `concepts`, `prerequisites`, `practices`, and - # `foregone` because the first pass has already checked that each has unique - # values. - ConceptExercise* = object - slug*: string - # name*: string - # uuid*: string - concepts*: HashSet[string] - prerequisites*: HashSet[string] - status*: Status - - PracticeExercise* = object - slug*: string - # name*: string - # uuid*: string - # difficulty*: int - practices*: HashSet[string] - prerequisites*: HashSet[string] - status*: Status - - Exercises* = object - `concept`*: seq[ConceptExercise] - practice*: seq[PracticeExercise] - foregone*: HashSet[string] - - Concept* = object - name*: string - slug*: string - uuid*: string - - Concepts* = seq[Concept] - - FilePatterns* = object - solution*: seq[string] - test*: seq[string] - example*: seq[string] - exemplar*: seq[string] - editor*: seq[string] - invalidator*: seq[string] - - TrackConfig* = object - slug*: string - files*: FilePatterns - exercises*: Exercises - concepts*: Concepts - -proc tidyJsonyErrorMsg(trackConfigContents: string): string = - let jsonyMsg = getCurrentExceptionMsg() - let details = tidyJsonyMessage(jsonyMsg, trackConfigContents) - const bugNotice = """ - -------------------------------------------------------------------------------- - THIS IS A CONFIGLET BUG. PLEASE REPORT IT. - - The JSON parsing error above should not occur - it indicates a bug in configlet! - - If you are seeing this, please open an issue in this repo: - https://github.com/exercism/configlet - - Please include: - - a copy of the error message above - - the contents of the track `config.json` file at the time `configlet lint` ran - - Thank you. - -------------------------------------------------------------------------------- - """.unindent() - result = &"JSON parsing error:\nconfig.json{details}\n\n{bugNotice}" - -proc init*(T: typedesc[TrackConfig]; trackConfigContents: string): T = - ## Deserializes `trackConfigContents` using `jsony` to a `TrackConfig` object. - try: - result = fromJson(trackConfigContents, TrackConfig) - except jsony.JsonError: - let msg = tidyJsonyErrorMsg(trackConfigContents) - showError(msg) - func getConceptSlugs(concepts: Concepts): HashSet[string] = ## Returns a set of every `slug` in the top-level `concepts` array of a track ## `config.json` file. @@ -409,7 +325,7 @@ proc checkPractices(practiceExercises: seq[PracticeExercise]; practicesNotInTopLevelConcepts.incl conceptPracticed # TODO: Eventually make this an error, not a warning. if false: - let msg = &"The Practice Exercise {q practiceExercise.slug} has " & + let msg = &"The Practice Exercise {q $practiceExercise.slug} has " & &"{q conceptPracticed} in its `practices` array, which " & "is not a `slug` in the top-level `concepts` array" b.setFalseAndPrint(msg, path) @@ -453,12 +369,12 @@ proc checkExerciseConcepts(conceptExercises: seq[ConceptExercise]; for conceptTaught in conceptExercise.concepts: # Build a set of every concept taught by a user-facing Concept Exercise if result.containsOrIncl(conceptTaught): - let msg = &"The Concept Exercise {q conceptExercise.slug} has " & + let msg = &"The Concept Exercise {q $conceptExercise.slug} has " & &"{q conceptTaught} in its `concepts`, but that concept " & "appears in the `concepts` of another Concept Exercise" b.setFalseAndPrint(msg, path) if conceptTaught notin conceptSlugs: - let msg = &"The Concept Exercise {q conceptExercise.slug} has " & + let msg = &"The Concept Exercise {q $conceptExercise.slug} has " & &"{q conceptTaught} in its `concepts`, which is not a " & "`slug` in the top-level `concepts` array" b.setFalseAndPrint(msg, path) @@ -512,17 +428,17 @@ proc checkPrerequisites(conceptExercises: seq[ConceptExercise]; for c in conceptExercise.concepts: prerequisitesByConcept[c].add prereq if prereq in conceptExercise.concepts: - let msg = &"The Concept Exercise {q conceptExercise.slug} has " & + let msg = &"The Concept Exercise {q $conceptExercise.slug} has " & &"{q prereq} in both its `prerequisites` and its `concepts`" b.setFalseAndPrint(msg, path) elif prereq notin conceptsTaught: - let msg = &"The Concept Exercise {q conceptExercise.slug} has " & + let msg = &"The Concept Exercise {q $conceptExercise.slug} has " & &"{q prereq} in its `prerequisites`, which is not in the " & "`concepts` array of any other user-facing Concept Exercise" b.setFalseAndPrint(msg, path) if prereq notin conceptSlugs: - let msg = &"The Concept Exercise {q conceptExercise.slug} has " & + let msg = &"The Concept Exercise {q $conceptExercise.slug} has " & &"{q prereq} in its `prerequisites`, which is not a " & "`slug` in the top-level `concepts` array" b.setFalseAndPrint(msg, path) @@ -531,7 +447,7 @@ proc checkPrerequisites(conceptExercises: seq[ConceptExercise]; for conceptExercise in visible(conceptExercises): var hadCycle = false for c in conceptExercise.concepts: - checkForCycle(prerequisitesByConcept, c, @[], conceptExercise.slug, b, + checkForCycle(prerequisitesByConcept, c, @[], $conceptExercise.slug, b, hadCycle, path) proc checkPrerequisites(practiceExercises: seq[PracticeExercise]; @@ -550,7 +466,7 @@ proc checkPrerequisites(practiceExercises: seq[PracticeExercise]; prereqsNotTaught.incl prereq # TODO: Eventually make this an error, not a warning. if false: - let msg = &"The Practice Exercise {q practiceExercise.slug} has " & + let msg = &"The Practice Exercise {q $practiceExercise.slug} has " & &"{q prereq} in its `prerequisites`, which is not in " & "the `concepts` array of any user-facing Concept Exercise" b.setFalseAndPrint(msg, path) @@ -558,7 +474,7 @@ proc checkPrerequisites(practiceExercises: seq[PracticeExercise]; prereqsNotInTopLevelConcepts.incl prereq # TODO: Eventually make this an error, not a warning. if false: - let msg = &"The Practice Exercise {q practiceExercise.slug} has " & + let msg = &"The Practice Exercise {q $practiceExercise.slug} has " & &"{q prereq} in its `prerequisites`, which is not a " & "`slug` in the top-level `concepts` array" b.setFalseAndPrint(msg, path) @@ -596,7 +512,7 @@ func statusMsg(exercise: ConceptExercise | PracticeExercise; else: &"is user-facing (because its `status` key has the value {q $exercise.status})" - result = &"The {exerciseKind} {q exercise.slug} {statusStr}" & + result = &"The {exerciseKind} {q $exercise.slug} {statusStr}" & &", but has {problem}" proc checkExercisesPCP(exercises: seq[ConceptExercise] | seq[PracticeExercise]; @@ -634,9 +550,9 @@ proc checkExercisesPCP(exercises: seq[ConceptExercise] | seq[PracticeExercise]; # Check `prerequisites` when exercise is ConceptExercise: if exercise.prerequisites.len == 0: - conceptExercisesWithEmptyPrereqs.add exercise.slug + conceptExercisesWithEmptyPrereqs.add $exercise.slug else: - if exercise.slug == "hello-world": + if $exercise.slug == "hello-world": if exercise.prerequisites.len > 0: let msg = "The Practice Exercise `hello-world` must have an " & "empty array of `prerequisites`" @@ -682,15 +598,15 @@ proc checkExerciseSlugsAndForegone(exercises: Exercises; b: var bool; var conceptExerciseSlugs = initHashSet[string](exercises.`concept`.len) for conceptExercise in exercises.`concept`: let slug = conceptExercise.slug - if conceptExerciseSlugs.containsOrIncl slug: - let msg = &"There is more than one Concept Exercise with the slug {q slug}" + if conceptExerciseSlugs.containsOrIncl $slug: + let msg = &"There is more than one Concept Exercise with the slug {q $slug}" b.setFalseAndPrint(msg, path) var practiceExerciseSlugs = initHashSet[string](exercises.practice.len) for practiceExercise in exercises.practice: let slug = practiceExercise.slug - if practiceExerciseSlugs.containsOrIncl slug: - let msg = &"There is more than one Practice Exercise with the slug {q slug}" + if practiceExerciseSlugs.containsOrIncl $slug: + let msg = &"There is more than one Practice Exercise with the slug {q $slug}" b.setFalseAndPrint(msg, path) for slug in conceptExerciseSlugs: diff --git a/src/sync/sync.nim b/src/sync/sync.nim index 6efb4053..f862434c 100644 --- a/src/sync/sync.nim +++ b/src/sync/sync.nim @@ -1,5 +1,5 @@ import std/[os, sequtils, strformat, strutils, terminal] -import ".."/[cli, logger] +import ".."/[cli, logger, types_track_config] import "."/[exercises, probspecs, sync_common, sync_docs, sync_filepaths, sync_metadata, sync_tests] diff --git a/src/sync/sync_common.nim b/src/sync/sync_common.nim index 4c95750f..4caef63d 100644 --- a/src/sync/sync_common.nim +++ b/src/sync/sync_common.nim @@ -1,6 +1,6 @@ import std/[algorithm, enumutils, json, options, os, sets, strformat, strutils] import pkg/jsony -import ".."/[cli, helpers, lint/validators] +import ".."/[cli, helpers, lint/validators, types_exercise_config, types_track_config] proc userSaysYes*(syncKind: SyncKind): bool = ## Asks the user if they want to sync the given `syncKind`, and returns `true` @@ -15,31 +15,6 @@ proc userSaysYes*(syncKind: SyncKind): bool = else: stderr.writeLine "Unrecognized response. Please answer [y]es or [n]o." -type - Slug* = distinct string ## A `slug` value in a track `config.json` file is a kebab-case string. - - ConceptExercise* = object - slug*: Slug - - PracticeExercise* = object - slug*: Slug - - Exercises* = object - `concept`*: seq[ConceptExercise] - practice*: seq[PracticeExercise] - - FilePatterns* = object - solution*: seq[string] - test*: seq[string] - exemplar*: seq[string] - example*: seq[string] - editor*: seq[string] - invalidator*: seq[string] - - TrackConfig* = object - exercises*: Exercises - files*: FilePatterns - proc postHook*(e: ConceptExercise | PracticeExercise) = ## Quits with an error message if an `e.slug` value is not a kebab-case ## string. @@ -61,7 +36,6 @@ func getSlugs*(e: seq[ConceptExercise] | seq[PracticeExercise]): seq[Slug] = sort result func len*(slug: Slug): int {.borrow.} -func `$`*(slug: Slug): string {.borrow.} func truncateAndAdd*(s: var string, truncateLen: int, slug: Slug) = ## Truncates `s` to `truncateLen`, then appends `slug`. @@ -85,130 +59,6 @@ func addExerciseConfigPath*(s: var string) = const pathExerciseConfig = DirSep & joinPath(".meta", "config.json") s.add pathExerciseConfig -type - ExerciseKind* = enum - ekConcept = "concept" - ekPractice = "practice" - -# Silence the styleCheck hint for `source_url`. -{.push hint[Name]: off.} - -type - # TODO: can/should we refactor the below types as variant objects? - # - # The difficulty: an exercise `config.json` file does not contain a shared - # key that we can use as a discriminator. - # - # For example, we cannot simply write: - # - # ExerciseConfig* = object - # authors: seq[string] - # contributors: Option[seq[string]] - # files*: Files - # language_versions: string - # case kind*: ExerciseKind - # of ekConcept: - # forked_from: Option[seq[string]] - # icon: string - # of ekPractice: - # test_runner: Option[bool] - # blurb*: string - # source*: string - # source_url*: string - # custom*: Option[JsonNode] - # - # and parse with `jsony.fromJson` because the JSON does not actually contain a - # `kind` key. Furthermore, the unique keys for Practice and Concept exercises - # are optional, and so we cannot use them either - if those keys are missing, - # we cannot determine the `kind`. - # - # However, the `files` key _is_ required, and within that, the `exemplar` and - # `example` keys _are_ required. So while we cannot write: - # - # Files* = object - # solution*: seq[string] - # test*: seq[string] - # editor*: seq[string] - # invalidator*: seq[string] - # case kind*: ExerciseKind - # of ekConcept: - # exemplar*: seq[string] - # of ekPractice: - # example*: seq[string] - # - # (again because the JSON `files` data does not contain a `kind` key) we can - # theoretically determine the `kind` by the presence of the `exemplar` or - # `example` keys. - # - # Alternative hack: inject two `kind` key/value pairs into each exercise - # `.meta/config.json` file after we read it, but before parsing with `jsony`. - - ExerciseConfigKey* = enum - eckAuthors = "authors" - eckContributors = "contributors" - eckFiles = "files" - eckLanguageVersions = "language_versions" - eckForkedFrom = "forked_from" - eckIcon = "icon" - eckTestRunner = "test_runner" - eckBlurb = "blurb" - eckSource = "source" - eckSourceUrl = "source_url" - eckCustom = "custom" - - FilesKey* = enum - fkSolution = "solution" - fkTest = "test" - fkExemplar = "exemplar" - fkExample = "example" - fkEditor = "editor" - fkInvalidator = "invalidator" - - ConceptExerciseFiles* = object - originalKeyOrder: seq[FilesKey] - solution*: seq[string] - test*: seq[string] - exemplar*: seq[string] - editor*: seq[string] - invalidator*: seq[string] - - PracticeExerciseFiles* = object - originalKeyOrder: seq[FilesKey] - solution*: seq[string] - test*: seq[string] - example*: seq[string] - editor*: seq[string] - invalidator*: seq[string] - - ConceptExerciseConfig* = object - originalKeyOrder: seq[ExerciseConfigKey] - authors: seq[string] - contributors: Option[seq[string]] - files*: ConceptExerciseFiles - language_versions: string - forked_from: Option[seq[string]] ## Allowed only for a Concept Exercise. - icon: string ## Allowed only for a Concept Exercise. - blurb*: string - source*: string - source_url*: string - custom*: Option[JsonNode] - - PracticeExerciseConfig* = object - originalKeyOrder*: seq[ExerciseConfigKey] - authors: seq[string] - contributors: Option[seq[string]] - files*: PracticeExerciseFiles - language_versions: string - test_runner*: Option[bool] ## Allowed only for a Practice Exercise. - # The below fields are synced for a Practice Exercise that exists in the - # `exercism/problem-specifications` repo. - blurb*: string - source*: string - source_url*: string - custom*: Option[JsonNode] - -{.pop.} - func identity(s: string): string = s diff --git a/src/sync/sync_docs.nim b/src/sync/sync_docs.nim index 8a8974ad..067aac85 100644 --- a/src/sync/sync_docs.nim +++ b/src/sync/sync_docs.nim @@ -1,5 +1,5 @@ import std/[os, strformat, strutils] -import ".."/[cli, logger] +import ".."/[cli, logger, types_track_config] import "."/sync_common type diff --git a/src/sync/sync_filepaths.nim b/src/sync/sync_filepaths.nim index a280015b..a0151dbb 100644 --- a/src/sync/sync_filepaths.nim +++ b/src/sync/sync_filepaths.nim @@ -1,6 +1,6 @@ import std/[os, strformat, strutils] import pkg/jsony -import ".."/[cli, logger] +import ".."/[cli, logger, types_exercise_config, types_track_config] import "."/sync_common func replace(slug: Slug, sub: char, by: char): string {.borrow.} diff --git a/src/sync/sync_metadata.nim b/src/sync/sync_metadata.nim index 8e0e7bff..9b5ea8e5 100644 --- a/src/sync/sync_metadata.nim +++ b/src/sync/sync_metadata.nim @@ -1,6 +1,6 @@ import std/[os, strformat, strutils] import pkg/parsetoml -import ".."/[cli, logger] +import ".."/[cli, logger, types_exercise_config, types_track_config] import "."/sync_common # Silence the styleCheck hint for `source_url`. diff --git a/src/types_exercise_config.nim b/src/types_exercise_config.nim new file mode 100644 index 00000000..f5a4802b --- /dev/null +++ b/src/types_exercise_config.nim @@ -0,0 +1,120 @@ +import std/[json, options] + +# Silence the styleCheck hint for `source_url`. +{.push hint[Name]: off.} + +type + # TODO: can/should we refactor the below types as variant objects? + # + # The difficulty: an exercise `config.json` file does not contain a shared + # key that we can use as a discriminator. + # + # For example, we cannot simply write: + # + # ExerciseConfig* = object + # authors: seq[string] + # contributors: Option[seq[string]] + # files*: Files + # language_versions: string + # case kind*: ExerciseKind + # of ekConcept: + # forked_from: Option[seq[string]] + # icon: string + # of ekPractice: + # test_runner: Option[bool] + # blurb*: string + # source*: string + # source_url*: string + # custom*: Option[JsonNode] + # + # and parse with `jsony.fromJson` because the JSON does not actually contain a + # `kind` key. Furthermore, the unique keys for Practice and Concept exercises + # are optional, and so we cannot use them either - if those keys are missing, + # we cannot determine the `kind`. + # + # However, the `files` key _is_ required, and within that, the `exemplar` and + # `example` keys _are_ required. So while we cannot write: + # + # Files* = object + # solution*: seq[string] + # test*: seq[string] + # editor*: seq[string] + # invalidator*: seq[string] + # case kind*: ExerciseKind + # of ekConcept: + # exemplar*: seq[string] + # of ekPractice: + # example*: seq[string] + # + # (again because the JSON `files` data does not contain a `kind` key) we can + # theoretically determine the `kind` by the presence of the `exemplar` or + # `example` keys. + # + # Alternative hack: inject two `kind` key/value pairs into each exercise + # `.meta/config.json` file after we read it, but before parsing with `jsony`. + + ExerciseConfigKey* = enum + eckAuthors = "authors" + eckContributors = "contributors" + eckFiles = "files" + eckLanguageVersions = "language_versions" + eckForkedFrom = "forked_from" + eckIcon = "icon" + eckTestRunner = "test_runner" + eckBlurb = "blurb" + eckSource = "source" + eckSourceUrl = "source_url" + eckCustom = "custom" + + FilesKey* = enum + fkSolution = "solution" + fkTest = "test" + fkExemplar = "exemplar" + fkExample = "example" + fkEditor = "editor" + fkInvalidator = "invalidator" + + ConceptExerciseFiles* = object + originalKeyOrder*: seq[FilesKey] + solution*: seq[string] + test*: seq[string] + exemplar*: seq[string] + editor*: seq[string] + invalidator*: seq[string] + + PracticeExerciseFiles* = object + originalKeyOrder*: seq[FilesKey] + solution*: seq[string] + test*: seq[string] + example*: seq[string] + editor*: seq[string] + invalidator*: seq[string] + + ConceptExerciseConfig* = object + originalKeyOrder*: seq[ExerciseConfigKey] + authors*: seq[string] + contributors*: Option[seq[string]] + files*: ConceptExerciseFiles + language_versions*: string + forked_from*: Option[seq[string]] ## Allowed only for a Concept Exercise. + icon*: string ## Allowed only for a Concept Exercise. + blurb*: string + source*: string + source_url*: string + custom*: Option[JsonNode] + + PracticeExerciseConfig* = object + originalKeyOrder*: seq[ExerciseConfigKey] + authors*: seq[string] + contributors*: Option[seq[string]] + files*: PracticeExerciseFiles + language_versions*: string + test_runner*: Option[bool] ## Allowed only for a Practice Exercise. + # The below fields are synced for a Practice Exercise that exists in the + # `exercism/problem-specifications` repo. + blurb*: string + source*: string + source_url*: string + custom*: Option[JsonNode] + +{.pop.} diff --git a/src/types_track_config.nim b/src/types_track_config.nim new file mode 100644 index 00000000..cf64a06c --- /dev/null +++ b/src/types_track_config.nim @@ -0,0 +1,68 @@ +import std/sets +import pkg/jsony +import "."/[cli, helpers] + +type + Slug* = distinct string ## A `slug` value in a track `config.json` file is a kebab-case string. + + Status* = enum + sMissing = "missing" + sWip = "wip" + sBeta = "beta" + sActive = "active" + sDeprecated = "deprecated" + + # We can use a `HashSet` for `concepts`, `prerequisites`, `practices`, and + # `foregone` because the first pass has already checked that each has unique + # values. + ConceptExercise* = object + slug*: Slug + concepts*: HashSet[string] + prerequisites*: HashSet[string] + status*: Status + + PracticeExercise* = object + slug*: Slug + practices*: HashSet[string] + prerequisites*: HashSet[string] + status*: Status + + Exercises* = object + `concept`*: seq[ConceptExercise] + practice*: seq[PracticeExercise] + foregone*: HashSet[string] + + FilePatterns* = object + solution*: seq[string] + test*: seq[string] + exemplar*: seq[string] + example*: seq[string] + editor*: seq[string] + invalidator*: seq[string] + + Concept* = object + name*: string + slug*: string + uuid*: string + + Concepts* = seq[Concept] + + TrackConfig* = object + slug*: string + exercises*: Exercises + files*: FilePatterns + concepts*: Concepts + + ExerciseKind* = enum + ekConcept = "concept" + ekPractice = "practice" + +func `$`*(slug: Slug): string {.borrow.} + +proc init*(T: typedesc[TrackConfig]; trackConfigContents: string): T = + ## Deserializes `trackConfigContents` using `jsony` to a `TrackConfig` object. + try: + result = fromJson(trackConfigContents, TrackConfig) + except jsony.JsonError: + let msg = tidyJsonyErrorMsg(trackConfigContents) + showError(msg) diff --git a/tests/test_fmt.nim b/tests/test_fmt.nim index 960c6f7d..0908d36c 100644 --- a/tests/test_fmt.nim +++ b/tests/test_fmt.nim @@ -1,6 +1,6 @@ import std/[importutils, json, os, options, random, strutils, unittest] import pkg/jsony -import "."/[exec, helpers, sync/sync_common] +import "."/[exec, helpers, sync/sync_common, types_exercise_config] const testsDir = currentSourcePath().parentDir() diff --git a/tests/test_sync.nim b/tests/test_sync.nim index ae431eb2..813f6b91 100644 --- a/tests/test_sync.nim +++ b/tests/test_sync.nim @@ -1,5 +1,5 @@ import std/[importutils, json, os, options, strutils, unittest] -import "."/[exec, helpers, sync/sync_common] +import "."/[exec, helpers, sync/sync_common, types_exercise_config, types_track_config] from "."/sync/sync_filepaths {.all.} import update from "."/sync/sync_metadata {.all.} import UpstreamMetadata, parseMetadataToml, metadataAreUpToDate, update