From f76d21a7ab60ef0b9976448d01e3ca1325750bd3 Mon Sep 17 00:00:00 2001 From: Julien Caillon Date: Tue, 3 Dec 2024 18:03:33 +0100 Subject: [PATCH] :sparkles: self extend to create extensions --- .vscode/settings.json | 3 +- docs/content/docs/002.installation/_index.md | 8 +- .../docs/500.working-on-bash/_index.md | 2 +- extras/.vscode/settings.json | 3 +- extras/{.vscode => }/valet.code-snippets | 0 tests.d/1102-self-build/results.approved.md | 16 +- tests.d/1103-self-release/results.approved.md | 6 +- tests.d/1106-self-install/test | 21 ++- .../1107-self-document/01.self-document.sh | 4 +- .../1107-self-document/results.approved.md | 10 +- tests.d/1300-valet-cli/results.approved.md | 6 +- valet.d/commands.d/self-document.sh | 4 +- valet.d/commands.d/self-extend.sh | 151 +++++++++++++----- valet.d/lib-io | 13 +- valet.d/version | 2 +- 15 files changed, 173 insertions(+), 76 deletions(-) rename extras/{.vscode => }/valet.code-snippets (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 11dfbcb..0c0cdab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { // https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html "bashIde.globPattern": "**/@(core|main|lib-!(valet)|self-*)", - "bashIde.includeAllWorkspaceSymbols": true + "bashIde.includeAllWorkspaceSymbols": true, + "terminal.integrated.wordSeparators": " ()[]{}',\"`─‘’“”|⌜⌝" } \ No newline at end of file diff --git a/docs/content/docs/002.installation/_index.md b/docs/content/docs/002.installation/_index.md index 4865ee5..32d7179 100644 --- a/docs/content/docs/002.installation/_index.md +++ b/docs/content/docs/002.installation/_index.md @@ -45,6 +45,11 @@ Finally, call `valet` to get started with the example commands! 2. Add this directory to your PATH so you can call `valet` from your terminal. 3. Call `valet` to get started with the example commands! +## ⚙️ Extra config for your terminal + +- **Nerd font**: Valet can make use of [Nerd fonts][nerdFontsLink] to display icons in the logs and interactive sessions. You can download any nerd font from [here][nerdFontsLink], install it, and configure your terminal to use that font. +- **Word separator**: Valet usually prints important values between ⌜quotes⌝ using the UTF8 top left and top right corners (respectively U+231C and U+231D). You can add these two characters as word separators in your terminal for quick double click selection: `⌜⌝`. + ## 🪟 Use Valet on windows It is recommended to use Git bash, installed with [Git for Windows][git-for-windows-link] and [Windows terminal][windows-terminal-link] as your terminal program. You can also use Valet with any Linux distribution and [Windows Subsystem for Linux][wsl-installation-link] (WSL). @@ -79,4 +84,5 @@ Alternatively, use `docker run --rm -it noyacode/valet` to pull and run the imag [selfInstallCommandUsageLink]: https://github.com/jcaillon/valet/blob/latest/tests.d/1106-self-update/results.approved.md [docker]: https://www.docker.com/ [podman]: https://podman.io/ -[valetImageTagsLink]: https://github.com/jcaillon/valet/pkgs/container/valet \ No newline at end of file +[valetImageTagsLink]: https://github.com/jcaillon/valet/pkgs/container/valet +[nerdFontsLink]: https://www.nerdfonts.com/font-downloads \ No newline at end of file diff --git a/docs/content/docs/500.working-on-bash/_index.md b/docs/content/docs/500.working-on-bash/_index.md index 98c6cc5..a8b8424 100644 --- a/docs/content/docs/500.working-on-bash/_index.md +++ b/docs/content/docs/500.working-on-bash/_index.md @@ -41,7 +41,7 @@ You can open your `~/.valet.d` directory as a workspace on vscode. ### Autocompletion on Valet library functions -You can use the Valet [vscode snippets](https://github.com/jcaillon/valet/blob/latest/extras/.vscode/valet.code-snippets) which should be generated locally using the `valet self document` command. +You can use the Valet [vscode snippets](https://github.com/jcaillon/valet/blob/latest/extras/valet.code-snippets) which should be generated locally using the `valet self document` command. Then: diff --git a/extras/.vscode/settings.json b/extras/.vscode/settings.json index 322650f..99d1727 100644 --- a/extras/.vscode/settings.json +++ b/extras/.vscode/settings.json @@ -1,5 +1,6 @@ { // https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html "bashIde.globPattern": "**/@(lib-*|src/*.sh)", - "bashIde.includeAllWorkspaceSymbols": true + "bashIde.includeAllWorkspaceSymbols": true, + "terminal.integrated.wordSeparators": " ()[]{}',\"`─‘’“”|⌜⌝" } \ No newline at end of file diff --git a/extras/.vscode/valet.code-snippets b/extras/valet.code-snippets similarity index 100% rename from extras/.vscode/valet.code-snippets rename to extras/valet.code-snippets diff --git a/tests.d/1102-self-build/results.approved.md b/tests.d/1102-self-build/results.approved.md index 307dda9..51a2d16 100644 --- a/tests.d/1102-self-build/results.approved.md +++ b/tests.d/1102-self-build/results.approved.md @@ -11,7 +11,7 @@ Exit code: `0` ```plaintext CMD_ALL_COMMANDS=$'self\nhelp\nself build\nself config\nself document\nself export\nself extend\nself mock1\nself mock2\nself mock3\nself mock4\nself release\nself setup\nself test\nself uninstall\nself update' CMD_ALL_COMMANDS_ARRAY=0 "help" 1 "self build" 2 "self config" 3 "self document" 4 "self export" 5 "self extend" 6 "self mock1" 7 "self mock2" 8 "self mock3" 9 "self mock4" 10 "self release" 11 "self setup" 12 "self test" 13 "self uninstall" 14 "self update" -CMD_ALL_COMMAND_SELECTION_ITEMS_ARRAY=0 "help Show the help of this program or of a specific command." 1 "self build Index all the commands and libraries present in the valet user directory and installation directory." 2 "self config Open the configuration file of Valet with your default editor." 3 "self document Generate the documentation and code snippets for all the library functions of Valet." 4 "self extend Extends Valet by downloading a new application or library in the user directory." 5 "self test Test your valet custom commands." 6 "self update Update valet and its extensions to the latest releases." +CMD_ALL_COMMAND_SELECTION_ITEMS_ARRAY=0 "help Show the help of this program or of a specific command." 1 "self build Index all the commands and libraries present in the valet user directory and installation directory." 2 "self config Open the configuration file of Valet with your default editor." 3 "self document Generate the documentation and code snippets for all the library functions of Valet." 4 "self extend Extends Valet by creating or downloading a new extension in the user directory." 5 "self test Test your valet custom commands." 6 "self update Update valet and its extensions to the latest releases." CMD_ALL_FUNCTIONS=$'this\nshowCommandHelp\nselfBuild\nselfConfig\nselfDocument\nselfExport\nselfExtend\nselfUpdate\nselfMock1\nselfMock2\nselfMock3\nselfMock4\nselfRelease\nselfSetup\nselfTest\nselfUninstall' CMD_ALL_FUNCTIONS_ARRAY=0 "this" 1 "showCommandHelp" 2 "selfBuild" 3 "selfConfig" 4 "selfDocument" 5 "selfExport" 6 "selfExtend" 7 "selfUpdate" 8 "selfMock1" 9 "selfMock2" 10 "selfMock3" 11 "selfMock4" 12 "selfRelease" 13 "selfSetup" 14 "selfTest" 15 "selfUninstall" CMD_ALL_MENU_COMMANDS_ARRAY=0 "self" @@ -21,7 +21,7 @@ CMD_ARGS_LAST_IS_ARRAY_selfMock2='true' CMD_ARGS_LAST_IS_ARRAY_selfMock4='false' CMD_ARGS_LAST_IS_ARRAY_showCommandHelp='true' CMD_ARGS_LAST_IS_ARRAY_this='true' -CMD_ARGS_NAME_selfExtend=0 "repositoryUri" +CMD_ARGS_NAME_selfExtend=0 "extensionUri" CMD_ARGS_NAME_selfMock1=0 "action" CMD_ARGS_NAME_selfMock2=0 "firstArg" 1 "more" CMD_ARGS_NAME_selfMock4=0 "firstArg" 1 "secondArg" @@ -33,19 +33,19 @@ CMD_ARGS_NB_OPTIONAL_selfMock2='0' CMD_ARGS_NB_OPTIONAL_selfMock4='0' CMD_ARGS_NB_OPTIONAL_showCommandHelp='1' CMD_ARGS_NB_OPTIONAL_this='1' -CMD_ARGUMENTS_DESCRIPTION_selfExtend=0 $'The URL of the repository to download and install in Valet.\n\nUsually a GitHub or GitLab repository URL such as `https://github.com/jcaillon/valet-devops-toolbox.git`.\n\nIf the repository is private, you can pass the URL with the username and password like this:\n`https://username:password@my.gitlab.private/group/project.git`.' +CMD_ARGUMENTS_DESCRIPTION_selfExtend=0 $'The URI of the extension to install or create.\n\n1. If you want to create a new extension, this argument should be the name of your\n new extension (e.g. `my-new-extension`).\n2. If you want to setup an existing directory as an extension, this argument should be `.`.\n3. If you want to download an extension, this argument should be the URL of the repository.\n Usually a GitHub or GitLab repository URL such as `https://github.com/jcaillon/valet-devops-toolbox.git`.\n\n> If the repository is private, you can pass the URL with the username and password like this:\n> `https://username:password@my.gitlab.private/group/project.git`.' CMD_ARGUMENTS_DESCRIPTION_selfMock1=0 $'The action to perform.\nOne of the following options:\n\n- error\n- fail\n- fail2\n- exit\n- unknown-command\n- create-temp-files\n- logging-level\n- wait-indefinitely\n- show-help\n- print-raw-and-file\n' CMD_ARGUMENTS_DESCRIPTION_selfMock2=0 "First argument." 1 "Will be an an array of strings." CMD_ARGUMENTS_DESCRIPTION_selfMock4=0 "First argument." 1 $'Second argument.\n' CMD_ARGUMENTS_DESCRIPTION_showCommandHelp=0 $'The name of the command to show the help for.\nIf not provided, show the help for the program.' CMD_ARGUMENTS_DESCRIPTION_this=0 $'The command or sub commands to execute.\nSee the commands section for more information.' -CMD_ARGUMENTS_NAME_selfExtend=0 "repositoryUri" +CMD_ARGUMENTS_NAME_selfExtend=0 "extensionUri" CMD_ARGUMENTS_NAME_selfMock1=0 "action" CMD_ARGUMENTS_NAME_selfMock2=0 "firstArg" 1 "more..." CMD_ARGUMENTS_NAME_selfMock4=0 "firstArg" 1 "secondArg" CMD_ARGUMENTS_NAME_showCommandHelp=0 "commands?..." CMD_ARGUMENTS_NAME_this=0 "commands?..." -CMD_COMMANDS_DESCRIPTION_this=0 "Show the help of this program or of a specific command." 1 "Index all the commands and libraries present in the valet user directory and installation directory." 2 "Open the configuration file of Valet with your default editor." 3 "Generate the documentation and code snippets for all the library functions of Valet." 4 "Returns a string that can be evaluated to have Valet functions in bash." 5 "Extends Valet by downloading a new application or library in the user directory." 6 "A command that only for testing valet core functions." 7 "A command that only for testing valet core functions." 8 "A command that only for testing valet core functions." 9 "A command that only for testing valet core functions." 10 "Release a new version of valet." 11 "The command run after the installation of Valet to setup the tool." 12 "Test your valet custom commands." 13 "A command to uninstall Valet." 14 "Update valet and its extensions to the latest releases." +CMD_COMMANDS_DESCRIPTION_this=0 "Show the help of this program or of a specific command." 1 "Index all the commands and libraries present in the valet user directory and installation directory." 2 "Open the configuration file of Valet with your default editor." 3 "Generate the documentation and code snippets for all the library functions of Valet." 4 "Returns a string that can be evaluated to have Valet functions in bash." 5 "Extends Valet by creating or downloading a new extension in the user directory." 6 "A command that only for testing valet core functions." 7 "A command that only for testing valet core functions." 8 "A command that only for testing valet core functions." 9 "A command that only for testing valet core functions." 10 "Release a new version of valet." 11 "The command run after the installation of Valet to setup the tool." 12 "Test your valet custom commands." 13 "A command to uninstall Valet." 14 "Update valet and its extensions to the latest releases." CMD_COMMANDS_NAME_this=0 "help" 1 "self build" 2 "self config" 3 "self document" 4 "self export" 5 "self extend" 6 "self mock1" 7 "self mock2" 8 "self mock3" 9 "self mock4" 10 "self release" 11 "self setup" 12 "self test" 13 "self uninstall" 14 "self update" CMD_COMMAND_selfBuild='self build' CMD_COMMAND_selfConfig='self config' @@ -68,7 +68,7 @@ CMD_DESCRIPTION_selfBuild=$'Index all the command and libraries present in the v CMD_DESCRIPTION_selfConfig=$'Open the configuration file of Valet with your default editor.\n\nThis allows you to set advanced options for Valet.' CMD_DESCRIPTION_selfDocument=$'Generate the documentation and code snippets for all the library functions of Valet.\n\nIt will parse all the library files and generate:\n\n- A markdown file with the documentation.\n- A bash file with the prototype of each function.\n- A vscode snippet file for each function.' CMD_DESCRIPTION_selfExport=$'If you want to use Valet functions directly in bash, you can use this command like this:\n\n```bash\neval "$(valet self export 2>/dev/null)"\n```\n\nThis will export all the necessary functions and variables to use the Valet log library by default.\n\nYou can optionally export all the functions if needed.' -CMD_DESCRIPTION_selfExtend=$'Extends Valet by downloading a new application or library in the user directory.\n\n- Applications usually add new commands to Valet.\n- Libraries usually add new callable functions to Valet.\n\nThis command will download the given repository and install it in the Valet user directory.\nIf a `extension.setup.sh` script is present in the repository root directory, it will be executed.\n\nFor GitHub and GitLab repositories, this command will:\n\n1. If git is installed, clone the repository for the given reference (version option).\n2. Otherwise, download source tarball for the given reference and extract it.\n\nOnce an extension is installed, you can use the `valet self update` command to update it.' +CMD_DESCRIPTION_selfExtend=$'Extends Valet by creating or downloading a new extension in the user directory.\nExtensions can add new commands or functions to Valet.\n\nThis command will either:\n\n- Create and setup a new extension directory under the valet user directory,\n- setup an existing directory as a valet extension,\n- or download the given extension (repository) and install it in the Valet user directory.\n\nFor downloaded extensions, all GIT repositories are supported.\nFor the specific cases of GitHub and GitLab repositories, this command will:\n\n1. If git is installed, clone the repository for the given reference (version option).\n2. If git is not installed, download source tarball for the given reference and extract it.\n\nFor downloaded extensions, if a `extension.setup.sh` script is present in the repository root directory,\nit will be executed. This gives the extension the opportunity to setup itself.\n\nOnce an extension is installed, you can use the `valet self update` command to update it.' CMD_DESCRIPTION_selfMock1='A command that only for testing valet core functions.' CMD_DESCRIPTION_selfMock2=$'An example of description.\n\nYou can put any text here, it will be wrapped to fit the terminal width.\n\nYou can highlight some text as well.' CMD_DESCRIPTION_selfMock3=$'Before starting this command, valet will check if sudo is available.\n\nIf so, it will require the user to enter the sudo password and use sudo inside the command\n' @@ -255,7 +255,7 @@ CMD_SHORT_DESCRIPTION_selfBuild='Index all the commands and libraries present in CMD_SHORT_DESCRIPTION_selfConfig='Open the configuration file of Valet with your default editor.' CMD_SHORT_DESCRIPTION_selfDocument='Generate the documentation and code snippets for all the library functions of Valet.' CMD_SHORT_DESCRIPTION_selfExport='Returns a string that can be evaluated to have Valet functions in bash.' -CMD_SHORT_DESCRIPTION_selfExtend='Extends Valet by downloading a new application or library in the user directory.' +CMD_SHORT_DESCRIPTION_selfExtend='Extends Valet by creating or downloading a new extension in the user directory.' CMD_SHORT_DESCRIPTION_selfMock1='A command that only for testing valet core functions.' CMD_SHORT_DESCRIPTION_selfMock2='A command that only for testing valet core functions.' CMD_SHORT_DESCRIPTION_selfMock3='A command that only for testing valet core functions.' @@ -328,7 +328,7 @@ help Show the help of this program or of a specific command. self build Index all the commands and libraries present in the valet user directory and installation directory. self config Open the configuration file of Valet with your default editor. self document Generate the documentation and code snippets for all the library functions of Valet. -self extend Extends Valet by downloading a new application or library in the user directory. +self extend Extends Valet by creating or downloading a new extension in the user directory. self test Test your valet custom commands. self update Update valet and its extensions to the latest releases. diff --git a/tests.d/1103-self-release/results.approved.md b/tests.d/1103-self-release/results.approved.md index 7f0bb7a..fb05b27 100644 --- a/tests.d/1103-self-release/results.approved.md +++ b/tests.d/1103-self-release/results.approved.md @@ -226,8 +226,8 @@ INFO The documentation has been generated in ⌜$GLOBAL_VALET_HOME/extras/li ▶ called io::writeToFile $GLOBAL_VALET_HOME/extras/lib-valet ▶ called io::writeToFile $GLOBAL_VALET_HOME/extras/lib-valet INFO The prototype script has been generated in ⌜$GLOBAL_VALET_HOME/extras/lib-valet⌝. -▶ called io::writeToFile $GLOBAL_VALET_HOME/extras/.vscode/valet.code-snippets -INFO The vscode snippets have been generated in ⌜$GLOBAL_VALET_HOME/extras/.vscode/valet.code-snippets⌝. +▶ called io::writeToFile $GLOBAL_VALET_HOME/extras/valet.code-snippets +INFO The vscode snippets have been generated in ⌜$GLOBAL_VALET_HOME/extras/valet.code-snippets⌝. ▶ called io::invoke rm -f $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/array.md ▶ called io::invoke rm -f $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/codes.md ▶ called io::invoke rm -f $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/core.md @@ -256,7 +256,7 @@ INFO The vscode snippets have been generated in ⌜$GLOBAL_VALET_HOME/extras ▶ called io::invoke cp $GLOBAL_VALET_HOME/.vscode/extensions.json $GLOBAL_VALET_HOME/extras/extensions.json ▶ called io::invoke git add $GLOBAL_VALET_HOME/docs/static/config.md ▶ called io::invoke git add $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/array.md $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/codes.md $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/core.md $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/fsfs.md $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/interactive.md $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/io.md $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/kurl.md $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/log.md $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/profiler.md $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/string.md $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/system.md $GLOBAL_VALET_HOME/docs/content/docs/300.libraries/test.md -▶ called io::invoke git add $GLOBAL_VALET_HOME/extras/base.code-snippets $GLOBAL_VALET_HOME/extras/lib-valet $GLOBAL_VALET_HOME/extras/lib-valet.md +▶ called io::invoke git add $GLOBAL_VALET_HOME/extras/base.code-snippets $GLOBAL_VALET_HOME/extras/lib-valet $GLOBAL_VALET_HOME/extras/lib-valet.md $GLOBAL_VALET_HOME/extras/valet.code-snippets ▶ called io::invoke git commit -m :memo: updating the documentation SUCCESS The documentation update has been committed. ▶ called io::invoke sed -E -i s/VALET_RELEASED_VERSION="[0-9]+\.[^"]+"/VALET_RELEASED_VERSION="1.2.3"/ $GLOBAL_VALET_HOME/valet.d/commands.d/self-install.sh diff --git a/tests.d/1106-self-install/test b/tests.d/1106-self-install/test index d650bde..471d190 100644 --- a/tests.d/1106-self-install/test +++ b/tests.d/1106-self-install/test @@ -116,6 +116,24 @@ function main() { + + echo "================================================" + echo "Create extension: test-extension1." + echo "================================================" + + valet self extend test-extension1 + + echo "================================================" + echo "Setup extension directory: test-extension2." + echo "================================================" + + mkdir -p ~/.valet.d/test-extension2 + pushd ~/.valet.d/test-extension2 &>/dev/null + valet self extend . + popd &>/dev/null + + + echo "================================================" echo "Updating Valet: --skip-extensions." echo "================================================" @@ -219,5 +237,4 @@ function main() { main -eval "$(valet self export -a)" -log::success "Self install test passed." +echo "Self install test passed." diff --git a/tests.d/1107-self-document/01.self-document.sh b/tests.d/1107-self-document/01.self-document.sh index 365e3c5..9df302c 100644 --- a/tests.d/1107-self-document/01.self-document.sh +++ b/tests.d/1107-self-document/01.self-document.sh @@ -18,8 +18,8 @@ function testSelfDocument() { echo "→ cat ${directory}/lib-valet" io::cat "${directory}/lib-valet" echo - echo "→ cat ${directory}/.vscode/valet.code-snippets" - io::cat "${directory}/.vscode/valet.code-snippets" + echo "→ cat ${directory}/valet.code-snippets" + io::cat "${directory}/valet.code-snippets" test::endTest "Testing selfDocument" 0 } diff --git a/tests.d/1107-self-document/results.approved.md b/tests.d/1107-self-document/results.approved.md index 825609e..483b7c2 100644 --- a/tests.d/1107-self-document/results.approved.md +++ b/tests.d/1107-self-document/results.approved.md @@ -1103,7 +1103,7 @@ io::createLink "/path/to/link" "/path/to/linked" true ``` > On unix, the function uses the `ln` command. -> On Windows, the function uses `powershell`. +> On Windows, the function uses `powershell` (and optionally ls to check the existing link). ## io::createTempDirectory @@ -3590,7 +3590,7 @@ function io::createFilePathIfNeeded() { :; } # ``` # # > On unix, the function uses the `ln` command. -# > On Windows, the function uses `powershell`. +# > On Windows, the function uses `powershell` (and optionally ls to check the existing link). # function io::createLink() { :; } @@ -4989,7 +4989,7 @@ function test::endTest() { :; } -→ cat /tmp/valet.d/d1-1/.vscode/valet.code-snippets +→ cat /tmp/valet.d/d1-1/valet.code-snippets { // Documentation generated for the version 1.2.3 (2013-11-10). @@ -5774,7 +5774,7 @@ function test::endTest() { :; } "prefix": "io::createLink#withdoc", "description": "Create a soft or hard link (original ← link)...", "scope": "", - "body": [ "# ## io::createLink\n# \n# Create a soft or hard link (original ← link).\n# \n# Reminder:\n# \n# - A soft (symbolic) link is a new file that contains a reference to another file or directory in the\n# form of an absolute or relative path.\n# - A hard link is a directory entry that associates a new pathname with an existing\n# file (inode + data block) on a file system.\n# \n# This function allows to create a symbolic link on Windows as well as on Unix.\n# \n# - \\$1: **linked path** _as string_:\n# the path to link to (the original file)\n# - \\$2: **link path** _as string_:\n# the path where to create the link\n# - \\$3: hard link _as boolean_:\n# (optional) true to create a hard link, false to create a symbolic link\n# (defaults to false)\n# - \\$4: force _as boolean_:\n# (optional) true to overwrite the link or file if it already exists.\n# Otherwise, the function will fail on an existing link.\n# (defaults to true)\n# \n# ```bash\n# io::createLink \"/path/to/link\" \"/path/to/linked\"\n# io::createLink \"/path/to/link\" \"/path/to/linked\" true\n# ```\n# \n# > On unix, the function uses the `ln` command.\n# > On Windows, the function uses `powershell`.\n# \nio::createLink \"${1:**linked path**}\" \"${2:**link path**}\" \"${3:hard link}\" \"${4:force}\"$0" ] + "body": [ "# ## io::createLink\n# \n# Create a soft or hard link (original ← link).\n# \n# Reminder:\n# \n# - A soft (symbolic) link is a new file that contains a reference to another file or directory in the\n# form of an absolute or relative path.\n# - A hard link is a directory entry that associates a new pathname with an existing\n# file (inode + data block) on a file system.\n# \n# This function allows to create a symbolic link on Windows as well as on Unix.\n# \n# - \\$1: **linked path** _as string_:\n# the path to link to (the original file)\n# - \\$2: **link path** _as string_:\n# the path where to create the link\n# - \\$3: hard link _as boolean_:\n# (optional) true to create a hard link, false to create a symbolic link\n# (defaults to false)\n# - \\$4: force _as boolean_:\n# (optional) true to overwrite the link or file if it already exists.\n# Otherwise, the function will fail on an existing link.\n# (defaults to true)\n# \n# ```bash\n# io::createLink \"/path/to/link\" \"/path/to/linked\"\n# io::createLink \"/path/to/link\" \"/path/to/linked\" true\n# ```\n# \n# > On unix, the function uses the `ln` command.\n# > On Windows, the function uses `powershell` (and optionally ls to check the existing link).\n# \nio::createLink \"${1:**linked path**}\" \"${2:**link path**}\" \"${3:hard link}\" \"${4:force}\"$0" ] }, "io::createTempDirectory": { @@ -6890,6 +6890,6 @@ INFO Generating documentation for the core functions only. INFO Found 118 functions with documentation. INFO The documentation has been generated in ⌜/tmp/valet.d/d1-1/lib-valet.md⌝. INFO The prototype script has been generated in ⌜/tmp/valet.d/d1-1/lib-valet⌝. -INFO The vscode snippets have been generated in ⌜/tmp/valet.d/d1-1/.vscode/valet.code-snippets⌝. +INFO The vscode snippets have been generated in ⌜/tmp/valet.d/d1-1/valet.code-snippets⌝. ``` diff --git a/tests.d/1300-valet-cli/results.approved.md b/tests.d/1300-valet-cli/results.approved.md index ef0efc4..3a2720d 100644 --- a/tests.d/1300-valet-cli/results.approved.md +++ b/tests.d/1300-valet-cli/results.approved.md @@ -519,7 +519,7 @@ Exit code: `0` self build Index all the commands and libraries present in the valet user directory and installation directory. self config Open the configuration file of Valet with your default editor. self document Generate the documentation and code snippets for all the library functions of Valet. -self extend Extends Valet by downloading a new application or library in the user directory. +self extend Extends Valet by creating or downloading a new extension in the user directory. self test Test your valet custom commands. self update Update valet and its extensions to the latest releases.⌉ ``` @@ -1086,7 +1086,7 @@ Exit code: `0` ⌈self build Index all the commands and libraries present in the valet user directory and installation directory. self config Open the configuration file of Valet with your default editor. self document Generate the documentation and code snippets for all the library functions of Valet. -self extend Extends Valet by downloading a new application or library in the user directory. +self extend Extends Valet by creating or downloading a new extension in the user directory. self test Test your valet custom commands. self update Update valet and its extensions to the latest releases.⌉ ``` @@ -1155,7 +1155,7 @@ COMMANDS self export Returns a string that can be evaluated to have Valet functions in bash. self extend - Extends Valet by downloading a new application or library in the user directory. + Extends Valet by creating or downloading a new extension in the user directory. self mock1 A command that only for testing valet core functions. self mock2 diff --git a/valet.d/commands.d/self-document.sh b/valet.d/commands.d/self-document.sh index d563893..91b91ad 100644 --- a/valet.d/commands.d/self-document.sh +++ b/valet.d/commands.d/self-document.sh @@ -77,8 +77,8 @@ function selfDocument() { log::info "The prototype script has been generated in ⌜${output}/lib-valet⌝." # write each function to the snippet file - selfRelease_writeAllFunctionsToCodeSnippets "${pageFooter}" "${output}/.vscode/valet.code-snippets" - log::info "The vscode snippets have been generated in ⌜${output}/.vscode/valet.code-snippets⌝." + selfRelease_writeAllFunctionsToCodeSnippets "${pageFooter}" "${output}/valet.code-snippets" + log::info "The vscode snippets have been generated in ⌜${output}/valet.code-snippets⌝." } # Returns the footer for the documentation. diff --git a/valet.d/commands.d/self-extend.sh b/valet.d/commands.d/self-extend.sh index 6d62e2c..ae200c8 100644 --- a/valet.d/commands.d/self-extend.sh +++ b/valet.d/commands.d/self-extend.sh @@ -30,31 +30,40 @@ source curl # command: self extend # function: selfExtend # author: github.com/jcaillon -# shortDescription: Extends Valet by downloading a new application or library in the user directory. +# shortDescription: Extends Valet by creating or downloading a new extension in the user directory. # description: |- -# Extends Valet by downloading a new application or library in the user directory. +# Extends Valet by creating or downloading a new extension in the user directory. +# Extensions can add new commands or functions to Valet. # -# - Applications usually add new commands to Valet. -# - Libraries usually add new callable functions to Valet. +# This command will either: # -# This command will download the given repository and install it in the Valet user directory. -# If a `extension.setup.sh` script is present in the repository root directory, it will be executed. +# - Create and setup a new extension directory under the valet user directory, +# - setup an existing directory as a valet extension, +# - or download the given extension (repository) and install it in the Valet user directory. # -# For GitHub and GitLab repositories, this command will: +# For downloaded extensions, all GIT repositories are supported. +# For the specific cases of GitHub and GitLab repositories, this command will: # # 1. If git is installed, clone the repository for the given reference (version option). -# 2. Otherwise, download source tarball for the given reference and extract it. +# 2. If git is not installed, download source tarball for the given reference and extract it. +# +# For downloaded extensions, if a `extension.setup.sh` script is present in the repository root directory, +# it will be executed. This gives the extension the opportunity to setup itself. # # Once an extension is installed, you can use the `valet self update` command to update it. # arguments: -# - name: repositoryUri +# - name: extensionUri # description: |- -# The URL of the repository to download and install in Valet. +# The URI of the extension to install or create. # -# Usually a GitHub or GitLab repository URL such as `https://github.com/jcaillon/valet-devops-toolbox.git`. +# 1. If you want to create a new extension, this argument should be the name of your +# new extension (e.g. `my-new-extension`). +# 2. If you want to setup an existing directory as an extension, this argument should be `.`. +# 3. If you want to download an extension, this argument should be the URL of the repository. +# Usually a GitHub or GitLab repository URL such as `https://github.com/jcaillon/valet-devops-toolbox.git`. # -# If the repository is private, you can pass the URL with the username and password like this: -# `https://username:password@my.gitlab.private/group/project.git`. +# > If the repository is private, you can pass the URL with the username and password like this: +# > `https://username:password@my.gitlab.private/group/project.git`. # options: # - name: -v, --version # description: |- @@ -74,12 +83,12 @@ source curl # Download the latest version of the valet-devops-toolbox application and install it for Valet. ##VALET_COMMAND function selfExtend() { - local repositoryUri version skipSetup name + local extensionUri version skipSetup name core::parseArguments "$@" && eval "${RETURNED_VALUE}" core::checkParseResults "${help:-}" "${parsingErrors:-}" local action="created" - if [[ ${repositoryUri} =~ ^(https|git) ]]; then + if [[ ${extensionUri} =~ ^(https|git) ]]; then action="installed" fi @@ -88,12 +97,18 @@ function selfExtend() { local userDirectory="${RETURNED_VALUE}" io::createDirectoryIfNeeded "${userDirectory}" + # case of extension creation + if [[ ${action} == "created" ]]; then + selfExtend_createExtension "${extensionUri}" "${userDirectory}" + return 0 + fi + local repositoryName - if [[ ${repositoryUri} =~ .*/([^/]+) ]]; then + if [[ ${extensionUri} =~ .*/([^/]+) ]]; then repositoryName="${BASH_REMATCH[1]:-}" repositoryName="${repositoryName%.git}" fi - local extensionName="${name:-${repositoryName:-${repositoryUri}}}" + local extensionName="${name:-${repositoryName:-${extensionUri}}}" local extensionDirectory="${userDirectory}/${extensionName}" log::info "The extension will be ${action} under ⌜${extensionDirectory}⌝." @@ -106,15 +121,9 @@ function selfExtend() { fi fi - # case of extension creation - if [[ ${action} == "created" ]]; then - selfExtend_createExtension "${extensionName}" "${extensionDirectory}" - return 0 - fi - # if Git is installed, we simply clone the repository for the correct reference if command -v git &>/dev/null; then - selfExtend_gitClone "${repositoryUri}" "${version}" "${extensionDirectory}" + selfExtend_gitClone "${extensionUri}" "${version}" "${extensionDirectory}" else # if Git is not installed, we download the source tarball and extract it @@ -126,8 +135,8 @@ function selfExtend() { fi # get the sha1 of the reference, fail if not found - selfExtend_getSha1 "${repositoryUri}" "${version}" - selfExtend_downloadTarball "${repositoryUri}" "${version}" "${extensionDirectory}" "${RETURNED_VALUE}" + selfExtend_getSha1 "${extensionUri}" "${version}" + selfExtend_downloadTarball "${extensionUri}" "${version}" "${extensionDirectory}" "${RETURNED_VALUE}" fi # execute the setup script of the extension, if any @@ -151,13 +160,67 @@ function selfExtend() { # Create a new extension for Valet. function selfExtend_createExtension() { local extensionName="${1}" - local extensionDirectory="${2}" + local userDirectory="${2}" - log::info "Creating the extension ⌜${extensionName}⌝ in ⌜${extensionDirectory}⌝." + local extensionDirectory - io::createDirectoryIfNeeded "${extensionDirectory}" + if [[ ${extensionName} == "." ]]; then + # setup an existing directory as an extension + extensionName="${PWD##*/}" + log::info "Setting up the current directory ⌜${extensionName}⌝ as an extension." - log::success "The extension ⌜${extensionName}⌝ has been created in ⌜${extensionDirectory}⌝." + if [[ ${PWD} != "${userDirectory}"* ]]; then + core::fail "Extension directories must be created in the user directory ⌜${userDirectory}⌝, the current directory is ⌜${PWD}⌝." + fi + extensionDirectory="${PWD}" + else + # create a new extension directory + extensionDirectory="${userDirectory}/${extensionName}" + log::info "Creating the extension ⌜${extensionName}⌝ in ⌜${extensionDirectory}⌝." + + if [[ -d "${extensionDirectory}" ]]; then + core::fail "The extension ⌜${extensionName}⌝ already exists in ⌜${extensionDirectory}⌝." + fi + + local -a subDirectories=(src libs.d tests.d) + local subdir + for subdir in "${subDirectories[@]}"; do + io::createDirectoryIfNeeded "${extensionDirectory}/${subdir}" + done + fi + + # verify that we have lib-valet generated in the user directory + if [[ ! -f "${userDirectory}/lib-valet" ]]; then + log::info "Rebuilding the documentation because ⌜${userDirectory}/lib-valet⌝ is missing." + core::sourceFunction selfDocument + selfDocument + fi + + # vscode stuff + if command -v code &>/dev/null; then + io::createDirectoryIfNeeded "${extensionDirectory}/.vscode" + cp -n "${GLOBAL_VALET_HOME}/extras/.vscode/settings.json" "${extensionDirectory}/.vscode/settings.json" || log::error "Could not copy the vscode settings file." + cp -n "${GLOBAL_VALET_HOME}/extras/.vscode/extensions.json" "${extensionDirectory}/.vscode/extensions.json" || log::error "Could not copy the vscode extensions file." + + # link the snippets + io::createLink "${userDirectory}/valet.code-snippets" "${extensionDirectory}/.vscode/valet.code-snippets" + fi + + # git stuff + if command -v git &>/dev/null; then + io::createFilePathIfNeeded "${extensionDirectory}/.gitignore" + io::readFile "${extensionDirectory}/.gitignore" + if [[ ${RETURNED_VALUE} != *"### Valet ###"* ]]; then + local content=$'\n'$'\n'"### Valet ###"$'\n'"lib-valet"$'\n'"lib-valet.md"$'\n'".vscode/valet.code-snippets" + io::writeToFile "${extensionDirectory}/.gitignore" "${content}" true + fi + fi + + # link lib-valet + io::createLink "${userDirectory}/lib-valet" "${extensionDirectory}/lib-valet" + io::createLink "${userDirectory}/lib-valet.md" "${extensionDirectory}/lib-valet.md" + + log::success "The extension ⌜${extensionName}⌝ has been setup in ⌜${extensionDirectory}⌝." } # Download the source tarball for a given repository and reference. @@ -169,24 +232,24 @@ function selfExtend_createExtension() { # It stores the sha1 of the downloaded commit in the extension directory. # It allows to know when we actually have updates. function selfExtend_downloadTarball() { - local repositoryUri="${1}" + local repositoryUrl="${1}" local reference="${2}" local targetDirectory="${3}" local sha1="${4}" - log::info "Attempting to download the source tarball for ⌜${repositoryUri}⌝ in ⌜${targetDirectory}⌝." + log::info "Attempting to download the source tarball for ⌜${repositoryUrl}⌝ in ⌜${targetDirectory}⌝." local tarballUrlPattern # get tarball for github api - if [[ ${repositoryUri} =~ "https://github.com/"([^/]+)"/"([^/]+) ]]; then + if [[ ${repositoryUrl} =~ "https://github.com/"([^/]+)"/"([^/]+) ]]; then local repoName="${BASH_REMATCH[2]:-}" repoName="${repoName%.git}" tarballUrlPattern="https://github.com/${BASH_REMATCH[1]:-}/${repoName}/archive/%SHA1%.tar.gz" fi if [[ -z ${tarballUrlPattern} || -z ${sha1} ]]; then - core::fail "Cannot download the source tarball for the repository ⌜${repositoryUri}⌝. Consider installing git so that the extension can be git cloned." + core::fail "Cannot download the source tarball for the repository ⌜${repositoryUrl}⌝. Consider installing git so that the extension can be git cloned." fi local tarballUrl="${tarballUrlPattern/"%SHA1%"/"${sha1}"}" @@ -204,8 +267,8 @@ function selfExtend_downloadTarball() { tar -xzf "${tempDirectory}/${sha1}.tar.gz" -C "${tempDirectory}" || core::fail "Could not untar the extension tarball ⌜${tempDirectory}/${sha1}.tar.gz⌝ using tar." # move the files to the target directory - rm -Rf "${targetDirectory}" - mkdir -p "${targetDirectory}" || core::fail "Could not create the directory ⌜${targetDirectory}⌝." + rm -Rf "${targetDirectory}" 1>/dev/null || core::fail "Could not remove the existing files in ⌜${targetDirectory}⌝." + io::createDirectoryIfNeeded "${targetDirectory}" io::listDirectories "${tempDirectory}" false if (( ${#RETURNED_ARRAY[@]} != 1 )); then core::fail "The tarball ⌜${tempDirectory}/${sha1}.tar.gz⌝ did not contain a single directory." @@ -215,7 +278,7 @@ function selfExtend_downloadTarball() { # write the sha1 to the targetDirectory so we known which commit we fetched io::writeToFile "${targetDirectory}/.sha1" "${sha1}" io::writeToFile "${targetDirectory}/.reference" "${reference}" - io::writeToFile "${targetDirectory}/.repo" "${repositoryUri}" + io::writeToFile "${targetDirectory}/.repo" "${repositoryUrl}" } # Get the sha1 from a git server API. @@ -224,19 +287,19 @@ function selfExtend_downloadTarball() { # # - GitHub: It will use the GitHub API to get the sha1 of the reference. function selfExtend_getSha1() { - local repositoryUri="${1}" + local repositoryUrl="${1}" local reference="${2}" - log::info "Getting the head commit for the reference ⌜${reference}⌝ in the repository ⌜${repositoryUri}⌝." + log::info "Getting the head commit for the reference ⌜${reference}⌝ in the repository ⌜${repositoryUrl}⌝." local sha1 # get sha1 from github api - if [[ ${repositoryUri} =~ "https://github.com/"([^/]+)"/"([^/]+) ]]; then + if [[ ${repositoryUrl} =~ "https://github.com/"([^/]+)"/"([^/]+) ]]; then local owner="${BASH_REMATCH[1]:-}" local repo="${BASH_REMATCH[2]:-}" repo="${repo%.git}" - log::debug "Found owner ⌜${owner}⌝ and repo ⌜${repo}⌝ for the repository ⌜${repositoryUri}⌝." + log::debug "Found owner ⌜${owner}⌝ and repo ⌜${repo}⌝ for the repository ⌜${repositoryUrl}⌝." # get the sha1 RETURNED_VALUE="" @@ -252,7 +315,7 @@ function selfExtend_getSha1() { interactive::stopProgress if [[ ${httpCode} == 404 ]]; then - core::fail "Could not find a branch or tag for the reference ⌜${reference}⌝ in repository ⌜${repositoryUri}⌝: ${response}${error}." + core::fail "Could not find a branch or tag for the reference ⌜${reference}⌝ in repository ⌜${repositoryUrl}⌝: ${response}${error}." elif [[ ${httpCode} != 200 ]]; then log::warning "Unexpected error returned from the GitHub API for the URL ⌜${url}⌝: ${response}${error}" fi @@ -260,12 +323,12 @@ function selfExtend_getSha1() { sha1="${BASH_REMATCH[2]:-}" else # sha1="ok" - core::fail "Could not find the sha1 for the reference ⌜${reference}⌝ in the repository ⌜${repositoryUri}⌝ using GitHub API. Check the version for this extension. Consider installing git so that the extension can be git cloned." + core::fail "Could not find the sha1 for the reference ⌜${reference}⌝ in the repository ⌜${repositoryUrl}⌝ using GitHub API. Check the version for this extension. Consider installing git so that the extension can be git cloned." fi fi if [[ -n ${sha1} ]]; then - log::debug "Found sha1 for ⌜${repositoryUri}⌝ and reference ⌜${reference}⌝: ${sha1}." + log::debug "Found sha1 for ⌜${repositoryUrl}⌝ and reference ⌜${reference}⌝: ${sha1}." fi RETURNED_VALUE="${sha1}" diff --git a/valet.d/lib-io b/valet.d/lib-io index a06f382..6e8055a 100644 --- a/valet.d/lib-io +++ b/valet.d/lib-io @@ -962,7 +962,7 @@ function io::runPs1Command() { # ``` # # > On unix, the function uses the `ln` command. -# > On Windows, the function uses `powershell`. +# > On Windows, the function uses `powershell` (and optionally ls to check the existing link). function io::createLink() { local linkedPath="${1}" local linkPath="${2}" @@ -981,6 +981,15 @@ function io::createLink() { # check the link path if [[ -f ${linkPath} || -L ${linkPath} ]]; then + # on windows, creating a link is costly so don't do it if it's already good + if [[ "${OSTYPE:-}" == "msys"* ]] && command -v ls &>/dev/null; then + io::invoke ls -l "${linkPath}" + if [[ ${RETURNED_VALUE%%$'\n'*} == *"${linkedPath}" ]]; then + log::debug "The link ⌜${linkPath}⌝ already exists and points to ⌜${linkedPath}⌝." + return 0 + fi + fi + # if force, delete the link (or file) if it exists if [[ ${force} == true ]]; then rm -f "${linkPath}" || core::fail "Failed to delete the existing link ⌜${linkPath}⌝ to replace it." @@ -1003,7 +1012,7 @@ function io::createLink() { io::convertToWindowsPath "${linkedPath}" linkedPath="${RETURNED_VALUE}" - log::debug "Using powershell to link ⌜${linkedPath}⌝ ← ⌜${linkPath}⌝." + log::info "Creating a new link ⌜${linkedPath}⌝ ← ⌜${linkPath}⌝ using powershell." if [[ ${hardLink} == "true" ]]; then io::runPs1Command "New-Item -ItemType HardLink -Path \"${linkParentPath}\" -Name \"${linkFileName}\" -Value \"${linkedPath}\" | Out-Null" true || core::fail "Failed to create the hard link ⌜${linkedPath}⌝ ← ⌜${linkPath}⌝." else diff --git a/valet.d/version b/valet.d/version index c4a1a41..7c81dee 100644 --- a/valet.d/version +++ b/valet.d/version @@ -1 +1 @@ -0.27.173 \ No newline at end of file +0.27.177 \ No newline at end of file