Initial commit: hotpcalc CLI
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user