Skip to content

Commit

Permalink
lint: begin linting concept exercise config.json
Browse files Browse the repository at this point in the history
  • Loading branch information
ee7 committed Feb 3, 2021
1 parent 357f313 commit 7d2e999
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 8 deletions.
6 changes: 6 additions & 0 deletions src/helpers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ proc getSortedSubdirs*(dir: string): seq[string] =
if kind == pcDir:
result.add path
sort result

template writeError*(description: string, details: string) =
stdout.styledWriteLine(fgRed, description & ":")
stdout.writeLine(details)
stdout.write "\n"
result = false
37 changes: 37 additions & 0 deletions src/lint/concept_exercises.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import std/[json, os, terminal]
import ".."/helpers
import "."/validators

proc isValidAuthorOrContributor(data: JsonNode, key: string, path: string): bool =
checkObject(key)
checkString("github_username")
checkString("exercism_username")

template checkFiles(data: JsonNode, context, path: string) =
checkObject(context)
checkArrayOfStrings(context, "solution")
checkArrayOfStrings(context, "test")
checkArrayOfStrings(context, "exemplar")

proc isValidConceptExerciseConfig(data: JsonNode, path: string): bool =
checkObject("")
checkArrayOf("authors", isValidAuthorOrContributor)
checkArrayOf("contributors", isValidAuthorOrContributor, isRequired = false)
checkFiles(data, "files", path)
checkArrayOfStrings("", "forked_from", isRequired = false)
checkString("language_versions", isRequired = false)

proc isEveryConceptExerciseConfigValid*(trackDir: string): bool =
let conceptExercisesDir = trackDir / "exercises" / "concept"
result = true
if dirExists(conceptExercisesDir):
for exerciseDir in getSortedSubdirs(conceptExercisesDir):
let configPath = exerciseDir / ".meta" / "config.json"
let j =
try:
parseFile(configPath)
except:
writeError("JSON parsing error", getCurrentExceptionMsg())
continue
if not isValidConceptExerciseConfig(j, configPath):
result = false
13 changes: 5 additions & 8 deletions src/lint/lint.nim
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import std/[json, os, terminal]
import ".."/[cli, helpers]

template writeError(description: string, details: string) =
stdout.styledWriteLine(fgRed, description & ":")
stdout.writeLine(details)
stdout.write "\n"
result = false
import "."/concept_exercises

proc isValidTrackConfig(trackDir: string): bool =
result = true
Expand Down Expand Up @@ -68,12 +63,14 @@ proc lint*(conf: Conf) =
let b1 = isValidTrackConfig(trackDir)
let b2 = conceptExerciseFilesExist(trackDir)
let b3 = conceptFilesExist(trackDir)
let b4 = isEveryConceptExerciseConfigValid(trackDir)

if b1 and b2 and b3:
if b1 and b2 and b3 and b4:
echo """
Basic linting finished successfully:
- config.json exists and is valid JSON
- Every concept exercise has the required .md files and a .meta/config.json file
- Every concept has the required .md files and links.json file"""
- Every concept has the required .md files and links.json file
- Every concept exercise .meta/config.json file is valid"""
else:
quit(1)
62 changes: 62 additions & 0 deletions src/lint/validators.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import std/json
import ".."/helpers

template checkObject*(key: string) =
if key.len == 0:
if data.kind != JObject:
writeError("JSON root is not an object", path)
return false
elif data.hasKey(key):
if data[key].kind != JObject:
writeError("Not an object: " & key, path)
return false
else:
writeError("Missing key: " & key, path)
return false
result = true

template checkString*(key: string, isRequired = true) =
if data.hasKey(key):
if data[key].kind == JString:
if data[key].getStr().len == 0:
writeError("String is zero-length: " & key, path)
else:
writeError("Not a string: `" & key & ": " & $data[key] & "`", path)
elif isRequired:
writeError("Missing key: " & key, path)

template checkArrayOfStrings*(context, key: string; isRequired = true) =
if data.hasKey(key):
if data[key].kind == JArray:
if data[key].len == 0:
writeError("Array is empty: " & key, path)
else:
for item in data[key]:
if item.kind != JString:
result = false
# writeError("Array contains item with the wrong kind" & key & $item, path)
# break
elif item.getStr().len == 0:
writeError("Array contains zero-length string: " & key, path)
else:
writeError("Not an array: " & key, path)
elif isRequired:
writeError("Missing key: " & context & "." & key, path)

template checkArrayOf*(key: string,
call: proc(d: JsonNode; key, path: string): bool,
isRequired = true) =
if data.hasKey(key):
if data[key].kind == JArray:
if data[key].len == 0:
writeError("Array is empty: " & key, path)
else:
for item in data[key]:
if not call(item, key, path):
result = false
# writeError("Item in array fails check: " & $item, path)
# break
else:
writeError("Not an array: " & key, path)
elif isRequired:
writeError("Missing key: " & key, path)

0 comments on commit 7d2e999

Please sign in to comment.