diff --git a/lib/index.js b/lib/index.js index 3453276..8cabbe0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,7 @@ const { SUPPORTED_SHELLS, SHELL_LOCATIONS } = require('./constants'); const prompt = require('./prompt'); const installer = require('./installer'); -const { tabtabDebug, systemShell } = require('./utils'); +const { tabtabDebug } = require('./utils'); /** * @typedef {import('./constants').SupportedShell} SupportedShell @@ -18,6 +18,33 @@ const debug = tabtabDebug('tabtab'); */ const isShellSupported = shell => (/** @type {ReadonlyArray.} */ (SUPPORTED_SHELLS)).includes(shell); +/** + * This function is to be used inside a completer. + * + * An environment variable named `SHELL` shall be explicitly set + * by the completion script when it invokes the completer. + * + * The value of `SHELL` is expected to be one of the supported shells. + * If this expectation isn't met, it will result in an error. + * + * @example + * const shell = getShellFromEnv(process.env) + * + * @param {Readonly.>} env - Env objects that may contain `SHELL`, usually `process.env`. + * @returns {SupportedShell} + */ +const getShellFromEnv = env => { + const shell = env.SHELL; + if (!shell) { + throw new TypeError('SHELL cannot be empty'); + } + if (!isShellSupported(shell)) { + const supportedValues = SUPPORTED_SHELLS.map(x => `'${x}'`).join(', '); + throw new TypeError(`SHELL was set to an invalid value (${shell}). Supported values are: ${supportedValues}`); + } + return shell; +} + /** * Construct a completion script. * @param {Object} options - Options object. @@ -253,7 +280,7 @@ const logFiles = () => { module.exports = { SUPPORTED_SHELLS, - shell: systemShell, + getShellFromEnv, isShellSupported, getCompletionScript, install, diff --git a/lib/installer.js b/lib/installer.js index 3a238af..1c9ae5e 100644 --- a/lib/installer.js +++ b/lib/installer.js @@ -2,7 +2,7 @@ const fs = require('fs'); const path = require('path'); const untildify = require('untildify'); const { promisify } = require('util'); -const { tabtabDebug, systemShell, exists } = require('./utils'); +const { tabtabDebug, exists } = require('./utils'); const { SUPPORTED_SHELLS } = require('./constants') const debug = tabtabDebug('tabtab:installer'); diff --git a/lib/templates/completion.bash b/lib/templates/completion.bash index 090a953..8cf6fb9 100644 --- a/lib/templates/completion.bash +++ b/lib/templates/completion.bash @@ -13,6 +13,7 @@ if type complete &>/dev/null; then IFS=$'\n' COMPREPLY=($(COMP_CWORD="$cword" \ COMP_LINE="$COMP_LINE" \ COMP_POINT="$COMP_POINT" \ + SHELL=bash \ {completer} completion-server -- "${words[@]}" \ 2>/dev/null)) || return $? IFS="$si" diff --git a/lib/templates/completion.fish b/lib/templates/completion.fish index 4fa81e0..71ca357 100644 --- a/lib/templates/completion.fish +++ b/lib/templates/completion.fish @@ -4,7 +4,7 @@ function _{pkgname}_completion set cursor (commandline -C) set words (count $cmd) - set completions (eval env DEBUG=\"" \"" COMP_CWORD=\""$words\"" COMP_LINE=\""$cmd \"" COMP_POINT=\""$cursor\"" {completer} completion-server -- $cmd) + set completions (eval env DEBUG=\"" \"" COMP_CWORD=\""$words\"" COMP_LINE=\""$cmd \"" COMP_POINT=\""$cursor\"" SHELL=fish {completer} completion-server -- $cmd) if [ "$completions" = "__tabtab_complete_files__" ] set -l matches (commandline -ct)* diff --git a/lib/templates/completion.zsh b/lib/templates/completion.zsh index a5744b4..5ca0f89 100644 --- a/lib/templates/completion.zsh +++ b/lib/templates/completion.zsh @@ -4,7 +4,7 @@ if type compdef &>/dev/null; then local reply local si=$IFS - IFS=$'\n' reply=($(COMP_CWORD="$((CURRENT-1))" COMP_LINE="$BUFFER" COMP_POINT="$CURSOR" {completer} completion-server -- "${words[@]}")) + IFS=$'\n' reply=($(COMP_CWORD="$((CURRENT-1))" COMP_LINE="$BUFFER" COMP_POINT="$CURSOR" SHELL=zsh {completer} completion-server -- "${words[@]}")) IFS=$si if [ "$reply" = "__tabtab_complete_files__" ]; then diff --git a/lib/utils/index.js b/lib/utils/index.js index 244849d..4d3da4c 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -1,9 +1,7 @@ const tabtabDebug = require('./tabtabDebug'); -const systemShell = require('./systemShell'); const exists = require('./exists'); module.exports = { - systemShell, tabtabDebug, exists }; diff --git a/lib/utils/systemShell.js b/lib/utils/systemShell.js deleted file mode 100644 index 3978a91..0000000 --- a/lib/utils/systemShell.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Utility to figure out the shell used on the system. - * - * Sadly, we can't use `echo $0` in node, maybe with more work. So we rely on - * process.env.SHELL. - * - * TODO: More work on this, namely to detect Git bash on Windows (bash.exe) - */ -const systemShell = () => (process.env.SHELL || '').split('/').slice(-1)[0]; - -module.exports = systemShell; diff --git a/test/basic.js b/test/basic.js deleted file mode 100644 index f3f373c..0000000 --- a/test/basic.js +++ /dev/null @@ -1,21 +0,0 @@ -const assert = require('assert'); -const tabtab = require('..'); - -describe('tabtab', () => { - it('tabtab.shell()', () => { - const previousShell = process.env.SHELL; - process.env.SHELL = '/bin/bash'; - let shell = tabtab.shell(); - assert.equal(shell, 'bash'); - - process.env.SHELL = '/usr/bin/zsh'; - shell = tabtab.shell(); - assert.equal(shell, 'zsh'); - - process.env.SHELL = '/usr/bin/fish'; - shell = tabtab.shell(); - assert.equal(shell, 'fish'); - - process.env.SHELL = previousShell; - }); -}); diff --git a/test/getShellFromEnv.js b/test/getShellFromEnv.js new file mode 100644 index 0000000..876c2b3 --- /dev/null +++ b/test/getShellFromEnv.js @@ -0,0 +1,29 @@ +const assert = require('assert'); +const { SUPPORTED_SHELLS, getShellFromEnv } = require('..'); + +describe('getShellFromEnv', () => { + it('errors when env lacks SHELL', () => { + assert.throws( + () => getShellFromEnv({}), + { + message: 'SHELL cannot be empty', + }, + ) + }); + + it('errors on unsupported shells', () => { + assert.throws( + () => getShellFromEnv({ SHELL: 'unknown' }), + { + message: "SHELL was set to an invalid value (unknown). Supported values are: 'bash', 'fish', 'pwsh', 'zsh'", + }, + ); + }) + + it('returns supported shells', () => { + assert.deepStrictEqual( + SUPPORTED_SHELLS.map(SHELL => getShellFromEnv({ SHELL })), + SUPPORTED_SHELLS, + ); + }); +});