howl.command
local cmd, readline
before_each ->
readline = Spy as_null_object: true
app.window = :readline
cmd = name: 'foo', description: 'desc', handler: spy.new -> true
after_each ->
command.unregister name for name in *command.names!
app.window = nil
.names() returns a list of all command names
command.register cmd
assert.includes command.names!, 'foo'
.<name> allows direct indexing of commands
command.register cmd
assert.equal command.foo.handler, cmd.handler
.get(name) returns the command with the specified name
command.register cmd
assert.equal command.get('foo').handler, cmd.handler
allows a registered command to be invoked directly
cmd.handler = (num) -> num * 2
command.register cmd
assert.equal command.foo(2), 4
.unregister(command) removes the command and any aliases
command.register cmd
command.alias 'foo', 'bar'
command.unregister 'foo'
assert.is_nil command.foo
assert.is_nil command.bar
assert.same command.names!, {}
.register(command)
raises an error if any of the mandatory fields are missing
assert.raises 'name', -> command.register {}
assert.raises 'description', -> command.register name: 'foo'
assert.raises 'handler', -> command.register name: 'foo', description: 'do'
.alias(target, name)
raises an error if target does not exist
assert.raises 'exist', -> command.alias 'nothing', 'something'
allows for multiple names for the same command
command.register cmd
command.alias 'foo', 'bar'
assert.equal 'foo', command.bar
assert.includes command.names!, 'bar'
(when command name is a non-lua identifier)
before_each -> cmd.name = 'foo-cmd:bar'
register() adds accessible aliases for the direct indexing
command.register cmd
assert.equal command['foo-cmd:bar'].handler, cmd.handler
assert.equal command.foo_cmd_bar.handler, cmd.handler
the accessible alias is not part of names()
command.register cmd
assert.same command.names!, { 'foo-cmd:bar' }
unregister() removes the accessible name as well
command.register cmd
command.unregister 'foo-cmd:bar'
assert.is_nil command.foo_cmd_bar
.run(cmd_string)
local first_input, second_input
before_each ->
inputs.register 'test_first', ->
first_input = {
complete: -> 'completions'
should_complete: -> 'perhaps'
close_on_cancel: -> true
value_for: -> 123
on_completed: Spy!
go_back: Spy!
on_cancelled: Spy!
}
first_input
inputs.register 'test_second', ->
second_input = {
complete: -> 'other completions'
should_complete: -> 'oh yes'
close_on_cancel: -> false
on_completed: Spy!
go_back: Spy!
on_cancelled: Spy!
}
second_input
inputs.register 'dummy', -> {}
after_each ->
inputs.unregister 'test_first'
inputs.unregister 'test_second'
inputs.unregister 'dummy'
(when <cmd_string> is empty or missing)
invokes howl.app.window.readline with a ":" prompt
command.run!
assert.is_true readline.read.called
_, prompt = unpack readline.read.called_with
assert.equal prompt, ':'
(when <cmd_string> is given)
(.. and it matches a simple command without parameters)
that command is invoked direcly
cmd.handler = Spy!
command.register cmd
command.run cmd.name
assert.is_true cmd.handler.called
(.. when it specifies a command with all required parameters)
that command is invoked directly with converted values
cmd.handler = Spy!
cmd.inputs = { 'test_first' }
command.register cmd
command.run cmd.name .. ' foo'
assert.same cmd.handler.called_with, { first_input.value_for! }
(.. when it specifies a command without all required parameters)
invokes howl.app.window.readline with the prompt set to the given string
cmd.inputs = { 'test_first', 'test_second' }
command.register cmd
command.run cmd.name .. ' arg'
assert.is_true readline.read.called
_, prompt = unpack readline.read.called_with
assert.equal prompt, ':' .. cmd.name .. ' arg '
(.. when it specifies a unknown command)
invokes readline.read with the text set to the given string
command.run 'what-the-heck now'
assert.is_true readline.read.called
assert.equals 'what-the-heck now', readline.read.called_with[4].text
(parameter parsing)
separates arguments on whitespace
cmd.inputs = { 'dummy', 'dummy' }
command.register cmd
command.run "#{cmd.name} first second"
assert.spy(cmd.handler).was.called_with('first', 'second')
command.run "#{cmd.name} tab1\ttab2"
assert.spy(cmd.handler).was.called_with('tab1', 'tab2')
cmd.inputs = { 'dummy', '*dummy' }
command.register cmd
command.run "#{cmd.name} first second / third"
assert.spy(cmd.handler).was.called_with('first', 'second / third')
cmd.inputs = { '*dummy' }
command.register cmd
command.run "#{cmd.name} first second / third"
assert.spy(cmd.handler).was.called_with('first second / third')
input = value_for: -> 'yay'
cmd.inputs = { -> input }
command.register cmd
command.run "#{cmd.name} arg"
assert.spy(cmd.handler).was.called_with('yay')
cmd.inputs = { '*dummy' }
command.register cmd
command.run "#{cmd.name} first second / third"
assert.spy(cmd.handler).was.called_with('first second / third')
cmd.inputs = { '*dummy', 'dummy' }
assert.raises 'Wildcard', -> command.register cmd
(interacting with readline)
local input, readline, handler, co, return_value
run = (...) ->
f = coroutine.wrap (...) -> command.run ...
return_value = f ...
fake_return = (...) ->
coroutine.resume co, ...
before_each ->
readline = read: (prompt, i) =>
co = coroutine.running!
input = i
@prompt = prompt
@text = ''
coroutine.yield!
app.window = :readline
handler = spy.new -> nil
command.register {
name: 'p_cmd',
description: 'desc',
:handler
inputs: { 'test_first', 'test_second' }
}
after_each -> command.unregister name for name in *command.names!
(.. when entering a command)
should_complete(..) returns false
run!
assert.is_false input\should_complete '', readline
complete(..) returns a list of command names, key bindings, and descriptions
keymap.ctrl_shift_p = 'p_cmd'
run!
completions = input\complete '', readline
assert.same completions, { { 'p_cmd', 'ctrl_shift_p', 'desc' } }
(.. when entering command arguments)
run!
input\update 'p_cmd first', readline
assert.not_nil first_input
assert.equal input\complete(readline.text, readline), first_input.complete!
assert.equal input\should_complete(readline.text, readline), first_input.should_complete!
assert.equal input\close_on_cancel(readline.text, readline), first_input.close_on_cancel!
input\on_completed(readline.text, readline)
assert.is_true first_input.on_completed.called
input\go_back(readline)
assert.is_true first_input.go_back.called
readline.text ..= ' second'
input\update readline.text, readline
assert.not_nil second_input
assert.equal input\complete(readline.text, readline), second_input.complete!
assert.equal input\should_complete(readline.text, readline), second_input.should_complete!
assert.equal input\close_on_cancel(readline.text, readline), second_input.close_on_cancel!
input\on_completed(readline.text, readline)
assert.is_true second_input.on_completed.called
input\go_back(readline)
assert.is_true second_input.go_back.called
run!
input\update 'p_cmd first', readline
fake_return nil
assert.is_true first_input.on_cancelled.called
assert.is_false second_input.on_cancelled.called
updates the readline prompt to include arguments when they are finished
run!
input\update 'p_cmd first', readline
assert.match readline.prompt, 'p_cmd $'
readline.text ..= ' second'
input\update readline.text, readline
assert.match readline.prompt, 'p_cmd first $'
runs the command when the user submits
run!
readline.text = 'p_cmd first final'
input\update readline.text, readline
readline.text = 'final'
fake_return 'final'
assert.spy(handler).was_called_with first_input\value_for!, 'final'
(.. with a wildcard input)
before_each ->
cmd.inputs = { '*dummy' }
command.register cmd
run!
does not update the readline prompt to include partial arguments
input\update "#{cmd.name} first second third", readline
assert.match readline.prompt, "#{cmd.name} $"
handles multiple updates when typing
readline.text = "#{cmd.name} first second third"
input\update readline.text, readline
input\update readline.text, readline
assert.match readline.prompt, "#{cmd.name} $"
fake_return readline.text
assert.spy(cmd.handler).was_called_with readline.text
(.. when the user submits an unknown command)
the on_submit callback returns false to keep readline open
run!
assert.is_false input\on_submit 'unknowncommand', readline
(.. when the user submits a known command but with too few arguments)
the on_submit callback returns false to keep readline open
run!
assert.is_false input\on_submit 'p_cmd', readline
the callback adds command to the readline prompt
run!
readline.text = 'p_cmd'
input\update readline.text, readline
input\on_submit 'p_cmd', readline
assert.match readline.prompt, 'p_cmd $'