/*
* Copyright 2015 Red Hat, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*
*
* Copyright (c) 2015 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*
*/
/**
* = 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 io.vertx.ext.shell.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:${maven.groupId}:${maven.artifactId}:${maven.version}
* ----
*
* 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:${maven.groupId}:${maven.artifactId}:${maven.version}
* ----
*
* 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:${maven.groupId}:${maven.artifactId}:${maven.version}
* ----
*
* You can also deploy this service inside your own verticle:
*
* [source,$lang,subs="+attributes"]
* ----
* {@link examples.Examples#deployTelnetService(io.vertx.core.Vertx)}
* ----
*
* or
*
* [source,$lang,subs="+attributes"]
* ----
* {@link examples.Examples#deploySSHServiceWithShiro(io.vertx.core.Vertx)}
* ----
*
* or
*
* [source,$lang,subs="+attributes"]
* ----
* {@link examples.Examples#deployHttpServiceWithShiro(io.vertx.core.Vertx)}
* ----
*
* NOTE: when Vert.x Shell is already on your classpath you can use `service:io.vertx.ext.shell` instead
* or `maven:${maven.groupId}:${maven.artifactId}:${maven.version}`
*
* === Programmatic service
*
* The {@link io.vertx.ext.shell.ShellService} takes care of starting an instance of Vert.x Shell.
*
* Starting a shell service available via SSH:
*
* [source,$lang]
* ----
* {@link examples.Examples#runSSHServiceWithShiro(io.vertx.core.Vertx)}
* ----
*
* Starting a shell service available via Telnet:
*
* [source,$lang]
* ----
* {@link examples.Examples#runTelnetService}
* ----
*
* The {@link io.vertx.ext.shell.term.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,$lang]
* ----
* {@link examples.Examples#runHttpService}
* ----
*
* == 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 io.vertx.ext.auth.AuthOptions}:
*
* - {@link io.vertx.ext.auth.shiro.ShiroAuthOptions} for Shiro
* - {@link io.vertx.ext.auth.jdbc.JDBCAuthOptions} for JDBC
* - {@link io.vertx.ext.auth.mongo.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 io.vertx.ext.shell.ShellServiceOptions#setTelnetOptions},
* the {@link io.vertx.ext.shell.term.TelnetTermOptions} extends the {@link io.vertx.core.net.NetServerOptions} so they
* have the exact same configuration.
*
* == SSH term configuration
*
* SSH terms are configured by {@link io.vertx.ext.shell.ShellServiceOptions#setSSHOptions}:
*
* - {@link io.vertx.ext.shell.term.SSHTermOptions#setPort}: port
* - {@link io.vertx.ext.shell.term.SSHTermOptions#setHost}: 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 io.vertx.ext.shell.term.SSHTermOptions#setAuthOptions}: configures user authentication
*
* The server key configuration reuses the key pair store configuration scheme provided by _Vert.x Core_:
*
* - {@link io.vertx.ext.shell.term.SSHTermOptions#setKeyPairOptions}: set `.jks` key pair store
* - {@link io.vertx.ext.shell.term.SSHTermOptions#setPfxKeyPairOptions}: set `.pfx` key pair store
* - {@link io.vertx.ext.shell.term.SSHTermOptions#setPemKeyPairOptions}: set `.pem` key pair store
*
*
* .Deploying the Shell Service on SSH with Mongo authentication
* [source,$lang,subs="+attributes"]
* ----
* {@link examples.Examples#deploySSHServiceWithMongo(io.vertx.core.Vertx)}
* ----
*
* .Running the Shell Service on SSH with Mongo authentication
* [source,$lang,subs="+attributes"]
* ----
* {@link examples.Examples#runSSHServiceWithMongo(io.vertx.core.Vertx)}
* ----
*
* .Deploying the Shell Service on SSH with JDBC authentication
* [source,$lang,subs="+attributes"]
* ----
* {@link examples.Examples#deploySSHServiceWithJDBC(io.vertx.core.Vertx)}
* ----
*
* .Running the Shell Service on SSH with JDBC authentication
* [source,$lang,subs="+attributes"]
* ----
* {@link examples.Examples#runSSHServiceWithJDBC(io.vertx.core.Vertx)}
* ----
*
* == HTTP term configuration
*
* HTTP terms are configured by {@link io.vertx.ext.shell.ShellServiceOptions#setHttpOptions}, the http options
* extends the {@link io.vertx.core.http.HttpServerOptions} so they expose the exact same configuration.
*
* In addition there are extra options for configuring an HTTP term:
*
* - {@link io.vertx.ext.shell.term.HttpTermOptions#setAuthOptions}: configures user authentication
* - {@link io.vertx.ext.shell.term.HttpTermOptions#setSockJSHandlerOptions}: configures SockJS
* - {@link io.vertx.ext.shell.term.HttpTermOptions#setSockJSPath}: the SockJS path in the router
*
* .Deploying the Shell Service on HTTP with Mongo authentication
* [source,$lang,subs="+attributes"]
* ----
* {@link examples.Examples#deployHttpServiceWithMongo(io.vertx.core.Vertx)}
* ----
*
* .Running the Shell Service on HTTP with Mongo authentication
* [source,$lang,subs="+attributes"]
* ----
* {@link examples.Examples#runHTTPServiceWithMongo(io.vertx.core.Vertx)}
* ----
*
* .Deploying the Shell Service on HTTP with JDBC authentication
* [source,$lang,subs="+attributes"]
* ----
* {@link examples.Examples#deployHttpServiceWithJDBC(io.vertx.core.Vertx)}
* ----
*
* .Running the Shell Service on HTTP with JDBC authentication
* [source,$lang,subs="+attributes"]
* ----
* {@link examples.Examples#runHTTPServiceWithJDBC(io.vertx.core.Vertx)}
* ----
*
* == Keymap configuration
*
* The shell uses a default keymap configuration that can be overriden using the `inputrc` property of the various
* term configuration object:
*
* - {@link io.vertx.ext.shell.term.TelnetTermOptions#setIntputrc}
* - {@link io.vertx.ext.shell.term.SSHTermOptions#setIntputrc}
* - {@link io.vertx.ext.shell.term.HttpTermOptions#setIntputrc}
*
* 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 io.vertx.ext.shell.command.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 io.vertx.ext.shell.command.CommandBuilder#processHandler}
* method:
*
* [source,$lang]
* ----
* {@link examples.Examples#helloWorld}
* ----
*
* After a command is created, it needs to be registed to a {@link io.vertx.ext.shell.command.CommandRegistry}. The
* command registry holds all the commands for a Vert.x instance.
*
* A command is registered until it is unregistered with the {@link io.vertx.ext.shell.command.CommandRegistry#unregisterCommand(java.lang.String)}.
* 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 io.vertx.ext.shell.command.CommandProcess} object can be used for interacting with the shell.
*
* === Command arguments
*
* The {@link io.vertx.ext.shell.command.CommandProcess#args()} returns the command arguments:
*
* [source,$lang]
* ----
* {@link examples.Examples#commandArgs}
* ----
*
* Besides it is also possible to create commands using {@link io.vertx.core.cli.CLI Vert.x CLI}: it makes easier to
* write command line argument parsing:
*
* - _option_ and _argument_ parsing
* - argument _validation_
* - generation of the command _usage_
*
* [source,$lang]
* ----
* {@link examples.Examples#cliCommand()}
* ----
*
* 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,$lang]
* ----
* {@link examples.Examples#cliCommandWithHelp()}
* ----
*
* When the command executes the {@link io.vertx.ext.shell.command.CommandProcess process} is provided for interacting
* with the shell. A {@link io.vertx.ext.shell.command.CommandProcess} extends {@link io.vertx.ext.shell.term.Tty}
* which is used for interacting with the terminal.
*
* === Terminal usage
*
* ==== terminal I/O
*
* The {@link io.vertx.ext.shell.term.Tty#stdinHandler} handler is used to be notified when the terminal
* receives data, e.g the user uses his keyboard:
*
* [source,$lang]
* ----
* {@link examples.Examples#readStdin}
* ----
*
* A command can use the {@link io.vertx.ext.shell.term.Tty#write} to write to the standard output.
*
* [source,$lang]
* ----
* {@link examples.Examples#writeStdout}
* ----
*
* ==== Terminal size
*
* The current terminal size can be obtained using {@link io.vertx.ext.shell.term.Tty#width()} and
* {@link io.vertx.ext.shell.term.Tty#height()}.
*
* [source,$lang]
* ----
* {@link examples.Examples#terminalSize}
* ----
*
* ==== Resize event
*
* When the size of the terminal changes the {@link io.vertx.ext.shell.term.Tty#resizehandler(io.vertx.core.Handler)}
* is called, the new terminal size can be obtained with {@link io.vertx.ext.shell.term.Tty#width()} and
* {@link io.vertx.ext.shell.term.Tty#height()}.
*
* [source,$lang]
* ----
* {@link examples.Examples#resizeHandlerTerminal}
* ----
*
* ==== Terminal type
*
* The terminal type is useful for sending escape codes to the remote terminal: {@link io.vertx.ext.shell.term.Tty#type()}
* returns the current terminal type, it can be null if the terminal has not advertised the value.
*
* [source,$lang]
* ----
* {@link examples.Examples#terminalType}
* ----
*
* === 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 io.vertx.ext.shell.command.CommandProcess#session()}:
*
* [source,$lang]
* ----
* {@link examples.Examples#session}
* ----
*
* === Process termination
*
* Calling {@link io.vertx.ext.shell.command.CommandProcess#end()} ends the current process. It can be called directly
* in the invocation of the command handler or any time later:
*
* [source,$lang]
* ----
* {@link examples.Examples#asyncCommand}
* ----
*
* === Process events
*
* A command can subscribe to a few process events.
*
* ==== Interrupt event
*
* The {@link io.vertx.ext.shell.command.CommandProcess#interruptHandler(io.vertx.core.Handler)} 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,$lang]
* ----
* {@link examples.Examples#interruptHandler}
* ----
*
* 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 io.vertx.ext.shell.command.CommandProcess#suspendHandler(io.vertx.core.Handler)} 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 io.vertx.ext.shell.command.CommandProcess#resumeHandler(io.vertx.core.Handler)} 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,$lang]
* ----
* {@link examples.Examples#suspendResumeHandler}
* ----
*
* ==== End events
*
* The {@link io.vertx.ext.shell.command.CommandProcess#endHandler(io.vertx.core.Handler)} (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,$lang]
* ----
* {@link examples.Examples#endHandler}
* ----
*
* The end handler is called even when the command invokes {@link io.vertx.ext.shell.command.CommandProcess#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 io.vertx.ext.shell.command.CommandBuilder#completionHandler(io.vertx.core.Handler) completion
* handler} is non blocking because the implementation may use Vert.x services, e.g the file system.
*
* The {@link io.vertx.ext.shell.cli.Completion#lineTokens()} returns a list of {@link io.vertx.ext.shell.cli.CliToken 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 io.vertx.ext.shell.cli.Completion#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 io.vertx.ext.shell.cli.Completion#complete(java.util.List)}.
*
* == 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 io.vertx.ext.shell.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,$lang]
* ----
* {@link examples.Examples#shellServer}
* ----
* <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 io.vertx.ext.shell.ShellServer#createShell}:
*
* [source,$lang]
* ----
* {@link examples.Examples#creatingShell}
* ----
*
* The main use case is running or testing a command:
*
* [source,$lang]
* ----
* {@link examples.Examples#runningShellCommand}
* ----
*
* The {@link io.vertx.ext.shell.term.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 io.vertx.ext.shell.system.JobController#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 io.vertx.ext.shell.term.Term} handler must be set on a term server before starting it. This handler will
* handle each term when the user connects.
*
* An {@link io.vertx.ext.auth.AuthOptions} can be set on {@link io.vertx.ext.shell.term.SSHTermOptions} and {@link io.vertx.ext.shell.term.HttpTermOptions}.
* Alternatively, an {@link io.vertx.ext.auth.AuthProvider} can be {@link io.vertx.ext.shell.term.TermServer#authProvider(io.vertx.ext.auth.AuthProvider) set}
* directly on the term server before starting it.
*
* === SSH term
*
* The terminal server {@link io.vertx.ext.shell.term.Term} handler accepts incoming terminal connections.
* When a remote terminal connects, the {@link io.vertx.ext.shell.term.Term} can be used to interact with connected
* terminal.
*
* [source,$lang]
* ----
* {@link examples.Examples#sshEchoTerminal}
* ----
*
* The {@link io.vertx.ext.shell.term.Term} is also a {@link io.vertx.ext.shell.term.Tty}, this section explains
* how to use the tty.
*
* === Telnet term
*
* [source,$lang]
* ----
* {@link examples.Examples#telnetEchoTerminal}
* ----
*
* === HTTP term
*
* The {@link io.vertx.ext.shell.term.TermServer#createHttpTermServer} method creates an HTTP term server, built
* on top of Vert.x Web using the SockJS protocol.
*
* [source,$lang]
* ----
* {@link examples.Examples#httpEchoTerminal}
* ----
*
* An HTTP term can start its own HTTP server, or it can reuse an existing Vert.x Web {@link io.vertx.ext.web.Router}.
*
* The shell can be found at `/shell.html`.
*
* [source,$lang]
* ----
* {@link examples.Examples#httpEchoTerminalUsingRouter}
* ----
*
* 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 {@code java.util.ServiceLoader} of a {@link io.vertx.ext.shell.spi.CommandResolverFactory}.
*
* [source,java]
* ----
* public class CustomCommands implements CommandResolverFactory {
*
* public void resolver(Vertx vertx, Handler<AsyncResult<CommandResolver>> resolverHandler) {
* resolverHandler.handler(() -> Arrays.asList(myCommand1, myCommand2));
* }
* }
* ----
*
* The {@code 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 io.vertx.ext.shell.ShellService}. {@link io.vertx.ext.shell.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]
* ----
* {@link examples.pack.CommandPackExample}
* ----
*
* 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
* ----
*/
@ModuleGen(name = "vertx-shell", groupPackage = "io.vertx")
@Document(fileName = "index.adoc")
package io.vertx.ext.shell;
import io.vertx.codegen.annotations.ModuleGen;
import io.vertx.docgen.Document;