Skip to content

Commit 658fef6

Browse files
committed
sync: improve error handling
Replace: - `showError` with `errorAndHelp` - (`stderr.writeLine msg` then `quit 1`) with `error` Refs: 123 Refs: 325
1 parent ceaad10 commit 658fef6

10 files changed

Lines changed: 50 additions & 54 deletions

File tree

src/cli.nim

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import std/[os, parseutils, strformat, strutils, terminal]
22
import pkg/[cligen/parseopt3, supersnappy]
33

4+
type
5+
ConfigletError* = object of CatchableError ## Quit with exit code 1
6+
ConfigletErrorAndHelp* = object of CatchableError ## Quit with exit code 1, and print help
7+
8+
func error*(s: string) =
9+
raise newException(ConfigletError, s)
10+
11+
func errorAndHelp*(s: string) =
12+
raise newException(ConfigletErrorAndHelp, s)
13+
414
type
515
ActionKind* = enum
616
actNil = "nil"

src/configlet.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ proc configlet =
3131
proc main =
3232
try:
3333
configlet()
34+
except ConfigletError:
35+
let s = getCurrentExceptionMsg()
36+
showError(s, writeHelp = false)
3437
except CatchableError:
3538
let msg = getCurrentExceptionMsg()
3639
showError(msg)

src/sync/probspecs.nim

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ proc getNameOfRemote*(probSpecsDir: ProbSpecsDir;
101101
discard line.scanf("$s$w$s$+fetch)$.", remoteName, remoteUrl)
102102
if remoteUrl.contains(host) and remoteUrl.contains(location):
103103
return remoteName
104-
showError(&"there is no remote that points to '{location}' at '{host}' in " &
105-
&"the cached problem-specifications directory: '{probSpecsDir}'")
104+
errorAndHelp &"there is no remote that points to '{location}' at '{host}' in " &
105+
&"the cached problem-specifications directory: '{probSpecsDir}'"
106106

107107
func isOffline(conf: Conf): bool =
108108
(conf.action.kind == actSync and conf.action.offline) or
@@ -124,8 +124,8 @@ proc validate(probSpecsDir: ProbSpecsDir, conf: Conf) =
124124
&"git repository: '{probSpecsDir}'")
125125

126126
if rootCommitRef != "8ba81069dab8e96a53630f3e51446487b6ec9212\n":
127-
showError("the git repo at the cached problem-specifications location " &
128-
&"has an unexpected initial commit: '{probSpecsDir}'")
127+
errorAndHelp "the git repo at the cached problem-specifications location " &
128+
&"has an unexpected initial commit: '{probSpecsDir}'"
129129

130130
# Exit if the working directory is not clean (allowing untracked files).
131131
discard gitCheck(0, ["diff-index", "--quiet", "HEAD"], "the cached " &
@@ -189,19 +189,17 @@ proc init*(T: typedesc[ProbSpecsDir], conf: Conf): T =
189189
validate(result, conf)
190190
elif isOffline(conf):
191191
let msg = fmt"""
192-
Error: --offline was passed, but there is no cached 'problem-specifications' repo at:
192+
--offline was passed, but there is no cached 'problem-specifications' repo at:
193193
'{result}'
194194
Please run once without --offline to clone 'problem-specifications' to that location.
195195
196196
If you currently have no (or limited) network connectivity, but you do have a local
197197
'problem-specifications' elsewhere, you can copy it to the above location and then
198198
use it with --offline.""".unindent()
199-
stderr.writeLine msg
200-
quit 1
199+
error msg
201200
else:
202201
try:
203202
createDir result.parentDir()
204203
except IOError, OSError:
205-
stderr.writeLine &"Error: {getCurrentExceptionMsg()}"
206-
quit 1
204+
error getCurrentExceptionMsg()
207205
cloneExercismRepo("problem-specifications", result.string, shallow = false)

src/sync/sync.nim

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ proc validate(conf: Conf) =
2121
2222
If no syncing scope option is provided, configlet uses the full syncing scope.
2323
If '--tests' is provided without an argument, configlet uses the 'choose' mode.""".dedent(8)
24-
showError(msg)
24+
errorAndHelp msg
2525
if not conf.action.yes and not isatty(stdin):
2626
let intersection = conf.action.scope * {skDocs, skFilepaths, skMetadata}
2727
if intersection.len > 0:
@@ -37,7 +37,7 @@ proc validate(conf: Conf) =
3737
that configlet performs no changes
3838
- or run the same command in an interactive terminal, to update by answering
3939
prompts""".dedent(10)
40-
showError(msg)
40+
errorAndHelp msg
4141

4242
type
4343
TrackExerciseSlugs* = object
@@ -74,11 +74,9 @@ proc getSlugs*(exercises: Exercises, conf: Conf,
7474
result.`concept`.setLen 0
7575
result.practice = @[userExercise]
7676
else:
77-
let msg = &"The `-e, --exercise` option was used to specify an " &
78-
&"exercise slug, but `{userExercise}` is not an slug in the " &
79-
&"track config:\n{trackConfigPath}"
80-
stderr.writeLine msg
81-
quit 1
77+
error "The `-e, --exercise` option was used to specify an exercise " &
78+
&"slug, but `{userExercise}` is not an slug in the track config:" &
79+
&"\n{trackConfigPath}"
8280

8381
proc syncImpl(conf: Conf): set[SyncKind] =
8482
## Checks the data specified in `conf.action.scope`, and updates them if

src/sync/sync_common.nim

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ proc postHook*(e: ConceptExercise | PracticeExercise) =
2020
## string.
2121
let s = e.slug.string
2222
if not isKebabCase(s):
23-
let msg = "Error: the track `config.json` file contains " &
24-
&"an exercise slug of \"{s}\", which is not a kebab-case string"
25-
stderr.writeLine msg
26-
quit 1
23+
let msg = "the track `config.json` file contains an exercise slug of " &
24+
&"\"{s}\", which is not a kebab-case string"
25+
error msg
2726

2827
func getSlugs*(e: seq[ConceptExercise] | seq[PracticeExercise],
2928
withDeprecated = true): seq[Slug] =
@@ -107,24 +106,22 @@ func renameHook*(f: var (ConceptExerciseFiles | PracticeExerciseFiles); key: str
107106

108107
proc parseFile*(path: string, T: typedesc): T =
109108
## Parses the JSON file at `path` into `T`.
110-
let contents =
109+
let contents = block:
110+
var res = ""
111111
try:
112-
readFile(path)
112+
res = readFile(path)
113113
except IOError:
114-
let msg = getCurrentExceptionMsg()
115-
stderr.writeLine &"Error: {msg}"
116-
quit 1
114+
error getCurrentExceptionMsg()
115+
res
117116
if contents.len > 0:
118117
try:
119-
contents.fromJson(T)
118+
result = contents.fromJson(T)
120119
except jsony.JsonError:
121120
let jsonyMsg = getCurrentExceptionMsg()
122121
let details = tidyJsonyMessage(jsonyMsg, contents)
123-
let msg = &"JSON parsing error:\n{path}{details}"
124-
stderr.writeLine msg
125-
quit 1
122+
error &"JSON parsing error:\n{path}{details}"
126123
else:
127-
T()
124+
result = T()
128125

129126
func addNewlineAndIndent(s: var string, indentLevel: int) =
130127
## Appends a newline and spaces (given by `indentLevel` multiplied by 2) to
@@ -269,9 +266,7 @@ proc addObject(s: var string; key: string; val: JsonNode; indentLevel = 1) =
269266
else:
270267
s.add c
271268
else:
272-
stderr.writeLine &"The value of a `{key}` key is not a JSON object:"
273-
stderr.writeLine val.pretty()
274-
quit 1
269+
error &"The value of a `{key}` key is not a JSON object:\n{val.pretty()}"
275270

276271
func keyOrderForSync(originalKeyOrder: seq[ExerciseConfigKey]): seq[ExerciseConfigKey] =
277272
if originalKeyOrder.len == 0:

src/sync/sync_docs.nim

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,7 @@ proc write(pairsToWrite: seq[PathAndContents]) =
122122
createDir path.parentDir()
123123
writeFile(path, pathAndContents.contents)
124124
else:
125-
stderr.writeLine &"Unexpected path before writing: {path}"
126-
quit 1
125+
error &"Unexpected path before writing: {path}"
127126
let s = if pairsToWrite.len > 1: "s" else: ""
128127
logNormal(&"Updated the docs for {pairsToWrite.len} Practice Exercise{s}")
129128

src/sync/sync_filepaths.nim

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,7 @@ proc write(configPairs: seq[PathAndUpdatedExerciseConfig]) =
147147
configPair.exerciseConfig.p.pretty(prettyMode = pmSync)
148148
writeFile(path, contents)
149149
else:
150-
stderr.writeLine &"Unexpected path before writing: {path}"
151-
quit 1
150+
error &"Unexpected path before writing: {path}"
152151
let s = if configPairs.len > 1: "s" else: ""
153152
logNormal(&"Updated the filepaths for {configPairs.len} exercise{s}")
154153

src/sync/sync_metadata.nim

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,7 @@ proc write(configPairs: seq[PathAndUpdatedConfig]) =
100100
createDir path.parentDir()
101101
writeFile(path, updatedJson)
102102
else:
103-
stderr.writeLine &"Unexpected path before writing: {path}"
104-
quit 1
103+
error &"Unexpected path before writing: {path}"
105104
let s = if configPairs.len > 1: "s" else: ""
106105
logNormal(&"Updated the metadata for {configPairs.len} Practice Exercise{s}")
107106

src/sync/tracks.nim

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,9 @@ proc getPracticeExerciseSlugs(trackDir: TrackDir,
4848
let slug = exercise["slug"].getStr()
4949
result.add PracticeExerciseSlug(slug)
5050
else:
51-
stderr.writeLine "Error: file does not have an `exercises` key:\n" &
52-
configFile
53-
quit(1)
51+
error &"file does not have an `exercises` key:\n{configFile}"
5452
else:
55-
stderr.writeLine "Error: file does not exist:\n" & configFile
56-
quit(1)
57-
53+
error &"file does not exist:\n{configFile}"
5854
sort result
5955

6056
func init(T: typedesc[PracticeExerciseTests]): T =
@@ -68,21 +64,20 @@ proc init(T: typedesc[PracticeExerciseTests], testsPath: string): T =
6864
## included and excluded test case UUIDs.
6965
result = PracticeExerciseTests.init()
7066
if fileExists(testsPath):
71-
let tests =
67+
let tests = block:
68+
var res = TomlValueRef()
7269
try:
73-
parsetoml.parseFile(testsPath)
70+
res = parsetoml.parseFile(testsPath)
7471
except TomlError: # Note that `TomlError` inherits from `Defect`.
75-
stderr.writeLine fmt"""
72+
let msg = fmt"""
7673
77-
Error: A 'tests.toml' file contains invalid TOML:
74+
a 'tests.toml' file contains invalid TOML:
7875
{getCurrentExceptionMsg()}
7976
8077
The expected 'tests.toml' format is documented in
8178
https://exercism.org/docs/building/configlet/sync#h-tests""".unindent()
82-
quit 1
83-
except CatchableError:
84-
stderr.writeLine "Error: " & getCurrentExceptionMsg()
85-
quit 1
79+
error msg
80+
res
8681

8782
for uuid, val in tests.getTable():
8883
if val.hasKey("include"):

tests/test_binary.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ proc testsForSync(binaryPath: static string) =
148148
suite "sync, for an exercise that does not exist (prints the expected output, and exits with 1)":
149149
test "-e foo":
150150
const expectedOutput = fmt"""
151-
The `-e, --exercise` option was used to specify an exercise slug, but `foo` is not an slug in the track config:
151+
Error: The `-e, --exercise` option was used to specify an exercise slug, but `foo` is not an slug in the track config:
152152
{trackDir / "config.json"}
153153
""".unindent()
154154
execAndCheck(1, &"{syncOffline} -e foo --docs", expectedOutput)

0 commit comments

Comments
 (0)