Перейти ежедневную библиотеку кобра

Go

Введение

cobraЭто библиотека командной строки, которую можно использовать для написания программ командной строки. В то же время он также обеспечивает леса, Фреймворк для создания приложений на основе кобры. Многие известные проекты с открытым исходным кодом используют библиотеку cobra для построения командной строки, напримерKubernetes,Hugo,etcdИ т. д. и т. д. В этой статье представлены основы использования и некоторые интересные функции библиотеки cobra.

Об автореspf13, вот еще два слова. У spf13 много проектов с открытым исходным кодом, и качество его проектов с открытым исходным кодом относительно высокое. Я думаю, что каждый, кто использовал vim, знаетspf13-vim, известный как окончательная конфигурация vim. Его можно настроить одним щелчком мыши, что, безусловно, является благом для ленивых людей, таких как я. егоviperпредставляет собой полное решение для конфигурации. Он отлично поддерживает файлы конфигурации свойств JSON/TOML/YAML/HCL/envfile/Java и другие форматы, а также некоторые более практичные функции, такие как горячее обновление конфигурации, несколько каталогов поиска, сохранение конфигурации и т. д. Есть также очень популярные генераторы статических сайтов.hugoТакже его работа.

быстрый в использовании

Все сторонние библиотеки необходимо сначала установить, а затем использовать. Установлена ​​следующая командаcobraПрограмма-генератор и библиотека cobra:

$ go get github.com/spf13/cobra/cobra

если естьgolang.org/x/textДля таких ошибок, как библиотека не найдена, вам необходимо вручную загрузить библиотеку с GitHub, а затем выполнить указанную выше команду установки. Я написал блог раньшеНастройка среды разработки Goупомянул этот метод.

Мы реализуем простую программу командной строки git, конечно, это не настоящий git, просто имитирует его командную строку. наконец прошлоos/execБиблиотека вызывает внешнюю программу для выполнения настоящей команды git и возвращает результат. Итак, нам нужно установить git в нашу систему, а git находится в пути к исполняемому файлу. В настоящее время мы добавляем только одну подкомандуversion. Структура каталогов следующая:

▾ get-started/
    ▾ cmd/
        helper.go
        root.go
        version.go
    main.go

root.go:

package cmd

import (
  "errors"

  "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command {
  Use: "git",
  Short: "Git is a distributed version control system.",
  Long: `Git is a free and open source distributed version control system
designed to handle everything from small to very large projects 
with speed and efficiency.`,
  Run: func(cmd *cobra.Command, args []string) {
    Error(cmd, args, errors.New("unrecognized command"))
  },
}

func Execute() {
  rootCmd.Execute()
}

version.go:

package cmd

import (
  "fmt"
  "os"

  "github.com/spf13/cobra"
)

var versionCmd = &cobra.Command {
  Use: "version",
  Short: "version subcommand show git version info.",
  
  Run: func(cmd *cobra.Command, args []string) {
    output, err := ExecuteCommand("git", "version", args...)
    if err != nil {
      Error(cmd, args, err)
    }

    fmt.Fprint(os.Stdout, output)
  },
}

func init() {
  rootCmd.AddCommand(versionCmd)
}

main.goФайл просто вызывает запись команды:

package main

import (
  "github.com/darjun/go-daily-lib/cobra/get-started/cmd"
)

func main() {
  cmd.Execute()
}

Для удобства кодирования вhelpers.goВызов внешних программ и функций обработки ошибок инкапсулирован в:

package cmd

import (
  "fmt"
  "os"
  "os/exec"

  "github.com/spf13/cobra"
)

func ExecuteCommand(name string, subname string, args ...string) (string, error) {
  args = append([]string{subname}, args...)

  cmd := exec.Command(name, args...)
  bytes, err := cmd.CombinedOutput()

  return string(bytes), err
}

func Error(cmd *cobra.Command, args []string, err error) {
  fmt.Fprintf(os.Stderr, "execute %s args:%v error:%v\n", cmd.Name(), args, err)
  os.Exit(1)
}

