package main import ( "fmt" "log/slog" "os" "regexp" "strconv" "strings" "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" "github.com/go-git/go-git/v5" "github.com/koding/multiconfig" ) type JobType int const ( HC JobType = iota CQ OJ ) 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 CopyInCwd *bool CopyOut *[]string CopyOutCached *[]string CopyOutMax *uint64 CopyOutDir *string TTY *bool StrictMemoryLimit *bool DataSegmentLimit *bool AddressSpaceLimit *bool } 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 _validateHw(hw string) error { matched, err := regexp.MatchString(`^hw[0-9]+$`, hw) if err != nil { return fmt.Errorf("error compiling regex: %w", err) } if !matched { return fmt.Errorf("error: hw does not match the required pattern") } return nil } func _getExList(pattern string) (hwList []string, err error) { re, err := regexp.Compile(pattern) if err != nil { return nil, fmt.Errorf("failed to compile regex: %w", err) } files, err := os.ReadDir(".") if err != nil { return nil, fmt.Errorf("error reading directory: %w", err) } for _, file := range files { if !file.IsDir() && re.MatchString(file.Name()) { hwList = append(hwList, file.Name()) } } return hwList, nil } func _contains(list []string, item string) bool { for _, v := range list { if v == item { return true } } return false } func commitMsgToConf() (confs []Conf, jobtype JobType, err error) { r, err := git.PlainOpen(".") if err != nil { return } ref, err := r.Head() if err != nil { return } commit, err := r.CommitObject(ref.Hash()) if err != nil { return } var conf Conf var exList []string jobtype = HC file := "conf.json" msg := commit.Message slog.Debug("commit msg to conf", "msg", msg) // INFO: default config conf, err = parseConfFile(file) confs = append(confs, conf) if msg == "" { return } line := strings.Split(msg, "\n")[0] words := strings.Fields(line) head := words[0] var hw string // INFO: get worktype if strings.HasSuffix(head, ":") || strings.HasSuffix(head, ".") { head = head[:len(head)-1] } switch head { case "feat", "fix", "refactor", "perf", "test", "build", "revert": jobtype = CQ case "joj", "grading": jobtype = OJ } // INFO: get configs if len(words) >= 2 { hw = words[1] if err = _validateHw(hw); err == nil { file = strings.Replace(file, "conf", "conf-"+hw, 1) } var exTot []string // INFO: get all hw ex if no args provided exTot, err = _getExList(file) if len(words) == 2 { exList = exTot } else { var tmpList []string for idx := 2; idx < len(words); idx++ { word := words[idx] num, err := strconv.Atoi(word) if err != nil { return confs, HC, fmt.Errorf("error: bad ex number") } file := fmt.Sprintf("conf-"+hw+"-ex%d.json", num) if !_contains(exList, file) { return confs, HC, fmt.Errorf("error: bad ex number") } tmpList = append(tmpList, file) } exList = tmpList } } for _, ex := range exList { conf, err = parseConfFile(ex) if err != nil { return } confs = append(confs, conf) } return }