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 Jan 31, 2021
1 parent 856bd46 commit 12a9220
Show file tree
Hide file tree
Showing 4 changed files with 102 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)
checkStringKey("github_username", isRequired = true)
checkStringKey("exercism_username", isRequired = false)

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

proc isValidConceptExerciseConfig(data: JsonNode, path: string): bool =
checkObject("root")
checkArrayOf("authors", isValidAuthorOrContributor, isRequired = true)
checkArrayOf("contributors", isValidAuthorOrContributor, isRequired = false)
checkArrayOfStrings("", "forked_from", isRequired = false)
isValidFiles(data, "files", path)
checkStringKey("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, strformat, 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 @@ -64,12 +59,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)
54 changes: 54 additions & 0 deletions src/lint/validators.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import std/json
import ".."/helpers

template checkObject*(key: string) =
if data.kind != JObject:
writeError("Not an object: " & key, path)
return false
result = true

template checkStringKey*(key: string, isRequired: bool) =
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: bool) =
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: string, path: string): bool,
isRequired: bool) =
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 12a9220

Please sign in to comment.