Каждая программа кобры имеет корневую команду, к которой можно добавить любое количество подкоманд. мы вversion.goизinitдля добавления подкоманд к корневой команде.

Скомпилируйте программу. Обратите внимание, что вы не можете напрямуюgo run main.go, которая больше не является однофайловой программой. Если вы вынуждены использовать его, пожалуйста, используйтеgo run .:

$ go build -o main.exe

Справочная информация автоматически генерируется коброй, очень круто:

$ ./main.exe -h
Git is a free and open source distributed version control system
designed to handle everything from small to very large projects
with speed and efficiency.

Usage:
  git [flags]
  git [command]

Available Commands:
  help        Help about any command
  version     version subcommand show git version info.

Flags:
  -h, --help   help for git

Use "git [command] --help" for more information about a command.

Справка для отдельных подкоманд:

$ ./main.exe version -h
version subcommand show git version info.

Usage:
  git version [flags]

Flags:
  -h, --help   help for version

Вызвать подкоманду:

$ ./main.exe version
git version 2.19.1.windows.1

Неизвестная подкоманда:

$ ./main.exe clone
Error: unknown command "clone" for "git"
Run 'git --help' for usage.

При компиляции можноmain.exeизменить наgit, будет удобнее пользоваться 🤩.

$ go build -o git
$ ./git version
git version 2.19.1.windows.1

При использовании кобры для построения командной строки структура каталогов программы, как правило, относительно проста.Рекомендуется следующая структура:

▾ appName/
    ▾ cmd/
        cmd1.go
        cmd2.go
        cmd3.go
        root.go
    main.go

Каждая команда реализует файл, и все командные файлы хранятся вcmdПод содержанием. внешнийmain.goИнициализируйте только кобру.

характеристика

cobra предоставляет очень богатые возможности:

  • Легко поддерживает подкоманды, такие какapp server,app fetchЖдать;
  • Полностью совместим с опциями POSIX (включая короткие и длинные опции);
  • вложенные подкоманды;
  • Варианты глобального, локального уровня. Вы можете установить параметры в нескольких местах и ​​использовать их в определенном порядке;
  • С легкостью создавайте процедурные скелеты и команды с помощью скаффолдинга.

Во-первых, необходимо уточнить три основных понятия:

  • Команда: это операция, которую необходимо выполнить;
  • Параметр (Arg): Параметр команды, то есть объект, которым нужно управлять;
  • Параметры (флаг): параметры команды могут регулировать поведение команды.

В приведенном ниже примереserverявляется (под)командой,--portэто вариант:

hugo server --port=1313

В приведенном ниже примереcloneявляется (под)командой,URLпараметр,--bareэто вариант:

git clone URL --bare

Заказ

В кобре как команды, так и подкоманды используютCommandпредставлена ​​структурой.CommandЕсть много полей для настройки поведения команды. На практике чаще всего используются лишь некоторые из них. Мы видели в предыдущем примереUse/Short/Long/Run.

UseУказывает информацию об использовании, то есть способ вызова команды в форматеname arg1 [arg2].nameимя команды, за которым следуетarg1обязательный параметр,arg3Для необязательных параметров может быть несколько параметров.

Short/LongОбе являются справочной информацией для указанной команды, но первая краткая, а вторая подробная.

Runэто функция, которая фактически выполняет операцию.

Определить новую подкоманду так же просто, как создатьcobra.Commandпеременная, установите некоторые поля, затем добавьте в корневую команду. Например, мы хотим добавитьcloneПодкоманда:

package cmd

import (
  "fmt"
  "os"

  "github.com/spf13/cobra"
)

var cloneCmd = &cobra.Command {
  Use: "clone url [destination]",
  Short: "Clone a repository into a new directory",
  Run: func(cmd *cobra.Command, args []string) {
    output, err := ExecuteCommand("git", "clone", args...)
    if err != nil {
      Error(cmd, args, err)
    }

    fmt.Fprintf(os.Stdout, output)
  },
}

func init() {
  rootCmd.AddCommand(cloneCmd)
}

вUseполеclone url [destination]Указывает, что имя подкомандыclone,параметрurlтребуется, целевой путьdestinationПо желанию.

