Skip to content

Commit

Permalink
cmd/shell: Add support for interactive shell (#310)
Browse files Browse the repository at this point in the history
* cmd: Remove all interaction

* feat/shell: Add shell command in cmd

* feat/shell: Refactor input frame

* feat/shell: Refactor input frame with go-prompt

* feat/shell: Add support for local filepath

* cmd: Refactor subcommand in shell, add subCommandHandler to handle preRun and postRun in each sub command

* cmd/shell: Remove support for cat and tee in shell

* cmd: Add comments for codes

* cmd: Unify all cmd output

* cmd: Add tips and help info at the beginning of shell
  • Loading branch information
Prnyself committed Jul 30, 2020
1 parent 86c0f68 commit b8fbe99
Show file tree
Hide file tree
Showing 24 changed files with 804 additions and 320 deletions.
6 changes: 5 additions & 1 deletion cmd/qsctl/cat.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ var CatCommand = &cobra.Command{
i18n.Sprintf("Cat object: qsctl cat qs://prefix/a"),
),
Args: cobra.ExactArgs(1),
RunE: catRun,
Run: func(cmd *cobra.Command, args []string) {
if err := catRun(cmd, args); err != nil {
i18n.Fprintf(cmd.OutOrStderr(), "Execute %s command error: %s\n", "cat", err.Error())
}
},
}

