= 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.

=== Shell 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.8.3
----

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"},"authOptions":{"provider":"shiro","config":{"properties_path":"file:auth.properties"}}}}' maven:io.vertx:vertx-shell:3.8.3
----

or

.Starting a shell service available via HTTP
[source,subs="+attributes"]
----
# create a certificate for the HTTP server
keytool -genkey -keyalg RSA -keystore keystore.jks -keysize 2048 -validity 1095 -dname CN=localhost -keypass secret -storepass secret
# create the auth config
echo user.admin=password > auth.properties
vertx run -conf '{"httpOptions":{"port":8080,"ssl":true,"keyStoreOptions":{"path":"keystore.jks","password":"secret"},"authOptions":{"provider":""shiro,"config":{"properties_path":"file:auth.properties"}}}}' maven:io.vertx:vertx-shell:3.8.3
----

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' => "src/test/resources/ssh.jks",
        'password' => "wibble"
      },
      'authOptions' => {
        'provider' => "shiro",
        'config' => {
          'properties_path' => "file:/path/to/my/auth.properties"
        }
      }
    }
  }
})

----

or

[source,ruby,subs="+attributes"]
----
vertx.deploy_verticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}", {
  'config' => {
    'httpOptions' => {
      'host' => "localhost",
      'port' => 8080,
      'ssl' => true,
      'keyPairOptions' => {
        'path' => "src/test/resources/server-keystore.jks",
        'password' => "wibble"
      },
      'authOptions' => {
        'provider' => "shiro",
        '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.8.3`

=== 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"
    },
    'authOptions' => {
      '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#TelnetTermOptions[TelnetTermOptions]` 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.

Starting a shell service available via HTTP:

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

----

== Authentication

The SSH and HTTP connectors provide both authentication built on top of _vertx-auth_ with the following supported
providers:

- _shiro_ : provides `.properties` and _LDAP_ backend as seen in the ShellService presentation
- _jdbc_ : JDBC backend
- _mongo_ : MongoDB backend

These options can be created directly using directly `link:../dataobjects.html#AuthOptions[AuthOptions]`:

- `link:../dataobjects.html#ShiroAuthOptions[ShiroAuthOptions]` for Shiro
- `link:../dataobjects.html#JDBCAuthOptions[JDBCAuthOptions]` for JDBC
- `link:../dataobjects.html#MongoAuthOptions[MongoAuthOptions]` for Mongo

As for external service configuration in Json, the `authOptions` uses the `provider` property to distinguish:

----
{
 ...
 "authOptions": {
   "provider":"shiro",
   "config": {
     "properties_path":"file:auth.properties"
   }
 }
 ...
}
----

== Telnet term configuration

Telnet terms are configured by `link:../dataobjects.html#ShellServiceOptions#set_telnet_options-instance_method[telnetOptions]`,
the `link:../dataobjects.html#TelnetTermOptions[TelnetTermOptions]` extends the `link:../dataobjects.html#NetServerOptions[NetServerOptions]` so they
have the exact same configuration.

== SSH term configuration

SSH terms are configured by `link:../dataobjects.html#ShellServiceOptions#set_ssh_options-instance_method[SSHOptions]`:

- `link:../dataobjects.html#SSHTermOptions#set_port-instance_method[port]`: port
- `link:../dataobjects.html#SSHTermOptions#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#SSHTermOptions#set_auth_options-instance_method[authOptions]`: configures user authentication

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

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


.Deploying the Shell Service on SSH with Mongo authentication
[source,ruby,subs="+attributes"]
----
vertx.deploy_verticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}", {
  'config' => {
    'sshOptions' => {
      'host' => "localhost",
      'port' => 5000,
      'keyPairOptions' => {
        'path' => "src/test/resources/ssh.jks",
        'password' => "wibble"
      },
      'authOptions' => {
        'provider' => "mongo",
        'config' => {
          'connection_string' => "mongodb://localhost:27018"
        }
      }
    }
  }
})

----

.Running the Shell Service on SSH with Mongo authentication
[source,ruby,subs="+attributes"]
----
require 'vertx-shell/shell_service'
service = VertxShell::ShellService.create(vertx, {
  'sSHOptions' => {
    'host' => "localhost",
    'port' => 5000,
    'keyPairOptions' => {
      'path' => "server-keystore.jks",
      'password' => "wibble"
    },
    'authOptions' => {
      'config' => {
        'connection_string' => "mongodb://localhost:27018"
      }
    }
  }
})
service.start()

