Skip to content

Commit

Permalink
fmt, info, lint, sync: extract common types (#564)
Browse files Browse the repository at this point in the history
Reduce duplication by moving types to some new common files:
`types_exercise_config` and `types_track_config`.

This means that, for example, `info` no longer needs to import
`lint/track_config`. However, `fmt` still needs to import modules from
`sync` for now (to obtain common procedures).

Closes: #558

Co-authored-by: ee7 <45465154+ee7@users.noreply.github.com>
  • Loading branch information
ErikSchierboom and ee7 committed May 10, 2022
1 parent 098985d commit 85e262c
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 262 deletions.
2 changes: 1 addition & 1 deletion 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
Expand Down
23 changes: 22 additions & 1 deletion 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 =
Expand Down Expand Up @@ -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}"
4 changes: 2 additions & 2 deletions 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
Expand Down Expand Up @@ -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],
Expand Down
118 changes: 17 additions & 101 deletions 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 =
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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];
Expand All @@ -550,15 +466,15 @@ 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)
if prereq notin conceptSlugs:
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)
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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`"
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion 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]

Expand Down

0 comments on commit 85e262c

Please sign in to comment.