From c3742ade4ec62f69ec8c32588f60c41df541c8ee Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Fri, 12 Nov 2021 21:36:27 +0100 Subject: [PATCH] Implement number flags --- admin/api/exercice.go | 3 +- admin/sync/exercice_defines.go | 3 ++ admin/sync/exercice_keys.go | 49 ++++++++++++++++++++++- frontend/ui/src/components/FlagKey.svelte | 15 ++++++- libfic/flag_key.go | 3 +- libfic/team_my.go | 36 +++++++++++++++++ 6 files changed, 104 insertions(+), 5 deletions(-) diff --git a/admin/api/exercice.go b/admin/api/exercice.go index ef597be2..5486c876 100644 --- a/admin/api/exercice.go +++ b/admin/api/exercice.go @@ -399,6 +399,7 @@ func deleteExerciceHint(hint fic.EHint, _ []byte) (interface{}, error) { } type uploadedFlag struct { + Type string Label string Placeholder string IgnoreCase bool @@ -424,7 +425,7 @@ func createExerciceFlag(exercice fic.Exercice, body []byte) (interface{}, error) vre = uk.ValidatorRe } - return exercice.AddRawFlagKey(uk.Label, uk.Placeholder, uk.IgnoreCase, uk.Multiline, vre, []byte(uk.Flag), uk.ChoicesCost) + return exercice.AddRawFlagKey(uk.Label, uk.Type, uk.Placeholder, uk.IgnoreCase, uk.Multiline, vre, []byte(uk.Flag), uk.ChoicesCost) } func showExerciceFlag(flag fic.FlagKey, _ fic.Exercice, body []byte) (interface{}, error) { diff --git a/admin/sync/exercice_defines.go b/admin/sync/exercice_defines.go index 814fe134..8bb946dd 100644 --- a/admin/sync/exercice_defines.go +++ b/admin/sync/exercice_defines.go @@ -46,6 +46,9 @@ type ExerciceFlag struct { LockedFile []ExerciceUnlockFile `toml:"unlock_file,omitempty"` NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"` NoShuffle bool + NumberMin interface{} `toml:"min,omitempty"` + NumberMax interface{} `toml:"max,omitempty"` + NumberStep interface{} `toml:"step,omitempty"` } // ExerciceFlagChoice holds informations about a choice (for MCQ and UCQ). diff --git a/admin/sync/exercice_keys.go b/admin/sync/exercice_keys.go index 10030fac..2979f00c 100644 --- a/admin/sync/exercice_keys.go +++ b/admin/sync/exercice_keys.go @@ -5,6 +5,7 @@ import ( "math/rand" "path" "sort" + "strconv" "strings" "unicode" @@ -83,6 +84,8 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo prep = fmt.Sprintf("`%s%s%d", separator, ignord, nbLines) } else if f, ok := input.(int64); ok { raw = fmt.Sprintf("%d", f) + } else if f, ok := input.(float64); ok { + raw = strconv.FormatFloat(f, 'f', -1, 64) } else if f, ok := input.(string); !ok { errs = append(errs, fmt.Sprintf("has an invalid type: can only be []string or string, not %T", input)) return @@ -127,6 +130,7 @@ func buildKeyFlag(exercice fic.Exercice, flag ExerciceFlag, flagline int, defaul return } fl := fic.Flag(fic.FlagKey{ + Type: flag.Type, IdExercice: exercice.Id, Order: int8(flagline), Label: flag.Label, @@ -192,6 +196,20 @@ type importFlag struct { FlagsDeps []int64 } +func iface2Number(input interface{}, output *string) error { + if input != nil { + if v, ok := input.(int64); ok { + *output = fmt.Sprintf("%d", v) + } else if v, ok := input.(float64); ok { + *output = strconv.FormatFloat(v, 'f', -1, 64) + fmt.Printf("%s\n", *output) + } else { + return fmt.Errorf("has an invalid type: expected int or float, got %T", input) + } + } + return nil +} + // buildExerciceFlags read challenge.txt and extract all flags. func buildExerciceFlag(i Importer, exercice fic.Exercice, flag ExerciceFlag, nline int) (ret []importFlag, errs []string) { switch strings.ToLower(flag.Type) { @@ -199,6 +217,23 @@ func buildExerciceFlag(i Importer, exercice fic.Exercice, flag ExerciceFlag, nli flag.Type = "key" case "key": flag.Type = "key" + case "number": + var smin, smax, sstep string + err := iface2Number(flag.NumberMin, &smin) + if err != nil { + errs = append(errs, fmt.Sprintf("%q: flag #%d: min %s.", path.Base(exercice.Path), nline+1, err.Error())) + } + + err = iface2Number(flag.NumberMax, &smax) + if err != nil { + errs = append(errs, fmt.Sprintf("%q: flag #%d: max %s.", path.Base(exercice.Path), nline+1, err.Error())) + } + + err = iface2Number(flag.NumberStep, &sstep) + if err != nil { + errs = append(errs, fmt.Sprintf("%q: flag #%d: step %s.", path.Base(exercice.Path), nline+1, err.Error())) + } + flag.Type = fmt.Sprintf("number,%s,%s,%s", smin, smax, sstep) case "text": flag.Type = "text" case "vector": @@ -208,11 +243,21 @@ func buildExerciceFlag(i Importer, exercice fic.Exercice, flag ExerciceFlag, nli case "mcq": flag.Type = "mcq" default: - errs = append(errs, fmt.Sprintf("%q: flag #%d: invalid type of flag: should be 'key', 'text', 'mcq', 'ucq' or 'vector'.", path.Base(exercice.Path), nline+1)) + errs = append(errs, fmt.Sprintf("%q: flag #%d: invalid type of flag: should be 'key', 'number', 'text', 'mcq', 'ucq' or 'vector'.", path.Base(exercice.Path), nline+1)) return } - if flag.Type == "key" || flag.Type == "text" || flag.Type == "ucq" || flag.Type == "vector" { + if !strings.HasPrefix(flag.Type, "number") { + if flag.NumberMin != nil { + errs = append(errs, fmt.Sprintf("%q: flag #%d: property min undefined for this kind of flag: should the type be 'number'.", path.Base(exercice.Path), nline+1)) + } else if flag.NumberMax != nil { + errs = append(errs, fmt.Sprintf("%q: flag #%d: property max undefined for this kind of flag: should the type be 'number'.", path.Base(exercice.Path), nline+1)) + } else if flag.NumberStep != nil { + errs = append(errs, fmt.Sprintf("%q: flag #%d: property step undefined for this kind of flag: should the type be 'number'.", path.Base(exercice.Path), nline+1)) + } + } + + if flag.Type == "key" || strings.HasPrefix(flag.Type, "number") || flag.Type == "text" || flag.Type == "ucq" || flag.Type == "vector" { addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag") if len(berrs) > 0 { errs = append(errs, berrs...) diff --git a/frontend/ui/src/components/FlagKey.svelte b/frontend/ui/src/components/FlagKey.svelte index b826b7c8..f97434ab 100644 --- a/frontend/ui/src/components/FlagKey.svelte +++ b/frontend/ui/src/components/FlagKey.svelte @@ -103,7 +103,20 @@ {#each values as v, index}
{#if !flag.choices} - {#if !flag.multiline} + {#if flag.type == 'number'} + + {:else if !flag.multiline} 0 { + tmp_min, err = strconv.ParseFloat(fields[1], 64) + if err != nil { + return nil, err + } + flag.Min = &tmp_min + } + + if len(fields[2]) > 0 { + tmp_max, err = strconv.ParseFloat(fields[2], 64) + if err != nil { + return nil, err + } + flag.Max = &tmp_max + } + + if len(fields[3]) > 0 { + tmp_step, err = strconv.ParseFloat(fields[3], 64) + if err != nil { + return nil, err + } + flag.Step = &tmp_step + } + } + } + // Retrieve solved state or solution for public iface if t == nil { flag.IgnoreCase = k.IgnoreCase