Собираем программу какmygitисполняемый файл, затем поместите его в$GOPATH/binсередина. Я люблю$GOPATH/binставить$PATH, так что вы можете напрямую позвонитьmygitкоманда:

$ go build -o mygit
$ mv mygit $GOPATH/bin
$ mygit clone https://github.com/darjun/leetcode
Cloning into 'leetcode'...

Вы можете продолжить добавлять команды. Но я просто поленился и перенаправил операции на настоящий git для выполнения. Это действительно не имеет практического значения. С этой идеей представьте, что мы можем объединить несколько команд для реализации многих полезных инструментов, таких как инструменты упаковки 😉.

опции

В кобре есть два варианта, одинпостоянный вариант, можно использовать команду, которая его определяет, и ее подкоманды. Определите глобальные параметры, добавив параметр в корневую команду. Другойместные варианты, может использоваться только в команде, в которой он определен.

использование кобрыpflagРазобрать параметры командной строки.pflagИспользуйте в основном то же самоеflagТак же, в этой серии статей есть введениеflagбиблиотека,Флаг ежедневной библиотеки Go.

иflagТочно так же переменные, в которых хранятся параметры, также должны быть определены заранее:

var Verbose bool
var Source string

Установите постоянные параметры:

rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")

Установите локальные параметры:

localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")

Оба параметра одинаковы, длинное/короткое имя параметра, значение по умолчанию и справочная информация.

Ниже мы используем пример, чтобы продемонстрировать использование опций.

Предположим, мы хотим сделать простой калькулятор, поддерживающий сложение, вычитание, умножение и деление. И вы можете указать, следует ли игнорировать нечисловые параметры через параметры, и указать, следует ли сообщать об ошибке при делении на 0. Очевидно, что первую опцию следует поместить в глобальные опции, а вторую — в команду дивизии. Структура программы следующая:

▾ math/
    ▾ cmd/
        add.go
        divide.go
        minus.go
        multiply.go
        root.go
    main.go

Показать здесьdivide.goиroot.go, другие командные файлы аналогичны. Я вставил полный кодGitHubвверх.

divide.go:

var (
  dividedByZeroHanding int // 除 0 如何处理
)
var divideCmd = &cobra.Command {
  Use: "divide",
  Short: "Divide subcommand divide all passed args.",
  Run: func(cmd *cobra.Command, args []string) {
    values := ConvertArgsToFloat64Slice(args, ErrorHandling(parseHandling))
    result := calc(values, DIVIDE)
    fmt.Printf("%s = %.2f\n", strings.Join(args, "/"), result)
  },
}

func init() {
  divideCmd.Flags().IntVarP(&dividedByZeroHanding, "divide_by_zero", "d", int(PanicOnDividedByZero), "do what when divided by zero")

  rootCmd.AddCommand(divideCmd)
}

root.go:

var (
  parseHandling int
)

var rootCmd = &cobra.Command {
  Use: "math",
  Short: "Math calc the accumulative result.",
  Run: func(cmd *cobra.Command, args []string) {
    Error(cmd, args, errors.New("unrecognized subcommand"))
  },
}

func init() {
  rootCmd.PersistentFlags().IntVarP(&parseHandling, "parse_error", "p", int(ContinueOnParseError), "do what when parse arg error")
}

func Execute() {
  rootCmd.Execute()
}

существуетdivide.goВарианты того, как обрабатывать ошибки деления на 0, определены вroot.goПараметры того, как обрабатывать ошибки синтаксического анализа, определены в . Варианты перечислены следующим образом:

const (
  ContinueOnParseError  ErrorHandling = 1 // 解析错误尝试继续处理
  ExitOnParseError      ErrorHandling = 2 // 解析错误程序停止
  PanicOnParseError     ErrorHandling = 3 // 解析错误 panic
  ReturnOnDividedByZero ErrorHandling = 4 // 除0返回
  PanicOnDividedByZero  ErrorHandling = 5 // 除0 painc
)

На самом деле логика выполнения команды не сложная, это преобразование параметров вfloat64. Затем выполните соответствующую операцию и выведите результат.

