sync: Replace []error by go.uber.org/multierr

This commit is contained in:
nemunaire 2023-11-22 12:16:53 +01:00
parent 9f49a689fd
commit b6966d47ce
25 changed files with 380 additions and 348 deletions

View File

@ -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)

View File

@ -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 {

View File

@ -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))
}
}

View File

@ -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)))
}
}
}

View File

@ -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, &params.Flags[nline], nline+1, fmt.Errorf("identifier already used (%d), using a random one.", flag.Id)))
errs = multierr.Append(errs, NewFlagError(exercice, &params.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, &params.Flags[nline], nline+1, fmt.Errorf("dependancy on another scenario is not implemented yet.")))
errs = multierr.Append(errs, NewFlagError(exercice, &params.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)))
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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

View File

@ -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"))
}
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)
}
}

View File

@ -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))
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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"))
}
}