Files
hotp_calc/cmd/hotpcalc/commands.go
T

117 lines
4.4 KiB
Go

package main
import (
"errors"
"flag"
"fmt"
"os"
"strings"
"hotpcalc/internal/store"
)
// command — подкоманда командной строки.
type command struct {
name string
brief string
args string // краткое описание позиционных аргументов для справки
run func(args []string) error
}
func commands() []*command {
return []*command{
{"new", "выпустить новый токен и выгрузить XML", "", cmdNew},
{"add", "добавить в хранилище известный токен по его seed", "<seed>", cmdAdd},
{"import", "загрузить токены из XML-файла инициализации", "<файл.xml>", cmdImport},
{"list", "список токенов в хранилище", "", cmdList},
{"show", "параметры и состояние токена, счётчик не двигается", "<серийник>", cmdShow},
{"next", "выдать следующее значение — «нажатие кнопки»", "<серийник>", cmdNext},
{"token", "интерактивный режим: Enter = нажатие кнопки", "<серийник>", cmdToken},
{"sync", "найти позицию по двум кодам и записать её в хранилище", "<серийник> <код1> <код2>", cmdSync},
{"set", "изменить параметры токена", "<серийник>", cmdSet},
{"export", "выгрузить XML-файл инициализации", "[серийник...]", cmdExport},
{"remove", "удалить токен из хранилища", "<серийник>...", cmdRemove},
{"calc", "явный вызов калькулятора", "[seed] [код1] [код2]", runCalc},
}
}
func lookupCommand(name string) *command {
for _, c := range commands() {
if c.name == name {
return c
}
}
return nil
}
// newFlagSet готовит набор флагов подкоманды с общей справкой.
func newFlagSet(name string) *flag.FlagSet {
fs := flag.NewFlagSet("hotpcalc "+name, flag.ContinueOnError)
cmd := lookupCommand(name)
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "hotpcalc %s %s\n\n %s\n\nФлаги:\n", name, cmd.args, cmd.brief)
fs.PrintDefaults()
}
return fs
}
// storeFlag добавляет флаг пути к хранилищу.
func storeFlag(fs *flag.FlagSet, dst *string) {
fs.StringVar(dst, "store", store.DefaultPath(),
"файл хранилища токенов (переменная "+store.EnvStore+")")
}
// parseFlags разбирает аргументы подкоманды с перестановкой флагов и
// позиционных аргументов.
func parseFlags(fs *flag.FlagSet, args []string) error {
if err := fs.Parse(permute(fs, args)); err != nil {
if errors.Is(err, flag.ErrHelp) {
return errHelp
}
return err
}
return nil
}
// errHelp сигнализирует, что была напечатана справка и работа окончена.
var errHelp = errors.New("справка напечатана")
// warn печатает предупреждение в stderr.
func warn(format string, args ...any) {
fmt.Fprintf(os.Stderr, "Внимание: "+format+"\n", args...)
}
// openStore открывает хранилище с блокировкой.
func openStore(path string) (*store.Store, error) {
return store.Open(path, func(m string) { warn("%s", m) })
}
// getToken читает токен, требуя, чтобы он существовал.
func getToken(s *store.Store, serial string) (store.Token, error) {
t, ok, err := s.Get(serial)
if err != nil {
return store.Token{}, err
}
if !ok {
hint := "хранилище пусто"
if serials := s.Serials(); len(serials) > 0 {
hint = "известные токены: " + strings.Join(serials, ", ")
}
return store.Token{}, fmt.Errorf("токен %s не найден в %s (%s)", serial, s.Path(), hint)
}
return t, nil
}
// requireArgs проверяет число позиционных аргументов.
func requireArgs(fs *flag.FlagSet, min, max int, what string) error {
n := fs.NArg()
if n < min {
fs.Usage()
return fmt.Errorf("не хватает аргументов: нужен %s", what)
}
if max >= 0 && n > max {
return fmt.Errorf("лишние аргументы: %s", strings.Join(fs.Args()[max:], " "))
}
return nil
}