тестовая программа:

$ go build -o math
$ ./math add 1 2 3 4
1+2+3+4 = 10.00

$ ./math minus 1 2 3 4
1-2-3-4 = -8.00

$ ./math multiply 1 2 3 4
1*2*3*4 = 24.00

$ ./math divide 1 2 3 4
1/2/3/4 = 0.04

По умолчанию ошибки синтаксического анализа игнорируются, и оценивается только результат правильно сформированных аргументов:

$ ./math add 1 2a 3b 4
1+2a+3b+4 = 5.00

$ ./math divide 1 2a 3b 4
1/2a/3b/4 = 0.25

Установить обработку сбоя парсинга, 2 означает выход из программы, 3 означает панику (см. перечисление выше):

$ ./math add 1 2a 3b 4 -p 2
invalid number: 2a

$ ./math add 1 2a 3b 4 -p 3
panic: strconv.ParseFloat: parsing "2a": invalid syntax

goroutine 1 [running]:
github.com/darjun/go-daily-lib/cobra/math/cmd.ConvertArgsToFloat64Slice(0xc00004e300, 0x4, 0x6, 0x3, 0xc00008bd70, 0x504f6b, 0xc000098600)
    D:/code/golang/src/github.com/darjun/go-daily-lib/cobra/math/cmd/helper.go:58 +0x2c3
github.com/darjun/go-daily-lib/cobra/math/cmd.glob..func1(0x74c620, 0xc00004e300, 0x4, 0x6)
    D:/code/golang/src/github.com/darjun/go-daily-lib/cobra/math/cmd/add.go:14 +0x6d
github.com/spf13/cobra.(*Command).execute(0x74c620, 0xc00004e1e0, 0x6, 0x6, 0x74c620, 0xc00004e1e0)
    D:/code/golang/src/github.com/spf13/cobra/command.go:835 +0x2b1
github.com/spf13/cobra.(*Command).ExecuteC(0x74d020, 0x0, 0x599ee0, 0xc000056058)
    D:/code/golang/src/github.com/spf13/cobra/command.go:919 +0x302
github.com/spf13/cobra.(*Command).Execute(...)
    D:/code/golang/src/github.com/spf13/cobra/command.go:869
github.com/darjun/go-daily-lib/cobra/math/cmd.Execute(...)
    D:/code/golang/src/github.com/darjun/go-daily-lib/cobra/math/cmd/root.go:45
main.main()
    D:/code/golang/src/github.com/darjun/go-daily-lib/cobra/math/main.go:8 +0x35

Что касается варианта деления на 0, попробуйте сами.

Внимательные друзья должны были заметить, что в программе все же есть некоторые несовершенства. Например, здесь, если вы введете нечисловой параметр, этот параметр также будет показан в результате:

$ ./math add 1 2 3d cc
1+2+3d+cc = 3.00

Если вам интересно, вы можете улучшить его самостоятельно~

строительные леса

В предыдущем введении мы также видели, что структура команды кобры относительно фиксирована. Вот тут-то и появляются инструменты, которые могут значительно повысить эффективность нашей разработки.

Программа скаффолдинга также была установлена, когда ранее была установлена ​​библиотека cobra. Ниже мы опишем, как использовать этот генератор.

использоватьcobra initКоманда создает приложение Cobra:

$ cobra init scaffold --pkg-name github.com/darjun/go-daily-lib/cobra/scaffold

вscaffoldимя приложения, за которым следуетpkg-nameoption указывает путь к пакету. Сгенерированная структура каталогов программы выглядит следующим образом:

▾ scaffold/
    ▾ cmd/
        root.go
    LICENSE
    main.go

Эта структура проекта точно такая же, как и предыдущая, а также структура, рекомендованная Cobra. Так же,main.goТолько вход.

существуетroot.go, инструмент дополнительно генерирует для нас некоторый код.

В команду root добавлен параметр файла конфигурации, необходимый для большинства приложений:

func init() {
  cobra.OnInitialize(initConfig)

  rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.scaffold.yaml)")
  rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