----

.Deploying the Shell Service on SSH with JDBC authentication
[source,ruby,subs="+attributes"]
----
vertx.deploy_verticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}", {
  'config' => {
    'sshOptions' => {
      'host' => "localhost",
      'port' => 5000,
      'keyPairOptions' => {
        'path' => "src/test/resources/ssh.jks",
        'password' => "wibble"
      },
      'authOptions' => {
        'provider' => "jdbc",
        'config' => {
          'url' => "jdbc:hsqldb:mem:test?shutdown=true",
          'driver_class' => "org.hsqldb.jdbcDriver"
        }
      }
    }
  }
})

----

.Running the Shell Service on SSH with JDBC authentication
[source,ruby,subs="+attributes"]
----
require 'vertx-shell/shell_service'
service = VertxShell::ShellService.create(vertx, {
  'sSHOptions' => {
    'host' => "localhost",
    'port' => 5000,
    'keyPairOptions' => {
      'path' => "server-keystore.jks",
      'password' => "wibble"
    },
    'authOptions' => {
      'config' => {
        'url' => "jdbc:hsqldb:mem:test?shutdown=true",
        'driver_class' => "org.hsqldb.jdbcDriver"
      }
    }
  }
})
service.start()

----

== HTTP term configuration

HTTP terms are configured by `link:../dataobjects.html#ShellServiceOptions#set_http_options-instance_method[httpOptions]`, the http options
extends the `link:../dataobjects.html#HttpServerOptions[HttpServerOptions]` so they expose the exact same configuration.

In addition there are extra options for configuring an HTTP term:

- `link:../dataobjects.html#HttpTermOptions#set_auth_options-instance_method[authOptions]`: configures user authentication
- `link:../dataobjects.html#HttpTermOptions#set_sock_js_handler_options-instance_method[sockJSHandlerOptions]`: configures SockJS
- `link:../dataobjects.html#HttpTermOptions#set_sock_js_path-instance_method[sockJSPath]`: the SockJS path in the router

.Deploying the Shell Service on HTTP with Mongo authentication
[source,ruby,subs="+attributes"]
----
vertx.deploy_verticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}", {
  'config' => {
    'httpOptions' => {
      'host' => "localhost",
      'port' => 8080,
      'ssl' => true,
      'keyPairOptions' => {
        'path' => "src/test/resources/server-keystore.jks",
        'password' => "wibble"
      },
      'authOptions' => {
        'provider' => "mongo",
        'config' => {
          'connection_string' => "mongodb://localhost:27018"
        }
      }
    }
  }
})

----

.Running the Shell Service on HTTP with Mongo authentication
[source,ruby,subs="+attributes"]
----
require 'vertx-shell/shell_service'
service = VertxShell::ShellService.create(vertx, {
  'httpOptions' => {
    'host' => "localhost",
    'port' => 8080,
    'authOptions' => {
      'config' => {
        'connection_string' => "mongodb://localhost:27018"
      }
    }
  }
})
service.start()

----

.Deploying the Shell Service on HTTP with JDBC authentication
[source,ruby,subs="+attributes"]
----
vertx.deploy_verticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}", {
  'config' => {
    'httpOptions' => {
      'host' => "localhost",
      'port' => 8080,
      'ssl' => true,
      'keyPairOptions' => {
        'path' => "src/test/resources/server-keystore.jks",
        'password' => "wibble"
      },
      'authOptions' => {
        'provider' => "jdbc",
        'config' => {
          'url' => "jdbc:hsqldb:mem:test?shutdown=true",
          'driver_class' => "org.hsqldb.jdbcDriver"
        }
      }
    }
  }
})

----

.Running the Shell Service on HTTP with JDBC authentication
[source,ruby,subs="+attributes"]
----
require 'vertx-shell/shell_service'
service = VertxShell::ShellService.create(vertx, {
  'httpOptions' => {
    'host' => "localhost",
    'port' => 8080,
    'authOptions' => {
      'config' => {
        'url' => "jdbc:hsqldb:mem:test?shutdown=true",
        'driver_class' => "org.hsqldb.jdbcDriver"
      }
    }
  }
})
service.start()

----

== Keymap configuration

The shell uses a default keymap configuration that can be overriden using the `inputrc` property of the various
term configuration object:

