Implement flag type 'text': this is like keys, but on multiple lines

This commit is contained in:
nemunaire 2020-01-16 15:33:28 +01:00
parent 8f998485bb
commit 47ba134b55
9 changed files with 30 additions and 14 deletions

View file

@ -325,6 +325,7 @@ type uploadedFlag struct {
Label string Label string
Help string Help string
IgnoreCase bool IgnoreCase bool
Multiline bool
ValidatorRe *string `json:"validator_regexp"` ValidatorRe *string `json:"validator_regexp"`
Flag string Flag string
Value []byte Value []byte
@ -346,7 +347,7 @@ func createExerciceFlag(exercice fic.Exercice, body []byte) (interface{}, error)
vre = uk.ValidatorRe vre = uk.ValidatorRe
} }
return exercice.AddRawFlagKey(uk.Label, uk.Help, uk.IgnoreCase, vre, []byte(uk.Flag), uk.ChoicesCost) return exercice.AddRawFlagKey(uk.Label, uk.Help, uk.IgnoreCase, uk.Multiline, vre, []byte(uk.Flag), uk.ChoicesCost)
} }
func showExerciceFlag(flag fic.FlagKey, _ fic.Exercice, body []byte) (interface{}, error) { func showExerciceFlag(flag fic.FlagKey, _ fic.Exercice, body []byte) (interface{}, error) {

View file

@ -146,6 +146,10 @@
<input type="checkbox" class="custom-control-input" id="kicase{{flag.id}}" ng-model="flag.ignorecase"> <input type="checkbox" class="custom-control-input" id="kicase{{flag.id}}" ng-model="flag.ignorecase">
<label class="custom-control-label" for="kicase{{flag.id}}">Ignore case</label> <label class="custom-control-label" for="kicase{{flag.id}}">Ignore case</label>
</div> </div>
<div class="col-auto custom-control custom-checkbox ml-1">
<input type="checkbox" class="custom-control-input" id="kicase{{flag.id}}" ng-model="flag.multiline">
<label class="custom-control-label" for="kmline{{flag.id}}">Multiline</label>
</div>
</div> </div>
<div class="row"> <div class="row">
<input type="text" id="kvre{{flag.id}}" ng-model="flag.validator_regexp" class="col form-control form-control-sm" placeholder="Regexp selecting validation string" title="Regexp selecting validation string"> <input type="text" id="kvre{{flag.id}}" ng-model="flag.validator_regexp" class="col form-control form-control-sm" placeholder="Regexp selecting validation string" title="Regexp selecting validation string">

View file

@ -116,7 +116,7 @@ func buildKeyFlag(exercice fic.Exercice, flag ExerciceFlag, flagline int, defaul
} }
flag.Label = prep + flag.Label flag.Label = prep + flag.Label
if !isFullGraphic(raw) { if (flag.Type == "text" && !isFullGraphic(strings.Replace(raw, "\n", "", -1))) || (flag.Type != "text" && !isFullGraphic(raw)) {
errs = append(errs, fmt.Sprintf("%q: WARNING flag #%d: non-printable characters in flag, is this really expected?", path.Base(exercice.Path), flagline)) errs = append(errs, fmt.Sprintf("%q: WARNING flag #%d: non-printable characters in flag, is this really expected?", path.Base(exercice.Path), flagline))
} }
@ -130,6 +130,7 @@ func buildKeyFlag(exercice fic.Exercice, flag ExerciceFlag, flagline int, defaul
Label: flag.Label, Label: flag.Label,
Help: flag.Help, Help: flag.Help,
IgnoreCase: !flag.CaseSensitive, IgnoreCase: !flag.CaseSensitive,
Multiline: flag.Type == "text",
ValidatorRegexp: validatorRegexp(flag.ValidatorRe), ValidatorRegexp: validatorRegexp(flag.ValidatorRe),
Checksum: hashedFlag[:], Checksum: hashedFlag[:],
ChoicesCost: flag.ChoicesCost, ChoicesCost: flag.ChoicesCost,
@ -191,6 +192,8 @@ func buildExerciceFlag(i Importer, exercice fic.Exercice, flag ExerciceFlag, nli
flag.Type = "key" flag.Type = "key"
case "key": case "key":
flag.Type = "key" flag.Type = "key"
case "text":
flag.Type = "text"
case "vector": case "vector":
flag.Type = "vector" flag.Type = "vector"
case "ucq": case "ucq":
@ -198,11 +201,11 @@ func buildExerciceFlag(i Importer, exercice fic.Exercice, flag ExerciceFlag, nli
case "mcq": case "mcq":
flag.Type = "mcq" flag.Type = "mcq"
default: default:
errs = append(errs, fmt.Sprintf("%q: flag #%d: invalid type of flag: should be 'key', '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', 'text', 'mcq', 'ucq' or 'vector'.", path.Base(exercice.Path), nline+1))
return return
} }
if flag.Type == "key" || flag.Type == "ucq" || flag.Type == "vector" { if flag.Type == "key" || flag.Type == "text" || flag.Type == "ucq" || flag.Type == "vector" {
addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag") addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag")
if len(berrs) > 0 { if len(berrs) > 0 {
errs = append(errs, berrs...) errs = append(errs, berrs...)

View file

@ -125,7 +125,8 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
<label for="sol_{{ $ctrl.kid }}_0" ng-class="{'text-light': !$ctrl.key.found}">{{ $ctrl.key.label }}&nbsp;:</label> <label for="sol_{{ $ctrl.kid }}_0" ng-class="{'text-light': !$ctrl.key.found}">{{ $ctrl.key.label }}&nbsp;:</label>
<span ng-if="$ctrl.key.found && $ctrl.key.value" ng-bind="$ctrl.key.value"></span> <span ng-if="$ctrl.key.found && $ctrl.key.value" ng-bind="$ctrl.key.value"></span>
<div class="input-group" ng-repeat="v in $ctrl.key.values track by $index" ng-class="{'mt-1': !$first}" ng-if="!$ctrl.key.found"> <div class="input-group" ng-repeat="v in $ctrl.key.values track by $index" ng-class="{'mt-1': !$first}" ng-if="!$ctrl.key.found">
<input type="text" class="form-control flag" id="sol_{{ $ctrl.kid }}_{{ $index }}" autocomplete="off" name="sol_{{ $ctrl.kid }}_{{ $index }}" ng-model="$ctrl.key.values[$index]" ng-if="!$ctrl.key.choices" placeholder="{{ $ctrl.key.help }}" title="{{ $ctrl.key.help }}"> <input type="text" class="form-control flag" id="sol_{{ $ctrl.kid }}_{{ $index }}" autocomplete="off" name="sol_{{ $ctrl.kid }}_{{ $index }}" ng-model="$ctrl.key.values[$index]" ng-if="!$ctrl.key.choices && !$ctrl.key.multiline" placeholder="{{ $ctrl.key.help }}" title="{{ $ctrl.key.help }}">
<textarea class="form-control flag" id="sol_{{ $ctrl.kid }}_{{ $index }}" autocomplete="off" name="sol_{{ $ctrl.kid }}_{{ $index }}" ng-model="$ctrl.key.values[$index]" ng-if="!$ctrl.key.choices && $ctrl.key.multiline" placeholder="{{ $ctrl.key.help }}" title="{{ $ctrl.key.help }}"></textarea>
<select class="custom-select" id="sol_{{ $ctrl.kid }}" name="sol_{{ $ctrl.kid }}" ng-model="$ctrl.key.values[$index]" ng-if="$ctrl.key.choices" ng-options="l as v for (l, v) in $ctrl.key.choices"></select> <select class="custom-select" id="sol_{{ $ctrl.kid }}" name="sol_{{ $ctrl.kid }}" ng-model="$ctrl.key.values[$index]" ng-if="$ctrl.key.choices" ng-options="l as v for (l, v) in $ctrl.key.choices"></select>
<div class="input-group-append" ng-if="$ctrl.key.choices_cost"> <div class="input-group-append" ng-if="$ctrl.key.choices_cost">
<button class="btn btn-success" type="button" ng-click="$ctrl.wantchoices($ctrl.kid)" ng-class="{disabled: $ctrl.key.wcsubmitted}" title="Cliquez pour échanger ce champ de texte par une liste de choix. L'opération vous coûtera {{ $ctrl.key.choices_cost * $ctrl.settings.wchoiceCurrentCoefficient }} points."> <button class="btn btn-success" type="button" ng-click="$ctrl.wantchoices($ctrl.kid)" ng-class="{disabled: $ctrl.key.wcsubmitted}" title="Cliquez pour échanger ce champ de texte par une liste de choix. L'opération vous coûtera {{ $ctrl.key.choices_cost * $ctrl.settings.wchoiceCurrentCoefficient }} points.">

View file

@ -35,7 +35,7 @@ func (c submissionChecker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" { if r.Method != "POST" {
http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest) http.Error(w, "{\"errmsg\":\"Requête invalide.\"}", http.StatusBadRequest)
return return
} else if r.ContentLength < 0 || r.ContentLength > 1023 { } else if r.ContentLength < 0 || r.ContentLength > 2047 {
http.Error(w, "{\"errmsg\":\"Requête trop longue ou de taille inconnue\"}", http.StatusRequestEntityTooLarge) http.Error(w, "{\"errmsg\":\"Requête trop longue ou de taille inconnue\"}", http.StatusRequestEntityTooLarge)
return return
} }

View file

@ -174,6 +174,7 @@ CREATE TABLE IF NOT EXISTS exercice_flags(
type VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL,
help VARCHAR(255) NOT NULL, help VARCHAR(255) NOT NULL,
ignorecase BOOLEAN NOT NULL DEFAULT 0, ignorecase BOOLEAN NOT NULL DEFAULT 0,
multiline BOOLEAN NOT NULL DEFAULT 0,
validator_regexp VARCHAR(255) NULL, validator_regexp VARCHAR(255) NULL,
cksum BINARY(64) NOT NULL, cksum BINARY(64) NOT NULL,
choices_cost INTEGER NOT NULL, choices_cost INTEGER NOT NULL,

View file

@ -323,7 +323,7 @@ func (f EFile) GetDepends() ([]Flag, error) {
if err := rows.Scan(&d); err != nil { if err := rows.Scan(&d); err != nil {
return nil, err return nil, err
} }
deps = append(deps, FlagKey{d, f.IdExercice, "", "", false, nil, []byte{}, 0}) deps = append(deps, FlagKey{d, f.IdExercice, "", "", false, false, nil, []byte{}, 0})
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
return nil, err return nil, err

View file

@ -21,6 +21,8 @@ type FlagKey struct {
Help string `json:"help"` Help string `json:"help"`
// IgnoreCase indicates if the case is sensitive to case or not // IgnoreCase indicates if the case is sensitive to case or not
IgnoreCase bool `json:"ignorecase"` IgnoreCase bool `json:"ignorecase"`
// Multiline indicates if the flag is stored on multiple lines
Multiline bool `json:"multiline"`
// ValidatorRegexp extracts a subset of the player's answer, that will be checked. // ValidatorRegexp extracts a subset of the player's answer, that will be checked.
ValidatorRegexp *string `json:"validator_regexp"` ValidatorRegexp *string `json:"validator_regexp"`
// Checksum is the expected hashed flag // Checksum is the expected hashed flag
@ -31,7 +33,7 @@ type FlagKey struct {
// GetFlagKeys returns a list of key's flags comming with the challenge. // GetFlagKeys returns a list of key's flags comming with the challenge.
func (e Exercice) GetFlagKeys() ([]FlagKey, error) { func (e Exercice) GetFlagKeys() ([]FlagKey, error) {
if rows, err := DBQuery("SELECT id_flag, id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil { if rows, err := DBQuery("SELECT id_flag, id_exercice, type, help, ignorecase, multiline, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_exercice = ?", e.Id); err != nil {
return nil, err return nil, err
} else { } else {
defer rows.Close() defer rows.Close()
@ -41,7 +43,7 @@ func (e Exercice) GetFlagKeys() ([]FlagKey, error) {
var k FlagKey var k FlagKey
k.IdExercice = e.Id k.IdExercice = e.Id
if err := rows.Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost); err != nil { if err := rows.Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.Multiline, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost); err != nil {
return nil, err return nil, err
} }
@ -57,13 +59,13 @@ func (e Exercice) GetFlagKeys() ([]FlagKey, error) {
// GetFlagKey returns a list of flags comming with the challenge. // GetFlagKey returns a list of flags comming with the challenge.
func GetFlagKey(id int64) (k FlagKey, err error) { func GetFlagKey(id int64) (k FlagKey, err error) {
err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_flag = ?", id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost) err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, multiline, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE id_flag = ?", id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.Multiline, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost)
return return
} }
// GetFlagKeyByLabel returns a flag matching the given label. // GetFlagKeyByLabel returns a flag matching the given label.
func (e Exercice) GetFlagKeyByLabel(label string) (k FlagKey, err error) { func (e Exercice) GetFlagKeyByLabel(label string) (k FlagKey, err error) {
err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE type LIKE ? AND id_exercice = ?", label, e.Id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost) err = DBQueryRow("SELECT id_flag, id_exercice, type, help, ignorecase, multiline, validator_regexp, cksum, choices_cost FROM exercice_flags WHERE type LIKE ? AND id_exercice = ?", label, e.Id).Scan(&k.Id, &k.IdExercice, &k.Label, &k.Help, &k.IgnoreCase, &k.Multiline, &k.ValidatorRegexp, &k.Checksum, &k.ChoicesCost)
return return
} }
@ -103,7 +105,7 @@ 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. // 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, help string, ignorecase bool, validator_regexp *string, raw_value []byte, choicescost int64) (f FlagKey, err error) { func (e Exercice) AddRawFlagKey(name string, help 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) hash, errr := ComputeHashedFlag(raw_value, ignorecase, validator_regexp)
if errr != nil { if errr != nil {
return f, err return f, err
@ -113,6 +115,7 @@ func (e Exercice) AddRawFlagKey(name string, help string, ignorecase bool, valid
Label: name, Label: name,
Help: help, Help: help,
IgnoreCase: ignorecase, IgnoreCase: ignorecase,
Multiline: multiline,
ValidatorRegexp: validator_regexp, ValidatorRegexp: validator_regexp,
Checksum: hash[:], Checksum: hash[:],
ChoicesCost: choicescost, ChoicesCost: choicescost,
@ -145,7 +148,7 @@ func (k FlagKey) Create(e Exercice) (Flag, error) {
} }
} }
if res, err := DBExec("INSERT INTO exercice_flags (id_exercice, type, help, ignorecase, validator_regexp, cksum, choices_cost) VALUES (?, ?, ?, ?, ?, ?, ?)", e.Id, k.Label, k.Help, k.IgnoreCase, k.ValidatorRegexp, k.Checksum, k.ChoicesCost); err != nil { if res, err := DBExec("INSERT INTO exercice_flags (id_exercice, type, help, ignorecase, multiline, validator_regexp, cksum, choices_cost) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", e.Id, k.Label, k.Help, k.IgnoreCase, k.Multiline, k.ValidatorRegexp, k.Checksum, k.ChoicesCost); err != nil {
return k, err return k, err
} else if kid, err := res.LastInsertId(); err != nil { } else if kid, err := res.LastInsertId(); err != nil {
return k, err return k, err
@ -170,7 +173,7 @@ func (k FlagKey) Update() (int64, error) {
} }
} }
if res, err := DBExec("UPDATE exercice_flags SET id_exercice = ?, type = ?, help = ?, ignorecase = ?, validator_regexp = ?, cksum = ?, choices_cost = ? WHERE id_flag = ?", k.IdExercice, k.Label, k.Help, k.IgnoreCase, k.ValidatorRegexp, k.Checksum, k.ChoicesCost, k.Id); err != nil { if res, err := DBExec("UPDATE exercice_flags SET id_exercice = ?, type = ?, help = ?, ignorecase = ?, multiline = ?, validator_regexp = ?, cksum = ?, choices_cost = ? WHERE id_flag = ?", k.IdExercice, k.Label, k.Help, k.IgnoreCase, k.Multiline, k.ValidatorRegexp, k.Checksum, k.ChoicesCost, k.Id); err != nil {
return 0, err return 0, err
} else if nb, err := res.RowsAffected(); err != nil { } else if nb, err := res.RowsAffected(); err != nil {
return 0, err return 0, err

View file

@ -33,6 +33,7 @@ type myTeamFlag struct {
NbLines uint64 `json:"nb_lines,omitempty"` NbLines uint64 `json:"nb_lines,omitempty"`
IgnoreOrder bool `json:"ignore_order,omitempty"` IgnoreOrder bool `json:"ignore_order,omitempty"`
IgnoreCase bool `json:"ignore_case,omitempty"` IgnoreCase bool `json:"ignore_case,omitempty"`
Multiline bool `json:"multiline,omitempty"`
ValidatorRe *string `json:"validator_regexp,omitempty"` ValidatorRe *string `json:"validator_regexp,omitempty"`
Solved *time.Time `json:"found,omitempty"` Solved *time.Time `json:"found,omitempty"`
Soluce string `json:"soluce,omitempty"` Soluce string `json:"soluce,omitempty"`
@ -201,6 +202,8 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
flag.Solved = t.HasPartiallySolved(k) flag.Solved = t.HasPartiallySolved(k)
} }
flag.Multiline = k.Multiline
var fl FlagLabel var fl FlagLabel
if fl, err = k.GetMCQJustification(); err == nil { if fl, err = k.GetMCQJustification(); err == nil {
k.Label = fl.Label k.Label = fl.Label