В обратном вызове завершения инициализации, если параметр оказывается пустым, по умолчанию используется домашний каталог..scaffold.yamlдокумент:

func initConfig() {
  if cfgFile != "" {
    viper.SetConfigFile(cfgFile)
  } else {
    home, err := homedir.Dir()
    if err != nil {
      fmt.Println(err)
      os.Exit(1)
    }

    viper.AddConfigPath(home)
    viper.SetConfigName(".scaffold")
  }

  viper.AutomaticEnv()

  if err := viper.ReadInConfig(); err == nil {
    fmt.Println("Using config file:", viper.ConfigFileUsed())
  }
}

Вот что я представил несколько дней назадgo-homedirбиблиотека. Файл конфигурации читается с использованием собственного проекта spf13 с открытым исходным кодом.viper(Ядовитый дракон? Какой гений называть имена).

В дополнение к файлам кода, Cobra также создает файл LICENSE.

Сейчас эта программа ничего не умеет, нам нужно добавить к ней подкоманды, используяcobra addЗаказ:

$ cobra add date

Команда находится вcmdдобавлено в каталогdate.goдокумент. Базовая структура настроена, осталось только изменить некоторые описания и добавить некоторые параметры.

Теперь мы реализуем такую ​​функцию для печати календаря этого месяца в соответствии с входящим годом и месяцем. Если параметры не переданы, используются текущий год и месяц.

Определения опций:

func init() {
  rootCmd.AddCommand(dateCmd)

  dateCmd.PersistentFlags().IntVarP(&year, "year", "y", 0, "year to show (should in [1000, 9999]")
  dateCmd.PersistentFlags().IntVarP(&month, "month", "m", 0, "month to show (should in [1, 12]")
}

ИсправлятьdateCmdизRunфункция:

Run: func(cmd *cobra.Command, args []string) {
  if year < 1000 && year > 9999 {
    fmt.Fprintln(os.Stderr, "invalid year should in [1000, 9999], actual:%d", year)
    os.Exit(1)
  }

  if month < 1 && year > 12 {
    fmt.Fprintln(os.Stderr, "invalid month should in [1, 12], actual:%d", month)
    os.Exit(1)
  }

  showCalendar()
}

showCalendarфункция заключается в использованииtimeПредоставленный метод реализован и здесь повторяться не будет. Если вам интересно, вы можете зайти на мой GitHub, чтобы увидеть реализацию.

Посмотрите, как работает программа:

$ go build -o main.exe
$ ./main.exe date
  Sun  Mon  Tue  Wed  Thu  Fri  Sat
                   1    2    3    4
    5    6    7    8    9   10   11
    12   13   14   15   16   17   18
    19   20   21   22   23   24   25
    26   27   28   29   30   31

$ ./main.exe date --year 2019 --month 12
  Sun  Mon  Tue  Wed  Thu  Fri  Sat
    1    2    3    4    5    6    7
    8    9   10   11   12   13   14
   15   16   17   18   19   20   21
   22   23   24   25   26   27   28
   29   30   31

Вы можете добавить другие функции в эту программу, попробуйте~

разное

Cobra предоставляет очень богатые функции и настраиваемые интерфейсы, такие как:

  • Настройте функции ловушек для выполнения определенных операций до и после выполнения команды;
  • Создание документов в формате Markdown/ReStructed Text/Man Page;
  • И т. д. и т. д.

Из-за нехватки места они не будут представлены по одному. Заинтересованные могут провести собственное исследование. Библиотека cobra широко используется, и используется многими известными проектами, о которых также упоминалось ранее. Узнайте, как эти проекты используют Cobra, чтобы узнать о возможностях Cobra и рекомендациях. Это также отличный способ узнать о проектах с открытым исходным кодом.

Весь пример кода в этой статье был загружен на мой GitHub,Ежедневная библиотека,GitHub.com/Darenjun/go-of…

Ссылаться на

  1. cobraРепозиторий GitHub

я

мой блог

Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat [GoUpUp], учитесь вместе и добивайтесь прогресса вместе ~

Эта статья опубликована в блогеOpenWriteвыпуск!