2017-12-09 00:21:58 +00:00
package sync
import (
"fmt"
2018-12-02 04:05:59 +00:00
"math/rand"
2017-12-09 00:21:58 +00:00
"path"
2018-12-04 18:09:58 +00:00
"sort"
2018-12-04 17:34:38 +00:00
"strings"
2017-12-17 15:10:59 +00:00
"unicode"
2017-12-09 00:21:58 +00:00
"srs.epita.fr/fic-server/libfic"
)
2018-03-09 18:07:08 +00:00
// isFullGraphic detects if some rune are not graphic one.
// This function is usefull to display warning when importing key ending with \r.
2017-12-17 15:10:59 +00:00
func isFullGraphic ( s string ) bool {
for _ , c := range s {
if ! unicode . IsGraphic ( c ) {
return false
}
}
return true
}
2018-11-16 19:46:19 +00:00
func validatorRegexp ( vre string ) ( validator_regexp * string ) {
if len ( vre ) > 0 {
validator_regexp = & vre
} else {
validator_regexp = nil
}
return
}
2019-01-18 03:24:28 +00:00
func getRawKey ( input interface { } , validatorRe string , ordered bool ) ( raw string , prep string , errs [ ] string ) {
separator := ","
2019-01-16 04:25:20 +00:00
// Concatenate array
2019-01-18 03:24:28 +00:00
if f , ok := input . ( [ ] interface { } ) ; ok {
if len ( validatorRe ) > 0 {
2019-01-16 04:25:20 +00:00
errs = append ( errs , "ValidatorRe cannot be defined for this kind of flag." )
2019-01-18 03:24:28 +00:00
validatorRe = ""
2019-01-16 04:25:20 +00:00
}
2019-01-18 03:24:28 +00:00
if len ( separator ) == 0 {
separator = ","
} else if len ( separator ) > 1 {
separator = string ( separator [ 0 ] )
2019-01-16 04:25:20 +00:00
errs = append ( errs , "separator truncated to %q" )
}
var fitems [ ] string
for _ , v := range f {
if g , ok := v . ( string ) ; ok {
2019-01-18 03:24:28 +00:00
if strings . Index ( g , separator ) != - 1 {
2019-01-16 04:25:20 +00:00
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
}
}
2019-01-17 21:29:09 +00:00
ignord := "f"
2019-01-18 03:24:28 +00:00
if ! ordered {
2019-01-16 04:25:20 +00:00
sort . Strings ( fitems )
2019-01-17 21:29:09 +00:00
ignord = "t"
2019-01-16 04:25:20 +00:00
}
2019-01-18 03:24:28 +00:00
raw = strings . Join ( fitems , separator ) + separator
2019-01-16 04:25:20 +00:00
2019-01-18 03:24:28 +00:00
prep = "`" + separator + ignord
} else if f , ok := input . ( int64 ) ; ok {
2019-01-16 04:25:20 +00:00
raw = fmt . Sprintf ( "%d" , f )
2019-01-18 03:24:28 +00:00
} 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 ) )
2019-01-16 04:25:20 +00:00
return
} else {
raw = f
}
return
}
2018-09-24 08:00:17 +00:00
// 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 {
2017-12-09 00:21:58 +00:00
errs = append ( errs , err . Error ( ) )
2018-05-12 00:01:49 +00:00
} else if _ , err := exercice . WipeMCQs ( ) ; err != nil {
errs = append ( errs , err . Error ( ) )
} else if params , err := parseExerciceParams ( i , exercice . Path ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: challenge.txt: %s" , path . Base ( exercice . Path ) , err ) )
} else if len ( params . Flags ) == 0 && len ( params . FlagsUCQ ) == 0 && len ( params . FlagsMCQ ) == 0 {
errs = append ( errs , fmt . Sprintf ( "%q: has no flag" , path . Base ( exercice . Path ) ) )
2017-12-09 00:21:58 +00:00
} else {
2018-12-02 18:21:07 +00:00
kmap := map [ int64 ] fic . Flag { }
2019-01-16 04:25:20 +00:00
// 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 ,
} )
}
2018-05-12 00:01:49 +00:00
// Import normal flags
for nline , flag := range params . Flags {
if len ( flag . Label ) == 0 {
flag . Label = "Flag"
2017-12-09 00:21:58 +00:00
}
2018-12-04 17:34:38 +00:00
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 : ]
}
2019-01-16 04:25:20 +00:00
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 ) )
2018-12-04 17:34:38 +00:00
continue
}
2019-01-16 04:25:20 +00:00
var addedFlag fic . Flag
2017-12-09 00:21:58 +00:00
2019-01-16 04:25:20 +00:00
if flag . Type == "key" || flag . Type == "ucq" {
2019-01-18 03:24:28 +00:00
raw , prep , terrs := getRawKey ( flag . Raw , flag . ValidatorRe , flag . Ordered )
2018-12-02 18:21:07 +00:00
2019-01-16 04:25:20 +00:00
if len ( terrs ) > 0 {
for _ , err := range terrs {
errs = append ( errs , fmt . Sprintf ( "%q: flag #%d: %s" , path . Base ( exercice . Path ) , nline + 1 , err ) )
2018-12-02 18:21:07 +00:00
}
2019-01-16 04:25:20 +00:00
continue
2018-12-02 18:21:07 +00:00
}
2019-01-16 04:25:20 +00:00
flag . Label = prep + flag . Label
2018-12-02 18:21:07 +00:00
2019-01-16 04:25:20 +00:00
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 ) )
2018-09-07 18:53:08 +00:00
}
2017-12-16 02:39:57 +00:00
2019-01-16 04:25:20 +00:00
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
if ! flag . NoShuffle {
rand . Shuffle ( len ( flag . Choice ) , func ( i , j int ) {
flag . Choice [ i ] , flag . Choice [ j ] = flag . Choice [ j ] , flag . Choice [ i ]
} )
}
2018-12-02 18:21:07 +00:00
2019-01-16 04:25:20 +00:00
for cid , choice := range flag . Choice {
2019-01-18 03:24:28 +00:00
val , prep , terrs := getRawKey ( choice . Value , "" , false )
2019-01-16 04:25:20 +00:00
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
}
if len ( choice . Label ) == 0 {
choice . Label = val
}
2019-01-18 03:24:28 +00:00
choice . Label = prep + choice . Label
2019-01-16 04:25:20 +00:00
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 ) )
}
2018-12-02 18:21:07 +00:00
}
}
2019-01-16 04:25:20 +00:00
} 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 ]
} )
2018-09-07 18:53:08 +00:00
}
2018-12-02 04:05:59 +00:00
2019-01-16 04:25:20 +00:00
for cid , choice := range flag . Choice {
var val bool
var justify string
2019-01-18 03:24:28 +00:00
if choice . Justification != 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 ) )
2019-01-16 04:25:20 +00:00
continue
}
2019-01-18 03:24:28 +00:00
val = true
isJustified = true
} else if p , ok := choice . Value . ( bool ) ; ok {
val = p
2019-01-16 04:25:20 +00:00
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
}
2019-01-18 03:24:28 +00:00
} else if choice . Value == nil {
val = false
2019-01-16 04:25:20 +00:00
} else {
2019-01-18 03:24:28 +00:00
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
}
2019-01-16 04:25:20 +00:00
2019-01-18 03:24:28 +00:00
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 isJustified && choice . Justification != nil {
2019-01-16 04:25:20 +00:00
var prep string
var terrs [ ] string
2019-01-18 03:24:28 +00:00
justify , prep , terrs = getRawKey ( choice . Justification . Raw , choice . Justification . ValidatorRe , choice . Justification . Ordered )
2019-01-16 04:25:20 +00:00
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
}
2018-12-02 04:05:59 +00:00
2019-01-18 03:24:28 +00:00
if len ( choice . Justification . Label ) == 0 {
choice . Justification . Label = "Flag correspondant"
}
choice . Justification . Label = prep + choice . Justification . Label
if _ , err := exercice . AddRawFlagKey ( fmt . Sprintf ( "%%%d%%%s" , e . Id , choice . Justification . Label ) , choice . Justification . Help , choice . Justification . IgnoreCase , validatorRegexp ( choice . Justification . ValidatorRe ) , [ ] byte ( justify ) , flag . ChoicesCost ) ; err != nil {
2019-01-16 04:25:20 +00:00
errs = append ( errs , fmt . Sprintf ( "%q: error MCQ #%d: %s" , path . Base ( exercice . Path ) , nline + 1 , err ) )
continue
}
}
2018-11-21 03:10:22 +00:00
2019-01-16 04:25:20 +00:00
if val {
hasOne = true
}
2018-11-21 03:10:22 +00:00
}
2019-01-16 04:25:20 +00:00
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 ) )
2018-11-21 03:10:22 +00:00
}
}
2017-12-16 02:39:57 +00:00
}
2019-01-16 04:25:20 +00:00
if flag . Id != 0 {
kmap [ flag . Id ] = addedFlag
}
2018-12-02 04:05:59 +00:00
2019-01-16 04:25:20 +00:00
// 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
2018-12-02 04:05:59 +00:00
}
2019-01-16 04:25:20 +00:00
}
2018-12-02 04:05:59 +00:00
2019-01-16 04:25:20 +00:00
// 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
2017-12-16 02:39:57 +00:00
}
}
}
}
2018-05-12 00:01:49 +00:00
return
2017-12-16 02:39:57 +00:00
}