package sync import ( "fmt" "math/rand" "path" "strings" "unicode" "srs.epita.fr/fic-server/libfic" ) // isFullGraphic detects if some rune are not graphic one. // This function is usefull to display warning when importing key ending with \r. func isFullGraphic(s string) bool { for _, c := range s { if !unicode.IsGraphic(c) { return false } } return true } func validatorRegexp(vre string) (validator_regexp *string) { if len(vre) > 0 { validator_regexp = &vre } else { validator_regexp = nil } return } // SyncExerciceFlags reads the content of challenge.txt and import "classic" flags as Key for the given challenge. func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) { if _, err := exercice.WipeFlags(); err != nil { errs = append(errs, err.Error()) } else if _, err := exercice.WipeMCQs(); err != nil { errs = append(errs, err.Error()) } else if params, err := parseExerciceParams(i, exercice.Path); err != nil { errs = append(errs, fmt.Sprintf("%q: challenge.txt: %s", path.Base(exercice.Path), err)) } else if len(params.Flags) == 0 && len(params.FlagsUCQ) == 0 && len(params.FlagsMCQ) == 0 { errs = append(errs, fmt.Sprintf("%q: has no flag", path.Base(exercice.Path))) } else { kmap := map[int64]fic.Flag{} // Import normal flags flags: for nline, flag := range params.Flags { if len(flag.Label) == 0 { flag.Label = "Flag" } if flag.Label[0] == '`' { errs = append(errs, fmt.Sprintf("%q: flag #%d: Label should not begin with `.", path.Base(exercice.Path), nline + 1)) flag.Label = flag.Label[1:] } // Concatenate array var raw string if f, ok := flag.Raw.([]interface{}); ok { if len(flag.ValidatorRe) > 0 { errs = append(errs, fmt.Sprintf("%q: flag #%d: ValidatorRe cannot be defined for this kind of flag.", path.Base(exercice.Path), nline + 1)) flag.ValidatorRe = "" } if len(flag.Separator) == 0 { flag.Separator = "," } else if len(flag.Separator) > 1 { flag.Separator = string(flag.Separator[0]) errs = append(errs, fmt.Sprintf("%q: flag #%d: separator truncated to %q", path.Base(exercice.Path), nline + 1, flag.Separator)) } for i, v := range f { if g, ok := v.(string); ok { if strings.Index(g, flag.Separator) != -1 { errs = append(errs, fmt.Sprintf("%q: flag #%d: flag items cannot contain %q character as it is used as separator. Change the separator attribute for this flag.", path.Base(exercice.Path), nline + 1, flag.Separator)) continue flags } else { raw += g + flag.Separator } } else { errs = append(errs, fmt.Sprintf("%q: flag #%d, item %d has an invalid type: can only be string, is %T.", path.Base(exercice.Path), nline + 1, i, v)) continue flags } } flag.Label = "`" + flag.Separator + flag.Label } else if f, ok := flag.Raw.(int64); ok { raw = fmt.Sprintf("%d", f) } else if f, ok := flag.Raw.(string); !ok { errs = append(errs, fmt.Sprintf("%q: flag #%d has an invalid type: can only be []string or string, not %T", path.Base(exercice.Path), nline + 1, flag.Raw)) continue } else { raw = f } if !isFullGraphic(raw) { errs = append(errs, fmt.Sprintf("%q: WARNING flag #%d: non-printable characters in flag, is this really expected?", path.Base(exercice.Path), nline + 1)) } if k, err := exercice.AddRawFlag(flag.Label, flag.Help, flag.IgnoreCase, validatorRegexp(flag.ValidatorRe), []byte(raw), 0); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag #%d: %s", path.Base(exercice.Path), nline + 1, err)) continue } else { if flag.Id != 0 { kmap[flag.Id] = k } // Import dependency to flag for _, nf := range flag.NeedFlag { if rf, ok := kmap[nf.Id]; !ok { errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to flag id=%s: id not defined, perhaps not available at time of processing", path.Base(exercice.Path), nline + 1, nf.Id)) continue } else if err := k.AddDepend(rf); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, nf.Id, err)) continue } } // Import dependency to file for _, lf := range flag.LockedFile { if rf, err := exercice.GetFileByFilename(lf.Filename); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err)) continue } else if err := rf.AddDepend(k); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err)) continue } } } } // Import UCQ flags for nline, flag := range params.FlagsUCQ { if len(flag.Label) == 0 { flag.Label = "Flag" } if !isFullGraphic(flag.Raw) { errs = append(errs, fmt.Sprintf("%q: WARNING flag UCQ #%d: non-printable characters in flag, is this really expected?", path.Base(exercice.Path), nline + 1)) } if k, err := exercice.AddRawFlag(flag.Label, flag.Help, flag.IgnoreCase, validatorRegexp(flag.ValidatorRe), []byte(flag.Raw), flag.ChoicesCost); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag UCQ #%d: %s", path.Base(exercice.Path), nline + 1, err)) continue } else { // Import dependency to file if flag.Id != 0 { kmap[flag.Id] = k } // Import dependency to flag for _, nf := range flag.NeedFlag { if rf, ok := kmap[nf.Id]; !ok { errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to flag id=%s: id not defined, perhaps not available at time of processing", path.Base(exercice.Path), nline + 1, nf.Id)) continue } else if err := k.AddDepend(rf); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, nf.Id, err)) continue } } // Import dependency to file for _, lf := range flag.LockedFile { if rf, err := exercice.GetFileByFilename(lf.Filename); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag UCQ #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err)) continue } else if err := rf.AddDepend(k); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag UCQ #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err)) continue } } // 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] }) } for cid, choice := range flag.Choice { if len(choice.Label) == 0 { choice.Label = choice.Value } if _, err := k.AddChoice(choice.Label, choice.Value); err != nil { errs = append(errs, fmt.Sprintf("%q: error in UCQ %d choice %d: %s", path.Base(exercice.Path), nline + 1, cid, err)) continue } if choice.Value == flag.Raw { hasOne = true } } if !hasOne { errs = append(errs, fmt.Sprintf("%q: error in UCQ %d: no valid answer defined.", path.Base(exercice.Path), nline + 1)) } } } // Import MCQ flags for nline, quest := range params.FlagsMCQ { if flag, err := exercice.AddMCQ(quest.Label); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag MCQ #%d: %s", path.Base(exercice.Path), nline + 1, err)) continue } else { hasOne := false isJustified := false if !quest.NoShuffle { rand.Shuffle(len(quest.Choice), func(i, j int) { quest.Choice[i], quest.Choice[j] = quest.Choice[j], quest.Choice[i] }) } for cid, choice := range quest.Choice { var val bool var justify string if choice.Value == nil { val = false } else if p, ok := choice.Value.(bool); ok { val = p if len(choice.Help) > 0 { errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: help field has to be used only with justified mcq.", path.Base(exercice.Path), nline + 1)) continue } if isJustified { errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline + 1)) continue } } else if p, ok := choice.Value.(string); ok { val = true if len(choice.Help) == 0 { choice.Help = "Flag correspondant" } if hasOne && !isJustified { errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline + 1)) continue } isJustified = true justify = p } else { errs = append(errs, fmt.Sprintf("%q: error in MCQ %d choice %d: has an invalid type. Expected true, false or a string", path.Base(exercice.Path), nline + 1, cid)) continue } if e, err := flag.AddEntry(choice.Label, val); err != nil { errs = append(errs, fmt.Sprintf("%q: error in MCQ %d choice %d: %s", path.Base(exercice.Path), nline + 1, cid, err)) continue } else if len(justify) > 0 { if _, err := exercice.AddRawFlag(fmt.Sprintf("%%%d%%%s", e.Id, choice.Help), "", false, nil, []byte(justify), 0); err != nil { errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: %s", path.Base(exercice.Path), nline + 1, err)) continue } } if val { hasOne = true } } if !hasOne { errs = append(errs, fmt.Sprintf("%q: warning MCQ %d: no valid answer defined, is this really expected?", path.Base(exercice.Path), nline + 1)) } } } } return }