server/admin/sync/exercice_defines.go
Pierre-Olivier Mercier ea8ad1d6db
All checks were successful
continuous-integration/drone/push Build is passing
sync: Don't warn about no flag if WIP
2024-10-11 14:55:49 +02:00

227 lines
6.2 KiB
Go

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 {
if !params.WIP {
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
}