From 5b176a1184685d5353f61c9ef21fb9fcbe27e245 Mon Sep 17 00:00:00 2001 From: Julien Caillon Date: Sat, 30 Mar 2024 20:08:35 +0100 Subject: [PATCH] :sparkles: implementing interactive functions --- .gitignore | 3 +- README.md | 2 +- examples.d/commands | 40 ++- .../001-showcase-test-suite/00.tests.sh | 0 .../results.approved.md | 0 tests.d/0000-valet-cli/.before-test | 2 +- tests.d/0000-valet-cli/04.interactive-mode.sh | 2 +- tests.d/0000-valet-cli/05.logging.sh | 4 +- tests.d/0000-valet-cli/results.approved.md | 115 ++++++-- tests.d/0003-self/results.approved.md | 47 +++- valet | 6 +- valet.d/commands.d/self-build | 9 +- valet.d/commands.d/self-download-binaries.sh | 10 +- valet.d/commands.d/self-install.sh | 241 ++++++++++++++++- valet.d/commands.d/self-release.sh | 112 +++++--- valet.d/commands.d/self-test.sh | 2 +- valet.d/commands.d/self.sh | 4 +- valet.d/core | 250 +++++++++++++++++- valet.d/main | 62 ++--- valet.d/version | 2 +- 20 files changed, 758 insertions(+), 155 deletions(-) rename examples.d/showcase/{.tests.d => tests.d}/001-showcase-test-suite/00.tests.sh (100%) rename examples.d/showcase/{.tests.d => tests.d}/001-showcase-test-suite/results.approved.md (100%) diff --git a/.gitignore b/.gitignore index 26a253e..68ecc46 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ folder.ico bin/ .tmp/ sandbox.sh -*.tag.gz \ No newline at end of file +**.tag.gz +**.zip diff --git a/README.md b/README.md index 3730fd2..3731316 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ You need bash version 5 or higher to be installed on your machine to run Valet. Run the following command to install Valet: ```bash -curl https://github.com/jcaillon/valet/TOBEDONE! | bash +bash -c "$(curl -fsSL https://raw.githubusercontent.com/jcaillon/valet/main/valet.d/commands.d/self-install.sh)" ``` > [!NOTE] diff --git a/examples.d/commands b/examples.d/commands index ca1b3c5..72f2277 100644 --- a/examples.d/commands +++ b/examples.d/commands @@ -20,6 +20,7 @@ selfRelease selfTest selfTestCore selfUpdate +selfWelcomeUser showCaseSudo showCommandHelp showcaseCommand1 @@ -37,6 +38,7 @@ CMD_FUNCTION_NAME_self_release="selfRelease" CMD_FUNCTION_NAME_self_test="selfTest" CMD_FUNCTION_NAME_self_test_core="selfTestCore" CMD_FUNCTION_NAME_self_update="selfUpdate" +CMD_FUNCTION_NAME_self_welcome_user="selfWelcomeUser" CMD_FUNCTION_NAME_showcase_sudo_command="showCaseSudo" CMD_FUNCTION_NAME_help="showCommandHelp" CMD_FUNCTION_NAME_showcase_command1="showcaseCommand1" @@ -59,6 +61,7 @@ self release self test self test-core self update +self welcome-user showcase showcase command1 showcase hello-world @@ -71,9 +74,7 @@ CMD_MAX_COMMAND_WIDTH="22" # Get the input for fzf to display the header commands menu -CMD_COMMANDS_MENU_HEADER="Please select the command to run (filter by typing anything) - -Command name Short description" +CMD_COMMANDS_MENU_HEADER="Please select the command to run (filter by typing anything)" CMD_COMMANDS_MENU_BODY="help Show the help this program or of a specific command self build Re-build the menu of valet from your commands. self download-binaries Download the required binaries for valet. @@ -82,6 +83,7 @@ self Show the valet self-maintenance sub menu. self test-core Test valet core features. self test Test your valet custom commands. self update Update valet using the latest release on GitHub. +self welcome-user The command run after the installation of Valet to guide the user. showcase command1 A showcase command that uses arguments and options. showcase hello-world An hello world command showcase Show the showcase sub menu. @@ -113,6 +115,7 @@ CMD_COMMAND_selfRelease="self release" CMD_COMMAND_selfTest="self test" CMD_COMMAND_selfTestCore="self test-core" CMD_COMMAND_selfUpdate="self update" +CMD_COMMAND_selfWelcomeUser="self welcome-user" CMD_COMMAND_showCaseSudo="showcase sudo-command" CMD_COMMAND_showCommandHelp="help" CMD_COMMAND_showcaseCommand1="showcase command1" @@ -125,6 +128,7 @@ CMD_FILETOSOURCE_selfRelease="valet.d/commands.d/self-release.sh" CMD_FILETOSOURCE_selfTest="valet.d/commands.d/self-test.sh" CMD_FILETOSOURCE_selfTestCore="valet.d/commands.d/self-test.sh" CMD_FILETOSOURCE_selfUpdate="valet.d/commands.d/self-install.sh" +CMD_FILETOSOURCE_selfWelcomeUser="valet.d/commands.d/self-install.sh" CMD_FILETOSOURCE_showCaseSudo="examples.d/showcase/showcase.sh" CMD_FILETOSOURCE_showcaseCommand1="examples.d/showcase/showcase.sh" CMD_FILETOSOURCE_showcaseMenu="examples.d/showcase/showcase.sh" @@ -138,6 +142,7 @@ CMD_SHORTDESCRIPTION_selfRelease="Release a new version of valet." CMD_SHORTDESCRIPTION_selfTest="Test your valet custom commands." CMD_SHORTDESCRIPTION_selfTestCore="Test valet core features." CMD_SHORTDESCRIPTION_selfUpdate="Update valet using the latest release on GitHub." +CMD_SHORTDESCRIPTION_selfWelcomeUser="The command run after the installation of Valet to guide the user." CMD_SHORTDESCRIPTION_showCaseSudo="A command that requires sudo" CMD_SHORTDESCRIPTION_showCommandHelp="Show the help this program or of a specific command" CMD_SHORTDESCRIPTION_showcaseCommand1="A showcase command that uses arguments and options." @@ -162,6 +167,10 @@ It will: CMD_DESCRIPTION_selfTest="Test your valet custom commands using approval tests approach." CMD_DESCRIPTION_selfTestCore="Test valet core features using approval tests approach." CMD_DESCRIPTION_selfUpdate="Update valet using the latest release on GitHub." +CMD_DESCRIPTION_selfWelcomeUser="The command run after the installation of Valet to guide the user. + +Adjust the Valet configuration according to the user environment. +Let the user know what to do next." CMD_DESCRIPTION_showCaseSudo="Before starting this command, valet will check if sudo is available. If so, it will require the user to enter the sudo password and use sudo inside the command" @@ -194,15 +203,15 @@ Once you have created your new command script, run the ⌜valet self build⌝ co In addition to the environment variables defined for each options, you can define the following environment variables to configure valet: -- VALET_USER_DIRECTORY=\"my/path\": set the path to the valet user directory (in which to find user commands). +- VALET_USER_DIRECTORY=\"~/valet.d\": set the path to the valet user directory (in which to find user commands). - VALET_NO_COLOR=\"true\": will disable the color output for logs and help. - VALET_COLOR_XXX=\"color\": will set the colors for the logs and the help, XXX can be one of these: DEFAULT, TITLE, OPTION, ARGUMENT, COMMAND, DEBUG, INFO, WARNING, SUCCESS, ERROR, TIMESTAMP, HIGHLIGHT. - VALET_NO_WRAP=\"true\": will disable the text wrapping for logs. - VALET_NO_ICON=\"true\": will disable the icons for logs and help. - VALET_NO_TIMESTAMP=\"true\": will disable the timestamp for logs. -- VALET_LOG_COLUMNS=\"120\": the number of columns at which to wrap the logs (if wrap is enabled); defaults to the terminal width. +- VALET_LOG_COLUMNS=\"120\": set the number of columns at which to wrap the logs to 120 (if wrap is enabled); defaults to the terminal width. - VALET_CI_MODE=\"true\": will simplify the log output for CI/CD environments (or slow systems), will display the logs without colors, without wrapping lines and with the full date. -- VALET_REMEMBER_LAST_CHOICES=\"3\": number of last choices to remember when selecting an item from a menu. Set to 0 to disable this feature and always display items in the alphabetical order. +- VALET_REMEMBER_LAST_CHOICES=\"3\": number of last choices to remember when selecting an item from a command menu. Set to 0 to disable this feature and always display items in the alphabetical order. - VALET_DO_NOT_USE_LOCAL_BIN=\"false\": if true, valet will use the executable from the PATH even if they exist in the valet bin/ directory. These variables can be exported in your .bashrc file. @@ -292,6 +301,7 @@ CMD_OPTIONS_NAME_selfRelease=( "-t, --github-release-token " "-b, --bump-level " "--dry-run" + "--upload-artifacts-only" "-h, --help" ) CMD_OPTIONS_DESCRIPTION_selfRelease=( @@ -303,6 +313,8 @@ Can be either: major or minor. This option can be set by exporting the variable VALET_BUMP_LEVEL=\"\"." "Do not perform the release, just show what would be done. This option can be set by exporting the variable VALET_DRY_RUN=\"true\"." + "Do no create the release, just upload the artifacts to the latest release. +This option can be set by exporting the variable VALET_UPLOAD_ARTIFACTS_ONLY=\"true\"." "Display the help for this command" ) CMD_OPTIONS_NAME_selfTest=( @@ -386,6 +398,12 @@ CMD_OPTIONS_NAME_selfUpdate=( CMD_OPTIONS_DESCRIPTION_selfUpdate=( "Display the help for this command" ) +CMD_OPTIONS_NAME_selfWelcomeUser=( + "-h, --help" +) +CMD_OPTIONS_DESCRIPTION_selfWelcomeUser=( + "Display the help for this command" +) CMD_OPTIONS_NAME_showCaseSudo=( "-h, --help" ) @@ -491,6 +509,7 @@ CMD_COMMANDS_NAME_selfMenu=( "test" "test-core" "update" + "welcome-user" ) CMD_COMMANDS_DESCRIPTION_selfMenu=( "Re-build the menu of valet from your commands." @@ -499,6 +518,7 @@ CMD_COMMANDS_DESCRIPTION_selfMenu=( "Test your valet custom commands." "Test valet core features." "Update valet using the latest release on GitHub." + "The command run after the installation of Valet to guide the user." ) CMD_COMMANDS_NAME_showcaseMenu=( "hello-world" @@ -519,6 +539,7 @@ CMD_COMMANDS_NAME_this=( "self test" "self test-core" "self update" + "self welcome-user" "showcase sudo-command" "help" "showcase command1" @@ -533,6 +554,7 @@ CMD_COMMANDS_DESCRIPTION_this=( "Test your valet custom commands." "Test valet core features." "Update valet using the latest release on GitHub." + "The command run after the installation of Valet to guide the user." "A command that requires sudo" "Show the help this program or of a specific command" "A showcase command that uses arguments and options." @@ -588,23 +610,27 @@ CMD_OPTS_selfRelease=( "-t --github-release-token" "-b --bump-level" "--dry-run" + "--upload-artifacts-only" "-h --help" ) CMD_OPTS_HAS_VALUE_selfRelease=( "true" "true" "false" + "false" ) CMD_OPTS_NAME_selfRelease=( "githubReleaseToken" "bumpLevel" "dryRun" + "uploadArtifactsOnly" "help" ) CMD_OPTS_NAME_SC_selfRelease=( "GITHUB_RELEASE_TOKEN" "BUMP_LEVEL" "DRY_RUN" + "UPLOAD_ARTIFACTS_ONLY" ) CMD_OPTS_selfTest=( "-d --user-directory" @@ -696,6 +722,8 @@ CMD_OPTS_NAME_SC_selfTestCore=( ) CMD_OPTS_selfUpdate=("-h --help") CMD_OPTS_NAME_selfUpdate=("help") +CMD_OPTS_selfWelcomeUser=("-h --help") +CMD_OPTS_NAME_selfWelcomeUser=("help") CMD_OPTS_showCaseSudo=("-h --help") CMD_OPTS_NAME_showCaseSudo=("help") CMD_OPTS_showCommandHelp=( diff --git a/examples.d/showcase/.tests.d/001-showcase-test-suite/00.tests.sh b/examples.d/showcase/tests.d/001-showcase-test-suite/00.tests.sh similarity index 100% rename from examples.d/showcase/.tests.d/001-showcase-test-suite/00.tests.sh rename to examples.d/showcase/tests.d/001-showcase-test-suite/00.tests.sh diff --git a/examples.d/showcase/.tests.d/001-showcase-test-suite/results.approved.md b/examples.d/showcase/tests.d/001-showcase-test-suite/results.approved.md similarity index 100% rename from examples.d/showcase/.tests.d/001-showcase-test-suite/results.approved.md rename to examples.d/showcase/tests.d/001-showcase-test-suite/results.approved.md diff --git a/tests.d/0000-valet-cli/.before-test b/tests.d/0000-valet-cli/.before-test index c587285..792e103 100644 --- a/tests.d/0000-valet-cli/.before-test +++ b/tests.d/0000-valet-cli/.before-test @@ -35,7 +35,7 @@ function fzf() { echo "▶ fzf input stream was:" 1>&2 echo "⌈${inputStreamContent}⌉" 1>&2 - if [[ "${inputStreamContent}" == "ReturnLast"* ]]; then + if [[ "$*" == *"ReturnLast"* ]]; then # returning the last line of the input stream echo "${inputStreamContent##*$'\n'}" fi diff --git a/tests.d/0000-valet-cli/04.interactive-mode.sh b/tests.d/0000-valet-cli/04.interactive-mode.sh index 19d8a4a..45c5ff3 100644 --- a/tests.d/0000-valet-cli/04.interactive-mode.sh +++ b/tests.d/0000-valet-cli/04.interactive-mode.sh @@ -13,7 +13,7 @@ another3 This is another command 3" # testing showInteractiveCommandsMenu, should return the last line of the input stream echo "→ showInteractiveCommandsMenu \"ReturnLast My header"$'\n'"2 lines\" \"${commands}\"" - showInteractiveCommandsMenu "ReturnLast My header"$'\n'"2 lines" "${commands}" && echo "${LAST_RETURNED_VALUE}" + showInteractiveCommandsMenu "test-menu" "ReturnLast My header"$'\n'"2 lines" "${commands}" && echo "${LAST_RETURNED_VALUE}" endTest "Testing showInteractiveCommandsMenu, should return the last line of the input stream" $? } diff --git a/tests.d/0000-valet-cli/05.logging.sh b/tests.d/0000-valet-cli/05.logging.sh index ef5a3cf..bce548b 100644 --- a/tests.d/0000-valet-cli/05.logging.sh +++ b/tests.d/0000-valet-cli/05.logging.sh @@ -12,9 +12,9 @@ function testLogging() { unset VALET_LOG_LEVEL endTest "Testing log with success level" 0 - echo "→ valet --log-level warn self test-core --logging-level" + echo "→ valet --log-level warning self test-core --logging-level" ("${VALET_HOME}/valet" --log-level "warning" self test-core --logging-level) - endTest "Testing log with warn level" 0 + endTest "Testing log with warning level" 0 echo "→ valet -v self test-core --logging-level" ("${VALET_HOME}/valet" -v self test-core --logging-level) diff --git a/tests.d/0000-valet-cli/results.approved.md b/tests.d/0000-valet-cli/results.approved.md index f81c520..be4e649 100644 --- a/tests.d/0000-valet-cli/results.approved.md +++ b/tests.d/0000-valet-cli/results.approved.md @@ -159,17 +159,17 @@ ABOUT In addition to the environment variables defined for each options, you can define the following environment variables to configure valet: - - VALET_USER_DIRECTORY="my/path": set the path to the valet user directory (in which to find user commands). + - VALET_USER_DIRECTORY="~/valet.d": set the path to the valet user directory (in which to find user commands). - VALET_NO_COLOR="true": will disable the color output for logs and help. - VALET_COLOR_XXX="color": will set the colors for the logs and the help, XXX can be one of these: DEFAULT, TITLE, OPTION, ARGUMENT, COMMAND, DEBUG, INFO, WARNING, SUCCESS, ERROR, TIMESTAMP, HIGHLIGHT. - VALET_NO_WRAP="true": will disable the text wrapping for logs. - VALET_NO_ICON="true": will disable the icons for logs and help. - VALET_NO_TIMESTAMP="true": will disable the timestamp for logs. - - VALET_LOG_COLUMNS="120": the number of columns at which to wrap the logs (if wrap is enabled); defaults to the terminal width. + - VALET_LOG_COLUMNS="120": set the number of columns at which to wrap the logs to 120 (if wrap is enabled); defaults to the terminal width. - VALET_CI_MODE="true": will simplify the log output for CI/CD environments (or slow systems), will display the logs without colors, without wrapping lines and with the full date. - - VALET_REMEMBER_LAST_CHOICES="3": number of last choices to remember when selecting an item from a menu. Set to 0 to disable this feature and always display items in the + - VALET_REMEMBER_LAST_CHOICES="3": number of last choices to remember when selecting an item from a command menu. Set to 0 to disable this feature and always display items in the alphabetical order. - VALET_DO_NOT_USE_LOCAL_BIN="false": if true, valet will use the executable from the PATH even if they exist in the valet bin/ directory. @@ -226,6 +226,8 @@ COMMANDS Test valet core features. self update Update valet using the latest release on GitHub. + self welcome-user + The command run after the installation of Valet to guide the user. showcase sudo-command A command that requires sudo help @@ -450,11 +452,26 @@ another3 This is another command 3" **Error** output: ```log -▶ called fzf --tiebreak=begin,index --no-multi --cycle --layout=reverse --info=default --margin=0 --padding=0 --header-lines=2 --preview-window=right:4989:wrap --preview=echo {} | cut -d$'\t' -f1 | sed -e 's/[[:space:]]*$//' | xargs -P1 -I{} '$VALET_HOME/valet' --log-level fail help --columns 4987 {} +▶ called fzf --history=/tmp/d1-0/fzf-history-test-menu --history-size=50 --bind alt-up:prev-history --bind alt-down:next-history --bind=alt-h:preview(echo -e 'HELP + +Navigate through the options with the UP/DOWN keys. + +Validate your choice with ENTER. + +Cancel with ESC or CTRL+C. + +ADDITIONAL KEY BINDINGS + +ALT+H: Show this help. +ALT+/: Rotate through the preview options (this pane). +ALT+UP/ALT+DOWN: Previous/next query in the history. +SHIFT+UP/SHIFT+DOWN: Scroll the preview up and down. +') --preview-window=right,4989 --bind alt-/:change-preview-window(right,70%|down,40%,border-horizontal|hidden|) --layout=reverse --info=right --pointer=◆ --marker=✓ --cycle --tiebreak=begin,index --margin=0 --padding=0 --delimiter= + --tabstop=3 --header-first --header=Press ALT+H to display the help and keybindings. +ReturnLast My header +2 lines --print-query --no-multi --preview-label=Command help --preview=VALET_LOG_LEVEL=error '$VALET_HOME/valet' help --columns $((FZF_PREVIEW_COLUMNS - 1)) {1} ▶ fzf input stream was: -⌈ReturnLast My header -2 lines -cm1 This is command 1 +⌈cm1 This is command 1 cm2 This is command 2 sub cmd1 This is sub command 1 sub cmd2 This is sub command 2 @@ -474,12 +491,25 @@ Exit code: 0 **Error** output: ```log -▶ called fzf --tiebreak=begin,index --no-multi --cycle --layout=reverse --info=default --margin=0 --padding=0 --header-lines=3 --preview-window=right:4989:wrap --preview=echo {} | cut -d$'\t' -f1 | sed -e 's/[[:space:]]*$//' | xargs -P1 -I{} '$VALET_HOME/valet' --log-level fail help --columns 4987 {} -▶ fzf input stream was: -⌈Please select the command to run (filter by typing anything) +▶ called fzf --history=/tmp/d1-0/fzf-history-main-menu --history-size=50 --bind alt-up:prev-history --bind alt-down:next-history --bind=alt-h:preview(echo -e 'HELP + +Navigate through the options with the UP/DOWN keys. + +Validate your choice with ENTER. + +Cancel with ESC or CTRL+C. + +ADDITIONAL KEY BINDINGS -Command name Short description -help Show the help this program or of a specific command +ALT+H: Show this help. +ALT+/: Rotate through the preview options (this pane). +ALT+UP/ALT+DOWN: Previous/next query in the history. +SHIFT+UP/SHIFT+DOWN: Scroll the preview up and down. +') --preview-window=right,4989 --bind alt-/:change-preview-window(right,70%|down,40%,border-horizontal|hidden|) --layout=reverse --info=right --pointer=◆ --marker=✓ --cycle --tiebreak=begin,index --margin=0 --padding=0 --delimiter= + --tabstop=3 --header-first --header=Press ALT+H to display the help and keybindings. +Please select the command to run (filter by typing anything) --print-query --no-multi --preview-label=Command help --preview=VALET_LOG_LEVEL=error '$VALET_HOME/valet' help --columns $((FZF_PREVIEW_COLUMNS - 1)) {1} +▶ fzf input stream was: +⌈help Show the help this program or of a specific command self build Re-build the menu of valet from your commands. self download-binaries Download the required binaries for valet. self release Release a new version of valet. @@ -487,6 +517,7 @@ self Show the valet self-maintenance sub menu. self test-core Test valet core features. self test Test your valet custom commands. self update Update valet using the latest release on GitHub. +self welcome-user The command run after the installation of Valet to guide the user. showcase command1 A showcase command that uses arguments and options. showcase hello-world An hello world command showcase Show the showcase sub menu. @@ -513,14 +544,14 @@ WARNING This is a warning message. With a second line. ``` -### Testing log with warn level +### Testing log with warning level Exit code: 0 **Standard** output: ```plaintext -→ valet --log-level warn self test-core --logging-level +→ valet --log-level warning self test-core --logging-level ``` **Error** output: @@ -829,26 +860,56 @@ Exit code: 0 **Error** output: ```log -▶ called fzf --tiebreak=begin,index ---no-multi ---cycle +▶ called fzf --history=/tmp/d1-0/fzf-history-selfMenu +--history-size=50 +--bind +alt-up:prev-history +--bind +alt-down:next-history +--bind=alt-h:preview(echo -e 'HELP + +Navigate through the options with the UP/DOWN keys. + +Validate your choice with ENTER. + +Cancel with ESC or CTRL+C. + +ADDITIONAL KEY BINDINGS + +ALT+H: Show this help. +ALT+/: Rotate through the preview options (this pane). +ALT+UP/ALT+DOWN: Previous/next query in the history. +SHIFT+UP/SHIFT+DOWN: Scroll the preview up and down. +') +--preview-window=right,4989 +--bind +alt-/:change-preview-window(right,70%|down,40%,border-horizontal|hidden|) --layout=reverse ---info=default +--info=right +--pointer=◆ +--marker=✓ +--cycle +--tiebreak=begin,index --margin=0 --padding=0 ---header-lines=3 ---preview-window=right:4989:wrap ---preview=echo {} | cut -d$'\t' -f1 | sed -e 's/[[:space:]]*$//' | xargs -P1 -I{} '$VALET_HOME/valet' --log-level fail help --columns 4987 {} -▶ fzf input stream was: -⌈Please select the command to run (filter by typing anything) +--delimiter= -Command name Short description -self build Re-build the menu of valet from your commands. +--tabstop=3 +--header-first +--header=Press ALT+H to display the help and keybindings. +Please select the command to run (filter by typing anything) +--print-query +--no-multi +--preview-label=Command help +--preview=VALET_LOG_LEVEL=error '$VALET_HOME/valet' help --columns $((FZF_PREVIEW_COLUMNS - 1)) {1} +▶ fzf input stream was: +⌈self build Re-build the menu of valet from your commands. self download-binaries Download the required binaries for valet. self release Release a new version of valet. self test-core Test valet core features. self test Test your valet custom commands. -self update Update valet using the latest release on GitHub.⌉ +self update Update valet using the latest release on GitHub. +self welcome-user The command run after the installation of Valet to guide the user.⌉ ``` ### Testing that we can display the help of a sub menu @@ -888,6 +949,8 @@ COMMANDS Test valet core features. update Update valet using the latest release on GitHub. + welcome-user + The command run after the installation of Valet to guide the user. EXAMPLES diff --git a/tests.d/0003-self/results.approved.md b/tests.d/0003-self/results.approved.md index b776d67..bda348c 100644 --- a/tests.d/0003-self/results.approved.md +++ b/tests.d/0003-self/results.approved.md @@ -85,6 +85,19 @@ INFO Downloading yq from: https://github.com/mikefarah/yq/releases/download/ ▶ called kurlFile true 200 yq.exe https://github.com/mikefarah/yq/releases/download/v4.43.1/yq_windows_amd64.exe ▶ called invoke mv -f yq.exe $VALET_HOME/.tmp/bin/yq SUCCESS The binaries have been downloaded and stored in the bin directory of valet ⌜$VALET_HOME/.tmp/bin⌝. +▶ called invoke5 false 0 uname -m +INFO Downloading the binaries for the OS: darwin. +INFO Downloading fzf from: https://github.com/junegunn/fzf/releases/download/0.48.1/fzf-0.48.1-darwin_amd64.tar.gz. +▶ called kurlFile true 200 fzf.tar.gz https://github.com/junegunn/fzf/releases/download/0.48.1/fzf-0.48.1-darwin_amd64.tar.gz +▶ called invoke tar -xzf fzf.tar.gz +▶ called invoke mv -f fzf $VALET_HOME/.tmp/bin/fzf +INFO Downloading curl from: https://github.com/moparisthebest/static-curl/releases/download/v8.7.1/curl-amd64. +▶ called kurlFile true 200 curl https://github.com/moparisthebest/static-curl/releases/download/v8.7.1/curl-amd64 +▶ called invoke mv -f curl $VALET_HOME/.tmp/bin/curl +INFO Downloading yq from: https://github.com/mikefarah/yq/releases/download/v4.43.1/yq_darwin_amd64. +▶ called kurlFile true 200 yq https://github.com/mikefarah/yq/releases/download/v4.43.1/yq_darwin_amd64 +▶ called invoke mv -f yq $VALET_HOME/.tmp/bin/yq +SUCCESS The binaries have been downloaded and stored in the bin directory of valet ⌜$VALET_HOME/.tmp/bin⌝. SUCCESS The new version has been released, check: ⌜https://github.com/jcaillon/valet/releases/latest⌝. ``` @@ -169,7 +182,7 @@ SUCCESS The binaries have been downloaded and stored in the bin directory of va ▶ called invoke cp -R $VALET_HOME/valet.d . ▶ called invoke cp -R $VALET_HOME/valet . ▶ called invoke tar -czvf valet-linux-amd64.tar.gz examples.d valet.d valet bin -INFO The artifact has been created at ⌜valet-linux-amd64.tar.gz⌝ with: +DEBUG The artifact has been created at ⌜valet-linux-amd64.tar.gz⌝ with: INFO Uploading the artifact ⌜valet-linux-amd64.tar.gz⌝ to ⌜https://uploads.github.com/repos/jcaillon/valet/releases/xxxx/assets⌝. ▶ called kurl true -X POST -H Authorization: token token -H Content-Type: application/tar+gzip --data-binary @valet-linux-amd64.tar.gz https://uploads.github.com/repos/jcaillon/valet/releases/xxxx/assets?name=valet-linux-amd64.tar.gz @@ -199,15 +212,43 @@ SUCCESS The binaries have been downloaded and stored in the bin directory of va ▶ called invoke cp -R $VALET_HOME/valet.d . ▶ called invoke cp -R $VALET_HOME/valet . ▶ called invoke tar -czvf valet-windows-amd64.tar.gz examples.d valet.d valet bin -INFO The artifact has been created at ⌜valet-windows-amd64.tar.gz⌝ with: +DEBUG The artifact has been created at ⌜valet-windows-amd64.tar.gz⌝ with: INFO Uploading the artifact ⌜valet-windows-amd64.tar.gz⌝ to ⌜https://uploads.github.com/repos/jcaillon/valet/releases/xxxx/assets⌝. ▶ called kurl true -X POST -H Authorization: token token -H Content-Type: application/tar+gzip --data-binary @valet-windows-amd64.tar.gz https://uploads.github.com/repos/jcaillon/valet/releases/xxxx/assets?name=valet-windows-amd64.tar.gz +DEBUG Parsed arguments: +local parsingErrors forceOs destination help +parsingErrors="" +forceOs="darwin" +destination="$VALET_HOME/.tmp/bin" + +▶ called invoke5 false 0 uname -m +DEBUG Your CPU architecture is: x86_64. +INFO Downloading the binaries for the OS: darwin. +INFO Downloading fzf from: https://github.com/junegunn/fzf/releases/download/0.48.1/fzf-0.48.1-darwin_amd64.tar.gz. +▶ called kurlFile true 200 fzf.tar.gz https://github.com/junegunn/fzf/releases/download/0.48.1/fzf-0.48.1-darwin_amd64.tar.gz +▶ called invoke tar -xzf fzf.tar.gz +▶ called invoke mv -f fzf $VALET_HOME/.tmp/bin/fzf +INFO Downloading curl from: https://github.com/moparisthebest/static-curl/releases/download/v8.7.1/curl-amd64. +▶ called kurlFile true 200 curl https://github.com/moparisthebest/static-curl/releases/download/v8.7.1/curl-amd64 +▶ called invoke mv -f curl $VALET_HOME/.tmp/bin/curl +INFO Downloading yq from: https://github.com/mikefarah/yq/releases/download/v4.43.1/yq_darwin_amd64. +▶ called kurlFile true 200 yq https://github.com/mikefarah/yq/releases/download/v4.43.1/yq_darwin_amd64 +▶ called invoke mv -f yq $VALET_HOME/.tmp/bin/yq +SUCCESS The binaries have been downloaded and stored in the bin directory of valet ⌜$VALET_HOME/.tmp/bin⌝. +▶ called invoke cp -R $VALET_HOME/examples.d . +▶ called invoke cp -R $VALET_HOME/valet.d . +▶ called invoke cp -R $VALET_HOME/valet . +▶ called invoke tar -czvf .tar.gz examples.d valet.d valet bin +DEBUG The artifact has been created at ⌜.tar.gz⌝ with: + +INFO Uploading the artifact ⌜.tar.gz⌝ to ⌜https://uploads.github.com/repos/jcaillon/valet/releases/xxxx/assets⌝. +▶ called kurl true -X POST -H Authorization: token token -H Content-Type: application/tar+gzip --data-binary @.tar.gz https://uploads.github.com/repos/jcaillon/valet/releases/xxxx/assets?name=.tar.gz ▶ called invoke cp -R $VALET_HOME/examples.d . ▶ called invoke cp -R $VALET_HOME/valet.d . ▶ called invoke cp -R $VALET_HOME/valet . ▶ called invoke tar -czvf valet-no-binaries.tar.gz examples.d valet.d valet -INFO The artifact has been created at ⌜valet-no-binaries.tar.gz⌝ with: +DEBUG The artifact has been created at ⌜valet-no-binaries.tar.gz⌝ with: INFO Uploading the artifact ⌜valet-no-binaries.tar.gz⌝ to ⌜https://uploads.github.com/repos/jcaillon/valet/releases/xxxx/assets⌝. ▶ called kurl true -X POST -H Authorization: token token -H Content-Type: application/tar+gzip --data-binary @valet-no-binaries.tar.gz https://uploads.github.com/repos/jcaillon/valet/releases/xxxx/assets?name=valet-no-binaries.tar.gz diff --git a/valet b/valet index 404312d..a527982 100644 --- a/valet +++ b/valet @@ -39,15 +39,15 @@ description: |- In addition to the environment variables defined for each options, you can define the following environment variables to configure valet: - - VALET_USER_DIRECTORY=\"my/path\": set the path to the valet user directory (in which to find user commands). + - VALET_USER_DIRECTORY=\"~/valet.d\": set the path to the valet user directory (in which to find user commands). - VALET_NO_COLOR=\"true\": will disable the color output for logs and help. - VALET_COLOR_XXX=\"color\": will set the colors for the logs and the help, XXX can be one of these: DEFAULT, TITLE, OPTION, ARGUMENT, COMMAND, DEBUG, INFO, WARNING, SUCCESS, ERROR, TIMESTAMP, HIGHLIGHT. - VALET_NO_WRAP=\"true\": will disable the text wrapping for logs. - VALET_NO_ICON=\"true\": will disable the icons for logs and help. - VALET_NO_TIMESTAMP=\"true\": will disable the timestamp for logs. - - VALET_LOG_COLUMNS=\"120\": the number of columns at which to wrap the logs (if wrap is enabled); defaults to the terminal width. + - VALET_LOG_COLUMNS=\"120\": set the number of columns at which to wrap the logs to 120 (if wrap is enabled); defaults to the terminal width. - VALET_CI_MODE=\"true\": will simplify the log output for CI/CD environments (or slow systems), will display the logs without colors, without wrapping lines and with the full date. - - VALET_REMEMBER_LAST_CHOICES=\"3\": number of last choices to remember when selecting an item from a menu. Set to 0 to disable this feature and always display items in the alphabetical order. + - VALET_REMEMBER_LAST_CHOICES=\"3\": number of last choices to remember when selecting an item from a command menu. Set to 0 to disable this feature and always display items in the alphabetical order. - VALET_DO_NOT_USE_LOCAL_BIN=\"false\": if true, valet will use the executable from the PATH even if they exist in the valet bin/ directory. These variables can be exported in your .bashrc file. diff --git a/valet.d/commands.d/self-build b/valet.d/commands.d/self-build index 946539f..94d0672 100644 --- a/valet.d/commands.d/self-build +++ b/valet.d/commands.d/self-build @@ -78,13 +78,13 @@ function selfBuild() { fi inform "Sourcing builtin commands." - eval "$(find "${VALET_HOME}/valet.d/commands.d" -type f -not -path '*/.*' -name '*.sh' | xargs -P1 -I{} printf "source \"%s\"\n" "{}")" + eval "$(find "${VALET_HOME}/valet.d/commands.d" -type f -name '*.sh' -not -iname '.*' | xargs -P1 -I{} printf "source \"%s\"\n" "{}")" eval "$(getTextBetweenLines "function about_this" "}" "$(cat "${VALET_HOME}/valet")")" eval "$(getTextBetweenLines "function about_showCommandHelp" "}" "$(cat "${VALET_HOME}/valet.d/main")")" inform "Sourcing user commands from ⌜${userDirectory}⌝." if [[ -n "${userDirectory}" && -d "${userDirectory}" ]]; then - eval "$(find "${userDirectory}" -type f -not -path '*/.*' -name '*.sh' | xargs -P1 -I{} printf "source \"%s\"\n" "{}")" + eval "$(find "${userDirectory}" -type f -not -path '*/tests.d/*' -name '*.sh' -not -iname '.*' | xargs -P1 -I{} printf "source \"%s\"\n" "{}")" else inform "Skipping user directory with value ⌜${userDirectory}⌝." fi @@ -435,10 +435,7 @@ function writeCommandsMenu() { maxWidth=$(getMaxCommandWidth) maxWidth=$((maxWidth + 2)) echo "# Get the input for fzf to display the header commands menu" - echo "CMD_COMMANDS_MENU_HEADER=\"Please select the command to run (filter by typing anything) - -$(printf "%-${maxWidth}s\t%s\n" "Command name" "Short description")\"" - + echo "CMD_COMMANDS_MENU_HEADER=\"Please select the command to run (filter by typing anything)\"" echo "CMD_COMMANDS_MENU_BODY=\"$(getCommandsMenu | sort)\"" } diff --git a/valet.d/commands.d/self-download-binaries.sh b/valet.d/commands.d/self-download-binaries.sh index 23bcd53..5578ccb 100644 --- a/valet.d/commands.d/self-download-binaries.sh +++ b/valet.d/commands.d/self-download-binaries.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Title: valet.d/commands/* +# Title: valet.d/commands.d/* # Description: this script is a valet command # Author: github.com/jcaillon @@ -89,7 +89,7 @@ function downloadFzf() { local os="${1}" local version="${2}" local destination="${3}" - if [[ "${os}" != "windows" ]]; then + if [[ "${os}" == "linux" ]]; then local fzfUrl="https://github.com/junegunn/fzf/releases/download/${version}/fzf-${version}-${os}_amd64.tar.gz" inform "Downloading fzf from: ${fzfUrl}." kurlFile true 200 fzf.tar.gz "${fzfUrl}" @@ -100,7 +100,11 @@ function downloadFzf() { inform "Downloading fzf from: ${fzfUrl}." kurlFile true 200 fzf.zip "${fzfUrl}" invoke unzip fzf.zip - invoke mv -f "fzf.exe" "${destination}/fzf" + if [[ "${os}" == "darwin" ]]; then + invoke mv -f "fzf" "${destination}/fzf" + else + invoke mv -f "fzf.exe" "${destination}/fzf" + fi fi } diff --git a/valet.d/commands.d/self-install.sh b/valet.d/commands.d/self-install.sh index bf1d030..880bfb5 100644 --- a/valet.d/commands.d/self-install.sh +++ b/valet.d/commands.d/self-install.sh @@ -1,21 +1,88 @@ #!/usr/bin/env bash -# Title: valet.d/commands/* -# Description: this script is a valet command +# Title: valet.d/commands.d/* # Author: github.com/jcaillon +# +# Description: +# +# This script can be used as a standalone script to install Valet. +# The default behavior is to install Valet for all users, which will to type your password on sudo commands. +# Do not run this script with sudo, it will ask for your password when needed. +# +# This script will : +# - Download the latest release from GitHub. +# - Copy it in the Valet home directory, which defaults to: +# - '/opt/valet' in case of a multi user installation +# - '~/.local/valet' otherwise +# - Add a shim script to redirect to the Valet executable in: +# - '/usr/local/bin' in case of a multi user installation +# - '~/.local/bin' otherwise +# - Copy the examples in the user valet directory '~/.valet.d'. +# - Run `valet self build` to update the valet commands. +# - Run the post install command (in case of an installation). +# +# Environment variables to configure the installation: +# +# SINGLE_USER_INSTALLATION: set to 'true' to install Valet for the current user only. +# VALET_HOME: the directory where Valet will be installed. +# DEBUG: set to 'true' to display debug information. +# NO_SHIM: set to 'true' to not create the shim script in /usr/local/bin. +# +# Usage: +# +# To install Valet for all users: +# +# ./self-install.sh +# +# To install Valet for the current user only: +# +# SINGLE_USER_INSTALLATION=true ./self-install.sh + +# if not executing in bash, we can stop here +if [ -z "${BASH_VERSION:-}" ]; then + echo "❌ This script must be run with bash." 1>&2 + exit 0 +fi # import the main script (should always be skipped if the command is run from valet) if [ -z "${_MAIN_INCLUDED:-}" ]; then + NOT_EXECUTED_FROM_VALET=true + VALETD_DIR="${BASH_SOURCE[0]}" VALETD_DIR="${VALETD_DIR%/*}" # strip file name VALETD_DIR="${VALETD_DIR%/*}" # strip directory - # shellcheck source=../main - source "${VALETD_DIR}/main" + + if [[ -e "${VALETD_DIR}/main" ]]; then + # shellcheck source=../main + source "${VALETD_DIR}/main" + else + set -Eeu -o pipefail + + # we are executing this script without valet, create functions to replace the core functions. + function inform() { printf "%-8s %s\n" "INFO" "ℹ️ $*"; } + function debug() { if [[ "${DEBUG:-false}" == "true" ]]; then printf "%-8s %s\n" "DEBUG" "📰 $*"; fi; } + function warn() { printf "%-8s %s\n" "WARNING" "⚠️ $*"; } + function fail() { + printf "%-8s %s\n" "ERROR" "❌ $*" + exit 1 + } + function getOsName() { + case "${OSTYPE:-}" in + darwin*) LAST_RETURNED_VALUE="darwin" ;; + linux*) LAST_RETURNED_VALUE="linux" ;; + msys*) LAST_RETURNED_VALUE="windows" ;; + *) LAST_RETURNED_VALUE="unknown" ;; + esac + } + function getUserDirectory() { LAST_RETURNED_VALUE="${VALET_USER_DIRECTORY:-${HOME}/.valet.d}"; } + VALET_USER_CONFIG_FILE="${VALET_USER_CONFIG_FILE:-"${VALET_CONFIG_DIRECTORY:-${XDG_CONFIG_HOME:-$HOME/.config}/valet}/config"}" + fi fi # --- END OF COMMAND COMMON PART #=============================================================== -# >>> self update valet +# >>> command: self update #=============================================================== + function about_selfUpdate() { echo " command: self update @@ -38,12 +105,168 @@ function selfUpdate() { # Warn the user about: # If you see the replacement character � in my terminal, it means you don't have a [nerd font][nerd-font] setup in your terminal. - invoke fzf --version + # check if valet already exists + local valetAlreadyInstalled=false + if command -v valet &>/dev/null; then + inform "Valet is already installed, updating it." + valetAlreadyInstalled=true + fi + + local SUDO='' + if command -v sudo &>/dev/null; then + SUDO='sudo' + fi + + # set the default options + local binDirectory + if [[ "${SINGLE_USER_INSTALLATION:-false}" == "true" ]]; then + inform "Installing Valet for the current user only." + VALET_HOME="${VALET_HOME:-${HOME}/.local/valet}" + binDirectory="${HOME}/.local/bin" + else + inform "Installing Valet for all users." + VALET_HOME="${VALET_HOME:-/opt/valet}" + binDirectory="/usr/local/bin" + fi + + # get the os + getOsName + local os="${LAST_RETURNED_VALUE}" + inform "The current OS is: ${os}." + + local tempDirectory="${TMPDIR:-/tmp}/temp-${BASHPID}.valet.install.d" + mkdir -p "${tempDirectory}" 1>/dev/null || fail "Could not create the temporary directory ⌜${tempDirectory}⌝." + + # download the latest release and unpack it + local latestReleaseUrl="https://github.com/jcaillon/valet/releases/latest/download/valet-${os}-amd64.tar.gz" + local latestReleaseFile="${tempDirectory}/valet.tar.gz" + inform "Downloading the latest release from ⌜${latestReleaseUrl}⌝." + curl -fsSL -o "${latestReleaseFile}" "${latestReleaseUrl}" || fail "Could not download the latest release from ⌜${latestReleaseUrl}⌝." + debug "Unpacking the release in ⌜${VALET_HOME}⌝." + tar -xzf "${latestReleaseFile}" -C "${tempDirectory}" + debug "The release has been unpacked in ⌜${VALET_HOME}⌝ with:"$'\n'"${LAST_RETURNED_VALUE}." - (invoke fzf --version) + # remove the old valet directory and move the new one + rm -f "${latestReleaseFile}" + $SUDO rm -Rf "${VALET_HOME}" + $SUDO mv -f "${tempDirectory}" "${VALET_HOME}" - echo "ok" | invoke fzf --version + # make valet executable + chmod +x "${VALET_HOME}/valet" - return 0 + # create the shim in the bin directory + local valetBin="${binDirectory}/valet" + if [[ -e "${valetBin}" && "${valetAlreadyInstalled}" == "false" ]]; then + warn "A valet shim already exists in ⌜${valetBin}⌝!?" + else + inform "Creating a shim ⌜${VALET_HOME}/valet → ${valetBin}⌝." + echo "#!/usr/bin/env bash +'${VALET_HOME}/valet' \"\$@\"" >"${valetBin}" + fi + + # copy the examples if the user directory does not exist + getUserDirectory && local userDirectory="${LAST_RETURNED_VALUE}" + if [[ ! -d "${userDirectory}" ]]; then + inform "Copying the examples in ⌜${userDirectory}⌝." + cp -R "${VALET_HOME}/examples.d" "${userDirectory}" + fi + + # silently build the commands + LOG_LEVEL=error "${VALET_HOME}/valet" self build + + # run the post install command + "${VALET_HOME}/valet" self welcome-user } +#=============================================================== +# >>> command: self welcome-user +#=============================================================== + +function about_selfWelcomeUser() { + echo " +command: self welcome-user +fileToSource: ${BASH_SOURCE[0]} +shortDescription: The command run after the installation of Valet to guide the user. +description: |- + The command run after the installation of Valet to guide the user. + + Adjust the Valet configuration according to the user environment. + Let the user know what to do next. +" +} + +function selfWelcomeUser() { + inform "Valet has been successfully installed." + + local valetConfigFileContent + local answer + + inform "Now running test with you to set up Valet." + + echo "─────────────────────────────────────" + echo $'\033'"[0;36mThis is a COLOR CHECK, this line should be COLORED (in cyan by default)."$'\033'"[0m" + echo $'\033'"[0;32mThis is a COLOR CHECK, this line should be COLORED (in green by default)."$'\033'"[0m" + echo "─────────────────────────────────────" + + echo "Do you see the colors in the color check above the line?" + promptYesNo "Answer 'yes' if you see the colors."$'\n'"Answer 'no' if you don't see any colors." + answer="${LAST_RETURNED_VALUE}" + inform "You answered: ${answer}." + + if [[ "${answer}" == "false" ]]; then + valetConfigFileContent+="VALET_NO_COLOR=true"$'\n' + fi + + echo "─────────────────────────────────────" + echo "This is a nerd icon check, check out the next lines:" + echo "A cross within a square: "$'\uf2d3' + echo "A warning sign: "$'\uf071' + echo "A checked box: "$'\uf14a' + echo "An information icon: "$'\uf05a' + echo "─────────────────────────────────────" + + echo "Do you correctly see the nerd icons in the icon check above the line?" + promptYesNo "Answer 'yes' if you see the icons."$'\n'"Answer 'no' if you see ? or anything else instead of the icons." + answer="${LAST_RETURNED_VALUE}" + inform "You answered: ${answer}." + + if [[ "${answer}" == "false" ]]; then + inform "If you see the replacement character ? in my terminal, it means you don't have a nerd-font setup in your terminal."$'\n'"You can download any font here: https://www.nerdfonts.com/font-downloads and install it."$'\n'"After that, you need to setup your terminal to use this newly installed font." + + echo "Do you want to disable the icons in Valet?" + promptYesNo "Answer 'yes' to disable the icons."$'\n'"Answer 'no' if you plan to install a nerd font." + answer="${LAST_RETURNED_VALUE}" + inform "You answered: ${answer}." + + if [[ "${answer}" == "true" ]]; then + valetConfigFileContent+="VALET_NO_ICON=true"$'\n' + fi + fi + + if [[ -n "${valetConfigFileContent:-}" ]]; then + inform "Based on your answers, the following configuration will be added to your valet config file:"$'\n'"${valetConfigFileContent}" + inform "The valet config file is located at ⌜${VALET_USER_CONFIG_FILE}⌝." + + echo "Do you want to apply this configuration?" + promptYesNo "Answer 'yes' to apply the configuration."$'\n'"Answer 'no' to skip this step." + answer="${LAST_RETURNED_VALUE}" + inform "You answered: ${answer}." + if [[ "${answer}" == "true" ]]; then + mkdir -p "${VALET_USER_CONFIG_FILE%/*}" 1>/dev/null || fail "Could not create the valet config directory ⌜${VALET_USER_CONFIG_FILE%/*}⌝." + echo "${valetConfigFileContent}" >>"${VALET_USER_CONFIG_FILE}" + inform "The configuration has been applied." + fi + fi + + succeed "The setup is complete!" + + # tell the user about what's next todo + inform "You can now run ⌜valet --help⌝ to get started." + inform "You can create your own commands and have them available in valet, please check ⌜https://github.com/jcaillon/valet/blob/main/docs/create-new-command.md⌝ or the examples under examples.d to do so." + +} + +# if this script is run directly, execute the function, otherwise valet will do it +if [ "${NOT_EXECUTED_FROM_VALET:-false}" == "true" ]; then + selfUpdate "$@" +fi diff --git a/valet.d/commands.d/self-release.sh b/valet.d/commands.d/self-release.sh index c2669f2..98cd562 100644 --- a/valet.d/commands.d/self-release.sh +++ b/valet.d/commands.d/self-release.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Title: valet.d/commands/* +# Title: valet.d/commands.d/* # Description: this script is a valet command # Author: github.com/jcaillon @@ -43,6 +43,9 @@ options: - name: --dry-run description: |- Do not perform the release, just show what would be done. + - name: --upload-artifacts-only + description: |- + Do no create the release, just upload the artifacts to the latest release. " } @@ -52,7 +55,66 @@ function selfRelease() { if [[ "${dryRun:-}" == "true" ]]; then inform "Dry run mode is enabled, no changes will be made." + fi + + if [[ "${uploadArtifactsOnly:-}" != "true" ]]; then + # create a new release + createRelease "${githubReleaseToken:-}" "${bumpLevel:-}" "${dryRun:-}" + createdReleaseJson="${LAST_RETURNED_VALUE}" else + # get the latest release + kurl true '200' -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/jcaillon/valet/releases/latest" + createdReleaseJson="${LAST_RETURNED_VALUE}" + fi + + if [[ -n "${createdReleaseJson}" ]]; then + extractBetween "${createdReleaseJson}" '"upload_url":' '{?name,label}"' + extractBetween "${LAST_RETURNED_VALUE}" '"' '' + uploadUrl="${LAST_RETURNED_VALUE}" + debug "The upload URL is: ${uploadUrl:-}" + fi + + + # make sure to source the file in which this function is defined + sourceForFunction "selfDownloadBinaries" 2>/dev/null + + # prepare a temp folder to store the binaries + local tempDir="${VALET_HOME}/.tmp" + rm -Rf "${tempDir}" + mkdir -p "${tempDir}" + pushd "${tempDir}" 1>/dev/null + + for artifact in linux windows darwin no-binaries; do + # clean the folder + rm -Rf "${tempDir:?}"/* + + if [[ "${artifact:-}" != "no-binaries" ]]; then + # download the binaries + selfDownloadBinaries -os "${artifact}" --destination "${tempDir}/bin" + fi + + if [[ "${dryRun:-}" != "true" ]]; then + uploadArtifact "${uploadUrl}" "${artifact}" + fi + + done + + popd 1>/dev/null + + rm -Rf "${tempDir}" + + succeed "The new version has been released, check: ⌜https://github.com/jcaillon/valet/releases/latest⌝." + + return 0 +} + +function createRelease() { + local githubReleaseToken bumpLevel dryRun + githubReleaseToken="${1:-}" + bumpLevel="${2:-minor}" + dryRun="${3:-false}" + + if [[ "${dryRun:-}" != "true" ]]; then # check that we got the necessary token if [[ -z "${githubReleaseToken:-}" ]]; then fail "The GitHub release token is required to create a new release." @@ -78,7 +140,9 @@ function selfRelease() { local lastTag invoke git tag --sort=committerdate --no-color lastTag="${LAST_RETURNED_VALUE}" - lastTag="${lastTag%%$'\n'*}" + lastTag="${lastTag%%$'\n'}" + lastTag="${lastTag##*$'\n'}" + echo "⌜${lastTag}⌝" inform "The last tag is: ${lastTag}." # prepare the tag message @@ -127,6 +191,7 @@ function selfRelease() { # create the release on GitHub local uploadUrl + local createdReleaseJson if [[ "${dryRun:-}" != "true" ]]; then kurl true '201,422' -X POST \ -H "Authorization: token ${githubReleaseToken:-}" \ @@ -134,49 +199,13 @@ function selfRelease() { -H "Content-type: application/json; charset=utf-8" \ -d "${releasePayload}" \ "https://api.github.com/repos/jcaillon/valet/releases" - local createdReleaseJson + createdReleaseJson="${LAST_RETURNED_VALUE}" succeed "The new version has been released on GitHub." - - extractBetween "${createdReleaseJson}" '"upload_url":' '{?name,label}"' - extractBetween "${LAST_RETURNED_VALUE}" '"' '' - uploadUrl="${LAST_RETURNED_VALUE}" - - debug "The upload URL is: ${uploadUrl}" fi - # make sure to source the file in which this function is defined - sourceForFunction "selfDownloadBinaries" 2>/dev/null - - # prepare a temp folder to store the binaries - local tempDir="${VALET_HOME}/.tmp" - rm -Rf "${tempDir}" - mkdir -p "${tempDir}" - pushd "${tempDir}" 1>/dev/null - - for artifact in linux windows no-binaries; do - # clean the folder - rm -Rf "${tempDir:?}"/* - - if [[ "${artifact:-}" != "no-binaries" ]]; then - # download the binaries - selfDownloadBinaries -os "${artifact}" --destination "${tempDir}/bin" - fi - - if [[ "${dryRun:-}" != "true" ]]; then - uploadArtifact "${uploadUrl}" "${artifact}" - fi - - done - - popd 1>/dev/null - - rm -Rf "${tempDir}" - - succeed "The new version has been released, check: ⌜https://github.com/jcaillon/valet/releases/latest⌝." - - return 0 + LAST_RETURNED_VALUE="${createdReleaseJson:-}" } function uploadArtifact() { @@ -187,6 +216,7 @@ function uploadArtifact() { case "${os}" in linux) artifactName="valet-linux-amd64" ;; windows) artifactName="valet-windows-amd64" ;; + darwin) artifactName="valet-darwin-amd64" ;; no-binaries) artifactName="valet-no-binaries" ;; esac @@ -206,7 +236,7 @@ function uploadArtifact() { # prepare artifact local artifactPath="${artifactName}.tar.gz" invoke tar -czvf "${artifactPath}" "${files[@]}" - inform "The artifact has been created at ⌜${artifactPath}⌝ with:"$'\n'"${LAST_RETURNED_VALUE}" + debug "The artifact has been created at ⌜${artifactPath}⌝ with:"$'\n'"${LAST_RETURNED_VALUE}" # upload the artifact if [[ "${dryRun:-}" != "true" && -n "${uploadUrl}" ]]; then diff --git a/valet.d/commands.d/self-test.sh b/valet.d/commands.d/self-test.sh index 059906e..5f8a984 100644 --- a/valet.d/commands.d/self-test.sh +++ b/valet.d/commands.d/self-test.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Title: valet.d/commands/* +# Title: valet.d/commands.d/* # Description: this script is a valet command # Author: github.com/jcaillon diff --git a/valet.d/commands.d/self.sh b/valet.d/commands.d/self.sh index afdf495..a96df30 100644 --- a/valet.d/commands.d/self.sh +++ b/valet.d/commands.d/self.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Title: valet.d/commands/* +# Title: valet.d/commands.d/* # Description: this script is a valet command # Author: github.com/jcaillon @@ -13,6 +13,8 @@ if [ -z "${_MAIN_INCLUDED:-}" ]; then fi # --- END OF COMMAND COMMON PART +#TODO: valet self zefzef should not open the self menu! + #=============================================================== # >>> dev sub menu #=============================================================== diff --git a/valet.d/core b/valet.d/core index a7bcea7..2ece0ea 100644 --- a/valet.d/core +++ b/valet.d/core @@ -91,8 +91,11 @@ function cleanupTempFiles() { debug "Deleting temporary directory." rm -Rf "${_TEMPORARY_DIRECTORY}" 1>&2 2>/dev/null fi - if [ -e "${_TEMPORARY_STDOUT_FILE}" ]; then - rm -f "${_TEMPORARY_STDOUT_FILE}" 1>&2 2>/dev/null + if [ -e "${_TEMPORARY_WORK_FILE}" ]; then + rm -f "${_TEMPORARY_WORK_FILE}" 1>&2 2>/dev/null + fi + if [ -e "${_TEMPORARY_STDERR_FILE}" ]; then + rm -f "${_TEMPORARY_STDERR_FILE}" 1>&2 2>/dev/null fi if [ -e "${_TEMPORARY_STDERR_FILE}" ]; then rm -f "${_TEMPORARY_STDERR_FILE}" 1>&2 2>/dev/null @@ -104,9 +107,9 @@ function cleanupTempFiles() { #=============================================================== # determine the color mode expect if the user has set VALET_NO_COLOR -case "$TERM" in +case "${TERM:-}" in xterm-color|xterm-256color|linux) VALET_NO_COLOR="${VALET_NO_COLOR:-false}";; - xterm) [ -n "$COLORTERM" ] && VALET_NO_COLOR="${VALET_NO_COLOR:-false}";; + xterm) [ -n "${COLORTERM:-}" ] && VALET_NO_COLOR="${VALET_NO_COLOR:-false}" || VALET_NO_COLOR="${VALET_NO_COLOR:-true}";; *) VALET_NO_COLOR="${VALET_NO_COLOR:-true}";; esac @@ -1261,6 +1264,245 @@ function checkParseResults() { } +#=============================================================== +# >>> Fzf glorious functions +#=============================================================== + +# Open fzf with the given mode, history id, header and body. +# +# $1: the mode to use, can be "menu", "search", "options" +# $2: the history id to use (used in the name of the fzf history file) +# $3: the header to display +# $4: the body to display +# $5+: additional options to pass to fzf +# +# Returns: +# The choices made by the user in the global variable LAST_RETURNED_VALUE. +# The first line is the query (can be empty), the next lines are the choices. +# +# Usage: +# showFzf "menu" "my-command" "Choose an option" "option1"$'\n'"option2"$'\n'"option3" +function showFzf() { + local mode historyId header body + mode="${1}" + historyId="${2}" + header="${3}" + body="${4}" + shift 4 + + getCommandFullPath "fzf" + local fzfExecutable="${LAST_RETURNED_VALUE}" + + debug "Running fzf from ⌜${fzfExecutable}⌝." + + local -a defaultOptions + local -a modeOptions + + # get the history file + getLocalStateDirectory && local stateDirectory="${LAST_RETURNED_VALUE}" + local historyFile + if [[ -n "${historyId}" ]]; then + historyFile="${stateDirectory}/fzf-history-${historyId}" + if [ ! -e "${historyFile}" ]; then + touch "${historyFile}" + fi + defaultOptions+=( + --history="${historyFile}" + --history-size=50 + --bind "alt-up:prev-history" + --bind "alt-down:next-history" + ) + fi + + + # compute preview width + local previewWidth=$((_COLUMNS / 2 - 10)) + if [[ previewWidth -le 15 ]]; then + previewWidth=15 + fi + + local headerHelp="${COLOR_ERROR}Press ALT+H to display the help and keybindings.${COLOR_DEFAULT}"$'\n' + + local helpPreview + case "${mode}" in + "options" | "menu") + helpPreview="${COLOR_INFO}HELP${COLOR_DEFAULT}"$'\n'$'\n'"Navigate through the options with the ${COLOR_ERROR}UP${COLOR_DEFAULT}/${COLOR_ERROR}DOWN${COLOR_DEFAULT} keys."$'\n'$'\n'"Validate your choice with ${COLOR_ERROR}ENTER${COLOR_DEFAULT}." + modeOptions+=( + --no-multi + ) + if [[ "${mode}" == "options" ]]; then + modeOptions+=( + --height=~100% + --min-height=10 + ) + fi + ;; + "multi-options") + helpPreview="${COLOR_INFO}HELP${COLOR_DEFAULT}"$'\n'$'\n'"Navigate through the options with the ${COLOR_ERROR}UP${COLOR_DEFAULT}/${COLOR_ERROR}DOWN${COLOR_DEFAULT} keys."$'\n'$'\n'"Opt-in and out of an option using ${COLOR_ERROR}TAB${COLOR_DEFAULT}."$'\n'$'\n'"Validate with ${COLOR_ERROR}ENTER${COLOR_DEFAULT}." + modeOptions+=( + --multi + --height=~100% + --min-height=10 + ) + ;; + "yes-no") + helpPreview="${COLOR_INFO}HELP${COLOR_DEFAULT}"$'\n'$'\n'"Navigate through the options with the ${COLOR_ERROR}UP${COLOR_DEFAULT}/${COLOR_ERROR}DOWN${COLOR_DEFAULT} keys."$'\n'$'\n'"Validate your choice with ${COLOR_ERROR}ENTER${COLOR_DEFAULT}." + modeOptions+=( + --height=~100% + --min-height=10 + --no-multi + --no-info + --no-separator + ) + body="Yes"$'\n'"No" + headerHelp="" + helpPreview="" + ;; + "query") + helpPreview="${COLOR_INFO}HELP${COLOR_DEFAULT}"$'\n'$'\n'"Type your answer in the prompt."$'\n'$'\n'"Validate with ${COLOR_ERROR}ENTER${COLOR_DEFAULT}." + modeOptions+=( + --height=~100% + ) + if [[ -z "${body}" ]]; then + modeOptions+=( + --no-mouse + --no-info + --no-separator + --no-scrollbar + --pointer=' ' + --color=pointer:-1 + --color=fg+:-1 + --color=bg+:-1 + ) + headerHelp="" + helpPreview="" + else + modeOptions+=( + --bind "tab:replace-query" + --min-height=10 + ) + headerHelp="${COLOR_ERROR}Type your answer, press TAB to replace with the selection (ALT+H for help).${COLOR_DEFAULT}"$'\n' + helpPreview+=$'\n'$'\n'"Use ${COLOR_ERROR}TAB${COLOR_DEFAULT} to replace your answer with the current selection." + fi + ;; + "command-options") + helpPreview="${COLOR_INFO}HELP${COLOR_DEFAULT}"$'\n'$'\n'"Navigate through the options with the ${COLOR_ERROR}UP${COLOR_DEFAULT}/${COLOR_ERROR}DOWN${COLOR_DEFAULT} keys."$'\n'$'\n'"Opt-in and out of an option using ${COLOR_ERROR}TAB${COLOR_DEFAULT} or ${COLOR_ERROR}SPACE${COLOR_DEFAULT}."$'\n'$'\n'"Modify the value of an option by pressing ${COLOR_ERROR}M${COLOR_DEFAULT}."$'\n'$'\n'"Validate with ${COLOR_ERROR}ENTER${COLOR_DEFAULT}." + modeOptions+=( + --multi + --bind "tab:toggle" + --bind "space:toggle" + --bind "m:execute(echo {} | fzf)" + ) + ;; + *) + fail "Unknown fzf mode ⌜${mode}⌝." + ;; + esac + + helpPreview+=" + +Cancel with ${COLOR_ERROR}ESC${COLOR_DEFAULT} or ${COLOR_ERROR}CTRL+C${COLOR_DEFAULT}. + +${COLOR_INFO}ADDITIONAL KEY BINDINGS${COLOR_DEFAULT} + +${COLOR_ERROR}ALT+H${COLOR_DEFAULT}: Show this help. +${COLOR_ERROR}ALT+/${COLOR_DEFAULT}: Rotate through the preview options (this pane). +${COLOR_ERROR}ALT+UP${COLOR_DEFAULT}/${COLOR_ERROR}ALT+DOWN${COLOR_DEFAULT}: Previous/next query in the history. +${COLOR_ERROR}SHIFT+UP${COLOR_DEFAULT}/${COLOR_ERROR}SHIFT+DOWN${COLOR_DEFAULT}: Scroll the preview up and down. +" + + if [[ -n "${helpPreview}" ]]; then + wrapText "${helpPreview}" "${previewWidth}" + helpPreview="${LAST_RETURNED_VALUE}" + defaultOptions+=( + "--bind=alt-h:preview(echo -e '${helpPreview}')" + "--preview-window=right,${previewWidth}" + --bind 'alt-/:change-preview-window(right,70%|down,40%,border-horizontal|hidden|)' + ) + fi + + # shellcheck disable=SC2054 + defaultOptions+=( + --layout=reverse + --info=right + --pointer=◆ + --marker=✓ + --cycle + --tiebreak=begin,index + --margin=0 + --padding=0 + --delimiter=$'\n' + --tabstop=3 + --header-first + --header="${headerHelp}${header}" + --print-query + ) + + # reset the output files, but only if they already exist + [[ -s "${_TEMPORARY_STDOUT_FILE}" ]] && : >"${_TEMPORARY_STDOUT_FILE}" + + "${fzfExecutable}" "${defaultOptions[@]}" "${modeOptions[@]}" "$@" <<<"${body}" 1>"${_TEMPORARY_STDOUT_FILE}" || true + + local choice + if [ -s "${_TEMPORARY_STDOUT_FILE}" ]; then + IFS= read -rd '' choice <"${_TEMPORARY_STDOUT_FILE}" || true + else + choice="" + fi + + # if any, append the query to the history file + if [[ -n "${historyId}" ]]; then + local query=${choice%%$'\n'*} + if [ -n "${query}" ]; then + echo "${query}" >>"${historyFile}" + fi + fi + + LAST_RETURNED_VALUE="${choice}" +} + +# Ask the user for a string input. +# +# $1: the prompt to display +# $2: a list of options that the user can select with TAB (can be empty, separated by new lines) +# +# Returns: +# The value entered by the user in the global variable LAST_RETURNED_VALUE. +# +# Usage: +# promptString "What is your name?" "John" && local name="${LAST_RETURNED_VALUE}" +function promptString() { + local prompt autocompletionValues + prompt="${1}" + autocompletionValues="${2}" + + showFzf "query" "" "${prompt}" "${autocompletionValues:-}" + + # we keep only the query, i.e. the first line + LAST_RETURNED_VALUE="${LAST_RETURNED_VALUE%%$'\n'*}" +} + +# Ask the user to yes or no. +# +# $1: the prompt to display +# +# Returns: +# True or false in the global variable LAST_RETURNED_VALUE. +# +# Usage: +# promptYesNo "Do you want to continue?" && local answer="${LAST_RETURNED_VALUE}" +function promptYesNo() { + local prompt + prompt="${1}" + + showFzf "yes-no" "" "${prompt}" '' + + # we keep only the selected value, i.e. the 2nd line + LAST_RETURNED_VALUE="${LAST_RETURNED_VALUE#*$'\n'}" + LAST_RETURNED_VALUE="${LAST_RETURNED_VALUE%%$'\n'*}" + [[ "${LAST_RETURNED_VALUE}" == "Yes" ]] && LAST_RETURNED_VALUE=true || LAST_RETURNED_VALUE=false +} + #=============================================================== # >>> Include main #=============================================================== diff --git a/valet.d/main b/valet.d/main index 8cb6c2d..578df08 100644 --- a/valet.d/main +++ b/valet.d/main @@ -665,7 +665,7 @@ function showCommandsMenuAndRun() { sortCommands "${menuId}" "${commands}" && sortedCommandsMenuBody="${LAST_RETURNED_VALUE}" local commandChoice - showInteractiveCommandsMenu "${CMD_COMMANDS_MENU_HEADER}" "${sortedCommandsMenuBody}" && commandChoice="${LAST_RETURNED_VALUE}" + showInteractiveCommandsMenu "${menuId}" "${CMD_COMMANDS_MENU_HEADER}" "${sortedCommandsMenuBody}" && commandChoice="${LAST_RETURNED_VALUE}" if [[ -z "${commandChoice}" ]]; then # the user pressed escape or ctrl-c exit 0 @@ -799,58 +799,30 @@ function addLastChoice() { # show an interactive menu for commands # returns the selected command in standard output -# $1: the menu header -# $2: the menu body -# $3+: additional options to pass to fzf (e.g. --height=10) +# $1: the menu id +# $2: the menu header +# $3: the menu body +# $4+: additional options to pass to fzf (e.g. --height=10) # # Usage: -# showInteractiveCommandsMenu "header" "body" && choice="${LAST_RETURNED_VALUE}" -# showInteractiveCommandsMenu "header" "body" --height=10 && choice="${LAST_RETURNED_VALUE}" +# showInteractiveCommandsMenu "menuId" "header" "body" && choice="${LAST_RETURNED_VALUE}" +# showInteractiveCommandsMenu "menuId" "header" "body" --height=10 && choice="${LAST_RETURNED_VALUE}" function showInteractiveCommandsMenu() { local menuHeader menuBody - menuHeader="${1}" - menuBody="${2}" + menuId="${1}" + menuHeader="${2}" + menuBody="${3}" shift 2 - local -i menuHeaderLength - local line - while read -r line; do - menuHeaderLength+=1 - done <<<"${menuHeader}" - - local commandsPrompt previewWidth commandChoice - commandsPrompt="${menuHeader}"$'\n'"${menuBody}" - previewWidth=$((_COLUMNS / 2 - 10)) - if [[ previewWidth -le 0 ]]; then - previewWidth=10 - fi - - local choice - - # reset the output files, but only if they already exist - [[ -s "${_TEMPORARY_STDOUT_FILE}" ]] && : >"${_TEMPORARY_STDOUT_FILE}" + showFzf "menu" "${menuId}" "${menuHeader}" "${menuBody}" \ + --preview-label='Command help' \ + --preview="VALET_LOG_LEVEL=error '${VALET_HOME}/valet' help --columns \$((FZF_PREVIEW_COLUMNS - 1)) {1}" - getCommandFullPath "fzf" - local fzfExecutable="${LAST_RETURNED_VALUE}" + choice="${LAST_RETURNED_VALUE}" + choice="${choice#*$'\n'}" # delete first line (query) + choice="${choice%%$'\t'*}" + choice="${choice%% *}" - debug "Running fzf from ⌜${fzfExecutable}⌝." - - "${fzfExecutable}" \ - --tiebreak=begin,index --no-multi --cycle \ - --layout=reverse --info=default \ - --margin=0 --padding=0 \ - "--header-lines=${menuHeaderLength}" \ - --preview-window=right:$((previewWidth)):wrap \ - --preview="echo {} | cut -d$'\t' -f1 | sed -e 's/[[:space:]]*$//' | xargs -P1 -I{} '${VALET_HOME}/valet' --log-level fail help --columns $((previewWidth - 2)) {}" \ - "$@" <<<"${commandsPrompt}" 1>"${_TEMPORARY_STDOUT_FILE}" || true - - if [ -s "${LAST_RETURNED_VALUE}" ]; then - IFS= read -rd '' choice <"${_TEMPORARY_STDOUT_FILE}" || true - choice="${choice%%$'\t'*}" - choice="${choice%% *}" - else - choice="" - fi LAST_RETURNED_VALUE="${choice}" } diff --git a/valet.d/version b/valet.d/version index a2268e2..53b61ec 100644 --- a/valet.d/version +++ b/valet.d/version @@ -1 +1 @@ -0.3.1 \ No newline at end of file +0.3.6 \ No newline at end of file