sync: Replace []error by go.uber.org/multierr
This commit is contained in:
parent
9f49a689fd
commit
b6966d47ce
25 changed files with 380 additions and 348 deletions
|
|
@ -5,6 +5,8 @@ import (
|
|||
"path"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
|
|
@ -130,12 +132,12 @@ func parseExerciceParams(i Importer, exPath string) (p ExerciceParams, md toml.M
|
|||
}
|
||||
|
||||
// getExerciceParams returns normalized
|
||||
func getExerciceParams(i Importer, exercice *fic.Exercice) (params ExerciceParams, errs []error) {
|
||||
func getExerciceParams(i Importer, exercice *fic.Exercice) (params ExerciceParams, errs error) {
|
||||
var err error
|
||||
if params, _, err = parseExerciceParams(i, exercice.Path); err != nil {
|
||||
errs = append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
} else if len(params.Flags) == 0 && len(params.FlagsUCQ) == 0 && len(params.FlagsMCQ) == 0 {
|
||||
errs = append(errs, NewChallengeTxtError(exercice, 0, fmt.Errorf("has no flag")))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, fmt.Errorf("has no flag")))
|
||||
} else {
|
||||
// Treat legacy UCQ flags as ExerciceFlag
|
||||
for _, flag := range params.FlagsUCQ {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"unicode"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
|
@ -50,7 +51,7 @@ func isURLAllowed(in string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files []string, digests map[string][]byte, errs []error) {
|
||||
func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files []string, digests map[string][]byte, errs error) {
|
||||
// If no files directory, don't display error
|
||||
if !i.Exists(path.Join(exercice.Path, into)) {
|
||||
return
|
||||
|
|
@ -58,15 +59,15 @@ func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files
|
|||
|
||||
// Parse DIGESTS.txt
|
||||
if digs, err := GetFileContent(i, path.Join(exercice.Path, into, "DIGESTS.txt")); err != nil {
|
||||
errs = append(errs, NewExerciceError(exercice, fmt.Errorf("unable to read %s: %w", path.Join(into, "DIGESTS.txt"), err)))
|
||||
errs = multierr.Append(errs, NewExerciceError(exercice, fmt.Errorf("unable to read %s: %w", path.Join(into, "DIGESTS.txt"), err)))
|
||||
} else {
|
||||
digests = map[string][]byte{}
|
||||
for nline, d := range strings.Split(digs, "\n") {
|
||||
if dsplt := strings.SplitN(d, " ", 2); len(dsplt) < 2 {
|
||||
errs = append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: invalid format", path.Join(into, "DIGESTS.txt"), nline+1)))
|
||||
errs = multierr.Append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: invalid format", path.Join(into, "DIGESTS.txt"), nline+1)))
|
||||
continue
|
||||
} else if hash, err := hex.DecodeString(dsplt[0]); err != nil {
|
||||
errs = append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: %w", path.Join(into, "DIGESTS.txt"), nline+1, err)))
|
||||
errs = multierr.Append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: %w", path.Join(into, "DIGESTS.txt"), nline+1, err)))
|
||||
continue
|
||||
} else {
|
||||
digests[strings.TrimFunc(dsplt[1], unicode.IsSpace)] = hash
|
||||
|
|
@ -76,7 +77,7 @@ func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files
|
|||
|
||||
// Read file list
|
||||
if flist, err := i.listDir(path.Join(exercice.Path, into)); err != nil {
|
||||
errs = append(errs, NewExerciceError(exercice, err))
|
||||
errs = multierr.Append(errs, NewExerciceError(exercice, err))
|
||||
} else {
|
||||
for _, fname := range flist {
|
||||
if fname == "DIGESTS.txt" || fname == ".gitattributes" {
|
||||
|
|
@ -128,9 +129,9 @@ func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files
|
|||
}
|
||||
|
||||
// CheckExerciceFilesPresence limits remote checks to presence, don't get it to check digest.
|
||||
func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []string, errs []error) {
|
||||
func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []string, errs error) {
|
||||
flist, digests, berrs := BuildFilesListInto(i, exercice, "files")
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
paramsFiles, _ := GetExerciceFilesParams(i, exercice)
|
||||
|
||||
|
|
@ -138,28 +139,28 @@ func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []str
|
|||
if !i.Exists(path.Join(exercice.Path, "files", fname)) && !i.Exists(path.Join(exercice.Path, "files", fname+".00")) {
|
||||
// File not found locally, is this a remote file?
|
||||
if pf, exists := paramsFiles[fname]; !exists || pf.URL == "" {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("No such file or directory")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("No such file or directory")))
|
||||
continue
|
||||
} else if !isURLAllowed(pf.URL) {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("URL hostname is not whitelisted")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("URL hostname is not whitelisted")))
|
||||
continue
|
||||
} else {
|
||||
resp, err := http.Head(pf.URL)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("Unexpected status code for the HTTP response: %d %s", resp.StatusCode, resp.Status)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("Unexpected status code for the HTTP response: %d %s", resp.StatusCode, resp.Status)))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := digests[fname]; !ok {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to import file: No digest given")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to import file: No digest given")))
|
||||
} else {
|
||||
files = append(files, fname)
|
||||
}
|
||||
|
|
@ -169,7 +170,7 @@ func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []str
|
|||
if !i.Exists(path.Join(exercice.Path, "files", fname)) && !i.Exists(path.Join(exercice.Path, "files", fname+".gz")) && !i.Exists(path.Join(exercice.Path, "files", fname+".00")) && !i.Exists(path.Join(exercice.Path, "files", fname+".gz.00")) {
|
||||
if pf, exists := paramsFiles[fname]; !exists || pf.URL == "" {
|
||||
if pf, exists := paramsFiles[fname+".gz"]; !exists || pf.URL == "" {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: No such file or directory. Check your DIGESTS.txt for legacy entries.")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: No such file or directory. Check your DIGESTS.txt for legacy entries.")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -179,13 +180,13 @@ func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []str
|
|||
}
|
||||
|
||||
// CheckExerciceFiles checks that remote files have the right digest.
|
||||
func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (files []string, errs []error) {
|
||||
func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (files []string, errs error) {
|
||||
flist, digests, berrs := BuildFilesListInto(i, exercice, "files")
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
paramsFiles, err := GetExerciceFilesParams(i, exercice)
|
||||
if err != nil {
|
||||
errs = append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
}
|
||||
|
||||
for _, fname := range flist {
|
||||
|
|
@ -193,14 +194,14 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
|
||||
if pf, exists := paramsFiles[fname]; exists && pf.URL != "" {
|
||||
if li, ok := i.(LocalImporter); ok {
|
||||
errs = append(errs, downloadExerciceFile(paramsFiles[fname], li.GetLocalPath(dest), exercice, false)...)
|
||||
errs = multierr.Append(errs, downloadExerciceFile(paramsFiles[fname], li.GetLocalPath(dest), exercice, false))
|
||||
} else {
|
||||
errs = append(errs, downloadExerciceFile(paramsFiles[fname], dest, exercice, false)...)
|
||||
errs = multierr.Append(errs, downloadExerciceFile(paramsFiles[fname], dest, exercice, false))
|
||||
}
|
||||
}
|
||||
|
||||
if fd, closer, err := GetFile(i, dest); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: %w", err)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: %w", err)))
|
||||
continue
|
||||
} else {
|
||||
defer closer()
|
||||
|
|
@ -208,9 +209,9 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
hash160, hash512 := fic.CreateHashBuffers(fd)
|
||||
|
||||
if _, err := fic.CheckBufferHash(hash160, hash512, digests[fname]); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
} else if size, err := GetFileSize(i, path.Join(exercice.Path, "files", fname)); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
} else {
|
||||
var digest_shown []byte
|
||||
if strings.HasSuffix(fname, ".gz") {
|
||||
|
|
@ -219,11 +220,11 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
|
||||
// Check that gunzipped file digest is correct
|
||||
if fd, closer, err := GetFile(i, path.Join(exercice.Path, "files", fname)); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: %w", err)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: %w", err)))
|
||||
continue
|
||||
} else if gunzipfd, err := gzip.NewReader(fd); err != nil {
|
||||
closer()
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to gunzip file: %w", err)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to gunzip file: %w", err)))
|
||||
continue
|
||||
} else {
|
||||
defer gunzipfd.Close()
|
||||
|
|
@ -232,7 +233,7 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
hash160_inflate, hash512_inflate := fic.CreateHashBuffers(gunzipfd)
|
||||
|
||||
if _, err := fic.CheckBufferHash(hash160_inflate, hash512_inflate, digest_shown); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, strings.TrimSuffix(fname, ".gz"), err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, strings.TrimSuffix(fname, ".gz"), err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -242,13 +243,13 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
if f, exists := paramsFiles[fname]; exists {
|
||||
// Call checks hooks
|
||||
for _, hk := range hooks.mdTextHooks {
|
||||
for _, err := range hk(f.Disclaimer, exercice.Language, exceptions) {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
for _, err := range multierr.Errors(hk(f.Disclaimer, exercice.Language, exceptions)) {
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
}
|
||||
}
|
||||
|
||||
if disclaimer, err = ProcessMarkdown(i, fixnbsp(f.Disclaimer), exercice.Path); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -256,8 +257,8 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.fileHooks {
|
||||
for _, e := range h(file, exercice, exceptions) {
|
||||
errs = append(errs, NewFileError(exercice, fname, e))
|
||||
for _, e := range multierr.Errors(h(file, exercice, exceptions)) {
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -269,7 +270,7 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
}
|
||||
|
||||
// downloadExerciceFile is responsible to fetch remote files.
|
||||
func downloadExerciceFile(pf ExerciceFile, dest string, exercice *fic.Exercice, force bool) (errs []error) {
|
||||
func downloadExerciceFile(pf ExerciceFile, dest string, exercice *fic.Exercice, force bool) (errs error) {
|
||||
if st, err := os.Stat(dest); !force && !os.IsNotExist(err) {
|
||||
resp, err := http.Head(pf.URL)
|
||||
if err == nil && resp.ContentLength == st.Size() {
|
||||
|
|
@ -278,33 +279,35 @@ func downloadExerciceFile(pf ExerciceFile, dest string, exercice *fic.Exercice,
|
|||
}
|
||||
|
||||
if !isURLAllowed(pf.URL) {
|
||||
errs = append(errs, NewFileError(exercice, path.Base(dest), fmt.Errorf("URL hostname is not whitelisted")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), fmt.Errorf("URL hostname is not whitelisted")))
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Download exercice file: ", pf.URL)
|
||||
|
||||
resp, err := http.Get(pf.URL)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err = os.MkdirAll(path.Dir(dest), 0751); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
return
|
||||
}
|
||||
|
||||
// Write file
|
||||
var fdto *os.File
|
||||
if fdto, err = os.Create(dest); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
return
|
||||
} else {
|
||||
defer fdto.Close()
|
||||
|
||||
_, err = io.Copy(fdto, resp.Body)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, path.Base(dest), err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -314,19 +317,19 @@ func downloadExerciceFile(pf ExerciceFile, dest string, exercice *fic.Exercice,
|
|||
|
||||
// SyncExerciceFiles reads the content of files/ directory and import it as EFile for the given challenge.
|
||||
// It takes care of DIGESTS.txt and ensure imported files match.
|
||||
func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (errs []error) {
|
||||
func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (errs error) {
|
||||
if _, err := exercice.WipeFiles(); err != nil {
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
}
|
||||
|
||||
paramsFiles, err := GetExerciceFilesParams(i, exercice)
|
||||
if err != nil {
|
||||
errs = append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
return
|
||||
}
|
||||
|
||||
files, digests, berrs := BuildFilesListInto(i, exercice, "files")
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
// Import standard files
|
||||
for _, fname := range files {
|
||||
|
|
@ -345,13 +348,13 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
|||
|
||||
// Call checks hooks
|
||||
for _, hk := range hooks.mdTextHooks {
|
||||
for _, err := range hk(f.Disclaimer, exercice.Language, exceptions) {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
for _, err := range multierr.Errors(hk(f.Disclaimer, exercice.Language, exceptions)) {
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
}
|
||||
}
|
||||
|
||||
if disclaimer, err = ProcessMarkdown(i, fixnbsp(f.Disclaimer), exercice.Path); err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err)))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("error during markdown formating of disclaimer: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -370,7 +373,7 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
|||
}
|
||||
|
||||
if f == nil {
|
||||
errs = append(errs, downloadExerciceFile(paramsFiles[fname], dest, exercice, false)...)
|
||||
errs = multierr.Append(errs, downloadExerciceFile(paramsFiles[fname], dest, exercice, false))
|
||||
|
||||
f, err = actionAfterImport(dest, pf.URL)
|
||||
}
|
||||
|
|
@ -379,19 +382,19 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
errs = append(errs, NewFileError(exercice, fname, err))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, err))
|
||||
continue
|
||||
}
|
||||
|
||||
if f.(*fic.EFile).Size == 0 {
|
||||
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("imported file is empty!")))
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, fmt.Errorf("imported file is empty!")))
|
||||
} else {
|
||||
file := f.(*fic.EFile)
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.fileHooks {
|
||||
for _, e := range h(file, exercice, exceptions) {
|
||||
errs = append(errs, NewFileError(exercice, fname, e))
|
||||
for _, e := range multierr.Errors(h(file, exercice, exceptions)) {
|
||||
errs = multierr.Append(errs, NewFileError(exercice, fname, e))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/multierr"
|
||||
_ "golang.org/x/crypto/blake2b"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
|
|
@ -23,10 +24,10 @@ type importHint struct {
|
|||
FlagsDeps []int64
|
||||
}
|
||||
|
||||
func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (hints []importHint, errs []error) {
|
||||
func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (hints []importHint, errs error) {
|
||||
params, _, err := parseExerciceParams(i, exercice.Path)
|
||||
if err != nil {
|
||||
errs = append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -45,10 +46,10 @@ func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
|
||||
if hint.Filename != "" {
|
||||
if hint.Content != "" {
|
||||
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be filled at the same time")))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be filled at the same time")))
|
||||
continue
|
||||
} else if !i.Exists(path.Join(exercice.Path, "hints", hint.Filename)) {
|
||||
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: File not found", hint.Filename)))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: File not found", hint.Filename)))
|
||||
continue
|
||||
} else {
|
||||
// Handle files as downloadable content
|
||||
|
|
@ -70,35 +71,35 @@ func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
// Special format for downloadable hints: $FILES + hexhash + path from FILES/
|
||||
return "$FILES" + hex.EncodeToString(result512) + strings.TrimPrefix(filePath, fic.FilesDir), nil
|
||||
}); err != nil {
|
||||
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: %w", hint.Filename, err)))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: %w", hint.Filename, err)))
|
||||
continue
|
||||
} else if s, ok := res.(string); !ok {
|
||||
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: invalid string returned as filename", hint.Filename)))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: invalid string returned as filename", hint.Filename)))
|
||||
continue
|
||||
} else {
|
||||
h.Content = s
|
||||
}
|
||||
}
|
||||
} else if hint.Content == "" {
|
||||
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be empty at the same time")))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be empty at the same time")))
|
||||
continue
|
||||
} else {
|
||||
// Call checks hooks
|
||||
for _, hk := range hooks.mdTextHooks {
|
||||
for _, err := range hk(h.Content, exercice.Language, exceptions) {
|
||||
errs = append(errs, NewHintError(exercice, h, n, err))
|
||||
for _, err := range multierr.Errors(hk(h.Content, exercice.Language, exceptions)) {
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, err))
|
||||
}
|
||||
}
|
||||
|
||||
if h.Content, err = ProcessMarkdown(i, fixnbsp(hint.Content), exercice.Path); err != nil {
|
||||
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("error during markdown formating: %w", err)))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, fmt.Errorf("error during markdown formating: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
// Call checks hooks
|
||||
for _, hook := range hooks.hintHooks {
|
||||
for _, e := range hook(h, exercice, exceptions) {
|
||||
errs = append(errs, NewHintError(exercice, h, n, e))
|
||||
for _, e := range multierr.Errors(hook(h, exercice, exceptions)) {
|
||||
errs = multierr.Append(errs, NewHintError(exercice, h, n, e))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,28 +120,28 @@ func buildExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
}
|
||||
|
||||
// CheckExerciceHints checks if all hints are corrects..
|
||||
func CheckExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) ([]importHint, []error) {
|
||||
func CheckExerciceHints(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) ([]importHint, error) {
|
||||
exceptions = exceptions.GetFileExceptions("challenge.txt", "challenge.toml")
|
||||
|
||||
return buildExerciceHints(i, exercice, exceptions)
|
||||
}
|
||||
|
||||
// SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge.
|
||||
func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int64]fic.Flag, exceptions *CheckExceptions) (hintsBindings map[int]*fic.EHint, errs []error) {
|
||||
func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int64]fic.Flag, exceptions *CheckExceptions) (hintsBindings map[int]*fic.EHint, errs error) {
|
||||
if _, err := exercice.WipeHints(); err != nil {
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
} else {
|
||||
exceptions = exceptions.GetFileExceptions("challenge.txt", "challenge.toml")
|
||||
|
||||
hints, berrs := buildExerciceHints(i, exercice, exceptions)
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
hintsBindings = map[int]*fic.EHint{}
|
||||
|
||||
for _, hint := range hints {
|
||||
// Import hint
|
||||
if h, err := exercice.AddHint(hint.Hint.Title, hint.Hint.Content, hint.Hint.Cost); err != nil {
|
||||
errs = append(errs, NewHintError(exercice, hint.Hint, hint.Line, err))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, err))
|
||||
} else {
|
||||
hintsBindings[hint.Line] = h
|
||||
|
||||
|
|
@ -148,10 +149,10 @@ func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int
|
|||
for _, nf := range hint.FlagsDeps {
|
||||
if f, ok := flagsBindings[nf]; ok {
|
||||
if herr := h.AddDepend(f); herr != nil {
|
||||
errs = append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: %w", nf, herr)))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: %w", nf, herr)))
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: Unexistant flag", nf)))
|
||||
errs = multierr.Append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: Unexistant flag", nf)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"unicode"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
|
@ -35,11 +36,11 @@ func validatorRegexp(vre string) (validator_regexp *string) {
|
|||
return
|
||||
}
|
||||
|
||||
func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bool, separator string) (raw string, prep string, errs []error) {
|
||||
func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bool, separator string) (raw string, prep string, errs error) {
|
||||
// Concatenate array
|
||||
if f, ok := input.([]interface{}); ok {
|
||||
if len(validatorRe) > 0 {
|
||||
errs = append(errs, fmt.Errorf("ValidatorRe cannot be defined for this kind of flag."))
|
||||
errs = multierr.Append(errs, fmt.Errorf("ValidatorRe cannot be defined for this kind of flag."))
|
||||
validatorRe = ""
|
||||
}
|
||||
|
||||
|
|
@ -47,20 +48,20 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
|
|||
separator = ","
|
||||
} else if len(separator) > 1 {
|
||||
separator = string(separator[0])
|
||||
errs = append(errs, fmt.Errorf("separator truncated to %q", separator))
|
||||
errs = multierr.Append(errs, fmt.Errorf("separator truncated to %q", separator))
|
||||
}
|
||||
|
||||
var fitems []string
|
||||
for i, v := range f {
|
||||
if g, ok := v.(string); ok {
|
||||
if strings.Index(g, separator) != -1 {
|
||||
errs = append(errs, fmt.Errorf("flag items cannot contain %q character as it is used as separator. Change the separator attribute for this flag.", separator))
|
||||
errs = multierr.Append(errs, fmt.Errorf("flag items cannot contain %q character as it is used as separator. Change the separator attribute for this flag.", separator))
|
||||
return
|
||||
} else {
|
||||
fitems = append(fitems, g)
|
||||
}
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("item %d has an invalid type: can only be string, is %T.", i, g))
|
||||
errs = multierr.Append(errs, fmt.Errorf("item %d has an invalid type: can only be string, is %T.", i, g))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +78,7 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
|
|||
nbLines := 0
|
||||
if showLines {
|
||||
if len(fitems) > 9 {
|
||||
errs = append(errs, fmt.Errorf("too much items in vector to use ShowLines features, max 9."))
|
||||
errs = multierr.Append(errs, fmt.Errorf("too much items in vector to use ShowLines features, max 9."))
|
||||
} else {
|
||||
nbLines = len(fitems)
|
||||
}
|
||||
|
|
@ -91,7 +92,7 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
|
|||
} else if f, ok := input.(float64); ok {
|
||||
raw = strconv.FormatFloat(f, 'f', -1, 64)
|
||||
} else if f, ok := input.(string); !ok {
|
||||
errs = append(errs, fmt.Errorf("has an invalid type: can only be []string or string, not %T", input))
|
||||
errs = multierr.Append(errs, fmt.Errorf("has an invalid type: can only be []string or string, not %T", input))
|
||||
return
|
||||
} else {
|
||||
raw = f
|
||||
|
|
@ -99,21 +100,21 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
|
|||
return
|
||||
}
|
||||
|
||||
func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, exceptions *CheckExceptions) (f *fic.FlagLabel, errs []error) {
|
||||
func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, exceptions *CheckExceptions) (f *fic.FlagLabel, errs error) {
|
||||
if len(flag.Label) == 0 {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label cannot be empty.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label cannot be empty.")))
|
||||
return
|
||||
}
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(flag.Label, exercice.Language, exceptions.Filter2ndCol(strconv.Itoa(flagline))) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, err))
|
||||
for _, err := range multierr.Errors(h(flag.Label, exercice.Language, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, err))
|
||||
}
|
||||
}
|
||||
|
||||
if mdlabel, err := ProcessMarkdown(GlobalImporter, flag.Label, exercice.Path); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("unable to parse property label as Markdown: %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("unable to parse property label as Markdown: %w", err)))
|
||||
} else {
|
||||
if strings.Count(flag.Label, "\n\n") == 0 {
|
||||
flag.Label = mdlabel[3 : len(mdlabel)-4]
|
||||
|
|
@ -123,11 +124,11 @@ func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, exc
|
|||
}
|
||||
|
||||
if flag.Raw != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("raw cannot be defined.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("raw cannot be defined.")))
|
||||
}
|
||||
|
||||
if len(flag.Choice) != 0 {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("choices cannot be defined.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("choices cannot be defined.")))
|
||||
}
|
||||
|
||||
f = &fic.FlagLabel{
|
||||
|
|
@ -138,32 +139,33 @@ func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, exc
|
|||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.flagLabelHooks {
|
||||
for _, e := range h(f, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline))) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
for _, e := range multierr.Errors(h(f, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defaultLabel string, exceptions *CheckExceptions) (f *fic.Flag, choices []*fic.FlagChoice, errs []error) {
|
||||
func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defaultLabel string, exceptions *CheckExceptions) (f *fic.Flag, choices []*fic.FlagChoice, errs error) {
|
||||
if len(flag.Label) == 0 {
|
||||
flag.Label = defaultLabel
|
||||
}
|
||||
|
||||
if len(flag.Variant) != 0 {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("variant is not defined for this kind of flag.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("variant is not defined for this kind of flag.")))
|
||||
}
|
||||
|
||||
if flag.Label[0] == '`' {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label should not begin with `.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label should not begin with `.")))
|
||||
flag.Label = flag.Label[1:]
|
||||
}
|
||||
|
||||
raw, prep, terrs := getRawKey(flag.Raw, flag.CaptureRe, flag.Ordered, flag.ShowLines, flag.Separator)
|
||||
|
||||
if len(terrs) > 0 {
|
||||
for _, terr := range terrs {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, terr))
|
||||
errors := multierr.Errors(terrs)
|
||||
if len(errors) > 0 {
|
||||
for _, terr := range errors {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, terr))
|
||||
}
|
||||
f = nil
|
||||
return
|
||||
|
|
@ -171,16 +173,16 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
|||
flag.Label = prep + flag.Label
|
||||
|
||||
if len(flag.Label) > 255 && flag.Type != "label" {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("label is too long (max 255 chars per label).")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("label is too long (max 255 chars per label).")))
|
||||
}
|
||||
|
||||
if (flag.Type == "text" && !isFullGraphic(strings.Replace(raw, "\n", "", -1))) || (flag.Type != "text" && !isFullGraphic(raw)) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("WARNING non-printable characters in flag, is this really expected?")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("WARNING non-printable characters in flag, is this really expected?")))
|
||||
}
|
||||
|
||||
hashedFlag, err := fic.ComputeHashedFlag([]byte(raw), !flag.CaseSensitive, flag.NoTrim, validatorRegexp(flag.CaptureRe), flag.SortReGroups)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, err))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, err))
|
||||
return
|
||||
}
|
||||
fk := &fic.FlagKey{
|
||||
|
|
@ -202,8 +204,8 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
|||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.flagKeyHooks {
|
||||
for _, e := range h(fk, raw, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline))) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
for _, e := range multierr.Errors(h(fk, raw, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,9 +224,10 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
|||
|
||||
for _, choice := range flag.Choice {
|
||||
val, prep, terrs := getRawKey(choice.Value, "", false, false, "")
|
||||
if len(terrs) > 0 {
|
||||
for _, terr := range terrs {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, terr))
|
||||
errors := multierr.Errors(terrs)
|
||||
if len(errors) > 0 {
|
||||
for _, terr := range errors {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, terr))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
|
@ -245,15 +248,15 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
|||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.flagChoiceHooks {
|
||||
for _, e := range h(fc, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline))) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
for _, e := range multierr.Errors(h(fc, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
}
|
||||
}
|
||||
|
||||
choices = append(choices, fc)
|
||||
|
||||
if val == "true" || val == "false" {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("value can't be %q, this is not a MCQ, the value has to be meaningful. The value is shown to players as response identifier.", val)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("value can't be %q, this is not a MCQ, the value has to be meaningful. The value is shown to players as response identifier.", val)))
|
||||
}
|
||||
|
||||
if val == raw || (!flag.CaseSensitive && val == strings.ToLower(raw)) {
|
||||
|
|
@ -261,13 +264,13 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
|||
}
|
||||
}
|
||||
if !hasOne {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("no valid answer defined.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("no valid answer defined.")))
|
||||
}
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.flagKeyWithChoicesHooks {
|
||||
for _, e := range h(fk, raw, choices, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline))) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
for _, e := range multierr.Errors(h(fk, raw, choices, exercice, exceptions.Filter2ndCol(strconv.Itoa(flagline)))) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -297,7 +300,7 @@ func iface2Number(input interface{}, output *string) error {
|
|||
}
|
||||
|
||||
// buildExerciceFlag read challenge.txt and extract all flags.
|
||||
func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nline int, exceptions *CheckExceptions) (ret []importFlag, errs []error) {
|
||||
func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nline int, exceptions *CheckExceptions) (ret []importFlag, errs error) {
|
||||
switch strings.ToLower(flag.Type) {
|
||||
case "":
|
||||
flag.Type = "key"
|
||||
|
|
@ -309,17 +312,17 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
|||
var smin, smax, sstep string
|
||||
err := iface2Number(flag.NumberMin, &smin)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("min %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("min %w", err)))
|
||||
}
|
||||
|
||||
err = iface2Number(flag.NumberMax, &smax)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("max %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("max %w", err)))
|
||||
}
|
||||
|
||||
err = iface2Number(flag.NumberStep, &sstep)
|
||||
if err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("step %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("step %w", err)))
|
||||
}
|
||||
flag.Type = fmt.Sprintf("number,%s,%s,%s", smin, smax, sstep)
|
||||
case "text":
|
||||
|
|
@ -333,23 +336,23 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
|||
case "mcq":
|
||||
flag.Type = "mcq"
|
||||
default:
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("invalid type of flag: should be 'key', 'number', 'text', 'mcq', 'ucq', 'radio' or 'vector'")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("invalid type of flag: should be 'key', 'number', 'text', 'mcq', 'ucq', 'radio' or 'vector'")))
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(flag.Type, "number") {
|
||||
if flag.NumberMin != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property min undefined for this kind of flag: should the type be 'number'")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property min undefined for this kind of flag: should the type be 'number'")))
|
||||
} else if flag.NumberMax != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property max undefined for this kind of flag: should the type be 'number'")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property max undefined for this kind of flag: should the type be 'number'")))
|
||||
} else if flag.NumberStep != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property step undefined for this kind of flag: should the type be 'number'")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("property step undefined for this kind of flag: should the type be 'number'")))
|
||||
}
|
||||
}
|
||||
|
||||
if len(flag.Help) > 0 {
|
||||
if mdhelp, err := ProcessMarkdown(i, flag.Help, exercice.Path); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("unable to parse property help as Markdown: %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("unable to parse property help as Markdown: %w", err)))
|
||||
} else {
|
||||
flag.Help = mdhelp[3 : len(mdhelp)-4]
|
||||
}
|
||||
|
|
@ -357,7 +360,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
|||
|
||||
if flag.Type == "label" {
|
||||
addedFlag, berrs := buildLabelFlag(exercice, flag, nline+1, exceptions)
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
if addedFlag != nil {
|
||||
ret = append(ret, importFlag{
|
||||
Line: nline + 1,
|
||||
|
|
@ -366,7 +369,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
|||
}
|
||||
} else if flag.Type == "key" || strings.HasPrefix(flag.Type, "number") || flag.Type == "text" || flag.Type == "ucq" || flag.Type == "radio" || flag.Type == "vector" {
|
||||
addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag", exceptions)
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
if addedFlag != nil {
|
||||
ret = append(ret, importFlag{
|
||||
Line: nline + 1,
|
||||
|
|
@ -386,7 +389,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
|||
isJustified := false
|
||||
|
||||
if len(flag.Variant) != 0 {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("variant is not defined for this kind of flag")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("variant is not defined for this kind of flag")))
|
||||
}
|
||||
|
||||
if !flag.NoShuffle {
|
||||
|
|
@ -400,7 +403,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
|||
|
||||
if choice.Raw != nil {
|
||||
if hasOne && !isJustified {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -409,13 +412,13 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
|||
} else if p, ok := choice.Value.(bool); ok {
|
||||
val = p
|
||||
if isJustified {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
|
||||
continue
|
||||
}
|
||||
} else if choice.Value == nil {
|
||||
val = false
|
||||
} else {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("choice %d: incorrect type for value: %T is not boolean.", cid, choice.Value)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("choice %d: incorrect type for value: %T is not boolean.", cid, choice.Value)))
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -427,9 +430,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
|||
|
||||
if isJustified && choice.Raw != nil {
|
||||
addedFlag, choices, berrs := buildKeyFlag(exercice, choice.ExerciceFlag, nline+1, "Flag correspondant", exceptions)
|
||||
if len(berrs) > 0 {
|
||||
errs = append(errs, berrs...)
|
||||
}
|
||||
errs = multierr.Append(errs, berrs)
|
||||
if addedFlag != nil {
|
||||
ret = append(ret, importFlag{
|
||||
Line: nline + 1,
|
||||
|
|
@ -443,8 +444,8 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
|||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.flagMCQHooks {
|
||||
for _, e := range h(&addedFlag, addedFlag.Entries, exercice, exceptions) {
|
||||
errs = append(errs, NewFlagError(exercice, &flag, nline+1, e))
|
||||
for _, e := range multierr.Errors(h(&addedFlag, addedFlag.Entries, exercice, exceptions)) {
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, &flag, nline+1, e))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -457,9 +458,9 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
|
|||
}
|
||||
|
||||
// buildExerciceFlags read challenge.txt and extract all flags.
|
||||
func buildExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (flags map[int64]importFlag, flagids []int64, errs []error) {
|
||||
func buildExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (flags map[int64]importFlag, flagids []int64, errs error) {
|
||||
params, gerrs := getExerciceParams(i, exercice)
|
||||
if len(gerrs) > 0 {
|
||||
if len(multierr.Errors(gerrs)) > 0 {
|
||||
return flags, flagids, gerrs
|
||||
}
|
||||
|
||||
|
|
@ -473,14 +474,12 @@ func buildExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
|
||||
// Ensure flag ID is unique
|
||||
for _, ok := flags[flag.Id]; ok; _, ok = flags[flag.Id] {
|
||||
errs = append(errs, NewFlagError(exercice, ¶ms.Flags[nline], nline+1, fmt.Errorf("identifier already used (%d), using a random one.", flag.Id)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, ¶ms.Flags[nline], nline+1, fmt.Errorf("identifier already used (%d), using a random one.", flag.Id)))
|
||||
flag.Id = rand.Int63()
|
||||
}
|
||||
|
||||
newFlags, ferrs := buildExerciceFlag(i, exercice, flag, nline, exceptions)
|
||||
if len(ferrs) > 0 {
|
||||
errs = append(errs, ferrs...)
|
||||
}
|
||||
errs = multierr.Append(errs, ferrs)
|
||||
if len(newFlags) > 0 {
|
||||
for _, newFlag := range newFlags {
|
||||
fId := flag.Id
|
||||
|
|
@ -491,7 +490,7 @@ func buildExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
// Read dependency to flag
|
||||
for _, nf := range flag.NeedFlag {
|
||||
if len(nf.Theme) > 0 {
|
||||
errs = append(errs, NewFlagError(exercice, ¶ms.Flags[nline], nline+1, fmt.Errorf("dependancy on another scenario is not implemented yet.")))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, ¶ms.Flags[nline], nline+1, fmt.Errorf("dependancy on another scenario is not implemented yet.")))
|
||||
}
|
||||
newFlag.FlagsDeps = append(newFlag.FlagsDeps, nf.Id)
|
||||
}
|
||||
|
|
@ -514,18 +513,18 @@ func buildExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExc
|
|||
}
|
||||
|
||||
// CheckExerciceFlags checks if all flags for the given challenge are correct.
|
||||
func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exceptions *CheckExceptions) (rf []fic.Flag, errs []error) {
|
||||
func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exceptions *CheckExceptions) (rf []fic.Flag, errs error) {
|
||||
exceptions = exceptions.GetFileExceptions("challenge.txt", "challenge.toml")
|
||||
|
||||
flags, flagsids, berrs := buildExerciceFlags(i, exercice, exceptions)
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
for _, flagid := range flagsids {
|
||||
if flag, ok := flags[flagid]; ok {
|
||||
// Check dependency to flag
|
||||
for _, nf := range flag.FlagsDeps {
|
||||
if _, ok := flags[nf]; !ok {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on flag id=%d: id not defined", nf)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on flag id=%d: id not defined", nf)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -533,7 +532,7 @@ func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exce
|
|||
deps := flag.FlagsDeps
|
||||
for i := 0; i < len(deps); i++ {
|
||||
if deps[i] == flagid {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag dependency loop detected: flag id=%d: depends on itself", flagid)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag dependency loop detected: flag id=%d: depends on itself", flagid)))
|
||||
break
|
||||
}
|
||||
|
||||
|
|
@ -558,7 +557,7 @@ func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string, exce
|
|||
}
|
||||
}
|
||||
if !found {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on %s: No such file", lf)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on %s: No such file", lf)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -587,16 +586,16 @@ func ExerciceFlagsMap(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.Fl
|
|||
}
|
||||
|
||||
// SyncExerciceFlags imports all kind of flags for the given challenge.
|
||||
func SyncExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (kmap map[int64]fic.Flag, errs []error) {
|
||||
func SyncExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExceptions) (kmap map[int64]fic.Flag, errs error) {
|
||||
if _, err := exercice.WipeFlags(); err != nil {
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
} else if _, err := exercice.WipeMCQs(); err != nil {
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
} else {
|
||||
exceptions = exceptions.GetFileExceptions("challenge.txt", "challenge.toml")
|
||||
|
||||
flags, flagids, berrs := buildExerciceFlags(i, exercice, exceptions)
|
||||
errs = append(errs, berrs...)
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
kmap = map[int64]fic.Flag{}
|
||||
|
||||
|
|
@ -610,12 +609,12 @@ func SyncExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
|||
}
|
||||
|
||||
if addedFlag, err := exercice.AddFlag(flag.Flag); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, err))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, err))
|
||||
} else {
|
||||
if f, ok := addedFlag.(*fic.FlagKey); ok {
|
||||
for _, choice := range flag.Choices {
|
||||
if _, err := f.AddChoice(choice); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("choice #FIXME: %w", err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("choice #FIXME: %w", err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -625,18 +624,18 @@ func SyncExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExce
|
|||
// Import dependency to flag
|
||||
for _, nf := range flag.FlagsDeps {
|
||||
if rf, ok := kmap[nf]; !ok {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to flag id=%d: id not defined, perhaps not available at time of processing", nf)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to flag id=%d: id not defined, perhaps not available at time of processing", nf)))
|
||||
} else if err := addedFlag.AddDepend(rf); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to id=%d: %w", nf, err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to id=%d: %w", nf, err)))
|
||||
}
|
||||
}
|
||||
|
||||
// Import dependency to file
|
||||
for _, lf := range flag.FilesDeps {
|
||||
if rf, err := exercice.GetFileByFilename(lf); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
|
||||
} else if err := rf.AddDepend(addedFlag); err != nil {
|
||||
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
|
||||
errs = multierr.Append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/BurntSushi/toml"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yuin/goldmark"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
|
@ -93,7 +94,7 @@ func parseExerciceDirname(edir string) (eid int, ename string, err error) {
|
|||
}
|
||||
|
||||
// BuildExercice creates an Exercice from a given importer.
|
||||
func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice, exceptions_in *CheckExceptions) (e *fic.Exercice, p ExerciceParams, eid int, exceptions *CheckExceptions, edir string, errs []error) {
|
||||
func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice, exceptions_in *CheckExceptions) (e *fic.Exercice, p ExerciceParams, eid int, exceptions *CheckExceptions, edir string, errs error) {
|
||||
e = &fic.Exercice{}
|
||||
|
||||
e.Path = epath
|
||||
|
|
@ -104,7 +105,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
if err != nil {
|
||||
// Ignore eid if we are certain this is an exercice directory, eid will be 0
|
||||
if !i.Exists(path.Join(epath, "title.txt")) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to parse exercice directory: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to parse exercice directory: %w", err), theme))
|
||||
return nil, p, eid, exceptions_in, edir, errs
|
||||
}
|
||||
}
|
||||
|
|
@ -118,7 +119,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
if language, err := GetFileContent(i, path.Join(epath, "language.txt")); err == nil {
|
||||
language = strings.TrimSpace(language)
|
||||
if strings.Contains(language, "\n") {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("language.txt: Language can't contain new lines"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("language.txt: Language can't contain new lines"), theme))
|
||||
} else {
|
||||
e.Language = language
|
||||
}
|
||||
|
|
@ -128,7 +129,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
if myTitle, err := GetFileContent(i, path.Join(epath, "title.txt")); err == nil {
|
||||
myTitle = strings.TrimSpace(myTitle)
|
||||
if strings.Contains(myTitle, "\n") {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("title.txt: Title can't contain new lines"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("title.txt: Title can't contain new lines"), theme))
|
||||
} else {
|
||||
e.Title = myTitle
|
||||
}
|
||||
|
|
@ -136,7 +137,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
|
||||
// Character reserved for WIP exercices
|
||||
if len(e.Title) > 0 && e.Title[0] == '%' {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("title can't contain start by '%%'"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("title can't contain start by '%%'"), theme))
|
||||
}
|
||||
|
||||
e.URLId = fic.ToURLid(e.Title)
|
||||
|
|
@ -144,7 +145,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
|
||||
if i.Exists(path.Join(epath, "AUTHORS.txt")) {
|
||||
if authors, err := getAuthors(i, epath); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
|
||||
} else {
|
||||
// Format authors
|
||||
e.Authors = strings.Join(authors, ", ")
|
||||
|
|
@ -158,13 +159,13 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
e.Headline, err = GetFileContent(i, path.Join(epath, "headline.md"))
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to get exercice's headline: %w", err)))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to get exercice's headline: %w", err)))
|
||||
}
|
||||
if e.Headline != "" {
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(e.Headline, e.Language, exceptions.GetFileExceptions("headline.md", "headline.txt")) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("headline.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(e.Headline, e.Language, exceptions.GetFileExceptions("headline.md", "headline.txt"))) {
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("headline.md: %w", err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -178,14 +179,14 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
err = fmt.Errorf("Unable to find overview.txt nor overview.md")
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.txt: %s", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.txt: %s", err), theme))
|
||||
} else {
|
||||
e.Overview = fixnbsp(e.Overview)
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(e.Overview, e.Language, exceptions.GetFileExceptions("overview.md", "overview.txt")) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(e.Overview, e.Language, exceptions.GetFileExceptions("overview.md", "overview.txt"))) {
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.md: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,14 +194,14 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
if e.Headline == "" {
|
||||
err := goldmark.Convert([]byte(strings.Split(e.Overview, "\n")[0]), &buf)
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating of the headline: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating of the headline: %w", err), theme))
|
||||
} else {
|
||||
e.Headline = string(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
if e.Overview, err = ProcessMarkdown(i, e.Overview, epath); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating: %w", err), theme))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,17 +213,17 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
err = fmt.Errorf("Unable to find statement.txt nor statement.md")
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("statement.md: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("statement.md: %w", err), theme))
|
||||
} else {
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(e.Statement, e.Language, exceptions.GetFileExceptions("statement.md", "statement.txt")) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("statement.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(e.Statement, e.Language, exceptions.GetFileExceptions("statement.md", "statement.txt"))) {
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("statement.md: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
if e.Statement, err = ProcessMarkdown(i, fixnbsp(e.Statement), epath); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("statement.md: an error occurs during markdown formating: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("statement.md: an error occurs during markdown formating: %w", err), theme))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -232,17 +233,17 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
e.Finished, err = GetFileContent(i, path.Join(epath, "finished.md"))
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("finished.md: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("finished.md: %w", err), theme))
|
||||
} else if len(e.Finished) > 0 {
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(e.Finished, e.Language, exceptions.GetFileExceptions("finished.md", "finished.txt")) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("finished.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(e.Finished, e.Language, exceptions.GetFileExceptions("finished.md", "finished.txt"))) {
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("finished.md: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
if e.Finished, err = ProcessMarkdown(i, e.Finished, epath); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("finished.md: an error occurs during markdown formating: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("finished.md: an error occurs during markdown formating: %w", err), theme))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -251,31 +252,31 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
} else if i.Exists(path.Join(epath, "heading.png")) {
|
||||
e.Image = path.Join(epath, "heading.png")
|
||||
} else if theme.Image == "" {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("heading.jpg: No such file")))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("heading.jpg: No such file")))
|
||||
}
|
||||
|
||||
// Parse challenge.txt
|
||||
var md toml.MetaData
|
||||
p, md, err = parseExerciceParams(i, epath)
|
||||
if err != nil {
|
||||
errs = append(errs, NewChallengeTxtError(e, 0, err, theme))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(e, 0, err, theme))
|
||||
return
|
||||
}
|
||||
|
||||
// Alert about unknown keys in challenge.txt
|
||||
if len(md.Undecoded()) > 0 {
|
||||
for _, k := range md.Undecoded() {
|
||||
errs = append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("unknown key %q found, check https://fic.srs.epita.fr/doc/files/challenge/", k), theme))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("unknown key %q found, check https://fic.srs.epita.fr/doc/files/challenge/", k), theme))
|
||||
}
|
||||
}
|
||||
|
||||
e.WIP = p.WIP
|
||||
if p.WIP && !AllowWIPExercice {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("exercice declared Work In Progress in challenge.txt"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("exercice declared Work In Progress in challenge.txt"), theme))
|
||||
}
|
||||
|
||||
if p.Gain == 0 {
|
||||
errs = append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("Undefined gain for challenge"), theme))
|
||||
errs = multierr.Append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("Undefined gain for challenge"), theme))
|
||||
} else {
|
||||
e.Gain = p.Gain
|
||||
}
|
||||
|
|
@ -283,11 +284,11 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
// Handle dependency
|
||||
if len(p.Dependencies) > 0 {
|
||||
if len(p.Dependencies[0].Theme) > 0 && p.Dependencies[0].Theme != theme.Name {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to treat dependency to another theme (%q): not implemented.", p.Dependencies[0].Theme), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to treat dependency to another theme (%q): not implemented.", p.Dependencies[0].Theme), theme))
|
||||
} else {
|
||||
if dmap == nil {
|
||||
if dmap2, err := buildDependancyMap(i, theme); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to build dependency map: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to build dependency map: %w", err), theme))
|
||||
} else {
|
||||
dmap = &dmap2
|
||||
}
|
||||
|
|
@ -305,7 +306,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
for k, _ := range *dmap {
|
||||
dmap_keys = append(dmap_keys, fmt.Sprintf("%d", k))
|
||||
}
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("Unable to find required exercice dependancy %d (available at time of processing: %s)", p.Dependencies[0].Id, strings.Join(dmap_keys, ",")), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("Unable to find required exercice dependancy %d (available at time of processing: %s)", p.Dependencies[0].Id, strings.Join(dmap_keys, ",")), theme))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -318,10 +319,10 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
if !i.Exists(e.VideoURI) {
|
||||
e.VideoURI = ""
|
||||
} else if size, err := GetFileSize(i, e.VideoURI); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: %w", err), theme))
|
||||
e.VideoURI = ""
|
||||
} else if size == 0 {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: The file is empty!"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: The file is empty!"), theme))
|
||||
e.VideoURI = ""
|
||||
} else {
|
||||
e.VideoURI = strings.Replace(url.PathEscape(path.Join("$RFILES$", e.VideoURI)), "%2F", "/", -1)
|
||||
|
|
@ -335,21 +336,21 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
|
||||
if i.Exists(writeup) {
|
||||
if size, err := GetFileSize(i, writeup); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
|
||||
} else if size == 0 {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: The file is empty!"), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: The file is empty!"), theme))
|
||||
} else if e.Resolution, err = GetFileContent(i, writeup); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
|
||||
} else {
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(e.Resolution, e.Language, exceptions.GetFileExceptions("resolution.md"), p.GetRawFlags()...) {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(e.Resolution, e.Language, exceptions.GetFileExceptions("resolution.md"), p.GetRawFlags()...)) {
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
if e.Resolution, err = ProcessMarkdown(i, e.Resolution, epath); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: error during markdown processing: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: error during markdown processing: %w", err), theme))
|
||||
} else {
|
||||
resolutionFound = true
|
||||
}
|
||||
|
|
@ -357,28 +358,26 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
|
|||
}
|
||||
|
||||
if !resolutionFound {
|
||||
errs = append(errs, NewExerciceError(e, ErrResolutionNotFound, theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, ErrResolutionNotFound, theme))
|
||||
}
|
||||
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.exerciceHooks {
|
||||
for _, err := range h(e, exceptions) {
|
||||
errs = append(errs, NewExerciceError(e, err))
|
||||
for _, err := range multierr.Errors(h(e, exceptions)) {
|
||||
errs = multierr.Append(errs, NewExerciceError(e, err))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SyncExercice imports new or updates existing given exercice.
|
||||
func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice, exceptions_in *CheckExceptions) (e *fic.Exercice, eid int, exceptions *CheckExceptions, errs []error) {
|
||||
func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice, exceptions_in *CheckExceptions) (e *fic.Exercice, eid int, exceptions *CheckExceptions, errs error) {
|
||||
var err error
|
||||
var p ExerciceParams
|
||||
var berrors []error
|
||||
var berrors error
|
||||
|
||||
e, p, eid, exceptions, _, berrors = BuildExercice(i, theme, epath, dmap, exceptions_in)
|
||||
for _, e := range berrors {
|
||||
errs = append(errs, e)
|
||||
}
|
||||
errs = multierr.Append(errs, berrors)
|
||||
|
||||
if e != nil {
|
||||
if len(e.Image) > 0 {
|
||||
|
|
@ -396,24 +395,24 @@ func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*f
|
|||
|
||||
return nil, err
|
||||
}); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to import heading image: %w", err)))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to import heading image: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
// Create or update the exercice
|
||||
err = theme.SaveNamedExercice(e)
|
||||
if err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("error on exercice save: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("error on exercice save: %w", err), theme))
|
||||
return
|
||||
}
|
||||
|
||||
// Import eercice tags
|
||||
if _, err := e.WipeTags(); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to wipe tags: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to wipe tags: %w", err), theme))
|
||||
}
|
||||
for _, tag := range p.Tags {
|
||||
if _, err := e.AddTag(tag); err != nil {
|
||||
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to add tag: %w", err), theme))
|
||||
errs = multierr.Append(errs, NewExerciceError(e, fmt.Errorf("unable to add tag: %w", err), theme))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -423,9 +422,9 @@ func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*f
|
|||
}
|
||||
|
||||
// SyncExercices imports new or updates existing exercices, in a given theme.
|
||||
func SyncExercices(i Importer, theme *fic.Theme, exceptions *CheckExceptions) (exceptions_out map[int]*CheckExceptions, errs []error) {
|
||||
func SyncExercices(i Importer, theme *fic.Theme, exceptions *CheckExceptions) (exceptions_out map[int]*CheckExceptions, errs error) {
|
||||
if exercices, err := GetExercices(i, theme); err != nil {
|
||||
errs = append(errs, err)
|
||||
errs = multierr.Append(errs, err)
|
||||
} else {
|
||||
exceptions_out = make(map[int]*CheckExceptions)
|
||||
emap := map[string]int{}
|
||||
|
|
@ -438,7 +437,7 @@ func SyncExercices(i Importer, theme *fic.Theme, exceptions *CheckExceptions) (e
|
|||
emap[e.Title] = eid
|
||||
dmap[int64(eid)] = e
|
||||
exceptions_out[eid] = ex_exceptions
|
||||
errs = append(errs, cur_errs...)
|
||||
errs = multierr.Append(errs, cur_errs)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"srs.epita.fr/fic-server/admin/generation"
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
|
@ -49,7 +51,7 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) {
|
|||
|
||||
errs.DateStart = startTime
|
||||
exceptions, sterrs := SyncThemes(i)
|
||||
for _, sterr := range sterrs {
|
||||
for _, sterr := range multierr.Errors(sterrs) {
|
||||
errs.ThemesSync = append(errs.ThemesSync, sterr.Error())
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +62,7 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) {
|
|||
for tid, theme := range themes {
|
||||
DeepSyncProgress = 3 + uint8(tid)*themeStep
|
||||
ex_exceptions, seerrs := SyncExercices(i, theme, exceptions[theme.Path])
|
||||
for _, seerr := range seerrs {
|
||||
for _, seerr := range multierr.Errors(seerrs) {
|
||||
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], seerr.Error())
|
||||
}
|
||||
|
||||
|
|
@ -74,13 +76,13 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) {
|
|||
|
||||
DeepSyncProgress = 3 + uint8(tid)*themeStep + uint8(eid)*exerciceStep
|
||||
flagsBindings, ferrs := SyncExerciceFlags(i, exercice, ex_exceptions[eid])
|
||||
for _, ferr := range ferrs {
|
||||
for _, ferr := range multierr.Errors(ferrs) {
|
||||
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], ferr.Error())
|
||||
}
|
||||
|
||||
DeepSyncProgress += exerciceStep / 2
|
||||
_, herrs := SyncExerciceHints(i, exercice, flagsBindings, ex_exceptions[eid])
|
||||
for _, herr := range herrs {
|
||||
for _, herr := range multierr.Errors(herrs) {
|
||||
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], herr.Error())
|
||||
}
|
||||
}
|
||||
|
|
@ -113,7 +115,7 @@ func SyncDeep(i Importer) (errs SyncReport) {
|
|||
|
||||
errs.DateStart = startTime
|
||||
exceptions, sterrs := SyncThemes(i)
|
||||
for _, sterr := range sterrs {
|
||||
for _, sterr := range multierr.Errors(sterrs) {
|
||||
errs.ThemesSync = append(errs.ThemesSync, sterr.Error())
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +125,7 @@ func SyncDeep(i Importer) (errs SyncReport) {
|
|||
|
||||
for tid, theme := range themes {
|
||||
stderrs := SyncThemeDeep(i, theme, tid, themeStep, exceptions[theme.Path])
|
||||
for _, stderr := range stderrs {
|
||||
for _, stderr := range multierr.Errors(stderrs) {
|
||||
errs.Themes[theme.Name] = append(errs.Themes[theme.Name], stderr.Error())
|
||||
}
|
||||
}
|
||||
|
|
@ -199,7 +201,7 @@ func EditDeepReport(errs *SyncReport, erase bool) {
|
|||
}
|
||||
|
||||
// SyncThemeDeep performs a recursive synchronisation: from challenges to challenge items.
|
||||
func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8, exceptions *CheckExceptions) (errs []error) {
|
||||
func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8, exceptions *CheckExceptions) (errs error) {
|
||||
var ex_exceptions map[int]*CheckExceptions
|
||||
|
||||
oneThemeDeepSync.Lock()
|
||||
|
|
@ -214,15 +216,15 @@ func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8, excep
|
|||
log.Printf("Deep synchronization in progress: %d/255 - doing Theme %q, Exercice %q: %q\n", DeepSyncProgress, theme.Name, exercice.Title, exercice.Path)
|
||||
|
||||
DeepSyncProgress = 3 + uint8(tid)*themeStep + uint8(eid)*exerciceStep
|
||||
errs = append(errs, SyncExerciceFiles(i, exercice, ex_exceptions[eid])...)
|
||||
errs = multierr.Append(errs, SyncExerciceFiles(i, exercice, ex_exceptions[eid]))
|
||||
|
||||
DeepSyncProgress += exerciceStep / 3
|
||||
flagsBindings, ferrs := SyncExerciceFlags(i, exercice, ex_exceptions[eid])
|
||||
errs = append(errs, ferrs...)
|
||||
errs = multierr.Append(errs, ferrs)
|
||||
|
||||
DeepSyncProgress += exerciceStep / 3
|
||||
_, herrs := SyncExerciceHints(i, exercice, flagsBindings, ex_exceptions[eid])
|
||||
errs = append(errs, herrs...)
|
||||
errs = multierr.Append(errs, herrs)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@ import (
|
|||
|
||||
var hooks = &CheckHooks{customHooks: map[string]CustomCheckHook{}}
|
||||
|
||||
type CheckFlagChoiceHook func(*fic.FlagChoice, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckFlagKeyHook func(*fic.FlagKey, string, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckFlagKeyWithChoicesHook func(*fic.FlagKey, string, []*fic.FlagChoice, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckFlagLabelHook func(*fic.FlagLabel, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckFlagMCQHook func(*fic.MCQ, []*fic.MCQ_entry, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckFileHook func(*fic.EFile, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckHintHook func(*fic.EHint, *fic.Exercice, *CheckExceptions) []error
|
||||
type CheckMDTextHook func(string, string, *CheckExceptions, ...string) []error
|
||||
type CheckExerciceHook func(*fic.Exercice, *CheckExceptions) []error
|
||||
type CustomCheckHook func(interface{}, *CheckExceptions) []error
|
||||
type CheckFlagChoiceHook func(*fic.FlagChoice, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckFlagKeyHook func(*fic.FlagKey, string, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckFlagKeyWithChoicesHook func(*fic.FlagKey, string, []*fic.FlagChoice, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckFlagLabelHook func(*fic.FlagLabel, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckFlagMCQHook func(*fic.MCQ, []*fic.MCQ_entry, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckFileHook func(*fic.EFile, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckHintHook func(*fic.EHint, *fic.Exercice, *CheckExceptions) error
|
||||
type CheckMDTextHook func(string, string, *CheckExceptions, ...string) error
|
||||
type CheckExerciceHook func(*fic.Exercice, *CheckExceptions) error
|
||||
type CustomCheckHook func(interface{}, *CheckExceptions) error
|
||||
|
||||
type CheckHooks struct {
|
||||
flagChoiceHooks []CheckFlagChoiceHook
|
||||
|
|
@ -73,7 +73,7 @@ func (h *CheckHooks) RegisterCustomHook(hookname string, f CustomCheckHook) {
|
|||
h.customHooks[hookname] = f
|
||||
}
|
||||
|
||||
func (h *CheckHooks) CallCustomHook(hookname string, data interface{}, exceptions *CheckExceptions) []error {
|
||||
func (h *CheckHooks) CallCustomHook(hookname string, data interface{}, exceptions *CheckExceptions) error {
|
||||
if v, ok := h.customHooks[hookname]; ok {
|
||||
return v(data, exceptions)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yuin/goldmark"
|
||||
"go.uber.org/multierr"
|
||||
"golang.org/x/image/draw"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
|
|
@ -104,7 +105,7 @@ func getAuthors(i Importer, tname string) ([]string, error) {
|
|||
}
|
||||
|
||||
// BuildTheme creates a Theme from a given importer.
|
||||
func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExceptions, errs []error) {
|
||||
func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExceptions, errs error) {
|
||||
th = &fic.Theme{}
|
||||
|
||||
th.Path = tdir
|
||||
|
|
@ -116,7 +117,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
|||
if language, err := GetFileContent(i, path.Join(tdir, "language.txt")); err == nil {
|
||||
language = strings.TrimSpace(language)
|
||||
if strings.Contains(language, "\n") {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("language.txt: Language can't contain new lines")))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("language.txt: Language can't contain new lines")))
|
||||
} else {
|
||||
th.Language = language
|
||||
}
|
||||
|
|
@ -133,7 +134,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
|||
th.URLId = fic.ToURLid(th.Name)
|
||||
|
||||
if authors, err := getAuthors(i, tdir); err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
|
||||
return nil, nil, errs
|
||||
} else {
|
||||
// Format authors
|
||||
|
|
@ -147,13 +148,13 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
|||
th.Headline, err = GetFileContent(i, path.Join(tdir, "headline.md"))
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("unable to get theme's headline: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get theme's headline: %w", err)))
|
||||
}
|
||||
if th.Headline != "" {
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(th.Headline, th.Language, exceptions.GetFileExceptions("headline.md", "headline.txt")) {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("headline.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(th.Headline, th.Language, exceptions.GetFileExceptions("headline.md", "headline.txt"))) {
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("headline.md: %w", err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -167,12 +168,12 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
|||
err = fmt.Errorf("unable to find overview.txt nor overview.md")
|
||||
}
|
||||
if err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("unable to get theme's overview: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get theme's overview: %w", err)))
|
||||
} else {
|
||||
// Call checks hooks
|
||||
for _, h := range hooks.mdTextHooks {
|
||||
for _, err := range h(intro, th.Language, exceptions.GetFileExceptions("overview.md", "overview.txt")) {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("overview.md: %w", err)))
|
||||
for _, err := range multierr.Errors(h(intro, th.Language, exceptions.GetFileExceptions("overview.md", "overview.txt"))) {
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("overview.md: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,12 +189,12 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
|||
// Format overview (markdown)
|
||||
th.Intro, err = ProcessMarkdown(i, intro, tdir)
|
||||
if err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("overview.txt: an error occurs during markdown formating: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("overview.txt: an error occurs during markdown formating: %w", err)))
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err := goldmark.Convert([]byte(th.Headline), &buf)
|
||||
if err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("overview.txt: an error occurs during markdown formating of the headline: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("overview.txt: an error occurs during markdown formating of the headline: %w", err)))
|
||||
} else {
|
||||
th.Headline = string(buf.Bytes())
|
||||
}
|
||||
|
|
@ -204,7 +205,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
|||
} else if i.Exists(path.Join(tdir, "heading.png")) {
|
||||
th.Image = path.Join(tdir, "heading.png")
|
||||
} else {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("heading.jpg: No such file")))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("heading.jpg: No such file")))
|
||||
}
|
||||
|
||||
if i.Exists(path.Join(tdir, "partner.jpg")) {
|
||||
|
|
@ -215,11 +216,11 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
|||
|
||||
if i.Exists(path.Join(tdir, "partner.txt")) {
|
||||
if txt, err := GetFileContent(i, path.Join(tdir, "partner.txt")); err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("unable to get partner's text: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get partner's text: %w", err)))
|
||||
} else {
|
||||
th.PartnerText, err = ProcessMarkdown(i, txt, tdir)
|
||||
if err != nil {
|
||||
errs = append(errs, NewThemeError(th, fmt.Errorf("partner.txt: an error occurs during markdown formating: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("partner.txt: an error occurs during markdown formating: %w", err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -227,9 +228,9 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept
|
|||
}
|
||||
|
||||
// SyncThemes imports new or updates existing themes.
|
||||
func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs []error) {
|
||||
func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs error) {
|
||||
if themes, err := GetThemes(i); err != nil {
|
||||
errs = append(errs, fmt.Errorf("Unable to list themes: %w", err))
|
||||
errs = multierr.Append(errs, fmt.Errorf("Unable to list themes: %w", err))
|
||||
} else {
|
||||
rand.Shuffle(len(themes), func(i, j int) {
|
||||
themes[i], themes[j] = themes[j], themes[i]
|
||||
|
|
@ -239,9 +240,7 @@ func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs []erro
|
|||
|
||||
for _, tdir := range themes {
|
||||
btheme, excepts, berrs := BuildTheme(i, tdir)
|
||||
for _, e := range berrs {
|
||||
errs = append(errs, e)
|
||||
}
|
||||
errs = multierr.Append(errs, berrs)
|
||||
|
||||
if btheme == nil {
|
||||
continue
|
||||
|
|
@ -259,7 +258,7 @@ func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs []erro
|
|||
btheme.Image = strings.TrimPrefix(filePath, fic.FilesDir)
|
||||
return nil, nil
|
||||
}); err != nil {
|
||||
errs = append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -269,14 +268,14 @@ func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs []erro
|
|||
btheme.PartnerImage = strings.TrimPrefix(filePath, fic.FilesDir)
|
||||
return nil, nil
|
||||
}); err != nil {
|
||||
errs = append(errs, NewThemeError(btheme, fmt.Errorf("unable to import partner image: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("unable to import partner image: %w", err)))
|
||||
}
|
||||
}
|
||||
|
||||
var theme *fic.Theme
|
||||
if theme, err = fic.GetThemeByPath(btheme.Path); err != nil {
|
||||
if _, err := fic.CreateTheme(btheme); err != nil {
|
||||
errs = append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during add: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during add: %w", err)))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
@ -284,7 +283,7 @@ func SyncThemes(i Importer) (exceptions map[string]*CheckExceptions, errs []erro
|
|||
if !fic.CmpTheme(theme, btheme) {
|
||||
btheme.Id = theme.Id
|
||||
if _, err := btheme.Update(); err != nil {
|
||||
errs = append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during update: %w", err)))
|
||||
errs = multierr.Append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during update: %w", err)))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue