sync: Introduce repochecker-ack.txt to support check exceptions

This commit is contained in:
nemunaire 2022-10-29 17:03:57 +02:00
parent edde9f885d
commit fb368d79d1
17 changed files with 283 additions and 106 deletions

View File

@ -110,6 +110,14 @@ func ExerciceHandler(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Exercice not found"})
return
}
theme, err = exercice.GetTheme()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to find the attached theme."})
return
}
c.Set("theme", theme)
}
c.Set("exercice", exercice)

View File

@ -52,8 +52,10 @@ func declareSyncRoutes(router *gin.RouterGroup) {
apiSyncDeepRoutes.POST("", func(c *gin.Context) {
theme := c.MustGet("theme").(*fic.Theme)
exceptions := sync.LoadException(sync.GlobalImporter, theme)
var st []string
for _, se := range sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250) {
for _, se := range sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250, exceptions) {
st = append(st, se.Error())
}
sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]string{theme.Name: st}}, false)
@ -68,7 +70,8 @@ func declareSyncRoutes(router *gin.RouterGroup) {
apiSyncRoutes.POST("/fixurlids", fixAllURLIds)
apiSyncRoutes.POST("/themes", func(c *gin.Context) {
c.JSON(http.StatusOK, sync.SyncThemes(sync.GlobalImporter))
_, errs := sync.SyncThemes(sync.GlobalImporter)
c.JSON(http.StatusOK, errs)
})
apiSyncThemesRoutes := apiSyncRoutes.Group("/themes/:thid")
@ -130,25 +133,29 @@ func declareSyncRoutes(router *gin.RouterGroup) {
func declareSyncExercicesRoutes(router *gin.RouterGroup) {
router.POST("/exercices", func(c *gin.Context) {
theme := c.MustGet("theme").(*fic.Theme)
c.JSON(http.StatusOK, sync.SyncExercices(sync.GlobalImporter, theme))
exceptions := sync.LoadException(sync.GlobalImporter, theme)
c.JSON(http.StatusOK, sync.SyncExercices(sync.GlobalImporter, theme, exceptions))
})
apiSyncExercicesRoutes := router.Group("/exercices/:eid")
apiSyncExercicesRoutes.Use(ExerciceHandler)
apiSyncExercicesRoutes.POST("", func(c *gin.Context) {
theme, exists := c.Get("theme")
if !exists {
c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"errmsg": "You should sync exercice only through a theme."})
return
}
theme := c.MustGet("theme").(*fic.Theme)
exceptions := sync.LoadException(sync.GlobalImporter, theme)
exercice := c.MustGet("exercice").(*fic.Exercice)
_, _, errs := sync.SyncExercice(sync.GlobalImporter, theme.(*fic.Theme), exercice.Path, nil)
_, _, errs := sync.SyncExercice(sync.GlobalImporter, theme, exercice.Path, nil, exceptions)
c.JSON(http.StatusOK, errs)
})
apiSyncExercicesRoutes.POST("/files", func(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
c.JSON(http.StatusOK, sync.SyncExerciceFiles(sync.GlobalImporter, exercice))
theme := c.MustGet("theme").(*fic.Theme)
exceptions := sync.LoadException(sync.GlobalImporter, theme)
c.JSON(http.StatusOK, sync.SyncExerciceFiles(sync.GlobalImporter, exercice, exceptions))
})
apiSyncExercicesRoutes.POST("/fixurlid", func(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
@ -167,13 +174,20 @@ func declareSyncExercicesRoutes(router *gin.RouterGroup) {
})
apiSyncExercicesRoutes.POST("/hints", func(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
_, errs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
theme := c.MustGet("theme").(*fic.Theme)
exceptions := sync.LoadException(sync.GlobalImporter, theme)
_, errs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice), exceptions)
c.JSON(http.StatusOK, errs)
})
apiSyncExercicesRoutes.POST("/flags", func(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice)
_, herrs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
theme := c.MustGet("theme").(*fic.Theme)
exceptions := sync.LoadException(sync.GlobalImporter, theme)
_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice, exceptions)
_, herrs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice), exceptions)
c.JSON(http.StatusOK, append(errs, herrs...))
})
}
@ -214,8 +228,10 @@ func autoSync(c *gin.Context) {
}
}
exceptions := sync.LoadException(sync.GlobalImporter, theme)
var st []string
for _, se := range sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250) {
for _, se := range sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250, exceptions) {
st = append(st, se.Error())
}
sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]string{theme.Name: st}}, false)

62
admin/sync/exceptions.go Normal file
View File

