= Vert.x Shell

Vert.x Shell is a command line interface for the Vert.x runtime available from regular
terminals using different protocols.

Vert.x Shell provides a variety of commands for interacting live with Vert.x services.

Vert.x Shell can be extended with custom commands in any language supported by Vert.x

== Using Vert.x Shell

Vert.x Shell is a Vert.x Service and can be started programmatically via the `link:yardoc/VertxShell/ShellService.html[ShellService]`
or deployed as a service.

=== Deployed service

The shell can be started as a service directly either from the command line or as a the Vert.x deployment:

.Starting a shell service available via Telnet
[source,subs="+attributes"]
----
vertx run -conf '{"telnetOptions":{"port":5000}}' maven:io.vertx:vertx-shell:3.1.0
----

or

.Starting a shell service available via SSH
[source,subs="+attributes"]
----
# create a key pair for the SSH server
keytool -genkey -keyalg RSA -keystore ssh.jks -keysize 2048 -validity 1095 -dname CN=localhost -keypass secret -storepass secret
# create the auth config
echo user.admin=password > auth.properties
# start the shell
vertx run -conf '{"sshOptions":{"port":4000,"keyPairOptions":{"path":"ssh.jks","password":"secret"},"shiroAuthOptions":{"config":{"properties_path":"file:auth.properties"}}}}' maven:io.vertx:vertx-shell:3.1.0
----

You can also deploy this service inside your own verticle:

[source,ruby,subs="+attributes"]
----
vertx.deploy_verticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}", {
  'config' => {
    'telnetOptions' => {
      'host' => "localhost",
      'port' => 4000
    }
  }
})

----

or

[source,ruby,subs="+attributes"]
----
vertx.deploy_verticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}", {
  'config' => {
    'sshOptions' => {
      'host' => "localhost",
      'port' => 5000,
      'keyPairOptions' => {
        'path' => "server-keystore.jks",
        'password' => "wibble"
      },
      'config' => {
        'properties_path' => "file:/path/to/my/auth.properties"
      }
    }
  }
})

----

NOTE: when Vert.x Shell is already on your classpath you can use `service:io.vertx.ext.shell` instead
or `maven:io.vertx:vertx-shell:3.1.0`

=== Programmatic service

The `link:yardoc/VertxShell/ShellService.html[ShellService]` takes care of starting an instance of Vert.x Shell.

Starting a shell service available via SSH:

[source,ruby]
----
require 'vertx-shell/shell_service'
service = VertxShell::ShellService.create(vertx, {
  'sSHOptions' => {
    'host' => "localhost",
    'port' => 5000,
    'keyPairOptions' => {
      'path' => "server-keystore.jks",
      'password' => "wibble"
    },
    'shiroAuthOptions' => {
      'type' => :PROPERTIES,
      'config' => {
        'properties_path' => "file:/path/to/my/auth.properties"
      }
    }
  }
})
service.start()

----

Starting a shell service available via Telnet:

[source,ruby]
----
require 'vertx-shell/shell_service'
service = VertxShell::ShellService.create(vertx, {
  'telnetOptions' => {
    'host' => "localhost",
    'port' => 4000
  }
})
service.start()

----

The `link:../dataobjects.html#TelnetOptions[TelnetOptions]` extends the Vert.x Core `NetServerOptions` as the Telnet server
implementation is based on a `NetServer`.

CAUTION: Telnet does not provide any authentication nor encryption at all.

== Telnet configuration

The telnet connector is configured by `link:../dataobjects.html#ShellServiceOptions#set_telnet_options-instance_method[telnetOptions]`,
the `link:../dataobjects.html#TelnetOptions[TelnetOptions]` extends the `link:../../vertx-core/dataobjects.html#NetServerOptions[NetServerOptions]` so they
have the exact same configuration.

== SSH configuration

The SSH connector is configured by `link:../dataobjects.html#ShellServiceOptions#set_ssh_options-instance_method[SSHOptions]`:

- `link:../dataobjects.html#SSHOptions#set_port-instance_method[port]`: port
- `link:../dataobjects.html#SSHOptions#set_host-instance_method[host]`: host

Only username/password authentication is supported at the moment, it can be configured with property file
or LDAP, see Vert.x Auth for more info:

- `link:../dataobjects.html#SSHOptions#set_shiro_auth_options-instance_method[shiroAuthOptions]`: configures user authentication

The server key configuration reuses the key pair store configuration scheme provided by _Vert.x Core_:

