UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

237 lines (192 loc) 7.58 kB
const path = require('node:path'); const cds = require('../../bin/cds'); const { bold } = require('../../lib/util/term'); module.exports = Object.assign(completion, { handleCompletion, options: ['--shell', '--curr', '--line', '--prev'], shortcuts: ['-s'], flags: ['--add', '--profile', '--remove'], help: ` # SYNOPSIS *cds completion* <options> Adds or removes shell completion for cds commands. Currently supported shells: Bash, fish, Git Bash, Zsh, and PowerShell. Depending on the operating system and the shell type, the following files will be changed: Linux, WSL Bash: ~/.bashrc fish: ~/.config/fish/config.fish Zsh: ~/.zshrc macOS Bash: ~/.bash_profile fish: ~/.config/fish/config.fish Zsh: ~/.zshrc Windows PowerShell: $PROFILE Git Bash: ~/.bash_profile This command also acts as an internal completion handler for the shell completion scripts. # OPTIONS *--add* Add shell completion for cds commands (see examples). *--remove* Remove shell completion for cds commands (see examples). *--shell* <bash | fish | gitbash | ps | zsh> Force the shell completion code to be added to the correct shell profile file. bash: macOS, Linux, WSL fish: macOS, Linux, WSL zsh: macOS, Linux, WSL gitbash: Git Bash for Windows ps: PowerShell # EXAMPLES *cds* completion --add # Add shell completion for cds commands *cds* completion --remove # Remove shell completion for cds commands *cds* completion --add --shell bash # Add shell completion for cds commands and enforce using Bash shell # ALIASES *cds* add completion # Add shell completion for cds commands *cds* add completion --shell bash # Add shell completion for cds commands and enforce using Bash shell `}); function getScriptFile(shell) { switch (shell) { case 'bash': case 'gitbash': return 'cds-bash.sh'; case 'fish': return 'cds-fish.fish'; case 'ps': return 'cds-ps.ps1'; case 'zsh': return 'cds-zsh.sh'; case '': case undefined: case null: throw 'Internal error: option --shell is missing'; default: { const completionSetup = require('../../lib/init/template/completion/completionSetup'); throw `Internal error: unsupported shell: ${shell}, supported shells are ${completionSetup.supportedShells.join(', ')}`; } } } async function completion(_argv, options) { if (options.profile) { const scriptFile = getScriptFile(options.shell); return console.log(path.join(__dirname, 'scripts', scriptFile)); } const completionSetup = require('../../lib/init/template/completion/completionSetup'); if (options.add || options.remove) { await completionSetup.setup(options.add, options); return; } // workaround to parse complex clis containing -- as part of value, // e.g. cds completion --line "cds add --" const localArgv = process.argv.slice(3); const localOptions = {} for (let i = 0; i < localArgv.length; i++) { localOptions[localArgv[i].replaceAll('-', '')] = localArgv[++i]; } if (localOptions.curr === undefined || localOptions.prev === undefined || localOptions.line === undefined) { throw `Some information is missing. Please run ${bold('cds help completion')} for details on how to call this command.`; } const shell = localOptions.shell; if (!completionSetup.supportedShells.includes(shell)) { throw `internal Error: no completion handler found for shell type: ${shell}, supported shells are ${completionSetup.supportedShells.join(', ')}`; } const currentWord = localOptions.curr?.trim().replace(/^'|'$/g, ''); const previousWord = localOptions.prev?.trim().replace(/^'|'$/g, ''); const line = localOptions.line?.trim().replace(/^'|'$/g, ''); const segments = getSegments(line); const cdsCmd = segments[1] || ''; const completions = await getCompletions(cdsCmd, previousWord, currentWord, segments); reply(completions); } async function handleCompletion(currentWord, previousWord, argv) { const allOptionsFlags = ['--add', '--remove', '--shell'] .filter(e => !argv.includes(e)); if (currentWord?.startsWith('-')) { return allOptionsFlags; } if (previousWord === '--shell') { const { supportedShells } = require('../../lib/init/template/completion/completionSetup'); return supportedShells; } return allOptionsFlags; } function reply(values) { if (!Array.isArray(values) || values.length === 0) { return; } values = values.filter(e => !!(typeof e === 'string' && e.trim())); values = [...new Set(values)]; values.sort((a, b) => { if (a.startsWith('-') && !b.startsWith('-')) { return 1; } if (!a.startsWith('-') && b.startsWith('-')) { return -1; } return a.localeCompare(b); }); console.log(values.join('\n')); } function getSegments(line) { const cliSplit = /'(.*?)'|"(.*?)"|(\\,|\\ |[^, ])+/gs; const result = line.match(cliSplit); return result.map(s => s.trim() .replace(/(\\ )/g, ' ') .replace(/^["']|["']$/g, '') ); } function getCdsCmd(cdsCmd) { cdsCmd = cds.Shortcuts?.[cdsCmd] ?? cdsCmd; return cds.load(cdsCmd); } function getAllOptions(cdsCmdObj, segments) { return [ ...cdsCmdObj.options ?? [], ...cdsCmdObj.flags?.filter(o => !segments.includes(o)) ?? [] ]; } function getUtil() { const completionFs = require('./completionFs'); return { cds, completionFs } } async function getCompletions(cdsCmd, previousWord, currentWord, segments) { const _filter = (completions, currentWord) => { return completions.filter(token => token?.startsWith(currentWord)); } const _getCompletions = async () => { if (previousWord === 'cds') { if (!currentWord) { return cds.cmds; } else { const completions = _filter(cds.cmds, currentWord); if (completions.length > 0) { return completions; } cdsCmd = 'compile'; previousWord = 'compile'; } } try { const cdsCmdObj = getCdsCmd(cdsCmd); if (typeof cdsCmdObj.handleCompletion === 'function') { return await cdsCmdObj.handleCompletion(currentWord, previousWord, segments, getUtil()); } if (currentWord?.startsWith('-')) { return getAllOptions(cdsCmdObj, segments); } const completionFs = require('./completionFs'); const all = await completionFs.readdir(currentWord); if (!(cdsCmdObj.options?.includes(previousWord) || cdsCmdObj.shortcuts?.includes(previousWord))) { const allOptions = getAllOptions(cdsCmdObj, segments); all.push(...allOptions); } return all; } catch { return []; } } let completions = await _getCompletions(); if (currentWord) { completions = _filter(completions, currentWord); } return completions; }