Implement flag arrays
This commit is contained in:
parent
3056a19d09
commit
dbf1985d25
@ -20,7 +20,7 @@ Tous les textes doivent utiliser l'encodage UTF8.
|
|||||||
- `[[flag]]` : drapeau classique à valider pour résoudre le challenge :
|
- `[[flag]]` : drapeau classique à valider pour résoudre le challenge :
|
||||||
* `id = 42` : (facultatif) identifiant du flag au sein de l'exercice, pour définir des dépendances ;
|
* `id = 42` : (facultatif) identifiant du flag au sein de l'exercice, pour définir des dépendances ;
|
||||||
* `label = "Intitulé"` : (facultatif, par défaut : `Flag`) intitulé du drapeau ;
|
* `label = "Intitulé"` : (facultatif, par défaut : `Flag`) intitulé du drapeau ;
|
||||||
* `raw = 'MieH2athxuPhai6u'` : drapeau exact à trouver ;
|
* `raw = 'MieH2athxuPhai6u'` ou `raw = ['part1', 'part2']` : drapeau exact à trouver ; sous forme de tableau, le participant n'aura pas connaissaance du nombre d'éléments ;
|
||||||
* `validator_regexp = "^(?:sudo +)?(.*)$"` : (facultatif) expression rationnelle dont les groupes capturés serviront comme chaîne à valider (notez que `?:` au début d'un groupe ne le capturera pas) ;
|
* `validator_regexp = "^(?:sudo +)?(.*)$"` : (facultatif) expression rationnelle dont les groupes capturés serviront comme chaîne à valider (notez que `?:` au début d'un groupe ne le capturera pas) ;
|
||||||
* `ignorecase = true` : (facultatif, par défaut : `false`) ignore la case de ce drapeau ;
|
* `ignorecase = true` : (facultatif, par défaut : `false`) ignore la case de ce drapeau ;
|
||||||
* `help = "Indication"` : (facultatif) chaîne de caractères placée sous le champ du formulaire, idéale pour donner une indication de format ;
|
* `help = "Indication"` : (facultatif) chaîne de caractères placée sous le champ du formulaire, idéale pour donner une indication de format ;
|
||||||
|
@ -29,7 +29,8 @@ type ExerciceUnlockFile struct {
|
|||||||
type ExerciceFlag struct {
|
type ExerciceFlag struct {
|
||||||
Id int64
|
Id int64
|
||||||
Label string `toml:",omitempty"`
|
Label string `toml:",omitempty"`
|
||||||
Raw string
|
Raw interface{}
|
||||||
|
Separator string `toml:",omitempty"`
|
||||||
IgnoreCase bool `toml:",omitempty"`
|
IgnoreCase bool `toml:",omitempty"`
|
||||||
ValidatorRe string `toml:"validator_regexp,omitempty"`
|
ValidatorRe string `toml:"validator_regexp,omitempty"`
|
||||||
Help string `toml:",omitempty"`
|
Help string `toml:",omitempty"`
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
@ -43,16 +44,61 @@ func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) {
|
|||||||
kmap := map[int64]fic.Flag{}
|
kmap := map[int64]fic.Flag{}
|
||||||
|
|
||||||
// Import normal flags
|
// Import normal flags
|
||||||
|
flags:
|
||||||
for nline, flag := range params.Flags {
|
for nline, flag := range params.Flags {
|
||||||
if len(flag.Label) == 0 {
|
if len(flag.Label) == 0 {
|
||||||
flag.Label = "Flag"
|
flag.Label = "Flag"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isFullGraphic(flag.Raw) {
|
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))
|
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(flag.Raw), 0); err != nil {
|
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))
|
errs = append(errs, fmt.Sprintf("%q: error flag #%d: %s", path.Base(exercice.Path), nline + 1, err))
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
|
@ -171,6 +171,10 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||||||
angular.forEach(exercice.flags, function(flag, fid) {
|
angular.forEach(exercice.flags, function(flag, fid) {
|
||||||
if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].flags[fid] && $scope.my.exercices[eid].flags[fid].value !== undefined)
|
if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].flags[fid] && $scope.my.exercices[eid].flags[fid].value !== undefined)
|
||||||
data.exercices[eid].flags[fid].value = $scope.my.exercices[eid].flags[fid].value;
|
data.exercices[eid].flags[fid].value = $scope.my.exercices[eid].flags[fid].value;
|
||||||
|
if ($scope.my && $scope.my.exercices[eid] && $scope.my.exercices[eid].flags[fid] && $scope.my.exercices[eid].flags[fid].values !== undefined)
|
||||||
|
data.exercices[eid].flags[fid].values = $scope.my.exercices[eid].flags[fid].values;
|
||||||
|
else
|
||||||
|
data.exercices[eid].flags[fid].values = [""];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
angular.forEach(data.exercices, function(exercice, eid) {
|
angular.forEach(data.exercices, function(exercice, eid) {
|
||||||
@ -270,6 +274,10 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||||||
var cbs;
|
var cbs;
|
||||||
var cbd;
|
var cbd;
|
||||||
|
|
||||||
|
$scope.additem = function(key) {
|
||||||
|
key.values.push("");
|
||||||
|
};
|
||||||
|
|
||||||
$scope.ssubmit = function() {
|
$scope.ssubmit = function() {
|
||||||
var resp = {}
|
var resp = {}
|
||||||
var check = undefined
|
var check = undefined
|
||||||
@ -279,6 +287,22 @@ angular.module("FICApp", ["ngRoute", "ngSanitize"])
|
|||||||
{
|
{
|
||||||
resp["flags"] = {};
|
resp["flags"] = {};
|
||||||
angular.forEach($scope.my.exercices[$rootScope.current_exercice].flags, function(flag,kid) {
|
angular.forEach($scope.my.exercices[$rootScope.current_exercice].flags, function(flag,kid) {
|
||||||
|
if (flag.values !== undefined) {
|
||||||
|
if (flag.separator) {
|
||||||
|
for (var i = flag.values.length - 1; i >= 0; i--) {
|
||||||
|
if (!flag.values[i].length)
|
||||||
|
flag.values.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.value = flag.values.join(flag.separator) + flag.separator;
|
||||||
|
|
||||||
|
if (flag.values.length == 0)
|
||||||
|
flag.values = [""];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
flag.value = flag.values[0];
|
||||||
|
}
|
||||||
|
|
||||||
if (flag.found == null) {
|
if (flag.found == null) {
|
||||||
if (flag.soluce !== undefined) {
|
if (flag.soluce !== undefined) {
|
||||||
if (check === undefined) check = true;
|
if (check === undefined) check = true;
|
||||||
|
@ -88,15 +88,20 @@
|
|||||||
<div class="form-group" ng-repeat="(kid,key) in my.exercices[current_exercice].flags">
|
<div class="form-group" ng-repeat="(kid,key) in my.exercices[current_exercice].flags">
|
||||||
<label for="sol_{{ kid }}">{{ key.label }} :</label>
|
<label for="sol_{{ kid }}">{{ key.label }} :</label>
|
||||||
<span ng-if="key.found && key.value" ng-bind="key.value"></span>
|
<span ng-if="key.found && key.value" ng-bind="key.value"></span>
|
||||||
<div class="input-group">
|
<div class="input-group" ng-repeat="v in key.values track by $index" ng-class="{'mt-1': !$first}" ng-if="!key.found">
|
||||||
<input type="text" class="form-control" id="sol_{{ kid }}" autocomplete="off" name="sol_{{ kid }}" ng-model="key.value" ng-if="!key.found && !key.choices">
|
<input type="text" class="form-control" id="sol_{{ kid }}_{{ $index }}" autocomplete="off" name="sol_{{ kid }}" ng-model="key.values[$index]" ng-if="!key.choices">
|
||||||
<select class="custom-select" id="sol_{{ kid }}" name="sol_{{ kid }}" ng-model="key.value" ng-if="!key.found && key.choices" ng-options="l as v for (l, v) in key.choices"></select>
|
<select class="custom-select" id="sol_{{ kid }}" name="sol_{{ kid }}" ng-model="key.values[$index]" ng-if="key.choices" ng-options="l as v for (l, v) in key.choices"></select>
|
||||||
<div class="input-group-append" ng-if="key.choices_cost">
|
<div class="input-group-append" ng-if="key.choices_cost">
|
||||||
<button class="btn btn-success" type="button" ng-click="wantchoices(kid)" ng-class="{disabled: key.wcsubmitted}" title="Cliquez pour échanger ce champ de texte par une liste de choix. L'opération vous coûtera {{ key.choices_cost }} points.">
|
<button class="btn btn-success" type="button" ng-click="wantchoices(kid)" ng-class="{disabled: key.wcsubmitted}" title="Cliquez pour échanger ce champ de texte par une liste de choix. L'opération vous coûtera {{ key.choices_cost }} points.">
|
||||||
<span class="glyphicon glyphicon-tasks" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-tasks" aria-hidden="true"></span>
|
||||||
Liste de propositions ({{ key.choices_cost }} points)
|
Liste de propositions ({{ key.choices_cost }} points)
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="input-group-append" ng-if="key.separator && $last">
|
||||||
|
<button class="btn btn-success" type="button" ng-click="additem(key)" title="Ajouter un élément.">
|
||||||
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small class="form-text text-muted" ng-if="!key.found && key.help.length > 0" ng-bind="key.help"></small>
|
<small class="form-text text-muted" ng-if="!key.found && key.help.length > 0" ng-bind="key.help"></small>
|
||||||
<span class="glyphicon glyphicon-ok form-control-feedback text-success" aria-hidden="true" ng-if="key.found" title="Flag trouvé à {{ key.found | date:'mediumTime'}}"></span>
|
<span class="glyphicon glyphicon-ok form-control-feedback text-success" aria-hidden="true" ng-if="key.found" title="Flag trouvé à {{ key.found | date:'mediumTime'}}"></span>
|
||||||
|
@ -39,6 +39,7 @@ type myTeamMCQ struct {
|
|||||||
type myTeamFlag struct {
|
type myTeamFlag struct {
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Help string `json:"help,omitempty"`
|
Help string `json:"help,omitempty"`
|
||||||
|
Separator string `json:"separator,omitempty"`
|
||||||
Solved *time.Time `json:"found,omitempty"`
|
Solved *time.Time `json:"found,omitempty"`
|
||||||
Soluce string `json:"soluce,omitempty"`
|
Soluce string `json:"soluce,omitempty"`
|
||||||
Choices map[string]string `json:"choices,omitempty"`
|
Choices map[string]string `json:"choices,omitempty"`
|
||||||
@ -194,7 +195,12 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) {
|
|||||||
flag.Solved = t.HasPartiallySolved(k)
|
flag.Solved = t.HasPartiallySolved(k)
|
||||||
}
|
}
|
||||||
|
|
||||||
flag.Label = k.Label
|
if k.Label[0] == '`' && len(k.Label) > 2 {
|
||||||
|
flag.Label = k.Label[2:]
|
||||||
|
flag.Separator = string(k.Label[1])
|
||||||
|
} else {
|
||||||
|
flag.Label = k.Label
|
||||||
|
}
|
||||||
|
|
||||||
if flag.Solved == nil {
|
if flag.Solved == nil {
|
||||||
flag.Help = k.Help
|
flag.Help = k.Help
|
||||||
|
Loading…
Reference in New Issue
Block a user