Skip to content

Commit

Permalink
fix(core-support): add history -c support
Browse files Browse the repository at this point in the history
also implement a real usage model for the history command

Fixes #179
  • Loading branch information
starpit committed Jan 10, 2019
1 parent ba39641 commit e7a76c9
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 59 deletions.
54 changes: 32 additions & 22 deletions app/plugins/modules/core-support/src/lib/cmds/history/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,27 @@ const parseN = str => {
}

const usage = {
history: `List current history, optionally filtering by a given string.
Examples:
history list the most recent ${DEFAULT_HISTORY_N} commands
history <N> list the most recent N commands
history <N> <str> filter the most recent N commands, showing only those that contain the given string
history <str> ibid, but using the default of N=${DEFAULT_HISTORY_N}`,

again: op => `Re-execute a given command index.
Examples:
${op} re-execute the previous comman
${op} <N> ibid, but at the given history index; hint: use history to list recently executed commands`
history: {
command: 'history',
strict: 'history',
docs: 'List current command history, optionally filtering by a given string',
example: 'history 100 filterString',
optional: [
{ name: 'N', positional: true, docs: 'list the most recent N commands' },
{ name: 'filterString', positional: true, docs: 'filter command history' },
{ name: '--clear', alias: '-c', docs: 'clear your command history' }
]
},

again: command => ({
command,
strict: command,
docs: 'Re-execute a given command index',
example: `${command} 50`,
optional: [
{ name: 'N', positional: true, docs: 're-execute the given history index N' }
]
})
}