- `link:../dataobjects.html#TelnetTermOptions#set_intputrc-instance_method[intputrc]`
- `link:../dataobjects.html#SSHTermOptions#set_intputrc-instance_method[intputrc]`
- `link:../dataobjects.html#HttpTermOptions#set_intputrc-instance_method[intputrc]`

The `inputrc` must point to a file available via the classloader or the filesystem.

The `inputrc` only function bindings and the available functions are:

- _backward-char_
- _forward-char_
- _next-history_
- _previous-history_
- _backward-delete-char_
- _backward-delete-char_
- _backward-word_
- _end-of-line_
- _beginning-of-line_
- _delete-char_
- _delete-char_
- _complete_
- _accept-line_
- _accept-line_
- _kill-line_
- _backward-word_
- _forward-word_
- _backward-kill-word_

NOTE: Extra functions can be added, however this is done by implementing functions of the `Term.d` project on which
Vert.x Shell is based, for instance the https://github.com/termd/termd/blob/c1629623c8a3add4bde7778640bf8cc233a7c98f/src/examples/java/examples/readlinefunction/ReverseFunction.java[reverse function]
can be implemented and then declared in a `META-INF/services/io.termd.core.readline.Function` to be loaded by the shell.

== 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: deploys a verticle with deployment options as JSON string
.. 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
. 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. Other Vert.x project may provide commands to extend
Vert.x Shell, for instance Dropwizard Metrics.

== 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_shared(vertx)
registry.register_command(builder.build(vertx))

----

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.

A command is registered until it is unregistered with the `link:../../yardoc/VertxShell/CommandRegistry.html#unregister_command-instance_method[unregisterCommand]`.
When a command is registered from a Verticle, this command is unregistered when this verticle is undeployed.

NOTE: Command callbacks are invoked in the {@literal 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:../../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|
  # ...
}

----

When the command executes the `link:../../yardoc/VertxShell/CommandProcess.html[process]` is provided for interacting
with the shell. A `link:../../yardoc/VertxShell/CommandProcess.html[CommandProcess]` extends `link:../../yardoc/VertxShell/Tty.html[Tty]`
which is used for interacting with the terminal.

=== Terminal usage

==== terminal I/O

The `link:../../yardoc/VertxShell/Tty.html#stdin_handler-instance_method[stdinHandler]` handler is used to be notified when the terminal
receives data, e.g the user uses his keyboard:

[source,ruby]
----
tty.stdin_handler() { |data|
  puts "Received #{data}"
}

----

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

[source,ruby]
----
tty.write("Hello World")

----

==== 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]
----
tty.write("Current terminal size: (#{tty.width()}, #{tty.height()})")

----

==== Resize event

When the size of the terminal changes the `link:../../yardoc/VertxShell/Tty.html#resizehandler-instance_method[resizehandler]`
is called, 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]`.

[source,ruby]
----
tty.resizehandler() { |v|
  puts "terminal resized : #{tty.width()} #{tty.height()}"
}

----

==== Terminal type

The terminal type is useful for sending escape codes to the remote terminal: `link:../../yardoc/VertxShell/Tty.html#type-instance_method[type]`
returns the current terminal type, it can be null if the terminal has not advertised the value.

[source,ruby]
----
puts "terminal type : #{tty.type()}"

----

=== 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/CommandProcess.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 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.

==== Interrupt event

The `link:../../yardoc/VertxShell/CommandProcess.html#interrupt_handler-instance_method[interruptHandler]` is called 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.interrupt_handler() { |v|
    vertx.cancel_timer?(periodicId)
    process.end()
  }
}

----

When no interrupt 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.

==== Suspend/resume events

The `link:../../yardoc/VertxShell/CommandProcess.html#suspend_handler-instance_method[suspendHandler]` is called when the process
is running and the user press _Ctrl+Z_, the command is _suspended_:

- the command can receive the suspend 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 command can receive interrupts event or end events

The `link:../../yardoc/VertxShell/CommandProcess.html#resume_handler-instance_method[resumeHandler]` is called when the process
is resumed, usually when the user types _fg_:

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

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

  # Command is suspended
  process.suspend_handler() { |v|
    puts "Suspended"
  }

  # Command is resumed
  process.resume_handler() { |v|
    puts "Resumed"
  }
}

----

==== End events

