package sync import ( "fmt" "math/rand" "path" "sort" "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 } func getRawKey(flag ExerciceFlag) (raw string, prep string, errs []string) { // Concatenate array if f, ok := flag.Raw.([]interface{}); ok { if len(flag.ValidatorRe) > 0 { errs = append(errs, "ValidatorRe cannot be defined for this kind of flag.") flag.ValidatorRe = "" } if len(flag.Separator) == 0 { flag.Separator = "," } else if len(flag.Separator) > 1 { flag.Separator = string(flag.Separator[0]) errs = append(errs, "separator truncated to %q") } var fitems []string for _, v := range f { if g, ok := v.(string); ok { if strings.Index(g, flag.Separator) != -1 { errs = append(errs, "flag items cannot contain %q character as it is used as separator. Change the separator attribute for this flag.") return } else { fitems = append(fitems, g) } } else { errs = append(errs, "item %d has an invalid type: can only be string, is %T.") return } } ignord := "t" if flag.Ordered { sort.Strings(fitems) ignord = "f" } raw = strings.Join(fitems, flag.Separator) + flag.Separator prep = "`" + flag.Separator + ignord } else if f, ok := flag.Raw.(int64); ok { raw = fmt.Sprintf("%d", f) } else if f, ok := flag.Value.(string); ok && len(f) > 0 { raw = f } else if f, ok := flag.Raw.(string); !ok { errs = append(errs, fmt.Sprintf("has an invalid type: can only be []string or string, not %T", flag.Raw)) return } else { raw = f } 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 UCQ flags for _, flag := range params.FlagsUCQ { params.Flags = append(params.Flags, ExerciceFlag{ Id: flag.Id, Label: flag.Label, Type: "ucq", Raw: flag.Raw, ValidatorRe: flag.ValidatorRe, Help: flag.Help, ChoicesCost: flag.ChoicesCost, Choice: flag.Choice, LockedFile: flag.LockedFile, NeedFlag: flag.NeedFlag, }) } // Import MCQ flags 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, }) } // Import normal 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:] } switch strings.ToLower(flag.Type) { case "": flag.Type = "key" case "key": flag.Type = "key" case "ucq": flag.Type = "ucq" case "mcq": flag.Type = "mcq" default: errs = append(errs, fmt.Sprintf("%q: flag #%d: invalid type of flag: should be 'key', 'mcq' or 'ucq'.", path.Base(exercice.Path), nline + 1)) continue } var addedFlag fic.Flag if flag.Type == "key" || flag.Type == "ucq" { raw, prep, terrs := getRawKey(flag) if len(terrs) > 0 { for _, err := range terrs { errs = append(errs, fmt.Sprintf("%q: flag #%d: %s", path.Base(exercice.Path), nline + 1, err)) } continue } flag.Label = prep + flag.Label 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.AddRawFlagKey(flag.Label, flag.Help, flag.IgnoreCase, validatorRegexp(flag.ValidatorRe), []byte(raw), flag.ChoicesCost); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag #%d: %s", path.Base(exercice.Path), nline + 1, err)) continue } else { addedFlag = k if len(flag.Choice) > 0 || flag.Type == "ucq" { // 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 { val, prep, terrs := getRawKey(choice) if len(terrs) > 0 { for _, err := range terrs { errs = append(errs, fmt.Sprintf("%q: flag UCQ #%d: %s", path.Base(exercice.Path), nline + 1, err)) } continue } choice.Label = prep + choice.Label if len(choice.Label) == 0 { choice.Label = val } if _, err := k.AddChoice(choice.Label, val); 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 val == 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)) } } } } else if flag.Type == "mcq" { if f, err := exercice.AddMCQ(flag.Label); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag MCQ #%d: %s", path.Base(exercice.Path), nline + 1, err)) continue } else { addedFlag = f hasOne := false isJustified := 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 { 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 choice.Raw == nil && choice.Value != nil { choice.Raw = choice.Value choice.Value = nil } 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 } var prep string var terrs []string val = true isJustified = true justify, prep, terrs = getRawKey(choice) if len(terrs) > 0 { for _, err := range terrs { errs = append(errs, fmt.Sprintf("%q: flag MCQ #%d, choice #%d: %s", path.Base(exercice.Path), nline + 1, cid, err)) } continue } choice.Help = prep + choice.Help } if e, err := f.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.AddRawFlagKey(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)) } } } if flag.Id != 0 { kmap[flag.Id] = addedFlag } // 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=%d: id not defined, perhaps not available at time of processing", path.Base(exercice.Path), nline + 1, nf.Id)) continue } else if err := addedFlag.AddDepend(rf); err != nil { errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to id=%d: %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(addedFlag); 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 } } } } return }