server/admin/sync/exercice_keys.go

716 lines
22 KiB
Go
Raw Normal View History

package sync
import (
"fmt"
"math"
"math/rand"
2022-05-16 09:38:46 +00:00
"net/http"
"path"
2018-12-04 18:09:58 +00:00
"sort"
2021-11-12 20:36:27 +00:00
"strconv"
2018-12-04 17:34:38 +00:00
"strings"
2017-12-17 15:10:59 +00:00
"unicode"
2022-05-16 09:38:46 +00:00
"github.com/gin-gonic/gin"
"go.uber.org/multierr"
"srs.epita.fr/fic-server/libfic"
)
2018-03-09 18:07:08 +00:00
// isFullGraphic detects if some rune are not graphic one.
// This function is usefull to display warning when importing key ending with \r.
2017-12-17 15:10:59 +00:00
func isFullGraphic(s string) bool {
for _, c := range s {
if !unicode.IsGraphic(c) {
return false
}
}
return true
}
2018-11-16 19:46:19 +00:00
func validatorRegexp(vre string) (validator_regexp *string) {
if len(vre) > 0 {
validator_regexp = &vre
} else {
validator_regexp = nil
}
return
}
func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bool, separator string) (raw string, prep string, errs error) {
2019-01-16 04:25:20 +00:00
// Concatenate array
2019-01-18 03:24:28 +00:00
if f, ok := input.([]interface{}); ok {
if len(validatorRe) > 0 {
errs = multierr.Append(errs, fmt.Errorf("ValidatorRe cannot be defined for this kind of flag."))
2019-01-18 03:24:28 +00:00
validatorRe = ""
2019-01-16 04:25:20 +00:00
}
2019-01-18 03:24:28 +00:00
if len(separator) == 0 {
separator = ","
} else if len(separator) > 1 {
separator = string(separator[0])
errs = multierr.Append(errs, fmt.Errorf("separator truncated to %q", separator))
2019-01-16 04:25:20 +00:00
}
var fitems []string
for i, v := range f {
2019-01-16 04:25:20 +00:00
if g, ok := v.(string); ok {
2019-01-18 03:24:28 +00:00
if strings.Index(g, separator) != -1 {
errs = multierr.Append(errs, fmt.Errorf("flag items cannot contain %q character as it is used as separator. Change the separator attribute for this flag.", separator))
2019-01-16 04:25:20 +00:00
return
} else {
fitems = append(fitems, g)
}
} else {
errs = multierr.Append(errs, fmt.Errorf("item %d has an invalid type: can only be string, is %T.", i, g))
2019-01-16 04:25:20 +00:00
return
}
}
2019-01-17 21:29:09 +00:00
ignord := "f"
2019-01-18 03:24:28 +00:00
if !ordered {
// Sort the list without taking the case in count.
sort.Slice(fitems, func(i, j int) bool {
return strings.ToLower(fitems[i]) < strings.ToLower(fitems[j])
})
2019-01-17 21:29:09 +00:00
ignord = "t"
2019-01-16 04:25:20 +00:00
}
nbLines := 0
if showLines {
if len(fitems) > 9 {
errs = multierr.Append(errs, fmt.Errorf("too much items in vector to use ShowLines features, max 9."))
} else {
nbLines = len(fitems)
}
}
2019-01-18 03:24:28 +00:00
raw = strings.Join(fitems, separator) + separator
2019-01-16 04:25:20 +00:00
prep = fmt.Sprintf("`%s%s%d", separator, ignord, nbLines)
2019-01-18 03:24:28 +00:00
} else if f, ok := input.(int64); ok {
2019-01-16 04:25:20 +00:00
raw = fmt.Sprintf("%d", f)
2021-11-12 20:36:27 +00:00
} else if f, ok := input.(float64); ok {
raw = strconv.FormatFloat(f, 'f', -1, 64)
2019-01-18 03:24:28 +00:00
} else if f, ok := input.(string); !ok {
errs = multierr.Append(errs, fmt.Errorf("has an invalid type: can only be []string or string, not %T", input))
2019-01-16 04:25:20 +00:00
return
} else {
raw = f
}
return
}
func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, exceptions *CheckExceptions) (f *fic.FlagLabel, errs error) {
2022-01-21 12:06:37 +00:00
if len(flag.Label) == 0 {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label cannot be empty.")))
2022-01-21 12:06:37 +00:00
return
}
// Call checks hooks
for _, h := range hooks.mdTextHooks {
for _, err := range multierr.Errors(h(flag.Label, exercice.Language, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, err))
}
}
if mdlabel, err := ProcessMarkdown(GlobalImporter, flag.Label, exercice.Path); err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("unable to parse property label as Markdown: %w", err)))
} else {
if strings.Count(flag.Label, "\n\n") == 0 {
flag.Label = mdlabel[3 : len(mdlabel)-4]
} else {
flag.Label = mdlabel
}
}
2022-01-21 12:06:37 +00:00
if flag.Raw != nil {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("raw cannot be defined.")))
2022-01-21 12:06:37 +00:00
}
if len(flag.Choice) != 0 {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("choices cannot be defined.")))
2022-01-21 12:06:37 +00:00
}
f = &fic.FlagLabel{
Order: int8(flagline),
Label: flag.Label,
Variant: flag.Variant,
}
2022-07-11 17:57:33 +00:00
// Call checks hooks
for _, h := range hooks.flagLabelHooks {
for _, e := range multierr.Errors(h(f, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
2022-07-11 17:57:33 +00:00
}
}
2022-01-21 12:06:37 +00:00
return
}
func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defaultLabel string, exceptions *CheckExceptions) (f *fic.Flag, choices []*fic.FlagChoice, errs error) {
if len(flag.Label) == 0 {
flag.Label = defaultLabel
}
2018-12-02 18:21:07 +00:00
2022-01-21 12:06:37 +00:00
if len(flag.Variant) != 0 {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("variant is not defined for this kind of flag.")))
2022-01-21 12:06:37 +00:00
}
if flag.Label[0] == '`' {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label should not begin with `.")))
2022-06-12 20:58:26 +00:00
flag.Label = flag.Label[1:]
}
raw, prep, terrs := getRawKey(flag.Raw, flag.CaptureRe, flag.Ordered, flag.ShowLines, flag.Separator)
errors := multierr.Errors(terrs)
if len(errors) > 0 {
for _, terr := range errors {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, terr))
2022-07-11 17:57:33 +00:00
}
f = nil
return
}
flag.Label = prep + flag.Label
if len(flag.Label) > 255 && flag.Type != "label" {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("label is too long (max 255 chars per label).")))
2023-07-16 17:00:10 +00:00
}
if (flag.Type == "text" && !isFullGraphic(strings.Replace(raw, "\n", "", -1))) || (flag.Type != "text" && !isFullGraphic(raw)) {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("WARNING non-printable characters in flag, is this really expected?")))
}
2019-01-16 04:25:20 +00:00
hashedFlag, err := fic.ComputeHashedFlag([]byte(raw), !flag.CaseSensitive, flag.NoTrim, validatorRegexp(flag.CaptureRe), flag.SortReGroups)
if err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, err))
return
}
2022-07-11 17:57:33 +00:00
fk := &fic.FlagKey{
Type: flag.Type,
IdExercice: exercice.Id,
Order: int8(flagline),
Label: flag.Label,
Placeholder: flag.Placeholder,
Help: flag.Help,
Unit: flag.Unit,
IgnoreCase: !flag.CaseSensitive,
Multiline: flag.Type == "text",
CaptureRegexp: validatorRegexp(flag.CaptureRe),
SortReGroups: flag.SortReGroups,
Checksum: hashedFlag[:],
ChoicesCost: flag.ChoicesCost,
BonusGain: flag.BonusGain,
2022-07-11 17:57:33 +00:00
}
// Call checks hooks
for _, h := range hooks.flagKeyHooks {
for _, e := range multierr.Errors(h(fk, raw, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
2022-07-11 17:57:33 +00:00
}
}
fl := fic.Flag(fk)
f = &fl
2021-11-12 23:58:03 +00:00
if len(flag.Choice) > 0 || (flag.Type == "ucq" || flag.Type == "radio") {
// Import choices
hasOne := false
if !flag.NoShuffle {
rand.Shuffle(len(flag.Choice), func(i, j int) {
flag.Choice[i], flag.Choice[j] = flag.Choice[j], flag.Choice[i]
2019-01-16 04:25:20 +00:00
})
}
for _, choice := range flag.Choice {
2020-12-11 20:03:12 +00:00
val, prep, terrs := getRawKey(choice.Value, "", false, false, "")
errors := multierr.Errors(terrs)
if len(errors) > 0 {
for _, terr := range errors {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, terr))
2022-07-11 17:57:33 +00:00
}
continue
}
if len(choice.Label) == 0 {
choice.Label = val
2018-12-04 17:34:38 +00:00
}
choice.Label = prep + choice.Label
2018-12-04 17:34:38 +00:00
if !flag.CaseSensitive {
val = strings.ToLower(val)
}
2022-07-11 17:57:33 +00:00
fc := &fic.FlagChoice{
Label: choice.Label,
Value: val,
2022-07-11 17:57:33 +00:00
}
// Call checks hooks
for _, h := range hooks.flagChoiceHooks {
for _, e := range multierr.Errors(h(fc, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
2022-07-11 17:57:33 +00:00
}
}
choices = append(choices, fc)
if val == "true" || val == "false" {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("value can't be %q, this is not a MCQ, the value has to be meaningful. The value is shown to players as response identifier.", val)))
}
2020-01-28 17:47:36 +00:00
if val == raw || (!flag.CaseSensitive && val == strings.ToLower(raw)) {
hasOne = true
2018-12-04 17:34:38 +00:00
}
}
if !hasOne {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("no valid answer defined.")))
2022-07-11 17:57:33 +00:00
}
// Call checks hooks
for _, h := range hooks.flagKeyWithChoicesHooks {
for _, e := range multierr.Errors(h(fk, raw, choices, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
2022-07-11 17:57:33 +00:00
}
}
}
return
}
2018-12-04 17:34:38 +00:00
type importFlag struct {
Line int
Flag fic.Flag
2022-10-31 20:55:43 +00:00
JustifyOf *fic.MCQ_entry
2021-11-22 14:35:07 +00:00
Choices []*fic.FlagChoice
FilesDeps []string
FlagsDeps []int64
}
func iface2Number(input interface{}, output *string) (norm float64, err error) {
2021-11-12 20:36:27 +00:00
if input != nil {
if v, ok := input.(int64); ok {
*output = fmt.Sprintf("%d", v)
norm = float64(v)
2021-11-12 20:36:27 +00:00
} else if v, ok := input.(float64); ok {
*output = strconv.FormatFloat(v, 'f', -1, 64)
norm = v
2021-11-12 20:36:27 +00:00
} else {
err = fmt.Errorf("has an invalid type: expected int or float, got %T", input)
2021-11-12 20:36:27 +00:00
}
}
return
2021-11-12 20:36:27 +00:00
}
2022-10-31 20:55:43 +00:00
// buildExerciceFlag read challenge.txt and extract all flags.
func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nline int, exceptions *CheckExceptions) (ret []importFlag, errs error) {
switch strings.ToLower(flag.Type) {
case "":
flag.Type = "key"
2022-01-21 12:06:37 +00:00
case "label":
flag.Type = "label"
case "key":
flag.Type = "key"
2021-11-12 20:36:27 +00:00
case "number":
var smin, smax, sstep string
fstep, err := iface2Number(flag.NumberStep, &sstep)
if err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("step %w", err)))
}
_, err = iface2Number(flag.NumberMin, &smin)
2021-11-12 20:36:27 +00:00
if err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("min %w", err)))
2021-11-12 20:36:27 +00:00
}
_, err = iface2Number(flag.NumberMax, &smax)
2021-11-12 20:36:27 +00:00
if err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("max %w", err)))
2021-11-12 20:36:27 +00:00
}
// Ensure step permit validating the flag
if rns, err := flag.RawNumber(); err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("raw %w", err)))
} else {
if fstep == 0 {
fstep = 1.0
}
for _, rn := range rns {
v := math.Abs(rn) / fstep
if float64(int(v)) != v {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("choosen step=%f doesn't include response=%f", fstep, rn)))
}
}
2021-11-12 20:36:27 +00:00
}
2021-11-12 20:36:27 +00:00
flag.Type = fmt.Sprintf("number,%s,%s,%s", smin, smax, sstep)
case "text":
flag.Type = "text"
case "vector":
flag.Type = "vector"
case "ucq":
flag.Type = "ucq"
2021-11-12 23:58:03 +00:00
case "radio":
flag.Type = "radio"
case "mcq":
flag.Type = "mcq"
2024-05-17 22:28:59 +00:00
case "justified":
flag.Type = "justified"
default:
2024-05-17 22:28:59 +00:00
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("invalid type of flag: should be 'key', 'number', 'text', 'mcq', 'justified', 'ucq', 'radio' or 'vector'")))
return
}
2021-11-12 20:36:27 +00:00
if !strings.HasPrefix(flag.Type, "number") {
if flag.NumberMin != nil {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property min undefined for this kind of flag: should the type be 'number'")))
2021-11-12 20:36:27 +00:00
} else if flag.NumberMax != nil {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property max undefined for this kind of flag: should the type be 'number'")))
2021-11-12 20:36:27 +00:00
} else if flag.NumberStep != nil {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property step undefined for this kind of flag: should the type be 'number'")))
2021-11-12 20:36:27 +00:00
}
}
2022-01-21 07:45:07 +00:00
if len(flag.Help) > 0 {
// Call checks hooks
for _, hk := range hooks.mdTextHooks {
for _, err := range multierr.Errors(hk(flag.Help, exercice.Language, exceptions)) {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, err))
}
}
2022-01-21 07:45:07 +00:00
if mdhelp, err := ProcessMarkdown(i, flag.Help, exercice.Path); err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("unable to parse property help as Markdown: %w", err)))
2022-01-21 07:45:07 +00:00
} else {
flag.Help = mdhelp[3 : len(mdhelp)-4]
}
}
2022-01-21 12:06:37 +00:00
if flag.Type == "label" {
addedFlag, berrs := buildLabelFlag(exercice, flag, nline+1, exceptions)
errs = multierr.Append(errs, berrs)
2022-01-21 12:06:37 +00:00
if addedFlag != nil {
ret = append(ret, importFlag{
Line: nline + 1,
Flag: addedFlag,
})
}
} else if flag.Type == "key" || strings.HasPrefix(flag.Type, "number") || flag.Type == "text" || flag.Type == "ucq" || flag.Type == "radio" || flag.Type == "vector" {
addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag", exceptions)
errs = multierr.Append(errs, berrs)
if addedFlag != nil {
ret = append(ret, importFlag{
Line: nline + 1,
Flag: *addedFlag,
Choices: choices,
})
}
2024-05-17 22:28:59 +00:00
} else if flag.Type == "mcq" || flag.Type == "justified" {
addedFlag := fic.MCQ{
IdExercice: exercice.Id,
Order: int8(nline + 1),
Title: flag.Label,
2021-11-22 14:35:07 +00:00
Entries: []*fic.MCQ_entry{},
}
hasOne := false
2024-05-17 22:28:59 +00:00
isJustified := flag.Type == "justified"
2022-01-21 12:06:37 +00:00
if len(flag.Variant) != 0 {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("variant is not defined for this kind of flag")))
2022-01-21 12:06:37 +00:00
}
if !flag.NoShuffle {
rand.Shuffle(len(flag.Choice), func(i, j int) {
flag.Choice[i], flag.Choice[j] = flag.Choice[j], flag.Choice[i]
})
}
for cid, choice := range flag.Choice {
var val bool
if choice.Raw != nil {
if hasOne && !isJustified {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
continue
}
val = true
isJustified = true
} else if p, ok := choice.Value.(bool); ok {
val = p
if isJustified {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
continue
}
} else if choice.Value == nil {
val = false
} else {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("choice %d: incorrect type for value: %T is not boolean.", cid, choice.Value)))
continue
}
2022-10-31 20:55:43 +00:00
entry := &fic.MCQ_entry{
Label: choice.Label,
Response: val,
2022-10-31 20:55:43 +00:00
}
addedFlag.Entries = append(addedFlag.Entries, entry)
if isJustified && choice.Raw != nil {
addedFlag, choices, berrs := buildKeyFlag(exercice, choice.ExerciceFlag, nline+1, "Flag correspondant", exceptions)
errs = multierr.Append(errs, berrs)
if addedFlag != nil {
ret = append(ret, importFlag{
2022-10-31 20:55:43 +00:00
Line: nline + 1,
Flag: *addedFlag,
JustifyOf: entry,
Choices: choices,
})
}
}
}
2022-07-11 17:57:33 +00:00
// Call checks hooks
for _, h := range hooks.flagMCQHooks {
for _, e := range multierr.Errors(h(&addedFlag, addedFlag.Entries, exercice, exceptions)) {
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, e))
2022-07-11 17:57:33 +00:00
}
}
2022-10-31 20:55:43 +00:00
ret = append([]importFlag{importFlag{
2020-04-15 05:39:38 +00:00
Line: nline + 1,
2021-11-22 14:35:07 +00:00
Flag: &addedFlag,
2022-10-31 20:55:43 +00:00
}}, ret...)
}
return
}
// buildExerciceFlags read challenge.txt and extract all flags.
func buildExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (flags map[int64]importFlag, flagids []int64, errs error) {
params, gerrs := getExerciceParams(i, exercice)
if len(multierr.Errors(gerrs)) > 0 {
return flags, flagids, gerrs
}
2018-12-02 18:21:07 +00:00
flags = map[int64]importFlag{}
2018-12-02 18:21:07 +00:00
for nline, flag := range params.Flags {
if flag.Id == 0 {
// TODO: should be more smart than that. Perhaps search to increment if possible.
flag.Id = rand.Int63()
}
2017-12-16 02:39:57 +00:00
// Ensure flag ID is unique
for _, ok := flags[flag.Id]; ok; _, ok = flags[flag.Id] {
errs = multierr.Append(errs, NewFlagError(exercice, &params.Flags[nline], nline+1, fmt.Errorf("identifier already used (%d), using a random one.", flag.Id)))
flag.Id = rand.Int63()
}
2018-12-02 18:21:07 +00:00
newFlags, ferrs := buildExerciceFlag(i, exercice, flag, nline, exceptions)
errs = multierr.Append(errs, ferrs)
if len(newFlags) > 0 {
for _, newFlag := range newFlags {
fId := flag.Id
for _, ok := flags[fId]; ok; _, ok = flags[fId] {
fId = rand.Int63()
2018-12-02 18:21:07 +00:00
}
// Read dependency to flag
for _, nf := range flag.NeedFlag {
if len(nf.Theme) > 0 {
errs = multierr.Append(errs, NewFlagError(exercice, &params.Flags[nline], nline+1, fmt.Errorf("dependancy on another scenario is not implemented yet.")))
}
newFlag.FlagsDeps = append(newFlag.FlagsDeps, nf.Id)
}
for _, nf := range flag.NeedFlags {
newFlag.FlagsDeps = append(newFlag.FlagsDeps, nf)
}
2019-01-16 04:25:20 +00:00
// Read dependency to file
for _, lf := range flag.LockedFile {
newFlag.FilesDeps = append(newFlag.FilesDeps, lf.Filename)
2018-11-21 03:10:22 +00:00
}
flags[fId] = newFlag
flagids = append(flagids, fId)
}
}
}
return
}
// CheckExerciceFlags checks if all flags for the given challenge are correct.
func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exceptions *CheckExceptions) (rf []fic.Flag, errs error) {
exceptions = exceptions.GetFileExceptions("challenge.toml", "challenge.txt")
flags, flagsids, berrs := buildExerciceFlags(i, exercice, exceptions)
errs = multierr.Append(errs, berrs)
for _, flagid := range flagsids {
if flag, ok := flags[flagid]; ok {
// Check dependency to flag
for _, nf := range flag.FlagsDeps {
if _, ok := flags[nf]; !ok {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on flag id=%d: id not defined", nf)))
}
}
if fk, ok := flag.Flag.(*fic.FlagKey); ok {
// Check dependency to flag optional flag
if fk.BonusGain == 0 {
for _, nf := range flag.FlagsDeps {
if fk2, ok := flags[nf].Flag.(*fic.FlagKey); ok && fk2.BonusGain != 0 {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag is not optional but depend on flag id=%d which is optional", nf)))
}
}
}
if int64(fk.ChoicesCost) >= exercice.Gain {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag's choice_cost is higher than exercice gain")))
}
}
// Check dependency loop
deps := flag.FlagsDeps
for i := 0; i < len(deps); i++ {
if deps[i] == flagid {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag dependency loop detected: flag id=%d: depends on itself", flagid)))
break
}
deploppadd:
for _, d := range flags[deps[i]].FlagsDeps {
for _, dd := range deps {
if dd == d {
continue deploppadd
}
}
deps = append(deps, d)
}
}
// Check dependency to file
for _, lf := range flag.FilesDeps {
found := false
for _, f := range files {
if f == lf {
found = true
break
}
}
if !found {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on %s: No such file", lf)))
}
2019-01-16 04:25:20 +00:00
}
rf = append(rf, flag.Flag)
}
}
return
}
// ExerciceFlagsMap builds the flags bindings between challenge.txt and DB.
2021-11-22 14:35:07 +00:00
func ExerciceFlagsMap(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.Flag) {
flags, flagids, _ := buildExerciceFlags(i, exercice, nil)
kmap = map[int64]fic.Flag{}
for _, flagid := range flagids {
if flag, ok := flags[flagid]; ok {
if addedFlag, err := flag.Flag.RecoverId(); err == nil {
kmap[flagid] = addedFlag
}
}
}
return
}
// SyncExerciceFlags imports all kind of flags for the given challenge.
func SyncExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (kmap map[int64]fic.Flag, errs error) {
if _, err := exercice.WipeFlags(); err != nil {
errs = multierr.Append(errs, err)
} else if _, err := exercice.WipeMCQs(); err != nil {
errs = multierr.Append(errs, err)
} else {
exceptions = exceptions.GetFileExceptions("challenge.toml", "challenge.txt")
flags, flagids, berrs := buildExerciceFlags(i, exercice, exceptions)
errs = multierr.Append(errs, berrs)
kmap = map[int64]fic.Flag{}
// Import flags
for _, flagid := range flagids {
if flag, ok := flags[flagid]; ok {
2022-10-31 20:55:43 +00:00
if flag.JustifyOf != nil {
if f, ok := flag.Flag.(*fic.FlagKey); ok {
f.Label = fmt.Sprintf("%%%d%%%s", flag.JustifyOf.Id, f.Label)
}
}
if addedFlag, err := exercice.AddFlag(flag.Flag); err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, err))
} else {
2021-11-22 14:35:07 +00:00
if f, ok := addedFlag.(*fic.FlagKey); ok {
for _, choice := range flag.Choices {
if _, err := f.AddChoice(choice); err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("choice #FIXME: %w", err)))
}
}
}
kmap[flagid] = addedFlag
// Import dependency to flag
for _, nf := range flag.FlagsDeps {
if rf, ok := kmap[nf]; !ok {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to flag id=%d: id not defined, perhaps not available at time of processing", nf)))
} else if err := addedFlag.AddDepend(rf); err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to id=%d: %w", nf, err)))
}
}
// Import dependency to file
for _, lf := range flag.FilesDeps {
if rf, err := exercice.GetFileByFilename(lf); err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
} else if err := rf.AddDepend(addedFlag); err != nil {
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
}
}
2017-12-16 02:39:57 +00:00
}
}
}
}
2018-05-12 00:01:49 +00:00
return
2017-12-16 02:39:57 +00:00
}
// ApiListRemoteExerciceFlags is an accessor letting foreign packages to access remote exercice flags.
2022-05-16 09:38:46 +00:00
func ApiGetRemoteExerciceFlags(c *gin.Context) {
theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
if theme != nil {
exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions)
if exercice != nil {
flags, errs := CheckExerciceFlags(GlobalImporter, exercice, []string{}, eexceptions)
if flags != nil {
2022-05-16 09:38:46 +00:00
c.JSON(http.StatusOK, flags)
return
}
2022-05-16 09:38:46 +00:00
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
return
}
2022-05-16 09:38:46 +00:00
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
return
}
2022-05-16 09:38:46 +00:00
c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs))
return
}