Skip to content

Commit

Permalink
✨ fuzzy finding on options
Browse files Browse the repository at this point in the history
  • Loading branch information
jcaillon committed Jun 25, 2024
1 parent 1dba5eb commit f1f13ce
Show file tree
Hide file tree
Showing 11 changed files with 315 additions and 135 deletions.
3 changes: 1 addition & 2 deletions docs/content/docs/800.roadmap/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ url: /docs/roadmap

This page lists the features that I would like to implement in Valet. They come in addition to new features described in the [issues][valet-issues].

- We can have fuzzy matching on options too; just make sure it is not ambiguous.
- Allow to regroup single letter options (e.g. -fsSL).
- Add full support for interactive mode.
- For dropdown with a set list of options, we can verify that the input value is one of the expected value.
- Generate an autocompletion script for bash and zsh.
Expand All @@ -28,6 +26,7 @@ This page lists the features that I would like to implement in Valet. They come
- add valet in brew
- Improve the self install script / check for updates by comparing the version number / suggest the user to git pull the repositories existing under .valet.d. Also add snippets and all functions...
- For argument and option autocompletion, accept any multiline string that will be eval and that should set RETURNED_ARRAY with the list of possible completion.
- Allow to regroup single letter options (e.g. -fsSL).


[valet-issues]: https://github.com/jcaillon/valet/issues
22 changes: 19 additions & 3 deletions tests.d/1001-main-functions/02.arguments-parser.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,56 @@

