Implement number flags

This commit is contained in:
nemunaire 2021-11-12 21:36:27 +01:00
parent 30d0afe43f
commit c3742ade4e
6 changed files with 104 additions and 5 deletions

View File

@ -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) {

View File

@ -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).

View File

@ -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...)

View File

@ -103,7 +103,20 @@
{#each values as v, index}
<div class="input-group" class:mt-1={index != 0}>
{#if !flag.choices}
{#if !flag.multiline}
{#if flag.type == 'number'}
<input
type="number"
class="form-control flag"
id="sol_{flag.type}{flag.id}_{index}"
autocomplete="off"
bind:value={values[index]}
placeholder={flag.placeholder}
title={flag.placeholder}
min={flag.min}
max={flag.max}
step={flag.step}
>
{:else if !flag.multiline}
<input
type="text"
class="form-control flag"

View File

@ -111,13 +111,14 @@ func ExecValidatorRegexp(vre string, val []byte, ignorecase bool) ([]byte, error
}
// AddRawFlagKey creates and fills a new struct FlagKey, from a non-hashed flag, and registers it into the database.
func (e Exercice) AddRawFlagKey(name string, placeholder string, ignorecase bool, multiline bool, validator_regexp *string, raw_value []byte, choicescost int64) (f FlagKey, err error) {
func (e Exercice) AddRawFlagKey(name string, t string, placeholder string, ignorecase bool, multiline bool, validator_regexp *string, raw_value []byte, choicescost int64) (f FlagKey, err error) {
hash, errr := ComputeHashedFlag(raw_value, ignorecase, validator_regexp)
if errr != nil {
return f, err
}
f = FlagKey{
Type: t,
Label: name,
Placeholder: placeholder,
IgnoreCase: ignorecase,

View File

@ -46,6 +46,9 @@ type myTeamFlag struct {
Justify bool `json:"justify,omitempty"`
Choices map[string]interface{} `json:"choices,omitempty"`
ChoicesCost int64 `json:"choices_cost,omitempty"`
Min *float64 `json:"min,omitempty"`
Max *float64 `json:"max,omitempty"`
Step *float64 `json:"step,omitempty"`
}
type myTeamMCQJustifiedChoice struct {
Label string `json:"label"`
@ -204,6 +207,39 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
Help: k.Help,
}
if strings.HasPrefix(flag.Type, "number") {
fields := strings.Split(flag.Type, ",")
if len(fields) == 4 {
flag.Type = fields[0]
var tmp_min, tmp_max, tmp_step float64
if len(fields[1]) > 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