ENTENDA A IDEIA
A ideia central
Você já recebeu um script que exige editar uma constante antes de cada uso. Para trocar o nome de entrada, abre o arquivo; para mudar o formato, altera outra linha; para descobrir as opções, procura no código. Uma CLI profissional move essas escolhas para a chamada. O executável permanece igual, enquanto flags como -name Bia descrevem o que varia naquela execução.
ENTENDA A IDEIA
Pense no início do programa como a recepção de um prédio. Os tokens chegam em fila, mas ainda não têm significado. A recepção confere o crachá de cada um, converte valores para o tipo esperado e entrega uma ficha estruturada ao restante do sistema. O pacote flag faz essa triagem. Sem ela, cada comando inventa cortes de string, aceita grafias diferentes e produz erros incompatíveis.
Uma flag é uma opção nomeada, como -upper; um argumento posicional é um valor cuja função depende da posição. Flags comunicam melhor opções opcionais porque a ordem pode variar: -upper -name Bia e -name Bia -upper dizem a mesma coisa. Argumentos posicionais funcionam bem para entidades centrais, mas precisam de contrato explícito. Nesta primeira versão não aceitaremos nenhum posicional: qualquer sobra será erro, não entrada silenciosamente ignorada.
ENTENDA A IDEIA
Em menos de dez minutos você terá um programa executável que aceita nome, aplica um valor padrão e transforma a saída. Rode go run . -name Bia -upper e obtenha OI, BIA!. Rode sem flags e obtenha Oi, mundo!. Esse primeiro ganho já serve para geradores, scripts internos e pequenos utilitários; o restante da aula fortalece erros e testabilidade.
A decisão importante é não acoplar parsing a os.Args nem à saída global. Um flag.FlagSet recebe um slice escolhido pelo chamador e pode escrever diagnósticos em um destino controlado. Isso permite testar a recepção com casos determinísticos no navegador, sem fingir um processo do sistema. A função principal fica responsável apenas por conectar a lógica ao processo real.
EXPERIMENTE
Experimente com o código
No programa abaixo, antes de rodar, preveja a saída com args := []string{"-times", "2", "-name", "Go"} e depois com args := []string{}. O valor padrão deve aparecer sem condição extra na aplicação. Em seguida, inverta a ordem das flags e confirme que o resultado não muda.
Faça mais duas mudanças: use -times nope para observar validação de tipo e depois acrescente -times 0. Decida se zero deve ser aceito. O parser garante que é inteiro, mas a regra de negócio precisa decidir o intervalo válido; são responsabilidades diferentes.
CÓDIGO ANOTADO
Código anotado
O programa completo cria um conjunto de flags exclusivo. Os imports revelam as duas responsabilidades: interpretar opções e construir texto. io.Discard evita que o parser imprima por conta própria; a aplicação decide como apresentar cada erro.
package main
import (
"flag"
"fmt"
"io"
"strings"
) CÓDIGO ANOTADO
A struct é a ficha entregue pela recepção. O restante do programa não precisa conhecer ponteiros internos de flag nem tokens crus.
type options struct {
name string
upper bool
} CÓDIGO ANOTADO
ContinueOnError devolve o erro em vez de encerrar o processo. Essa escolha é essencial para reutilização: uma função de parsing não deve decidir quando toda a aplicação termina.
func parseOptions(args []string) (options, error) {
flags := flag.NewFlagSet("greet", flag.ContinueOnError)
flags.SetOutput(io.Discard)
name := flags.String("name", "mundo", "nome da pessoa")
upper := flags.Bool("upper", false, "usa letras maiúsculas") CÓDIGO ANOTADO
Depois do parse, verificamos tokens restantes. Sem essa guarda, greet arquivo.txt pareceria ter funcionado embora o arquivo nunca fosse usado.
if err := flags.Parse(args); err != nil {
return options{}, err
}
if flags.NArg() != 0 {
return options{}, fmt.Errorf("argumento inesperado: %s", flags.Arg(0))
}
return options{name: *name, upper: *upper}, nil
} CÓDIGO ANOTADO
A transformação recebe valores, não parser. Ela pode ser chamada em teste, em outro comando ou por uma interface futura.
func greeting(config options) string {
message := fmt.Sprintf("Oi, %s!", config.name)
if config.upper {
return strings.ToUpper(message)
}
return message
} CÓDIGO ANOTADO
Para o exemplo rodar no navegador, main fornece argumentos determinísticos. No executável instalado, você trocaria esse slice por os.Args[1:]; a regra não muda.
func main() {
config, err := parseOptions([]string{"-name", "Bia", "-upper"})
if err != nil {
fmt.Println("erro:", err)
return
}
fmt.Println(greeting(config))
} PASSO A PASSO
Veja a ideia em movimento
Avance pelos passos e observe como o estado muda a cada decisão.
DESAFIO
Interprete flags sem vazar saída
Implemente ParseGreeting(args []string) (GreetingOptions, error). Reconheça -name, cujo padrão é mundo, e -upper, cujo padrão é false. A função deve aceitar qualquer ordem das flags, devolver erro para flag desconhecida, valor inválido ou argumento posicional restante, e nunca encerrar o processo.
Entrada: tokens sem o nome do executável. Saída: opções tipadas ou erro. Exemplo: [-upper -name Lia] produz {Name: "Lia", Upper: true}. Um slice vazio produz o nome padrão. A restrição central é não usar o conjunto global de flags, porque os testes chamam a função várias vezes no mesmo processo.
Dica 1
Crie um FlagSet próprio para receber args em vez de usar o conjunto global.
Dica 2
Use ContinueOnError, descarte a saída interna e rejeite flags.Args que sobrarem.
A solução aparece depois do acerto ou de 3 tentativas.
Solução e explicação
package main
import (
"flag"
"fmt"
"io"
)
type GreetingOptions struct {
Name string
Upper bool
}
func ParseGreeting(args []string) (GreetingOptions, error) {
flags := flag.NewFlagSet("greet", flag.ContinueOnError)
flags.SetOutput(io.Discard)
name := flags.String("name", "mundo", "nome da pessoa")
upper := flags.Bool("upper", false, "usa letras maiúsculas")
if err := flags.Parse(args); err != nil {
return GreetingOptions{}, err
}
if flags.NArg() != 0 {
return GreetingOptions{}, fmt.Errorf("argumento inesperado: %s", flags.Arg(0))
}
return GreetingOptions{Name: *name, Upper: *upper}, nil
}
Ver harness de testes somente leitura
package main
import (
"encoding/json"
"fmt"
)
type caseResult struct {
Case int `json:"case"`
Pass bool `json:"pass"`
Input string `json:"input,omitempty"`
Want string `json:"want,omitempty"`
Got string `json:"got,omitempty"`
}
func emit(n int, input, want, got string) {
b, _ := json.Marshal(caseResult{n, want == got, input, want, got})
fmt.Printf("RESULT %s\n", b)
}
func main() {
o, e := ParseGreeting(nil)
g := fmt.Sprintf("%s|%t", o.Name, o.Upper)
if e != nil {
g = "error"
}
emit(1, "[]", "mundo|false", g)
o, e = ParseGreeting([]string{"-name", "Bia"})
g = fmt.Sprintf("%s|%t", o.Name, o.Upper)
if e != nil {
g = "error"
}
emit(2, "-name Bia", "Bia|false", g)
o, e = ParseGreeting([]string{"-upper", "-name", "Lia"})
g = fmt.Sprintf("%s|%t", o.Name, o.Upper)
if e != nil {
g = "error"
}
emit(3, "flags", "Lia|true", g)
_, e = ParseGreeting([]string{"extra"})
g = "ok"
if e != nil {
g = "error"
}
emit(4, "extra", "error", g)
}
DESAFIO
Formate a saudação final
Implemente FormatGreeting(name string, upper bool) string. Monte exatamente Oi, !; quando upper for verdadeiro, devolva a mensagem inteira em maiúsculas. Não imprima dentro da função.
Entrada: qualquer string e um booleano. Saída: uma string determinística. ("Go", false) produz Oi, Go!; ("Ana", true) produz OI, ANA!. O caso de nome vazio continua válido e produz Oi, !, pois validação de nome pertence a outra camada.
Dica 1
Monte a mensagem uma vez antes de decidir se transforma as letras.
Dica 2
Aplique strings.ToUpper somente quando upper for true.
A solução aparece depois do acerto ou de 3 tentativas.
Solução e explicação
package main
import (
"fmt"
"strings"
)
func FormatGreeting(name string, upper bool) string {
message := fmt.Sprintf("Oi, %s!", name)
if upper {
return strings.ToUpper(message)
}
return message
}
Ver harness de testes somente leitura
package main
import (
"encoding/json"
"fmt"
)
type caseResult struct {
Case int `json:"case"`
Pass bool `json:"pass"`
Input string `json:"input,omitempty"`
Want string `json:"want,omitempty"`
Got string `json:"got,omitempty"`
}
func emit(n int, input, want, got string) {
b, _ := json.Marshal(caseResult{n, want == got, input, want, got})
fmt.Printf("RESULT %s\n", b)
}
func main() {
emit(1, "Go,false", "Oi, Go!", FormatGreeting("Go", false))
emit(2, "Ana,true", "OI, ANA!", FormatGreeting("Ana", true))
emit(3, "empty", "Oi, !", FormatGreeting("", false))
}
VOCÊ CHEGOU AO RESUMO
O que você leva desta aula
- Flags nomeiam opções e podem aparecer em ordens diferentes; argumentos posicionais dependem de contrato de posição.
flag.NewFlagSetisola parsing eContinueOnErrordevolve controle ao chamador.- Defaults pertencem à declaração da flag; validação de domínio acontece depois da conversão de tipo.
- Parsing, transformação e impressão separadas produzem funções reutilizáveis e testes determinísticos.
- Na próxima aula, minis/cli-profissional/02-subcomandos, você divide uma ferramenta em verbos como
addelistsem criar um executável para cada operação.