Files
kurious/cmd/dev/sravnicli/core.go
2023-12-04 01:24:24 +03:00

184 lines
4.2 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os"
"git.loyso.art/frx/kurious/internal/common/client/sravni"
"git.loyso.art/frx/kurious/internal/common/config"
"git.loyso.art/frx/kurious/internal/common/errors"
"git.loyso.art/frx/kurious/internal/kurious/adapters"
"github.com/teris-io/cli"
)
var (
limitOption = cli.NewOption("limit", "Limits amount of items to return").WithType(cli.TypeInt)
offsetOption = cli.NewOption("offset", "Offsets items to return").WithType(cli.TypeInt)
debugOption = cli.NewOption("verbose", "Enables debug logging").WithChar('v').WithType(cli.TypeBool)
jsonOption = cli.NewOption("json", "Sets output as json").WithType(cli.TypeBool)
)
type actionWrapper func(next cli.Action) cli.Action
func buildCLICommand(f func() cli.Command) cliCommand {
return makeCLICommand(f(), actionWrapperParseConfig)
}
func makeCLICommand(cmd cli.Command, wrappers ...actionWrapper) cliCommand {
return cliCommand{
Command: cmd,
actionWrappers: wrappers,
}
}
type cliCommand struct {
cli.Command
actionWrappers []actionWrapper
}
// WithCommand is disabled since it adds action wrappers and
// not tested in other cases.
func (c cliCommand) WithCommand(cli.Command) cli.Command {
panic("wrapped in cliCommand is expected to be leaf command")
}
func (c cliCommand) Action() cli.Action {
out := c.Command.Action()
if out == nil {
return nil
}
for _, wrapper := range c.actionWrappers {
out = wrapper(out)
}
return out
}
func actionWrapperParseConfig(next cli.Action) cli.Action {
return func(args []string, options map[string]string) int {
var result int
parseConfigOnce.Do(func() {
cfgpath, ok := options["config"]
if !ok || cfgpath == "" {
cfgpath = defaultConfigPath
}
payload, err := os.ReadFile(cfgpath)
if err != nil {
slog.Error("unable to read config file", slog.Any("err", err))
result = -1
return
}
err = json.Unmarshal(payload, &currentConfig)
if err != nil {
slog.Error("unable to unmarshal config file", slog.Any("err", err))
result = -1
return
}
})
if result != 0 {
return result
}
return next(args, options)
}
}
func isJSONFormatEnabled(options map[string]string) bool {
_, ok := options[jsonOption.Key()]
return ok
}
type outputEncoderF func(any) error
func makeOutputEncoder(options map[string]string) outputEncoderF {
if isJSONFormatEnabled(options) {
out := json.NewEncoder(defaultOutput)
out.SetIndent("", " ")
return out.Encode
}
return outputEncoderF(func(a any) error {
_, err := fmt.Fprintf(defaultOutput, "%#v", a)
return err
})
}
func makeLogger(options map[string]string) *slog.Logger {
level := slog.LevelInfo
if _, ok := options[debugOption.Key()]; ok {
level = slog.LevelDebug
}
opts := slog.HandlerOptions{
Level: level,
}
var h slog.Handler
if isJSONFormatEnabled(options) {
h = slog.NewJSONHandler(os.Stdout, &opts)
} else {
h = slog.NewTextHandler(os.Stdout, &opts)
}
return slog.New(h)
}
func makeSravniClient(ctx context.Context, log *slog.Logger, options map[string]string) (sravni.Client, error) {
_, isDebug := options[debugOption.Key()]
client, err := sravni.NewClient(ctx, log, isDebug)
if err != nil {
return nil, fmt.Errorf("making new client: %w", err)
}
return client, nil
}
type baseAction struct {
ctx context.Context
log *slog.Logger
out outputEncoderF
}
func (ba *baseAction) getYDBConnection() (*adapters.YDBConnection, error) {
if currentConfig.YDB == (config.YDB{}) {
return nil, errors.SimpleError("no ydb config set")
}
ydbConn, err := adapters.NewYDBConnection(ba.ctx, currentConfig.YDB, ba.log.With(slog.String("db", "ydb")))
if err != nil {
return nil, fmt.Errorf("making new ydb course repository: %w", err)
}
return ydbConn, nil
}
func (ba *baseAction) parse(_ []string, options map[string]string) (err error) {
ba.log = makeLogger(options).With(slog.String("component", "action"))
ba.out = makeOutputEncoder(options)
return nil
}
func (ba *baseAction) handle() error {
return errors.ErrNotImplemented
}
func (ba *baseAction) context() context.Context {
return ba.ctx
}
func newBaseAction(ctx context.Context) *baseAction {
return &baseAction{
ctx: ctx,
}
}