blob: 61522b789fee1291f8c68e7f63eaee5820ef69c2 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"fmt"
"io"
"os"
"strings"
isatty "github.com/mattn/go-isatty"
"v.io/v23/context"
"v.io/v23/syncbase"
"v.io/x/lib/cmdline"
"v.io/x/ref/cmd/sb/commands"
"v.io/x/ref/cmd/sb/internal/reader"
)
var cmdSbShell = &cmdline.Command{
Runner: commands.SbRunner(runSbShell),
Name: "sh",
Short: "Start a syncQL shell",
Long: `
Connect to a database on the Syncbase service and start a syncQL shell.
`,
}
// Runs the shell.
// Takes commands as input, executes them, and prints out output.
func runSbShell(ctx *context.T, db syncbase.Database, env *cmdline.Env, args []string) error {
// Test if input is interactive and get reader.
// TODO(ivanpi): This is hacky, it would be better for lib/cmdline to support IsTerminal.
var input *reader.T
stdinFile, ok := env.Stdin.(*os.File)
isTerminal := ok && isatty.IsTerminal(stdinFile.Fd())
if isTerminal {
input = reader.NewInteractive()
} else {
input = reader.NewNonInteractive()
}
defer input.Close()
// Read-exec loop.
for true {
// Read command.
query, err := input.GetQueryWithTerminator('\n')
if err != nil {
if err == io.EOF && isTerminal {
// ctrl-d
fmt.Println()
}
break
}
// Exec command.
fields := strings.Fields(query)
if len(fields) > 0 {
switch cmdName := fields[0]; cmdName {
case "exit", "quit":
return nil
case "help":
if err := help(fields[1:]); err != nil {
return err
}
default:
if err := runCommand(ctx, env, db, fields, isTerminal); err != nil {
return err
}
}
}
}
return nil
}
func runCommand(ctx *context.T, env *cmdline.Env, db syncbase.Database,
fields []string, isTerminal bool) error {
commands.SetCtx(ctx)
commands.SetDB(db)
resetFlags(cmdSb, false)
if err := cmdline.ParseAndRun(cmdSb, env, fields); err != nil {
if isTerminal {
fmt.Fprintln(env.Stderr, "Error:", err)
} else {
// If running non-interactively, errors halt execution.
return err
}
}
return nil
}
func resetFlags(cmd *cmdline.Command, resetThisLevel bool) {
if resetThisLevel {
if cmd.ParsedFlags != nil {
cmd.ParsedFlags.Visit(func(f *flag.Flag) {
cmd.ParsedFlags.Set(f.Name, f.DefValue)
})
}
}
for _, nextCmd := range cmd.Children {
resetFlags(nextCmd, true)
}
}
func help(args []string) error {
switch len(args) {
case 0:
fmt.Println("Commands:")
for _, cmd := range commands.Commands {
fmt.Printf("\t%s\t%s\n", cmd.Name, cmd.Short)
}
fmt.Println("\thelp\tPrint a list of all commands")
fmt.Println("\texit\tEnd session (aliased to quit)")
return nil
case 1:
cmdName := args[0]
if cmdName == "help" {
fmt.Println("Print a list of all commands, or useful information about a single command.")
fmt.Println()
fmt.Println("Usage:")
fmt.Println("\thelp [command_name]")
} else {
cmd, err := commands.GetCommand(cmdName)
if err != nil {
return err
}
commands.PrintUsage(cmd)
}
return nil
default:
return fmt.Errorf("too many arguments")
}
}