function testMain::parseFunctionArguments() {

# missing argument
echo "→ main::parseFunctionArguments selfMock2"
main::parseFunctionArguments selfMock2 && echo "${RETURNED_VALUE}"
echo

# ok
echo "→ main::parseFunctionArguments selfMock2 -o -2 optionValue2 arg1 more1 more2"
main::parseFunctionArguments selfMock2 -o -2 optionValue2 arg1 more1 more2 && echo "${RETURNED_VALUE}"
echo

# missing argument
echo "→ main::parseFunctionArguments selfMock2 -o -2 optionValue2 arg1"
main::parseFunctionArguments selfMock2 -o -2 optionValue2 arg1 && echo "${RETURNED_VALUE}"
echo

# unknown options
echo "→ main::parseFunctionArguments selfMock2 -unknown -what optionValue2 arg"
main::parseFunctionArguments selfMock2 -unknown -what optionValue2 arg && echo "${RETURNED_VALUE}"
echo

# ok with the option at the end
echo "→ main::parseFunctionArguments selfMock2 arg more1 more2 -o"
main::parseFunctionArguments selfMock2 arg more1 more2 -o && echo "${RETURNED_VALUE}"
echo

# fuzzy match the option -this
echo "→ main::parseFunctionArguments selfMock2 -this arg more1"
main::parseFunctionArguments selfMock2 -this arg more1 && echo "${RETURNED_VALUE}"

echo

# ok, --option1 is interpreted as the value for --this-is-option2
echo "→ main::parseFunctionArguments selfMock2 --this-is-option2 --option1 arg more1"
main::parseFunctionArguments selfMock2 --this-is-option2 --option1 arg more1 && echo "${RETURNED_VALUE}"

echo

# ok only args
echo "→ main::parseFunctionArguments selfMock4 arg1 arg2"
main::parseFunctionArguments selfMock4 arg1 arg2 && echo "${RETURNED_VALUE}"

echo

# ok with -- to separate options from args
echo "→ main::parseFunctionArguments selfMock2 -- --arg1-- --arg2--"
main::parseFunctionArguments selfMock2 -- --arg1-- --arg2-- && echo "${RETURNED_VALUE}"
echo
echo

# ambiguous fuzzy match
echo "→ main::parseFunctionArguments selfMock2 arg1 arg2 --th"
main::parseFunctionArguments selfMock2 arg1 arg2 --th && echo "${RETURNED_VALUE}"
echo

test::endTest "Testing main::parseFunctionArguments" 0
}
Expand Down
27 changes: 21 additions & 6 deletions tests.d/1001-main-functions/99.tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,31 @@ function testGetMaxPossibleCommandLevel() {

function testFuzzyFindOption() {

echo "→ main::fuzzyFindOption '--opt1 --derp2 --allo3' 'de'"
main::fuzzyFindOption de --opt1 --derp2 --allo3 && echo "${RETURNED_VALUE}"
# single match, strict mode is enabled
echo "→ VALET_CONFIG_STRICT_MATCHING=true main::fuzzyFindOption de --opt1 --derp2 --allo3"

VALET_CONFIG_STRICT_MATCHING=true main::fuzzyFindOption de --opt1 --derp2 --allo3 && echo "${RETURNED_VALUE}" && echo "${RETURNED_VALUE2}"
unset VALET_CONFIG_STRICT_MATCHING

# single match, strict mode is disabled
echo
echo "→ main::fuzzyFindOption de --opt1 --derp2 --allo3"
main::fuzzyFindOption de --opt1 --derp2 --allo3 && echo "${RETURNED_VALUE}" && echo "${RETURNED_VALUE2}"

# multiple matches, strict mode is enabled
echo
echo "→ VALET_CONFIG_STRICT_MATCHING=true main::fuzzyFindOption -a --opt1 --derp2 --allo3"
VALET_CONFIG_STRICT_MATCHING=true main::fuzzyFindOption -p --opt1 --derp2 --allo3 && echo "${RETURNED_VALUE}" && echo "${RETURNED_VALUE2}"

# multiple matches, strict mode is disabled
echo
echo "→ main::fuzzyFindOption '--opt1 --derp2 --allo3' '-a'"
main::fuzzyFindOption -a --opt1 --derp2 --allo3 && echo "${RETURNED_VALUE}"
echo "→ main::fuzzyFindOption -a --opt1 --derp2 --allo3"
main::fuzzyFindOption -p --opt1 --derp2 --allo3 && echo "${RETURNED_VALUE}" && echo "${RETURNED_VALUE2}"

# no match
echo
echo "→ main::fuzzyFindOption '--opt1 --derp2 --allo3' 'thing'"
main::fuzzyFindOption thing --opt1 --derp2 --allo3 && echo "${RETURNED_VALUE}"
echo "→ main::fuzzyFindOption thing --opt1 --derp2 --allo3"
main::fuzzyFindOption thing --opt1 --derp2 --allo3 && echo "${RETURNED_VALUE}" && echo "${RETURNED_VALUE2}"

test::endTest "Testing main::fuzzyFindOption" 0
}
Expand Down
126 changes: 103 additions & 23 deletions tests.d/1001-main-functions/results.approved.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,12 @@ Exit code: `0`

```plaintext
→ main::parseFunctionArguments selfMock2
local parsingErrors option1 thisIsOption2 help firstArg
local parsingErrors option1 thisIsOption2 flag3 withDefault help firstArg
local -a more
option1=""
thisIsOption2="${VALET_THIS_IS_OPTION2:-}"
flag3="${VALET_FLAG3:-}"
withDefault="${VALET_WITH_DEFAULT:-"cool"}"
help=""
parsingErrors="Expecting ⌜2⌝ argument(s) but got ⌜0⌝.
Use ⌜valet self mock2 --help⌝ to get help.
Expand All @@ -120,8 +122,10 @@ more=(
)
→ main::parseFunctionArguments selfMock2 -o -2 optionValue2 arg1 more1 more2
local parsingErrors option1 thisIsOption2 help firstArg
local parsingErrors option1 thisIsOption2 flag3 withDefault help firstArg
local -a more
flag3="${VALET_FLAG3:-}"
withDefault="${VALET_WITH_DEFAULT:-"cool"}"
help=""
parsingErrors=""
option1="true"
Expand All @@ -133,8 +137,10 @@ more=(
)
→ main::parseFunctionArguments selfMock2 -o -2 optionValue2 arg1
local parsingErrors option1 thisIsOption2 help firstArg
local parsingErrors option1 thisIsOption2 flag3 withDefault help firstArg
local -a more
flag3="${VALET_FLAG3:-}"
withDefault="${VALET_WITH_DEFAULT:-"cool"}"
help=""
parsingErrors="Expecting ⌜2⌝ argument(s) but got ⌜1⌝.
Use ⌜valet self mock2 --help⌝ to get help.
Expand All @@ -148,23 +154,39 @@ more=(
)
→ main::parseFunctionArguments selfMock2 -unknown -what optionValue2 arg
local parsingErrors option1 thisIsOption2 help firstArg
local parsingErrors option1 thisIsOption2 flag3 withDefault help firstArg
local -a more
option1=""
thisIsOption2="${VALET_THIS_IS_OPTION2:-}"
flag3="${VALET_FLAG3:-}"
help=""
parsingErrors="Unknown option ⌜-unknown⌝.
Unknown option ⌜-what⌝.
Use ⌜valet self mock2 --help⌝ to get help."
firstArg="optionValue2"
parsingErrors="Unknown option ⌜-unknown⌝, valid options are:
-o
--option1
-2
--this-is-option2
-3
--flag3
-4
--with-default
-h
--help
Expecting ⌜2⌝ argument(s) but got ⌜1⌝.
Use ⌜valet self mock2 --help⌝ to get help.
Usage:
valet [global options] self mock2 [options] [--] <firstArg> <more...>"
withDefault="optionValue2"
firstArg="arg"
more=(
"arg"
)
→ main::parseFunctionArguments selfMock2 arg more1 more2 -o
local parsingErrors option1 thisIsOption2 help firstArg
local parsingErrors option1 thisIsOption2 flag3 withDefault help firstArg
local -a more
thisIsOption2="${VALET_THIS_IS_OPTION2:-}"
flag3="${VALET_FLAG3:-}"
withDefault="${VALET_WITH_DEFAULT:-"cool"}"
help=""
parsingErrors=""
firstArg="arg"
Expand All @@ -175,22 +197,28 @@ more=(
)
→ main::parseFunctionArguments selfMock2 -this arg more1
local parsingErrors option1 thisIsOption2 help firstArg
local parsingErrors option1 thisIsOption2 flag3 withDefault help firstArg
local -a more
option1=""
thisIsOption2="${VALET_THIS_IS_OPTION2:-}"
flag3="${VALET_FLAG3:-}"
withDefault="${VALET_WITH_DEFAULT:-"cool"}"
help=""
parsingErrors="Unknown option ⌜-this⌝ (did you mean ⌜--this-is-option2⌝?).
Use ⌜valet self mock2 --help⌝ to get help."
firstArg="arg"
parsingErrors="Expecting ⌜2⌝ argument(s) but got ⌜1⌝.
Use ⌜valet self mock2 --help⌝ to get help.
Usage:
valet [global options] self mock2 [options] [--] <firstArg> <more...>"
thisIsOption2="arg"
firstArg="more1"
more=(
"more1"
)
→ main::parseFunctionArguments selfMock2 --this-is-option2 --option1 arg more1
local parsingErrors option1 thisIsOption2 help firstArg
local parsingErrors option1 thisIsOption2 flag3 withDefault help firstArg
local -a more
option1=""
flag3="${VALET_FLAG3:-}"
withDefault="${VALET_WITH_DEFAULT:-"cool"}"
help=""
parsingErrors=""
thisIsOption2="--option1"
Expand All @@ -208,16 +236,44 @@ secondArg="arg2"
→ main::parseFunctionArguments selfMock2 -- --arg1-- --arg2--
local parsingErrors option1 thisIsOption2 help firstArg
local parsingErrors option1 thisIsOption2 flag3 withDefault help firstArg
local -a more
option1=""
thisIsOption2="${VALET_THIS_IS_OPTION2:-}"
flag3="${VALET_FLAG3:-}"
withDefault="${VALET_WITH_DEFAULT:-"cool"}"
help=""
parsingErrors=""
firstArg="--arg1--"
more=(
"--arg2--"
)
→ main::parseFunctionArguments selfMock2 arg1 arg2 --th
local parsingErrors option1 thisIsOption2 flag3 withDefault help firstArg
local -a more
option1=""
thisIsOption2="${VALET_THIS_IS_OPTION2:-}"
flag3="${VALET_FLAG3:-}"
withDefault="${VALET_WITH_DEFAULT:-"cool"}"
help=""
parsingErrors="Found multiple matches for the option ⌜--th⌝, please be more specific:
CHI-CDECHI-CDECHItCDECHIhCDEis-is-option2
CHI-CDECHI-CDEwiCHItCDECHIhCDE-default
Use ⌜valet self mock2 --help⌝ to get help."
firstArg="arg1"
more=(
"arg2"
)
```

**Error** output:

```log
INFO Fuzzy matching the option ⌜-what⌝ to ⌜--with-default⌝.
INFO Fuzzy matching the option ⌜-this⌝ to ⌜--this-is-option2⌝.
```

## Test script 99.tests
Expand Down Expand Up @@ -298,13 +354,37 @@ Exit code: `0`
**Standard** output:

```plaintext
→ main::fuzzyFindOption '--opt1 --derp2 --allo3' 'de'
(did you mean ⌜--derp2⌝?)
→ VALET_CONFIG_STRICT_MATCHING=true main::fuzzyFindOption de --opt1 --derp2 --allo3
Unknown option ⌜de⌝, did you mean ⌜--derp2⌝?
→ main::fuzzyFindOption '--opt1 --derp2 --allo3' '-a'
(did you mean ⌜--allo3⌝?)
→ main::fuzzyFindOption de --opt1 --derp2 --allo3
→ main::fuzzyFindOption '--opt1 --derp2 --allo3' 'thing'
--derp2
→ VALET_CONFIG_STRICT_MATCHING=true main::fuzzyFindOption -a --opt1 --derp2 --allo3
Unknown option ⌜-p⌝, valid matches are:
CHI-CDE-oCHIpCDEt1
CHI-CDE-derCHIpCDE2
→ main::fuzzyFindOption -a --opt1 --derp2 --allo3
Found multiple matches for the option ⌜-p⌝, please be more specific:
CHI-CDE-oCHIpCDEt1
CHI-CDE-derCHIpCDE2
→ main::fuzzyFindOption thing --opt1 --derp2 --allo3
Unknown option ⌜thing⌝, valid options are:
--opt1
--derp2
--allo3
```

**Error** output:

```log
INFO Fuzzy matching the option ⌜de⌝ to ⌜--derp2⌝.
```

14 changes: 7 additions & 7 deletions tests.d/1102-self-build/results.approved.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ CMD_OPTIONS_DESCRIPTION_selfBuild=0 $'Specify the directory in which to look for
CMD_OPTIONS_DESCRIPTION_selfConfig=0 $'Create the configuration file if it does not exist but do not open it.\nThis option can be set by exporting the variable VALET_NO_EDIT=\'true\'.' 1 $'Override of the configuration file even if it already exists.\nUnless the option --export-current-values is used, the existing values will be reset.\nThis option can be set by exporting the variable VALET_OVERRIDE=\'true\'.' 2 $'When writing the configuration file, export the current values of the variables.\n\nThis option can be set by exporting the variable VALET_EXPORT_CURRENT_VALUES=\'true\'.' 3 "Display the help for this command."
CMD_OPTIONS_DESCRIPTION_selfExport=0 $'Export all the libraries.\n\nThis option can be set by exporting the variable VALET_EXPORT_ALL=\'true\'.' 1 "Display the help for this command."
CMD_OPTIONS_DESCRIPTION_selfMock1=0 "Display the help for this command."
CMD_OPTIONS_DESCRIPTION_selfMock2=0 "First option." 1 $'An option with a value.\nThis option can be set by exporting the variable VALET_THIS_IS_OPTION2=\'<level>\'.' 2 "Display the help for this command."
CMD_OPTIONS_DESCRIPTION_selfMock2=0 "First option." 1 $'An option with a value.\nThis option can be set by exporting the variable VALET_THIS_IS_OPTION2=\'<level>\'.' 2 $'Third option.\nThis option can be set by exporting the variable VALET_FLAG3=\'true\'.' 3 $'An option with a default value.\nThis option can be set by exporting the variable VALET_WITH_DEFAULT=\'<val>\'.' 4 "Display the help for this command."
CMD_OPTIONS_DESCRIPTION_selfMock3=0 "Display the help for this command."
CMD_OPTIONS_DESCRIPTION_selfMock4=0 "Display the help for this command."
CMD_OPTIONS_DESCRIPTION_selfRelease=0 $'The token necessary to create the release on GitHub and upload artifacts.\nThis option can be set by exporting the variable VALET_GITHUB_RELEASE_TOKEN=\'<token>\'.' 1 $'The semver level to bump the version.\n\nCan be either: major or minor. Defaults to minor.\nThis option can be set by exporting the variable VALET_BUMP_LEVEL=\'<semver>\'.' 2 $'Do not perform the release, just show what would be done.\nThis option can be set by exporting the variable VALET_DRY_RUN=\'true\'.' 3 $'Do no create the release, just upload the artifacts to the latest release.\n\nThis option can be set by exporting the variable VALET_UPLOAD_ARTIFACTS_ONLY=\'true\'.' 4 "Display the help for this command."
Expand All @@ -131,7 +131,7 @@ CMD_OPTIONS_NAME_selfBuild=0 "-d, --user-directory <path>" 1 "-o, --output <path
CMD_OPTIONS_NAME_selfConfig=0 "--no-edit" 1 "--override" 2 "--export-current-values" 3 "-h, --help"
CMD_OPTIONS_NAME_selfExport=0 "-a, --export-all" 1 "-h, --help"
CMD_OPTIONS_NAME_selfMock1=0 "-h, --help"
CMD_OPTIONS_NAME_selfMock2=0 "-o, --option1" 1 "-2, --this-is-option2 <level>" 2 "-h, --help"
CMD_OPTIONS_NAME_selfMock2=0 "-o, --option1" 1 "-2, --this-is-option2 <level>" 2 "-3, --flag3" 3 "-4, --with-default <val>" 4 "-h, --help"
CMD_OPTIONS_NAME_selfMock3=0 "-h, --help"
CMD_OPTIONS_NAME_selfMock4=0 "-h, --help"
CMD_OPTIONS_NAME_selfRelease=0 "-t, --github-release-token <token>" 1 "-b, --bump-level <semver>" 2 "--dry-run" 3 "--upload-artifacts-only" 4 "-h, --help"
Expand All @@ -144,7 +144,7 @@ CMD_OPTS_DEFAULT_selfBuild=0 "" 1 "" 2 ""
CMD_OPTS_DEFAULT_selfConfig=0 "" 1 "" 2 "" 3 ""
CMD_OPTS_DEFAULT_selfExport=0 "" 1 ""
CMD_OPTS_DEFAULT_selfMock1=0 ""
CMD_OPTS_DEFAULT_selfMock2=0 "" 1 "" 2 ""
CMD_OPTS_DEFAULT_selfMock2=0 "" 1 "" 2 "" 3 "cool" 4 ""
CMD_OPTS_DEFAULT_selfMock3=0 ""
CMD_OPTS_DEFAULT_selfMock4=0 ""
CMD_OPTS_DEFAULT_selfRelease=0 "" 1 "" 2 "" 3 "" 4 ""
Expand All @@ -157,7 +157,7 @@ CMD_OPTS_HAS_VALUE_selfBuild=0 "true" 1 "true" 2 "false"
CMD_OPTS_HAS_VALUE_selfConfig=0 "false" 1 "false" 2 "false" 3 "false"
CMD_OPTS_HAS_VALUE_selfExport=0 "false" 1 "false"
CMD_OPTS_HAS_VALUE_selfMock1=0 "false"
CMD_OPTS_HAS_VALUE_selfMock2=0 "false" 1 "true" 2 "false"
CMD_OPTS_HAS_VALUE_selfMock2=0 "false" 1 "true" 2 "false" 3 "true" 4 "false"
CMD_OPTS_HAS_VALUE_selfMock3=0 "false"
CMD_OPTS_HAS_VALUE_selfMock4=0 "false"
CMD_OPTS_HAS_VALUE_selfRelease=0 "true" 1 "true" 2 "false" 3 "false" 4 "false"
Expand All @@ -170,7 +170,7 @@ CMD_OPTS_NAME_SC_selfBuild=0 "VALET_USER_DIRECTORY" 1 "VALET_OUTPUT" 2 ""
CMD_OPTS_NAME_SC_selfConfig=0 "VALET_NO_EDIT" 1 "VALET_OVERRIDE" 2 "VALET_EXPORT_CURRENT_VALUES" 3 ""
CMD_OPTS_NAME_SC_selfExport=0 "VALET_EXPORT_ALL" 1 ""
CMD_OPTS_NAME_SC_selfMock1=0 ""
CMD_OPTS_NAME_SC_selfMock2=0 "" 1 "VALET_THIS_IS_OPTION2" 2 ""
CMD_OPTS_NAME_SC_selfMock2=0 "" 1 "VALET_THIS_IS_OPTION2" 2 "VALET_FLAG3" 3 "VALET_WITH_DEFAULT" 4 ""
CMD_OPTS_NAME_SC_selfMock3=0 ""
CMD_OPTS_NAME_SC_selfMock4=0 ""
CMD_OPTS_NAME_SC_selfRelease=0 "VALET_GITHUB_RELEASE_TOKEN" 1 "VALET_BUMP_LEVEL" 2 "VALET_DRY_RUN" 3 "VALET_UPLOAD_ARTIFACTS_ONLY" 4 ""
Expand All @@ -184,7 +184,7 @@ CMD_OPTS_NAME_selfBuild=0 "userDirectory" 1 "output" 2 "help"
CMD_OPTS_NAME_selfConfig=0 "noEdit" 1 "override" 2 "exportCurrentValues" 3 "help"
CMD_OPTS_NAME_selfExport=0 "exportAll" 1 "help"
CMD_OPTS_NAME_selfMock1=0 "help"
CMD_OPTS_NAME_selfMock2=0 "option1" 1 "thisIsOption2" 2 "help"
CMD_OPTS_NAME_selfMock2=0 "option1" 1 "thisIsOption2" 2 "flag3" 3 "withDefault" 4 "help"
CMD_OPTS_NAME_selfMock3=0 "help"
CMD_OPTS_NAME_selfMock4=0 "help"
CMD_OPTS_NAME_selfRelease=0 "githubReleaseToken" 1 "bumpLevel" 2 "dryRun" 3 "uploadArtifactsOnly" 4 "help"
Expand All @@ -198,7 +198,7 @@ CMD_OPTS_selfBuild=0 "-d --user-directory" 1 "-o --output" 2 "-h --help"
CMD_OPTS_selfConfig=0 "--no-edit" 1 "--override" 2 "--export-current-values" 3 "-h --help"
CMD_OPTS_selfExport=0 "-a --export-all" 1 "-h --help"
CMD_OPTS_selfMock1=0 "-h --help"
CMD_OPTS_selfMock2=0 "-o --option1" 1 "-2 --this-is-option2" 2 "-h --help"
CMD_OPTS_selfMock2=0 "-o --option1" 1 "-2 --this-is-option2" 2 "-3 --flag3" 3 "-4 --with-default" 4 "-h --help"
CMD_OPTS_selfMock3=0 "-h --help"
CMD_OPTS_selfMock4=0 "-h --help"
CMD_OPTS_selfRelease=0 "-t --github-release-token" 1 "-b --bump-level" 2 "--dry-run" 3 "--upload-artifacts-only" 4 "-h --help"
Expand Down
Loading

0 comments on commit f1f13ce

Please sign in to comment.