117 lines
4.4 KiB
Go
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
|
|
}
|