- `link:../dataobjects.html#SSHOptions#set_key_pair_options-instance_method[keyPairOptions]`: set `.jks` key pair store
- `link:../dataobjects.html#SSHOptions#set_pfx_key_pair_options-instance_method[pfxKeyPairOptions]`: set `.pfx` key pair store
- `link:../dataobjects.html#SSHOptions#set_pem_key_pair_options-instance_method[pemKeyPairOptions]`: set `.pem` key pair store

== Base commands

To find out the available commands you can use the _help_ builtin command:

. Verticle commands
.. verticle-ls: list all deployed verticles
.. verticle-undeploy: undeploy a verticle
.. verticle-deploy: deployes a verticle
.. verticle-factories: list all known verticle factories
. File system commands
.. ls
.. cd
.. pwd
. Bus commands
.. bus-tail: display all incoming messages on an event bus address
.. bus-send: send a message on the event bus
. Net commands
.. net-ls: list all available net servers, including HTTP servers
. Shared data commands
.. local-map-put
.. local-map-get
.. local-map-rm
. Metrics commands (requires Dropwizard metrics setup)
.. metrics-ls: show all available metrics
.. metrics-info: show particular metrics
. Various commands
.. echo
.. sleep
.. help
.. exit
.. logout
. Job control
.. fg
.. bg
.. jobs

NOTE: this command list should evolve in next releases of Vert.x Shell

== Extending Vert.x Shell

Vert.x Shell can be extended with custom commands in any of the languages supporting code generation.

A command is created by the `link:yardoc/VertxShell/CommandBuilder.html#command-class_method[CommandBuilder.command]` method: the command process handler is called
by the shell when the command is executed, this handler can be set with the `link:yardoc/VertxShell/CommandBuilder.html#process_handler-instance_method[processHandler]`
method:

[source,ruby]
----
require 'vertx-shell/command_builder'
require 'vertx-shell/command_registry'

builder = VertxShell::CommandBuilder.command("my-command")
builder.process_handler() { |process|

  # Write a message to the console
  process.write("Hello World")

  # End the process
  process.end()
}

# Register the command
registry = VertxShell::CommandRegistry.get(vertx)
registry.register_command(builder.build())

----

After a command is created, it needs to be registed to a `link:yardoc/VertxShell/CommandRegistry.html[CommandRegistry]`. The
command registry holds all the commands for a Vert.x instance.

NOTE: Command callbacks are invoked in the `io.vertx.core.Context` when the command is registered in the
registry. Keep this in mind if you maintain state in a command.

The `link:yardoc/VertxShell/CommandProcess.html[CommandProcess]` object can be used for interacting with the shell.

=== Command arguments

The `link:yardoc/VertxShell/CommandProcess.html#args-instance_method[args]` returns the command arguments:

[source,ruby]
----
command.process_handler() { |process|

  process.args().each do |arg|
    # Print each argument on the console
    process.write("Argument #{arg}")
  end

  process.end()
}

----

Besides it is also possible to create commands using `link:../../vertx-core/ruby/yardoc/Vertx/CLI.html[Vert.x CLI]`: it makes easier to
write command line argument parsing:

- _option_ and _argument_ parsing
- argument _validation_
- generation of the command _usage_

[source,ruby]
----
require 'vertx/cli'
require 'vertx-shell/command_builder'
cli = Vertx::CLI.create("my-command").add_argument({
  'argName' => "my-arg"
}).add_option({
  'shortName' => "m",
  'longName' => "my-option"
})
command = VertxShell::CommandBuilder.command(cli)
command.process_handler() { |process|

  commandLine = process.command_line()

  argValue = commandLine.get_argument_value(0)
  optValue = commandLine.get_option_value("my-option")
  process.write("The argument is #{argValue} and the option is #{optValue}")

  process.end()
}

----

When an option named _help_ is added to the CLI object, the shell will take care of generating the command usage
when the option is activated:

[source,ruby]
----
require 'vertx/cli'
require 'vertx-shell/command_builder'
cli = Vertx::CLI.create("my-command").add_argument({
  'argName' => "my-arg"
}).add_option({
  'argName' => "help",
  'shortName' => "h",
  'longName' => "help"
})
command = VertxShell::CommandBuilder.command(cli)
command.process_handler() { |process|
  # ...
}

----

=== Terminal size

The current terminal size can be obtained using `link:yardoc/VertxShell/Tty.html#width-instance_method[width]` and
`link:yardoc/VertxShell/Tty.html#height-instance_method[height]`.

[source,ruby]
----
command.process_handler() { |process|
  process.write("Current terminal size: (#{process.width()}, #{process.height()})").end()
}