@ -0,0 +1,62 @@
package sync
import (
"fmt"
"path/filepath"
"strings"
"srs.epita.fr/fic-server/libfic"
)
type CheckExceptions map[string]string
func (c *CheckExceptions) GetExerciceExceptions(e *fic.Exercice) *CheckExceptions {
return c.GetFileExceptions(filepath.Base(e.Path))
}
func (c *CheckExceptions) GetFileExceptions(path string) *CheckExceptions {
if c == nil {
return nil
}
ret := CheckExceptions{}
for k, v := range *c {
if strings.HasPrefix(k, "*:") || strings.HasPrefix(k, path) {
k = strings.TrimPrefix(k, path)
if strings.HasPrefix(k, "/") {
k = strings.TrimPrefix(k, "/")
}
ret[k] = v
}
}
return &ret
}
func (c *CheckExceptions) HasException(ref string) bool {
if c == nil {
return false
}
for k, _ := range *c {
if strings.HasSuffix(k, ref) {
return true
}
}
return false
}
func LoadException(i Importer, th *fic.Theme) (exceptions *CheckExceptions) {
if fexcept, err := GetFileContent(i, filepath.Join(th.Path, "repochecker-ack.txt")); err == nil {
exceptions = &CheckExceptions{}
for n, line := range strings.Split(fexcept, "\n") {
(*exceptions)[line] = fmt.Sprintf("repochecker-ack.txt:%d", n+1)
}
}
return
}

View File

