package sync import ( "fmt" "path" "strconv" "github.com/BurntSushi/toml" "go.uber.org/multierr" "srs.epita.fr/fic-server/libfic" ) // ExerciceHintParams holds EHint definition infomation. type ExerciceHintParams struct { Filename string Content string Cost *int64 Title string NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"` } // ExerciceDependency holds dependency definitions information. type ExerciceDependency struct { Id int64 Theme string `toml:",omitempty"` } // ExerciceUnlockFile holds parameters related to a locked file. type ExerciceUnlockFile struct { Filename string `toml:",omitempty"` } // ExerciceFile defines attributes on files. type ExerciceFile struct { Filename string `toml:",omitempty"` URL string `toml:",omitempty"` Hidden bool `toml:",omitempty"` Disclaimer string `toml:",omitempty"` } // ExerciceFlag holds informations about one flag. type ExerciceFlag struct { Id int64 Label string `toml:",omitempty"` Type string `toml:",omitempty"` Raw interface{} Separator string `toml:",omitempty"` ShowLines bool `toml:",omitempty"` Ordered bool `toml:",omitempty"` CaseSensitive bool `toml:",omitempty"` NoTrim bool `toml:",omitempty"` CaptureRe string `toml:"capture_regexp,omitempty"` SortReGroups bool `toml:"sort_capture_regexp_groups,omitempty"` Placeholder string `toml:",omitempty"` Help string `toml:",omitempty"` BonusGain int32 `toml:"bonus_gain,omitempty"` ChoicesCost int32 `toml:"choices_cost,omitempty"` Choice []ExerciceFlagChoice LockedFile []ExerciceUnlockFile `toml:"unlock_file,omitempty"` NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"` NeedFlags []int64 `toml:"need_flags,omitempty"` NoShuffle bool Unit string `toml:"unit,omitempty"` Variant string `toml:"variant,omitempty"` NumberMin interface{} `toml:"min,omitempty"` NumberMax interface{} `toml:"max,omitempty"` NumberStep interface{} `toml:"step,omitempty"` } func (f ExerciceFlag) RawString() []string { switch f.Raw.(type) { case string: return []string{f.Raw.(string)} case []string: return f.Raw.([]string) default: return nil } } func (f ExerciceFlag) RawNumber() ([]float64, error) { switch f.Raw.(type) { case float64: return []float64{f.Raw.(float64)}, nil case []float64: return f.Raw.([]float64), nil case int64: return []float64{float64(f.Raw.(int64))}, nil case []int64: var res []float64 for _, raw := range f.Raw.([]int64) { res = append(res, float64(raw)) } return res, nil case string: if v, err := strconv.ParseFloat(f.Raw.(string), 64); err == nil { return []float64{v}, nil } else { return nil, err } case []string: var res []float64 for _, raw := range f.Raw.([]string) { if v, err := strconv.ParseFloat(raw, 64); err == nil { res = append(res, v) } else { return nil, err } } return res, nil default: return nil, fmt.Errorf("invalid raw type: %T", f.Raw) } } // ExerciceFlagChoice holds informations about a choice (for MCQ and UCQ). type ExerciceFlagChoice struct { ExerciceFlag Value interface{} `toml:",omitempty"` } // ExerciceParams contains values parsed from defines.txt. type ExerciceParams struct { WIP bool `toml:"wip"` Gain int64 Tags []string Files []ExerciceFile `toml:"file"` Hints []ExerciceHintParams `toml:"hint"` Dependencies []ExerciceDependency `toml:"depend"` Flags []ExerciceFlag `toml:"flag"` FlagsMCQ []ExerciceFlag `toml:"flag_mcq"` FlagsUCQ []ExerciceFlag `toml:"flag_ucq"` } func (p ExerciceParams) GetRawFlags() (ret []string) { for _, f := range append(p.Flags, p.FlagsUCQ...) { raw := f.RawString() if len(raw) > 0 { ret = append(ret, raw...) } } for _, f := range p.FlagsMCQ { for _, c := range f.Choice { ret = append(ret, c.Label) } } return } // parseExerciceParams reads challenge definitions from defines.txt and extract usefull data to set up the challenge. func parseExerciceParams(i Importer, exPath string) (p ExerciceParams, md toml.MetaData, err error) { var defs string if i.Exists(path.Join(exPath, "challenge.toml")) { defs, err = GetFileContent(i, path.Join(exPath, "challenge.toml")) } else { defs, err = GetFileContent(i, path.Join(exPath, "challenge.txt")) } if err != nil { return } md, err = toml.Decode(defs, &p) return } // getExerciceParams returns normalized func getExerciceParams(i Importer, exercice *fic.Exercice) (params ExerciceParams, errs error) { var err error if params, _, err = parseExerciceParams(i, exercice.Path); err != nil { errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err)) } else if len(params.Flags) == 0 && len(params.FlagsUCQ) == 0 && len(params.FlagsMCQ) == 0 { errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, fmt.Errorf("has no flag"))) } else { // Treat legacy UCQ flags as ExerciceFlag for _, flag := range params.FlagsUCQ { params.Flags = append(params.Flags, ExerciceFlag{ Id: flag.Id, Label: flag.Label, Type: "ucq", Raw: flag.Raw, CaptureRe: flag.CaptureRe, Placeholder: flag.Placeholder, ChoicesCost: flag.ChoicesCost, Choice: flag.Choice, LockedFile: flag.LockedFile, NeedFlag: flag.NeedFlag, }) } params.FlagsUCQ = []ExerciceFlag{} // Treat legacy MCQ flags as ExerciceFlag for _, flag := range params.FlagsMCQ { params.Flags = append(params.Flags, ExerciceFlag{ Id: flag.Id, Label: flag.Label, Type: "mcq", ChoicesCost: flag.ChoicesCost, Choice: flag.Choice, LockedFile: flag.LockedFile, NeedFlag: flag.NeedFlag, }) } params.FlagsMCQ = []ExerciceFlag{} } return } func GetExerciceFilesParams(i Importer, exercice *fic.Exercice) (map[string]ExerciceFile, error) { params, _, err := parseExerciceParams(i, exercice.Path) if err != nil { return nil, err } paramsFiles := map[string]ExerciceFile{} for _, f := range params.Files { paramsFiles[f.Filename] = f } return paramsFiles, nil }