sync: Replace []error by go.uber.org/multierr
This commit is contained in:
parent
9f49a689fd
commit
b6966d47ce
@ -13,10 +13,11 @@ import (
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/multierr"
|
||||
)
|
||||
|
||||
func flatifySyncErrors(errs []error) (ret []string) {
|
||||
for _, err := range errs {
|
||||
func flatifySyncErrors(errs error) (ret []string) {
|
||||
for _, err := range multierr.Errors(errs) {
|
||||
ret = append(ret, err.Error())
|
||||
}
|
||||
return
|
||||
@ -62,7 +63,7 @@ func declareSyncRoutes(router *gin.RouterGroup) {
|
||||
exceptions := sync.LoadThemeException(sync.GlobalImporter, theme)
|
||||
|
||||
var st []string
|
||||
for _, se := range sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250, exceptions) {
|
||||
for _, se := range multierr.Errors(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)
|
||||
@ -195,7 +196,7 @@ func declareSyncExercicesRoutes(router *gin.RouterGroup) {
|
||||
exceptions := sync.LoadExerciceException(sync.GlobalImporter, theme, exercice, nil)
|
||||
_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice, exceptions)
|
||||
_, herrs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice), exceptions)
|
||||
c.JSON(http.StatusOK, flatifySyncErrors(append(errs, herrs...)))
|
||||
c.JSON(http.StatusOK, flatifySyncErrors(multierr.Append(errs, herrs)))
|
||||
})
|
||||
}
|
||||
|
||||
@ -286,7 +287,7 @@ func autoSync(c *gin.Context) {
|
||||
exceptions := sync.LoadThemeException(sync.GlobalImporter, theTheme)
|
||||
|
||||
var st []string
|
||||
for _, se := range sync.SyncThemeDeep(sync.GlobalImporter, theTheme, 0, 250, exceptions) {
|
||||
for _, se := range multierr.Errors(sync.SyncThemeDeep(sync.GlobalImporter, theTheme, 0, 250, exceptions)) {
|
||||
st = append(st, se.Error())
|
||||
}
|
||||
sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]string{theTheme.Name: st}}, false)
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
@ -130,12 +132,12 @@ func parseExerciceParams(i Importer, exPath string) (p ExerciceParams, md toml.M
|
||||
}
|
||||
|
||||
// getExerciceParams returns normalized
|
||||
func getExerciceParams(i Importer, exercice *fic.Exercice) (params ExerciceParams, errs []error) {
|
||||
func getExerciceParams(i Importer, exercice *fic.Exercice) (params ExerciceParams, errs error) {
|
||||
var err error
|
||||
if params, _, err = parseExerciceParams(i, exercice.Path); err != nil {
|
||||
errs = append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
} else if len(params.Flags) == 0 && len(params.FlagsUCQ) == 0 && len(params.FlagsMCQ) == 0 {
|
||||
errs = append(errs, NewChallengeTxtError(exercice, 0, fmt.Errorf("has no flag")))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, fmt.Errorf("has no flag")))
|
||||
} else {
|
||||
// Treat legacy UCQ flags as ExerciceFlag
|
||||
for _, flag := range params.FlagsUCQ {
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
@ -50,7 +51,7 @@ func isURLAllowed(in string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files []string, digests map[string][]byte, errs []error) {
|
||||
func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files []string, digests map[string][]byte, errs error) {
|
||||
// If no files directory, don't display error
|
||||
if !i.Exists(path.Join(exercice.Path, into)) {
|
||||
return
|
||||
@ -58,15 +59,15 @@ func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files
|
||||
|
||||
// Parse DIGESTS.txt
|
||||
if digs, err := GetFileContent(i, path.Join(exercice.Path, into, "DIGESTS.txt")); err != nil {
|
||||
errs = append(errs, NewExerciceError(exercice, fmt.Errorf("unable to read %s: %w", path.Join(into, "DIGESTS.txt"), err)))
|
||||
errs = multierr.Append(errs, NewExerciceError(exercice, fmt.Errorf("unable to read %s: %w", path.Join(into, "DIGESTS.txt"), err)))
|
||||
} else {
|
||||
digests = map[string][]byte{}
|
||||
for nline, d := range strings.Split(digs, "\n") {
|
||||
if dsplt := strings.SplitN(d, " ", 2); len(dsplt) < 2 {
|
||||
errs = append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: invalid format", path.Join(into, "DIGESTS.txt"), nline+1)))
|
||||
errs = multierr.Append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: invalid format", path.Join(into, "DIGESTS.txt"), nline+1)))
|
||||
continue
|
||||
} else if hash, err := hex.DecodeString(dsplt[0]); err != nil {
|
||||
errs = append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: %w", path.Join(into, "DIGESTS.txt"), nline+1, err)))
|
||||
errs = multierr.Append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: %w", path.Join(into, "DIGESTS.txt"), nline+1, err)))
|
||||
continue
|
||||
} else {
|
||||
digests[strings.TrimFunc(dsplt[1], unicode.IsSpace)] = hash
|
||||
@ -76,7 +77,7 @@ func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files
|
||||
|
||||
// Read file list
|
||||
if flist, err := i.listDir(path.Join(exercice.Path, into)); err != nil {
|
||||
errs = append(errs, NewExerciceError(exercice, err))
|
||||
errs = multierr.Append(errs, NewExerciceError(exercice, err))
|
||||
} else {
|
||||
for _, fname := range flist {
|
||||
if fname == "DIGESTS.txt" || fname == ".gitattributes" {
|
||||
@ -128,9 +129,9 @@ func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files
|
||||
}
|
||||
|
||||
// CheckExerciceFilesPresence limits remote checks to presence, don't get it to check digest.
|
||||
func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []string, errs []error) {
|
||||
func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []string, errs error) {
|
||||
flist, digests, berrs := BuildFilesListInto(i, exercice, "files")
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
paramsFiles, _ := GetExerciceFilesParams(i, exercice)
|
||||
|
||||
@ -138,28 +139,28 @@ func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []str
|
||||
if !i.Exists(path.Join(exercice.Path, "files", fname)) && !i.Exists(path.Join(exercice.Path, "files", fname+".00")) {
|
||||
// File not found locally, is this a remote file?
|
||||
if pf, exists := paramsFiles[fname]; !exists || pf.URL == "" {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("No such file or directory")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("No such file or directory")))
|
||||
continue
|
||||
} else if !isURLAllowed(pf.URL) {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("URL hostname is not whitelisted")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("URL hostname is not whitelisted")))
|
||||
continue
|
||||
} else {
|
||||
resp, err := http.Head(pf.URL)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("Unexpected status code for the HTTP response: %d %s", resp.StatusCode, resp.Status)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("Unexpected status code for the HTTP response: %d %s", resp.StatusCode, resp.Status)))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := digests[fname]; !ok {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to import file: No digest given")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to import file: No digest given")))
|
||||
} else {
|
||||
files = append(files, fname)
|
||||
}
|
||||
@ -169,7 +170,7 @@ func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []str
|
||||
if !i.Exists(path.Join(exercice.Path, "files", fname)) && !i.Exists(path.Join(exercice.Path, "files", fname+".gz")) && !i.Exists(path.Join(exercice.Path, "files", fname+".00")) && !i.Exists(path.Join(exercice.Path, "files", fname+".gz.00")) {
|
||||
if pf, exists := paramsFiles[fname]; !exists || pf.URL == "" {
|
||||
if pf, exists := paramsFiles[fname+".gz"]; !exists || pf.URL == "" {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: No such file or directory. Check your DIGESTS.txt for legacy entries.")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: No such file or directory. Check your DIGESTS.txt for legacy entries.")))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -179,13 +180,13 @@ 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, exceptions *CheckExceptions) (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...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
paramsFiles, err := GetExerciceFilesParams(i, exercice)
|
||||
if err != nil {
|
||||
errs = append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
}
|
||||
|
||||
for _, fname := range flist {
|
||||
@ -193,14 +194,14 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
|
||||
if pf, exists := paramsFiles[fname]; exists && pf.URL != "" {
|
||||
if li, ok := i.(LocalImporter); ok {
|
||||
errs = append(errs, downloadExerciceFile(paramsFiles[fname], li.GetLocalPath(dest), exercice, false)...)
|
||||
errs = multierr.Append(errs, downloadExerciceFile(paramsFiles[fname], li.GetLocalPath(dest), exercice, false))
|
||||
} else {
|
||||
errs = append(errs, downloadExerciceFile(paramsFiles[fname], dest, exercice, false)...)
|
||||
errs = multierr.Append(errs, downloadExerciceFile(paramsFiles[fname], dest, exercice, false))
|
||||
}
|
||||
}
|
||||
|
||||
if fd, closer, err := GetFile(i, dest); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: %w", err)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: %w", err)))
|
||||
continue
|
||||
} else {
|
||||
defer closer()
|
||||
@ -208,9 +209,9 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
hash160, hash512 := fic.CreateHashBuffers(fd)
|
||||
|
||||
if _, err := fic.CheckBufferHash(hash160, hash512, digests[fname]); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
} else if size, err := GetFileSize(i, path.Join(exercice.Path, "files", fname)); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
} else {
|
||||
var digest_shown []byte
|
||||
if strings.HasSuffix(fname, ".gz") {
|
||||
@ -219,11 +220,11 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
|
||||
// Check that gunzipped file digest is correct
|
||||
if fd, closer, err := GetFile(i, path.Join(exercice.Path, "files", fname)); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: %w", err)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: %w", err)))
|
||||
continue
|
||||
} else if gunzipfd, err := gzip.NewReader(fd); err != nil {
|
||||
closer()
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to gunzip file: %w", err)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to gunzip file: %w", err)))
|
||||
continue
|
||||
} else {
|
||||
defer gunzipfd.Close()
|
||||
@ -232,7 +233,7 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
hash160_inflate, hash512_inflate := fic.CreateHashBuffers(gunzipfd)
|
||||
|
||||
if _, err := fic.CheckBufferHash(hash160_inflate, hash512_inflate, digest_shown); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, strings.TrimSuffix(fname, ".gz"), err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, strings.TrimSuffix(fname, ".gz"), err))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -242,13 +243,13 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
if f, exists := paramsFiles[fname]; exists {
|
||||
// Call checks hooks
|
||||
for _, hk := range hooks.mdTextHooks {
|
||||
for _, err := range hk(f.Disclaimer, exercice.Language, exceptions) {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
for _, err := range multierr.Errors(hk(f.Disclaimer, exercice.Language, exceptions)) {
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
}
|
||||
}
|
||||
|
||||
if disclaimer, err = ProcessMarkdown(i, fixnbsp(f.Disclaimer), exercice.Path); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,8 +257,8 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.fileHooks {
|
||||
for _, e := range h(file, exercice, exceptions) {
|
||||
errs = append(errs, NewFileError(exercice, fname, e))
|
||||
for _, e := range multierr.Errors(h(file, exercice, exceptions)) {
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, e))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -269,7 +270,7 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
}
|
||||
|
||||
// downloadExerciceFile is responsible to fetch remote files.
|
||||
func downloadExerciceFile(pf ExerciceFile, dest string, exercice *fic.Exercice, force bool) (errs []error) {
|
||||
func downloadExerciceFile(pf ExerciceFile, dest string, exercice *fic.Exercice, force bool) (errs error) {
|
||||
if st, err := os.Stat(dest); !force && !os.IsNotExist(err) {
|
||||
resp, err := http.Head(pf.URL)
|
||||
if err == nil && resp.ContentLength == st.Size() {
|
||||
@ -278,33 +279,35 @@ func downloadExerciceFile(pf ExerciceFile, dest string, exercice *fic.Exercice,
|
||||
}
|
||||
|
||||
if !isURLAllowed(pf.URL) {
|
||||
errs = append(errs, NewFileError(exercice, path.Base(dest), fmt.Errorf("URL hostname is not whitelisted")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), fmt.Errorf("URL hostname is not whitelisted")))
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Download exercice file: ", pf.URL)
|
||||
|
||||
resp, err := http.Get(pf.URL)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err = os.MkdirAll(path.Dir(dest), 0751); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
return
|
||||
}
|
||||
|
||||
// Write file
|
||||
var fdto *os.File
|
||||
if fdto, err = os.Create(dest); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
return
|
||||
} else {
|
||||
defer fdto.Close()
|
||||
|
||||
_, err = io.Copy(fdto, resp.Body)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -314,19 +317,19 @@ func downloadExerciceFile(pf ExerciceFile, dest string, exercice *fic.Exercice,
|
||||
|
||||
// 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, exceptions *CheckExceptions) (errs []error) {
|
||||
func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (errs error) {
|
||||
if _, err := exercice.WipeFiles(); err != nil {
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
}
|
||||
|
||||
paramsFiles, err := GetExerciceFilesParams(i, exercice)
|
||||
if err != nil {
|
||||
errs = append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
return
|
||||
}
|
||||
|
||||
files, digests, berrs := BuildFilesListInto(i, exercice, "files")
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
// Import standard files
|
||||
for _, fname := range files {
|
||||
@ -345,13 +348,13 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
||||
|
||||
// Call checks hooks
|
||||
for _, hk := range hooks.mdTextHooks {
|
||||
for _, err := range hk(f.Disclaimer, exercice.Language, exceptions) {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
for _, err := range multierr.Errors(hk(f.Disclaimer, exercice.Language, exceptions)) {
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
}
|
||||
}
|
||||
|
||||
if disclaimer, err = ProcessMarkdown(i, fixnbsp(f.Disclaimer), exercice.Path); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,7 +373,7 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
||||
}
|
||||
|
||||
if f == nil {
|
||||
errs = append(errs, downloadExerciceFile(paramsFiles[fname], dest, exercice, false)...)
|
||||
errs = multierr.Append(errs, downloadExerciceFile(paramsFiles[fname], dest, exercice, false))
|
||||
|
||||
f, err = actionAfterImport(dest, pf.URL)
|
||||
}
|
||||
@ -379,19 +382,19 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
continue
|
||||
}
|
||||
|
||||
if f.(*fic.EFile).Size == 0 {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("imported file is empty!")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("imported file is empty!")))
|
||||
} else {
|
||||
file := f.(*fic.EFile)
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.fileHooks {
|
||||
for _, e := range h(file, exercice, exceptions) {
|
||||
errs = append(errs, NewFileError(exercice, fname, e))
|
||||
for _, e := range multierr.Errors(h(file, exercice, exceptions)) {
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, e))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/multierr"
|
||||
_ "golang.org/x/crypto/blake2b"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
@ -23,10 +24,10 @@ type importHint struct {
|
||||
FlagsDeps []int64
|
||||
}
|
||||
|
||||
func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (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))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -45,10 +46,10 @@ func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
|
||||
if hint.Filename != "" {
|
||||
if hint.Content != "" {
|
||||
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be filled at the same time")))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be filled at the same time")))
|
||||
continue
|
||||
} else if !i.Exists(path.Join(exercice.Path, "hints", hint.Filename)) {
|
||||
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: File not found", hint.Filename)))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: File not found", hint.Filename)))
|
||||
continue
|
||||
} else {
|
||||
// Handle files as downloadable content
|
||||
@ -70,35 +71,35 @@ func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
// Special format for downloadable hints: $FILES + hexhash + path from FILES/
|
||||
return "$FILES" + hex.EncodeToString(result512) + strings.TrimPrefix(filePath, fic.FilesDir), nil
|
||||
}); err != nil {
|
||||
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: %w", hint.Filename, err)))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: %w", hint.Filename, err)))
|
||||
continue
|
||||
} else if s, ok := res.(string); !ok {
|
||||
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: invalid string returned as filename", hint.Filename)))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: invalid string returned as filename", hint.Filename)))
|
||||
continue
|
||||
} else {
|
||||
h.Content = s
|
||||
}
|
||||
}
|
||||
} else if hint.Content == "" {
|
||||
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be empty at the same time")))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be empty at the same time")))
|
||||
continue
|
||||
} else {
|
||||
// Call checks hooks
|
||||
for _, hk := range hooks.mdTextHooks {
|
||||
for _, err := range hk(h.Content, exercice.Language, exceptions) {
|
||||
errs = append(errs, NewHintError(exercice, h, n, err))
|
||||
for _, err := range multierr.Errors(hk(h.Content, exercice.Language, exceptions)) {
|
||||
errs = multierr.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)))
|
||||
errs = multierr.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, exercice, exceptions) {
|
||||
errs = append(errs, NewHintError(exercice, h, n, e))
|
||||
for _, e := range multierr.Errors(hook(h, exercice, exceptions)) {
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, e))
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,28 +120,28 @@ func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
}
|
||||
|
||||
// CheckExerciceHints checks if all hints are corrects..
|
||||
func CheckExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) ([]importHint, []error) {
|
||||
func CheckExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) ([]importHint, error) {
|
||||
exceptions = exceptions.GetFileExceptions("challenge.txt", "challenge.toml")
|
||||
|
||||
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, exceptions *CheckExceptions) (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)
|
||||
errs = multierr.Append(errs, err)
|
||||
} else {
|
||||
exceptions = exceptions.GetFileExceptions("challenge.txt", "challenge.toml")
|
||||
|
||||
hints, berrs := buildExerciceHints(i, exercice, exceptions)
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
hintsBindings = map[int]*fic.EHint{}
|
||||
|
||||
for _, hint := range hints {
|
||||
// Import hint
|
||||
if h, err := exercice.AddHint(hint.Hint.Title, hint.Hint.Content, hint.Hint.Cost); err != nil {
|
||||
errs = append(errs, NewHintError(exercice, hint.Hint, hint.Line, err))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, err))
|
||||
} else {
|
||||
hintsBindings[hint.Line] = h
|
||||
|
||||
@ -148,10 +149,10 @@ func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int
|
||||
for _, nf := range hint.FlagsDeps {
|
||||
if f, ok := flagsBindings[nf]; ok {
|
||||
if herr := h.AddDepend(f); herr != nil {
|
||||
errs = append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: %w", nf, herr)))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: %w", nf, herr)))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: Unexistant flag", nf)))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: Unexistant flag", nf)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
@ -35,11 +36,11 @@ func validatorRegexp(vre string) (validator_regexp *string) {
|
||||
return
|
||||
}
|
||||
|
||||
func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bool, separator string) (raw string, prep string, errs []error) {
|
||||
func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bool, separator string) (raw string, prep string, errs error) {
|
||||
// Concatenate array
|
||||
if f, ok := input.([]interface{}); ok {
|
||||
if len(validatorRe) > 0 {
|
||||
errs = append(errs, fmt.Errorf("ValidatorRe cannot be defined for this kind of flag."))
|
||||
errs = multierr.Append(errs, fmt.Errorf("ValidatorRe cannot be defined for this kind of flag."))
|
||||
validatorRe = ""
|
||||
}
|
||||
|
||||
@ -47,20 +48,20 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
|
||||
separator = ","
|
||||
} else if len(separator) > 1 {
|
||||
separator = string(separator[0])
|
||||
errs = append(errs, fmt.Errorf("separator truncated to %q", separator))
|
||||
errs = multierr.Append(errs, fmt.Errorf("separator truncated to %q", separator))
|
||||
}
|
||||
|
||||
var fitems []string
|
||||
for i, v := range f {
|
||||
if g, ok := v.(string); ok {
|
||||
if strings.Index(g, separator) != -1 {
|
||||
errs = append(errs, fmt.Errorf("flag items cannot contain %q character as it is used as separator. Change the separator attribute for this flag.", separator))
|
||||
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))
|
||||
return
|
||||
} else {
|
||||
fitems = append(fitems, g)
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("item %d has an invalid type: can only be string, is %T.", i, g))
|
||||
errs = multierr.Append(errs, fmt.Errorf("item %d has an invalid type: can only be string, is %T.", i, g))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -77,7 +78,7 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
|
||||
nbLines := 0
|
||||
if showLines {
|
||||
if len(fitems) > 9 {
|
||||
errs = append(errs, fmt.Errorf("too much items in vector to use ShowLines features, max 9."))
|
||||
errs = multierr.Append(errs, fmt.Errorf("too much items in vector to use ShowLines features, max 9."))
|
||||
} else {
|
||||
nbLines = len(fitems)
|
||||
}
|
||||
@ -91,7 +92,7 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
|
||||
} else if f, ok := input.(float64); ok {
|
||||
raw = strconv.FormatFloat(f, 'f', -1, 64)
|
||||
} else if f, ok := input.(string); !ok {
|
||||
errs = append(errs, fmt.Errorf("has an invalid type: can only be []string or string, not %T", input))
|
||||
errs = multierr.Append(errs, fmt.Errorf("has an invalid type: can only be []string or string, not %T", input))
|
||||
return
|
||||
} else {
|
||||
raw = f
|
||||
@ -99,21 +100,21 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
|
||||
return
|
||||
}
|
||||
|
||||
func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, exceptions *CheckExceptions) (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.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label cannot be empty.")))
|
||||
return
|
||||
}
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(flag.Label, exercice.Language, exceptions.Filter2ndCol(strconv.Itoa(flagline))) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, err))
|
||||
for _, err := range multierr.Errors(h(flag.Label, exercice.Language, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, err))
|
||||
}
|
||||
}
|
||||
|
||||
if mdlabel, err := ProcessMarkdown(GlobalImporter, flag.Label, exercice.Path); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("unable to parse property label as Markdown: %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("unable to parse property label as Markdown: %w", err)))
|
||||
} else {
|
||||
if strings.Count(flag.Label, "\n\n") == 0 {
|
||||
flag.Label = mdlabel[3 : len(mdlabel)-4]
|
||||
@ -123,11 +124,11 @@ func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, exc
|
||||
}
|
||||
|
||||
if flag.Raw != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("raw cannot be defined.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("raw cannot be defined.")))
|
||||
}
|
||||
|
||||
if len(flag.Choice) != 0 {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("choices cannot be defined.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("choices cannot be defined.")))
|
||||
}
|
||||
|
||||
f = &fic.FlagLabel{
|
||||
@ -138,32 +139,33 @@ func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, exc
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.flagLabelHooks {
|
||||
for _, e := range h(f, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline))) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
for _, e := range multierr.Errors(h(f, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defaultLabel string, exceptions *CheckExceptions) (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
|
||||
}
|
||||
|
||||
if len(flag.Variant) != 0 {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("variant is not defined for this kind of flag.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("variant is not defined for this kind of flag.")))
|
||||
}
|
||||
|
||||
if flag.Label[0] == '`' {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label should not begin with `.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label should not begin with `.")))
|
||||
flag.Label = flag.Label[1:]
|
||||
}
|
||||
|
||||
raw, prep, terrs := getRawKey(flag.Raw, flag.CaptureRe, flag.Ordered, flag.ShowLines, flag.Separator)
|
||||
|
||||
if len(terrs) > 0 {
|
||||
for _, terr := range terrs {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, terr))
|
||||
errors := multierr.Errors(terrs)
|
||||
if len(errors) > 0 {
|
||||
for _, terr := range errors {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, terr))
|
||||
}
|
||||
f = nil
|
||||
return
|
||||
@ -171,16 +173,16 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
||||
flag.Label = prep + flag.Label
|
||||
|
||||
if len(flag.Label) > 255 && flag.Type != "label" {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("label is too long (max 255 chars per label).")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("label is too long (max 255 chars per label).")))
|
||||
}
|
||||
|
||||
if (flag.Type == "text" && !isFullGraphic(strings.Replace(raw, "\n", "", -1))) || (flag.Type != "text" && !isFullGraphic(raw)) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("WARNING non-printable characters in flag, is this really expected?")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("WARNING non-printable characters in flag, is this really expected?")))
|
||||
}
|
||||
|
||||
hashedFlag, err := fic.ComputeHashedFlag([]byte(raw), !flag.CaseSensitive, flag.NoTrim, validatorRegexp(flag.CaptureRe), flag.SortReGroups)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, err))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, err))
|
||||
return
|
||||
}
|
||||
fk := &fic.FlagKey{
|
||||
@ -202,8 +204,8 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.flagKeyHooks {
|
||||
for _, e := range h(fk, raw, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline))) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
for _, e := range multierr.Errors(h(fk, raw, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,9 +224,10 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
||||
|
||||
for _, choice := range flag.Choice {
|
||||
val, prep, terrs := getRawKey(choice.Value, "", false, false, "")
|
||||
if len(terrs) > 0 {
|
||||
for _, terr := range terrs {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, terr))
|
||||
errors := multierr.Errors(terrs)
|
||||
if len(errors) > 0 {
|
||||
for _, terr := range errors {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, terr))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -245,15 +248,15 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.flagChoiceHooks {
|
||||
for _, e := range h(fc, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline))) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
for _, e := range multierr.Errors(h(fc, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
}
|
||||
}
|
||||
|
||||
choices = append(choices, fc)
|
||||
|
||||
if val == "true" || val == "false" {
|
||||
errs = 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)))
|
||||
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)))
|
||||
}
|
||||
|
||||
if val == raw || (!flag.CaseSensitive && val == strings.ToLower(raw)) {
|
||||
@ -261,13 +264,13 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
||||
}
|
||||
}
|
||||
if !hasOne {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("no valid answer defined.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("no valid answer defined.")))
|
||||
}
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.flagKeyWithChoicesHooks {
|
||||
for _, e := range h(fk, raw, choices, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline))) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
for _, e := range multierr.Errors(h(fk, raw, choices, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -297,7 +300,7 @@ func iface2Number(input interface{}, output *string) error {
|
||||
}
|
||||
|
||||
// buildExerciceFlag read challenge.txt and extract all flags.
|
||||
func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nline int, exceptions *CheckExceptions) (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"
|
||||
@ -309,17 +312,17 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
||||
var smin, smax, sstep string
|
||||
err := iface2Number(flag.NumberMin, &smin)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("min %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("min %w", err)))
|
||||
}
|
||||
|
||||
err = iface2Number(flag.NumberMax, &smax)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("max %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("max %w", err)))
|
||||
}
|
||||
|
||||
err = iface2Number(flag.NumberStep, &sstep)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("step %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("step %w", err)))
|
||||
}
|
||||
flag.Type = fmt.Sprintf("number,%s,%s,%s", smin, smax, sstep)
|
||||
case "text":
|
||||
@ -333,23 +336,23 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
||||
case "mcq":
|
||||
flag.Type = "mcq"
|
||||
default:
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("invalid type of flag: should be 'key', 'number', 'text', 'mcq', 'ucq', 'radio' or 'vector'")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("invalid type of flag: should be 'key', 'number', 'text', 'mcq', 'ucq', 'radio' or 'vector'")))
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(flag.Type, "number") {
|
||||
if flag.NumberMin != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property min undefined for this kind of flag: should the type be 'number'")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property min undefined for this kind of flag: should the type be 'number'")))
|
||||
} else if flag.NumberMax != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property max undefined for this kind of flag: should the type be 'number'")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property max undefined for this kind of flag: should the type be 'number'")))
|
||||
} else if flag.NumberStep != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property step undefined for this kind of flag: should the type be 'number'")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property step undefined for this kind of flag: should the type be 'number'")))
|
||||
}
|
||||
}
|
||||
|
||||
if len(flag.Help) > 0 {
|
||||
if mdhelp, err := ProcessMarkdown(i, flag.Help, exercice.Path); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("unable to parse property help as Markdown: %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("unable to parse property help as Markdown: %w", err)))
|
||||
} else {
|
||||
flag.Help = mdhelp[3 : len(mdhelp)-4]
|
||||
}
|
||||
@ -357,7 +360,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
||||
|
||||
if flag.Type == "label" {
|
||||
addedFlag, berrs := buildLabelFlag(exercice, flag, nline+1, exceptions)
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
if addedFlag != nil {
|
||||
ret = append(ret, importFlag{
|
||||
Line: nline + 1,
|
||||
@ -366,7 +369,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", exceptions)
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
if addedFlag != nil {
|
||||
ret = append(ret, importFlag{
|
||||
Line: nline + 1,
|
||||
@ -386,7 +389,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
||||
isJustified := false
|
||||
|
||||
if len(flag.Variant) != 0 {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("variant is not defined for this kind of flag")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("variant is not defined for this kind of flag")))
|
||||
}
|
||||
|
||||
if !flag.NoShuffle {
|
||||
@ -400,7 +403,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
||||
|
||||
if choice.Raw != nil {
|
||||
if hasOne && !isJustified {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
|
||||
continue
|
||||
}
|
||||
|
||||
@ -409,13 +412,13 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
||||
} else if p, ok := choice.Value.(bool); ok {
|
||||
val = p
|
||||
if isJustified {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
|
||||
continue
|
||||
}
|
||||
} else if choice.Value == nil {
|
||||
val = false
|
||||
} else {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("choice %d: incorrect type for value: %T is not boolean.", cid, choice.Value)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("choice %d: incorrect type for value: %T is not boolean.", cid, choice.Value)))
|
||||
continue
|
||||
}
|
||||
|
||||
@ -427,9 +430,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", exceptions)
|
||||
if len(berrs) > 0 {
|
||||
errs = append(errs, berrs...)
|
||||
}
|
||||
errs = multierr.Append(errs, berrs)
|
||||
if addedFlag != nil {
|
||||
ret = append(ret, importFlag{
|
||||
Line: nline + 1,
|
||||
@ -443,8 +444,8 @@ 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, exercice, exceptions) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, e))
|
||||
for _, e := range multierr.Errors(h(&addedFlag, addedFlag.Entries, exercice, exceptions)) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, e))
|
||||
}
|
||||
}
|
||||
|
||||
@ -457,9 +458,9 @@ 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, exceptions *CheckExceptions) (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 {
|
||||
if len(multierr.Errors(gerrs)) > 0 {
|
||||
return flags, flagids, gerrs
|
||||
}
|
||||
|
||||
@ -473,14 +474,12 @@ func buildExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
|
||||
// Ensure flag ID is unique
|
||||
for _, ok := flags[flag.Id]; ok; _, ok = flags[flag.Id] {
|
||||
errs = append(errs, NewFlagError(exercice, ¶ms.Flags[nline], nline+1, fmt.Errorf("identifier already used (%d), using a random one.", flag.Id)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, ¶ms.Flags[nline], nline+1, fmt.Errorf("identifier already used (%d), using a random one.", flag.Id)))
|
||||
flag.Id = rand.Int63()
|
||||
}
|
||||
|
||||
newFlags, ferrs := buildExerciceFlag(i, exercice, flag, nline, exceptions)
|
||||
if len(ferrs) > 0 {
|
||||
errs = append(errs, ferrs...)
|
||||
}
|
||||
errs = multierr.Append(errs, ferrs)
|
||||
if len(newFlags) > 0 {
|
||||
for _, newFlag := range newFlags {
|
||||
fId := flag.Id
|
||||
@ -491,7 +490,7 @@ func buildExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
// Read dependency to flag
|
||||
for _, nf := range flag.NeedFlag {
|
||||
if len(nf.Theme) > 0 {
|
||||
errs = append(errs, NewFlagError(exercice, ¶ms.Flags[nline], nline+1, fmt.Errorf("dependancy on another scenario is not implemented yet.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, ¶ms.Flags[nline], nline+1, fmt.Errorf("dependancy on another scenario is not implemented yet.")))
|
||||
}
|
||||
newFlag.FlagsDeps = append(newFlag.FlagsDeps, nf.Id)
|
||||
}
|
||||
@ -514,18 +513,18 @@ func buildExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
||||
}
|
||||
|
||||
// CheckExerciceFlags checks if all flags for the given challenge are correct.
|
||||
func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exceptions *CheckExceptions) (rf []fic.Flag, errs []error) {
|
||||
func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exceptions *CheckExceptions) (rf []fic.Flag, errs error) {
|
||||
exceptions = exceptions.GetFileExceptions("challenge.txt", "challenge.toml")
|
||||
|
||||
flags, flagsids, berrs := buildExerciceFlags(i, exercice, exceptions)
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
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, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on flag id=%d: id not defined", nf)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on flag id=%d: id not defined", nf)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -533,7 +532,7 @@ func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exce
|
||||
deps := flag.FlagsDeps
|
||||
for i := 0; i < len(deps); i++ {
|
||||
if deps[i] == flagid {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag dependency loop detected: flag id=%d: depends on itself", flagid)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag dependency loop detected: flag id=%d: depends on itself", flagid)))
|
||||
break
|
||||
}
|
||||
|
||||
@ -558,7 +557,7 @@ func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exce
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on %s: No such file", lf)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on %s: No such file", lf)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -587,16 +586,16 @@ 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, exceptions *CheckExceptions) (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)
|
||||
errs = multierr.Append(errs, err)
|
||||
} else if _, err := exercice.WipeMCQs(); err != nil {
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
} else {
|
||||
exceptions = exceptions.GetFileExceptions("challenge.txt", "challenge.toml")
|
||||
|
||||
flags, flagids, berrs := buildExerciceFlags(i, exercice, exceptions)
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
kmap = map[int64]fic.Flag{}
|
||||
|
||||
@ -610,12 +609,12 @@ func SyncExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
||||
}
|
||||
|
||||
if addedFlag, err := exercice.AddFlag(flag.Flag); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, err))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, 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, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("choice #FIXME: %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("choice #FIXME: %w", err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -625,18 +624,18 @@ func SyncExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
||||
// Import dependency to flag
|
||||
for _, nf := range flag.FlagsDeps {
|
||||
if rf, ok := kmap[nf]; !ok {
|
||||
errs = 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)))
|
||||
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)))
|
||||
} else if err := addedFlag.AddDepend(rf); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to id=%d: %w", nf, err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to id=%d: %w", nf, err)))
|
||||
}
|
||||
}
|
||||
|
||||
// Import dependency to file
|
||||
for _, lf := range flag.FilesDeps {
|
||||
if rf, err := exercice.GetFileByFilename(lf); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
|
||||
} else if err := rf.AddDepend(addedFlag); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yuin/goldmark"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
@ -93,7 +94,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, exceptions_in *CheckExceptions) (e *fic.Exercice, p ExerciceParams, eid int, exceptions *CheckExceptions, edir string, errs []error) {
|
||||
func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice, exceptions_in *CheckExceptions) (e *fic.Exercice, p ExerciceParams, eid int, exceptions *CheckExceptions, edir string, errs error) {
|
||||
e = &fic.Exercice{}
|
||||
|
||||
e.Path = epath
|
||||
@ -104,7 +105,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
if err != nil {
|
||||
// Ignore eid if we are certain this is an exercice directory, eid will be 0
|
||||
if !i.Exists(path.Join(epath, "title.txt")) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to parse exercice directory: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to parse exercice directory: %w", err), theme))
|
||||
return nil, p, eid, exceptions_in, edir, errs
|
||||
}
|
||||
}
|
||||
@ -118,7 +119,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
if language, err := GetFileContent(i, path.Join(epath, "language.txt")); err == nil {
|
||||
language = strings.TrimSpace(language)
|
||||
if strings.Contains(language, "\n") {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("language.txt: Language can't contain new lines"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("language.txt: Language can't contain new lines"), theme))
|
||||
} else {
|
||||
e.Language = language
|
||||
}
|
||||
@ -128,7 +129,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
if myTitle, err := GetFileContent(i, path.Join(epath, "title.txt")); err == nil {
|
||||
myTitle = strings.TrimSpace(myTitle)
|
||||
if strings.Contains(myTitle, "\n") {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("title.txt: Title can't contain new lines"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("title.txt: Title can't contain new lines"), theme))
|
||||
} else {
|
||||
e.Title = myTitle
|
||||
}
|
||||
@ -136,7 +137,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
|
||||
// Character reserved for WIP exercices
|
||||
if len(e.Title) > 0 && e.Title[0] == '%' {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("title can't contain start by '%%'"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("title can't contain start by '%%'"), theme))
|
||||
}
|
||||
|
||||
e.URLId = fic.ToURLid(e.Title)
|
||||
@ -144,7 +145,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
|
||||
if i.Exists(path.Join(epath, "AUTHORS.txt")) {
|
||||
if authors, err := getAuthors(i, epath); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
|
||||
} else {
|
||||
// Format authors
|
||||
e.Authors = strings.Join(authors, ", ")
|
||||
@ -158,13 +159,13 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
e.Headline, err = GetFileContent(i, path.Join(epath, "headline.md"))
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to get exercice's headline: %w", err)))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to get exercice's headline: %w", err)))
|
||||
}
|
||||
if e.Headline != "" {
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(e.Headline, e.Language, exceptions.GetFileExceptions("headline.md", "headline.txt")) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("headline.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(e.Headline, e.Language, exceptions.GetFileExceptions("headline.md", "headline.txt"))) {
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("headline.md: %w", err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -178,14 +179,14 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
err = fmt.Errorf("Unable to find overview.txt nor overview.md")
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.txt: %s", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.txt: %s", err), theme))
|
||||
} else {
|
||||
e.Overview = fixnbsp(e.Overview)
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(e.Overview, e.Language, exceptions.GetFileExceptions("overview.md", "overview.txt")) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(e.Overview, e.Language, exceptions.GetFileExceptions("overview.md", "overview.txt"))) {
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.md: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,14 +194,14 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
if e.Headline == "" {
|
||||
err := goldmark.Convert([]byte(strings.Split(e.Overview, "\n")[0]), &buf)
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating of the headline: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating of the headline: %w", err), theme))
|
||||
} else {
|
||||
e.Headline = string(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
if e.Overview, err = ProcessMarkdown(i, e.Overview, epath); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating: %w", err), theme))
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,17 +213,17 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
err = fmt.Errorf("Unable to find statement.txt nor statement.md")
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("statement.md: %w", err), theme))
|
||||
errs = multierr.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, e.Language, exceptions.GetFileExceptions("statement.md", "statement.txt")) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("statement.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(e.Statement, e.Language, exceptions.GetFileExceptions("statement.md", "statement.txt"))) {
|
||||
errs = multierr.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))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("statement.md: an error occurs during markdown formating: %w", err), theme))
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,17 +233,17 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
e.Finished, err = GetFileContent(i, path.Join(epath, "finished.md"))
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("finished.md: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("finished.md: %w", err), theme))
|
||||
} else if len(e.Finished) > 0 {
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(e.Finished, e.Language, exceptions.GetFileExceptions("finished.md", "finished.txt")) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("finished.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(e.Finished, e.Language, exceptions.GetFileExceptions("finished.md", "finished.txt"))) {
|
||||
errs = multierr.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.md: an error occurs during markdown formating: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("finished.md: an error occurs during markdown formating: %w", err), theme))
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,31 +252,31 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
} else if i.Exists(path.Join(epath, "heading.png")) {
|
||||
e.Image = path.Join(epath, "heading.png")
|
||||
} else if theme.Image == "" {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("heading.jpg: No such file")))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("heading.jpg: No such file")))
|
||||
}
|
||||
|
||||
// Parse challenge.txt
|
||||
var md toml.MetaData
|
||||
p, md, err = parseExerciceParams(i, epath)
|
||||
if err != nil {
|
||||
errs = append(errs, NewChallengeTxtError(e, 0, err, theme))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(e, 0, err, theme))
|
||||
return
|
||||
}
|
||||
|
||||
// Alert about unknown keys in challenge.txt
|
||||
if len(md.Undecoded()) > 0 {
|
||||
for _, k := range md.Undecoded() {
|
||||
errs = append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("unknown key %q found, check https://fic.srs.epita.fr/doc/files/challenge/", k), theme))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("unknown key %q found, check https://fic.srs.epita.fr/doc/files/challenge/", k), theme))
|
||||
}
|
||||
}
|
||||
|
||||
e.WIP = p.WIP
|
||||
if p.WIP && !AllowWIPExercice {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("exercice declared Work In Progress in challenge.txt"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("exercice declared Work In Progress in challenge.txt"), theme))
|
||||
}
|
||||
|
||||
if p.Gain == 0 {
|
||||
errs = append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("Undefined gain for challenge"), theme))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("Undefined gain for challenge"), theme))
|
||||
} else {
|
||||
e.Gain = p.Gain
|
||||
}
|
||||
@ -283,11 +284,11 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
// Handle dependency
|
||||
if len(p.Dependencies) > 0 {
|
||||
if len(p.Dependencies[0].Theme) > 0 && p.Dependencies[0].Theme != theme.Name {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to treat dependency to another theme (%q): not implemented.", p.Dependencies[0].Theme), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to treat dependency to another theme (%q): not implemented.", p.Dependencies[0].Theme), theme))
|
||||
} else {
|
||||
if dmap == nil {
|
||||
if dmap2, err := buildDependancyMap(i, theme); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to build dependency map: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to build dependency map: %w", err), theme))
|
||||
} else {
|
||||
dmap = &dmap2
|
||||
}
|
||||
@ -305,7 +306,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
for k, _ := range *dmap {
|
||||
dmap_keys = append(dmap_keys, fmt.Sprintf("%d", k))
|
||||
}
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("Unable to find required exercice dependancy %d (available at time of processing: %s)", p.Dependencies[0].Id, strings.Join(dmap_keys, ",")), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("Unable to find required exercice dependancy %d (available at time of processing: %s)", p.Dependencies[0].Id, strings.Join(dmap_keys, ",")), theme))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -318,10 +319,10 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
if !i.Exists(e.VideoURI) {
|
||||
e.VideoURI = ""
|
||||
} else if size, err := GetFileSize(i, e.VideoURI); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: %w", err), theme))
|
||||
e.VideoURI = ""
|
||||
} else if size == 0 {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: The file is empty!"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: The file is empty!"), theme))
|
||||
e.VideoURI = ""
|
||||
} else {
|
||||
e.VideoURI = strings.Replace(url.PathEscape(path.Join("$RFILES$", e.VideoURI)), "%2F", "/", -1)
|
||||
@ -335,21 +336,21 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
|
||||
if i.Exists(writeup) {
|
||||
if size, err := GetFileSize(i, writeup); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
|
||||
} else if size == 0 {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: The file is empty!"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: The file is empty!"), theme))
|
||||
} else if e.Resolution, err = GetFileContent(i, writeup); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
|
||||
} else {
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(e.Resolution, e.Language, exceptions.GetFileExceptions("resolution.md"), p.GetRawFlags()...) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(e.Resolution, e.Language, exceptions.GetFileExceptions("resolution.md"), p.GetRawFlags()...)) {
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
if e.Resolution, err = ProcessMarkdown(i, e.Resolution, epath); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: error during markdown processing: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: error during markdown processing: %w", err), theme))
|
||||
} else {
|
||||
resolutionFound = true
|
||||
}
|
||||
@ -357,28 +358,26 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
||||
}
|
||||
|
||||
if !resolutionFound {
|
||||
errs = append(errs, NewExerciceError(e, ErrResolutionNotFound, theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, ErrResolutionNotFound, theme))
|
||||
}
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.exerciceHooks {
|
||||
for _, err := range h(e, exceptions) {
|
||||
errs = append(errs, NewExerciceError(e, err))
|
||||
for _, err := range multierr.Errors(h(e, exceptions)) {
|
||||
errs = multierr.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, exceptions_in *CheckExceptions) (e *fic.Exercice, eid int, exceptions *CheckExceptions, errs []error) {
|
||||
func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice, exceptions_in *CheckExceptions) (e *fic.Exercice, eid int, exceptions *CheckExceptions, errs error) {
|
||||
var err error
|
||||
var p ExerciceParams
|
||||
var berrors []error
|
||||
var berrors error
|
||||
|
||||
e, p, eid, exceptions, _, berrors = BuildExercice(i, theme, epath, dmap, exceptions_in)
|
||||
for _, e := range berrors {
|
||||
errs = append(errs, e)
|
||||
}
|
||||
errs = multierr.Append(errs, berrors)
|
||||
|
||||
if e != nil {
|
||||
if len(e.Image) > 0 {
|
||||
@ -396,24 +395,24 @@ func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*f
|
||||
|
||||
return nil, err
|
||||
}); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to import heading image: %w", err)))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to import heading image: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
// Create or update the exercice
|
||||
err = theme.SaveNamedExercice(e)
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("error on exercice save: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("error on exercice save: %w", err), theme))
|
||||
return
|
||||
}
|
||||
|
||||
// Import eercice tags
|
||||
if _, err := e.WipeTags(); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to wipe tags: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to wipe tags: %w", err), theme))
|
||||
}
|
||||
for _, tag := range p.Tags {
|
||||
if _, err := e.AddTag(tag); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to add tag: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to add tag: %w", err), theme))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -423,9 +422,9 @@ 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, exceptions *CheckExceptions) (exceptions_out map[int]*CheckExceptions, errs []error) {
|
||||
func SyncExercices(i Importer, theme *fic.Theme, exceptions *CheckExceptions) (exceptions_out map[int]*CheckExceptions, errs error) {
|
||||
if exercices, err := GetExercices(i, theme); err != nil {
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
} else {
|
||||
exceptions_out = make(map[int]*CheckExceptions)
|
||||
emap := map[string]int{}
|
||||
@ -438,7 +437,7 @@ func SyncExercices(i Importer, theme *fic.Theme, exceptions *CheckExceptions) (e
|
||||
emap[e.Title] = eid
|
||||
dmap[int64(eid)] = e
|
||||
exceptions_out[eid] = ex_exceptions
|
||||
errs = append(errs, cur_errs...)
|
||||
errs = multierr.Append(errs, cur_errs)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/generation"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
@ -49,7 +51,7 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) {
|
||||
|
||||
errs.DateStart = startTime
|
||||
exceptions, sterrs := SyncThemes(i)
|
||||
for _, sterr := range sterrs {
|
||||
for _, sterr := range multierr.Errors(sterrs) {
|
||||
errs.ThemesSync = append(errs.ThemesSync, sterr.Error())
|
||||
}
|
||||
|
||||
@ -60,7 +62,7 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) {
|
||||
for tid, theme := range themes {
|
||||
DeepSyncProgress = 3 + uint8(tid)*themeStep
|
||||
ex_exceptions, seerrs := SyncExercices(i, theme, exceptions[theme.Path])
|
||||
for _, seerr := range seerrs {
|
||||
for _, seerr := range multierr.Errors(seerrs) {
|
||||
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], seerr.Error())
|
||||
}
|
||||
|
||||
@ -74,13 +76,13 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) {
|
||||
|
||||
DeepSyncProgress = 3 + uint8(tid)*themeStep + uint8(eid)*exerciceStep
|
||||
flagsBindings, ferrs := SyncExerciceFlags(i, exercice, ex_exceptions[eid])
|
||||
for _, ferr := range ferrs {
|
||||
for _, ferr := range multierr.Errors(ferrs) {
|
||||
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], ferr.Error())
|
||||
}
|
||||
|
||||
DeepSyncProgress += exerciceStep / 2
|
||||
_, herrs := SyncExerciceHints(i, exercice, flagsBindings, ex_exceptions[eid])
|
||||
for _, herr := range herrs {
|
||||
for _, herr := range multierr.Errors(herrs) {
|
||||
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], herr.Error())
|
||||
}
|
||||
}
|
||||
@ -113,7 +115,7 @@ func SyncDeep(i Importer) (errs SyncReport) {
|
||||
|
||||
errs.DateStart = startTime
|
||||
exceptions, sterrs := SyncThemes(i)
|
||||
for _, sterr := range sterrs {
|
||||
for _, sterr := range multierr.Errors(sterrs) {
|
||||
errs.ThemesSync = append(errs.ThemesSync, sterr.Error())
|
||||
}
|
||||
|
||||
@ -123,7 +125,7 @@ func SyncDeep(i Importer) (errs SyncReport) {
|
||||
|
||||
for tid, theme := range themes {
|
||||
stderrs := SyncThemeDeep(i, theme, tid, themeStep, exceptions[theme.Path])
|
||||
for _, stderr := range stderrs {
|
||||
for _, stderr := range multierr.Errors(stderrs) {
|
||||
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], stderr.Error())
|
||||
}
|
||||
}
|
||||
@ -199,7 +201,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, exceptions *CheckExceptions) (errs []error) {
|
||||
func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8, exceptions *CheckExceptions) (errs error) {
|
||||
var ex_exceptions map[int]*CheckExceptions
|
||||
|
||||
oneThemeDeepSync.Lock()
|
||||
@ -214,15 +216,15 @@ func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8, excep
|
||||
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, ex_exceptions[eid])...)
|
||||
errs = multierr.Append(errs, SyncExerciceFiles(i, exercice, ex_exceptions[eid]))
|
||||
|
||||
DeepSyncProgress += exerciceStep / 3
|
||||
flagsBindings, ferrs := SyncExerciceFlags(i, exercice, ex_exceptions[eid])
|
||||
errs = append(errs, ferrs...)
|
||||
errs = multierr.Append(errs, ferrs)
|
||||
|
||||
DeepSyncProgress += exerciceStep / 3
|
||||
_, herrs := SyncExerciceHints(i, exercice, flagsBindings, ex_exceptions[eid])
|
||||
errs = append(errs, herrs...)
|
||||
errs = multierr.Append(errs, herrs)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,16 +9,16 @@ import (
|
||||
|
||||
var hooks = &CheckHooks{customHooks: map[string]CustomCheckHook{}}
|
||||
|
||||
type CheckFlagChoiceHook func(*fic.FlagChoice, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckFlagKeyHook func(*fic.FlagKey, string, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckFlagKeyWithChoicesHook func(*fic.FlagKey, string, []*fic.FlagChoice, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckFlagLabelHook func(*fic.FlagLabel, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckFlagMCQHook func(*fic.MCQ, []*fic.MCQ_entry, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckFileHook func(*fic.EFile, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckHintHook func(*fic.EHint, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckMDTextHook func(string, string, *CheckExceptions, ...string) []error
|
||||
type CheckExerciceHook func(*fic.Exercice, *CheckExceptions) []error
|
||||
type CustomCheckHook func(interface{}, *CheckExceptions) []error
|
||||
type CheckFlagChoiceHook func(*fic.FlagChoice, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckFlagKeyHook func(*fic.FlagKey, string, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckFlagKeyWithChoicesHook func(*fic.FlagKey, string, []*fic.FlagChoice, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckFlagLabelHook func(*fic.FlagLabel, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckFlagMCQHook func(*fic.MCQ, []*fic.MCQ_entry, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckFileHook func(*fic.EFile, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckHintHook func(*fic.EHint, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckMDTextHook func(string, string, *CheckExceptions, ...string) error
|
||||
type CheckExerciceHook func(*fic.Exercice, *CheckExceptions) error
|
||||
type CustomCheckHook func(interface{}, *CheckExceptions) error
|
||||
|
||||
type CheckHooks struct {
|
||||
flagChoiceHooks []CheckFlagChoiceHook
|
||||
@ -73,7 +73,7 @@ func (h *CheckHooks) RegisterCustomHook(hookname string, f CustomCheckHook) {
|
||||
h.customHooks[hookname] = f
|
||||
}
|
||||
|
||||
func (h *CheckHooks) CallCustomHook(hookname string, data interface{}, exceptions *CheckExceptions) []error {
|
||||
func (h *CheckHooks) CallCustomHook(hookname string, data interface{}, exceptions *CheckExceptions) error {
|
||||
if v, ok := h.customHooks[hookname]; ok {
|
||||
return v(data, exceptions)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yuin/goldmark"
|
||||
"go.uber.org/multierr"
|
||||
"golang.org/x/image/draw"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
@ -104,7 +105,7 @@ 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, exceptions *CheckExceptions, errs []error) {
|
||||
func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExceptions, errs error) {
|
||||
th = &fic.Theme{}
|
||||
|
||||
th.Path = tdir
|
||||
@ -116,7 +117,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
||||
if language, err := GetFileContent(i, path.Join(tdir, "language.txt")); err == nil {
|
||||
language = strings.TrimSpace(language)
|
||||
if strings.Contains(language, "\n") {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("language.txt: Language can't contain new lines")))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("language.txt: Language can't contain new lines")))
|
||||
} else {
|
||||
th.Language = language
|
||||
}
|
||||
@ -133,7 +134,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
||||
th.URLId = fic.ToURLid(th.Name)
|
||||
|
||||
if authors, err := getAuthors(i, tdir); err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
|
||||
return nil, nil, errs
|
||||
} else {
|
||||
// Format authors
|
||||
@ -147,13 +148,13 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
||||
th.Headline, err = GetFileContent(i, path.Join(tdir, "headline.md"))
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("unable to get theme's headline: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get theme's headline: %w", err)))
|
||||
}
|
||||
if th.Headline != "" {
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(th.Headline, th.Language, exceptions.GetFileExceptions("headline.md", "headline.txt")) {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("headline.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(th.Headline, th.Language, exceptions.GetFileExceptions("headline.md", "headline.txt"))) {
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("headline.md: %w", err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,12 +168,12 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
||||
err = fmt.Errorf("unable to find overview.txt nor overview.md")
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("unable to get theme's overview: %w", err)))
|
||||
errs = multierr.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, th.Language, exceptions.GetFileExceptions("overview.md", "overview.txt")) {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("overview.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(intro, th.Language, exceptions.GetFileExceptions("overview.md", "overview.txt"))) {
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("overview.md: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,12 +189,12 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
||||
// Format overview (markdown)
|
||||
th.Intro, err = ProcessMarkdown(i, intro, tdir)
|
||||
if err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("overview.txt: an error occurs during markdown formating: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("overview.txt: an error occurs during markdown formating: %w", err)))
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err := goldmark.Convert([]byte(th.Headline), &buf)
|
||||
if err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("overview.txt: an error occurs during markdown formating of the headline: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("overview.txt: an error occurs during markdown formating of the headline: %w", err)))
|
||||
} else {
|
||||
th.Headline = string(buf.Bytes())
|
||||
}
|
||||
@ -204,7 +205,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
||||
} else if i.Exists(path.Join(tdir, "heading.png")) {
|
||||
th.Image = path.Join(tdir, "heading.png")
|
||||
} else {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("heading.jpg: No such file")))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("heading.jpg: No such file")))
|
||||
}
|
||||
|
||||
if i.Exists(path.Join(tdir, "partner.jpg")) {
|
||||
@ -215,11 +216,11 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
||||
|
||||
if i.Exists(path.Join(tdir, "partner.txt")) {
|
||||
if txt, err := GetFileContent(i, path.Join(tdir, "partner.txt")); err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("unable to get partner's text: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get partner's text: %w", err)))
|
||||
} else {
|
||||
th.PartnerText, err = ProcessMarkdown(i, txt, tdir)
|
||||
if err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("partner.txt: an error occurs during markdown formating: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("partner.txt: an error occurs during markdown formating: %w", err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -227,9 +228,9 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
||||
}
|
||||
|
||||
// SyncThemes imports new or updates existing themes.
|
||||
func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, 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))
|
||||
errs = multierr.Append(errs, fmt.Errorf("Unable to list themes: %w", err))
|
||||
} else {
|
||||
rand.Shuffle(len(themes), func(i, j int) {
|
||||
themes[i], themes[j] = themes[j], themes[i]
|
||||
@ -239,9 +240,7 @@ func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs []erro
|
||||
|
||||
for _, tdir := range themes {
|
||||
btheme, excepts, berrs := BuildTheme(i, tdir)
|
||||
for _, e := range berrs {
|
||||
errs = append(errs, e)
|
||||
}
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
if btheme == nil {
|
||||
continue
|
||||
@ -259,7 +258,7 @@ func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs []erro
|
||||
btheme.Image = strings.TrimPrefix(filePath, fic.FilesDir)
|
||||
return nil, nil
|
||||
}); err != nil {
|
||||
errs = append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,14 +268,14 @@ func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs []erro
|
||||
btheme.PartnerImage = strings.TrimPrefix(filePath, fic.FilesDir)
|
||||
return nil, nil
|
||||
}); err != nil {
|
||||
errs = append(errs, NewThemeError(btheme, fmt.Errorf("unable to import partner image: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import partner image: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
var theme *fic.Theme
|
||||
if theme, err = fic.GetThemeByPath(btheme.Path); err != nil {
|
||||
if _, err := fic.CreateTheme(btheme); err != nil {
|
||||
errs = append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during add: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during add: %w", err)))
|
||||
continue
|
||||
}
|
||||
}
|
||||
@ -284,7 +283,7 @@ func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs []erro
|
||||
if !fic.CmpTheme(theme, btheme) {
|
||||
btheme.Id = theme.Id
|
||||
if _, err := btheme.Update(); err != nil {
|
||||
errs = append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during update: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during update: %w", err)))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
1
go.mod
1
go.mod
@ -14,6 +14,7 @@ require (
|
||||
github.com/u2takey/ffmpeg-go v0.5.0
|
||||
github.com/yuin/goldmark v1.6.0
|
||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/crypto v0.15.0
|
||||
golang.org/x/image v0.14.0
|
||||
golang.org/x/oauth2 v0.14.0
|
||||
|
2
go.sum
2
go.sum
@ -345,6 +345,8 @@ github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
|
||||
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs=
|
||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
|
@ -5,29 +5,31 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
func EPITACheckFile(file *fic.EFile, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func EPITACheckFile(file *fic.EFile, _ *fic.Exercice, 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."))
|
||||
errs = multierr.Append(errs, fmt.Errorf("this file use a forbidden archive type."))
|
||||
}
|
||||
|
||||
// Check for stange file extension
|
||||
if strings.HasSuffix(file.Name, ".tar.zip") {
|
||||
errs = append(errs, fmt.Errorf(".tar.zip is not a valid tar format"))
|
||||
errs = multierr.Append(errs, fmt.Errorf(".tar.zip is not a valid tar format"))
|
||||
}
|
||||
|
||||
// Check .gz files have a dedicated hash
|
||||
if path.Ext(file.Name) == ".gz" && !strings.HasSuffix(file.Name, ".tar.gz") && len(file.ChecksumShown) == 0 {
|
||||
errs = append(errs, fmt.Errorf("digest of original, uncompressed, file missing in DIGESTS.txt"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("digest of original, uncompressed, file missing in DIGESTS.txt"))
|
||||
}
|
||||
|
||||
// Check for huge file to compress
|
||||
if file.Size > 4000000 && path.Ext(file.Name) == ".tar" {
|
||||
errs = append(errs, fmt.Errorf("archive to compress with bzip2"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("archive to compress with bzip2"))
|
||||
} else if file.Size > 40000000 && (path.Ext(file.Name) == "" ||
|
||||
path.Ext(file.Name) == ".csv" ||
|
||||
path.Ext(file.Name) == ".dump" ||
|
||||
@ -38,7 +40,7 @@ func EPITACheckFile(file *fic.EFile, _ *fic.Exercice, exceptions *sync.CheckExce
|
||||
path.Ext(file.Name) == ".pcap" ||
|
||||
path.Ext(file.Name) == ".pcapng" ||
|
||||
path.Ext(file.Name) == ".txt") {
|
||||
errs = append(errs, fmt.Errorf("huge file to compress with gzip"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("huge file to compress with gzip"))
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -6,53 +6,55 @@ import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
func EPITACheckKeyFlag(flag *fic.FlagKey, raw string, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func EPITACheckKeyFlag(flag *fic.FlagKey, raw string, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) {
|
||||
if (flag.Label[0] == 'Q' || flag.Label[0] == 'q') && (flag.Label[1] == 'U' || flag.Label[1] == 'u') ||
|
||||
(flag.Label[0] == 'W' || flag.Label[0] == 'w') && (flag.Label[1] == 'H' || flag.Label[1] == 'h') {
|
||||
errs = append(errs, fmt.Errorf("Label should not begin with %s. This seem to be a question. Reword your label as a description of the expected flag, `:` are automatically appended.", flag.Label[0:2]))
|
||||
errs = multierr.Append(errs, fmt.Errorf("Label should not begin with %s. This seem to be a question. Reword your label as a description of the expected flag, `:` are automatically appended.", flag.Label[0:2]))
|
||||
flag.Label = flag.Label[1:]
|
||||
}
|
||||
|
||||
if flag.Label[len(flag.Label)-1] != ')' && flag.Label[len(flag.Label)-1] != '©' && !unicode.IsLetter(rune(flag.Label[len(flag.Label)-1])) && !unicode.IsDigit(rune(flag.Label[len(flag.Label)-1])) {
|
||||
errs = append(errs, fmt.Errorf("Label should not end with punct (%q). Reword your label as a description of the expected flag, `:` are automatically appended.", flag.Label[len(flag.Label)-1]))
|
||||
errs = multierr.Append(errs, fmt.Errorf("Label should not end with punct (%q). Reword your label as a description of the expected flag, `:` are automatically appended.", flag.Label[len(flag.Label)-1]))
|
||||
}
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(raw), "cve-") && flag.Type != "ucq" {
|
||||
errs = append(errs, fmt.Errorf("CVE numbers are required to be UCQ with choice_cost"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("CVE numbers are required to be UCQ with choice_cost"))
|
||||
}
|
||||
|
||||
if _, err := strconv.ParseInt(raw, 10, 64); flag.Type == "key" && err == nil && !exceptions.HasException(":not-number-flag") {
|
||||
errs = append(errs, fmt.Errorf("shouldn't be this flag a number type? (:not-number-flag)"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("shouldn't be this flag a number type? (:not-number-flag)"))
|
||||
}
|
||||
|
||||
if flag.Placeholder == "" && (strings.HasPrefix(flag.Type, "number") || flag.Type == "key" || flag.Type == "text" || (flag.Type == "ucq" && flag.ChoicesCost > 0)) {
|
||||
errs = append(errs, fmt.Errorf("no placeholder defined"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("no placeholder defined"))
|
||||
}
|
||||
|
||||
if strings.HasPrefix(flag.Type, "number") {
|
||||
min, max, step, err := fic.AnalyzeNumberFlag(flag.Type)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
} else if min == nil || max == nil || step == nil {
|
||||
errs = append(errs, fmt.Errorf("please define min and max for your number flag"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("please define min and max for your number flag"))
|
||||
} else if (*max-*min) / *step <= 10 {
|
||||
errs = append(errs, fmt.Errorf("to avoid bruteforce, define more than 10 possibilities"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("to avoid bruteforce, define more than 10 possibilities"))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func EPITACheckKeyFlagWithChoices(flag *fic.FlagKey, raw string, choices []*fic.FlagChoice, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func EPITACheckKeyFlagWithChoices(flag *fic.FlagKey, raw string, choices []*fic.FlagChoice, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) {
|
||||
if !exceptions.HasException(":bruteforcable-choices") {
|
||||
if len(choices) < 10 && flag.ChoicesCost == 0 {
|
||||
errs = append(errs, fmt.Errorf("requires at least 10 choices to avoid brute-force"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("requires at least 10 choices to avoid brute-force"))
|
||||
} else if len(choices) < 6 && flag.ChoicesCost > 0 {
|
||||
errs = append(errs, fmt.Errorf("requires at least 10 choices to avoid brute-force"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("requires at least 10 choices to avoid brute-force"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,17 +4,19 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
func InspectFile(file *fic.EFile, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func InspectFile(file *fic.EFile, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) {
|
||||
if filepath.Ext(file.Name) == ".tar" || strings.HasSuffix(file.Name, ".tar.gz") || strings.HasSuffix(file.Name, ".tar.bz2") {
|
||||
// Check there is more than 1 file in tarball
|
||||
errs = append(errs, checkTarball(file, exceptions)...)
|
||||
errs = multierr.Append(errs, checkTarball(file, exceptions))
|
||||
} else if filepath.Ext(file.Name) == ".zip" {
|
||||
// Check there is more than 1 file in zip
|
||||
errs = append(errs, checkZip(file, exceptions)...)
|
||||
errs = multierr.Append(errs, checkZip(file, exceptions))
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -9,11 +9,13 @@ import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
func checkTarball(file *fic.EFile, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func checkTarball(file *fic.EFile, exceptions *sync.CheckExceptions) (errs error) {
|
||||
fd, closer, err := sync.GetFile(sync.GlobalImporter, file.GetOrigin())
|
||||
if err != nil {
|
||||
log.Printf("Unable to open %q: %s", file.GetOrigin(), err.Error())
|
||||
@ -56,11 +58,11 @@ func checkTarball(file *fic.EFile, exceptions *sync.CheckExceptions) (errs []err
|
||||
|
||||
if nbFile < 2 {
|
||||
if !exceptions.HasException(":one-file-tarball") {
|
||||
errs = append(errs, fmt.Errorf("don't make a tarball for one file"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("don't make a tarball for one file"))
|
||||
}
|
||||
} else if nbFile < 5 && false {
|
||||
if !exceptions.HasException(":few-files-tarball") {
|
||||
errs = append(errs, fmt.Errorf("don't make a tarball for so little files (:few-files-tarball)"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("don't make a tarball for so little files (:few-files-tarball)"))
|
||||
}
|
||||
} else {
|
||||
log.Printf("%d files found in %q", nbFile, file.Name)
|
||||
|
@ -7,11 +7,13 @@ import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
func checkZip(file *fic.EFile, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func checkZip(file *fic.EFile, exceptions *sync.CheckExceptions) (errs error) {
|
||||
fd, closer, err := sync.GetFile(sync.GlobalImporter, file.GetOrigin())
|
||||
if err != nil {
|
||||
log.Printf("Unable to open %q: %s", file.GetOrigin(), err.Error())
|
||||
@ -39,11 +41,11 @@ func checkZip(file *fic.EFile, exceptions *sync.CheckExceptions) (errs []error)
|
||||
|
||||
if len(r.File) < 2 {
|
||||
if !exceptions.HasException(":one-file-tarball") {
|
||||
errs = append(errs, fmt.Errorf("don't make a ZIP archive for one file, use gzip instead"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("don't make a ZIP archive for one file, use gzip instead"))
|
||||
}
|
||||
} else if len(r.File) < 5 && false {
|
||||
if !exceptions.HasException(":few-files-tarball") {
|
||||
errs = append(errs, fmt.Errorf("don't make a ZIP archive for so little files (:few-files-tarball)"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("don't make a ZIP archive for so little files (:few-files-tarball)"))
|
||||
}
|
||||
} else {
|
||||
log.Printf("%d files found in %q", len(r.File), file.Name)
|
||||
@ -83,7 +85,7 @@ func checkZip(file *fic.EFile, exceptions *sync.CheckExceptions) (errs []error)
|
||||
}
|
||||
|
||||
if nbLinuxDirFound > 2 && !exceptions.HasException(":not-a-linux-rootfs") {
|
||||
errs = append(errs, fmt.Errorf("don't use a ZIP archive to store an Unix file system, prefer a tarball (:not-a-linux-rootfs)"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("don't use a ZIP archive to store an Unix file system, prefer a tarball (:not-a-linux-rootfs)"))
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -6,51 +6,53 @@ import (
|
||||
"log"
|
||||
"unicode"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
lib "srs.epita.fr/fic-server/repochecker/grammalecte/lib"
|
||||
)
|
||||
|
||||
func GrammalecteCheckKeyFlag(flag *fic.FlagKey, raw string, exercice *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func GrammalecteCheckKeyFlag(flag *fic.FlagKey, raw string, exercice *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) {
|
||||
if !isRecognizedLanguage(exercice.Language) {
|
||||
return
|
||||
}
|
||||
|
||||
label, _, _, _ := flag.AnalyzeFlagLabel()
|
||||
for _, err := range grammalecte("label ", label, -1, exceptions, &CommonOpts) {
|
||||
for _, err := range multierr.Errors(grammalecte("label ", label, -1, exceptions, &CommonOpts)) {
|
||||
if e, ok := err.(lib.GrammarError); ok && e.RuleId == "poncfin_règle1" {
|
||||
continue
|
||||
}
|
||||
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
}
|
||||
|
||||
if len(flag.Help) > 0 {
|
||||
errs = append(errs, grammalecte("help ", flag.Help, -1, exceptions, &CommonOpts)...)
|
||||
errs = multierr.Append(errs, grammalecte("help ", flag.Help, -1, exceptions, &CommonOpts))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GrammalecteCheckFlagChoice(choice *fic.FlagChoice, exercice *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func GrammalecteCheckFlagChoice(choice *fic.FlagChoice, exercice *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) {
|
||||
if isRecognizedLanguage(exercice.Language) {
|
||||
errs = append(errs, grammalecte("label ", choice.Label, -1, exceptions, &CommonOpts)...)
|
||||
errs = multierr.Append(errs, grammalecte("label ", choice.Label, -1, exceptions, &CommonOpts))
|
||||
}
|
||||
|
||||
if len(errs) == 0 && !unicode.IsUpper(bytes.Runes([]byte(choice.Label))[0]) && !exceptions.HasException(":label_majuscule") {
|
||||
errs = append(errs, fmt.Errorf("%q nécessite une majuscule (:label_majuscule)", choice.Label))
|
||||
if len(multierr.Errors(errs)) == 0 && !unicode.IsUpper(bytes.Runes([]byte(choice.Label))[0]) && !exceptions.HasException(":label_majuscule") {
|
||||
errs = multierr.Append(errs, fmt.Errorf("%q nécessite une majuscule (:label_majuscule)", choice.Label))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GrammalecteCheckHint(hint *fic.EHint, exercice *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func GrammalecteCheckHint(hint *fic.EHint, exercice *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) {
|
||||
if len(hint.Title) > 0 {
|
||||
if isRecognizedLanguage(exercice.Language) {
|
||||
errs = append(errs, grammalecte("title ", hint.Title, -1, exceptions, &CommonOpts)...)
|
||||
errs = multierr.Append(errs, grammalecte("title ", hint.Title, -1, exceptions, &CommonOpts))
|
||||
}
|
||||
if len(errs) == 0 && !unicode.IsUpper(bytes.Runes([]byte(hint.Title))[0]) && !exceptions.HasException(":title_majuscule") {
|
||||
errs = append(errs, fmt.Errorf("%q nécessite une majuscule (:title_majuscule)", hint.Title))
|
||||
if len(multierr.Errors(errs)) == 0 && !unicode.IsUpper(bytes.Runes([]byte(hint.Title))[0]) && !exceptions.HasException(":title_majuscule") {
|
||||
errs = multierr.Append(errs, fmt.Errorf("%q nécessite une majuscule (:title_majuscule)", hint.Title))
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +61,7 @@ func GrammalecteCheckHint(hint *fic.EHint, exercice *fic.Exercice, exceptions *s
|
||||
return
|
||||
}
|
||||
|
||||
func GrammalecteCheckGrammar(data interface{}, exceptions *sync.CheckExceptions) []error {
|
||||
func GrammalecteCheckGrammar(data interface{}, exceptions *sync.CheckExceptions) error {
|
||||
if s, ok := data.(struct {
|
||||
Str string
|
||||
Language string
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
lib "srs.epita.fr/fic-server/repochecker/grammalecte/lib"
|
||||
)
|
||||
@ -119,7 +121,7 @@ var (
|
||||
mdimg = regexp.MustCompile(`!\[([^\]]+)\]\([^)]+\)`)
|
||||
)
|
||||
|
||||
func grammalecte(name string, text string, paragraph int, exceptions *sync.CheckExceptions, options *GrammalecteOptions) (errs []error) {
|
||||
func grammalecte(name string, text string, paragraph int, exceptions *sync.CheckExceptions, options *GrammalecteOptions) (errs error) {
|
||||
// Remove Markdown elements
|
||||
text = mdimg.ReplaceAllString(text, "Image : ${1}")
|
||||
|
||||
@ -173,7 +175,7 @@ func grammalecte(name string, text string, paragraph int, exceptions *sync.Check
|
||||
}
|
||||
|
||||
suggestions, _ := suggest(serror.Value)
|
||||
errs = append(errs, lib.SpellingError{
|
||||
errs = multierr.Append(errs, lib.SpellingError{
|
||||
Prefix: name,
|
||||
Source: data.Text,
|
||||
NSource: data.Paragraph,
|
||||
@ -207,7 +209,7 @@ func grammalecte(name string, text string, paragraph int, exceptions *sync.Check
|
||||
continue
|
||||
}
|
||||
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,9 +15,10 @@ import (
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
"go.uber.org/multierr"
|
||||
)
|
||||
|
||||
func GrammalecteCheckMDText(str string, lang string, exceptions *sync.CheckExceptions, forbiddenStrings ...string) (errs []error) {
|
||||
func GrammalecteCheckMDText(str string, lang string, exceptions *sync.CheckExceptions, forbiddenStrings ...string) (errs error) {
|
||||
if !isRecognizedLanguage(lang) {
|
||||
return
|
||||
}
|
||||
@ -34,7 +35,7 @@ func GrammalecteCheckMDText(str string, lang string, exceptions *sync.CheckExcep
|
||||
for _, s := range forbiddenStrings {
|
||||
if strings.Contains(str, s) {
|
||||
if !exceptions.HasException(":not-forbidden-string:" + s) {
|
||||
errs = append(errs, fmt.Errorf("Forbidden raw string %q included in file content, don't write it (:not-forbidden-string:%s)", s, s))
|
||||
errs = multierr.Append(errs, fmt.Errorf("Forbidden raw string %q included in file content, don't write it (:not-forbidden-string:%s)", s, s))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,13 +63,13 @@ func GrammalecteCheckMDText(str string, lang string, exceptions *sync.CheckExcep
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := markdown.Convert([]byte(str), &buf); err != nil {
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
}
|
||||
|
||||
errs = append(errs, checker.errs...)
|
||||
errs = append(errs, voidRenderer.Errors()...)
|
||||
errs = multierr.Append(errs, checker.errs)
|
||||
errs = multierr.Append(errs, voidRenderer.Errors())
|
||||
|
||||
for _, err := range grammalecte("", buf.String(), 0, exceptions, &CommonOpts) {
|
||||
for _, err := range multierr.Errors(grammalecte("", buf.String(), 0, exceptions, &CommonOpts)) {
|
||||
if gerror, ok := err.(lib.GrammarError); ok {
|
||||
if (gerror.RuleId == "redondances_paragraphe" || gerror.RuleId == "redondances_phrase") && gerror.GetPassage() == "SubstitutDeCode" {
|
||||
continue
|
||||
@ -79,7 +80,7 @@ func GrammalecteCheckMDText(str string, lang string, exceptions *sync.CheckExcep
|
||||
}
|
||||
}
|
||||
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
}
|
||||
|
||||
return
|
||||
@ -87,7 +88,7 @@ func GrammalecteCheckMDText(str string, lang string, exceptions *sync.CheckExcep
|
||||
|
||||
type grammarChecker struct {
|
||||
exceptions *sync.CheckExceptions
|
||||
errs []error
|
||||
errs error
|
||||
}
|
||||
|
||||
func (t *grammarChecker) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) {
|
||||
@ -99,11 +100,11 @@ func (t *grammarChecker) Transform(doc *ast.Document, reader text.Reader, pc par
|
||||
switch child := node.(type) {
|
||||
case *ast.Image:
|
||||
if len(child.Title) > 0 {
|
||||
t.errs = append(t.errs, grammalecte("", string(child.Title), 0, t.exceptions, &CommonOpts)...)
|
||||
t.errs = multierr.Append(t.errs, grammalecte("", string(child.Title), 0, t.exceptions, &CommonOpts))
|
||||
}
|
||||
case *ast.Link:
|
||||
if len(child.Title) > 0 {
|
||||
t.errs = append(t.errs, grammalecte("", string(child.Title), 0, t.exceptions, &CommonOpts)...)
|
||||
t.errs = multierr.Append(t.errs, grammalecte("", string(child.Title), 0, t.exceptions, &CommonOpts))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,11 @@ import (
|
||||
"github.com/yuin/goldmark/ast"
|
||||
goldrender "github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/util"
|
||||
"go.uber.org/multierr"
|
||||
)
|
||||
|
||||
type VoidRenderer struct {
|
||||
errs []error
|
||||
errs error
|
||||
}
|
||||
|
||||
func NewVoidRenderer() *VoidRenderer {
|
||||
@ -28,7 +29,7 @@ func (r *VoidRenderer) RegisterFuncs(reg goldrender.NodeRendererFuncRegisterer)
|
||||
reg.Register(ast.KindString, r.renderString)
|
||||
}
|
||||
|
||||
func (r *VoidRenderer) Errors() []error {
|
||||
func (r *VoidRenderer) Errors() error {
|
||||
return r.errs
|
||||
}
|
||||
|
||||
@ -77,7 +78,7 @@ func (r *VoidRenderer) renderImage(w util.BufWriter, source []byte, node ast.Nod
|
||||
// Check there is a correct image alt
|
||||
alt := nodeToText(n, source)
|
||||
if len(bytes.Fields(alt)) <= 1 {
|
||||
r.errs = append(r.errs, fmt.Errorf("No valid image alternative defined for %q", n.Destination))
|
||||
r.errs = multierr.Append(r.errs, fmt.Errorf("No valid image alternative defined for %q", n.Destination))
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
@ -108,14 +110,14 @@ func searchBinaryInGit(edir string) (ret []string) {
|
||||
return
|
||||
}
|
||||
|
||||
func checkExercice(theme *fic.Theme, edir string, dmap *map[int64]*fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func checkExercice(theme *fic.Theme, edir string, dmap *map[int64]*fic.Exercice, exceptions *sync.CheckExceptions) (errs error) {
|
||||
e, _, eid, exceptions, _, berrs := sync.BuildExercice(sync.GlobalImporter, theme, path.Join(theme.Path, edir), dmap, nil)
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
if e != nil {
|
||||
// Files
|
||||
var files []string
|
||||
var cerrs []error
|
||||
var cerrs error
|
||||
if !skipFileChecks {
|
||||
files, cerrs = sync.CheckExerciceFiles(sync.GlobalImporter, e, exceptions)
|
||||
log.Printf("%d files checked.\n", len(files))
|
||||
@ -123,16 +125,16 @@ func checkExercice(theme *fic.Theme, edir string, dmap *map[int64]*fic.Exercice,
|
||||
files, cerrs = sync.CheckExerciceFilesPresence(sync.GlobalImporter, e)
|
||||
log.Printf("%d files presents but not checked (please check digest yourself).\n", len(files))
|
||||
}
|
||||
errs = append(errs, cerrs...)
|
||||
errs = multierr.Append(errs, cerrs)
|
||||
|
||||
// Flags
|
||||
flags, cerrs := sync.CheckExerciceFlags(sync.GlobalImporter, e, files, exceptions)
|
||||
errs = append(errs, cerrs...)
|
||||
errs = multierr.Append(errs, cerrs)
|
||||
log.Printf("%d flags checked.\n", len(flags))
|
||||
|
||||
// Hints
|
||||
hints, cerrs := sync.CheckExerciceHints(sync.GlobalImporter, e, exceptions)
|
||||
errs = append(errs, cerrs...)
|
||||
errs = multierr.Append(errs, cerrs)
|
||||
log.Printf("%d hints checked.\n", len(hints))
|
||||
|
||||
if dmap != nil {
|
||||
@ -247,8 +249,9 @@ func main() {
|
||||
theme, exceptions, errs := sync.BuildTheme(sync.GlobalImporter, p)
|
||||
|
||||
if theme != nil && !sync.GlobalImporter.Exists(path.Join(p, "challenge.txt")) && !sync.GlobalImporter.Exists(path.Join(p, "challenge.toml")) {
|
||||
nberr += len(errs)
|
||||
for _, err := range errs {
|
||||
thiserrors := multierr.Errors(errs)
|
||||
nberr += len(thiserrors)
|
||||
for _, err := range thiserrors {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
@ -264,7 +267,7 @@ func main() {
|
||||
for _, edir := range exercices {
|
||||
ex_exceptions := exceptions.GetFileExceptions(edir)
|
||||
|
||||
for _, err := range checkExercice(theme, edir, &dmap, ex_exceptions) {
|
||||
for _, err := range multierr.Errors(checkExercice(theme, edir, &dmap, ex_exceptions)) {
|
||||
log.Println(err.Error())
|
||||
|
||||
if logMissingResolution {
|
||||
@ -294,7 +297,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{}, nil) {
|
||||
for _, err := range multierr.Errors(checkExercice(&fic.Theme{}, p, &map[int64]*fic.Exercice{}, nil)) {
|
||||
nberr += 1
|
||||
log.Println(err)
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func (pcapNGReader *PcapNgReader) ReadPacketData() (data []byte, ci gopacket.Cap
|
||||
|
||||
// Iterate thought each packet to find potentialy unwanted packets
|
||||
// TODO: Allow custom rules to specify what is a unwanted packet
|
||||
func CheckPcap(pcapReader PcapPacketDataReader, pcapName string) (errs []error) {
|
||||
func CheckPcap(pcapReader PcapPacketDataReader, pcapName string) (errs error) {
|
||||
warningFlows := make(map[gopacket.Flow]([]time.Time))
|
||||
|
||||
//
|
||||
@ -105,11 +105,11 @@ func CheckPcap(pcapReader PcapPacketDataReader, pcapName string) (errs []error)
|
||||
return
|
||||
}
|
||||
|
||||
func CheckTextFile(fd *os.File) (errs []error) {
|
||||
func CheckTextFile(fd *os.File) (errs error) {
|
||||
return
|
||||
}
|
||||
|
||||
func InspectFileForIPAddr(file *fic.EFile, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func InspectFileForIPAddr(file *fic.EFile, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) {
|
||||
fd, closer, err := sync.GetFile(sync.GlobalImporter, file.GetOrigin())
|
||||
if err != nil {
|
||||
log.Printf("Unable to open %q: %s", file.GetOrigin(), err.Error())
|
||||
|
@ -3,6 +3,8 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
@ -15,9 +17,9 @@ func RegisterChecksHooks(h *sync.CheckHooks) {
|
||||
h.RegisterExerciceHook(CheckResolutionVideo)
|
||||
}
|
||||
|
||||
func CheckResolutionVideo(e *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
for _, err := range checkResolutionVideo(e, exceptions) {
|
||||
errs = append(errs, fmt.Errorf("resolution.mp4: %w", err))
|
||||
func CheckResolutionVideo(e *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) {
|
||||
for _, err := range multierr.Errors(checkResolutionVideo(e, exceptions)) {
|
||||
errs = multierr.Append(errs, fmt.Errorf("resolution.mp4: %w", err))
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -9,14 +9,15 @@ import (
|
||||
|
||||
"github.com/asticode/go-astisub"
|
||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
)
|
||||
|
||||
func CheckGrammarSubtitleTrack(path string, index uint, lang string, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func CheckGrammarSubtitleTrack(path string, index uint, lang string, exceptions *sync.CheckExceptions) (errs error) {
|
||||
tmpfile, err := ioutil.TempFile("", "resolution-*.srt")
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("unable to create a temporary file: %w", err))
|
||||
errs = multierr.Append(errs, fmt.Errorf("unable to create a temporary file: %w", err))
|
||||
return
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
@ -26,7 +27,7 @@ func CheckGrammarSubtitleTrack(path string, index uint, lang string, exceptions
|
||||
Output(tmpfile.Name(), ffmpeg.KwArgs{"map": fmt.Sprintf("0:%d", index)}).
|
||||
OverWriteOutput().Run()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("ffmpeg returns an error when extracting subtitles track: %w", err))
|
||||
errs = multierr.Append(errs, fmt.Errorf("ffmpeg returns an error when extracting subtitles track: %w", err))
|
||||
}
|
||||
|
||||
subtitles, err := astisub.OpenFile(tmpfile.Name())
|
||||
@ -39,11 +40,11 @@ func CheckGrammarSubtitleTrack(path string, index uint, lang string, exceptions
|
||||
for _, item := range subtitles.Items {
|
||||
lines = append(lines, item.String())
|
||||
}
|
||||
for _, e := range hooks.CallCustomHook("CheckGrammar", struct {
|
||||
for _, e := range multierr.Errors(hooks.CallCustomHook("CheckGrammar", struct {
|
||||
Str string
|
||||
Language string
|
||||
}{Str: strings.Join(lines, "\n"), Language: lang[:2]}, exceptions) {
|
||||
errs = append(errs, fmt.Errorf("subtitle-track: %w", e))
|
||||
}{Str: strings.Join(lines, "\n"), Language: lang[:2]}, exceptions)) {
|
||||
errs = multierr.Append(errs, fmt.Errorf("subtitle-track: %w", e))
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/sync"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
@ -49,7 +50,7 @@ func gcd(a, b int) int {
|
||||
return bgcd(a, b, 1)
|
||||
}
|
||||
|
||||
func checkResolutionVideo(e *fic.Exercice, exceptions *sync.CheckExceptions) (errs []error) {
|
||||
func checkResolutionVideo(e *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) {
|
||||
i, ok := sync.GlobalImporter.(sync.LocalImporter)
|
||||
if !ok {
|
||||
log.Printf("Unable to load `videos-rules.so` as the current Importer is not a LocalImporter (%T).", sync.GlobalImporter)
|
||||
@ -71,7 +72,7 @@ func checkResolutionVideo(e *fic.Exercice, exceptions *sync.CheckExceptions) (er
|
||||
|
||||
data, err := ffmpeg.Probe(path)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("unable to open %q: %w", path, err))
|
||||
errs = multierr.Append(errs, fmt.Errorf("unable to open %q: %w", path, err))
|
||||
return
|
||||
}
|
||||
|
||||
@ -87,79 +88,79 @@ func checkResolutionVideo(e *fic.Exercice, exceptions *sync.CheckExceptions) (er
|
||||
if s.CodecType == "video" {
|
||||
video_seen = append(video_seen, idx)
|
||||
if (s.Width > 1920 || s.Height > 1080) && !exceptions.HasException(":size:above_maximum") {
|
||||
errs = append(errs, fmt.Errorf("video track is too wide: %dx%d (maximum allowed: 1920x1080)", s.Width, s.Height))
|
||||
errs = multierr.Append(errs, fmt.Errorf("video track is too wide: %dx%d (maximum allowed: 1920x1080)", s.Width, s.Height))
|
||||
}
|
||||
|
||||
ratio := s.Width * 10 / s.Height
|
||||
if ratio < 13 || ratio > 19 && !exceptions.HasException(":size:strange_ratio") {
|
||||
m := gcd(s.Width, s.Height)
|
||||
errs = append(errs, fmt.Errorf("video track has a strange ratio: %d:%d. Is this really expected?", s.Width/m, s.Height/m))
|
||||
errs = multierr.Append(errs, fmt.Errorf("video track has a strange ratio: %d:%d. Is this really expected?", s.Width/m, s.Height/m))
|
||||
}
|
||||
|
||||
if s.CodecName != "h264" {
|
||||
errs = append(errs, fmt.Errorf("video codec has to be H264 (currently: %s)", s.CodecLongName))
|
||||
errs = multierr.Append(errs, fmt.Errorf("video codec has to be H264 (currently: %s)", s.CodecLongName))
|
||||
}
|
||||
|
||||
duration, err := strconv.ParseFloat(s.Duration, 64)
|
||||
if err == nil {
|
||||
if duration < 45 && !exceptions.HasException(":duration:too_short") {
|
||||
errs = append(errs, fmt.Errorf("video is too short"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("video is too short"))
|
||||
}
|
||||
if duration > 450 && !exceptions.HasException(":duration:too_long") {
|
||||
errs = append(errs, fmt.Errorf("video is too long"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("video is too long"))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("invalid track duration: %q", s.Duration))
|
||||
errs = multierr.Append(errs, fmt.Errorf("invalid track duration: %q", s.Duration))
|
||||
}
|
||||
} else if s.CodecType == "subtitle" {
|
||||
subtitles_seen = append(subtitles_seen, idx)
|
||||
|
||||
if s.CodecName != "mov_text" {
|
||||
errs = append(errs, fmt.Errorf("subtitle format has to be MOV text/3GPP Timed Text (currently: %s)", s.CodecLongName))
|
||||
errs = multierr.Append(errs, fmt.Errorf("subtitle format has to be MOV text/3GPP Timed Text (currently: %s)", s.CodecLongName))
|
||||
}
|
||||
|
||||
nbframes, err := strconv.ParseInt(s.NbFrames, 10, 64)
|
||||
if err == nil {
|
||||
if nbframes < 5 && !exceptions.HasException(":subtitle:tiny") {
|
||||
errs = append(errs, fmt.Errorf("too few subtitles"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("too few subtitles"))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("invalid number of frame: %q", s.NbFrames))
|
||||
errs = multierr.Append(errs, fmt.Errorf("invalid number of frame: %q", s.NbFrames))
|
||||
}
|
||||
} else if s.CodecType == "audio" {
|
||||
if !exceptions.HasException(":audio:allowed") {
|
||||
errs = append(errs, fmt.Errorf("an audio track is present, use subtitle for explainations"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("an audio track is present, use subtitle for explainations"))
|
||||
}
|
||||
|
||||
if s.CodecName != "aac" {
|
||||
errs = append(errs, fmt.Errorf("audio codec has to be AAC (Advanced Audio Coding) (currently: %s)", s.CodecLongName))
|
||||
errs = multierr.Append(errs, fmt.Errorf("audio codec has to be AAC (Advanced Audio Coding) (currently: %s)", s.CodecLongName))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("unknown track found of type %q", s.CodecType))
|
||||
errs = multierr.Append(errs, fmt.Errorf("unknown track found of type %q", s.CodecType))
|
||||
}
|
||||
}
|
||||
|
||||
if len(video_seen) == 0 {
|
||||
errs = append(errs, fmt.Errorf("no video track found"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("no video track found"))
|
||||
} else if len(video_seen) > 1 {
|
||||
errs = append(errs, fmt.Errorf("%d video tracks found, is it expected?", len(video_seen)))
|
||||
errs = multierr.Append(errs, fmt.Errorf("%d video tracks found, is it expected?", len(video_seen)))
|
||||
}
|
||||
if len(subtitles_seen) == 0 && !exceptions.HasException(":subtitle:no_track") {
|
||||
errs = append(errs, fmt.Errorf("no subtitles track found"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("no subtitles track found"))
|
||||
} else if len(subtitles_seen) > 0 {
|
||||
for _, idx := range subtitles_seen {
|
||||
language := e.Language
|
||||
if lang, ok := vInfo.Streams[idx].Tags["language"]; e.Language != "" && (!ok || lang == "" || lang == "und") {
|
||||
errs = append(errs, fmt.Errorf("subtitles track %d with no language defined", vInfo.Streams[idx].Index))
|
||||
errs = multierr.Append(errs, fmt.Errorf("subtitles track %d with no language defined", vInfo.Streams[idx].Index))
|
||||
} else {
|
||||
language = lang
|
||||
}
|
||||
|
||||
errs = append(errs, CheckGrammarSubtitleTrack(path, vInfo.Streams[idx].Index, language, exceptions)...)
|
||||
errs = multierr.Append(errs, CheckGrammarSubtitleTrack(path, vInfo.Streams[idx].Index, language, exceptions))
|
||||
}
|
||||
|
||||
if e.Language != "" && len(subtitles_seen) < 2 {
|
||||
errs = append(errs, fmt.Errorf("subtitle tracks must exist in original language and translated, only one subtitle track found"))
|
||||
errs = multierr.Append(errs, fmt.Errorf("subtitle tracks must exist in original language and translated, only one subtitle track found"))
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user