sync: implement hint dependency on flags
This commit is contained in:
parent
6ad11e49d5
commit
f3a34c00db
5 changed files with 176 additions and 117 deletions
|
@ -65,13 +65,14 @@ func init() {
|
||||||
})))
|
})))
|
||||||
router.POST("/api/sync/exercices/:eid/hints", apiHandler(exerciceHandler(
|
router.POST("/api/sync/exercices/:eid/hints", apiHandler(exerciceHandler(
|
||||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||||
_, errs := sync.SyncExerciceHints(sync.GlobalImporter, exercice)
|
_, errs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
|
||||||
return errs, nil
|
return errs, nil
|
||||||
})))
|
})))
|
||||||
router.POST("/api/sync/exercices/:eid/flags", apiHandler(exerciceHandler(
|
router.POST("/api/sync/exercices/:eid/flags", apiHandler(exerciceHandler(
|
||||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||||
_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice)
|
_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice)
|
||||||
return errs, nil
|
_, herrs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
|
||||||
|
return append(errs, herrs...), nil
|
||||||
})))
|
})))
|
||||||
|
|
||||||
router.POST("/api/sync/exercices/:eid/fixurlid", apiHandler(exerciceHandler(
|
router.POST("/api/sync/exercices/:eid/fixurlid", apiHandler(exerciceHandler(
|
||||||
|
|
|
@ -70,13 +70,14 @@ func init() {
|
||||||
})))
|
})))
|
||||||
router.POST("/api/sync/themes/:thid/exercices/:eid/hints", apiHandler(exerciceHandler(
|
router.POST("/api/sync/themes/:thid/exercices/:eid/hints", apiHandler(exerciceHandler(
|
||||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||||
_, errs := sync.SyncExerciceHints(sync.GlobalImporter, exercice)
|
_, errs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
|
||||||
return errs, nil
|
return errs, nil
|
||||||
})))
|
})))
|
||||||
router.POST("/api/sync/themes/:thid/exercices/:eid/keys", apiHandler(exerciceHandler(
|
router.POST("/api/sync/themes/:thid/exercices/:eid/keys", apiHandler(exerciceHandler(
|
||||||
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
||||||
_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice)
|
_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice)
|
||||||
return errs, nil
|
_, herrs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
|
||||||
|
return append(errs, herrs...), nil
|
||||||
})))
|
})))
|
||||||
|
|
||||||
router.POST("/api/sync/themes/:thid/fixurlid", apiHandler(themeHandler(
|
router.POST("/api/sync/themes/:thid/fixurlid", apiHandler(themeHandler(
|
||||||
|
|
|
@ -17,7 +17,13 @@ import (
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildExerciceHints(i Importer, exercice fic.Exercice) (hints []fic.EHint, errs []string) {
|
type importHint struct {
|
||||||
|
Line int
|
||||||
|
Hint fic.EHint
|
||||||
|
FlagsDeps []int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildExerciceHints(i Importer, exercice fic.Exercice) (hints []importHint, errs []string) {
|
||||||
params, _, err := parseExerciceParams(i, exercice.Path)
|
params, _, err := parseExerciceParams(i, exercice.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, fmt.Sprintf("%q: challenge.txt: %s", path.Base(exercice.Path), err))
|
errs = append(errs, fmt.Sprintf("%q: challenge.txt: %s", path.Base(exercice.Path), err))
|
||||||
|
@ -80,31 +86,52 @@ func buildExerciceHints(i Importer, exercice fic.Exercice) (hints []fic.EHint, e
|
||||||
errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): error during markdown formating: %s", path.Base(exercice.Path), hint.Title, n+1, err))
|
errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): error during markdown formating: %s", path.Base(exercice.Path), hint.Title, n+1, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
hints = append(hints, h)
|
newHint := importHint{
|
||||||
|
Line: n + 1,
|
||||||
|
Hint: h,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read dependency to flag
|
||||||
|
for _, nf := range hint.NeedFlag {
|
||||||
|
newHint.FlagsDeps = append(newHint.FlagsDeps, nf.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
hints = append(hints, newHint)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckExerciceHints checks if all hints are corrects..
|
// CheckExerciceHints checks if all hints are corrects..
|
||||||
func CheckExerciceHints(i Importer, exercice fic.Exercice) ([]fic.EHint, []string) {
|
func CheckExerciceHints(i Importer, exercice fic.Exercice) ([]importHint, []string) {
|
||||||
return buildExerciceHints(i, exercice)
|
return buildExerciceHints(i, exercice)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge.
|
// SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge.
|
||||||
func SyncExerciceHints(i Importer, exercice fic.Exercice) (hintsBindings map[int]fic.EHint, errs []string) {
|
func SyncExerciceHints(i Importer, exercice fic.Exercice, flagsBindings map[int64]fic.Flag) (hintsBindings map[int]fic.EHint, errs []string) {
|
||||||
if _, err := exercice.WipeHints(); err != nil {
|
if _, err := exercice.WipeHints(); err != nil {
|
||||||
errs = append(errs, err.Error())
|
errs = append(errs, err.Error())
|
||||||
} else {
|
} else {
|
||||||
hints, berrs := buildExerciceHints(i, exercice)
|
hints, berrs := buildExerciceHints(i, exercice)
|
||||||
errs = append(errs, berrs...)
|
errs = append(errs, berrs...)
|
||||||
|
|
||||||
for n, hint := range hints {
|
hintsBindings = map[int]fic.EHint{}
|
||||||
|
|
||||||
|
for _, hint := range hints {
|
||||||
// Import hint
|
// Import hint
|
||||||
if h, err := exercice.AddHint(hint.Title, hint.Content, hint.Cost); err != nil {
|
if h, err := exercice.AddHint(hint.Hint.Title, hint.Hint.Content, hint.Hint.Cost); err != nil {
|
||||||
errs = append(errs, fmt.Sprintf("%q: hint #%d %s: %s", path.Base(exercice.Path), n+1, hint.Title, err))
|
errs = append(errs, fmt.Sprintf("%q: hint #%d %s: %s", path.Base(exercice.Path), hint.Line, hint.Hint.Title, err))
|
||||||
} else {
|
} else {
|
||||||
hintsBindings[n+1] = h
|
hintsBindings[hint.Line] = h
|
||||||
|
|
||||||
|
// Handle hints dependencies on flags
|
||||||
|
for _, nf := range hint.FlagsDeps {
|
||||||
|
if f, ok := flagsBindings[nf]; ok {
|
||||||
|
h.AddDepend(f)
|
||||||
|
} else {
|
||||||
|
errs = append(errs, fmt.Sprintf("%q: error hint #%d dependency to flag #%d: Unexistant flag", path.Base(exercice.Path), hint.Line, nf))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,6 +174,104 @@ type importFlag struct {
|
||||||
FlagsDeps []int64
|
FlagsDeps []int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
case "":
|
||||||
|
flag.Type = "key"
|
||||||
|
case "key":
|
||||||
|
flag.Type = "key"
|
||||||
|
case "vector":
|
||||||
|
flag.Type = "vector"
|
||||||
|
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', 'ucq' or 'vector'.", path.Base(exercice.Path), nline+1))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag.Type == "key" || flag.Type == "ucq" || flag.Type == "vector" {
|
||||||
|
addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag")
|
||||||
|
if len(berrs) > 0 {
|
||||||
|
errs = append(errs, berrs...)
|
||||||
|
}
|
||||||
|
if addedFlag != nil {
|
||||||
|
ret = append(ret, importFlag{
|
||||||
|
Line: nline + 1,
|
||||||
|
Flag: *addedFlag,
|
||||||
|
Choices: choices,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if flag.Type == "mcq" {
|
||||||
|
addedFlag := fic.MCQ{
|
||||||
|
IdExercice: exercice.Id,
|
||||||
|
Title: flag.Label,
|
||||||
|
Entries: []fic.MCQ_entry{},
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if choice.Raw != nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
val = true
|
||||||
|
isJustified = true
|
||||||
|
} else if p, ok := choice.Value.(bool); ok {
|
||||||
|
val = p
|
||||||
|
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 choice.Value == nil {
|
||||||
|
val = false
|
||||||
|
} else {
|
||||||
|
errs = append(errs, fmt.Sprintf("%q: error in MCQ %d choice %d: incorrect type for value: %T is not boolean.", path.Base(exercice.Path), nline+1, cid, choice.Value))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addedFlag.Entries = append(addedFlag.Entries, fic.MCQ_entry{
|
||||||
|
Label: choice.Label,
|
||||||
|
Response: val,
|
||||||
|
})
|
||||||
|
|
||||||
|
if isJustified && choice.Raw != nil {
|
||||||
|
addedFlag, choices, berrs := buildKeyFlag(exercice, choice.ExerciceFlag, nline+1, "Flag correspondant")
|
||||||
|
if len(berrs) > 0 {
|
||||||
|
errs = append(errs, berrs...)
|
||||||
|
}
|
||||||
|
if addedFlag != nil {
|
||||||
|
ret = append(ret, importFlag{
|
||||||
|
Line: nline + 1,
|
||||||
|
Flag: *addedFlag,
|
||||||
|
Choices: choices,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, importFlag{
|
||||||
|
Line: nline + 1,
|
||||||
|
Flag: addedFlag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// buildExerciceFlags read challenge.txt and extract all flags.
|
// buildExerciceFlags read challenge.txt and extract all flags.
|
||||||
func buildExerciceFlags(i Importer, exercice fic.Exercice) (flags map[int64]importFlag, flagids []int64, errs []string) {
|
func buildExerciceFlags(i Importer, exercice fic.Exercice) (flags map[int64]importFlag, flagids []int64, errs []string) {
|
||||||
params, gerrs := getExerciceParams(i, exercice)
|
params, gerrs := getExerciceParams(i, exercice)
|
||||||
|
@ -195,115 +293,29 @@ func buildExerciceFlags(i Importer, exercice fic.Exercice) (flags map[int64]impo
|
||||||
flag.Id = rand.Int63()
|
flag.Id = rand.Int63()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch strings.ToLower(flag.Type) {
|
newFlags, ferrs := buildExerciceFlag(i, exercice, flag, nline)
|
||||||
case "":
|
if len(ferrs) > 0 {
|
||||||
flag.Type = "key"
|
errs = append(errs, ferrs...)
|
||||||
case "key":
|
|
||||||
flag.Type = "key"
|
|
||||||
case "vector":
|
|
||||||
flag.Type = "vector"
|
|
||||||
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', 'ucq' or 'vector'.", path.Base(exercice.Path), nline+1))
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
if len(newFlags) > 0 {
|
||||||
if flag.Type == "key" || flag.Type == "ucq" || flag.Type == "vector" {
|
for _, newFlag := range newFlags {
|
||||||
addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag")
|
fId := flag.Id
|
||||||
if len(berrs) > 0 {
|
for _, ok := flags[fId]; ok; _, ok = flags[fId] {
|
||||||
errs = append(errs, berrs...)
|
fId = rand.Int63()
|
||||||
}
|
|
||||||
if addedFlag != nil {
|
|
||||||
flags[flag.Id] = importFlag{
|
|
||||||
Line: nline + 1,
|
|
||||||
Flag: *addedFlag,
|
|
||||||
Choices: choices,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if flag.Type == "mcq" {
|
|
||||||
addedFlag := fic.MCQ{
|
|
||||||
IdExercice: exercice.Id,
|
|
||||||
Title: flag.Label,
|
|
||||||
Entries: []fic.MCQ_entry{},
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
if choice.Raw != nil {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
val = true
|
|
||||||
isJustified = true
|
|
||||||
} else if p, ok := choice.Value.(bool); ok {
|
|
||||||
val = p
|
|
||||||
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 choice.Value == nil {
|
|
||||||
val = false
|
|
||||||
} else {
|
|
||||||
errs = append(errs, fmt.Sprintf("%q: error in MCQ %d choice %d: incorrect type for value: %T is not boolean.", path.Base(exercice.Path), nline+1, cid, choice.Value))
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addedFlag.Entries = append(addedFlag.Entries, fic.MCQ_entry{
|
// Read dependency to flag
|
||||||
Label: choice.Label,
|
for _, nf := range flag.NeedFlag {
|
||||||
Response: val,
|
newFlag.FlagsDeps = append(newFlag.FlagsDeps, nf.Id)
|
||||||
})
|
|
||||||
|
|
||||||
if isJustified && choice.Raw != nil {
|
|
||||||
addedFlag, choices, berrs := buildKeyFlag(exercice, choice.ExerciceFlag, nline+1, "Flag correspondant")
|
|
||||||
if len(berrs) > 0 {
|
|
||||||
errs = append(errs, berrs...)
|
|
||||||
}
|
|
||||||
if addedFlag != nil {
|
|
||||||
flags[flag.Id] = importFlag{
|
|
||||||
Line: nline,
|
|
||||||
Flag: *addedFlag,
|
|
||||||
Choices: choices,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
flags[flag.Id] = importFlag{
|
// Read dependency to file
|
||||||
Line: nline + 1,
|
for _, lf := range flag.LockedFile {
|
||||||
Flag: addedFlag,
|
newFlag.FilesDeps = append(newFlag.FilesDeps, lf.Filename)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
flagids = append(flagids, flag.Id)
|
flags[fId] = newFlag
|
||||||
|
flagids = append(flagids, fId)
|
||||||
// Read dependency to flag
|
|
||||||
for _, nf := range flag.NeedFlag {
|
|
||||||
if v, ok := flags[flag.Id]; ok {
|
|
||||||
v.FlagsDeps = append(v.FlagsDeps, nf.Id)
|
|
||||||
flags[flag.Id] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read dependency to file
|
|
||||||
for _, lf := range flag.LockedFile {
|
|
||||||
if v, ok := flags[flag.Id]; ok {
|
|
||||||
v.FilesDeps = append(v.FilesDeps, lf.Filename)
|
|
||||||
flags[flag.Id] = v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,6 +358,24 @@ func CheckExerciceFlags(i Importer, exercice fic.Exercice, files []fic.EFile) (r
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExerciceFlagsMap builds the flags bindings between challenge.txt and DB.
|
||||||
|
func ExerciceFlagsMap(i Importer, exercice fic.Exercice) (kmap map[int64]fic.Flag) {
|
||||||
|
flags, flagids, _ := buildExerciceFlags(i, exercice)
|
||||||
|
|
||||||
|
kmap = map[int64]fic.Flag{}
|
||||||
|
|
||||||
|
for _, flagid := range flagids {
|
||||||
|
if flag, ok := flags[flagid]; ok {
|
||||||
|
if addedFlag, err := flag.Flag.RecoverId(); err == nil {
|
||||||
|
kmap[flagid] = addedFlag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// SyncExerciceFlags imports all kind of flags for the given challenge.
|
// SyncExerciceFlags imports all kind of flags for the given challenge.
|
||||||
func SyncExerciceFlags(i Importer, exercice fic.Exercice) (kmap map[int64]fic.Flag, errs []string) {
|
func SyncExerciceFlags(i Importer, exercice fic.Exercice) (kmap map[int64]fic.Flag, errs []string) {
|
||||||
if _, err := exercice.WipeFlags(); err != nil {
|
if _, err := exercice.WipeFlags(); err != nil {
|
||||||
|
|
|
@ -55,11 +55,11 @@ func SyncDeep(i Importer) (errs map[string][]string) {
|
||||||
errs[theme.Name] = append(errs[theme.Name], SyncExerciceFiles(i, exercice)...)
|
errs[theme.Name] = append(errs[theme.Name], SyncExerciceFiles(i, exercice)...)
|
||||||
|
|
||||||
DeepSyncProgress += exerciceStep / 3
|
DeepSyncProgress += exerciceStep / 3
|
||||||
_, ferrs := SyncExerciceFlags(i, exercice)
|
flagsBindings, ferrs := SyncExerciceFlags(i, exercice)
|
||||||
errs[theme.Name] = append(errs[theme.Name], ferrs...)
|
errs[theme.Name] = append(errs[theme.Name], ferrs...)
|
||||||
|
|
||||||
DeepSyncProgress += exerciceStep / 3
|
DeepSyncProgress += exerciceStep / 3
|
||||||
_, herrs := SyncExerciceHints(i, exercice)
|
_, herrs := SyncExerciceHints(i, exercice, flagsBindings)
|
||||||
errs[theme.Name] = append(errs[theme.Name], herrs...)
|
errs[theme.Name] = append(errs[theme.Name], herrs...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue