package app import ( "fmt" "github.com/fatih/color" "github.com/moby/term" "github.com/spf13/cobra" "github.com/spf13/viper" "gogs.tyduyong.com/duyong/dy-pkg/app/flagsets" version2 "gogs.tyduyong.com/duyong/dy-pkg/app/version" "gogs.tyduyong.com/duyong/dy-pkg/logs" "io" "os" "runtime" "strings" ) var progressMessage = color.GreenString("==>") type App struct { basename string name string description string options CommandLineOptions runFunc RunFunc silence bool noVersion bool noConfig bool commands []*Command args cobra.PositionalArgs cmd *cobra.Command } type RunFunc func(basename string) error type Option func(*App) func WithOptions(opt CommandLineOptions) Option { return func(a *App) { a.options = opt } } func WithRunFunc(run RunFunc) Option { return func(a *App) { a.runFunc = run } } func WithDescription(desc string) Option { return func(a *App) { a.description = desc } } func WithSilence() Option { return func(a *App) { a.silence = true } } func WithNoVersion() Option { return func(a *App) { a.noVersion = true } } func WithNoConfig() Option { return func(a *App) { a.noConfig = true } } func WithValidArgs(args cobra.PositionalArgs) Option { return func(a *App) { a.args = args } } func WithDefaultValidArgs() Option { return func(a *App) { a.args = func(cmd *cobra.Command, args []string) error { for _, arg := range args { if len(arg) > 0 { return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args) } } return nil } } } func NewApp(name string, basename string, opts ...Option) *App { a := &App{ name: name, basename: basename, } for _, o := range opts { o(a) } a.buildCommand() return a } func (a *App) buildCommand() { cmd := cobra.Command{ Use: FormatBaseName(a.basename), Short: a.name, Long: a.description, // stop printing usage when the command errors SilenceUsage: true, SilenceErrors: true, Args: a.args, } // cmd.SetUsageTemplate(usageTemplate) cmd.SetOut(os.Stdout) cmd.SetErr(os.Stderr) cmd.Flags().SortFlags = true //cliflag.InitFlags(cmd.Flags()) if len(a.commands) > 0 { for _, command := range a.commands { cmd.AddCommand(command.cobraCommand()) } cmd.SetHelpCommand(helpCommand(FormatBaseName(a.basename))) } if a.runFunc != nil { cmd.RunE = a.runCommand } var namedFlagSets flagsets.NamedFlagSets if a.options != nil { namedFlagSets = a.options.Flags() fs := cmd.Flags() for _, f := range namedFlagSets.FlagSets { fs.AddFlagSet(f) } } if !a.noVersion { version2.AddFlags(namedFlagSets.FlagSet("global")) } if !a.noConfig { addConfigFlag(a.basename, namedFlagSets.FlagSet("global")) } cmd.Flags().AddFlagSet(namedFlagSets.FlagSet("global")) addCmdTemplate(&cmd, namedFlagSets) a.cmd = &cmd } func (a *App) runCommand(cmd *cobra.Command, args []string) error { if !a.noVersion { // display application version information version2.PrintAndExitIfRequested() } if !a.noConfig { if err := viper.BindPFlags(cmd.Flags()); err != nil { return err } if err := viper.Unmarshal(a.options); err != nil { return err } } if !a.silence { logs.Infof("%v Starting %s ...", progressMessage, a.name) if !a.noVersion { logs.Infof("%v Version: `%s`", progressMessage, version2.Get().ToJSON()) } if !a.noConfig { logs.Infof("%v Config file used: `%s`", progressMessage, viper.ConfigFileUsed()) } } if a.options != nil { if err := a.applyOptionRules(); err != nil { return err } } // run application if a.runFunc != nil { return a.runFunc(a.basename) } return nil } func (a *App) applyOptionRules() error { if completeableOptions, ok := a.options.(CompleteOptions); ok { if err := completeableOptions.Complete(); err != nil { return err } } if errs := a.options.Validate(); len(errs) != 0 { return errs[0] } if printableOptions, ok := a.options.(PrintOptions); ok && !a.silence { logs.Infof("%v Config: `%s`", progressMessage, printableOptions.String()) } return nil } func (a *App) Run() { if err := a.cmd.Execute(); err != nil { fmt.Printf("%v %v\n", color.RedString("Error:"), err) os.Exit(1) } } func FormatBaseName(basename string) string { // Make case-insensitive and strip executable suffix if present if runtime.GOOS == "windows" { basename = strings.ToLower(basename) basename = strings.TrimSuffix(basename, ".exe") } return basename } func addCmdTemplate(cmd *cobra.Command, namedFlagSets flagsets.NamedFlagSets) { usageFmt := "Usage:\n %s\n" cols, _, _ := TerminalSize(cmd.OutOrStdout()) cmd.SetUsageFunc(func(cmd *cobra.Command) error { fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine()) flagsets.PrintSections(cmd.OutOrStderr(), namedFlagSets, cols) return nil }) cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine()) flagsets.PrintSections(cmd.OutOrStdout(), namedFlagSets, cols) }) } // TerminalSize 获取当前终端宽高,非终端返回err,同时宽高返回0值 func TerminalSize(w io.Writer) (int, int, error) { outFd, isTerminal := term.GetFdInfo(w) if !isTerminal { return 0, 0, fmt.Errorf("given writer is no terminal") } winSize, err := term.GetWinsize(outFd) if err != nil { return 0, 0, err } return int(winSize.Width), int(winSize.Height), nil }