Clean up disparity between Context and Kong.
Previously, there was a confusing mix of functionality shared between
the two wherein you would need to use the Kong type for printing errors,
etc. but it did not have access to the context in order to print
context-sensitive usage information. This has been fixed.
Additionally, there are now fuzzy correction suggestions for flags and
commands
Also added a server example which shows how Kong can be used for parsing
in interactive shells. Run with:
$ go run ./_examples/server/*.go
Then interact with:
$ ssh -p 6740 127.0.0.1
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
// nolint: govet
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
// Ensure the grammar compiles.
|
||||
var _ = kong.Must(&grammar{})
|
||||
|
||||
// Server interface.
|
||||
type grammar struct {
|
||||
Help helpCmd `cmd help:"Show help."`
|
||||
Question helpCmd `cmd hidden name:"?" help:"Show help."`
|
||||
|
||||
Status statusCmd `cmd help:"Show server status."`
|
||||
}
|
||||
|
||||
type statusCmd struct {
|
||||
Verbose bool `short:"v" help:"Show verbose status information."`
|
||||
}
|
||||
|
||||
func (s *statusCmd) Run(ctx *kong.Context) error {
|
||||
ctx.Printf("OK")
|
||||
return nil
|
||||
}
|
||||
|
||||
type helpCmd struct {
|
||||
Command []string `arg optional help:"Show help on command."`
|
||||
}
|
||||
|
||||
// Run shows help.
|
||||
func (h *helpCmd) Run(realCtx *kong.Context) error {
|
||||
ctx, err := kong.Trace(realCtx.Kong, h.Command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.Error != nil {
|
||||
return ctx.Error
|
||||
}
|
||||
err = ctx.PrintUsage(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(realCtx.Stdout)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/gliderlabs/ssh"
|
||||
"github.com/google/shlex"
|
||||
"github.com/kr/pty"
|
||||
|
||||
"github.com/alecthomas/colour"
|
||||
"github.com/alecthomas/kong"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
kong *kong.Context
|
||||
rl *readline.Instance
|
||||
}
|
||||
|
||||
func handle(log *log.Logger, s ssh.Session) error {
|
||||
log.Printf("New SSH")
|
||||
sshPty, _, isPty := s.Pty()
|
||||
if !isPty {
|
||||
return errors.New("No PTY requested")
|
||||
}
|
||||
log.Printf("Using TERM=%s width=%d height=%d", sshPty.Term, sshPty.Window.Width, sshPty.Window.Height)
|
||||
cpty, tty, err := pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tty.Close()
|
||||
state, err := terminal.GetState(int(cpty.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer terminal.Restore(int(cpty.Fd()), state)
|
||||
|
||||
colour.Fprintln(tty, "^BWelcome!^R")
|
||||
go io.Copy(cpty, s)
|
||||
go io.Copy(s, cpty)
|
||||
|
||||
parser, err := buildShellParser(tty)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rl, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "> ",
|
||||
Stderr: tty,
|
||||
Stdout: tty,
|
||||
Stdin: tty,
|
||||
FuncOnWidthChanged: func(f func()) {},
|
||||
FuncMakeRaw: func() error {
|
||||
_, err := terminal.MakeRaw(int(cpty.Fd())) // nolint: govet
|
||||
return err
|
||||
},
|
||||
FuncExitRaw: func() error { return nil },
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("Loop")
|
||||
for {
|
||||
tty.Sync()
|
||||
|
||||
var line string
|
||||
line, err = rl.Readline()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
var args []string
|
||||
args, err := shlex.Split(string(line))
|
||||
if err != nil {
|
||||
parser.Errorf("%s", err)
|
||||
continue
|
||||
}
|
||||
var ctx *kong.Context
|
||||
ctx, err = parser.Parse(args)
|
||||
if err != nil {
|
||||
parser.Errorf("%s", err)
|
||||
if err, ok := err.(*kong.ParseError); ok {
|
||||
log.Println(err.Error())
|
||||
err.Context.PrintUsage(false)
|
||||
}
|
||||
continue
|
||||
}
|
||||
err = ctx.Run(ctx)
|
||||
if err != nil {
|
||||
parser.Errorf("%s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildShellParser(tty *os.File) (*kong.Kong, error) {
|
||||
parser, err := kong.New(&grammar{},
|
||||
kong.Name(""),
|
||||
kong.Description("Example using Kong for interactive command parsing."),
|
||||
kong.Writers(tty, tty),
|
||||
kong.Exit(func(int) {}),
|
||||
kong.ConfigureHelp(kong.HelpOptions{
|
||||
NoAppSummary: true,
|
||||
}),
|
||||
kong.NoDefaultHelp(),
|
||||
)
|
||||
return parser, err
|
||||
}
|
||||
|
||||
func handlerWithError(handle func(log *log.Logger, s ssh.Session) error) ssh.Handler {
|
||||
return func(s ssh.Session) {
|
||||
prefix := fmt.Sprintf("%s->%s ", s.LocalAddr(), s.RemoteAddr())
|
||||
l := log.New(os.Stdout, prefix, log.LstdFlags)
|
||||
err := handle(l, s)
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
s.Exit(1)
|
||||
} else {
|
||||
log.Printf("Bye")
|
||||
s.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cli struct {
|
||||
HostKey string `type:"existingfile" help:"SSH host key to use." default:"./_examples/server/server_rsa_key"`
|
||||
Bind string `help:"Bind address for server." default:"127.0.0.1:6740"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := kong.Parse(&cli,
|
||||
kong.Name("server"),
|
||||
kong.Description("A network server using Kong for interacting with clients."))
|
||||
|
||||
ssh.Handle(handlerWithError(handle))
|
||||
log.Printf("SSH listening on: %s", cli.Bind)
|
||||
log.Printf("Using host key: %s", cli.HostKey)
|
||||
log.Println()
|
||||
parts := strings.Split(cli.Bind, ":")
|
||||
log.Printf("Connect with: ssh -p %s %s", parts[1], parts[0])
|
||||
log.Println()
|
||||
err := ssh.ListenAndServe(cli.Bind, nil, ssh.HostKeyFile(cli.HostKey))
|
||||
ctx.FatalIfErrorf(err)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAxK1QbPQYibc0VFZdc6GHPka5oTeGnXsMZiyX4JbHUJl1oNDB
|
||||
Xg9NATbft+q/6ZDjyVEQhq8xgLvYFkL8qBLt/6UAaOub0RtmPqQwmxoNLWuXFFwn
|
||||
YlBKApQ4gf58/jOcGPpdEwfjkjwLb536Bni25XMU4cYrdIvQIhtMaK+Zqja/3MAC
|
||||
V6ZRZZCd8hABJqaZu+3mRElnF1d7gfMvA/hhaq7Y5VYr8rUrBHHimrT/GEP6aCbf
|
||||
Npo43SfRnUDu2+EAK7vA9cM8fg/O/mvNR1/9jzOWyr8ZDLD6R6iaZCQ2anEPcqim
|
||||
MCOtibSeVOms07Zcn/TSsgmfGwa8rQkpXVJA5wIDAQABAoIBAQCTUccGdajTrzkx
|
||||
WyfQ71NgoJV3XyIkYAEfn5N8FTTi+LAVb4kILanemP3mw55RE8isCV65pA0OgqYP
|
||||
tsmOE+/WKAAwlxs1/LIPhekqpM7uEMMv6v9NMxrc562UIc36kyn/w7loAebCqNtg
|
||||
FhMsOcu1/wfLPidau0eB5LTNTYtq5RuSKxoindvatk+Zmk0KjoA+f25MlwCEHQNr
|
||||
ygpopclyTHVln2t3t0o97/a7dHa9+HlmVO4GxWvTTiqtcFErTGWtTUW8aeZFS83r
|
||||
p+JZNxReSJ2MlM9bm15wJ0L86GTeYZQiaNuC1XETbFvX+9Ffkl+7EtsdYDLV1N6r
|
||||
/eOP2f0hAoGBAOKVDHmnru7SVmbH5BI8HW5sd6IVztZM3+BKzy6AaPc4/FgG6MOr
|
||||
bJyFbmN8S/gVi4OYOJXgfaeKcycYJFTjXUSnNRQ9eT0MseD9SxzEXV7RGtnvudiu
|
||||
pbRmtBRtf3e4beaN9X4SfWk4+Frw7B8UsPXwV/09s7AW279cES565IkfAoGBAN42
|
||||
TQSC/jQmJBpGSnqWfqQtKPTSKFoZ/JQbxoy9QckAMqVSFwBBgwQYr4MbI7WyjPRE
|
||||
s43kpf+Sq/++fc3hyk5XAWBKscK0KLs0HBRZyLybQYI+f4/x2giVzKeRRNVa9nQa
|
||||
VdIU8i+eO2AUzG690q89HGkRBsfXekjq5kXC9Cc5AoGAUY0b5F16FPMXrf6cFAQX
|
||||
A7t+g5Qd0fvxSCUk1LPbE8Aq8vPpqyN0ABH2XVBLd4spn7+V/jvCfh7Su2txCCyd
|
||||
USxtak+F53c+PqBr/HqgsJPKek5SMa8KbRfaENAoZMq4o5bMmQfGo6yhlvnHwpgL
|
||||
6TkMMlWW6vYPOZzFglkxEDkCgYApT78Rz6ii2VRs7hR6pe/1Zc/vdAK8fYhPoLpQ
|
||||
//5y9+5yfch467UH1e8LWMhSx1cdMoiPIKsb0JDZgviwhgGufs5qsHhL0mKgKxft
|
||||
UKPZLKQJKsVcZYI7hl396Sv63mZjP2IlJG/CGpC/VB6NmAzLN3lIrzmrfYvmcoVN
|
||||
AumRQQKBgB4Uznek3nq5ZQf93+ucvXpf0bLRqq1va7jYmRosyiCN52grbclj5uMq
|
||||
vxr1uoqmgtCfqdgUbm0s+kVK6D4bPkz4HQOSXImXhLs8/KdixYfPLSarxYvTxZKg
|
||||
mMF1XqcdRwSv3RZYtUbbF7dYQYsC1/ZKXvtPldeoDmTZ+U7b2qbE
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErVBs9BiJtzRUVl1zoYc+RrmhN4adewxmLJfglsdQmXWg0MFeD00BNt+36r/pkOPJURCGrzGAu9gWQvyoEu3/pQBo65vRG2Y+pDCbGg0ta5cUXCdiUEoClDiB/nz+M5wY+l0TB+OSPAtvnfoGeLblcxThxit0i9AiG0xor5mqNr/cwAJXplFlkJ3yEAEmppm77eZESWcXV3uB8y8D+GFqrtjlVivytSsEceKatP8YQ/poJt82mjjdJ9GdQO7b4QAru8D1wzx+D87+a81HX/2PM5bKvxkMsPpHqJpkJDZqcQ9yqKYwI62JtJ5U6azTtlyf9NKyCZ8bBrytCSldUkDn
|
||||
Reference in New Issue
Block a user