One place for hosting & domains

      Como usar o pacote Flag em Go


      Introdução

      Os utilitários de linha de comando raramente são úteis, pois não vêm prontos para usar e necessitam de configuração adicional. Padrões bons são importantes, mas os utilitários úteis precisam aceitar a configuração dos usuários. Na maioria das plataformas, os utilitários de linha de comando aceitam sinalizadores (flags) que permitem personalizar a execução do comando. Os sinalizadores são strings de valores-chave delimitados, adicionadas após o nome do comando. A linguagem Go permite que você crie utilitários de linha de comando que aceitam sinalizadores, usando o pacote flag da biblioteca padrão.

      Neste tutorial, serão explorados várias maneiras de usar o pacote flag para construir diferentes tipos de utilitários de linha de comando. Você usará um sinalizador para controlar o resultado do programa, introduzir argumentos posicionais – nos quais você mistura sinalizadores e outros dados e, em seguida, implementar subcomandos.

      Usando um sinalizador para alterar o comportamento de um programa

      Usar o pacote flag envolve três passos: primeiro, defina as variáveis para capturar os valores de sinalizador; em seguida, defina os sinalizadores que o seu aplicativo em Go usará e, por fim, analise os sinalizadores fornecidos para o aplicativo na execução. A maioria das funções dentro do pacote flag diz respeito à definição dos sinalizadores e de conectá-los às variáveis que você tiver definido. A fase de análise é manipulada pela função Parse().

      Para ilustrar, você criará um programa que define um sinalizador Booleano, o qual altera a mensagem que será impressa em um resultado padrão. Se houver um sinalizador -color fornecido, o programa irá imprimir uma mensagem em azul. Se nenhum sinalizador for fornecido, a mensagem será impressa sem qualquer cor.

      Crie um novo arquivo chamado boolean.go:

      Adicione o código a seguir ao arquivo para criar o programa:

      boolean.go

      package main
      
      import (
          "flag"
          "fmt"
      )
      
      type Color string
      
      const (
          ColorBlack  Color = "u001b[30m"
          ColorRed          = "u001b[31m"
          ColorGreen        = "u001b[32m"
          ColorYellow       = "u001b[33m"
          ColorBlue         = "u001b[34m"
          ColorReset        = "u001b[0m"
      )
      
      func colorize(color Color, message string) {
          fmt.Println(string(color), message, string(ColorReset))
      }
      
      func main() {
          useColor := flag.Bool("color", false, "display colorized output")
          flag.Parse()
      
          if *useColor {
              colorize(ColorBlue, "Hello, DigitalOcean!")
              return
          }
          fmt.Println("Hello, DigitalOcean!")
      }
      

      Esse exemplo usa Sequências de Escape ANSI para instruir o terminal a exibir um resultado colorido. Essas são sequências especializadas de caracteres, de modo que faz sentido definir um novo tipo para elas. Nesse exemplo, chamamos àquele tipo de Color e definimos o tipo como uma string. Então, definimos uma paleta de cores para usar no bloco const que segue. A função colorize, definida após o bloco const, aceita uma dessas constantes Color e uma variável string para que a mensagem seja colorida. Depois, a função diz ao terminal para alterar a cor, imprimindo primeiro a sequência de escape da cor solicitada; em seguida, imprime a mensagem e, por fim, solicita que o terminal redefina sua cor, imprimindo a sequência especial para redefinição de cores.

      Dentro do main, usamos a função flag.Bool para definir um sinalizador booleano chamado color. O segundo parâmetro para esta função, false, define o valor padrão para esse sinalizador, quando não for fornecido. Ao contrário das expectativas que você possa ter, definir o parâmetro como true não reverte o comportamento na medida em que o fornecimento de um sinalizador fará com que um parâmetro se torne falso. Consequentemente, o valor desse parâmetro é quase sempre false com os sinalizadores booleanos.

      O parâmetro final é uma string de documentação que pode ser impressa como uma mensagem de uso. O valor retornado dessa função é um ponteiro para um bool. A função flag.Parse, na linha seguinte, usa esse ponteiro para definir a variável bool com base nos sinalizadores transmitidos pelo usuário. Depois disso, conseguiremos verificar o valor desse ponteiro bool, desreferenciando o ponteiro. No tutorial sobre ponteiros, você encontra mais informações sobre variáveis de ponteiros. Usando esse valor Booleano, podemos então chamar colorize quando um sinalizador -color estiver definido e chamar a variável fmt.Println quando o sinalizador estiver ausente.

      Salve o arquivo e execute o programa sem nenhum sinalizador:

      Você verá o seguinte resultado:

      Output

      Hello, DigitalOcean!

      Agora, execute este programa novamente com o sinalizador -color:

      O resultado será o mesmo texto, mas, dessa vez, na cor azul.

      Os sinalizadores não são os únicos valores enviados para os comandos. Você também pode enviar nomes de arquivo ou outros dados.

      Normalmente, os comandos receberão uma série de argumentos que agem como o assunto que o comando tem em foco. Por exemplo, o comando head - que imprime as primeiras linhas de um arquivo - com frequência, é invocado como head example.txt. O arquivo example.txt é um argumento posicional na invocação do comando head.

      A função Parse() continuará a analisar os sinalizadores que encontrar, até detectar um argumento não sinalizador. O pacote flag os disponibiliza através das funções Args() e Arg().

      Para demonstrar isso, você compilará uma reimplementação simplificada do comando head, o qual exibe várias das primeiras linhas de um determinado arquivo:

      Crie um novo arquivo chamado head.go e adicione o seguinte código:

      head.go

      package main
      
      import (
          "bufio"
          "flag"
          "fmt"
          "io"
          "os"
      )
      
      func main() {
          var count int
          flag.IntVar(&count, "n", 5, "number of lines to read from the file")
          flag.Parse()
      
          var in io.Reader
          if filename := flag.Arg(0); filename != "" {
              f, err := os.Open(filename)
              if err != nil {
                  fmt.Println("error opening file: err:", err)
                  os.Exit(1)
              }
              defer f.Close()
      
              in = f
          } else {
              in = os.Stdin
          }
      
          buf := bufio.NewScanner(in)
      
          for i := 0; i < count; i++ {
              if !buf.Scan() {
                  break
              }
              fmt.Println(buf.Text())
          }
      
          if err := buf.Err(); err != nil {
              fmt.Fprintln(os.Stderr, "error reading: err:", err)
          }
      }
      

      Primeiro, definimos uma variável count para reter o número de linhas que o programa deverá ler do arquivo. Então, definimos um sinalizador -n, usando a flag.IntVar e espelhando o comportamento do programa original head. Essa função nos permite enviar nosso próprio ponteiro para uma variável, em contraste com as funções flag que não têm o sufixo Var. Além dessa diferença, os demais parâmetros para a flag.IntVar seguem sua contraparte flag.Int: o nome do sinalizador, um valor padrão e uma descrição. Como no exemplo anterior, chamamos o flag.Parse() para processar a entrada do usuário.

      A próxima seção lê o arquivo. Primeiro, definimos uma variável io.Reader que será definida para o arquivo solicitado pelo usuário, ou para a entrada padrão enviada para o programa. Dentro da instrução if, usamos a função flag.Arg para acessar o primeiro argumento posicional depois de todos os sinalizadores. Se o usuário forneceu um nome de arquivo, este estará definido. Caso contrário, será a string vazia (""). Quando um nome de arquivo estiver presente, usamos a função os.Open para abrir aquele arquivo e definir o io.Reader que definimos anteriormente para aquele arquivo. Caso contrário, usamos o os.Stdin para ler a partir da entrada padrão.

      A seção final usa um *bufio.Scanner, criado com o bufio.NewScanner para ler linhas da entrada in da variável io.Reader. Iteramos até o valor da count usando um loop for, que chamará o break caso a verificação da linha com o buf.Scan produza um valor false, indicando que o número de linhas é menor do que o número solicitado pelo usuário.

      Execute este programa e exiba o conteúdo do arquivo que acabou de escrever, usando o head.go como o argumento do arquivo:

      • go run head.go -- head.go

      O separador -- é um sinalizador especial reconhecido pelo pacote flag, o qual indica que não haverá mais argumentos de sinalização na sequência. Quando executar este comando, você recebe o seguinte resultado:

      Output

      package main import ( "bufio" "flag"

      Use o sinalizador -n que você definiu para ajustar a quantidade de resultado:

      • go run head.go -n 1 head.go

      Isso gera apenas a instrução do pacote:

      Output

      package main

      Por fim, quando o programa detecta que nenhum argumento posicional foi fornecido, ele lê a entrada das entradas padrão, assim como com o head. Tente executar este comando:

      • echo "fishnlobstersnsharksnminnows" | go run head.go -n 3

      Você verá o resultado:

      Output

      fish lobsters sharks

      O comportamento das funções flag que você viu até agora tem sido limitado a examinar a invocação do comando inteiro. Nem sempre você vai desejar esse comportamento, especialmente se estiver escrevendo uma ferramenta de linha de comando que suporte subcomandos.

      Usando o flagSet para implementar subcomandos

      Os aplicativos de linha de comando modernos com frequência implementam “subcomandos” para agrupar um conjunto de ferramentas sob um único comando. A ferramenta mais conhecida que usa esse padrão é a git. Ao examinar um comando como o git init, o git é o comando e o init é o subcomando do git. Uma característica notável dos subcomandos é que cada subcomando pode ter sua própria coleção de sinalizadores.

      Os aplicativos em Go podem ser oferecer suporte aos subcomandos com seu próprio conjunto de sinalizadores, usando o tipo flag.( *FlagSet). Para demonstrar isso, crie um programa que implementa um comando usando dois subcomandos com sinalizadores diferentes.

      Crie um novo arquivo chamado subcommand.go e adicione o seguinte conteúdo ao arquivo:

      package main
      
      import (
          "errors"
          "flag"
          "fmt"
          "os"
      )
      
      func NewGreetCommand() *GreetCommand {
          gc := &GreetCommand{
              fs: flag.NewFlagSet("greet", flag.ContinueOnError),
          }
      
          gc.fs.StringVar(&gc.name, "name", "World", "name of the person to be greeted")
      
          return gc
      }
      
      type GreetCommand struct {
          fs *flag.FlagSet
      
          name string
      }
      
      func (g *GreetCommand) Name() string {
          return g.fs.Name()
      }
      
      func (g *GreetCommand) Init(args []string) error {
          return g.fs.Parse(args)
      }
      
      func (g *GreetCommand) Run() error {
          fmt.Println("Hello", g.name, "!")
          return nil
      }
      
      type Runner interface {
          Init([]string) error
          Run() error
          Name() string
      }
      
      func root(args []string) error {
          if len(args) < 1 {
              return errors.New("You must pass a sub-command")
          }
      
          cmds := []Runner{
              NewGreetCommand(),
          }
      
          subcommand := os.Args[1]
      
          for _, cmd := range cmds {
              if cmd.Name() == subcommand {
                  cmd.Init(os.Args[2:])
                  return cmd.Run()
              }
          }
      
          return fmt.Errorf("Unknown subcommand: %s", subcommand)
      }
      
      func main() {
          if err := root(os.Args[1:]); err != nil {
              fmt.Println(err)
              os.Exit(1)
          }
      }
      

      Esse programa está dividido em algumas partes: a função main, a função root e as funções individuais para implementar o subcomando. A função main manipula os erros retornados dos comandos. Caso alguma função retorne um erro, a instrução if irá capturá-lo, imprimi-lo e o programa fechará com um código de status de 1, indicando ao resto do sistema operacional que um erro ocorreu. Dentro do main, enviamos todos os argumentos com os quais o programa foi invocado para o root. Removemos o primeiro argumento, que é o nome do programa (nos exemplos anteriores ./subcommand) dividindo o os.Args primeiro.

      A função root define o []Runner, onde todos os subcomandos seriam definidos. Runner é uma interface para subcomandos que permite à root recuperar o nome do subcomando usando o Name() e compará-lo ao conteúdo da variável subcommand. Assim que o subcomando correto for localizado, após a iteração através da variável cmds, inicializamos o subcomando com o resto dos argumentos e invocamos o método Run () do comando.

      Definimos apenas um subcomando, embora esse framework nos permita facilmente criar outros. O GreetCommand é instanciado usando o NewGreetCommand, onde criamos um novo *flag.FlagSet usando o flag.NewFlagSet. O flag.NewFlagSet recebe dois argumentos: um nome para o conjunto de sinalizadores e uma estratégia para a comunicação dos erros de análise. O nome do *flag.FlagSet é acessível usando o método flag.( *FlagSet). Name. Usamos isso no método (*GreetCommand). Name() para que o nome do subcomando corresponda ao nome que demos ao *flag.FlagSet. O NewGreetCommand também define um sinalizador -name de maneira semelhando à dos exemplos anteriores, mas ele chama isso como um método fora do campo *flag.FlagSet do *GreetCommand, gc.fs. Quando a root chama o método Init() do *GreetCommand, enviamos os argumentos fornecidos para o método Parse do campo *flag.FlagSet.

      Será mais fácil ver os subcomandos se você compilar este programa e, em seguida, executá-lo. Compile o programa:

      Agora, execute o programa sem argumentos:

      Você verá este resultado:

      Output

      You must pass a sub-command

      Agora, execute o programa com o subcomando greet:

      Isso produz o seguinte resultado:

      Output

      Hello World !

      Agora, utilize o sinalizador -name com o greet para especificar um nome:

      • ./subcommand greet -name Sammy

      Você verá este resultado do programa:

      Output

      Hello Sammy !

      Esse exemplo ilustra alguns princípios por trás do fato de como aplicativos de linha de comando maiores poderiam ser estruturados em Go. Os FlagSets foram desenvolvidos para dar aos desenvolvedores mais controle sobre onde e como os sinalizadores são processadas pela lógica de análise de sinalizadores.

      Conclusão

      Os sinalizadores tornam seus aplicativos mais úteis em mais contextos porque dão aos usuários controle sobre como os programas são executados. É importante dar aos usuários padrões úteis, mas você também deve dar a eles a oportunidade de substituir as configurações que não funcionam para sua situação. Você viu que o pacote flag oferece escolhas flexíveis para apresentar opções de configuração aos seus usuários. Você pode escolher alguns sinalizadores simples ou compilar um conjunto expansível de subcomandos. Em qualquer caso, usar o pacote flag ajudará na compilação de utilitários no estilo da longa história de ferramentas de linha de comando flexíveis e com scripts.

      Para aprender mais sobre a linguagem de programação Go, confira nossa série completa de artigos sobre Como programar em Go.



      Source link

      Cómo usar el paquete flag en Go


      Introducción

      Los servicios de línea de comandos rara vez son útiles cuando vienen listos para usar sin configuración adicional. Es importante disponer de buenos valores predeterminados, pero las utilidades prácticas deben aceptar la configuración de parte de los usuarios. En la mayoría de las plataformas, los servicios de línea de comandos aceptan indicadores para personalizar la ejecución del comando. Los parámetros son cadenas delimitadas por el valor de clave agregadas después del nombre del comando. Go le permite crear servicios de línea de comandos que acepten indicadores usando el paquete flag de la biblioteca estándar.

      En este tutorial, explorará varias formas de usar el paquete flag para crear diferentes tipos de utilidades de línea de comandos. Utilizará un indicador para controlar el resultado del programa, introducir argumentos en posición en los que intercambie indicadores y otros datos, y luego implementará subcomandos.

      Cómo usar un indicador para cambiar el comportamiento de un programa

      Al usar el paquete flag se deben seguir tres pasos: primero, definir variables para capturar valores de indicadores, definir los parámetros que utilizará su aplicación de Go y, por último, analizar los parámetros proporcionados a la aplicación al ejecutarse. La mayor parte de las funciones del paquete flag se ocupan de definir los indicadores y vincularlos a las variables que definió. La función Parse() lleva a cabo la fase de análisis.

      Para ilustrar esto, creará un programa que defina un indicador booleano que cambie el mensaje que se imprimirá para un resultado estándar. Si se proporciona un indicador -color, el programa imprimirá un mensaje en azul. Si no se proporciona un indicador, el mensaje se imprimirá sin color.

      Cree un nuevo archivo llamado boolean.go:

      Añada el siguiente código al archivo para crear el programa:

      boolean.go

      package main
      
      import (
          "flag"
          "fmt"
      )
      
      type Color string
      
      const (
          ColorBlack  Color = "u001b[30m"
          ColorRed          = "u001b[31m"
          ColorGreen        = "u001b[32m"
          ColorYellow       = "u001b[33m"
          ColorBlue         = "u001b[34m"
          ColorReset        = "u001b[0m"
      )
      
      func colorize(color Color, message string) {
          fmt.Println(string(color), message, string(ColorReset))
      }
      
      func main() {
          useColor := flag.Bool("color", false, "display colorized output")
          flag.Parse()
      
          if *useColor {
              colorize(ColorBlue, "Hello, DigitalOcean!")
              return
          }
          fmt.Println("Hello, DigitalOcean!")
      }
      

      En este ejemplo, se utilizan secuencias de escape ANSI para dar instrucciones al terminal a fin de que muestre un resultado colorido. Estas son secuencias especializadas de caracteres, por lo que tiene sentido definir un nuevo tipo para ellos. En este ejemplo, asignamos el nombre Color al tipo y lo definimos como una string. Luego, definimos una paleta de colores para usar en el bloque const que sigue. La función colorize definida después del bloque const acepta una de estas constantes Color y una variable string para que el mensaje sea de color. Luego, indica al terminal que cambie de color imprimiendo primero la secuencia de escape para el color solicitado, luego imprime el mensaje y finalmente solicita que el terminal restablezca su color imprimiendo la secuencia especial de restablecimiento de color.

      En main, usamos la función flag.Bool paradefinirun indicador booleano llamado color. El segundo parámetro de esta función, false, establece el valor predeterminado para este indicador cuando no se proporciona. En contra de las expectativas que pueda tener, fijar esto en true no invierte el comportamiento de modo tal que la incorporación de un indicador haga que se convierta en falso. Por lo tanto, el valor de este parámetro es casi siempre false con indicadores de booleano.

      El parámetro final es una cadena de documentación que puede imprimirse como mensaje de uso. El valor devuelto de esta función es un puntero de un bool. La función flag.Parse en la siguiente línea utiliza este puntero para configurar la variable bool en función de los indicadores pasados por el usuario. Luego, podemos verificar el valor de este puntero bool eliminado su referencia. Se puede encontrar más información sobre las variables de punteros en el tutorial sobre punteros. Mediante este valor booleano, podemos invocar colorize cuando esté configurado el puntero -color e invocar la variable fmt.Println cuando no se encuentre el indicador.

      Guarde el archivo y ejecute el programa sin indicadores:

      Verá el siguiente resultado:

      Output

      Hello, DigitalOcean!

      Ahora, ejecute este programa de nuevo con el indicador color:

      El resultado será el mismo texto, pero esta vez en color azul.

      Los indicadores no son los únicos valores pasados a comandos. Es posible que también envíe nombres de archivo u otros datos.

      Trabajar con argumentos posicionales

      Normalmente, los comandos toman una serie de argumentos que actúan como sujeto del foco del comando. Por ejemplo, el comando head, que imprime las primeras líneas de un archivo, a menudo se invoca como head example.txt. El archivo example.txt es un argumento posicional en la invocación del comando head.

      La función Parse() continuará analizando los indicadores que encuentre hasta detectar un argumento sin indicador. El paquete flag los pone a disposición a través de las funciones Args () y Arg ().

      Para ilustrar esto, compilará una nueva implementación simplificada del comando head, que muestra las primeras líneas de un archivo dado:

      Cree un nuevo archivo llamado head.go y agregue el siguiente código:

      head.go

      package main
      
      import (
          "bufio"
          "flag"
          "fmt"
          "io"
          "os"
      )
      
      func main() {
          var count int
          flag.IntVar(&count, "n", 5, "number of lines to read from the file")
          flag.Parse()
      
          var in io.Reader
          if filename := flag.Arg(0); filename != "" {
              f, err := os.Open(filename)
              if err != nil {
                  fmt.Println("error opening file: err:", err)
                  os.Exit(1)
              }
              defer f.Close()
      
              in = f
          } else {
              in = os.Stdin
          }
      
          buf := bufio.NewScanner(in)
      
          for i := 0; i < count; i++ {
              if !buf.Scan() {
                  break
              }
              fmt.Println(buf.Text())
          }
      
          if err := buf.Err(); err != nil {
              fmt.Fprintln(os.Stderr, "error reading: err:", err)
          }
      }
      

      Primero, definimos una variable count para contener el número de líneas que el programa debería leer del archivo. Luego definimos el indicador -n usando flag.IntVar, para reflejar el comportamiento del programa head original. Esta función nos permite pasar nuestro propio puntero a una variable en contraste con las funciones flag que no tienen el sufijo Var. Además de esta diferencia, el resto de los parámetros de flag.IntVar siguen a su homólogo flag.Int: el nombre del indicador, un valor predeterminado y una descripción. Como en el ejemplo anterior, invocamos a flag.Parse () para procesar la entrada del usuario.

      La siguiente sección lee el archivo. Primero definimos una variable io.Reader que se establecerá en el archivo solicitado por el usuario o la entrada estándar pasada al programa. Dentro de la instrucción if, usamos la función flag.Arg para acceder al primer argumento posicional después de todos los indicadores. Si el usuario proporcionó un nombre de archivo, se establecerá. De lo contrario, será la cadena vacía (""). Cuando hay un nombre de archivo presente, usamos la función os.Open para abrir ese archivo y configurar el io.Reader que definimos antes para él. De lo contrario, usamos os.Stdin para la lectura desde entrada estándares.

      En la sección final se utiliza un * bufio.Scanner creado con bufio.NewScanner para leer líneas de la variable in de io.Reader. Realizamos iteraciones hasta el valor de count utilizando un bucle for, invocando break si del análisis de la línea con buf.Scan surge un valor false, lo cual indica que el número de líneas es menor que el número solicitado por el usuario.

      Ejecute este programa y muestre el contenido del archivo que acaba de escribir usando head.go como argumento del archivo:

      • go run head.go -- head.go

      El separador -- es un indicador especial reconocido por el paquete flag que señala que no hay más argumentos de indicador a continuación. Cuando ejecute este comando, verá el siguiente resultado:

      Output

      package main import ( "bufio" "flag"

      Utilice el indicador -n que definió para ajustar la cantidad de resultado:

      • go run head.go -n 1 head.go

      Como resultado solo se muestra la instrucción del paquete:

      Output

      package main

      Por último, cuando el programa detecta que no se proporcionaron argumentos posicionales, lee la entrada estándar como en el caso de head. Intente ejecutar este comando:

      • echo "fishnlobstersnsharksnminnows" | go run head.go -n 3

      Verá el resultado:

      Output

      fish lobsters sharks

      El comportamiento de las funciones flag que vio hasta ahora se limitó a examinar toda la invocación del comando. No siempre le convendrá este comportamiento, sobre todo si escribe una herramienta de línea de comandos que admite subcomandos.

      Usar FlagSet para implementar subcomandos

      Las aplicaciones de línea de comandos modernas a menudo implementan “subcomandos” para empaquetar una serie de herramientas bajo un único comando. La herramienta más conocida que utiliza este patrón es git. Si se examina un comando como git init, git es el comando e init es el subcomando de git. Una característica notable de los subcomandos es que cada uno puede tener su propio conjunto de indicadores.

      Las aplicaciones de Go pueden admitir subcomandos con su propio conjunto de indicadores usando el tipo flag.( *FlagSet). Para ilustrar esto, cree un programa que ejecute un comando usando dos subcomandos con diferentes indicadores.

      Cree un nuevo archivo llamado subcommand.go y agregue a este el siguiente contenido:

      package main
      
      import (
          "errors"
          "flag"
          "fmt"
          "os"
      )
      
      func NewGreetCommand() *GreetCommand {
          gc := &GreetCommand{
              fs: flag.NewFlagSet("greet", flag.ContinueOnError),
          }
      
          gc.fs.StringVar(&gc.name, "name", "World", "name of the person to be greeted")
      
          return gc
      }
      
      type GreetCommand struct {
          fs *flag.FlagSet
      
          name string
      }
      
      func (g *GreetCommand) Name() string {
          return g.fs.Name()
      }
      
      func (g *GreetCommand) Init(args []string) error {
          return g.fs.Parse(args)
      }
      
      func (g *GreetCommand) Run() error {
          fmt.Println("Hello", g.name, "!")
          return nil
      }
      
      type Runner interface {
          Init([]string) error
          Run() error
          Name() string
      }
      
      func root(args []string) error {
          if len(args) < 1 {
              return errors.New("You must pass a sub-command")
          }
      
          cmds := []Runner{
              NewGreetCommand(),
          }
      
          subcommand := os.Args[1]
      
          for _, cmd := range cmds {
              if cmd.Name() == subcommand {
                  cmd.Init(os.Args[2:])
                  return cmd.Run()
              }
          }
      
          return fmt.Errorf("Unknown subcommand: %s", subcommand)
      }
      
      func main() {
          if err := root(os.Args[1:]); err != nil {
              fmt.Println(err)
              os.Exit(1)
          }
      }
      

      Este programa se divide en algunas partes: la función main, la función root y las funciones individuales para implementar el subcomando. La función main maneja los errores que muestran los comandos. Si alguna función muestra un error, la instrucción if lo detectará y lo imprimirá, y el programa se cerrará con un código de estado 1, lo cual indica que se produjo un error en el resto del sistema operativo. Dentro de main, pasamos todos los argumentos con los que se invocó el programa a root. Eliminamos el primer argumento, que es el nombre del programa (en los ejemplos anteriores, ./subcommand) cortando os.Args primero.

      La función root define [] Runner, donde se definirían todos los subcomandos. Runner es una interfaz para subcomandos que permite a root recuperar el nombre del subcomando usando Name () y compararlo con la variable subcommand de contenido. Una vez que se encuentre el subcomando correcto después de la iteración de la variable cmds, inicializamos el subcomando con el resto de los argumentos e invocamos el método Run() de ese comando.

      Solo definimos un subcomando, aunque este marco nos permitiría crear fácilmente otros. Se crean instancias de GreetCommand usando NewGreetCommand donde creamos un nuevo *flag.FlagSet usando flag.NewFlagSet. flag.NewFlagSet toma dos argumentos: un nombre para el conjunto de indicadores y una estrategia para informar errores de análisis. Es posible acceder al nombre de *flag.FlagSet con el método flag.( *FlagSet. Name. Lo usamos en (*GreetCommand). El método Name() para que el nombre del subcomando coincida con el nombre que asignamos a *flag.FlagSet. NewGreetCommand también define un indicador -name de una manera similar a la de los ejemplos anteriores, pero como alternativa lo invoca como un método fuera del campo *flag.FlagSet del *GreetCommand, gc.fs. Cuando root invoca al método Init () del * GreetCommand, pasamos los argumentos proporcionados al método Parse del campo *flag.FlagSet.

      Será más sencillo ver subcomandos si compila este programa y lo ejecuta. Compile el programa:

      Ahora, ejecute el programa sin argumentos:

      Verá este resultado:

      Output

      You must pass a sub-command

      Ahora, ejecute el programa con el subcomando greet:

      Esto produce el siguiente resultado:

      Output

      Hello World !

      Ahora, utilice el indicador -name con greet para especificar un nombre:

      • ./subcommand greet -name Sammy

      Verá este resultado del programa:

      Output

      Hello Sammy !

      Este ejemplo permite ver algunos principios relacionados con la estructura que podrían tener las aplicaciones de línea de comandos más grandes en Go. Los FlagSets están diseñados para dar a los desarrolladores más control respecto de dónde y cómo lógica de análisis de indicadores los procesa.

      Conclusión

      Los indicadores hacen que sus aplicaciones sean más útiles en más contextos porque dan a sus usuarios control respecto de cómo se ejecutan los programas. Es importante proporcionar a los usuarios valores predeterminados útiles, pero debería darles la oportunidad de anular ajustes que no funcionen en sus casos. Pudo observar que el paquete flag ofrece alternativas flexibles para presentar opciones de configuración a sus usuarios. Puede elegir algunos indicadores sencillos o crear una serie de subcomandos extensible. En cualquiera de los casos, usar el paquete flag le permitirá crear utilidades siguiendo el estilo de la larga historia de herramientas de línea de comandos flexibles y programables.

      Para obtener más información sobre el lenguaje de programación de Go, consulte nuestra serie Cómo realizar codificaciones en Go.



      Source link

      Использование пакета Flag в Go


      Введение

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

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

      Использование флага для изменения поведения программы

      Использование пакета flag предусматривает три шага. Вначале определяются переменные для сбора значений флагов, затем определяются флаги для использования приложением Go, и в заключение производится проверка синтаксиса флагов, переданных приложению при выполнении. Большинство функций в пакете flag связаны с определением флагов и их привязке к определенным вами переменным. Синтаксическая проверка выполняется функцией Parse().

      Для иллюстрации мы создадим программу, определяющую флаг Boolean для изменения сообщения, которое потом выводится стандартным способом. Если используется флаг -color, программа выводит сообщение синим цветом. Если флаг не указан, сообщение выводится без цветов.

      Создайте новый файл с именем boolean.go:

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

      boolean.go

      package main
      
      import (
          "flag"
          "fmt"
      )
      
      type Color string
      
      const (
          ColorBlack  Color = "u001b[30m"
          ColorRed          = "u001b[31m"
          ColorGreen        = "u001b[32m"
          ColorYellow       = "u001b[33m"
          ColorBlue         = "u001b[34m"
          ColorReset        = "u001b[0m"
      )
      
      func colorize(color Color, message string) {
          fmt.Println(string(color), message, string(ColorReset))
      }
      
      func main() {
          useColor := flag.Bool("color", false, "display colorized output")
          flag.Parse()
      
          if *useColor {
              colorize(ColorBlue, "Hello, DigitalOcean!")
              return
          }
          fmt.Println("Hello, DigitalOcean!")
      }
      

      В этом примере используются управляющие последовательности ANSI, предписывающие терминалу выводить цветной текст. Это специальные последовательности символов, поэтому для них полезно определить новый тип. В этом примере мы присвоили типу имя Color и определили его как string. Затем мы определили палитру цветов для использования в следующем блоке const. Функция colorize, определяемая после блока const, принимает одну из констант Color и переменную string для сообщения, которому назначается цвет. Затем она предписывает терминалу изменить цвет, сначала выводя управляющую последовательность для запрошенного цвета, а затем выводя сообщение, и, наконец, запрашивает сброс цвета терминала посредством вывода специальной последовательности сброса цвета.

      В main мы используем функцию flag.Bool для определения флага логического оператора с именем color. Второй параметр этой функции, false, задает значение этого флага по умолчанию, если оно отсутствует. Хотя вы можете ожидать иного, установка значения true не меняет поведения, так как при задании флага оно становится ложным. Поэтому этот параметр почти всегда будет иметь значение false с флагами логических операторов.

      Последний параметр — это строка документации, которая может быть выведена как сообщение об использовании. Возвращаемое этой функцией значение представляет собой указатель на bool. Функция flag.Parse на следующей строке использует этот указатель, чтобы задать переменную bool на базе флагов, передаваемых пользователем. Мы сможем проверить значение этого указателя bool, сняв ссылку на указатель. Дополнительную информацию о переменных указателей можно найти в обучающем руководстве по указателям. Используя это логическое значение, мы можем вызывать функцию colorize, если флаг -color установлен, и вызывать переменную fmt.Println, если флаг отсутствует.

      Сохраните файл и запустите программу без флагов:

      Вывод должен выглядеть так:

      Output

      Hello, DigitalOcean!

      Теперь запустите эту программу еще раз с помощью флага -color:

      При этом также будет выведен текст, но при этом синим цветом.

      Флаги — не единственные значения, которые передаются командам. Также вы можете отправлять имена файлов или другие данные.

      Работа с позиционными аргументами

      Обычно команды принимают несколько аргументов, которые выступают в качестве субъекта фокуса команды. Например, команда head, которая распечатывает первые строки файла, часто вызывается как head example.txt. Файл example.txt представляет собой позиционный аргумент вызова команды head.

      Функция Parse() продолжает выполнять синтаксическую проверку появляющихся флагов, пока не обнаружит аргумент, не являющийся флагом. Пакет flag делает их доступными через функции Args() и Arg().

      В качестве иллюстрации построим упрощенную реализацию команды head, отображающей первые несколько строк указанного файла:

      Создайте новый файл head.go и добавьте следующий код:

      head.go

      package main
      
      import (
          "bufio"
          "flag"
          "fmt"
          "io"
          "os"
      )
      
      func main() {
          var count int
          flag.IntVar(&count, "n", 5, "number of lines to read from the file")
          flag.Parse()
      
          var in io.Reader
          if filename := flag.Arg(0); filename != "" {
              f, err := os.Open(filename)
              if err != nil {
                  fmt.Println("error opening file: err:", err)
                  os.Exit(1)
              }
              defer f.Close()
      
              in = f
          } else {
              in = os.Stdin
          }
      
          buf := bufio.NewScanner(in)
      
          for i := 0; i < count; i++ {
              if !buf.Scan() {
                  break
              }
              fmt.Println(buf.Text())
          }
      
          if err := buf.Err(); err != nil {
              fmt.Fprintln(os.Stderr, "error reading: err:", err)
          }
      }
      

      Вначале мы определяем переменную count, где будет храниться количество строк, которое программа должна считывать из файла. Мы определяем флаг -n, используя flag.IntVar, что отражает поведение первоначальной программы head. Эта функция позволяет нам передать собственный указатель в переменную в отличие от функций flag, у которых нет суффикса Var. Помимо этой разницы остальные параметры flag.IntVar соответствуют параметрам flag.Int: имя флага, значение по умолчанию и описание. Как и в предыдущем примере, мы вызовем flag.Parse() для обработки пользовательских данных.

      Следующий раздел выполняет чтение файла. Мы определим переменную io.Reader, для которой будет задан запрошенный пользователем файл или которой будут передаваться стандартные входные данные программы. В выражении if мы используем функцию flag.Arg для доступа к первому аргументу после всех флагов. Если пользователь указал имя файла, оно будет задано. В противном случае, это будет пустая строка (""). Если имя файла имеется в наличии, мы используем функцию os.Open для открытия файла и задаем предварительно определенный для этого файла io.Reader. В противном случае мы используем os.Stdin для считывания стандартных исходных данных.

      В заключительном разделе используется *bufio.Scanner, созданный с помощью bufio.NewScanner, для считывания строк из переменной io.Reader in. Мы проводим итерацию до значения count в цикле for и вызываем break, если при сканировании строчки buf.Scan получается значение false, показывающее, что количество строчек меньше, чем запрошенное пользователем.

      Запустите эту программу и выведите содержимое записанного файла, используя head.go в качестве аргумента файла:

      • go run head.go -- head.go

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

      Output

      package main import ( "bufio" "flag"

      Используйте определенный вами флаг -n для изменения объема выводимых данных:

      • go run head.go -n 1 head.go

      Так выводится только выражение пакета:

      Output

      package main

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

      • echo "fishnlobstersnsharksnminnows" | go run head.go -n 3

      Результат должен выглядеть так:

      Output

      fish lobsters sharks

      Поведение функций flag, которое мы наблюдали, ограничено исследованием вызова всей команды. Такое поведение требуется не всегда, особенно если вы пишете инструмент командной строки, поддерживающий субкоманды.

      Использование FlagSet для реализации субкоманд

      В современных приложениях командной строки часто реализуются субкоманды, что позволяет объединить набор инструментов в одной команде. Самый известный инструмент, использующий такую схему, называется git. При проверке такой команды как git init командой является git, а init является субкомандой git. Важная характеристика субкоманд заключается в том, что каждая субкоманда может иметь собственный набор флагов.

      Приложения Go могут поддерживать субкоманды с собственным набором флагов, используя оператор типа flag.( *FlagSet). Для иллюстрации мы создадим программу, которая будет реализовать команду, используя две субкоманды с разными флагами.

      Создайте новый файл с именем subcommand.go и добавьте в него следующий код:

      package main
      
      import (
          "errors"
          "flag"
          "fmt"
          "os"
      )
      
      func NewGreetCommand() *GreetCommand {
          gc := &GreetCommand{
              fs: flag.NewFlagSet("greet", flag.ContinueOnError),
          }
      
          gc.fs.StringVar(&gc.name, "name", "World", "name of the person to be greeted")
      
          return gc
      }
      
      type GreetCommand struct {
          fs *flag.FlagSet
      
          name string
      }
      
      func (g *GreetCommand) Name() string {
          return g.fs.Name()
      }
      
      func (g *GreetCommand) Init(args []string) error {
          return g.fs.Parse(args)
      }
      
      func (g *GreetCommand) Run() error {
          fmt.Println("Hello", g.name, "!")
          return nil
      }
      
      type Runner interface {
          Init([]string) error
          Run() error
          Name() string
      }
      
      func root(args []string) error {
          if len(args) < 1 {
              return errors.New("You must pass a sub-command")
          }
      
          cmds := []Runner{
              NewGreetCommand(),
          }
      
          subcommand := os.Args[1]
      
          for _, cmd := range cmds {
              if cmd.Name() == subcommand {
                  cmd.Init(os.Args[2:])
                  return cmd.Run()
              }
          }
      
          return fmt.Errorf("Unknown subcommand: %s", subcommand)
      }
      
      func main() {
          if err := root(os.Args[1:]); err != nil {
              fmt.Println(err)
              os.Exit(1)
          }
      }
      

      Эта программа разделена на несколько частей: функция main, функция root и отдельные функции для реализации субкоманды. Функция main обрабатывает ошибки, возвращаемые командами. Если любая функция возвращает ошибку, выражение if определит ее, распечатает ошибку и закроет программу с кодом состояния 1, сообщающим операционной системе о возникновении ошибки. Внутри функции main мы передаем все аргументы вызова программы в функцию root. Удалим первый аргумент, представляющий собой имя программы (в предыдущих примерах ./subcommand), выделив срез os.Args.

      Функция root определяет []Runner, где определяются все субкоманды. Runner — это интерфейс субкоманд, позволяющий функции root получить имя субкоманды с помощью Name() и сравнить его с содержанием переменной subcommand. После обнаружения правильной субкоманды посредством итерации переменной cmds мы инициализируем субкоманду с остальными аргументами и вызываем метод Run() для этой команды.

      Мы определяем только одну субкоманду, хотя данная структура позволяет легко создавать и другие команды. Экземпляр GreetCommand создается с помощью NewGreetCommand при создани нового *flag.FlagSet с помощью flag.NewFlagSet. flag.NewFlagSet принимает два аргумента: имя набора флагов и стратегию отчета об ошибках проверки синтаксиса. Имя *flag.FlagSet доступно с помощью flag.( *FlagSet). Метод Name. Мы используем этот метод в (*GreetCommand). Name() так, что имя субкоманды соответствует имени, которое мы присвоили *flag.FlagSet. NewGreetCommand также определяет флаг -name аналогично предыдущим примерам, но вместо этого вызывает его как метод поля *flag.FlagSet *GreetCommand, gc.fs. Когда функция root вызывает метод Init() команды *GreetCommand, мы передаем указанные аргументы методу Parse поля *flag.FlagSet.

      Субкоманды будет проще увидеть, если вы соберете эту программу и запустите ее. Выполните сборку программы:

      Теперь запустите программу без аргументов:

      Вы увидите следующий результат:

      Output

      You must pass a sub-command

      Теперь запустите команду с субкомандой greet:

      Результат будет выглядеть следующим образом:

      Output

      Hello World !

      Теперь используйте флаг -name с greet для указания имени:

      • ./subcommand greet -name Sammy

      Программа выведет следующее:

      Output

      Hello Sammy !

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

      Заключение

      Флаги делают приложения более полезными в разных условиях, поскольку они дают пользователям контроль над способом выполнения программы. Очень важно дать пользователям полезные параметры по умолчанию, но также следует дать им возможностям изменять параметры, которые не подходят для их конкретной ситуации. Вы увидели, что пакет flag предоставляет вашим пользователям гибкие возможности выбора конфигурации. Вы можете выбрать несколько простых флагов или создать разветвленный набор субкоманд. В любом случае, использование пакета flag поможет вам выполнить сборку утилит в стиле длинной истории гибких инструментов командной строки с поддержкой сценариев.

      Ознакомьтесь с серией статей по программированию на Go, чтобы узнать больше о языке программирования Go.



      Source link