----

=== Shell session

The shell is a connected service that naturally maintains a session with the client, this session can be
used in commands to scope data. A command can get the session with `link:yardoc/VertxShell/ProcessContext.html#session-instance_method[session]`:

[source,ruby]
----
command.process_handler() { |process|

  session = process.session()

  if (session.get("my_key") == nil)
    session.put("my key", "my value")
  end

  process.end()
}

----

=== Process I/O

A command can set a `link:yardoc/VertxShell/Tty.html#set_stdin-instance_method[setStdin]` handler
to be notified when the shell receives data, e.g the user uses his keyboard:

[source,ruby]
----
command.process_handler() { |process|
  process.set_stdin() { |data|
    puts "Received #{data}"
  }
}

----

A command can use the `link:yardoc/VertxShell/Tty.html#stdout-instance_method[stdout]` to write to the standard output.

[source,ruby]
----
command.process_handler() { |process|
  process.stdout().write("Hello World")
  process.end()
}

----

Or it can use the `link:yardoc/VertxShell/CommandProcess.html#write-instance_method[write]` method:

[source,ruby]
----
command.process_handler() { |process|
  process.write("Hello World")
  process.end()
}

----

=== Process termination

Calling `link:yardoc/VertxShell/CommandProcess.html#end-instance_method[end]` ends the current process. It can be called directly
in the invocation of the command handler or any time later:

[source,ruby]
----
command.process_handler() { |process|
  vertx = process.vertx()

  # Set a timer
  vertx.set_timer(1000) { |id|

    # End the command when the timer is fired
    process.end()
  }
}

----

=== Process events

A command can subscribe to a few process events, named after the posix signals.

==== `SIGINT` event

The `link:todo[SIGINT]` event is fired when the process is interrupted, this event is fired when the user press
_Ctrl+C_ during the execution of a command. This handler can be used for interrupting commands _blocking_ the CLI and
gracefully ending the command process:

[source,ruby]
----
command.process_handler() { |process|
  vertx = process.vertx()

  # Every second print a message on the console
  periodicId = vertx.set_periodic(1000) { |id|
    process.write("tick\n")
  }

  # When user press Ctrl+C: cancel the timer and end the process
  process.event_handler(:SIGINT) { |event|
    vertx.cancel_timer?(periodicId)
    process.end()
  }
}

----

When no `SIGINT` handler is registered, pressing _Ctrl+C_ will have no effect on the current process and the event
will be delayed and will likely be handled by the shell, like printing a new line on the console.

==== `SIGTSTP`/`SIGCONT` events

The `link:todo[SIGTSTP]` event is fired when the process is running and the user press _Ctrl+Z_: the command
is _suspended_:

- the command can receive the `SIGTSTP` event when it has registered an handler for this event
- the command will not receive anymore data from the standard input
- the shell prompt the user for input

The `link:todo[SIGCONT]` event is fired when the process is resumed, usually when the user types _fg_:

- the command can receive the `SIGCONT` event when it has registered an handler for this event
- the command will receive anymore data from the standard input when it has registered an stdin handler

[source,ruby]
----
command.process_handler() { |process|

  # Command is suspended
  process.event_handler(:SIGTSTP) { |event|
    puts "Suspended"
  }

  # Command is resumed
  process.event_handler(:SIGCONT) { |event|
    puts "Resumed"
  }
}

----

==== `SIGWINCH` event

The `link:todo[SIGWINCH]` event is fired when the size of the terminal changes, the new terminal size can be obtained
with `link:yardoc/VertxShell/Tty.html#width-instance_method[width]` and `link:yardoc/VertxShell/Tty.html#height-instance_method[height]`.

=== Command completion

A command can provide a completion handler when it wants to provide contextual command line interface completion.

Like the process handler, the `link:yardoc/VertxShell/CommandBuilder.html#completion_handler-instance_method[completion
handler]` is non blocking because the implementation may use Vert.x services, e.g the file system.

The `link:yardoc/VertxShell/Completion.html#line_tokens-instance_method[lineTokens]` returns a list of `link:yardoc/VertxShell/CliToken.html[tokens]`
from the beginning of the line to the cursor position. The list can be empty if the cursor when the cursor is at the
beginning of the line.

The `link:yardoc/VertxShell/Completion.html#raw_line-instance_method[rawLine]` returns the current completed from the beginning
of the line to the cursor position, in raw format, i.e without any char escape performed.

Completion ends with a call to `link:yardoc/VertxShell/Completion.html#complete-instance_method[complete]`.