2017-12-09 00:21:58 +00:00
package sync
import (
2019-09-06 23:25:42 +00:00
"errors"
2017-12-09 00:21:58 +00:00
"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
2019-09-06 23:25:42 +00:00
"github.com/julienschmidt/httprouter"
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
}
2019-07-05 20:28:56 +00:00
func buildKeyFlag ( exercice fic . Exercice , flag ExerciceFlag , flagline int , defaultLabel string ) ( f * fic . Flag , choices [ ] fic . FlagChoice , errs [ ] string ) {
if len ( flag . Label ) == 0 {
flag . Label = defaultLabel
}
2018-12-02 18:21:07 +00:00
2019-07-05 20:28:56 +00:00
if flag . Label [ 0 ] == '`' {
errs = append ( errs , fmt . Sprintf ( "%q: flag #%d: Label should not begin with `." , path . Base ( exercice . Path ) , flagline ) )
flag . Label = flag . Label [ 1 : ]
}
raw , prep , terrs := getRawKey ( flag . Raw , flag . ValidatorRe , flag . Ordered )
if len ( terrs ) > 0 {
for _ , err := range terrs {
errs = append ( errs , fmt . Sprintf ( "%q: flag #%d: %s" , path . Base ( exercice . Path ) , flagline , err ) )
2019-01-16 04:25:20 +00:00
}
2019-07-05 20:28:56 +00:00
f = nil
return
}
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 ) , flagline ) )
}
2019-01-16 04:25:20 +00:00
2019-10-12 11:37:24 +00:00
hashedFlag , err := fic . ComputeHashedFlag ( [ ] byte ( raw ) , ! flag . CaseSensitive , validatorRegexp ( flag . ValidatorRe ) )
if err != nil {
errs = append ( errs , err . Error ( ) )
return
}
2019-07-05 20:28:56 +00:00
fl := fic . Flag ( fic . FlagKey {
IdExercice : exercice . Id ,
Label : flag . Label ,
Help : flag . Help ,
2019-09-06 21:03:08 +00:00
IgnoreCase : ! flag . CaseSensitive ,
2019-07-05 20:28:56 +00:00
ValidatorRegexp : validatorRegexp ( flag . ValidatorRe ) ,
Checksum : hashedFlag [ : ] ,
ChoicesCost : flag . ChoicesCost ,
} )
f = & fl
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 ]
2019-01-16 04:25:20 +00:00
} )
}
2019-07-05 20:28:56 +00:00
for _ , choice := range flag . Choice {
val , prep , terrs := getRawKey ( choice . Value , "" , false )
if len ( terrs ) > 0 {
for _ , err := range terrs {
errs = append ( errs , fmt . Sprintf ( "%q: flag #%d: %s" , path . Base ( exercice . Path ) , flagline , err ) )
}
continue
2017-12-09 00:21:58 +00:00
}
2019-07-05 20:28:56 +00:00
if len ( choice . Label ) == 0 {
choice . Label = val
2018-12-04 17:34:38 +00:00
}
2019-07-05 20:28:56 +00:00
choice . Label = prep + choice . Label
2018-12-04 17:34:38 +00:00
2019-07-05 20:28:56 +00:00
choices = append ( choices , fic . FlagChoice {
Label : choice . Label ,
Value : val ,
} )
if val == raw {
hasOne = true
2018-12-04 17:34:38 +00:00
}
2019-07-05 20:28:56 +00:00
}
if ! hasOne {
errs = append ( errs , fmt . Sprintf ( "%q: error in flag #%d: no valid answer defined." , path . Base ( exercice . Path ) , flagline ) )
}
}
return
}
2018-12-04 17:34:38 +00:00
2019-07-05 20:28:56 +00:00
type importFlag struct {
Line int
Flag fic . Flag
Choices [ ] fic . FlagChoice
FilesDeps [ ] string
FlagsDeps [ ] int64
}
2017-12-09 00:21:58 +00:00
2019-07-05 20:28:56 +00:00
// buildExerciceFlags read challenge.txt and extract all flags.
2019-10-12 10:54:01 +00:00
func buildExerciceFlags ( i Importer , exercice fic . Exercice ) ( flags map [ int64 ] importFlag , flagids [ ] int64 , errs [ ] string ) {
2019-07-05 20:28:56 +00:00
params , gerrs := getExerciceParams ( i , exercice )
if len ( gerrs ) > 0 {
2019-10-12 10:54:01 +00:00
return flags , flagids , gerrs
2019-07-05 20:28:56 +00:00
}
2018-12-02 18:21:07 +00:00
2019-07-05 20:28:56 +00:00
flags = map [ int64 ] importFlag { }
2018-12-02 18:21:07 +00:00
2019-07-05 20:28:56 +00:00
for nline , flag := range params . Flags {
if flag . Id == 0 {
// TODO: should be more smart than that. Perhaps search to increment if possible.
flag . Id = rand . Int63 ( )
}
2017-12-16 02:39:57 +00:00
2019-07-05 20:28:56 +00:00
// Ensure flag ID is unique
for _ , ok := flags [ flag . Id ] ; ok ; _ , ok = flags [ flag . Id ] {
errs = append ( errs , fmt . Sprintf ( "%q: flag #%d: identifier already used (%d), using a random one." , path . Base ( exercice . Path ) , nline + 1 , flag . Id ) )
flag . Id = rand . Int63 ( )
}
2018-12-02 18:21:07 +00:00
2019-07-05 20:28:56 +00:00
switch strings . ToLower ( flag . Type ) {
case "" :
flag . Type = "key"
case "key" :
flag . Type = "key"
2019-09-06 21:08:59 +00:00
case "vector" :
flag . Type = "vector"
2019-07-05 20:28:56 +00:00
case "ucq" :
flag . Type = "ucq"
case "mcq" :
flag . Type = "mcq"
default :
2019-09-06 21:08:59 +00:00
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 ) )
2019-07-05 20:28:56 +00:00
continue
}
2019-09-06 21:08:59 +00:00
if flag . Type == "key" || flag . Type == "ucq" || flag . Type == "vector" {
2019-07-05 20:28:56 +00:00
addedFlag , choices , berrs := buildKeyFlag ( exercice , flag , nline + 1 , "Flag" )
if len ( berrs ) > 0 {
errs = append ( errs , berrs ... )
}
if addedFlag != nil {
flags [ flag . Id ] = importFlag {
Line : nline + 1 ,
Flag : * addedFlag ,
Choices : choices ,
2018-12-02 18:21:07 +00:00
}
2019-07-05 20:28:56 +00:00
}
} 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
2019-09-06 20:58:30 +00:00
if choice . Raw != nil {
2019-07-05 20:28:56 +00:00
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
2018-09-07 18:53:08 +00:00
}
2018-12-02 04:05:59 +00:00
2019-07-05 20:28:56 +00:00
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
}
2019-01-16 04:25:20 +00:00
2019-07-05 20:28:56 +00:00
addedFlag . Entries = append ( addedFlag . Entries , fic . MCQ_entry {
Label : choice . Label ,
Response : val ,
} )
2018-11-21 03:10:22 +00:00
2019-09-06 20:58:30 +00:00
if isJustified && choice . Raw != nil {
addedFlag , choices , berrs := buildKeyFlag ( exercice , choice . ExerciceFlag , nline + 1 , "Flag correspondant" )
2019-07-05 20:28:56 +00:00
if len ( berrs ) > 0 {
errs = append ( errs , berrs ... )
2018-11-21 03:10:22 +00:00
}
2019-07-05 20:28:56 +00:00
if addedFlag != nil {
flags [ flag . Id ] = importFlag {
Line : nline ,
Flag : * addedFlag ,
Choices : choices ,
}
2018-11-21 03:10:22 +00:00
}
}
2017-12-16 02:39:57 +00:00
}
2019-09-06 21:23:50 +00:00
flags [ flag . Id ] = importFlag {
Line : nline + 1 ,
Flag : addedFlag ,
}
2019-07-05 20:28:56 +00:00
}
2017-12-16 02:39:57 +00:00
2019-10-12 10:54:01 +00:00
flagids = append ( flagids , flag . Id )
2019-07-05 20:28:56 +00:00
// 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
2019-01-16 04:25:20 +00:00
}
2019-07-05 20:28:56 +00:00
}
2018-12-02 04:05:59 +00:00
2019-07-05 20:28:56 +00:00
// 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
}
}
}
return
}
// CheckExerciceFlags checks if all flags for the given challenge are correct.
func CheckExerciceFlags ( i Importer , exercice fic . Exercice , files [ ] fic . EFile ) ( rf [ ] fic . Flag , errs [ ] string ) {
2019-10-12 10:54:01 +00:00
flags , flagsids , berrs := buildExerciceFlags ( i , exercice )
2019-07-05 20:28:56 +00:00
errs = append ( errs , berrs ... )
2019-10-12 10:54:01 +00:00
for _ , flagid := range flagsids {
if flag , ok := flags [ flagid ] ; ok {
// Check dependency to flag
for _ , nf := range flag . FlagsDeps {
if _ , ok := flags [ nf ] ; ! ok {
errs = append ( errs , fmt . Sprintf ( "%q: error flag #%d dependency to flag id=%d: id not defined" , path . Base ( exercice . Path ) , flag . Line , nf ) )
}
2019-07-05 20:28:56 +00:00
}
2019-10-12 10:54:01 +00:00
// Check dependency to file
for _ , lf := range flag . FilesDeps {
found := false
for _ , f := range files {
if f . Name == lf {
found = true
break
}
}
if ! found {
errs = append ( errs , fmt . Sprintf ( "%q: error flag #%d dependency to %s: No such file" , path . Base ( exercice . Path ) , flag . Line , lf ) )
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-10-12 10:54:01 +00:00
rf = append ( rf , flag . Flag )
}
2019-07-05 20:28:56 +00:00
}
return
}
// SyncExerciceFlags imports all kind of flags for the given challenge.
func SyncExerciceFlags ( i Importer , exercice fic . Exercice ) ( errs [ ] string ) {
if _ , err := exercice . WipeFlags ( ) ; err != nil {
errs = append ( errs , err . Error ( ) )
} else if _ , err := exercice . WipeMCQs ( ) ; err != nil {
errs = append ( errs , err . Error ( ) )
} else {
2019-10-12 10:54:01 +00:00
flags , flagids , berrs := buildExerciceFlags ( i , exercice )
2019-07-05 20:28:56 +00:00
errs = append ( errs , berrs ... )
kmap := map [ int64 ] fic . Flag { }
// Import flags
2019-10-12 10:54:01 +00:00
for _ , flagid := range flagids {
if flag , ok := flags [ flagid ] ; ok {
if addedFlag , err := exercice . AddFlag ( flag . Flag ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: error flag #%d: %s" , path . Base ( exercice . Path ) , flag . Line , err ) )
} else {
if f , ok := addedFlag . ( fic . FlagKey ) ; ok {
for _ , choice := range flag . Choices {
if _ , err := f . AddChoice ( choice ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: error in flag #%d choice #FIXME: %s" , path . Base ( exercice . Path ) , flag . Line , err ) )
}
2019-07-05 20:28:56 +00:00
}
}
2019-10-12 10:54:01 +00:00
kmap [ flagid ] = addedFlag
2019-07-05 20:28:56 +00:00
2019-10-12 10:54:01 +00:00
// Import dependency to flag
for _ , nf := range flag . FlagsDeps {
if rf , ok := kmap [ nf ] ; ! 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 ) , flag . Line , nf ) )
} 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 ) , flag . Line , nf , err ) )
}
2019-07-05 20:28:56 +00:00
}
2019-10-12 10:54:01 +00:00
// Import dependency to file
for _ , lf := range flag . FilesDeps {
if rf , err := exercice . GetFileByFilename ( lf ) ; err != nil {
errs = append ( errs , fmt . Sprintf ( "%q: error flag #%d dependency to %s: %s" , path . Base ( exercice . Path ) , flag . Line , lf , err ) )
} 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 ) , flag . Line , lf , err ) )
}
2019-07-05 20:28:56 +00:00
}
2017-12-16 02:39:57 +00:00
}
}
}
}
2019-07-05 20:28:56 +00:00
2018-05-12 00:01:49 +00:00
return
2017-12-16 02:39:57 +00:00
}
2019-09-06 23:25:42 +00:00
// ApiListRemoteExerciceFlags is an accessor letting foreign packages to access remote exercice flags.
func ApiGetRemoteExerciceFlags ( ps httprouter . Params , _ [ ] byte ) ( interface { } , error ) {
theme , errs := BuildTheme ( GlobalImporter , ps . ByName ( "thid" ) )
if theme != nil {
exercice , _ , _ , _ , errs := BuildExercice ( GlobalImporter , * theme , path . Join ( theme . Path , ps . ByName ( "exid" ) ) , nil )
if exercice != nil {
flags , errs := CheckExerciceFlags ( GlobalImporter , * exercice , [ ] fic . EFile { } )
if flags != nil {
return flags , nil
} else {
return flags , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
} else {
return exercice , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
} else {
return nil , errors . New ( fmt . Sprintf ( "%q" , errs ) )
}
}