Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
KSXGitHub committed Feb 5, 2024
1 parent 80dd7a2 commit 357d521
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 100 deletions.
12 changes: 8 additions & 4 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const getCompletionScript = async ({ name, completer, shell }) => {
* @param {String} options.name - Name of the program whose completion needs to be installed.
* @param {String} options.completer - Name of the program that provides completion service.
* @param {SupportedShell} [options.shell] - Name of the target shell. If not specified, it'll prompt the user.
* @param {() => String} options.getHomeDir - Function that returns the home directory, usually `os.homedir`
*/
const install = async (options) => {
const { name, completer } = options;
Expand All @@ -83,7 +84,8 @@ const install = async (options) => {
name,
completer,
location,
shell: options.shell
shell: options.shell,
getHomeDir: options.getHomeDir,
});
return;
}
Expand All @@ -94,7 +96,8 @@ const install = async (options) => {
name,
completer,
location,
shell
shell,
getHomeDir: options.getHomeDir,
});
};

Expand All @@ -107,13 +110,14 @@ const install = async (options) => {
* @param {Object} options
* @param {String} options.name - Name of the target program.
* @param {SupportedShell} [options.shell] - The target shell language. If not specified, target all supported shells.
* @param {() => String} options.getHomeDir - Function that returns the home directory, usually `os.homedir`
*/
const uninstall = async options => {
const { name, shell } = options;
const { name, shell, getHomeDir } = options;
if (!name) throw new TypeError('options.name is required');

try {
await installer.uninstall({ name, shell });
await installer.uninstall({ name, shell, getHomeDir });
} catch (err) {
console.error('ERROR while uninstalling', err);
}
Expand Down
75 changes: 44 additions & 31 deletions lib/installer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
const fs = require('fs');
const path = require('path');
const untildify = require('untildify');
const { promisify } = require('util');
const { tabtabDebug, exists } = require('./utils');
const { tabtabDebug, exists, untildify } = require('./utils');
const { SUPPORTED_SHELLS } = require('./constants')

const debug = tabtabDebug('tabtab:installer');
Expand Down Expand Up @@ -40,15 +39,16 @@ const scriptFromShell = shell => path.join(__dirname, 'templates', templateFileN
* provided shell value.
*
* @param {SupportedShell} shell - Shell value to test against
* @param {() => String} getHomeDir - Function that returns the home directory, usually `os.homedir`
* @returns {String} Either ~/.bashrc, ~/.zshrc or ~/.config/fish/config.fish,
* untildified. Defaults to ~/.bashrc if provided SHELL is not valid.
*/
const locationFromShell = shell => {
const locationFromShell = (shell, getHomeDir) => {
const location = SHELL_LOCATIONS[shell];
if (!location) {
throw new Error(`Unsupported shell: ${shell}`);
}
return untildify(location);
return untildify(location, getHomeDir);
};

/**
Expand Down Expand Up @@ -87,18 +87,19 @@ const sourceLineForShell = (scriptname, shell) => {
* Helper to check if a filename is one of the SHELL config we expect
*
* @param {String} filename - Filename to check against
* @param {() => String} getHomeDir - Function that returns the home directory, usually `os.homedir`
* @returns {Boolean} Either true or false
*/
const isInShellConfig = filename =>
const isInShellConfig = (filename, getHomeDir) =>
[
SHELL_LOCATIONS.bash,
SHELL_LOCATIONS.zsh,
SHELL_LOCATIONS.fish,
SHELL_LOCATIONS.pwsh,
untildify(SHELL_LOCATIONS.bash),
untildify(SHELL_LOCATIONS.zsh),
untildify(SHELL_LOCATIONS.fish),
untildify(SHELL_LOCATIONS.pwsh),
untildify(SHELL_LOCATIONS.bash, getHomeDir),
untildify(SHELL_LOCATIONS.zsh, getHomeDir),
untildify(SHELL_LOCATIONS.fish, getHomeDir),
untildify(SHELL_LOCATIONS.pwsh, getHomeDir),
].includes(filename);

/**
Expand All @@ -107,14 +108,15 @@ const isInShellConfig = filename =>
*
* @param {String} filename - The filename to check against
* @param {String} line - The line to look for
* @param {() => String} getHomeDir - Function that returns the home directory, usually `os.homedir`
* @returns {Promise.<Boolean>} true or false, false if the line is not present.
*/
const checkFilenameForLine = async (filename, line) => {
const checkFilenameForLine = async (filename, line, getHomeDir) => {
debug('Check filename (%s) for "%s"', filename, line);

let filecontent = '';
try {
filecontent = await readFile(untildify(filename), 'utf8');
filecontent = await readFile(untildify(filename, getHomeDir), 'utf8');
} catch (/** @type {any} */ err) {
if (err.code !== 'ENOENT') {
console.error(
Expand All @@ -137,14 +139,15 @@ const checkFilenameForLine = async (filename, line) => {
* @param {String} options.filename - The file to modify.
* @param {String} options.scriptname - The line to add sourcing this file.
* @param {String} options.name - The package being configured.
* @param {() => String} options.getHomeDir - Function that returns the home directory, usually `os.homedir`
* @param {SupportedShell} options.shell
* @returns {Promise.<void>}
*/
const writeLineToFilename = ({ filename, scriptname, name, shell }) => new Promise((
const writeLineToFilename = ({ filename, scriptname, name, shell, getHomeDir }) => new Promise((
resolve,
reject
) => {
const filepath = untildify(filename);
const filepath = untildify(filename, getHomeDir);

debug('Creating directory for %s file', filepath);
mkdir(path.dirname(filepath), { recursive: true })
Expand All @@ -156,7 +159,7 @@ const writeLineToFilename = ({ filename, scriptname, name, shell }) => new Promi
debug('Writing to shell configuration file (%s)', filename);
debug('scriptname:', scriptname);

const inShellConfig = isInShellConfig(filename);
const inShellConfig = isInShellConfig(filename, getHomeDir);
if (inShellConfig) {
stream.write(`\n# tabtab source for packages`);
} else {
Expand All @@ -183,9 +186,10 @@ const writeLineToFilename = ({ filename, scriptname, name, shell }) => new Promi
* @param {String} options.location - The SHELL script location (~/.bashrc, ~/.zshrc or
* ~/.config/fish/config.fish)
* @param {String} options.name - The package configured for completion
* @param {() => String} options.getHomeDir - Function that returns the home directory, usually `os.homedir`
* @param {SupportedShell} options.shell options.shell
*/
const writeToShellConfig = async ({ location, name, shell }) => {
const writeToShellConfig = async ({ location, name, shell, getHomeDir }) => {
const scriptname = path.join(
COMPLETION_DIR,
shell,
Expand All @@ -195,7 +199,7 @@ const writeToShellConfig = async ({ location, name, shell }) => {
const filename = location;

// Check if SHELL script already has a line for tabtab
const existing = await checkFilenameForLine(filename, scriptname);
const existing = await checkFilenameForLine(filename, scriptname, getHomeDir);
if (existing) {
return console.log('=> Tabtab line already exists in %s file', filename);
}
Expand All @@ -205,6 +209,7 @@ const writeToShellConfig = async ({ location, name, shell }) => {
scriptname,
name,
shell,
getHomeDir,
});
};

Expand All @@ -215,9 +220,10 @@ const writeToShellConfig = async ({ location, name, shell }) => {
*
* @param {Object} options - Options object with
* @param {String} options.name - The package configured for completion
* @param {() => String} options.getHomeDir - Function that returns the home directory, usually `os.homedir`
* @param {SupportedShell} options.shell
*/
const writeToTabtabScript = async ({ name, shell }) => {
const writeToTabtabScript = async ({ name, shell, getHomeDir }) => {
const filename = path.join(
COMPLETION_DIR,
shell,
Expand All @@ -231,12 +237,12 @@ const writeToTabtabScript = async ({ name, shell }) => {
);

// Check if tabtab completion file already has this line in it
const existing = await checkFilenameForLine(filename, scriptname);
const existing = await checkFilenameForLine(filename, scriptname, getHomeDir);
if (existing) {
return console.log('=> Tabtab line already exists in %s file', filename);
}

return writeLineToFilename({ filename, scriptname, name, shell });
return writeLineToFilename({ filename, scriptname, name, shell, getHomeDir });
};

/**
Expand Down Expand Up @@ -266,11 +272,13 @@ const getCompletionScript = async ({ name, completer, shell }) => {
* @param {Object} options - Options object with
* @param {String} options.name - The package configured for completion
* @param {String} options.completer - The binary that will act as the completer for `name` program
* @param {() => String} options.getHomeDir - Function that returns the home directory, usually `os.homedir`
* @param {SupportedShell} options.shell
*/
const writeToCompletionScript = async ({ name, completer, shell }) => {
const writeToCompletionScript = async ({ name, completer, shell, getHomeDir }) => {
const filename = untildify(
path.join(COMPLETION_DIR, shell, completionFileName(name, shell))
path.join(COMPLETION_DIR, shell, completionFileName(name, shell)),
getHomeDir,
);

try {
Expand All @@ -297,6 +305,7 @@ const writeToCompletionScript = async ({ name, completer, shell }) => {
* for `name` program. Can be the same.
* @param {String} options.location - The SHELL script config location (~/.bashrc, ~/.zshrc or
* ~/.config/fish/config.fish)
* @param {() => String} options.getHomeDir - Function that returns the home directory, usually `os.homedir`
* @param {SupportedShell} options.shell - the target shell language
*/
const install = async options => {
Expand Down Expand Up @@ -336,11 +345,12 @@ const install = async options => {
*
* @param {String} filename - The filename to operate on
* @param {String} name - The package name to look for
* @param {() => String} getHomeDir - Function that returns the home directory, usually `os.homedir`
*/
const removeLinesFromFilename = async (filename, name) => {
const removeLinesFromFilename = async (filename, name, getHomeDir) => {
/* eslint-disable no-unused-vars */
debug('Removing lines from %s file, looking for %s package', filename, name);
if (!(await exists(filename))) {
if (!(await exists(filename, getHomeDir))) {
return debug('File %s does not exist', filename);
}

Expand Down Expand Up @@ -403,30 +413,32 @@ const removeLinesFromFilename = async (filename, name) => {
* @param {Object} options
* @param {String} options.name - Name of the target program.
* @param {SupportedShell} [options.shell] - The target shell language. If not specified, target all supported shells.
* @param {() => String} options.getHomeDir - Function that returns the home directory, usually `os.homedir`
*/
const uninstall = async options => {
debug('Uninstall with options', options);
if (!options) {
throw new Error('options is required');
}

const { name, shell } = options;
const { name, shell, getHomeDir } = options;

if (!name) {
throw new Error('Unable to uninstall if options.name is missing');
}

if (!shell) {
await Promise.all(SUPPORTED_SHELLS.map(shell => uninstall({ name, shell })));
await Promise.all(SUPPORTED_SHELLS.map(shell => uninstall({ name, shell, getHomeDir })));
return;
}

const completionScript = untildify(
path.join(COMPLETION_DIR, shell, completionFileName(name, shell))
path.join(COMPLETION_DIR, shell, completionFileName(name, shell)),
getHomeDir,
);

// First, lets remove the completion script itself
if (await exists(completionScript)) {
if (await exists(completionScript, getHomeDir)) {
await unlink(completionScript);
console.log('=> Removed completion script (%s)', completionScript);
}
Expand All @@ -437,20 +449,21 @@ const uninstall = async options => {
COMPLETION_DIR,
shell,
tabtabFileName(shell),
)
),
getHomeDir,
);
await removeLinesFromFilename(tabtabScript, name);
await removeLinesFromFilename(tabtabScript, name, getHomeDir);

// Then, check if __tabtab.shell is empty, if so remove the last source line in SHELL config
const isEmpty = (await readFile(tabtabScript, 'utf8')).trim() === '';
if (isEmpty) {
const shellScript = locationFromShell(shell);
const shellScript = locationFromShell(shell, getHomeDir);
debug(
'File %s is empty. Removing source line from %s file',
tabtabScript,
shellScript
);
await removeLinesFromFilename(shellScript, name);
await removeLinesFromFilename(shellScript, name, getHomeDir);
}

console.log('=> Uninstalled completion for %s package', name);
Expand Down
7 changes: 4 additions & 3 deletions lib/utils/exists.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
const fs = require('fs');
const untildify = require('untildify');
const { promisify } = require('util');
const untildify = require('./untildify');

const readFile = promisify(fs.readFile);

/**
* @param {String} file
* @param {() => String} getHomeDir - Function that returns the home directory, usually `os.homedir`
*/
module.exports = async file => {
module.exports = async (file, getHomeDir) => {
let fileExists;
try {
await readFile(untildify(file));
await readFile(untildify(file, getHomeDir));
fileExists = true;
} catch (err) {
fileExists = false;
Expand Down
4 changes: 3 additions & 1 deletion lib/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const tabtabDebug = require('./tabtabDebug');
const exists = require('./exists');
const untildify = require('./untildify');

module.exports = {
tabtabDebug,
exists
exists,
untildify,
};
14 changes: 14 additions & 0 deletions lib/utils/untildify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Turn a path with leading tilde (`~`) into a real path
*
* @example
* const os = require('os')
* const userConfig = untildify('~/.config', os.homedir)
*
* @param {String} pathWithTilde - Path that may have leading tilde
* @param {() => String} getHomeDir - Function that returns the home directory, usually `os.homedir`
* @returns {String}
*/
const untildify = (pathWithTilde, getHomeDir) => pathWithTilde.replace(/^~(?=$|\/|\\)/, getHomeDir());

module.exports = untildify
10 changes: 2 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,13 @@
"dependencies": {
"debug": "^4.3.1",
"enquirer": "^2.3.6",
"minimist": "^1.2.5",
"untildify": "^4.0.0"
"minimist": "^1.2.5"
},
"auto-changelog": {
"template": "keepachangelog",
"unreleased": true,
"commitLimit": false,
"ignoreCommitPattern": "changelog|readme|^test"
},
"version": "0.5.1",
"pnpm": {
"patchedDependencies": {
"[email protected]": "patches/[email protected]"
}
}
"version": "0.5.1"
}
Loading

0 comments on commit 357d521

Please sign in to comment.