func catRun(c *cobra.Command, args []string) (err error) {
Expand Down
55 changes: 26 additions & 29 deletions cmd/qsctl/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@ package main
import (
"fmt"
"path/filepath"
"time"

"github.com/Xuanwo/storage/types"
"github.com/qingstor/noah/task"
"github.com/spf13/cobra"

"github.com/qingstor/qsctl/v2/cmd/qsctl/taskutils"
cutils "github.com/qingstor/qsctl/v2/cmd/utils"
"github.com/qingstor/qsctl/v2/constants"
"github.com/qingstor/qsctl/v2/pkg/i18n"
"github.com/qingstor/qsctl/v2/utils"
)

var cpInput struct {
CheckMD5 bool
ExpectSize string
MaximumMemoryContent string
Recursive bool
type cpFlags struct {
checkMD5 bool
expectSize string
maximumMemoryContent string
recursive bool
}

var cpFlag = cpFlags{}

// CpCommand will handle copy command.
var CpCommand = &cobra.Command{
Use: "cp <source-path> <dest-path>",
Expand All @@ -36,24 +36,31 @@ var CpCommand = &cobra.Command{
i18n.Sprintf("Write to stdout: qsctl cp qs://prefix/b - > /path/to/file"),
),
Args: cobra.ExactArgs(2),
RunE: cpRun,
Run: func(cmd *cobra.Command, args []string) {
if err := cpRun(cmd, args); err != nil {
i18n.Fprintf(cmd.OutOrStderr(), "Execute %s command error: %s\n", "cp", err.Error())
}
},
PostRun: func(_ *cobra.Command, _ []string) {
cpFlag = cpFlags{}
},
}

func initCpFlag() {
CpCommand.PersistentFlags().StringVar(&cpInput.ExpectSize,
CpCommand.PersistentFlags().StringVar(&cpFlag.expectSize,
constants.ExpectSizeFlag,
"",
i18n.Sprintf(`expected size of the input file
accept: 100MB, 1.8G
(only used and required for input from stdin)`),
)
CpCommand.PersistentFlags().StringVar(&cpInput.MaximumMemoryContent,
CpCommand.PersistentFlags().StringVar(&cpFlag.maximumMemoryContent,
constants.MaximumMemoryContentFlag,
"",
i18n.Sprintf(`maximum content loaded in memory
(only used for input from stdin)`),
)
CpCommand.Flags().BoolVarP(&cpInput.Recursive,
CpCommand.Flags().BoolVarP(&cpFlag.recursive,
constants.RecursiveFlag,
"r",
false,
Expand All @@ -69,11 +76,11 @@ func cpRun(c *cobra.Command, args []string) (err error) {
return
}

if rootTask.GetSourceType() == types.ObjectTypeDir && !cpInput.Recursive {
if rootTask.GetSourceType() == types.ObjectTypeDir && !cpFlag.recursive {
return fmt.Errorf(i18n.Sprintf("-r is required to copy a directory"))
}

if cpInput.Recursive && rootTask.GetSourceType() != types.ObjectTypeDir {
if cpFlag.recursive && rootTask.GetSourceType() != types.ObjectTypeDir {
return fmt.Errorf(i18n.Sprintf("src should be a directory while -r is set"))
}

Expand All @@ -82,19 +89,11 @@ func cpRun(c *cobra.Command, args []string) (err error) {
return fmt.Errorf(i18n.Sprintf("cannot copy a directory to a non-directory dest"))
}

// only show progress bar without no-progress flag set
if !noProgress && cutils.IsInteractiveEnable() {
go func() {
taskutils.StartProgress(time.Second)
}()
defer taskutils.FinishProgress()
}

if cpInput.Recursive {
if cpFlag.recursive {
t := task.NewCopyDir(rootTask)
t.SetCheckMD5(cpInput.CheckMD5)
t.SetCheckMD5(cpFlag.checkMD5)
t.SetHandleObjCallback(func(o *types.Object) {
fmt.Println(i18n.Sprintf("<%s> copied", o.Name))
i18n.Fprintf(c.OutOrStdout(), "<%s> copied\n", o.Name)
})
t.SetCheckTasks(nil)
t.Run()
Expand All @@ -103,22 +102,20 @@ func cpRun(c *cobra.Command, args []string) (err error) {
return t.GetFault()
}

taskutils.WaitProgress()
i18n.Printf("Dir <%s> copied to <%s>.\n",
i18n.Fprintf(c.OutOrStdout(), "Dir <%s> copied to <%s>.\n",
filepath.Join(srcWorkDir, t.GetSourcePath()), filepath.Join(dstWorkDir, t.GetDestinationPath()))
return nil
}

t := task.NewCopyFile(rootTask)
t.SetCheckMD5(cpInput.CheckMD5)
t.SetCheckMD5(cpFlag.checkMD5)
t.SetCheckTasks(nil)
t.Run()
if t.GetFault().HasError() {
return t.GetFault()
}

taskutils.WaitProgress()
i18n.Printf("File <%s> copied to <%s>.\n",
i18n.Fprintf(c.OutOrStdout(), "File <%s> copied to <%s>.\n",
filepath.Join(srcWorkDir, t.GetSourcePath()), filepath.Join(dstWorkDir, t.GetDestinationPath()))
return
}
70 changes: 22 additions & 48 deletions cmd/qsctl/init.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
package main

import (
"fmt"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/qingstor/qsctl/v2/cmd/utils"
"github.com/qingstor/qsctl/v2/constants"
"github.com/qingstor/qsctl/v2/pkg/i18n"
)

//go:generate go run ../../internal/cmd/generator/i18nextract
//go:generate go run ../../internal/cmd/generator/i18ngenerator

var globalFlag = globalFlags{}

// register available flag vars here
var (
type globalFlags struct {
// bench will be set if bench flag was set
bench bool
// configPath will be set if config flag was set
configPath string
debug bool
// noProgress will be set if no-progress flag was set
noProgress bool
// zone will be set if zone flag was set
zone string
)
}

// rootCmd is the main command of qsctl
var rootCmd = &cobra.Command{
Use: constants.Name,
Long: constants.Description,
Version: constants.Version,
// reset global flags after each sub-command run
PersistentPostRun: func(_ *cobra.Command, _ []string) {
globalFlag = globalFlags{}
},
}

func init() {
Expand All @@ -51,7 +52,7 @@ func init() {

// init config before command run
rootCmd.PersistentPreRunE = func(c *cobra.Command, args []string) error {
if debug {
if globalFlag.debug {
log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.PanicLevel)
Expand All @@ -60,6 +61,7 @@ func init() {
return initConfig()
}

rootCmd.AddCommand(ShellCommand)
// add sub-command to rootCmd
rootCmd.AddCommand(CatCommand)
rootCmd.AddCommand(CpCommand)
Expand All @@ -73,12 +75,9 @@ func init() {
rootCmd.AddCommand(SyncCommand)
rootCmd.AddCommand(TeeCommand)

rootCmd.SetVersionTemplate(i18n.Sprintf(
`{{with .Name}}{{printf "%%s " .}}{{end}}{{printf "version %%s\n" .Version}}`,
))
rootCmd.SetHelpTemplate(i18n.Sprintf(`{{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}}
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`))
rootCmd.SetVersionTemplate(i18n.Sprintf(constants.VersionTemplate))
rootCmd.SetUsageTemplate(i18n.Sprintf(constants.UsageTemplate))
rootCmd.SetHelpTemplate(i18n.Sprintf(constants.HelpTemplate))
}

func initConfig() (err error) {
Expand All @@ -92,9 +91,9 @@ func initConfig() (err error) {
viper.SetDefault(constants.ConfigProtocol, constants.DefaultProtocol)

// Load config from config file.
if configPath != "" {
if globalFlag.configPath != "" {
// Use config file from the flag.
viper.SetConfigFile(configPath)
viper.SetConfigFile(globalFlag.configPath)
} else {
// Search config in home directory with name ".qingstor" (without extension).
viper.AddConfigPath("$HOME/.qingstor")
Expand All @@ -106,8 +105,8 @@ func initConfig() (err error) {
}

// if zone flag was set, overwrite the config
if zone != "" {
viper.Set(constants.ConfigZone, zone)
if globalFlag.zone != "" {
viper.Set(constants.ConfigZone, globalFlag.zone)
}

// try to read config from path set above
Expand All @@ -128,47 +127,22 @@ func initConfig() (err error) {
return nil
}

// if env not set, try to start interactive setup
// if not run interactively, return error
if !utils.IsInteractiveEnable() {
log.Errorf("qsctl not run interactively, and cannot load config with err: [%v]", err)
return err
}

i18n.Printf("AccessKey and SecretKey not found. Please setup your config now, or exit and setup manually.")
log.Debug("AccessKey and SecretKey not found. Ready to turn into setup config interactively.")
var fileName string
fileName, err = utils.SetupConfigInteractive()
if err != nil {
return fmt.Errorf("setup config failed [%v], please try again", err)
}

i18n.Printf("Your config has been set to <%v>. You can still modify it manually.", fileName)
viper.SetConfigFile(fileName)
log.Debugf("Config was set to [%s]", fileName)
// read in config again after interactively setup config file
if err = viper.ReadInConfig(); err != nil {
log.Errorf("Read config after interactively setup failed: [%v]", err)
return err
}
return nil
return
}

func initGlobalFlag() {
// Add config flag which can be used in all sub commands.
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c",
rootCmd.PersistentFlags().StringVarP(&globalFlag.configPath, "config", "c",
"", i18n.Sprintf("assign config path manually"))
// Add zone flag which can be used in all sub commands.
rootCmd.PersistentFlags().StringVarP(&zone, constants.ConfigZone, "z",
rootCmd.PersistentFlags().StringVarP(&globalFlag.zone, constants.ConfigZone, "z",
"", i18n.Sprintf("in which zone to do the operation"))
// Add config flag which can be used in all sub commands.
rootCmd.PersistentFlags().BoolVar(&bench, constants.BenchFlag,
rootCmd.PersistentFlags().BoolVar(&globalFlag.bench, constants.BenchFlag,
false, i18n.Sprintf("enable benchmark or not"))
rootCmd.PersistentFlags().BoolVar(&noProgress, constants.NoProgressFlag,
false, i18n.Sprintf("disable progress bar display or not"))
// Overwrite the default help flag to free -h shorthand.
rootCmd.PersistentFlags().Bool("help", false, i18n.Sprintf("help for this command"))
rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, i18n.Sprintf("print logs for debug"))
rootCmd.PersistentFlags().BoolVar(&globalFlag.debug, "debug", false, i18n.Sprintf("print logs for debug"))
}

func silenceUsage(c *cobra.Command) {
Expand Down
Loading

0 comments on commit b8fbe99

Please sign in to comment.