/**
Expand Down Expand Up @@ -82,21 +90,24 @@ const again = (N: number, historyEntry) => {
*
*/
const showHistory = ({ argv, parsedOptions: options }) => {
if (options.help) {
throw new UsageError(usage.history)
if (options.c) {
debug('clearing command history')
return historyModel.wipe()
}

const historyIdx = argv.indexOf('history')
const Nargs = argv.length - historyIdx - 1
const firstArgLooksLikeN = parseN(argv[historyIdx + 1])
const Nidx = Nargs === 2 || firstArgLooksLikeN ? historyIdx + 1 : -1
const N = Nidx > 0 ? firstArgLooksLikeN : DEFAULT_HISTORY_N

// construct the filter
const filterIdx = Nargs === 2 ? historyIdx + 2 : !firstArgLooksLikeN ? historyIdx + 1 : -1
const filterStr = filterIdx > 0 && argv[filterIdx]
const filter = filterStr ? line => !line.raw.startsWith('history') && line.raw.indexOf(filterStr) >= 0 : () => true // ignore history commands if a filterStr is specified
const filter = filterStr ? line => line.raw.indexOf(filterStr) >= 0 : () => true

const startIdx = Math.max(0, historyModel.getCursor() - N - 1)
const endIdx = historyModel.getCursor() + 1
const endIdx = historyModel.getCursor() - 1
const recent = historyModel.lines.slice(startIdx, endIdx)

debug('argv', argv)
Expand All @@ -105,6 +116,7 @@ const showHistory = ({ argv, parsedOptions: options }) => {
debug('N', N)
debug('filterIdx', filterIdx)
debug('filterStr', filterStr)
debug('got', recent.length, startIdx, endIdx)

return recent.map((line, idx) => {
if (!filter(line)) return
Expand Down Expand Up @@ -140,19 +152,17 @@ const showHistory = ({ argv, parsedOptions: options }) => {
export default (commandTree, prequire) => {
debug('init')

commandTree.listen('/history', showHistory, { docs: 'Show recently executed commands' })
commandTree.listen('/history', showHistory, { usage: usage.history, noAuthOk: true })

/** clear view or clear history */
commandTree.listen('/history/purge', historyModel.wipe, { docs: 'Clear your command history' })
// commandTree.listen('/history/purge', historyModel.wipe, { docs: 'Clear your command history' })

/** re-execute from history */
const againCmd = op => ({ argv, execOptions, parsedOptions: options }) => {
const N = argv[1] || historyModel.getCursor() - 2 // use the last command, if the user entered only "!!"
console.error(execOptions)
return again(N, execOptions && execOptions.history)
}
const cmd = commandTree.listen('/!!',
againCmd('!!'),
{ docs: 'Re-execute the last command, or, with !! N, the command at history position N ' })
const cmd = commandTree.listen('/!!', againCmd('!!'), { usage: usage.again, noAuthOk: true })
commandTree.synonym('/again', againCmd('again'), cmd)
}
2 changes: 1 addition & 1 deletion app/plugins/modules/k8s/src/test/k8s/create-pod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe('k8s create pod kui.js headless mode', function (this: ISuite) {
doHeadless(this, kui)
})

describe('k8s create pod', function (this: ISuite) {
describe('k8s create pod electron mode', function (this: ISuite) {
before(common.before(this, { noOpenWhisk: true }))
after(common.after(this))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import * as common from '../../../../../../../../tests/lib/common' // tslint:dis
import * as ui from '../../../../../../../../tests/lib/ui'
const { cli, selectors, sidecar } = ui

describe('Create an action, list it, delete it, then list nothing (explicit entity type)', function (this: ISuite) {
describe('Create an action, list it, delete it, then list nothing explicit entity type', function (this: ISuite) {
before(common.before(this))
after(common.after(this))

Expand Down
2 changes: 1 addition & 1 deletion app/plugins/modules/openwhisk/src/test/openwhisk2/await.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('Invoke asynchronously and await', function (this: ISuite) {

// create the second action
it('should do an async of the action, using implicit context', () => cli.do(`async`, this.app)
.then(cli.expectJustOK))
.then(cli.expectOKWithString(actionName))) // e.g. "invoked `actionName` with id:"

// call await
it('should await completion of the activation', () => cli.do(`$ await`, this.app)
Expand Down
9 changes: 2 additions & 7 deletions app/plugins/modules/openwhisk/src/test/openwhisk2/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,12 @@
* limitations under the License.
*/

/**
* read-only tests against the cli's list APIs
*
*/

import { ISuite } from '../../../../../../../tests/lib/common'
import * as common from '../../../../../../../tests/lib/common' // tslint:disable-line:no-duplicate-imports
import * as ui from '../../../../../../../tests/lib/ui'
const { cli, rp, selectors, sidecar } = ui

describe('History commands', function (this: ISuite) {
describe('History', function (this: ISuite) {
before(common.before(this))
after(common.after(this))

Expand All @@ -50,7 +45,7 @@ describe('History commands', function (this: ISuite) {
.then(cli.expectOK)
.then(sidecar.expectClosed))

it('should re-execte from history', () => cli.do('history 5 create', this.app)
it('should re-execute from history', () => cli.do('history 5 create', this.app)
.then(cli.expectOKWithCustom({ passthrough: true }))
.then(N => this.app.client.click(`${ui.selectors.LIST_RESULTS_N(N)}:first-child .entity-name`))
.then(() => this.app)
Expand Down
14 changes: 8 additions & 6 deletions app/plugins/modules/openwhisk/src/test/openwhisk3/lcd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,29 @@ import * as common from '../../../../../../../tests/lib/common' // tslint:disabl
import * as ui from '../../../../../../../tests/lib/ui'
const { cli, selectors, sidecar } = ui

describe('Change shell directory via lcd', function (this: ISuite) {
import { normalize } from 'path'

describe('Change shell directory via cd and lcd', function (this: ISuite) {
before(common.before(this))
after(common.after(this))

it('should have an active repl', () => cli.waitForRepl(this.app))

it('should execute lcd data', () => cli.do(`lcd data`, this.app)
.then(cli.expectJustOK))
.then(cli.expectOKWithString('data')))

it('should create an action in the data directory', () => cli.do(`action create long openwhisk/long.js`, this.app)
.then(cli.expectOK)
.then(sidecar.expectOpen)
.then(sidecar.expectShowing('long'))
.catch(common.oops(this)))

it('should execute lcd - to change to previous dir', () => cli.do(`lcd -`, this.app)
.then(cli.expectJustOK))
it('should execute lcd - to change to previous dir', () => cli.do(`cd -`, this.app)
.then(cli.expectOKWithString(normalize(process.env.TEST_ROOT))))

// now we should be able to change back to data and re-do the action create
it('should execute lcd data', () => cli.do(`lcd data`, this.app)
.then(cli.expectJustOK))
.then(cli.expectOKWithString('data')))

it('should create an action in the data directory', () => cli.do(`action create long2 openwhisk/long.js`, this.app)
.then(cli.expectOK)
Expand All @@ -48,5 +50,5 @@ describe('Change shell directory via lcd', function (this: ISuite) {
.catch(common.oops(this)))

it('should execute lcd without arguments', () => cli.do(`lcd`, this.app)
.then(cli.expectJustOK))
.then(cli.expectOKWithString(normalize(process.env.HOME))))
})
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ describe('List root-most non-erroring activations with $$!', function (this: ISu

// invoke the sequences
it('should do an async of the non-erroring sequence', () => cli.do(`wsk action async ${goodSeqName} -p name nnn`, this.app)
.then(cli.expectJustOK)
.then(cli.expectOKWithString(goodSeqName)) // e.g. "invoked `goodSeqName` with id:"
.catch(common.oops(this)))

// call await
Expand All @@ -82,7 +82,7 @@ describe('List root-most non-erroring activations with $$!', function (this: ISu
.catch(common.oops(this)))

it('should do an async of the erroring sequence', () => cli.do(`wsk action async ${errorSeqName} -p name nnn`, this.app)
.then(cli.expectJustOK)
.then(cli.expectOKWithString(errorSeqName)) // e.g. "invoked `errorSeqName` with id:"
.catch(common.oops(this)))

// call await
Expand All @@ -96,7 +96,7 @@ describe('List root-most non-erroring activations with $$!', function (this: ISu

// call $ roots
it('should call $$! successfully', () => cli.do(`$$!`, this.app)
.then(cli.expectJustOK)
.then(cli.expectOKWithAny)
.catch(common.oops(this)))

it('should list the erroring sequence activation', () => cli.do(`$$!`, this.app)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('Create a sequence via let', function (this: ISuite) {

// invoke one of the sequences
it('should do an async of the sequence, using implicit context', () => cli.do(`async -p y 3`, this.app)
.then(cli.expectJustOK))
.then(cli.expectOKWithString(seqName3))) // e.g. "invoked `seqname3` with id:"

// call await
it('should await successful completion of the activation', () => cli.do(`await`, this.app)
Expand Down
8 changes: 4 additions & 4 deletions app/plugins/modules/openwhisk/src/test/openwhisk4/let.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ describe('Create an action via let core tests', function (this: ISuite) {
.then(sidecar.expectShowing(actionName13, undefined, undefined, packageName2)))

it('should create a sequence with inline file', () => cli.do(`wsk action let ${seqName1} = ${actionName2} -> ./data/openwhisk/hello.html`, this.app)
.then(cli.expectJustOK)
.then(cli.expectOKWithString('http')) // some web address, as this is a web action
.then(sidecar.expectOpen)
.then(sidecar.expectShowing(seqName1)))

it('should create a sequence with inline anonymous and inline file', () => cli.do(`wsk action let ${seqName2} = x=>x -> ./data/openwhisk/hello.html`, this.app)
.then(cli.expectJustOK)
.then(cli.expectOKWithString('http')) // some web address, as this is a web action
.then(sidecar.expectOpen)
.then(sidecar.expectShowing(seqName2)))

Expand All @@ -120,7 +120,7 @@ describe('Create an action via let core tests', function (this: ISuite) {
.then(sidecar.expectShowing(seqName3)))

it('should create a sequence with two inline files', () => cli.do(`wsk action let ${seqName4}=./data/openwhisk/foo.js-> ./data/openwhisk/hello.html`, this.app)
.then(cli.expectJustOK)
.then(cli.expectOKWithString('http')) // some web address, as this is a web action
.then(sidecar.expectOpen)
.then(sidecar.expectShowing(seqName4)))

Expand Down Expand Up @@ -333,7 +333,7 @@ describe('Create an action via let core tests', function (this: ISuite) {

// invoke it
it('should do an async of the action, using implicit context', () => cli.do(`async -p y 3`, this.app)
.then(cli.expectJustOK))
.then(cli.expectOKWithString(actionName))) // e.g. "invoked `actionName` with id:"

// call await
it('should await successful completion of the activation', () => cli.do(`$ await`, this.app)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const testPagination = (ctx: ISuite, actionName?: string) => {
const nextButton = `${lastBlock} .list-paginator-button-next`

return cli.do(`$ list --limit ${limit} ${extraArgs}`, app)
.then(cli.expectJustOK)
.then(cli.expectOKWithAny)
.then(() => app.client.waitUntil(() => {
return Promise.all([app.client.getText(description), app.client.elements(tableRowsFiltered)])
.then(([paginatorText, rows]) => {
Expand Down
8 changes: 5 additions & 3 deletions app/plugins/modules/openwhisk/src/test/openwhisk4/zip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ function main(args) {
.then(sidecar.expectBadge('zip')))
// invoke it
it('should do an async of the action, using implicit context', () => cli.do(`async -p y 3`, this.app)
.then(cli.expectJustOK))
.then(cli.expectOKWithString(actionName16)) // e.g. "invoked `actionName16` with id:"
.catch(common.oops(this)))
// call await
it('should await successful completion of the activation', () => cli.do(`$ await`, this.app)
.then(cli.expectJustOK)
Expand All @@ -219,12 +220,13 @@ function main(args) {
it('should create a zip action with npm install via let', () => rimraf([join(process.env.TEST_ROOT, 'data/openwhisk/zip-action/src/node_modules/**')])
.then(() => assert.ok(!fs.existsSync(join(process.env.TEST_ROOT, 'data/openwhisk/zip-action/src/node_modules'))))
.then(() => cli.do(`let ${actionName18}.zip = data/openwhisk/zip-action/src`, this.app))
.then(cli.expectContext('/wsk/actions', actionName16))
.then(cli.expectContext('/wsk/actions', actionName18))
.then(sidecar.expectOpen)
.then(sidecar.expectShowing(actionName18)))
// invoke it
it('should do an async of the action, using implicit context', () => cli.do(`async --param lines '["and now", "for something completely", "different" ]'`, this.app)
.then(cli.expectJustOK))
.then(cli.expectOKWithString(actionName18)) // e.g. "invoked `actionName18` with id:"
.catch(common.oops(this)))
// call await
it('should await successful completion of the activation', () => cli.do(`$ await`, this.app)
.then(cli.expectJustOK)
Expand Down
10 changes: 7 additions & 3 deletions app/src/core/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ const emptyPromise = () => {
const stripTrailer = (str: string) => str && str.replace(/\s+.*$/, '')

/** turn --foo into foo and -f into f */
const unflag = (opt: string) => stripTrailer(opt.replace(/^[-]+/, ''))
const unflag = (opt: string) => opt && stripTrailer(opt.replace(/^[-]+/, ''))

const emptyExecOptions = (): IExecOptions => new DefaultExecOptions()

Expand Down Expand Up @@ -505,7 +505,9 @@ export const exec = async (commandUntrimmed: string, execOptions = emptyExecOpti
// user passed an incorrect number of positional parameters?
//
if (!onlyEnforceOptions && nActualArgs !== nRequiredArgs) {
if (nActualArgs !== nRequiredArgs + nPositionalOptionals) {
// it's ok if we have nActualArgs in the range [nRequiredArgs, nRequiredArgs + nPositionalOptionals]
if (! (nActualArgs >= nRequiredArgs &&
nActualArgs <= nRequiredArgs + nPositionalOptionals)) {
// yup, scan for implicitOK
const implicitIdx = required.findIndex(({ implicitOK }) => implicitOK)
const selection = currentSelection()
Expand All @@ -526,7 +528,9 @@ export const exec = async (commandUntrimmed: string, execOptions = emptyExecOpti
// implicitOK, or the current selection
// (or lack thereof) didn't match with the
// command's typing requirement
const message = nRequiredArgs === 0 ? 'This command accepts no positional arguments'
const message = nRequiredArgs === 0 && nPositionalOptionals === 0
? 'This command accepts no positional arguments'
: nPositionalOptionals > 0 ? 'This command does not accept this many arguments'
: `This command requires ${nRequiredArgs} parameter${nRequiredArgs === 1 ? '' : 's'}, but you provided ${nActualArgsWithImplicit === 0 ? 'none' : nActualArgsWithImplicit}`
const err = new UsageError({ message, usage })
err.code = 497
Expand Down
8 changes: 4 additions & 4 deletions app/src/test/core2/clear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@ describe('Clear the console', function (this: ISuite) {
}))

// get something on the screen
it(`should list files`, () => cli.do('ls', this.app).then(cli.expectJustOK))
it(`should list files`, () => cli.do('ls ..', this.app).then(cli.expectOKWith('README.md')))

it('should clear the console', () => cli.do('clear', this.app)
.then(expectConsoleToBeClear)
.catch(common.oops(this)))

// get something on the screen
it(`should list files again`, () => cli.do('ls', this.app).then(cli.expectJustOK))
it(`should list files again`, () => cli.do('ls ..', this.app).then(cli.expectOKWith('README.md')))

const JUNK = 'junk text that should stay'
it('should clear the console with ctrl+l', () => cli.do(JUNK, this.app, true)
Expand All @@ -103,8 +103,8 @@ describe('Clear the console', function (this: ISuite) {
})

// get something on the screen
it(`should list files yet again`, () => cli.do('ls', this.app)
.then(cli.expectJustOK)
it(`should list files yet again`, () => cli.do('ls ..', this.app)
.then(cli.expectOKWith('README.md'))
.catch(common.oops(this)))

it('should clear properly despite existing prompt', () => cli.do('prompt', this.app) // wipe will change the placeholder text
Expand Down
Loading

0 comments on commit e7a76c9

Please sign in to comment.