From 58027bb9887bbb34bfe448591662818f78a42990 Mon Sep 17 00:00:00 2001 From: Alexey Date: Fri, 1 Aug 2025 12:54:18 +0300 Subject: [PATCH] add config processing --- hooks/initial.go | 132 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 123 insertions(+), 9 deletions(-) diff --git a/hooks/initial.go b/hooks/initial.go index 0763ed0..eb95a42 100644 --- a/hooks/initial.go +++ b/hooks/initial.go @@ -12,6 +12,7 @@ import ( "os" "os/signal" "path/filepath" + "reflect" "slices" "strings" "syscall" @@ -133,7 +134,7 @@ func Init4Hook(_ context.Context, cs *corestate.CoreState, x *app.AppX) { // post-init stage func Init5Hook(_ context.Context, cs *corestate.CoreState, x *app.AppX) { - nodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) { + NodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) { x.Log.Println("Cleaning up...") if err := run_manager.Clean(); err != nil { @@ -184,21 +185,29 @@ func Init5Hook(_ context.Context, cs *corestate.CoreState, x *app.AppX) { } } -func Init6Hook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) { +func Init6Hook(_ context.Context, cs *corestate.CoreState, x *app.AppX) { if !slices.Contains(*x.Config.Conf.DisableWarnings, "--WNonStdTmpDir") && os.TempDir() != "/tmp" { x.Log.Printf("%s: %s", colors.PrintWarn(), "Non-standard value specified for temporary directory") } - if strings.Contains(*x.Config.Conf.Log.OutPath, `%tmp%`) { - replaced := strings.ReplaceAll(*x.Config.Conf.Log.OutPath, "%tmp%", filepath.Clean(run_manager.RuntimeDir())) - x.Config.Conf.Log.OutPath = &replaced + + replacements := map[string]any{ + "%tmp%": filepath.Clean(run_manager.RuntimeDir()), + "%path%": *x.Config.Env.NodePath, + "%stdout%": os.Stdout, + "%stderr%": os.Stderr, } + + processConfig(&x.Config.Conf, replacements) + if !slices.Contains(logs.Levels.Available, *x.Config.Conf.Log.Level) { if !slices.Contains(*x.Config.Conf.DisableWarnings, "--WUndefLogLevel") { x.Log.Printf("%s: %s", colors.PrintWarn(), fmt.Sprintf("Unknown logging level %s, fallback level: %s", *x.Config.Conf.Log.Level, logs.Levels.Fallback)) } x.Config.Conf.Log.Level = &logs.Levels.Fallback } +} +func Init7Hook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) { if *x.Config.Conf.Node.ShowConfig { fmt.Printf("Configuration from %s:\n", x.Config.CMDLine.Run.ConfigPath) x.Config.Print(x.Config.Conf) @@ -206,16 +215,16 @@ func Init6Hook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) { fmt.Printf("Environment:\n") x.Config.Print(x.Config.Env) - if !askConfirm("Is that ok?", true) { + if cs.UUID32 != "" && !askConfirm("Is that ok?", true) { x.Log.Printf("Cancel launch") - nodeApp.CallFallback(ctx) + NodeApp.CallFallback(ctx) } } x.Log.Printf("Starting \"%s\" node", *x.Config.Conf.Node.Name) } -func Init7Hook(_ context.Context, cs *corestate.CoreState, x *app.AppX) { +func Init8Hook(_ context.Context, cs *corestate.CoreState, x *app.AppX) { cs.Stage = corestate.StageReady x.Log.SetPrefix(colors.SetGreen(fmt.Sprintf("(%s) ", cs.Stage))) @@ -228,6 +237,111 @@ func Init7Hook(_ context.Context, cs *corestate.CoreState, x *app.AppX) { *x.SLog = *newSlog } +func processConfig(conf any, replacements map[string]any) error { + val := reflect.ValueOf(conf) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + + switch val.Kind() { + case reflect.Struct: + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + if field.CanAddr() && field.CanSet() { + if err := processConfig(field.Addr().Interface(), replacements); err != nil { + return err + } + } + } + + case reflect.Slice: + for i := 0; i < val.Len(); i++ { + elem := val.Index(i) + if elem.CanAddr() && elem.CanSet() { + if err := processConfig(elem.Addr().Interface(), replacements); err != nil { + return err + } + } + } + + case reflect.Map: + for _, key := range val.MapKeys() { + elem := val.MapIndex(key) + if elem.CanInterface() { + newVal := reflect.New(elem.Type()).Elem() + newVal.Set(elem) + + if err := processConfig(newVal.Addr().Interface(), replacements); err != nil { + return err + } + + val.SetMapIndex(key, newVal) + } + } + + case reflect.String: + str := val.String() + + if replacement, exists := replacements[str]; exists { + if err := setValue(val, replacement); err != nil { + return fmt.Errorf("failed to set %q: %v", str, err) + } + } else { + for placeholder, replacement := range replacements { + if strings.Contains(str, placeholder) { + replacementStr, err := toString(replacement) + if err != nil { + return fmt.Errorf("invalid replacement for %q: %v", placeholder, err) + } + newStr := strings.ReplaceAll(str, placeholder, replacementStr) + val.SetString(newStr) + } + } + } + + case reflect.Ptr: + if !val.IsNil() { + return processConfig(val.Interface(), replacements) + } + } + + return nil +} + +func setValue(val reflect.Value, replacement any) error { + if !val.CanSet() { + return fmt.Errorf("value is not settable") + } + + replacementVal := reflect.ValueOf(replacement) + if replacementVal.Type().AssignableTo(val.Type()) { + val.Set(replacementVal) + return nil + } + + if val.Kind() == reflect.String { + str, err := toString(replacement) + if err != nil { + return fmt.Errorf("cannot convert replacement to string: %v", err) + } + val.SetString(str) + return nil + } + + return fmt.Errorf("type mismatch: cannot assign %T to %v", replacement, val.Type()) +} + +func toString(v any) (string, error) { + switch s := v.(type) { + case string: + return s, nil + case fmt.Stringer: + return s.String(), nil + default: + return fmt.Sprint(v), nil + } +} + func askConfirm(prompt string, defaultYes bool) bool { ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) @@ -249,7 +363,7 @@ func askConfirm(prompt string, defaultYes bool) bool { select { case <-ctx.Done(): fmt.Println("") - nodeApp.CallFallback(ctx) + NodeApp.CallFallback(ctx) os.Exit(3) case text := <-inputChan: text = strings.TrimSpace(strings.ToLower(text))