sync: Refactor exercice flags

This commit is contained in:
nemunaire 2019-01-16 05:25:20 +01:00
parent 1e183f60ee
commit ff3dec059c
2 changed files with 250 additions and 244 deletions

View File

@ -25,54 +25,23 @@ type ExerciceUnlockFile struct {
Filename string `toml:",omitempty"`
}
// ExerciceFlag holds informations about a "classic" flag.
// ExerciceFlag holds informations about one flag.
type ExerciceFlag struct {
Id int64
Label string `toml:",omitempty"`
Type string `toml:",omitempty"`
Raw interface{}
Value interface{} `toml:",omitempty"`
Separator string `toml:",omitempty"`
Ordered bool `toml:",omitempty"`
IgnoreCase bool `toml:",omitempty"`
ValidatorRe string `toml:"validator_regexp,omitempty"`
Help string `toml:",omitempty"`
ChoicesCost int64 `toml:"choices_cost,omitempty"`
Choice []ExerciceFlag
LockedFile []ExerciceUnlockFile `toml:"unlock_file,omitempty"`
NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"`
}
// ExerciceFlagMCQChoice holds a choice for an MCQ flag.
type ExerciceFlagMCQChoice struct {
Label string
Value interface{} `toml:",omitempty"`
Help string `toml:",omitempty"`
}
// ExerciceFlagMCQ holds information about a MCQ flag.
type ExerciceFlagMCQ struct {
Label string `toml:",omitempty"`
Choice []ExerciceFlagMCQChoice
NoShuffle bool
}
// ExerciceFlagUCQChoice holds a choice for an UCQ flag.
type ExerciceFlagUCQChoice struct {
Label string `toml:",omitempty"`
Value string
}
// ExerciceFlagUCQ holds information about a UCQ flag.
type ExerciceFlagUCQ struct {
Id int64
Label string `toml:",omitempty"`
Raw string
IgnoreCase bool `toml:",omitempty"`
ValidatorRe string `toml:"validator_regexp,omitempty"`
Help string `toml:",omitempty"`
DisplayAs string `toml:",omitempty"`
ChoicesCost int64 `toml:"choices_cost,omitempty"`
Choice []ExerciceFlagUCQChoice
LockedFile []ExerciceUnlockFile `toml:"unlock_file,omitempty"`
NeedFlag []ExerciceDependency `toml:"need_flag,omitempty"`
NoShuffle bool
NoShuffle bool
}
// ExerciceParams contains values parsed from defines.txt.
@ -82,8 +51,8 @@ type ExerciceParams struct {
Hints []ExerciceHintParams `toml:"hint"`
Dependencies []ExerciceDependency `toml:"depend"`
Flags []ExerciceFlag `toml:"flag"`
FlagsMCQ []ExerciceFlagMCQ `toml:"flag_mcq"`
FlagsUCQ []ExerciceFlagUCQ `toml:"flag_ucq"`
FlagsMCQ []ExerciceFlag `toml:"flag_mcq"`
FlagsUCQ []ExerciceFlag `toml:"flag_ucq"`
}
// parseExerciceParams reads challenge definitions from defines.txt and extract usefull data to set up the challenge.

View File

@ -31,6 +31,57 @@ func validatorRegexp(vre string) (validator_regexp *string) {
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 {
@ -44,8 +95,36 @@ func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) {
} 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
flags:
for nline, flag := range params.Flags {
if len(flag.Label) == 0 {
flag.Label = "Flag"
@ -56,223 +135,181 @@ func SyncExerciceFlags(i Importer, exercice fic.Exercice) (errs []string) {
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 = ""
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 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))
}
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
var fitems []string
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
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 {
fitems = append(fitems, g)
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
}
} 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
}
}
ignord := "t"
if flag.Ordered {
sort.Strings(fitems)
ignord = "f"
}
raw = strings.Join(fitems, flag.Separator) + flag.Separator
flag.Label = "`" + flag.Separator + ignord + 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))
}
if k, err := exercice.AddRawFlagKey(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))
continue
} else {
if flag.Id != 0 {
kmap[flag.Id] = k
}
// 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 := k.AddDepend(rf); err != nil {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %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(k); 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
}
}
}
}
// Import UCQ flags
for nline, flag := range params.FlagsUCQ {
if len(flag.Label) == 0 {
flag.Label = "Flag"
}
if !isFullGraphic(flag.Raw) {
errs = append(errs, fmt.Sprintf("%q: WARNING flag UCQ #%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(flag.Raw), flag.ChoicesCost); err != nil {
errs = append(errs, fmt.Sprintf("%q: error flag UCQ #%d: %s", path.Base(exercice.Path), nline + 1, err))
continue
} else {
// Import dependency to file
if flag.Id != 0 {
kmap[flag.Id] = k
}
// 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=%s: id not defined, perhaps not available at time of processing", path.Base(exercice.Path), nline + 1, nf.Id))
continue
} else if err := k.AddDepend(rf); err != nil {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %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 UCQ #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err))
continue
} else if err := rf.AddDepend(k); err != nil {
errs = append(errs, fmt.Sprintf("%q: error flag UCQ #%d dependency to %s: %s", path.Base(exercice.Path), nline + 1, lf.Filename, err))
continue
}
}
// 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 {
if len(choice.Label) == 0 {
choice.Label = choice.Value
}
if _, err := k.AddChoice(choice.Label, choice.Value); 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 choice.Value == flag.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))
}
}
}
// Import MCQ flags
for nline, quest := range params.FlagsMCQ {
if flag, err := exercice.AddMCQ(quest.Label); err != nil {
errs = append(errs, fmt.Sprintf("%q: error flag MCQ #%d: %s", path.Base(exercice.Path), nline + 1, err))
continue
} else {
hasOne := false
isJustified := false
if !quest.NoShuffle {
rand.Shuffle(len(quest.Choice), func(i, j int) {
quest.Choice[i], quest.Choice[j] = quest.Choice[j], quest.Choice[i]
})
}
for cid, choice := range quest.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))
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 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 p, ok := choice.Value.(string); ok {
val = true
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
}
isJustified = true
justify = p
} else {
errs = append(errs, fmt.Sprintf("%q: error in MCQ %d choice %d: has an invalid type. Expected true, false or a string", path.Base(exercice.Path), nline + 1, cid))
continue
}
if e, err := flag.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 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 !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
}
}
}