Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: inject console.log as parameter #26

Merged
merged 7 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions examples/tabtab-test-complete/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,23 @@ const tabtab = require('../..');
const args = opts._;

const completion = env => {
const shell = tabtab.getShellFromEnv(env);

if (!env.complete) return;

if (env.prev === 'someCommand') {
return tabtab.log(['is', 'this', 'the', 'real', 'life']);
return tabtab.logCompletion(['is', 'this', 'the', 'real', 'life'], shell, console.log);
}

if (env.prev === 'anotherOne') {
return tabtab.log(['is', 'this', 'just', 'fantasy']);
return tabtab.logCompletion(['is', 'this', 'just', 'fantasy'], shell, console.log);
}

if (env.prev === '--loglevel') {
return tabtab.log(['error', 'warn', 'info', 'notice', 'verbose']);
return tabtab.logCompletion(['error', 'warn', 'info', 'notice', 'verbose'], shell, console.log);
}

return tabtab.log([
return tabtab.logCompletion([
'--help',
'--version',
'--loglevel',
Expand All @@ -40,7 +42,7 @@ const completion = env => {
description: 'You must add a description for items with ":" in them'
},
'anotherOne'
]);
], shell, console.log);
};

const init = async () => {
Expand Down Expand Up @@ -90,13 +92,19 @@ const init = async () => {
}

if (cmd === 'install-completion') {
const shell = args[1];
if (!tabtab.isShellSupported(shell)) {
throw new Error(`${shell} is not supported`);
}

// Here we install for the program `tabtab-test` (this file), with
// completer being the same program. Sometimes, you want to complete
// another program that's where the `completer` option might come handy.
await tabtab
.install({
name: 'tabtab-test',
completer: 'tabtab-test'
completer: 'tabtab-test',
shell,
})
.catch(err => console.error('INSTALL ERROR', err));

Expand Down
13 changes: 7 additions & 6 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,12 @@ const completionItem = (item, shell) => {
* Bash needs in addition to filter out the args for the completion to work
* (zsh, fish don't need this).
*
* @param {Array.<CompletionItem | String>} args to log, Strings or Objects with name and
* @param {Array.<CompletionItem | String>} args - to log, Strings or Objects with name and
* description property.
* @param {SupportedShell} shell
* @param {(message: String) => void} log - Function to actually log to the console, usually `console.log`
*/
const log = (args, shell) => {
const logCompletion = (args, shell, log) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think a breaking change is needed in this case. The function can remain log. The new argument can be logCompletion and it can be optional. If not set, console.log is used.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!Array.isArray(args)) {
throw new Error('log: Invalid arguments, must be an array');
}
Expand All @@ -243,9 +244,9 @@ const log = (args, shell) => {
let lines = args.map(item => completionItem(item, shell)).map(item => {
const { name: rawName, description: rawDescription } = item;

const name = shell === 'zsh' ? rawName?.replace(/:/g, '\\:') : rawName;
const name = shell === 'zsh' ? rawName?.replaceAll(':', '\\:') : rawName;
const description =
shell === 'zsh' ? rawDescription?.replace(/:/g, '\\:') : rawDescription;
shell === 'zsh' ? rawDescription?.replaceAll(':', '\\:') : rawDescription;
let str = name;

if (shell === 'zsh' && description) {
Expand All @@ -263,7 +264,7 @@ const log = (args, shell) => {
}

for (const line of lines) {
console.log(`${line}`);
log(`${line}`);
}
};

Expand All @@ -286,6 +287,6 @@ module.exports = {
install,
uninstall,
parseEnv,
log,
logCompletion,
logFiles
};
6 changes: 3 additions & 3 deletions lib/installer.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,10 @@ const getCompletionScript = async ({ name, completer, shell }) => {
const templatePath = scriptFromShell(shell);
const templateContent = await readFile(templatePath, 'utf8');
const scriptContent = templateContent
.replace(/\{pkgname\}/g, name)
.replace(/{completer}/g, completer)
.replaceAll('{pkgname}', name)
.replaceAll('{completer}', completer)
// on Bash on windows, we need to make sure to remove any \r
.replace(/\r?\n/g, '\n');
.replaceAll(/\r?\n/g, '\n');
return scriptContent
};

Expand Down
34 changes: 21 additions & 13 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,25 @@ const opts = require('minimist')(process.argv.slice(2), {

const args = opts._;
const completion = env => {
const shell = tabtab.getShellFromEnv(env);

if (!env.complete) return;

// Write your completions there

if (env.prev === 'foo') {
return tabtab.log(['is', 'this', 'the', 'real', 'life']);
return tabtab.logCompletion(['is', 'this', 'the', 'real', 'life'], shell, console.log);
}

if (env.prev === 'bar') {
return tabtab.log(['is', 'this', 'just', 'fantasy']);
return tabtab.logCompletion(['is', 'this', 'just', 'fantasy'], shell, console.log);
}

if (env.prev === '--loglevel') {
return tabtab.log(['error', 'warn', 'info', 'notice', 'verbose']);
return tabtab.logCompletion(['error', 'warn', 'info', 'notice', 'verbose'], shell, console.log);
}

return tabtab.log([
return tabtab.logCompletion([
'--help',
'--version',
'--loglevel',
Expand All @@ -91,7 +93,7 @@ const completion = env => {
description: 'You must add a description for items with ":" in them'
},
'anotherOne'
]);
], shell, console.log);
};

const run = async () => {
Expand All @@ -103,10 +105,16 @@ const run = async () => {
// completer being the same program. Sometimes, you want to complete
// another program that's where the `completer` option might come handy.
if (cmd === 'install-completion') {
const shell = args[1];
if (!tabtab.isShellSupported(shell)) {
throw new Error(`${shell} is not supported`);
}

await tabtab
.install({
name: 'tabtab-test',
completer: 'tabtab-test'
completer: 'tabtab-test',
shell,
})
.catch(err => console.error('INSTALL ERROR', err));

Expand Down Expand Up @@ -189,10 +197,10 @@ Only one line will be added, even if it is called multiple times.
Once the completion is enabled and active, you can write completions for the
program (here, in this example `tabtab-test`). Briefly, adding completions is
as simple as logging output to `stdout`, with a few particularities (namely on
Bash, and for descriptions), but this is taken care of by `tabtab.log()`.
Bash, and for descriptions), but this is taken care of by `tabtab.logCompletion()`.

```js
tabtab.log([
tabtab.logCompletion([
'--help',
'--version',
'command'
Expand All @@ -206,7 +214,7 @@ to add descriptions for each completion item, for the shells that support them
(like Zsh or Fish). Or use the simpler `name:description` form.

```js
tabtab.log([
tabtab.logCompletion([
{ name: 'command', description: 'Description for command' },
'command-two:Description for command-two'
]);
Expand All @@ -215,7 +223,7 @@ tabtab.log([
The `{ name, description }` approach is preferable in case you have completion
items with `:` in them.

Note that you can call `tabtab.log()` multiple times if you prefer to do so, it
Note that you can call `tabtab.logCompletion()` multiple times if you prefer to do so, it
simply logs to the console in sequence.

#### Filesystem completion
Expand Down Expand Up @@ -262,7 +270,7 @@ Usually, you'll want to check against `env.last` or `env.prev`.

```js
if (env.prev === '--loglevel') {
tabtab.log(['error', 'warn', 'info', 'notice', 'verbose']);
tabtab.logCompletion(['error', 'warn', 'info', 'notice', 'verbose']);
}
```

Expand Down Expand Up @@ -376,8 +384,8 @@ When testing a completion, it can be useful to see those logs, but writing to
You can use the `TABTAB_DEBUG` environment variable to specify a file to log to
instead.

export TABTAB_DEBUG="/tmp/tabtab.log"
tail -f /tmp/tabtab.log
export TABTAB_DEBUG="/tmp/tabtab.logCompletion"
tail -f /tmp/tabtab.logCompletion

# in another shell
tabtab-test <tab>
Expand Down
6 changes: 3 additions & 3 deletions test/getCompletionScript.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ describe('getCompletionScript gets the right completion script for', () => {
shell
});
const expected = fs.readFileSync(require.resolve(`../lib/templates/completion.${COMPLETION_FILE_EXT[shell]}`), 'utf8')
.replace(/\{pkgname\}/g, 'foo')
.replace(/{completer}/g, 'foo-complete')
.replace(/\r?\n/g, '\n');
.replaceAll('{pkgname}', 'foo')
.replaceAll('{completer}', 'foo-complete')
.replaceAll(/\r?\n/g, '\n');
assert.equal(received, expected);
});
}
Expand Down
32 changes: 16 additions & 16 deletions test/log.js → test/logCompletion.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
const assert = require('assert');
const tabtab = require('..');

describe('tabtab.log', () => {
it('tabtab.log throws an Error in case args is not an Array', () => {
describe('tabtab.logCompletion', () => {
it('tabtab.logCompletion throws an Error in case args is not an Array', () => {
assert.throws(() => {
// @ts-ignore
tabtab.log('foo', 'bar');
tabtab.logCompletion('foo', 'bar');
}, /^Error: log: Invalid arguments, must be an array$/);
});

const logTestHelper = (items, shell) => {
const logs = [];
const { log } = console;
console.log = data => logs.push(data);
tabtab.log(items, shell);
console.log = log;
const log = message => {
logs.push(message);
}
tabtab.logCompletion(items, shell, log);
return logs;
};

it('tabtab.log logs item to the console', () => {
assert.equal(typeof tabtab.log, 'function');
it('tabtab.logCompletion logs item to the console', () => {
assert.equal(typeof tabtab.logCompletion, 'function');

const logs = logTestHelper(['--foo', '--bar'], 'bash');

assert.equal(logs.length, 2);
assert.deepStrictEqual(logs, ['--foo', '--bar']);
});

it('tabtab.log accepts { name, description }', () => {
it('tabtab.logCompletion accepts { name, description }', () => {
const logs = logTestHelper([
{ name: '--foo', description: 'Foo options' },
{ name: '--bar', description: 'Bar options' }
Expand All @@ -39,7 +39,7 @@ describe('tabtab.log', () => {
]);
});

it('tabtab.log normalize String and Objects', () => {
it('tabtab.logCompletion normalize String and Objects', () => {
const logs = logTestHelper([
{ name: '--foo', description: 'Foo options' },
{ name: '--bar', description: 'Bar options' },
Expand All @@ -53,7 +53,7 @@ describe('tabtab.log', () => {
]);
});

it('tabtab.log normalize String and Objects, with description stripped out on Bash', () => {
it('tabtab.logCompletion normalize String and Objects, with description stripped out on Bash', () => {
const logs = logTestHelper([
{ name: '--foo', description: 'Foo options' },
{ name: '--bar', description: 'Bar option' },
Expand All @@ -65,7 +65,7 @@ describe('tabtab.log', () => {
assert.deepStrictEqual(logs, ['--foo', '--bar', 'foobar', 'barfoo']);
});

it('tabtab.log with description NOT stripped out on Zsh', () => {
it('tabtab.logCompletion with description NOT stripped out on Zsh', () => {
const logs = logTestHelper([
{ name: '--foo', description: 'Foo option' },
{ name: '--bar', description: 'Bar option' },
Expand All @@ -82,7 +82,7 @@ describe('tabtab.log', () => {
]);
});

it('tabtab.log with description NOT stripped out on fish', () => {
it('tabtab.logCompletion with description NOT stripped out on fish', () => {
const logs = logTestHelper([
{ name: '--foo', description: 'Foo option' },
{ name: '--bar', description: 'Bar option' },
Expand All @@ -99,7 +99,7 @@ describe('tabtab.log', () => {
]);
});

it('tabtab.log could use {name, description} for completions with ":" in them', () => {
it('tabtab.logCompletion could use {name, description} for completions with ":" in them', () => {
const logs = logTestHelper([
{ name: '--foo:bar', description: 'Foo option' },
{ name: '--bar:foo', description: 'Bar option' },
Expand All @@ -116,7 +116,7 @@ describe('tabtab.log', () => {
]);
});

it('tabtab.log should escape ":" when name is given as an object without description', () => {
it('tabtab.logCompletion should escape ":" when name is given as an object without description', () => {
const logs = logTestHelper([
'foo:bar',
{ name: 'foo:bar' },
Expand Down
Loading