The `link:../../yardoc/VertxShell/CommandProcess.html#end_handler-instance_method[endHandler]` (io.vertx.core.Handler)} is
called when the process is running or suspended and the command terminates, for instance the shell session is closed,
the command is _terminated_.

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

  # Command terminates
  process.end_handler() { |v|
    puts "Terminated"
  }
}

----

The end handler is called even when the command invokes `link:../../yardoc/VertxShell/CommandProcess.html#end-instance_method[end]`.

This handler is useful for cleaning up resources upon command termination, for instance closing a client or a timer.

=== 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]`.

== Shell server

The Shell service is a convenient facade for starting a preconfigured shell either programmatically or as a Vert.x service.
When more flexibility is needed, a `link:../../yardoc/VertxShell/ShellServer.html[ShellServer]` can be used instead of the service.

For instance the shell http term can be configured to use an existing router instead of starting its own http server.

Using a shell server requires explicit configuration but provides full flexiblity, a shell server is setup in a few
steps:

[source,ruby]
----
require 'vertx-shell/shell_server'
require 'vertx-web/router'
require 'vertx-shell/term_server'
require 'vertx-shell/command_resolver'

server = VertxShell::ShellServer.create(vertx)

shellRouter = VertxWeb::Router.router(vertx)
router.mount_sub_router("/shell", shellRouter)
httpTermServer = VertxShell::TermServer.create_http_term_server(vertx, router)

sshTermServer = VertxShell::TermServer.create_ssh_term_server(vertx)

server.register_term_server(httpTermServer)
server.register_term_server(sshTermServer)

server.register_command_resolver(VertxShell::CommandResolver.base_commands(vertx))

server.listen()

----
<1> create a the shell server
<2> create an HTTP term server mounted on an existing router
<3> create an SSH term server
<4> register term servers
<5> register all base commands
<6> finally start the shell server

Besides, the shell server can also be used for creating in process shell session: it provides a programmatic interactive shell.

In process shell session can be created with `link:../../yardoc/VertxShell/ShellServer.html#create_shell-instance_method[createShell]`:

[source,ruby]
----

# Create a shell ession
shell = shellServer.create_shell()


----

The main use case is running or testing a command:

[source,ruby]
----
require 'vertx-shell/pty'

# Create a shell
shell = shellServer.create_shell()

# Create a job fo the command
job = shell.create_job("my-command 1234")

# Create a pseudo terminal
pty = VertxShell::Pty.create()
pty.stdout_handler() { |data|
  puts "Command wrote #{data}"
}

# Run the command
job.set_tty(pty.slave())
job.status_update_handler() { |status|
  puts "Command terminated with status #{status}"
}

----

The `link:../../yardoc/VertxShell/Pty.html[Pty]` pseudo terminal is the main interface for interacting with the command
when it's running:

- uses standard input/output for writing or reading strings
- resize the terminal

The `link:../../yardoc/VertxShell/JobController.html#close-instance_method[close]` closes the shell, it will terminate all jobs in the current shell
session.

== Terminal servers

Vert.x Shell also provides bare terminal servers for those who need to write pure terminal applications.

A `link:../../yardoc/VertxShell/Term.html[Term]` handler must be set on a term server before starting it. This handler will
handle each term when the user connects.

An `link:../dataobjects.html#AuthOptions[AuthOptions]` can be set on `link:../dataobjects.html#SSHTermOptions[SSHTermOptions]` and `link:../dataobjects.html#HttpTermOptions[HttpTermOptions]`.
Alternatively, an `link:../../yardoc/VertxAuthCommon/AuthProvider.html[AuthProvider]` can be `link:../../yardoc/VertxShell/TermServer.html#auth_provider-instance_method[set]`
directly on the term server before starting it.

=== SSH term

The terminal server `link:../../yardoc/VertxShell/Term.html[Term]` handler accepts incoming terminal connections.
When a remote terminal connects, the `link:../../yardoc/VertxShell/Term.html[Term]` can be used to interact with connected
terminal.

[source,ruby]
----
require 'vertx-shell/term_server'
server = VertxShell::TermServer.create_ssh_term_server(vertx, {
  'port' => 5000,
  'host' => "localhost"
})
server.term_handler() { |term|
  term.stdin_handler() { |line|
    term.write(line)
  }
}
server.listen()

----

The `link:../../yardoc/VertxShell/Term.html[Term]` is also a `link:../../yardoc/VertxShell/Tty.html[Tty]`, this section explains
how to use the tty.

=== Telnet term

