123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- package app
- import (
- "dy-pkg/app/flagsets"
- version2 "dy-pkg/app/version"
- "dy-pkg/logs"
- "fmt"
- "github.com/fatih/color"
- "github.com/moby/term"
- "github.com/spf13/cobra"
- "github.com/spf13/viper"
- "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
- }
|