2017-12-09 00:21:58 +00:00
package sync
import (
"fmt"
2024-01-13 15:40:25 +00:00
"math"
2018-12-02 04:05:59 +00:00
"math/rand"
2022-05-16 09:38:46 +00:00
"net/http"
2017-12-09 00:21:58 +00:00
"path"
2018-12-04 18:09:58 +00:00
"sort"
2021-11-12 20:36:27 +00:00
"strconv"
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
2022-05-16 09:38:46 +00:00
"github.com/gin-gonic/gin"
2023-11-22 11:16:53 +00:00
"go.uber.org/multierr"
2019-09-06 23:25:42 +00:00
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
}
2023-11-22 11:16:53 +00:00
func getRawKey ( input interface { } , validatorRe string , ordered bool , showLines bool , separator string ) ( raw string , prep string , errs error ) {
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 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , fmt . Errorf ( "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 ] )
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , fmt . Errorf ( "separator truncated to %q" , separator ) )
2019-01-16 04:25:20 +00:00
}
var fitems [ ] string
2022-07-01 22:01:05 +00:00
for i , v := range f {
2019-01-16 04:25:20 +00:00
if g , ok := v . ( string ) ; ok {
2019-01-18 03:24:28 +00:00
if strings . Index ( g , separator ) != - 1 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , fmt . Errorf ( "flag items cannot contain %q character as it is used as separator. Change the separator attribute for this flag." , separator ) )
2019-01-16 04:25:20 +00:00
return
} else {
fitems = append ( fitems , g )
}
} else {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , fmt . Errorf ( "item %d has an invalid type: can only be string, is %T." , i , g ) )
2019-01-16 04:25:20 +00:00
return
}
}
2019-01-17 21:29:09 +00:00
ignord := "f"
2019-01-18 03:24:28 +00:00
if ! ordered {
2023-11-05 10:23:58 +00:00
// Sort the list without taking the case in count.
sort . Slice ( fitems , func ( i , j int ) bool {
return strings . ToLower ( fitems [ i ] ) < strings . ToLower ( fitems [ j ] )
} )
2019-01-17 21:29:09 +00:00
ignord = "t"
2019-01-16 04:25:20 +00:00
}
2019-11-25 16:45:41 +00:00
nbLines := 0
if showLines {
if len ( fitems ) > 9 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , fmt . Errorf ( "too much items in vector to use ShowLines features, max 9." ) )
2019-11-25 16:45:41 +00:00
} else {
nbLines = len ( fitems )
}
}
2019-01-18 03:24:28 +00:00
raw = strings . Join ( fitems , separator ) + separator
2019-01-16 04:25:20 +00:00
2019-11-25 16:45:41 +00:00
prep = fmt . Sprintf ( "`%s%s%d" , separator , ignord , nbLines )
2019-01-18 03:24:28 +00:00
} else if f , ok := input . ( int64 ) ; ok {
2019-01-16 04:25:20 +00:00
raw = fmt . Sprintf ( "%d" , f )
2021-11-12 20:36:27 +00:00
} else if f , ok := input . ( float64 ) ; ok {
raw = strconv . FormatFloat ( f , 'f' , - 1 , 64 )
2019-01-18 03:24:28 +00:00
} else if f , ok := input . ( string ) ; ! ok {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , fmt . Errorf ( "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
}
2023-11-22 11:16:53 +00:00
func buildLabelFlag ( exercice * fic . Exercice , flag ExerciceFlag , flagline int , exceptions * CheckExceptions ) ( f * fic . FlagLabel , errs error ) {
2022-01-21 12:06:37 +00:00
if len ( flag . Label ) == 0 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , fmt . Errorf ( "Label cannot be empty." ) ) )
2022-01-21 12:06:37 +00:00
return
}
2022-12-07 13:43:44 +00:00
// Call checks hooks
for _ , h := range hooks . mdTextHooks {
2023-11-22 11:16:53 +00:00
for _ , err := range multierr . Errors ( h ( flag . Label , exercice . Language , exceptions . Filter2ndCol ( strconv . Itoa ( flagline ) ) ) ) {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , err ) )
2022-12-07 13:43:44 +00:00
}
}
if mdlabel , err := ProcessMarkdown ( GlobalImporter , flag . Label , exercice . Path ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , fmt . Errorf ( "unable to parse property label as Markdown: %w" , err ) ) )
2022-12-07 13:43:44 +00:00
} else {
if strings . Count ( flag . Label , "\n\n" ) == 0 {
flag . Label = mdlabel [ 3 : len ( mdlabel ) - 4 ]
} else {
flag . Label = mdlabel
}
}
2022-01-21 12:06:37 +00:00
if flag . Raw != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , fmt . Errorf ( "raw cannot be defined." ) ) )
2022-01-21 12:06:37 +00:00
}
if len ( flag . Choice ) != 0 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , fmt . Errorf ( "choices cannot be defined." ) ) )
2022-01-21 12:06:37 +00:00
}
f = & fic . FlagLabel {
Order : int8 ( flagline ) ,
Label : flag . Label ,
Variant : flag . Variant ,
}
2022-07-11 17:57:33 +00:00
// Call checks hooks
for _ , h := range hooks . flagLabelHooks {
2023-11-22 11:16:53 +00:00
for _ , e := range multierr . Errors ( h ( f , exercice , exceptions . Filter2ndCol ( strconv . Itoa ( flagline ) ) ) ) {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , e ) )
2022-07-11 17:57:33 +00:00
}
}
2022-01-21 12:06:37 +00:00
return
}
2023-11-22 11:16:53 +00:00
func buildKeyFlag ( exercice * fic . Exercice , flag ExerciceFlag , flagline int , defaultLabel string , exceptions * CheckExceptions ) ( f * fic . Flag , choices [ ] * fic . FlagChoice , errs error ) {
2019-07-05 20:28:56 +00:00
if len ( flag . Label ) == 0 {
flag . Label = defaultLabel
}
2018-12-02 18:21:07 +00:00
2022-01-21 12:06:37 +00:00
if len ( flag . Variant ) != 0 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , fmt . Errorf ( "variant is not defined for this kind of flag." ) ) )
2022-01-21 12:06:37 +00:00
}
2019-07-05 20:28:56 +00:00
if flag . Label [ 0 ] == '`' {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , fmt . Errorf ( "Label should not begin with `." ) ) )
2022-06-12 20:58:26 +00:00
flag . Label = flag . Label [ 1 : ]
}
2023-05-16 07:59:59 +00:00
raw , prep , terrs := getRawKey ( flag . Raw , flag . CaptureRe , flag . Ordered , flag . ShowLines , flag . Separator )
2019-07-05 20:28:56 +00:00
2023-11-22 11:16:53 +00:00
errors := multierr . Errors ( terrs )
if len ( errors ) > 0 {
for _ , terr := range errors {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , terr ) )
2022-07-11 17:57:33 +00:00
}
2019-07-05 20:28:56 +00:00
f = nil
return
}
flag . Label = prep + flag . Label
2023-11-04 22:09:30 +00:00
if len ( flag . Label ) > 255 && flag . Type != "label" {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , fmt . Errorf ( "label is too long (max 255 chars per label)." ) ) )
2023-07-16 17:00:10 +00:00
}
2020-01-16 14:33:28 +00:00
if ( flag . Type == "text" && ! isFullGraphic ( strings . Replace ( raw , "\n" , "" , - 1 ) ) ) || ( flag . Type != "text" && ! isFullGraphic ( raw ) ) {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , fmt . Errorf ( "WARNING non-printable characters in flag, is this really expected?" ) ) )
2019-07-05 20:28:56 +00:00
}
2019-01-16 04:25:20 +00:00
2023-05-16 07:59:59 +00:00
hashedFlag , err := fic . ComputeHashedFlag ( [ ] byte ( raw ) , ! flag . CaseSensitive , flag . NoTrim , validatorRegexp ( flag . CaptureRe ) , flag . SortReGroups )
2019-10-12 11:37:24 +00:00
if err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , err ) )
2019-10-12 11:37:24 +00:00
return
}
2022-07-11 17:57:33 +00:00
fk := & fic . FlagKey {
2023-05-16 07:59:59 +00:00
Type : flag . Type ,
IdExercice : exercice . Id ,
Order : int8 ( flagline ) ,
Label : flag . Label ,
Placeholder : flag . Placeholder ,
Help : flag . Help ,
Unit : flag . Unit ,
IgnoreCase : ! flag . CaseSensitive ,
Multiline : flag . Type == "text" ,
CaptureRegexp : validatorRegexp ( flag . CaptureRe ) ,
SortReGroups : flag . SortReGroups ,
Checksum : hashedFlag [ : ] ,
ChoicesCost : flag . ChoicesCost ,
BonusGain : flag . BonusGain ,
2022-07-11 17:57:33 +00:00
}
// Call checks hooks
for _ , h := range hooks . flagKeyHooks {
2023-11-22 11:16:53 +00:00
for _ , e := range multierr . Errors ( h ( fk , raw , exercice , exceptions . Filter2ndCol ( strconv . Itoa ( flagline ) ) ) ) {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , e ) )
2022-07-11 17:57:33 +00:00
}
}
fl := fic . Flag ( fk )
2019-07-05 20:28:56 +00:00
f = & fl
2021-11-12 23:58:03 +00:00
if len ( flag . Choice ) > 0 || ( flag . Type == "ucq" || flag . Type == "radio" ) {
2019-07-05 20:28:56 +00:00
// 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 {
2020-12-11 20:03:12 +00:00
val , prep , terrs := getRawKey ( choice . Value , "" , false , false , "" )
2023-11-22 11:16:53 +00:00
errors := multierr . Errors ( terrs )
if len ( errors ) > 0 {
for _ , terr := range errors {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , terr ) )
2022-07-11 17:57:33 +00:00
}
2019-07-05 20:28:56 +00:00
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
2021-11-20 17:03:53 +00:00
if ! flag . CaseSensitive {
val = strings . ToLower ( val )
}
2022-07-11 17:57:33 +00:00
fc := & fic . FlagChoice {
2019-07-05 20:28:56 +00:00
Label : choice . Label ,
Value : val ,
2022-07-11 17:57:33 +00:00
}
// Call checks hooks
for _ , h := range hooks . flagChoiceHooks {
2023-11-22 11:16:53 +00:00
for _ , e := range multierr . Errors ( h ( fc , exercice , exceptions . Filter2ndCol ( strconv . Itoa ( flagline ) ) ) ) {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , e ) )
2022-07-11 17:57:33 +00:00
}
}
choices = append ( choices , fc )
2019-07-05 20:28:56 +00:00
2021-10-29 21:28:10 +00:00
if val == "true" || val == "false" {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , fmt . Errorf ( "value can't be %q, this is not a MCQ, the value has to be meaningful. The value is shown to players as response identifier." , val ) ) )
2021-10-29 21:28:10 +00:00
}
2020-01-28 17:47:36 +00:00
if val == raw || ( ! flag . CaseSensitive && val == strings . ToLower ( raw ) ) {
2019-07-05 20:28:56 +00:00
hasOne = true
2018-12-04 17:34:38 +00:00
}
2019-07-05 20:28:56 +00:00
}
if ! hasOne {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , fmt . Errorf ( "no valid answer defined." ) ) )
2022-07-11 17:57:33 +00:00
}
// Call checks hooks
for _ , h := range hooks . flagKeyWithChoicesHooks {
2023-11-22 11:16:53 +00:00
for _ , e := range multierr . Errors ( h ( fk , raw , choices , exercice , exceptions . Filter2ndCol ( strconv . Itoa ( flagline ) ) ) ) {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , flagline , e ) )
2022-07-11 17:57:33 +00:00
}
2019-07-05 20:28:56 +00:00
}
}
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
2022-10-31 20:55:43 +00:00
JustifyOf * fic . MCQ_entry
2021-11-22 14:35:07 +00:00
Choices [ ] * fic . FlagChoice
2019-07-05 20:28:56 +00:00
FilesDeps [ ] string
FlagsDeps [ ] int64
}
2017-12-09 00:21:58 +00:00
2024-01-13 15:40:25 +00:00
func iface2Number ( input interface { } , output * string ) ( norm float64 , err error ) {
2021-11-12 20:36:27 +00:00
if input != nil {
if v , ok := input . ( int64 ) ; ok {
* output = fmt . Sprintf ( "%d" , v )
2024-01-13 15:40:25 +00:00
norm = float64 ( v )
2021-11-12 20:36:27 +00:00
} else if v , ok := input . ( float64 ) ; ok {
* output = strconv . FormatFloat ( v , 'f' , - 1 , 64 )
2024-01-13 15:40:25 +00:00
norm = v
2021-11-12 20:36:27 +00:00
} else {
2024-01-13 15:40:25 +00:00
err = fmt . Errorf ( "has an invalid type: expected int or float, got %T" , input )
2021-11-12 20:36:27 +00:00
}
}
2024-01-13 15:40:25 +00:00
return
2021-11-12 20:36:27 +00:00
}
2022-10-31 20:55:43 +00:00
// buildExerciceFlag read challenge.txt and extract all flags.
2023-11-22 11:16:53 +00:00
func buildExerciceFlag ( i Importer , exercice * fic . Exercice , flag ExerciceFlag , nline int , exceptions * CheckExceptions ) ( ret [ ] importFlag , errs error ) {
2019-11-25 15:18:59 +00:00
switch strings . ToLower ( flag . Type ) {
case "" :
flag . Type = "key"
2022-01-21 12:06:37 +00:00
case "label" :
flag . Type = "label"
2019-11-25 15:18:59 +00:00
case "key" :
flag . Type = "key"
2021-11-12 20:36:27 +00:00
case "number" :
var smin , smax , sstep string
2024-01-13 15:40:25 +00:00
fstep , err := iface2Number ( flag . NumberStep , & sstep )
if err != nil {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "step %w" , err ) ) )
}
_ , err = iface2Number ( flag . NumberMin , & smin )
2021-11-12 20:36:27 +00:00
if err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "min %w" , err ) ) )
2021-11-12 20:36:27 +00:00
}
2024-01-13 15:40:25 +00:00
_ , err = iface2Number ( flag . NumberMax , & smax )
2021-11-12 20:36:27 +00:00
if err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "max %w" , err ) ) )
2021-11-12 20:36:27 +00:00
}
2024-01-13 15:40:25 +00:00
// Ensure step permit validating the flag
if rns , err := flag . RawNumber ( ) ; err != nil {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "raw %w" , err ) ) )
} else {
2024-01-20 11:02:32 +00:00
if fstep == 0 {
fstep = 1.0
}
2024-01-13 15:40:25 +00:00
for _ , rn := range rns {
v := math . Abs ( rn ) / fstep
if float64 ( int ( v ) ) != v {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "choosen step=%f doesn't include response=%f" , fstep , rn ) ) )
}
}
2021-11-12 20:36:27 +00:00
}
2024-01-13 15:40:25 +00:00
2021-11-12 20:36:27 +00:00
flag . Type = fmt . Sprintf ( "number,%s,%s,%s" , smin , smax , sstep )
2020-01-16 14:33:28 +00:00
case "text" :
flag . Type = "text"
2019-11-25 15:18:59 +00:00
case "vector" :
flag . Type = "vector"
case "ucq" :
flag . Type = "ucq"
2021-11-12 23:58:03 +00:00
case "radio" :
flag . Type = "radio"
2019-11-25 15:18:59 +00:00
case "mcq" :
flag . Type = "mcq"
2024-05-17 22:28:59 +00:00
case "justified" :
flag . Type = "justified"
2019-11-25 15:18:59 +00:00
default :
2024-05-17 22:28:59 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "invalid type of flag: should be 'key', 'number', 'text', 'mcq', 'justified', 'ucq', 'radio' or 'vector'" ) ) )
2019-11-25 15:18:59 +00:00
return
}
2021-11-12 20:36:27 +00:00
if ! strings . HasPrefix ( flag . Type , "number" ) {
if flag . NumberMin != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "property min undefined for this kind of flag: should the type be 'number'" ) ) )
2021-11-12 20:36:27 +00:00
} else if flag . NumberMax != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "property max undefined for this kind of flag: should the type be 'number'" ) ) )
2021-11-12 20:36:27 +00:00
} else if flag . NumberStep != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "property step undefined for this kind of flag: should the type be 'number'" ) ) )
2021-11-12 20:36:27 +00:00
}
}
2022-01-21 07:45:07 +00:00
if len ( flag . Help ) > 0 {
2023-11-25 09:32:48 +00:00
// Call checks hooks
for _ , hk := range hooks . mdTextHooks {
for _ , err := range multierr . Errors ( hk ( flag . Help , exercice . Language , exceptions ) ) {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , err ) )
}
}
2022-01-21 07:45:07 +00:00
if mdhelp , err := ProcessMarkdown ( i , flag . Help , exercice . Path ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "unable to parse property help as Markdown: %w" , err ) ) )
2022-01-21 07:45:07 +00:00
} else {
flag . Help = mdhelp [ 3 : len ( mdhelp ) - 4 ]
}
}
2022-01-21 12:06:37 +00:00
if flag . Type == "label" {
2022-10-29 15:03:57 +00:00
addedFlag , berrs := buildLabelFlag ( exercice , flag , nline + 1 , exceptions )
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , berrs )
2022-01-21 12:06:37 +00:00
if addedFlag != nil {
ret = append ( ret , importFlag {
Line : nline + 1 ,
Flag : addedFlag ,
} )
}
} else if flag . Type == "key" || strings . HasPrefix ( flag . Type , "number" ) || flag . Type == "text" || flag . Type == "ucq" || flag . Type == "radio" || flag . Type == "vector" {
2022-10-29 15:03:57 +00:00
addedFlag , choices , berrs := buildKeyFlag ( exercice , flag , nline + 1 , "Flag" , exceptions )
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , berrs )
2019-11-25 15:18:59 +00:00
if addedFlag != nil {
ret = append ( ret , importFlag {
Line : nline + 1 ,
Flag : * addedFlag ,
Choices : choices ,
} )
}
2024-05-17 22:28:59 +00:00
} else if flag . Type == "mcq" || flag . Type == "justified" {
2019-11-25 15:18:59 +00:00
addedFlag := fic . MCQ {
IdExercice : exercice . Id ,
2021-08-30 16:33:14 +00:00
Order : int8 ( nline + 1 ) ,
2019-11-25 15:18:59 +00:00
Title : flag . Label ,
2021-11-22 14:35:07 +00:00
Entries : [ ] * fic . MCQ_entry { } ,
2019-11-25 15:18:59 +00:00
}
hasOne := false
2024-05-17 22:28:59 +00:00
isJustified := flag . Type == "justified"
2019-11-25 15:18:59 +00:00
2022-01-21 12:06:37 +00:00
if len ( flag . Variant ) != 0 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "variant is not defined for this kind of flag" ) ) )
2022-01-21 12:06:37 +00:00
}
2019-11-25 15:18:59 +00:00
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 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "all true items has to be justified in this MCQ" ) ) )
2019-11-25 15:18:59 +00:00
continue
}
val = true
isJustified = true
} else if p , ok := choice . Value . ( bool ) ; ok {
val = p
if isJustified {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "all true items has to be justified in this MCQ" ) ) )
2019-11-25 15:18:59 +00:00
continue
}
} else if choice . Value == nil {
val = false
} else {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , fmt . Errorf ( "choice %d: incorrect type for value: %T is not boolean." , cid , choice . Value ) ) )
2019-11-25 15:18:59 +00:00
continue
}
2022-10-31 20:55:43 +00:00
entry := & fic . MCQ_entry {
2019-11-25 15:18:59 +00:00
Label : choice . Label ,
Response : val ,
2022-10-31 20:55:43 +00:00
}
addedFlag . Entries = append ( addedFlag . Entries , entry )
2019-11-25 15:18:59 +00:00
if isJustified && choice . Raw != nil {
2022-10-29 15:03:57 +00:00
addedFlag , choices , berrs := buildKeyFlag ( exercice , choice . ExerciceFlag , nline + 1 , "Flag correspondant" , exceptions )
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , berrs )
2019-11-25 15:18:59 +00:00
if addedFlag != nil {
ret = append ( ret , importFlag {
2022-10-31 20:55:43 +00:00
Line : nline + 1 ,
Flag : * addedFlag ,
JustifyOf : entry ,
Choices : choices ,
2019-11-25 15:18:59 +00:00
} )
}
}
}
2022-07-11 17:57:33 +00:00
// Call checks hooks
for _ , h := range hooks . flagMCQHooks {
2023-11-22 11:16:53 +00:00
for _ , e := range multierr . Errors ( h ( & addedFlag , addedFlag . Entries , exercice , exceptions ) ) {
errs = multierr . Append ( errs , NewFlagError ( exercice , & flag , nline + 1 , e ) )
2022-07-11 17:57:33 +00:00
}
}
2022-10-31 20:55:43 +00:00
ret = append ( [ ] importFlag { importFlag {
2020-04-15 05:39:38 +00:00
Line : nline + 1 ,
2021-11-22 14:35:07 +00:00
Flag : & addedFlag ,
2022-10-31 20:55:43 +00:00
} } , ret ... )
2019-11-25 15:18:59 +00:00
}
return
}
2019-07-05 20:28:56 +00:00
// buildExerciceFlags read challenge.txt and extract all flags.
2023-11-22 11:16:53 +00:00
func buildExerciceFlags ( i Importer , exercice * fic . Exercice , exceptions * CheckExceptions ) ( flags map [ int64 ] importFlag , flagids [ ] int64 , errs error ) {
2019-07-05 20:28:56 +00:00
params , gerrs := getExerciceParams ( i , exercice )
2023-11-22 11:16:53 +00:00
if len ( multierr . Errors ( 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 ] {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & params . Flags [ nline ] , nline + 1 , fmt . Errorf ( "identifier already used (%d), using a random one." , flag . Id ) ) )
2019-07-05 20:28:56 +00:00
flag . Id = rand . Int63 ( )
}
2018-12-02 18:21:07 +00:00
2022-10-29 15:03:57 +00:00
newFlags , ferrs := buildExerciceFlag ( i , exercice , flag , nline , exceptions )
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , ferrs )
2019-11-25 15:18:59 +00:00
if len ( newFlags ) > 0 {
for _ , newFlag := range newFlags {
fId := flag . Id
for _ , ok := flags [ fId ] ; ok ; _ , ok = flags [ fId ] {
fId = rand . Int63 ( )
2018-12-02 18:21:07 +00:00
}
2019-07-05 20:28:56 +00:00
2019-11-25 15:18:59 +00:00
// Read dependency to flag
for _ , nf := range flag . NeedFlag {
2022-11-02 21:24:15 +00:00
if len ( nf . Theme ) > 0 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , & params . Flags [ nline ] , nline + 1 , fmt . Errorf ( "dependancy on another scenario is not implemented yet." ) ) )
2022-11-02 21:24:15 +00:00
}
2019-11-25 15:18:59 +00:00
newFlag . FlagsDeps = append ( newFlag . FlagsDeps , nf . Id )
2019-07-05 20:28:56 +00:00
}
2022-11-02 21:24:15 +00:00
for _ , nf := range flag . NeedFlags {
newFlag . FlagsDeps = append ( newFlag . FlagsDeps , nf )
}
2019-01-16 04:25:20 +00:00
2019-11-25 15:18:59 +00:00
// Read dependency to file
for _ , lf := range flag . LockedFile {
newFlag . FilesDeps = append ( newFlag . FilesDeps , lf . Filename )
2018-11-21 03:10:22 +00:00
}
2019-09-06 21:23:50 +00:00
2019-11-25 15:18:59 +00:00
flags [ fId ] = newFlag
flagids = append ( flagids , fId )
2019-07-05 20:28:56 +00:00
}
}
}
return
}
// CheckExerciceFlags checks if all flags for the given challenge are correct.
2023-11-22 11:16:53 +00:00
func CheckExerciceFlags ( i Importer , exercice * fic . Exercice , files [ ] string , exceptions * CheckExceptions ) ( rf [ ] fic . Flag , errs error ) {
2024-05-16 09:10:30 +00:00
exceptions = exceptions . GetFileExceptions ( "challenge.toml" , "challenge.txt" )
2022-11-03 16:49:06 +00:00
2022-10-29 15:03:57 +00:00
flags , flagsids , berrs := buildExerciceFlags ( i , exercice , exceptions )
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , berrs )
2019-07-05 20:28:56 +00:00
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 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , nil , flag . Line , fmt . Errorf ( "flag depend on flag id=%d: id not defined" , nf ) ) )
2019-10-12 10:54:01 +00:00
}
2019-07-05 20:28:56 +00:00
}
2024-03-17 17:10:21 +00:00
if fk , ok := flag . Flag . ( * fic . FlagKey ) ; ok {
// Check dependency to flag optional flag
if fk . BonusGain == 0 {
for _ , nf := range flag . FlagsDeps {
if fk2 , ok := flags [ nf ] . Flag . ( * fic . FlagKey ) ; ok && fk2 . BonusGain != 0 {
errs = multierr . Append ( errs , NewFlagError ( exercice , nil , flag . Line , fmt . Errorf ( "flag is not optional but depend on flag id=%d which is optional" , nf ) ) )
}
2024-03-17 16:58:11 +00:00
}
}
2024-03-17 17:10:21 +00:00
if int64 ( fk . ChoicesCost ) >= exercice . Gain {
errs = multierr . Append ( errs , NewFlagError ( exercice , nil , flag . Line , fmt . Errorf ( "flag's choice_cost is higher than exercice gain" ) ) )
}
2024-03-17 16:58:11 +00:00
}
2022-11-07 15:22:16 +00:00
// Check dependency loop
deps := flag . FlagsDeps
for i := 0 ; i < len ( deps ) ; i ++ {
if deps [ i ] == flagid {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , nil , flag . Line , fmt . Errorf ( "flag dependency loop detected: flag id=%d: depends on itself" , flagid ) ) )
2022-11-07 15:22:16 +00:00
break
}
2022-11-14 12:47:46 +00:00
deploppadd :
2022-11-07 15:22:16 +00:00
for _ , d := range flags [ deps [ i ] ] . FlagsDeps {
for _ , dd := range deps {
if dd == d {
2022-11-14 12:47:46 +00:00
continue deploppadd
2022-11-07 15:22:16 +00:00
}
}
deps = append ( deps , d )
}
}
2019-10-12 10:54:01 +00:00
// Check dependency to file
for _ , lf := range flag . FilesDeps {
found := false
for _ , f := range files {
2020-01-16 17:53:27 +00:00
if f == lf {
2019-10-12 10:54:01 +00:00
found = true
break
}
}
if ! found {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , nil , flag . Line , fmt . Errorf ( "flag depend on %s: No such file" , 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
}
2019-11-25 15:18:59 +00:00
// ExerciceFlagsMap builds the flags bindings between challenge.txt and DB.
2021-11-22 14:35:07 +00:00
func ExerciceFlagsMap ( i Importer , exercice * fic . Exercice ) ( kmap map [ int64 ] fic . Flag ) {
2022-10-29 15:03:57 +00:00
flags , flagids , _ := buildExerciceFlags ( i , exercice , nil )
2019-11-25 15:18:59 +00:00
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
}
2019-07-05 20:28:56 +00:00
// SyncExerciceFlags imports all kind of flags for the given challenge.
2023-11-22 11:16:53 +00:00
func SyncExerciceFlags ( i Importer , exercice * fic . Exercice , exceptions * CheckExceptions ) ( kmap map [ int64 ] fic . Flag , errs error ) {
2019-07-05 20:28:56 +00:00
if _ , err := exercice . WipeFlags ( ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , err )
2019-07-05 20:28:56 +00:00
} else if _ , err := exercice . WipeMCQs ( ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , err )
2019-07-05 20:28:56 +00:00
} else {
2024-05-16 09:10:30 +00:00
exceptions = exceptions . GetFileExceptions ( "challenge.toml" , "challenge.txt" )
2022-11-03 16:49:06 +00:00
2022-10-29 15:03:57 +00:00
flags , flagids , berrs := buildExerciceFlags ( i , exercice , exceptions )
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , berrs )
2019-07-05 20:28:56 +00:00
2019-11-25 13:57:21 +00:00
kmap = map [ int64 ] fic . Flag { }
2019-07-05 20:28:56 +00:00
// Import flags
2019-10-12 10:54:01 +00:00
for _ , flagid := range flagids {
if flag , ok := flags [ flagid ] ; ok {
2022-10-31 20:55:43 +00:00
if flag . JustifyOf != nil {
if f , ok := flag . Flag . ( * fic . FlagKey ) ; ok {
f . Label = fmt . Sprintf ( "%%%d%%%s" , flag . JustifyOf . Id , f . Label )
}
}
2019-10-12 10:54:01 +00:00
if addedFlag , err := exercice . AddFlag ( flag . Flag ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , nil , flag . Line , err ) )
2019-10-12 10:54:01 +00:00
} else {
2021-11-22 14:35:07 +00:00
if f , ok := addedFlag . ( * fic . FlagKey ) ; ok {
2019-10-12 10:54:01 +00:00
for _ , choice := range flag . Choices {
if _ , err := f . AddChoice ( choice ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , nil , flag . Line , fmt . Errorf ( "choice #FIXME: %w" , err ) ) )
2019-10-12 10:54:01 +00:00
}
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 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , nil , flag . Line , fmt . Errorf ( "dependency to flag id=%d: id not defined, perhaps not available at time of processing" , nf ) ) )
2019-10-12 10:54:01 +00:00
} else if err := addedFlag . AddDepend ( rf ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , nil , flag . Line , fmt . Errorf ( "dependency to id=%d: %w" , nf , err ) ) )
2019-10-12 10:54:01 +00:00
}
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 {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , nil , flag . Line , fmt . Errorf ( "dependency to %s: %w" , lf , err ) ) )
2019-10-12 10:54:01 +00:00
} else if err := rf . AddDepend ( addedFlag ) ; err != nil {
2023-11-22 11:16:53 +00:00
errs = multierr . Append ( errs , NewFlagError ( exercice , nil , flag . Line , fmt . Errorf ( "dependency to %s: %w" , lf , err ) ) )
2019-10-12 10:54:01 +00:00
}
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.
2022-05-16 09:38:46 +00:00
func ApiGetRemoteExerciceFlags ( c * gin . Context ) {
2022-10-29 15:03:57 +00:00
theme , exceptions , errs := BuildTheme ( GlobalImporter , c . Params . ByName ( "thid" ) )
2019-09-06 23:25:42 +00:00
if theme != nil {
2023-07-09 17:05:58 +00:00
exercice , _ , _ , eexceptions , _ , errs := BuildExercice ( GlobalImporter , theme , path . Join ( theme . Path , c . Params . ByName ( "exid" ) ) , nil , exceptions )
2019-09-06 23:25:42 +00:00
if exercice != nil {
2023-07-09 17:05:58 +00:00
flags , errs := CheckExerciceFlags ( GlobalImporter , exercice , [ ] string { } , eexceptions )
2019-09-06 23:25:42 +00:00
if flags != nil {
2022-05-16 09:38:46 +00:00
c . JSON ( http . StatusOK , flags )
return
2019-09-06 23:25:42 +00:00
}
2022-05-16 09:38:46 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , fmt . Errorf ( "%q" , errs ) )
return
2019-09-06 23:25:42 +00:00
}
2022-05-16 09:38:46 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , fmt . Errorf ( "%q" , errs ) )
return
2019-09-06 23:25:42 +00:00
}
2022-05-16 09:38:46 +00:00
c . AbortWithStatusJSON ( http . StatusInternalServerError , fmt . Errorf ( "%q" , errs ) )
return
2019-09-06 23:25:42 +00:00
}