[source,ruby]
----
require 'vertx-shell/term_server'
server = VertxShell::TermServer.create_telnet_term_server(vertx, {
  'port' => 5000,
  'host' => "localhost"
})
server.term_handler() { |term|
  term.stdin_handler() { |line|
    term.write(line)
  }
}
server.listen()

----

=== HTTP term

The `link:../../yardoc/VertxShell/TermServer.html#create_http_term_server-class_method[TermServer.createHttpTermServer]` method creates an HTTP term server, built
on top of Vert.x Web using the SockJS protocol.

[source,ruby]
----
require 'vertx-shell/term_server'
server = VertxShell::TermServer.create_http_term_server(vertx, {
  'port' => 5000,
  'host' => "localhost"
})
server.term_handler() { |term|
  term.stdin_handler() { |line|
    term.write(line)
  }
}
server.listen()

----

An HTTP term can start its own HTTP server, or it can reuse an existing Vert.x Web `link:../../yardoc/VertxWeb/Router.html[Router]`.

The shell can be found at `/shell.html`.

[source,ruby]
----
require 'vertx-shell/term_server'
server = VertxShell::TermServer.create_http_term_server(vertx, router, {
  'port' => 5000,
  'host' => "localhost"
})
server.term_handler() { |term|
  term.stdin_handler() { |line|
    term.write(line)
  }
}
server.listen()

----

The later option is convenient when the HTTP shell is integrated in an existing HTTP server.

The HTTP term server by default is configured for serving:

- the `shell.html` page
- the `https://github.com/chjj/term.js/[term.js]` client library
- the `vertxshell.js` client library

The `vertxshell.js` integrates `term.js` is the client side part of the HTTP term.

It integrates `term.js` with SockJS and needs the URL of the HTTP term server endpoint:

[source,javascript]
----
window.addEventListener('load', function () {
 var url = 'http://localhost/shell';
 new VertxTerm(url, {
   cols: 80,
   rows: 24
  });
});
----

Straight websockets can also be used, if so, the remote term URL should be suffixed with `/websocket`:

[source,javascript]
----
window.addEventListener('load', function () {
 var url = 'ws://localhost/shell/websocket';
 new VertxTerm(url, {
   cols: 80,
   rows: 24
  });
});
----

For customization purpose these resources can be copied and customized, they are available in the Vert.x Shell
jar under the `io.vertx.ext.shell` packages.

== Command discovery

The command discovery can be used when new commands need to be added to Vert.x without an explicit registration.

For example, the _Dropwizard_ metrics service, adds specific metrics command to the shell service on the fly.

It can be achieved via the `java.util.ServiceLoader` of a `link:unavailable[CommandResolverFactory]`.

[source,java]
----
public class CustomCommands implements CommandResolverFactory {

 public void resolver(Vertx vertx, Handler<AsyncResult<CommandResolver>> resolverHandler) {
   resolverHandler.handler(() -> Arrays.asList(myCommand1, myCommand2));
 }
}
----

The `resolver` method is async, because the resolver may need to wait some condition before commands
are resolved.

The shell service discovery using the service loader mechanism:

.The service provider file `META-INF/services/io.vertx.ext.shell.spi.CommandResolverFactory`
[source]
----
my.CustomCommands
----

This is only valid for the `link:../../yardoc/VertxShell/ShellService.html[ShellService]`. `link:../../yardoc/VertxShell/ShellServer.html[ShellServer]`
don't use this mechanism.

== Command pack

A command pack is a jar that provides new Vert.x Shell commands.

Such jar just need to be present on the classpath and it is discovered by Vertx. Shell.

[source,java]
----
public class CommandPackExample implements CommandResolverFactory {

  @Override
  public void resolver(Vertx vertx, Handler<AsyncResult<CommandResolver>> resolveHandler) {
    List<Command> commands = new ArrayList<>();

    // Add commands
    commands.add(Command.create(vertx, JavaCommandExample.class));

    // Add another command
    commands.add(CommandBuilder.command("another-command").processHandler(process -> {
      // Handle process
    }).build(vertx));

    // Resolve with the commands
    resolveHandler.handle(Future.succeededFuture(() -> commands));
  }
}
----

The command pack uses command discovery mechanism, so it needs the descriptor:

.`META-INF/services/io.vertx.ext.shell.spi.CommandResolverFactory` descriptor
[source]
----
examples.pack.CommandPackExample
----