-
-
Notifications
You must be signed in to change notification settings - Fork 16
completions: add completions for bash and fish #629
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Thanks! I'll let ee7 review this, as he is much more well-versed into this than I am. |
Update create-artifact script to include completions in artifact.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this. Very cool.
First pass at a review. Great that it's even got slug completion, but does it find the slugs at "tab time" or at source time? The latter would be a problem when working on multiple tracks. (I haven't tested yet, sorry).
I see that db12f3b..10dd1e0 removed the shell detection - are you leaning towards not trying to detect it?
Edit: Oh, from #615 (comment) I guess you'll make a separate PR for the fetch-configlet changes.
scripts/fetch-configlet
Outdated
| emit_completion_command() { | ||
| local shell file | ||
| if command -v getent >/dev/null; then | ||
| shell=$(getent passwd "${USER}" | cut -d: -f6) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| shell=$(getent passwd "${USER}" | cut -d: -f6) | |
| shell=$(getent passwd "${USER}" | cut -d: -f7) |
The format for /etc/passwd is the below, right?
account:password:UID:GID:GECOS:directory:shell
So we need the seventh field to get the shell?
For example, on Arch Linux:
$ getent passwd "${USER}"
myusername:x:1000:1000::/home/myusername:/bin/zsh
$ getent passwd "${USER}" | cut -d: -f6
/home/myusername
$ getent passwd "${USER}" | cut -d: -f7
/bin/zsh On OpenBSD:
$ getent passwd "${USER}"
myusername:*:1000:1000:myusername:/home/myusername:/bin/kshThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Will fix in next PR.
scripts/fetch-configlet
Outdated
| elif [[ -f /etc/passwd ]]; then | ||
| shell=$(awk -F: -v u="${USER}" '$1 == u {print $6}' /etc/passwd) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| elif [[ -f /etc/passwd ]]; then | |
| shell=$(awk -F: -v u="${USER}" '$1 == u {print $6}' /etc/passwd) | |
| elif [[ -f /etc/passwd ]]; then | |
| shell=$(awk -F: -v u="${USER}" '$1 == u {print $7}' /etc/passwd) |
Ditto.
| _configlet_complete_generate_() { | ||
| _configlet_complete_options_ "$global_opts" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For later: configlet generate will (soon-ish) have the same non-destructive-by-default behavior as configlet fmt, and so support the same options (-u, -y, -e) . So we should remember to update the completions accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When that happens, we can just dispatch:
_configlet_complete_generate_() {
_configlet_complete_fmt_
}
At tab time. |
Instead, it looks like we'll bake the completions into the configlet binary via a new subcommand [1]. [1] exercism#615 (comment)
We tend to use "prob-specs" rather than "prob-spec" to abbreviate
"problem-specifications". Furthermore, configlet used to have a
--prob-specs-dir option (not --prob-spec-dir), and configlet still
prints an advisory message when a user writes --prob-specs-dir.
$ git grep --heading --break --ignore-case 'prob.*spec[^i]'
src/cli.nim
394: if keyNormalized in ["p", "probspecsdir"]:
396: The --prob-specs-dir option was removed in configlet 4.0.0-beta.1 (April 2022).
src/info/info.nim
3:import ".."/[cli, sync/probspecs, types_track_config]
58: ProbSpecsExercises = object
63:proc init(T: typedesc[ProbSpecsExercises], probSpecsDir: ProbSpecsDir): T =
65: for kind, path in walkDir(probSpecsDir / "exercises"):
75:proc unimplementedProbSpecsExercises(practiceExercises: seq[PracticeExercise],
77: probSpecsDir: ProbSpecsDir): string =
79: probSpecsExercises = ProbSpecsExercises.init(probSpecsDir)
83: uWith = probSpecsExercises.withCanonicalData - practiceExerciseSlugs - foregone
84: uWithout = probSpecsExercises.withoutCanonicalData - practiceExerciseSlugs - foregone
133: let probSpecsDir = ProbSpecsDir.init(conf)
135: echo unimplementedProbSpecsExercises(t.exercises.practice, t.exercises.foregone,
136: probSpecsDir)
src/sync/exercises.nim
4:import "."/[probspecs, tracks]
5:export tracks.`$`, probspecs.pretty
11: json*: ProbSpecsTestCase
38: probSpecsTestCases: ProbSpecsTestCases): T =
40: for testCase in probSpecsTestCases:
49:proc new(T: typedesc[ExerciseTestCase], testCase: ProbSpecsTestCase): T =
56:proc getReimplementations(testCases: ProbSpecsTestCases): Table[string, string] =
65:proc init(T: typedesc[ExerciseTestCases], testCases: ProbSpecsTestCases): T =
84:iterator findExercises*(conf: Conf, probSpecsDir: ProbSpecsDir): Exercise {.inline.} =
88: let testCases = getCanonicalTests(probSpecsDir, practiceExercise.slug.string)
src/sync/probspecs.nim
5: ProbSpecsDir* {.requiresInit.} = distinct string
7: ProbSpecsExerciseDir {.requiresInit.} = distinct string
9: ProbSpecsTestCase* = distinct JsonNode
11: ProbSpecsTestCases* = seq[ProbSpecsTestCase]
13:proc `$`(dir: ProbSpecsDir): string {.borrow.}
14:proc dirExists(dir: ProbSpecsDir): bool {.borrow.}
15:proc createDir(dir: ProbSpecsDir) {.borrow.}
16:proc parentDir(path: ProbSpecsDir): string {.borrow.}
17:proc `/`*(head: ProbSpecsDir, tail: string): string {.borrow.}
18:proc `/`(head: ProbSpecsExerciseDir, tail: string): string {.borrow.}
19:proc lastPathPart(path: ProbSpecsExerciseDir): string {.borrow.}
20:proc `[]`(testCase: ProbSpecsTestCase, name: string): JsonNode {.borrow.}
21:proc hasKey(testCase: ProbSpecsTestCase, key: string): bool {.borrow.}
22:proc pretty*(testCase: ProbSpecsTestCase, indent = 2): string {.borrow.}
24:func canonicalDataFile(probSpecsExerciseDir: ProbSpecsExerciseDir): string =
25: probSpecsExerciseDir / "canonical-data.json"
27:func slug(probSpecsExerciseDir: ProbSpecsExerciseDir): string =
28: lastPathPart(probSpecsExerciseDir)
30:proc uuid*(testCase: ProbSpecsTestCase): string =
33:proc description*(testCase: ProbSpecsTestCase): string =
36:func isReimplementation*(testCase: ProbSpecsTestCase): bool =
39:proc reimplements*(testCase: ProbSpecsTestCase): string =
42:proc init(T: typedesc[ProbSpecsTestCases], node: JsonNode, prefix = ""): T =
50: result.add ProbSpecsTestCase(node)
58: result.add ProbSpecsTestCases.init(childNode, prefix)
69:proc parseProbSpecsTestCases(probSpecsExerciseDir: ProbSpecsExerciseDir): ProbSpecsTestCases =
72: let canonicalJsonPath = canonicalDataFile(probSpecsExerciseDir)
74: if slug(probSpecsExerciseDir) == "grains":
78: result = ProbSpecsTestCases.init(j)
80:proc getCanonicalTests*(probSpecsDir: ProbSpecsDir,
81: slug: string): ProbSpecsTestCases =
83: ## `probSpecsDir`.
84: let probSpecsExerciseDir = joinPath(probSpecsDir.string, "exercises",
85: slug).ProbSpecsExerciseDir
86: if fileExists(probSpecsExerciseDir.canonicalDataFile()):
87: result = parseProbSpecsTestCases(probSpecsExerciseDir)
89:proc getNameOfRemote*(probSpecsDir: ProbSpecsDir;
91: ## Returns the name of the remote in `probSpecsDir` that points to `location`
97: &"problem-specifications directory: '{probSpecsDir}'"
98: let remotes = gitCheck(0, ["-C", probSpecsDir.string, "remote", "-v"], msg)
105: &"the cached problem-specifications directory: '{probSpecsDir}'")
111:proc validate(probSpecsDir: ProbSpecsDir, conf: Conf) =
112: ## Raises an error if the given `probSpecsDir` is not a valid
116: logDetailed(&"Using cached 'problem-specifications' dir: {probSpecsDir}")
118: withDir probSpecsDir.string:
124: &"git repository: '{probSpecsDir}'")
128: &"has an unexpected initial commit: '{probSpecsDir}'")
133: &"'{probSpecsDir}'")
139: # prob-specs repo.
144: &"branch: '{probSpecsDir}'")
147: # prob-specs state that hasn't been merged to upstream `main`.
150: &"problem-specifications directory: '{probSpecsDir}'")
157: let remoteName = getNameOfRemote(probSpecsDir, upstreamHost, upstreamLocation)
163: &"problem-specifications directory: '{probSpecsDir}'")
167: &"problem-specifications directory: '{probSpecsDir}'")
169:proc init*(T: typedesc[ProbSpecsDir], conf: Conf): T =
src/sync/sync.nim
3:import "."/[exercises, probspecs, sync_common, sync_docs, sync_filepaths,
86: let probSpecsDir =
88: ProbSpecsDir("this_will_not_be_used")
90: ProbSpecsDir.init(conf)
92: let psExercisesDir = probSpecsDir / "exercises"
119: let exercises = toSeq findExercises(conf, probSpecsDir)
src/sync/sync_docs.nim
19: ProbSpecsSourceKind = enum
25: pssk: ProbSpecsSourceKind): string =
45: pssk: ProbSpecsSourceKind;
69:func toPath(pssk: ProbSpecsSourceKind): string =
src/sync/sync_tests.nim
3:import "."/[exercises, probspecs]
tests/all_tests.nim
7: test_probspecs,
tests/test_binary.nim
2:import "."/[exec, helpers, lint/validators, sync/probspecs]
838: # Don't leave cached prob-specs dir in detached HEAD state.
842: test "can pull changes into cached prob-specs":
854: let probSpecsDir = ProbSpecsDir(psDir) # Don't use `init` (it performs extra setup).
855: let remoteName = getNameOfRemote(probSpecsDir, upstreamHost, upstreamLocation)
868: &"problem-specifications directory: '{probSpecsDir}'")
tests/test_probspecs.nim
1:# This module contains tests for `src/probspecs.nim`
3:import "."/[cli, exec, sync/probspecs]
5:proc getProbSpecsExercises(probSpecsDir: ProbSpecsDir): Table[string,
6: seq[ProbSpecsTestCase]] =
8: ## each exercise in `probSpecsDir`.
9: let pattern = joinPath(probSpecsDir.string, "exercises", "*")
12: result[slug] = getCanonicalTests(probSpecsDir, slug)
22: let probSpecsDir = ProbSpecsDir.init(conf)
23: let probSpecsExercises = getProbSpecsExercises(probSpecsDir)
27: probSpecsExercises.len >= 116
30: let exercise = probSpecsExercises["accumulate"]
36: let firstTestCase = probSpecsExercises["accumulate"][0].JsonNode
52: let exercise = probSpecsExercises["acronym"]
58: let firstTestCase = probSpecsExercises["acronym"][0].JsonNode
tests/test_sync.nim
508: # Don't leave cached prob-specs dir in detached HEAD state.
Be consistent with how it's written for --filepaths on a later line:
complete -c configlet -n "__fish_seen_subcommand_from sync" -l filepaths -f -d 'Populate .meta/config.json "files" entry'
The same inconsistency was in the `configlet --help` message. Blame ee7.
That'll be fixed later.
Let's match the `fetch-configlet` script, which already exists on many
machines, and indents with 2 spaces.
We also use 2 spaces for the scripts everywhere else in this repo (that
is, the scripts in `bin/` and `.github/bin/`).
We could add something like `shfmt --diff -i 2 -ci -sr -kp` to CI in the
future.
$ shfmt --help
usage: shfmt [flags] [path ...]
shfmt formats shell programs. If the only argument is a dash ('-') or no
arguments are given, standard input will be used. If a given path is a
directory, all shell scripts found under that directory will be used.
--version show version and exit
-l, --list list files whose formatting differs from shfmt's
-w, --write write result to file instead of stdout
-d, --diff error with a diff when the formatting differs
-s, --simplify simplify the code
-mn, --minify minify the code to reduce its size (implies -s)
Parser options:
-ln, --language-dialect str bash/posix/mksh/bats, default "auto"
-p, --posix shorthand for -ln=posix
--filename str provide a name for the standard input file
Printer options:
-i, --indent uint 0 for tabs (default), >0 for number of spaces
-bn, --binary-next-line binary ops like && and | may start a line
-ci, --case-indent switch cases will be indented
-sr, --space-redirects redirect operators will be followed by a space
-kp, --keep-padding keep column alignment paddings
-fn, --func-next-line function opening braces are placed on a separate line
Utilities:
-f, --find recursively find all shell files and print the paths
--tojson print syntax tree to stdout as a typed JSON
For more information, see 'man shfmt' and https://github.com/mvdan/sh.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great. Thanks again, and sorry again for the delay in reviewing.
I hadn't used fish for a long time. Looks like they've improved compatibility a lot lately (e.g. support for && and ||, &>, and foo=bar echo $foo). And the completion approach for fish seems nice. Clear and self-documenting.
I took the liberty of pushing a few trivial commits (please yell at me if you object), and I've opened a bunch of follow-up issues. Minor things like #637 (I don't know if it's easy to address) and #642.
Merging.
|
Thanks @ee7! |
|
Released at last: https://github.com/exercism/configlet/releases/tag/4.0.0-beta.6 |
Not being a zsh or PS user, those completions are not included.
Changes to fetch-configlet to detect user's shell and print instructions will follow in a separate PR.
Closes #615