package main import ( "errors" "fmt" "log/slog" "os" "regexp" "strings" "github.com/joint-online-judge/JOJ3/internal/stage" "github.com/koding/multiconfig" ) type Conf struct { SandboxExecServer string `default:"localhost:5051"` SandboxToken string `default:""` LogLevel int `default:"0"` OutputPath string `default:"joj3_result.json"` Stages []struct { Name string Executor struct { Name string With struct { Default stage.Cmd Cases []OptionalCmd } } Parser struct { Name string With interface{} } } } type OptionalCmd struct { Args *[]string Env *[]string Stdin *stage.CmdFile Stdout *stage.CmdFile Stderr *stage.CmdFile CPULimit *uint64 RealCPULimit *uint64 ClockLimit *uint64 MemoryLimit *uint64 StackLimit *uint64 ProcLimit *uint64 CPURateLimit *uint64 CPUSetLimit *string CopyIn *map[string]stage.CmdFile CopyInCached *map[string]string CopyInDir *string CopyOut *[]string CopyOutCached *[]string CopyOutMax *uint64 CopyOutDir *string TTY *bool StrictMemoryLimit *bool DataSegmentLimit *bool AddressSpaceLimit *bool } // TODO: add other fields to match? not only limit to latest commit message type MetaConf struct { Patterns []struct { Filename string Regex string } } func parseMetaConfFile(path string) (metaConf MetaConf, err error) { // FIXME: remove this default meta config, it is only for demonstration if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { slog.Debug("meta conf not found", "path", path) return MetaConf{ Patterns: []struct { Filename string Regex string }{ { Filename: "conf.json", Regex: ".*", }, }, }, nil } d := &multiconfig.DefaultLoader{} loaders := []multiconfig.Loader{} loaders = append(loaders, &multiconfig.TagLoader{}) if strings.HasSuffix(path, "toml") { loaders = append(loaders, &multiconfig.TOMLLoader{Path: path}) } if strings.HasSuffix(path, "json") { loaders = append(loaders, &multiconfig.JSONLoader{Path: path}) } if strings.HasSuffix(path, "yml") || strings.HasSuffix(path, "yaml") { loaders = append(loaders, &multiconfig.YAMLLoader{Path: path}) } d.Loader = multiconfig.MultiLoader(loaders...) d.Validator = multiconfig.MultiValidator(&multiconfig.RequiredValidator{}) if err = d.Load(&metaConf); err != nil { slog.Error("parse meta conf", "error", err) return } if err = d.Validate(&metaConf); err != nil { slog.Error("validate meta conf", "error", err) return } return } func parseConfFile(path string) (conf Conf, err error) { d := &multiconfig.DefaultLoader{} d.Loader = multiconfig.MultiLoader( &multiconfig.TagLoader{}, &multiconfig.JSONLoader{Path: path}, ) d.Validator = multiconfig.MultiValidator(&multiconfig.RequiredValidator{}) if err = d.Load(&conf); err != nil { slog.Error("parse stages conf", "error", err) return } if err = d.Validate(&conf); err != nil { slog.Error("validate stages conf", "error", err) return } return } func msgToConf(metaConfPath string, msg string) (conf Conf, err error) { slog.Info("msg to conf", "msg", msg) metaConf, err := parseMetaConfFile(metaConfPath) if err != nil { return } for _, pattern := range metaConf.Patterns { if matched, _ := regexp.MatchString(pattern.Regex, msg); matched { slog.Debug("pattern matched", "pattern", pattern, "filename", pattern.Filename) slog.Info("pattern matched", "filename", pattern.Filename) return parseConfFile(pattern.Filename) } } err = fmt.Errorf("no pattern matched") return }