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", "", 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 }