-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
kakha urigashvili
committed
Nov 5, 2020
1 parent
c11beb8
commit 6f46a9d
Showing
14 changed files
with
385 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,3 +21,5 @@ coverage | |
# test temp | ||
test/temp | ||
|
||
autocomplete-hints.json | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#!/usr/bin/env node | ||
|
||
require('module-alias/register'); | ||
const commander = require('commander'); | ||
const { makeAutoCompleteCommander } = require('@src/commands/autocomplete'); | ||
const { makeSmapiCommander } = require('@src/commands/smapi/smapi-commander'); | ||
const ConfigureCommander = require('@src/commands/configure'); | ||
const DeployCommander = require('@src/commands/deploy'); | ||
const DialogCommander = require('@src/commands/dialog'); | ||
const InitCommander = require('@src/commands/init'); | ||
const NewCommander = require('@src/commands/new'); | ||
const UtilCommander = require('@src/commands/util/util-commander'); | ||
|
||
const smapiCommander = makeSmapiCommander(); | ||
const utilCommander = UtilCommander.commander; | ||
const configureCommander = ConfigureCommander.createCommand(commander); | ||
const deployCommander = DeployCommander.createCommand(commander); | ||
const newCommander = NewCommander.createCommand(commander); | ||
const initCommander = InitCommander.createCommand(commander); | ||
const dialogCommander = DialogCommander.createCommand(commander); | ||
const commanders = [smapiCommander, utilCommander, configureCommander, deployCommander, newCommander, initCommander, dialogCommander]; | ||
|
||
const autoCompleteCommander = makeAutoCompleteCommander(commanders); | ||
|
||
if (!process.argv.slice(2).length) { | ||
autoCompleteCommander.outputHelp(); | ||
} else { | ||
autoCompleteCommander.parse(process.argv); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
const fs = require('fs-extra'); | ||
|
||
const CONSTANTS = require('@src/utils/constants'); | ||
|
||
module.exports = class Helper { | ||
constructor(omelette, commanders = []) { | ||
this.commanders = commanders; | ||
this.completion = omelette(`ask <command> <subCommand> ${'<option> '.repeat(50).trim()}`); | ||
this.autoCompleteHintsFile = 'autocomplete-hints.json'; | ||
} | ||
|
||
_mapOptionsToParent(options, parent) { | ||
// default options | ||
parent['-h'] = {}; | ||
parent['--help'] = {}; | ||
options.forEach(opt => { | ||
const { long, short } = opt; | ||
// there is always long name | ||
parent[long] = {}; | ||
if (short) { | ||
parent[short] = {}; | ||
} | ||
}); | ||
} | ||
|
||
_getAutoCompleteOptions() { | ||
const options = {}; | ||
this.commanders.forEach(com => { | ||
options[com.name()] = {}; | ||
this._mapOptionsToParent(com.options, options[com.name()]); | ||
com.commands.forEach(sumCom => { | ||
options[com.name()][sumCom.name()] = {}; | ||
this._mapOptionsToParent(sumCom.options, options[com.name()][sumCom.name()]); | ||
}); | ||
}); | ||
|
||
return options; | ||
} | ||
|
||
_parseArguments(line) { | ||
const [ask, command, sumCommand] = line.split(' ').map(i => i.trim()); | ||
return { ask, command, sumCommand }; | ||
} | ||
|
||
/** | ||
* Initializes auto complete inside of the program | ||
*/ | ||
initAutoComplete() { | ||
if (fs.existsSync(this.autoCompleteHintsFile)) { | ||
const options = fs.readJsonSync(this.autoCompleteHintsFile); | ||
|
||
this.completion | ||
.on('command', ({ reply }) => { | ||
reply(CONSTANTS.TOP_LEVEL_COMMANDS); | ||
}) | ||
.on('subCommand', ({ line, reply }) => { | ||
const { command } = this._parseArguments(line); | ||
if (options[command]) { | ||
reply(Object.keys(options[command])); | ||
} | ||
}) | ||
.on('option', ({ line, reply }) => { | ||
const { command, sumCommand } = this._parseArguments(line); | ||
if (options[command] && options[command][sumCommand]) { | ||
reply(Object.keys(options[command][sumCommand])); | ||
} | ||
}); | ||
|
||
this.completion.init(); | ||
} | ||
} | ||
|
||
_withProcessExitDisabled(fn) { | ||
const origExit = process.exit; | ||
process.exit = () => {}; | ||
fn(); | ||
process.exit = origExit; | ||
} | ||
|
||
/** | ||
* Sets ups auto complete. For example, adds autocomplete entry to .bash_profile file | ||
*/ | ||
setUpAutoComplete() { | ||
const options = this._getAutoCompleteOptions(); | ||
fs.writeJSONSync(this.autoCompleteHintsFile, options); | ||
this._withProcessExitDisabled(() => this.completion.setupShellInitFile()); | ||
} | ||
|
||
/** | ||
* Removes auto complete. For example, removes autocomplete entry from .bash_profile file | ||
*/ | ||
cleanUpAutoComplete() { | ||
this._withProcessExitDisabled(() => this.completion.cleanupShellInitFile()); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
const commander = require('commander'); | ||
const omelette = require('omelette'); | ||
const Messenger = require('@src/view/messenger'); | ||
const Helper = require('./helper'); | ||
|
||
/** | ||
* Initializes auto complete inside of the program | ||
*/ | ||
const initAutoComplete = () => { | ||
const helper = new Helper(omelette); | ||
helper.initAutoComplete(); | ||
}; | ||
/** | ||
* Creates auto complete commander | ||
* @param {*} commanders list of commanders used for creating an autocomplete hints file | ||
*/ | ||
const makeAutoCompleteCommander = commanders => { | ||
const helper = new Helper(omelette, commanders); | ||
const program = new commander.Command(); | ||
program._name = 'autocomplete'; | ||
program.description('sets up ask cli terminal auto completion'); | ||
|
||
program.command('setup') | ||
.description('set up auto completion') | ||
.action(() => { | ||
helper.setUpAutoComplete(); | ||
Messenger.getInstance().info('Successfully set up auto completion. Please, reload the terminal.'); | ||
}); | ||
|
||
program.command('cleanup') | ||
.description('clean up auto completion') | ||
.action(() => { | ||
helper.cleanUpAutoComplete(); | ||
Messenger.getInstance().info('Successfully removed auto completion. Please, reload the terminal.'); | ||
}); | ||
|
||
return program; | ||
}; | ||
|
||
module.exports = { initAutoComplete, makeAutoCompleteCommander }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
const { expect } = require('chai'); | ||
const commander = require('commander'); | ||
const EventEmitter = require('events'); | ||
const fs = require('fs-extra'); | ||
const sinon = require('sinon'); | ||
|
||
const CONSTANTS = require('@src/utils/constants'); | ||
const Helper = require('@src/commands/autocomplete/helper'); | ||
|
||
describe('Commands autocomplete - helper test', () => { | ||
let helper; | ||
let setupShellInitFileStub; | ||
let cleanupShellInitFileStub; | ||
let initStub; | ||
let omeletteStub; | ||
|
||
const testCommander = new commander.Command(); | ||
testCommander._name = 'test'; | ||
testCommander.command('command-one'); | ||
testCommander.command('command-two'); | ||
testCommander.option('--large'); | ||
testCommander.option('-s, --small'); | ||
|
||
const commanders = [testCommander]; | ||
const hints = { | ||
smapi: { | ||
'-h': {}, | ||
'--help': {}, | ||
'list-catalogs-for-vendor': { | ||
'-h': {}, | ||
'--help': {}, | ||
'--next-token': {}, | ||
'--max-results': {}, | ||
'--profile': {}, | ||
'-p': {}, | ||
'--full-response': {}, | ||
'--debug': {} | ||
} | ||
} | ||
}; | ||
|
||
beforeEach(() => { | ||
setupShellInitFileStub = sinon.stub(); | ||
cleanupShellInitFileStub = sinon.stub(); | ||
initStub = sinon.stub(); | ||
|
||
omeletteStub = () => { | ||
class OmeletteStubClass extends EventEmitter { | ||
constructor() { | ||
super(); | ||
this.setupShellInitFile = setupShellInitFileStub; | ||
this.cleanupShellInitFile = cleanupShellInitFileStub; | ||
this.init = initStub; | ||
} | ||
} | ||
return new OmeletteStubClass(); | ||
}; | ||
|
||
helper = new Helper(omeletteStub, commanders); | ||
}); | ||
|
||
it('should set up autocomplete', () => { | ||
const writeJSONStub = sinon.stub(fs, 'writeJSONSync'); | ||
helper.setUpAutoComplete(); | ||
|
||
expect(writeJSONStub.callCount).eq(1); | ||
expect(setupShellInitFileStub.callCount).eq(1); | ||
}); | ||
|
||
it('should clean up autocomplete', () => { | ||
helper.cleanUpAutoComplete(); | ||
|
||
expect(cleanupShellInitFileStub.callCount).eq(1); | ||
}); | ||
|
||
it('should not initialize autocomplete if hint file is not present', () => { | ||
sinon.stub(fs, 'existsSync').withArgs(helper.autoCompleteHintsFile).returns(false); | ||
helper.initAutoComplete(); | ||
|
||
expect(initStub.callCount).eq(0); | ||
}); | ||
|
||
it('initialize autocomplete if hint file is present', () => { | ||
sinon.stub(fs, 'existsSync').withArgs(helper.autoCompleteHintsFile).returns(true); | ||
sinon.stub(fs, 'readJsonSync').withArgs(helper.autoCompleteHintsFile).returns(hints); | ||
helper.initAutoComplete(); | ||
|
||
expect(initStub.callCount).eq(1); | ||
}); | ||
|
||
it('should reply with first level commands', (done) => { | ||
sinon.stub(fs, 'existsSync').withArgs(helper.autoCompleteHintsFile).returns(true); | ||
sinon.stub(fs, 'readJsonSync').withArgs(helper.autoCompleteHintsFile).returns(hints); | ||
|
||
helper = new Helper(omeletteStub); | ||
helper.initAutoComplete(); | ||
|
||
const reply = (value) => { | ||
expect(value).eq(CONSTANTS.TOP_LEVEL_COMMANDS); | ||
done(); | ||
}; | ||
|
||
helper.completion.emit('command', { reply }); | ||
}); | ||
|
||
it('should reply with second level subCommands', (done) => { | ||
sinon.stub(fs, 'existsSync').withArgs(helper.autoCompleteHintsFile).returns(true); | ||
sinon.stub(fs, 'readJsonSync').withArgs(helper.autoCompleteHintsFile).returns(hints); | ||
|
||
helper = new Helper(omeletteStub); | ||
helper.initAutoComplete(); | ||
|
||
const reply = (value) => { | ||
expect(value).eql(Object.keys(hints.smapi)); | ||
done(); | ||
}; | ||
|
||
helper.completion.emit('subCommand', { reply, line: 'ask smapi' }); | ||
}); | ||
|
||
it('should reply with third level options', (done) => { | ||
sinon.stub(fs, 'existsSync').withArgs(helper.autoCompleteHintsFile).returns(true); | ||
sinon.stub(fs, 'readJsonSync').withArgs(helper.autoCompleteHintsFile).returns(hints); | ||
|
||
helper = new Helper(omeletteStub); | ||
helper.initAutoComplete(); | ||
|
||
const reply = (value) => { | ||
expect(value).eql(Object.keys(hints.smapi['list-catalogs-for-vendor'])); | ||
done(); | ||
}; | ||
|
||
helper.completion.emit('option', { reply, line: 'ask smapi list-catalogs-for-vendor' }); | ||
}); | ||
|
||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
}); |
Oops, something went wrong.