@ -97,7 +97,7 @@ func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []str
}
// CheckExerciceFiles checks that remote files have the right digest.
func CheckExerciceFiles(i Importer, exercice *fic.Exercice) (files []string, errs []error) {
func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (files []string, errs []error) {
flist, digests, berrs := BuildFilesListInto(i, exercice, "files")
errs = append(errs, berrs...)
@ -116,7 +116,7 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice) (files []string, err
// Call checks hooks
for _, h := range hooks.fileHooks {
for _, e := range h(file) {
for _, e := range h(file, exceptions) {
errs = append(errs, NewFileError(exercice, fname, e))
}
}
@ -129,7 +129,7 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice) (files []string, err
// SyncExerciceFiles reads the content of files/ directory and import it as EFile for the given challenge.
// It takes care of DIGESTS.txt and ensure imported files match.
func SyncExerciceFiles(i Importer, exercice *fic.Exercice) (errs []error) {
func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (errs []error) {
if _, err := exercice.WipeFiles(); err != nil {
errs = append(errs, err)
}
@ -150,7 +150,7 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice) (errs []error) {
} else {
// Call checks hooks
for _, h := range hooks.fileHooks {
for _, e := range h(f.(*fic.EFile)) {
for _, e := range h(f.(*fic.EFile), exceptions) {
errs = append(errs, NewFileError(exercice, fname, e))
}
}
@ -161,9 +161,9 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice) (errs []error) {
// ApiGetRemoteExerciceFiles is an accessor to remote exercice files list.
func ApiGetRemoteExerciceFiles(c *gin.Context) {
theme, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
if theme != nil {
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil)
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions)
if exercice != nil {
files, digests, errs := BuildFilesListInto(GlobalImporter, exercice, "files")
if files != nil {

View File

@ -23,7 +23,7 @@ type importHint struct {
FlagsDeps []int64
}
func buildExerciceHints(i Importer, exercice *fic.Exercice) (hints []importHint, errs []error) {
func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (hints []importHint, errs []error) {
params, _, err := parseExerciceParams(i, exercice.Path)
if err != nil {
errs = append(errs, NewChallengeTxtError(exercice, 0, err))
@ -82,13 +82,22 @@ func buildExerciceHints(i Importer, exercice *fic.Exercice) (hints []importHint,
} else if hint.Content == "" {
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be empty at the same time")))
continue
} else if h.Content, err = ProcessMarkdown(i, fixnbsp(hint.Content), exercice.Path); err != nil {
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("error during markdown formating: %w", err)))
} else {
// Call checks hooks
for _, hk := range hooks.mdTextHooks {
for _, err := range hk(h.Content, exceptions.GetFileExceptions("challenge.txt")) {
errs = append(errs, NewHintError(exercice, h, n, err))
}
}
if h.Content, err = ProcessMarkdown(i, fixnbsp(hint.Content), exercice.Path); err != nil {
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("error during markdown formating: %w", err)))
}
}
// Call checks hooks
for _, hook := range hooks.hintHooks {
for _, e := range hook(h) {
for _, e := range hook(h, exceptions.GetFileExceptions("challenge.txt")) {
errs = append(errs, NewHintError(exercice, h, n, e))
}
}
@ -110,16 +119,16 @@ func buildExerciceHints(i Importer, exercice *fic.Exercice) (hints []importHint,
}
// CheckExerciceHints checks if all hints are corrects..
func CheckExerciceHints(i Importer, exercice *fic.Exercice) ([]importHint, []error) {
return buildExerciceHints(i, exercice)
func CheckExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) ([]importHint, []error) {
return buildExerciceHints(i, exercice, exceptions)
}
// SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge.
func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int64]fic.Flag) (hintsBindings map[int]*fic.EHint, errs []error) {
func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int64]fic.Flag, exceptions *CheckExceptions) (hintsBindings map[int]*fic.EHint, errs []error) {
if _, err := exercice.WipeHints(); err != nil {
errs = append(errs, err)
} else {
hints, berrs := buildExerciceHints(i, exercice)
hints, berrs := buildExerciceHints(i, exercice, exceptions)
errs = append(errs, berrs...)
hintsBindings = map[int]*fic.EHint{}
@ -149,11 +158,11 @@ func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int
// ApiListRemoteExerciceHints is an accessor letting foreign packages to access remote exercice hints.
func ApiGetRemoteExerciceHints(c *gin.Context) {
theme, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
if theme != nil {
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil)
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions)
if exercice != nil {
hints, errs := CheckExerciceHints(GlobalImporter, exercice)
hints, errs := CheckExerciceHints(GlobalImporter, exercice, exceptions)
if hints != nil {
c.JSON(http.StatusOK, hints)
return

View File

@ -96,7 +96,7 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
return
}
func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int) (f *fic.FlagLabel, errs []error) {
func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, exceptions *CheckExceptions) (f *fic.FlagLabel, errs []error) {
if len(flag.Label) == 0 {
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label cannot be empty.")))
return
@ -118,14 +118,14 @@ func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int) (f
// Call checks hooks
for _, h := range hooks.flagLabelHooks {
for _, e := range h(f) {
for _, e := range h(f, exceptions) {
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
}
}
return
}
func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defaultLabel string) (f *fic.Flag, choices []*fic.FlagChoice, errs []error) {
func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defaultLabel string, exceptions *CheckExceptions) (f *fic.Flag, choices []*fic.FlagChoice, errs []error) {
if len(flag.Label) == 0 {
flag.Label = defaultLabel
}
@ -178,7 +178,7 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
// Call checks hooks
for _, h := range hooks.flagKeyHooks {
for _, e := range h(fk, raw) {
for _, e := range h(fk, raw, exceptions) {
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
}
}
@ -221,7 +221,7 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
// Call checks hooks
for _, h := range hooks.flagChoiceHooks {
for _, e := range h(fc) {
for _, e := range h(fc, exceptions) {
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
}
}
@ -242,7 +242,7 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
// Call checks hooks
for _, h := range hooks.flagKeyWithChoicesHooks {
for _, e := range h(fk, raw, choices) {
for _, e := range h(fk, raw, choices, exceptions) {
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
}
}
@ -273,7 +273,7 @@ func iface2Number(input interface{}, output *string) error {
}
// buildExerciceFlags read challenge.txt and extract all flags.
func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nline int) (ret []importFlag, errs []error) {
func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nline int, exceptions *CheckExceptions) (ret []importFlag, errs []error) {
switch strings.ToLower(flag.Type) {
case "":
flag.Type = "key"
@ -332,7 +332,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
}
if flag.Type == "label" {
addedFlag, berrs := buildLabelFlag(exercice, flag, nline+1)
addedFlag, berrs := buildLabelFlag(exercice, flag, nline+1, exceptions)
errs = append(errs, berrs...)
if addedFlag != nil {
ret = append(ret, importFlag{
@ -341,7 +341,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
})
}
} else if flag.Type == "key" || strings.HasPrefix(flag.Type, "number") || flag.Type == "text" || flag.Type == "ucq" || flag.Type == "radio" || flag.Type == "vector" {
addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag")
addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag", exceptions)
errs = append(errs, berrs...)
if addedFlag != nil {
ret = append(ret, importFlag{
@ -401,7 +401,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
})
if isJustified && choice.Raw != nil {
addedFlag, choices, berrs := buildKeyFlag(exercice, choice.ExerciceFlag, nline+1, "Flag correspondant")
addedFlag, choices, berrs := buildKeyFlag(exercice, choice.ExerciceFlag, nline+1, "Flag correspondant", exceptions)
if len(berrs) > 0 {
errs = append(errs, berrs...)
}
@ -417,7 +417,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
// Call checks hooks
for _, h := range hooks.flagMCQHooks {
for _, e := range h(&addedFlag, addedFlag.Entries) {
for _, e := range h(&addedFlag, addedFlag.Entries, exceptions) {
errs = append(errs, NewFlagError(exercice, &flag, nline+1, e))
}
}
@ -431,7 +431,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
}
// buildExerciceFlags read challenge.txt and extract all flags.
func buildExerciceFlags(i Importer, exercice *fic.Exercice) (flags map[int64]importFlag, flagids []int64, errs []error) {
func buildExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (flags map[int64]importFlag, flagids []int64, errs []error) {
params, gerrs := getExerciceParams(i, exercice)
if len(gerrs) > 0 {
return flags, flagids, gerrs
@ -451,7 +451,7 @@ func buildExerciceFlags(i Importer, exercice *fic.Exercice) (flags map[int64]imp
flag.Id = rand.Int63()
}
newFlags, ferrs := buildExerciceFlag(i, exercice, flag, nline)
newFlags, ferrs := buildExerciceFlag(i, exercice, flag, nline, exceptions)
if len(ferrs) > 0 {
errs = append(errs, ferrs...)
}
@ -482,8 +482,8 @@ func buildExerciceFlags(i Importer, exercice *fic.Exercice) (flags map[int64]imp
}
// CheckExerciceFlags checks if all flags for the given challenge are correct.
func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string) (rf []fic.Flag, errs []error) {
flags, flagsids, berrs := buildExerciceFlags(i, exercice)
func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exceptions *CheckExceptions) (rf []fic.Flag, errs []error) {
flags, flagsids, berrs := buildExerciceFlags(i, exercice, exceptions)
errs = append(errs, berrs...)
for _, flagid := range flagsids {
@ -518,7 +518,7 @@ func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string) (rf
// 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)
flags, flagids, _ := buildExerciceFlags(i, exercice, nil)
kmap = map[int64]fic.Flag{}
@ -534,13 +534,13 @@ func ExerciceFlagsMap(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.Fl
}
// SyncExerciceFlags imports all kind of flags for the given challenge.
func SyncExerciceFlags(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.Flag, errs []error) {
func SyncExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (kmap map[int64]fic.Flag, errs []error) {
if _, err := exercice.WipeFlags(); err != nil {
errs = append(errs, err)
} else if _, err := exercice.WipeMCQs(); err != nil {
errs = append(errs, err)
} else {
flags, flagids, berrs := buildExerciceFlags(i, exercice)
flags, flagids, berrs := buildExerciceFlags(i, exercice, exceptions)
errs = append(errs, berrs...)
kmap = map[int64]fic.Flag{}
@ -588,11 +588,11 @@ func SyncExerciceFlags(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.F
// ApiListRemoteExerciceFlags is an accessor letting foreign packages to access remote exercice flags.
func ApiGetRemoteExerciceFlags(c *gin.Context) {
theme, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
if theme != nil {
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil)
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions)
if exercice != nil {
flags, errs := CheckExerciceFlags(GlobalImporter, exercice, []string{})
flags, errs := CheckExerciceFlags(GlobalImporter, exercice, []string{}, exceptions)
if flags != nil {
c.JSON(http.StatusOK, flags)
return

View File

@ -89,7 +89,7 @@ func parseExerciceDirname(edir string) (eid int, ename string, err error) {
}
// BuildExercice creates an Exercice from a given importer.
func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice) (e *fic.Exercice, p ExerciceParams, eid int, edir string, errs []error) {
func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice, exceptions *CheckExceptions) (e *fic.Exercice, p ExerciceParams, eid int, edir string, errs []error) {
e = &fic.Exercice{}
e.Path = epath
@ -102,6 +102,10 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
return nil, p, eid, edir, errs
}
// Limit exceptions to this exercice
exceptions = exceptions.GetExerciceExceptions(e)
//log.Printf("Kept repochecker exceptions for this exercice: %v", exceptions)
// Overwrite title if title.txt exists
if myTitle, err := GetFileContent(i, path.Join(epath, "title.txt")); err == nil {
myTitle = strings.TrimSpace(myTitle)
@ -128,6 +132,13 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
} else {
e.Overview = fixnbsp(e.Overview)
// Call checks hooks
for _, h := range hooks.mdTextHooks {
for _, err := range h(e.Overview, exceptions.GetFileExceptions("overview.md")) {
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.md: %w", err)))
}
}
var buf bytes.Buffer
err := goldmark.Convert([]byte(strings.Split(e.Overview, "\n")[0]), &buf)
if err != nil {
@ -151,6 +162,13 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
if err != nil {
errs = append(errs, NewExerciceError(e, fmt.Errorf("statement.md: %w", err), theme))
} else {
// Call checks hooks
for _, h := range hooks.mdTextHooks {
for _, err := range h(e.Statement, exceptions.GetFileExceptions("statement.md")) {
errs = append(errs, NewExerciceError(e, fmt.Errorf("statement.md: %w", err)))
}
}
if e.Statement, err = ProcessMarkdown(i, fixnbsp(e.Statement), epath); err != nil {
errs = append(errs, NewExerciceError(e, fmt.Errorf("statement.md: an error occurs during markdown formating: %w", err), theme))
}
@ -161,6 +179,13 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
if err != nil {
errs = append(errs, NewExerciceError(e, fmt.Errorf("finished.txt: %w", err), theme))
} else {
// Call checks hooks
for _, h := range hooks.mdTextHooks {
for _, err := range h(e.Finished, exceptions.GetFileExceptions("finished.txt")) {
errs = append(errs, NewExerciceError(e, fmt.Errorf("finished.md: %w", err)))
}
}
if e.Finished, err = ProcessMarkdown(i, e.Finished, epath); err != nil {
errs = append(errs, NewExerciceError(e, fmt.Errorf("finished.txt: an error occurs during markdown formating: %w", err), theme))
}
@ -259,16 +284,22 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
errs = append(errs, NewExerciceError(e, ErrResolutionNotFound, theme))
}
// Call checks hooks
for _, h := range hooks.exerciceHooks {
for _, err := range h(e, exceptions.GetFileExceptions("challenge.txt")) {
errs = append(errs, NewExerciceError(e, err))
}
}
return
}
// SyncExercice imports new or updates existing given exercice.
func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice) (e *fic.Exercice, eid int, errs []error) {
func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice, exceptions *CheckExceptions) (e *fic.Exercice, eid int, errs []error) {
var err error
var p ExerciceParams
var berrors []error
e, p, eid, _, berrors = BuildExercice(i, theme, epath, dmap)
e, p, eid, _, berrors = BuildExercice(i, theme, epath, dmap, exceptions)
for _, e := range berrors {
errs = append(errs, e)
}
@ -297,7 +328,7 @@ func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*f
}
// SyncExercices imports new or updates existing exercices, in a given theme.
func SyncExercices(i Importer, theme *fic.Theme) (errs []error) {
func SyncExercices(i Importer, theme *fic.Theme, exceptions *CheckExceptions) (errs []error) {
if exercices, err := GetExercices(i, theme); err != nil {
errs = append(errs, err)
} else {
@ -306,7 +337,7 @@ func SyncExercices(i Importer, theme *fic.Theme) (errs []error) {
dmap, _ := buildDependancyMap(i, theme)
for _, edir := range exercices {
e, eid, cur_errs := SyncExercice(i, theme, path.Join(theme.Path, edir), &dmap)
e, eid, cur_errs := SyncExercice(i, theme, path.Join(theme.Path, edir), &dmap, exceptions)
if e != nil {
emap[e.Title] = eid
dmap[int64(eid)] = e
@ -328,7 +359,7 @@ func SyncExercices(i Importer, theme *fic.Theme) (errs []error) {
// ApiListRemoteExercices is an accessor letting foreign packages to access remote exercices list.
func ApiListRemoteExercices(c *gin.Context) {
theme, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
theme, _, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
if theme != nil {
exercices, err := GetExercices(GlobalImporter, theme)
if err != nil {
@ -345,9 +376,9 @@ func ApiListRemoteExercices(c *gin.Context) {
// ApiListRemoteExercice is an accessor letting foreign packages to access remote exercice attributes.
func ApiGetRemoteExercice(c *gin.Context) {
theme, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
if theme != nil {
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil)
exercice, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions)
if exercice != nil {
c.JSON(http.StatusOK, exercice)
return

View File

@ -60,7 +60,7 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) {
}
errs.DateStart = startTime
sterrs := SyncThemes(i)
exceptions, sterrs := SyncThemes(i)
for _, sterr := range sterrs {
errs.ThemesSync = append(errs.ThemesSync, sterr.Error())
}
@ -71,7 +71,7 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) {
for tid, theme := range themes {
DeepSyncProgress = 3 + uint8(tid)*themeStep
seerrs := SyncExercices(i, theme)
seerrs := SyncExercices(i, theme, exceptions[theme.Path])
for _, seerr := range seerrs {
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], seerr.Error())
}
@ -85,13 +85,13 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) {
log.Printf("Speedy synchronization in progress: %d/255 - doing Theme %q, Exercice %q: %q\n", DeepSyncProgress, theme.Name, exercice.Title, exercice.Path)
DeepSyncProgress = 3 + uint8(tid)*themeStep + uint8(eid)*exerciceStep
flagsBindings, ferrs := SyncExerciceFlags(i, exercice)
flagsBindings, ferrs := SyncExerciceFlags(i, exercice, exceptions[theme.Path])
for _, ferr := range ferrs {
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], ferr.Error())
}
DeepSyncProgress += exerciceStep / 2
_, herrs := SyncExerciceHints(i, exercice, flagsBindings)
_, herrs := SyncExerciceHints(i, exercice, flagsBindings, exceptions[theme.Path])
for _, herr := range herrs {
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], herr.Error())
}
@ -128,7 +128,7 @@ func SyncDeep(i Importer) (errs SyncReport) {
}
errs.DateStart = startTime
sterrs := SyncThemes(i)
exceptions, sterrs := SyncThemes(i)
for _, sterr := range sterrs {
errs.ThemesSync = append(errs.ThemesSync, sterr.Error())
}
@ -138,7 +138,7 @@ func SyncDeep(i Importer) (errs SyncReport) {
var themeStep uint8 = uint8(250) / uint8(len(themes))
for tid, theme := range themes {
stderrs := SyncThemeDeep(i, theme, tid, themeStep)
stderrs := SyncThemeDeep(i, theme, tid, themeStep, exceptions[theme.Path])
for _, stderr := range stderrs {
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], stderr.Error())
}
@ -209,7 +209,7 @@ func EditDeepReport(errs *SyncReport, erase bool) {
}
// SyncThemeDeep performs a recursive synchronisation: from challenges to challenge items.
func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8) (errs []error) {
func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8, exceptions *CheckExceptions) (errs []error) {
oneThemeDeepSync.Lock()
defer oneThemeDeepSync.Unlock()
@ -220,7 +220,7 @@ func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8) (errs
}
DeepSyncProgress = 3 + uint8(tid)*themeStep
errs = SyncExercices(i, theme)
errs = SyncExercices(i, theme, exceptions)
if exercices, err := theme.GetExercices(); err == nil && len(exercices) > 0 {
var exerciceStep uint8 = themeStep / uint8(len(exercices))
@ -228,14 +228,14 @@ func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8) (errs
log.Printf("Deep synchronization in progress: %d/255 - doing Theme %q, Exercice %q: %q\n", DeepSyncProgress, theme.Name, exercice.Title, exercice.Path)
DeepSyncProgress = 3 + uint8(tid)*themeStep + uint8(eid)*exerciceStep
errs = append(errs, SyncExerciceFiles(i, exercice)...)
errs = append(errs, SyncExerciceFiles(i, exercice, exceptions)...)
DeepSyncProgress += exerciceStep / 3
flagsBindings, ferrs := SyncExerciceFlags(i, exercice)
flagsBindings, ferrs := SyncExerciceFlags(i, exercice, exceptions)
errs = append(errs, ferrs...)
DeepSyncProgress += exerciceStep / 3
_, herrs := SyncExerciceHints(i, exercice, flagsBindings)
_, herrs := SyncExerciceHints(i, exercice, flagsBindings, exceptions)
errs = append(errs, herrs...)
}
}

View File

@ -9,13 +9,15 @@ import (
var hooks = &CheckHooks{}
type CheckFlagChoiceHook func(*fic.FlagChoice) []error
type CheckFlagKeyHook func(*fic.FlagKey, string) []error
type CheckFlagKeyWithChoicesHook func(*fic.FlagKey, string, []*fic.FlagChoice) []error
type CheckFlagLabelHook func(*fic.FlagLabel) []error
type CheckFlagMCQHook func(*fic.MCQ, []*fic.MCQ_entry) []error
type CheckFileHook func(*fic.EFile) []error
type CheckHintHook func(*fic.EHint) []error
type CheckFlagChoiceHook func(*fic.FlagChoice, *CheckExceptions) []error
type CheckFlagKeyHook func(*fic.FlagKey, string, *CheckExceptions) []error
type CheckFlagKeyWithChoicesHook func(*fic.FlagKey, string, []*fic.FlagChoice, *CheckExceptions) []error
type CheckFlagLabelHook func(*fic.FlagLabel, *CheckExceptions) []error
type CheckFlagMCQHook func(*fic.MCQ, []*fic.MCQ_entry, *CheckExceptions) []error
type CheckFileHook func(*fic.EFile, *CheckExceptions) []error
type CheckHintHook func(*fic.EHint, *CheckExceptions) []error
type CheckMDTextHook func(string, *CheckExceptions) []error
type CheckExerciceHook func(*fic.Exercice, *CheckExceptions) []error
type CheckHooks struct {
flagChoiceHooks []CheckFlagChoiceHook
@ -25,6 +27,8 @@ type CheckHooks struct {
flagMCQHooks []CheckFlagMCQHook
fileHooks []CheckFileHook
hintHooks []CheckHintHook
mdTextHooks []CheckMDTextHook
exerciceHooks []CheckExerciceHook
}
func (h *CheckHooks) RegisterFlagChoiceHook(f CheckFlagChoiceHook) {
@ -55,6 +59,14 @@ func (h *CheckHooks) RegisterHintHook(f CheckHintHook) {
h.hintHooks = append(h.hintHooks, f)
}
func (h *CheckHooks) RegisterMDTextHook(f CheckMDTextHook) {
h.mdTextHooks = append(h.mdTextHooks, f)
}
func (h *CheckHooks) RegisterExerciceHook(f CheckExerciceHook) {
h.exerciceHooks = append(h.exerciceHooks, f)
}
func LoadChecksPlugin(fname string) error {
p, err := plugin.Open(fname)
if err != nil {

View File

@ -104,11 +104,14 @@ func getAuthors(i Importer, tname string) ([]string, error) {
}
// BuildTheme creates a Theme from a given importer.
func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []error) {
func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExceptions, errs []error) {
th = &fic.Theme{}
th.Path = tdir
// Get exceptions
exceptions = LoadException(i, th)
// Extract theme's label
if tname, err := GetFileContent(i, path.Join(tdir, "title.txt")); err == nil {
th.Name = fixnbsp(tname)
@ -121,7 +124,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []error) {
if authors, err := getAuthors(i, tdir); err != nil {
errs = append(errs, NewThemeError(th, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
return nil, errs
return nil, nil, errs
} else {
// Format authors
th.Authors = strings.Join(authors, ", ")
@ -139,6 +142,13 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []error) {
if err != nil {
errs = append(errs, NewThemeError(th, fmt.Errorf("unable to get theme's overview: %w", err)))
} else {
// Call checks hooks
for _, h := range hooks.mdTextHooks {
for _, err := range h(intro, exceptions.GetFileExceptions("overview.md")) {
errs = append(errs, NewThemeError(th, fmt.Errorf("overview.md: %w", err)))
}
}
// Split headline from intro
ovrvw := strings.Split(fixnbsp(intro), "\n")
th.Headline = ovrvw[0]
@ -188,7 +198,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []error) {
}
// SyncThemes imports new or updates existing themes.
func SyncThemes(i Importer) (errs []error) {
func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs []error) {
if themes, err := GetThemes(i); err != nil {
errs = append(errs, fmt.Errorf("Unable to list themes: %w", err))
} else {
@ -196,8 +206,10 @@ func SyncThemes(i Importer) (errs []error) {
themes[i], themes[j] = themes[j], themes[i]
})
exceptions = map[string]*CheckExceptions{}
for _, tdir := range themes {
btheme, berrs := BuildTheme(i, tdir)
btheme, excepts, berrs := BuildTheme(i, tdir)
for _, e := range berrs {
errs = append(errs, e)
}
@ -206,6 +218,8 @@ func SyncThemes(i Importer) (errs []error) {
continue
}
exceptions[tdir] = excepts
if len(btheme.Image) > 0 {
if _, err := i.importFile(btheme.Image,
func(filePath string, origin string) (interface{}, error) {
@ -251,6 +265,10 @@ func SyncThemes(i Importer) (errs []error) {
return
}
func LoadThemeExceptions(i Importer, theme *fic.Theme) (*CheckExceptions, error) {
return nil, nil
}
// ApiListRemoteThemes is an accessor letting foreign packages to access remote themes list.
func ApiListRemoteThemes(c *gin.Context) {
themes, err := GetThemes(GlobalImporter)
@ -264,7 +282,7 @@ func ApiListRemoteThemes(c *gin.Context) {
// ApiListRemoteTheme is an accessor letting foreign packages to access remote main theme attributes.
func ApiGetRemoteTheme(c *gin.Context) {
r, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
r, _, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid"))
if r == nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)})
return

View File

@ -55,6 +55,16 @@ func GetTheme(id int64) (*Theme, error) {
return t, nil
}
// GetTheme retrieves a Theme from an given Exercice.
func (e *Exercice) GetTheme() (*Theme, error) {
t := &Theme{}
if err := DBQueryRow("SELECT id_theme, name, url_id, path, authors, intro, headline, image, partner_img, partner_href, partner_text FROM themes WHERE id_theme=?", e.IdTheme).Scan(&t.Id, &t.Name, &t.URLId, &t.Path, &t.Authors, &t.Intro, &t.Headline, &t.Image, &t.PartnerImage, &t.PartnerLink, &t.PartnerText); err != nil {
return t, err
}
return t, nil
}
// GetThemeByName retrieves a Theme from its title
func GetThemeByName(name string) (*Theme, error) {
t := &Theme{}

View File

@ -4,10 +4,11 @@ import (
"fmt"
"path"
"srs.epita.fr/fic-server/admin/sync"
"srs.epita.fr/fic-server/libfic"
)
func EPITACheckFile(file *fic.EFile) (errs []error) {
func EPITACheckFile(file *fic.EFile, exceptions *sync.CheckExceptions) (errs []error) {
// Enforce file format
if path.Ext(file.Name) == "rar" || path.Ext(file.Name) == "7z" {
errs = append(errs, fmt.Errorf("this file use a forbidden archive type."))

View File

@ -5,6 +5,7 @@ import (
"strings"
"unicode"
"srs.epita.fr/fic-server/admin/sync"
"srs.epita.fr/fic-server/libfic"
)

View File

@ -26,10 +26,11 @@ func (e SpellingError) Error() string {
}
return fmt.Sprintf(
"%sspelling error %s %s\n%q\n%s%s",
"%sspelling error %s %q (:spelling:%s)\n%q\n%s%s",
e.Prefix,
e.Type,
e.Value,
e.Value,
e.Source,
underline(1, e.Start, e.End),
suggestions,
@ -61,10 +62,10 @@ func (e GrammarError) Error() string {
}
return fmt.Sprintf(
"%s%s (%s)\n%q\n%s%s",
"%s%s (%d:%s)\n%q\n%s%s",
e.Prefix,
e.Message,
e.RuleId,
e.NSource, e.RuleId,
e.Source,
underline(1, e.Start, e.End),
suggestions,

View File

@ -1,36 +1,37 @@
package main
import (
"srs.epita.fr/fic-server/admin/sync"
"srs.epita.fr/fic-server/libfic"
)
func GrammalecteCheckKeyFlag(flag *fic.FlagKey, raw string) (errs []error) {
func GrammalecteCheckKeyFlag(flag *fic.FlagKey, raw string, exceptions *sync.CheckExceptions) (errs []error) {
label, _, _, _ := flag.AnalyzeFlagLabel()
errs = append(errs, grammalecte("label ", label, &CommonOpts)...)
errs = append(errs, grammalecte("label ", label, exceptions, &CommonOpts)...)
if len(flag.Help) > 0 {
errs = append(errs, grammalecte("help ", flag.Help, &CommonOpts)...)
errs = append(errs, grammalecte("help ", flag.Help, exceptions, &CommonOpts)...)
}
return
}
func GrammalecteCheckFlagChoice(choice *fic.FlagChoice) (errs []error) {
errs = append(errs, grammalecte("label ", choice.Label, &CommonOpts)...)
func GrammalecteCheckFlagChoice(choice *fic.FlagChoice, exceptions *sync.CheckExceptions) (errs []error) {
errs = append(errs, grammalecte("label ", choice.Label, exceptions, &CommonOpts)...)
return
}
func GrammalecteCheckHint(hint *fic.EHint) (errs []error) {
func GrammalecteCheckHint(hint *fic.EHint, exceptions *sync.CheckExceptions) (errs []error) {
if len(hint.Title) > 0 {
errs = append(errs, grammalecte("title ", hint.Title, &CommonOpts)...)
errs = append(errs, grammalecte("title ", hint.Title, exceptions, &CommonOpts)...)
}
return
}
func GrammalecteCheckMDText(str string) (errs []error) {
errs = append(errs, grammalecte("", str, &CommonOpts)...)
func GrammalecteCheckMDText(str string, exceptions *sync.CheckExceptions) (errs []error) {
errs = append(errs, grammalecte("", str, exceptions, &CommonOpts)...)
return
}

View File

@ -2,11 +2,14 @@ package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"srs.epita.fr/fic-server/admin/sync"
)
type GrammalecteOptions struct {
@ -115,7 +118,7 @@ var (
mdimg = regexp.MustCompile(`!\[([^\]]+)\]\([^)]+\)`)
)
func grammalecte(name string, text string, options *GrammalecteOptions) (errs []error) {
func grammalecte(name string, text string, exceptions *sync.CheckExceptions, options *GrammalecteOptions) (errs []error) {
// Remove Markdown elements
text = mdimg.ReplaceAllString(text, "Image : ${1}")
@ -161,7 +164,7 @@ func grammalecte(name string, text string, options *GrammalecteOptions) (errs []
break
}
}
if allowed {
if allowed || exceptions.HasException(":spelling:"+serror.Value) {
continue
}
@ -183,6 +186,10 @@ func grammalecte(name string, text string, options *GrammalecteOptions) (errs []
continue
}
if exceptions.HasException(fmt.Sprintf(":%d:%s", data.Paragraph, gerror.RuleId)) {
continue
}
errs = append(errs, GrammarError{
Prefix: name,
Source: data.Text,

View File

@ -108,8 +108,8 @@ func searchBinaryInGit(edir string) (ret []string) {
return
}
func checkExercice(theme *fic.Theme, edir string, dmap *map[int64]*fic.Exercice) (errs []error) {
e, _, eid, _, berrs := sync.BuildExercice(sync.GlobalImporter, theme, path.Join(theme.Path, edir), dmap)
func checkExercice(theme *fic.Theme, edir string, dmap *map[int64]*fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
e, _, eid, _, berrs := sync.BuildExercice(sync.GlobalImporter, theme, path.Join(theme.Path, edir), dmap, exceptions)
errs = append(errs, berrs...)
if e != nil {
@ -117,7 +117,7 @@ func checkExercice(theme *fic.Theme, edir string, dmap *map[int64]*fic.Exercice)
var files []string
var cerrs []error
if !skipFileChecks {
files, cerrs = sync.CheckExerciceFiles(sync.GlobalImporter, e)
files, cerrs = sync.CheckExerciceFiles(sync.GlobalImporter, e, exceptions)
log.Printf("%d files checked.\n", len(files))
} else {
files, cerrs = sync.CheckExerciceFilesPresence(sync.GlobalImporter, e)
@ -126,12 +126,12 @@ func checkExercice(theme *fic.Theme, edir string, dmap *map[int64]*fic.Exercice)
errs = append(errs, cerrs...)
// Flags
flags, cerrs := sync.CheckExerciceFlags(sync.GlobalImporter, e, files)
flags, cerrs := sync.CheckExerciceFlags(sync.GlobalImporter, e, files, exceptions)
errs = append(errs, cerrs...)
log.Printf("%d flags checked.\n", len(flags))
// Hints
hints, cerrs := sync.CheckExerciceHints(sync.GlobalImporter, e)
hints, cerrs := sync.CheckExerciceHints(sync.GlobalImporter, e, exceptions)
errs = append(errs, cerrs...)
log.Printf("%d hints checked.\n", len(hints))
@ -242,7 +242,7 @@ func main() {
}
nberr := 0
theme, errs := sync.BuildTheme(sync.GlobalImporter, p)
theme, exceptions, errs := sync.BuildTheme(sync.GlobalImporter, p)
if theme != nil {
nberr += len(errs)
@ -260,7 +260,7 @@ func main() {
dmap := map[int64]*fic.Exercice{}
for _, edir := range exercices {
for _, err := range checkExercice(theme, edir, &dmap) {
for _, err := range checkExercice(theme, edir, &dmap, exceptions) {
log.Println(err.Error())
if logMissingResolution {
@ -290,7 +290,7 @@ func main() {
} else {
log.Printf("This is not a theme directory, run checks for exercice.\n\n")
for _, err := range checkExercice(&fic.Theme{}, p, &map[int64]*fic.Exercice{}) {
for _, err := range checkExercice(&fic.Theme{}, p, &map[int64]*fic.Exercice{}, exceptions) {
nberr += 1
log.Println(err)
}