sync: Use errors instead of string to report

This commit is contained in:
nemunaire 2022-07-02 00:01:05 +02:00
parent d8943ba1f3
commit b0129e5239
9 changed files with 186 additions and 178 deletions

View file

@ -31,7 +31,7 @@ func declareSyncRoutes(router *gin.RouterGroup) {
// Speedy sync performs a recursive synchronization without importing files. // Speedy sync performs a recursive synchronization without importing files.
apiSyncRoutes.POST("/speed", func(c *gin.Context) { apiSyncRoutes.POST("/speed", func(c *gin.Context) {
st := sync.SpeedySyncDeep(sync.GlobalImporter) st := sync.SpeedySyncDeep(sync.GlobalImporter)
sync.EditDeepReport(st, false) sync.EditDeepReport(&st, false)
c.JSON(http.StatusOK, st) c.JSON(http.StatusOK, st)
}) })
@ -53,7 +53,7 @@ func declareSyncRoutes(router *gin.RouterGroup) {
theme := c.MustGet("theme").(*fic.Theme) theme := c.MustGet("theme").(*fic.Theme)
st := sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250) st := sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250)
sync.EditDeepReport(map[string][]string{theme.Name: st}, false) sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]error{theme.Name: st}}, false)
sync.DeepSyncProgress = 255 sync.DeepSyncProgress = 255
c.JSON(http.StatusOK, st) c.JSON(http.StatusOK, st)
}) })
@ -212,7 +212,7 @@ func autoSync(c *gin.Context) {
} }
st := sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250) st := sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250)
sync.EditDeepReport(map[string][]string{theme.Name: st}, false) sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]error{theme.Name: st}}, false)
sync.DeepSyncProgress = 255 sync.DeepSyncProgress = 255
settings.ForceRegeneration() settings.ForceRegeneration()

View file

@ -87,12 +87,12 @@ func parseExerciceParams(i Importer, exPath string) (p ExerciceParams, md toml.M
} }
// getExerciceParams returns normalized // getExerciceParams returns normalized
func getExerciceParams(i Importer, exercice *fic.Exercice) (params ExerciceParams, errs []string) { func getExerciceParams(i Importer, exercice *fic.Exercice) (params ExerciceParams, errs []error) {
var err error var err error
if params, _, err = parseExerciceParams(i, exercice.Path); err != nil { if params, _, err = parseExerciceParams(i, exercice.Path); err != nil {
errs = append(errs, fmt.Sprintf("%q: challenge.txt: %s", path.Base(exercice.Path), err)) errs = append(errs, fmt.Errorf("%q: challenge.txt: %w", path.Base(exercice.Path), err))
} else if len(params.Flags) == 0 && len(params.FlagsUCQ) == 0 && len(params.FlagsMCQ) == 0 { } else if len(params.Flags) == 0 && len(params.FlagsUCQ) == 0 && len(params.FlagsMCQ) == 0 {
errs = append(errs, fmt.Sprintf("%q: has no flag", path.Base(exercice.Path))) errs = append(errs, fmt.Errorf("%q: has no flag", path.Base(exercice.Path)))
} else { } else {
// Treat legacy UCQ flags as ExerciceFlag // Treat legacy UCQ flags as ExerciceFlag
for _, flag := range params.FlagsUCQ { for _, flag := range params.FlagsUCQ {

View file

@ -14,7 +14,7 @@ import (
"srs.epita.fr/fic-server/libfic" "srs.epita.fr/fic-server/libfic"
) )
func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files []string, digests map[string][]byte, errs []string) { 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 no files directory, don't display error
if !i.exists(path.Join(exercice.Path, into)) { if !i.exists(path.Join(exercice.Path, into)) {
return return
@ -22,15 +22,15 @@ func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files
// Parse DIGESTS.txt // Parse DIGESTS.txt
if digs, err := GetFileContent(i, path.Join(exercice.Path, into, "DIGESTS.txt")); err != nil { if digs, err := GetFileContent(i, path.Join(exercice.Path, into, "DIGESTS.txt")); err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to read DIGESTS.txt: %s", path.Base(exercice.Path), err)) errs = append(errs, fmt.Errorf("%q: unable to read DIGESTS.txt: %w", path.Base(exercice.Path), err))
} else { } else {
digests = map[string][]byte{} digests = map[string][]byte{}
for nline, d := range strings.Split(digs, "\n") { for nline, d := range strings.Split(digs, "\n") {
if dsplt := strings.SplitN(d, " ", 2); len(dsplt) < 2 { if dsplt := strings.SplitN(d, " ", 2); len(dsplt) < 2 {
errs = append(errs, fmt.Sprintf("%q: unable to parse DIGESTS.txt line %d: invalid format", path.Base(exercice.Path), nline+1)) errs = append(errs, fmt.Errorf("%q: unable to parse DIGESTS.txt line %d: invalid format", path.Base(exercice.Path), nline+1))
continue continue
} else if hash, err := hex.DecodeString(dsplt[0]); err != nil { } else if hash, err := hex.DecodeString(dsplt[0]); err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to parse DIGESTS.txt line %d: %s", path.Base(exercice.Path), nline+1, err)) errs = append(errs, fmt.Errorf("%q: unable to parse DIGESTS.txt line %d: %w", path.Base(exercice.Path), nline+1, err))
continue continue
} else { } else {
digests[strings.TrimFunc(dsplt[1], unicode.IsSpace)] = hash digests[strings.TrimFunc(dsplt[1], unicode.IsSpace)] = hash
@ -40,7 +40,7 @@ func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files
// Read file list // Read file list
if flist, err := i.listDir(path.Join(exercice.Path, into)); err != nil { if flist, err := i.listDir(path.Join(exercice.Path, into)); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
} else { } else {
for _, fname := range flist { for _, fname := range flist {
if fname == "DIGESTS.txt" || fname == ".gitattributes" { if fname == "DIGESTS.txt" || fname == ".gitattributes" {
@ -73,15 +73,15 @@ func BuildFilesListInto(i Importer, exercice *fic.Exercice, into string) (files
} }
// CheckExerciceFilesPresence limits remote checks to presence, don't get it to check digest. // CheckExerciceFilesPresence limits remote checks to presence, don't get it to check digest.
func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []string, errs []string) { func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []string, errs []error) {
flist, digests, berrs := BuildFilesListInto(i, exercice, "files") flist, digests, berrs := BuildFilesListInto(i, exercice, "files")
errs = append(errs, berrs...) errs = append(errs, berrs...)
for _, fname := range flist { for _, fname := range flist {
if !i.exists(path.Join(exercice.Path, "files", fname)) { if !i.exists(path.Join(exercice.Path, "files", fname)) {
errs = append(errs, fmt.Sprintf("%q: unable to read file %q: No such file or directory", path.Base(exercice.Path), fname)) errs = append(errs, fmt.Errorf("%q: unable to read file %q: No such file or directory", path.Base(exercice.Path), fname))
} else if _, ok := digests[fname]; !ok { } else if _, ok := digests[fname]; !ok {
errs = append(errs, fmt.Sprintf("%q: unable to import file %q: No digest given", path.Base(exercice.Path), fname)) errs = append(errs, fmt.Errorf("%q: unable to import file %q: No digest given", path.Base(exercice.Path), fname))
} else { } else {
files = append(files, fname) files = append(files, fname)
} }
@ -89,7 +89,7 @@ func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []str
for fname := range digests { for fname := range digests {
if !i.exists(path.Join(exercice.Path, "files", fname)) { if !i.exists(path.Join(exercice.Path, "files", fname)) {
errs = append(errs, fmt.Sprintf("%q: unable to read file %q: No such file or directory. Check your DIGESTS.txt for legacy entries.", path.Base(exercice.Path), fname)) errs = append(errs, fmt.Errorf("%q: unable to read file %q: No such file or directory. Check your DIGESTS.txt for legacy entries.", path.Base(exercice.Path), fname))
} }
} }
@ -97,7 +97,7 @@ func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []str
} }
// CheckExerciceFiles checks that remote files have the right digest. // CheckExerciceFiles checks that remote files have the right digest.
func CheckExerciceFiles(i Importer, exercice *fic.Exercice) (files []string, errs []string) { func CheckExerciceFiles(i Importer, exercice *fic.Exercice) (files []string, errs []error) {
flist, digests, berrs := BuildFilesListInto(i, exercice, "files") flist, digests, berrs := BuildFilesListInto(i, exercice, "files")
errs = append(errs, berrs...) errs = append(errs, berrs...)
@ -105,10 +105,10 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice) (files []string, err
w, hash160, hash512 := fic.CreateHashBuffers() w, hash160, hash512 := fic.CreateHashBuffers()
if err := GetFile(i, path.Join(exercice.Path, "files", fname), bufio.NewWriter(w)); err != nil { if err := GetFile(i, path.Join(exercice.Path, "files", fname), bufio.NewWriter(w)); err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to read file %q: %s", path.Base(exercice.Path), fname, err)) errs = append(errs, fmt.Errorf("%q: unable to read file %q: %w", path.Base(exercice.Path), fname, err))
continue continue
} else if _, err := fic.CheckBufferHash(hash160, hash512, digests[fname]); err != nil { } else if _, err := fic.CheckBufferHash(hash160, hash512, digests[fname]); err != nil {
errs = append(errs, fmt.Sprintf("%q: %s: %s", path.Base(exercice.Path), fname, err)) errs = append(errs, fmt.Errorf("%q: %s: %w", path.Base(exercice.Path), fname, err))
} }
files = append(files, fname) files = append(files, fname)
@ -118,9 +118,9 @@ func CheckExerciceFiles(i Importer, exercice *fic.Exercice) (files []string, err
// SyncExerciceFiles reads the content of files/ directory and import it as EFile for the given challenge. // 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. // It takes care of DIGESTS.txt and ensure imported files match.
func SyncExerciceFiles(i Importer, exercice *fic.Exercice) (errs []string) { func SyncExerciceFiles(i Importer, exercice *fic.Exercice) (errs []error) {
if _, err := exercice.WipeFiles(); err != nil { if _, err := exercice.WipeFiles(); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
} }
files, digests, berrs := BuildFilesListInto(i, exercice, "files") files, digests, berrs := BuildFilesListInto(i, exercice, "files")
@ -130,17 +130,17 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice) (errs []string) {
for _, fname := range files { for _, fname := range files {
// Enforce file format // Enforce file format
if path.Ext(fname) == "rar" || path.Ext(fname) == "7z" { if path.Ext(fname) == "rar" || path.Ext(fname) == "7z" {
errs = append(errs, fmt.Sprintf("%q: WARNING %q use a forbidden archive type.", path.Base(exercice.Path), fname)) errs = append(errs, fmt.Errorf("%q: WARNING %q use a forbidden archive type.", path.Base(exercice.Path), fname))
} }
if f, err := i.importFile(path.Join(exercice.Path, "files", fname), if f, err := i.importFile(path.Join(exercice.Path, "files", fname),
func(filePath string, origin string) (interface{}, error) { func(filePath string, origin string) (interface{}, error) {
return exercice.ImportFile(filePath, origin, digests[fname]) return exercice.ImportFile(filePath, origin, digests[fname])
}); err != nil { }); err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to import file %q: %s", path.Base(exercice.Path), fname, err)) errs = append(errs, fmt.Errorf("%q: unable to import file %q: %w", path.Base(exercice.Path), fname, err))
continue continue
} else if f.(*fic.EFile).Size == 0 { } else if f.(*fic.EFile).Size == 0 {
errs = append(errs, fmt.Sprintf("%q: WARNING imported file %q is empty!", path.Base(exercice.Path), fname)) errs = append(errs, fmt.Errorf("%q: WARNING imported file %q is empty!", path.Base(exercice.Path), fname))
} }
} }
return return

View file

@ -23,10 +23,10 @@ type importHint struct {
FlagsDeps []int64 FlagsDeps []int64
} }
func buildExerciceHints(i Importer, exercice *fic.Exercice) (hints []importHint, errs []string) { func buildExerciceHints(i Importer, exercice *fic.Exercice) (hints []importHint, errs []error) {
params, _, err := parseExerciceParams(i, exercice.Path) params, _, err := parseExerciceParams(i, exercice.Path)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: challenge.txt: %s", path.Base(exercice.Path), err)) errs = append(errs, fmt.Errorf("%q: challenge.txt: %w", path.Base(exercice.Path), err))
return return
} }
@ -45,10 +45,10 @@ func buildExerciceHints(i Importer, exercice *fic.Exercice) (hints []importHint,
if hint.Filename != "" { if hint.Filename != "" {
if hint.Content != "" { if hint.Content != "" {
errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): content and filename can't be filled at the same time", path.Base(exercice.Path), hint.Title, n+1)) errs = append(errs, fmt.Errorf("%q: challenge.txt: hint %s (%d): content and filename can't be filled at the same time", path.Base(exercice.Path), hint.Title, n+1))
continue continue
} else if !i.exists(path.Join(exercice.Path, "hints", hint.Filename)) { } else if !i.exists(path.Join(exercice.Path, "hints", hint.Filename)) {
errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): %s: File not found", path.Base(exercice.Path), hint.Title, n+1, hint.Filename)) errs = append(errs, fmt.Errorf("%q: challenge.txt: hint %s (%d): %s: File not found", path.Base(exercice.Path), hint.Title, n+1, hint.Filename))
continue continue
} else { } else {
// Handle files as downloadable content // Handle files as downloadable content
@ -70,20 +70,20 @@ func buildExerciceHints(i Importer, exercice *fic.Exercice) (hints []importHint,
// Special format for downloadable hints: $FILES + hexhash + path from FILES/ // Special format for downloadable hints: $FILES + hexhash + path from FILES/
return "$FILES" + hex.EncodeToString(result512) + strings.TrimPrefix(filePath, fic.FilesDir), nil return "$FILES" + hex.EncodeToString(result512) + strings.TrimPrefix(filePath, fic.FilesDir), nil
}); err != nil { }); err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to import hint file %q: %s", path.Base(exercice.Path), hint.Filename, err)) errs = append(errs, fmt.Errorf("%q: unable to import hint file %q: %w", path.Base(exercice.Path), hint.Filename, err))
continue continue
} else if s, ok := res.(string); !ok { } else if s, ok := res.(string); !ok {
errs = append(errs, fmt.Sprintf("%q: unable to import hint file %q: invalid string returned as filename", path.Base(exercice.Path), hint.Filename)) errs = append(errs, fmt.Errorf("%q: unable to import hint file %q: invalid string returned as filename", path.Base(exercice.Path), hint.Filename))
continue continue
} else { } else {
h.Content = s h.Content = s
} }
} }
} else if hint.Content == "" { } else if hint.Content == "" {
errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): content and filename can't be empty at the same time", path.Base(exercice.Path), hint.Title, n+1)) errs = append(errs, fmt.Errorf("%q: challenge.txt: hint %s (%d): content and filename can't be empty at the same time", path.Base(exercice.Path), hint.Title, n+1))
continue continue
} else if h.Content, err = ProcessMarkdown(i, fixnbsp(hint.Content), exercice.Path); err != nil { } else if h.Content, err = ProcessMarkdown(i, fixnbsp(hint.Content), exercice.Path); err != nil {
errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): error during markdown formating: %s", path.Base(exercice.Path), hint.Title, n+1, err)) errs = append(errs, fmt.Errorf("%q: challenge.txt: hint %s (%d): error during markdown formating: %w", path.Base(exercice.Path), hint.Title, n+1, err))
} }
newHint := importHint{ newHint := importHint{
@ -103,14 +103,14 @@ func buildExerciceHints(i Importer, exercice *fic.Exercice) (hints []importHint,
} }
// CheckExerciceHints checks if all hints are corrects.. // CheckExerciceHints checks if all hints are corrects..
func CheckExerciceHints(i Importer, exercice *fic.Exercice) ([]importHint, []string) { func CheckExerciceHints(i Importer, exercice *fic.Exercice) ([]importHint, []error) {
return buildExerciceHints(i, exercice) return buildExerciceHints(i, exercice)
} }
// SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge. // SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge.
func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int64]fic.Flag) (hintsBindings map[int]*fic.EHint, errs []string) { func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int64]fic.Flag) (hintsBindings map[int]*fic.EHint, errs []error) {
if _, err := exercice.WipeHints(); err != nil { if _, err := exercice.WipeHints(); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
} else { } else {
hints, berrs := buildExerciceHints(i, exercice) hints, berrs := buildExerciceHints(i, exercice)
errs = append(errs, berrs...) errs = append(errs, berrs...)
@ -120,7 +120,7 @@ func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int
for _, hint := range hints { for _, hint := range hints {
// Import hint // Import hint
if h, err := exercice.AddHint(hint.Hint.Title, hint.Hint.Content, hint.Hint.Cost); err != nil { if h, err := exercice.AddHint(hint.Hint.Title, hint.Hint.Content, hint.Hint.Cost); err != nil {
errs = append(errs, fmt.Sprintf("%q: hint #%d %s: %s", path.Base(exercice.Path), hint.Line, hint.Hint.Title, err)) errs = append(errs, fmt.Errorf("%q: hint #%d %s: %w", path.Base(exercice.Path), hint.Line, hint.Hint.Title, err))
} else { } else {
hintsBindings[hint.Line] = h hintsBindings[hint.Line] = h
@ -128,10 +128,10 @@ func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int
for _, nf := range hint.FlagsDeps { for _, nf := range hint.FlagsDeps {
if f, ok := flagsBindings[nf]; ok { if f, ok := flagsBindings[nf]; ok {
if herr := h.AddDepend(f); herr != nil { if herr := h.AddDepend(f); herr != nil {
errs = append(errs, fmt.Sprintf("%q: error hint #%d dependency to flag #%d: %s", path.Base(exercice.Path), hint.Line, nf, herr)) errs = append(errs, fmt.Errorf("%q: error hint #%d dependency to flag #%d: %w", path.Base(exercice.Path), hint.Line, nf, herr))
} }
} else { } else {
errs = append(errs, fmt.Sprintf("%q: error hint #%d dependency to flag #%d: Unexistant flag", path.Base(exercice.Path), hint.Line, nf)) errs = append(errs, fmt.Errorf("%q: error hint #%d dependency to flag #%d: Unexistant flag", path.Base(exercice.Path), hint.Line, nf))
} }
} }
} }

View file

@ -35,11 +35,11 @@ func validatorRegexp(vre string) (validator_regexp *string) {
return return
} }
func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bool, separator string) (raw string, prep string, errs []string) { func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bool, separator string) (raw string, prep string, errs []error) {
// Concatenate array // Concatenate array
if f, ok := input.([]interface{}); ok { if f, ok := input.([]interface{}); ok {
if len(validatorRe) > 0 { if len(validatorRe) > 0 {
errs = append(errs, "ValidatorRe cannot be defined for this kind of flag.") errs = append(errs, fmt.Errorf("ValidatorRe cannot be defined for this kind of flag."))
validatorRe = "" validatorRe = ""
} }
@ -47,20 +47,20 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
separator = "," separator = ","
} else if len(separator) > 1 { } else if len(separator) > 1 {
separator = string(separator[0]) separator = string(separator[0])
errs = append(errs, "separator truncated to %q") errs = append(errs, fmt.Errorf("separator truncated to %q", separator))
} }
var fitems []string var fitems []string
for _, v := range f { for i, v := range f {
if g, ok := v.(string); ok { if g, ok := v.(string); ok {
if strings.Index(g, separator) != -1 { if strings.Index(g, separator) != -1 {
errs = append(errs, "flag items cannot contain %q character as it is used as separator. Change the separator attribute for this flag.") errs = append(errs, fmt.Errorf("flag items cannot contain %q character as it is used as separator. Change the separator attribute for this flag."))
return return
} else { } else {
fitems = append(fitems, g) fitems = append(fitems, g)
} }
} else { } else {
errs = append(errs, "item %d has an invalid type: can only be string, is %T.") errs = append(errs, fmt.Errorf("item %d has an invalid type: can only be string, is %T.", i, g))
return return
} }
} }
@ -74,7 +74,7 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
nbLines := 0 nbLines := 0
if showLines { if showLines {
if len(fitems) > 9 { if len(fitems) > 9 {
errs = append(errs, "too much items in vector to use ShowLines features, max 9.") errs = append(errs, fmt.Errorf("too much items in vector to use ShowLines features, max 9."))
} else { } else {
nbLines = len(fitems) nbLines = len(fitems)
} }
@ -88,7 +88,7 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
} else if f, ok := input.(float64); ok { } else if f, ok := input.(float64); ok {
raw = strconv.FormatFloat(f, 'f', -1, 64) raw = strconv.FormatFloat(f, 'f', -1, 64)
} else if f, ok := input.(string); !ok { } else if f, ok := input.(string); !ok {
errs = append(errs, fmt.Sprintf("has an invalid type: can only be []string or string, not %T", input)) errs = append(errs, fmt.Errorf("has an invalid type: can only be []string or string, not %T", input))
return return
} else { } else {
raw = f raw = f
@ -96,18 +96,18 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
return return
} }
func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int) (f *fic.FlagLabel, errs []string) { func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int) (f *fic.FlagLabel, errs []error) {
if len(flag.Label) == 0 { if len(flag.Label) == 0 {
errs = append(errs, fmt.Sprintf("%q: flag #%d: Label cannot be empty.", path.Base(exercice.Path), flagline)) errs = append(errs, fmt.Errorf("Label cannot be empty."))
return return
} }
if flag.Raw != nil { if flag.Raw != nil {
errs = append(errs, fmt.Sprintf("%q: flag #%d: raw cannot be defined.", path.Base(exercice.Path), flagline)) errs = append(errs, fmt.Errorf("raw cannot be defined."))
} }
if len(flag.Choice) != 0 { if len(flag.Choice) != 0 {
errs = append(errs, fmt.Sprintf("%q: flag #%d: choices cannot be defined.", path.Base(exercice.Path), flagline)) errs = append(errs, fmt.Errorf("choices cannot be defined."))
} }
f = &fic.FlagLabel{ f = &fic.FlagLabel{
@ -118,48 +118,46 @@ func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int) (f
return return
} }
func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defaultLabel string) (f *fic.Flag, choices []*fic.FlagChoice, errs []string) { func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defaultLabel string) (f *fic.Flag, choices []*fic.FlagChoice, errs []error) {
if len(flag.Label) == 0 { if len(flag.Label) == 0 {
flag.Label = defaultLabel flag.Label = defaultLabel
} }
if len(flag.Variant) != 0 { if len(flag.Variant) != 0 {
errs = append(errs, fmt.Sprintf("%q: flag #%d: variant is not defined for this kind of flag.", path.Base(exercice.Path), flagline)) errs = append(errs, fmt.Errorf("variant is not defined for this kind of flag."))
} }
if flag.Label[0] == '`' { if flag.Label[0] == '`' {
errs = append(errs, fmt.Sprintf("%q: flag #%d: Label should not begin with `.", path.Base(exercice.Path), flagline)) errs = append(errs, fmt.Errorf("Label should not begin with `."))
flag.Label = flag.Label[1:] flag.Label = flag.Label[1:]
} }
if (flag.Label[0] == 'Q' || flag.Label[0] == 'q') && (flag.Label[1] == 'U' || flag.Label[1] == 'u') || 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') { (flag.Label[0] == 'W' || flag.Label[0] == 'w') && (flag.Label[1] == 'H' || flag.Label[1] == 'h') {
errs = append(errs, fmt.Sprintf("%q: flag #%d: 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.", path.Base(exercice.Path), flagline, flag.Label[0:2])) 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]))
flag.Label = flag.Label[1:] 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])) { 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.Sprintf("%q: flag #%d: Label should not end with punct (%q). Reword your label as a description of the expected flag, `:` are automatically appended.", path.Base(exercice.Path), flagline, 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]))
} }
raw, prep, terrs := getRawKey(flag.Raw, flag.ValidatorRe, flag.Ordered, flag.ShowLines, flag.Separator) raw, prep, terrs := getRawKey(flag.Raw, flag.ValidatorRe, flag.Ordered, flag.ShowLines, flag.Separator)
if len(terrs) > 0 { if len(terrs) > 0 {
for _, err := range terrs { errs = append(errs, terrs...)
errs = append(errs, fmt.Sprintf("%q: flag #%d: %s", path.Base(exercice.Path), flagline, err))
}
f = nil f = nil
return return
} }
flag.Label = prep + flag.Label flag.Label = prep + flag.Label
if (flag.Type == "text" && !isFullGraphic(strings.Replace(raw, "\n", "", -1))) || (flag.Type != "text" && !isFullGraphic(raw)) { if (flag.Type == "text" && !isFullGraphic(strings.Replace(raw, "\n", "", -1))) || (flag.Type != "text" && !isFullGraphic(raw)) {
errs = append(errs, fmt.Sprintf("%q: WARNING flag #%d: non-printable characters in flag, is this really expected?", path.Base(exercice.Path), flagline)) errs = append(errs, fmt.Errorf("WARNING non-printable characters in flag, is this really expected?"))
} }
hashedFlag, err := fic.ComputeHashedFlag([]byte(raw), !flag.CaseSensitive, flag.NoTrim, validatorRegexp(flag.ValidatorRe), flag.SortReGroups) hashedFlag, err := fic.ComputeHashedFlag([]byte(raw), !flag.CaseSensitive, flag.NoTrim, validatorRegexp(flag.ValidatorRe), flag.SortReGroups)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: flag #%d: %s", path.Base(exercice.Path), flagline, err.Error())) errs = append(errs, err)
return return
} }
fl := fic.Flag(&fic.FlagKey{ fl := fic.Flag(&fic.FlagKey{
@ -193,9 +191,7 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
for _, choice := range flag.Choice { for _, choice := range flag.Choice {
val, prep, terrs := getRawKey(choice.Value, "", false, false, "") val, prep, terrs := getRawKey(choice.Value, "", false, false, "")
if len(terrs) > 0 { if len(terrs) > 0 {
for _, err := range terrs { errs = append(errs, terrs...)
errs = append(errs, fmt.Sprintf("%q: flag #%d: %s", path.Base(exercice.Path), flagline, err))
}
continue continue
} }
@ -214,7 +210,7 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
}) })
if val == "true" || val == "false" { if val == "true" || val == "false" {
errs = append(errs, fmt.Sprintf("%q: flag #%d: 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.", path.Base(exercice.Path), flagline), val) errs = append(errs, 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)) { if val == raw || (!flag.CaseSensitive && val == strings.ToLower(raw)) {
@ -222,7 +218,7 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
} }
} }
if !hasOne { if !hasOne {
errs = append(errs, fmt.Sprintf("%q: error in flag #%d: no valid answer defined.", path.Base(exercice.Path), flagline)) errs = append(errs, fmt.Errorf("no valid answer defined."))
} }
} }
return return
@ -251,7 +247,7 @@ func iface2Number(input interface{}, output *string) error {
} }
// buildExerciceFlags read challenge.txt and extract all flags. // buildExerciceFlags read challenge.txt and extract all flags.
func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nline int) (ret []importFlag, errs []string) { func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nline int) (ret []importFlag, errs []error) {
switch strings.ToLower(flag.Type) { switch strings.ToLower(flag.Type) {
case "": case "":
flag.Type = "key" flag.Type = "key"
@ -263,17 +259,17 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
var smin, smax, sstep string var smin, smax, sstep string
err := iface2Number(flag.NumberMin, &smin) err := iface2Number(flag.NumberMin, &smin)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: flag #%d: min %s.", path.Base(exercice.Path), nline+1, err.Error())) errs = append(errs, fmt.Errorf("%q: flag #%d: min %s.", path.Base(exercice.Path), nline+1, err.Error()))
} }
err = iface2Number(flag.NumberMax, &smax) err = iface2Number(flag.NumberMax, &smax)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: flag #%d: max %s.", path.Base(exercice.Path), nline+1, err.Error())) errs = append(errs, fmt.Errorf("%q: flag #%d: max %s.", path.Base(exercice.Path), nline+1, err.Error()))
} }
err = iface2Number(flag.NumberStep, &sstep) err = iface2Number(flag.NumberStep, &sstep)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: flag #%d: step %s.", path.Base(exercice.Path), nline+1, err.Error())) errs = append(errs, fmt.Errorf("%q: flag #%d: step %s.", path.Base(exercice.Path), nline+1, err.Error()))
} }
flag.Type = fmt.Sprintf("number,%s,%s,%s", smin, smax, sstep) flag.Type = fmt.Sprintf("number,%s,%s,%s", smin, smax, sstep)
case "text": case "text":
@ -287,23 +283,23 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
case "mcq": case "mcq":
flag.Type = "mcq" flag.Type = "mcq"
default: default:
errs = append(errs, fmt.Sprintf("%q: flag #%d: invalid type of flag: should be 'key', 'number', 'text', 'mcq', 'ucq', 'radio' or 'vector'.", path.Base(exercice.Path), nline+1)) errs = append(errs, fmt.Errorf("%q: flag #%d: invalid type of flag: should be 'key', 'number', 'text', 'mcq', 'ucq', 'radio' or 'vector'.", path.Base(exercice.Path), nline+1))
return return
} }
if !strings.HasPrefix(flag.Type, "number") { if !strings.HasPrefix(flag.Type, "number") {
if flag.NumberMin != nil { if flag.NumberMin != nil {
errs = append(errs, fmt.Sprintf("%q: flag #%d: property min undefined for this kind of flag: should the type be 'number'.", path.Base(exercice.Path), nline+1)) errs = append(errs, fmt.Errorf("%q: flag #%d: property min undefined for this kind of flag: should the type be 'number'.", path.Base(exercice.Path), nline+1))
} else if flag.NumberMax != nil { } else if flag.NumberMax != nil {
errs = append(errs, fmt.Sprintf("%q: flag #%d: property max undefined for this kind of flag: should the type be 'number'.", path.Base(exercice.Path), nline+1)) errs = append(errs, fmt.Errorf("%q: flag #%d: property max undefined for this kind of flag: should the type be 'number'.", path.Base(exercice.Path), nline+1))
} else if flag.NumberStep != nil { } else if flag.NumberStep != nil {
errs = append(errs, fmt.Sprintf("%q: flag #%d: property step undefined for this kind of flag: should the type be 'number'.", path.Base(exercice.Path), nline+1)) errs = append(errs, fmt.Errorf("%q: flag #%d: property step undefined for this kind of flag: should the type be 'number'.", path.Base(exercice.Path), nline+1))
} }
} }
if len(flag.Help) > 0 { if len(flag.Help) > 0 {
if mdhelp, err := ProcessMarkdown(i, flag.Help, exercice.Path); err != nil { if mdhelp, err := ProcessMarkdown(i, flag.Help, exercice.Path); err != nil {
errs = append(errs, fmt.Sprintf("%q: flag #%d: unable to parse property help as Markdown: %s", path.Base(exercice.Path), nline+1), err.Error()) errs = append(errs, fmt.Errorf("%q: flag #%d: unable to parse property help as Markdown: %w", path.Base(exercice.Path), nline+1, err))
} else { } else {
flag.Help = mdhelp[3 : len(mdhelp)-4] flag.Help = mdhelp[3 : len(mdhelp)-4]
} }
@ -311,8 +307,8 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
if flag.Type == "label" { if flag.Type == "label" {
addedFlag, berrs := buildLabelFlag(exercice, flag, nline+1) addedFlag, berrs := buildLabelFlag(exercice, flag, nline+1)
if len(berrs) > 0 { for _, e := range berrs {
errs = append(errs, berrs...) errs = append(errs, fmt.Errorf("%q: flag #%d: %w", path.Base(exercice.Path), nline+1), e)
} }
if addedFlag != nil { if addedFlag != nil {
ret = append(ret, importFlag{ ret = append(ret, importFlag{
@ -322,8 +318,8 @@ 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" { } else if flag.Type == "key" || strings.HasPrefix(flag.Type, "number") || flag.Type == "text" || flag.Type == "ucq" || flag.Type == "radio" || flag.Type == "vector" {
addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag") addedFlag, choices, berrs := buildKeyFlag(exercice, flag, nline+1, "Flag")
if len(berrs) > 0 { for _, e := range berrs {
errs = append(errs, berrs...) errs = append(errs, fmt.Errorf("%q: flag #%d: %w", path.Base(exercice.Path), nline+1), e)
} }
if addedFlag != nil { if addedFlag != nil {
ret = append(ret, importFlag{ ret = append(ret, importFlag{
@ -344,7 +340,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
isJustified := false isJustified := false
if len(flag.Variant) != 0 { if len(flag.Variant) != 0 {
errs = append(errs, fmt.Sprintf("%q: flag #%d: variant is not defined for this kind of flag.", path.Base(exercice.Path), nline+1)) errs = append(errs, fmt.Errorf("%q: flag #%d: variant is not defined for this kind of flag.", path.Base(exercice.Path), nline+1))
} }
if !flag.NoShuffle { if !flag.NoShuffle {
@ -358,7 +354,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
if choice.Raw != nil { if choice.Raw != nil {
if hasOne && !isJustified { if hasOne && !isJustified {
errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline+1)) errs = append(errs, fmt.Errorf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline+1))
continue continue
} }
@ -367,13 +363,13 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
} else if p, ok := choice.Value.(bool); ok { } else if p, ok := choice.Value.(bool); ok {
val = p val = p
if isJustified { if isJustified {
errs = append(errs, fmt.Sprintf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline+1)) errs = append(errs, fmt.Errorf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline+1))
continue continue
} }
} else if choice.Value == nil { } else if choice.Value == nil {
val = false val = false
} else { } else {
errs = append(errs, fmt.Sprintf("%q: error in MCQ %d choice %d: incorrect type for value: %T is not boolean.", path.Base(exercice.Path), nline+1, cid, choice.Value)) errs = append(errs, fmt.Errorf("%q: error in MCQ %d choice %d: incorrect type for value: %T is not boolean.", path.Base(exercice.Path), nline+1, cid, choice.Value))
continue continue
} }
@ -406,7 +402,7 @@ func buildExerciceFlag(i Importer, exercice *fic.Exercice, flag ExerciceFlag, nl
} }
// buildExerciceFlags read challenge.txt and extract all flags. // buildExerciceFlags read challenge.txt and extract all flags.
func buildExerciceFlags(i Importer, exercice *fic.Exercice) (flags map[int64]importFlag, flagids []int64, errs []string) { func buildExerciceFlags(i Importer, exercice *fic.Exercice) (flags map[int64]importFlag, flagids []int64, errs []error) {
params, gerrs := getExerciceParams(i, exercice) params, gerrs := getExerciceParams(i, exercice)
if len(gerrs) > 0 { if len(gerrs) > 0 {
return flags, flagids, gerrs return flags, flagids, gerrs
@ -422,7 +418,7 @@ func buildExerciceFlags(i Importer, exercice *fic.Exercice) (flags map[int64]imp
// Ensure flag ID is unique // Ensure flag ID is unique
for _, ok := flags[flag.Id]; ok; _, ok = flags[flag.Id] { for _, ok := flags[flag.Id]; ok; _, ok = flags[flag.Id] {
errs = append(errs, fmt.Sprintf("%q: flag #%d: identifier already used (%d), using a random one.", path.Base(exercice.Path), nline+1, flag.Id)) errs = append(errs, fmt.Errorf("%q: flag #%d: identifier already used (%d), using a random one.", path.Base(exercice.Path), nline+1, flag.Id))
flag.Id = rand.Int63() flag.Id = rand.Int63()
} }
@ -457,7 +453,7 @@ func buildExerciceFlags(i Importer, exercice *fic.Exercice) (flags map[int64]imp
} }
// CheckExerciceFlags checks if all flags for the given challenge are correct. // CheckExerciceFlags checks if all flags for the given challenge are correct.
func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string) (rf []fic.Flag, errs []string) { func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string) (rf []fic.Flag, errs []error) {
flags, flagsids, berrs := buildExerciceFlags(i, exercice) flags, flagsids, berrs := buildExerciceFlags(i, exercice)
errs = append(errs, berrs...) errs = append(errs, berrs...)
@ -466,7 +462,7 @@ func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string) (rf
// Check dependency to flag // Check dependency to flag
for _, nf := range flag.FlagsDeps { for _, nf := range flag.FlagsDeps {
if _, ok := flags[nf]; !ok { if _, ok := flags[nf]; !ok {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to flag id=%d: id not defined", path.Base(exercice.Path), flag.Line, nf)) errs = append(errs, fmt.Errorf("%q: error flag #%d dependency to flag id=%d: id not defined", path.Base(exercice.Path), flag.Line, nf))
} }
} }
@ -480,7 +476,7 @@ func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string) (rf
} }
} }
if !found { if !found {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: No such file", path.Base(exercice.Path), flag.Line, lf)) errs = append(errs, fmt.Errorf("%q: error flag #%d dependency to %s: No such file", path.Base(exercice.Path), flag.Line, lf))
} }
} }
@ -509,11 +505,11 @@ func ExerciceFlagsMap(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.Fl
} }
// SyncExerciceFlags imports all kind of flags for the given challenge. // SyncExerciceFlags imports all kind of flags for the given challenge.
func SyncExerciceFlags(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.Flag, errs []string) { func SyncExerciceFlags(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.Flag, errs []error) {
if _, err := exercice.WipeFlags(); err != nil { if _, err := exercice.WipeFlags(); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
} else if _, err := exercice.WipeMCQs(); err != nil { } else if _, err := exercice.WipeMCQs(); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
} else { } else {
flags, flagids, berrs := buildExerciceFlags(i, exercice) flags, flagids, berrs := buildExerciceFlags(i, exercice)
errs = append(errs, berrs...) errs = append(errs, berrs...)
@ -524,12 +520,12 @@ func SyncExerciceFlags(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.F
for _, flagid := range flagids { for _, flagid := range flagids {
if flag, ok := flags[flagid]; ok { if flag, ok := flags[flagid]; ok {
if addedFlag, err := exercice.AddFlag(flag.Flag); err != nil { if addedFlag, err := exercice.AddFlag(flag.Flag); err != nil {
errs = append(errs, fmt.Sprintf("%q: error flag #%d: %s", path.Base(exercice.Path), flag.Line, err)) errs = append(errs, fmt.Errorf("%q: error flag #%d: %w", path.Base(exercice.Path), flag.Line, err))
} else { } else {
if f, ok := addedFlag.(*fic.FlagKey); ok { if f, ok := addedFlag.(*fic.FlagKey); ok {
for _, choice := range flag.Choices { for _, choice := range flag.Choices {
if _, err := f.AddChoice(choice); err != nil { if _, err := f.AddChoice(choice); err != nil {
errs = append(errs, fmt.Sprintf("%q: error in flag #%d choice #FIXME: %s", path.Base(exercice.Path), flag.Line, err)) errs = append(errs, fmt.Errorf("%q: error in flag #%d choice #FIXME: %w", path.Base(exercice.Path), flag.Line, err))
} }
} }
} }
@ -539,18 +535,18 @@ func SyncExerciceFlags(i Importer, exercice *fic.Exercice) (kmap map[int64]fic.F
// Import dependency to flag // Import dependency to flag
for _, nf := range flag.FlagsDeps { for _, nf := range flag.FlagsDeps {
if rf, ok := kmap[nf]; !ok { if rf, ok := kmap[nf]; !ok {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to flag id=%d: id not defined, perhaps not available at time of processing", path.Base(exercice.Path), flag.Line, nf)) errs = append(errs, fmt.Errorf("%q: error flag #%d dependency to flag id=%d: id not defined, perhaps not available at time of processing", path.Base(exercice.Path), flag.Line, nf))
} else if err := addedFlag.AddDepend(rf); err != nil { } else if err := addedFlag.AddDepend(rf); err != nil {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to id=%d: %s", path.Base(exercice.Path), flag.Line, nf, err)) errs = append(errs, fmt.Errorf("%q: error flag #%d dependency to id=%d: %w", path.Base(exercice.Path), flag.Line, nf, err))
} }
} }
// Import dependency to file // Import dependency to file
for _, lf := range flag.FilesDeps { for _, lf := range flag.FilesDeps {
if rf, err := exercice.GetFileByFilename(lf); err != nil { if rf, err := exercice.GetFileByFilename(lf); err != nil {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), flag.Line, lf, err)) errs = append(errs, fmt.Errorf("%q: error flag #%d dependency to %s: %w", path.Base(exercice.Path), flag.Line, lf, err))
} else if err := rf.AddDepend(addedFlag); err != nil { } else if err := rf.AddDepend(addedFlag); err != nil {
errs = append(errs, fmt.Sprintf("%q: error flag #%d dependency to %s: %s", path.Base(exercice.Path), flag.Line, lf, err)) errs = append(errs, fmt.Errorf("%q: error flag #%d dependency to %s: %w", path.Base(exercice.Path), flag.Line, lf, err))
} }
} }
} }

View file

@ -93,7 +93,7 @@ func parseExerciceDirname(edir string) (eid int, ename string, err error) {
} }
// BuildExercice creates an Exercice from a given importer. // BuildExercice creates an Exercice from a given importer.
func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice) (e *fic.Exercice, p ExerciceParams, eid int, edir string, errs []string) { func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice) (e *fic.Exercice, p ExerciceParams, eid int, edir string, errs []error) {
e = &fic.Exercice{} e = &fic.Exercice{}
e.Path = epath e.Path = epath
@ -102,7 +102,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
var err error var err error
eid, e.Title, err = parseExerciceDirname(edir) eid, e.Title, err = parseExerciceDirname(edir)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to parse exercice directory: %s", edir, err)) errs = append(errs, fmt.Errorf("unable to parse exercice directory: %w", err))
return nil, p, eid, edir, errs return nil, p, eid, edir, errs
} }
@ -110,7 +110,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 { if myTitle, err := GetFileContent(i, path.Join(epath, "title.txt")); err == nil {
myTitle = strings.TrimSpace(myTitle) myTitle = strings.TrimSpace(myTitle)
if strings.Contains(myTitle, "\n") { if strings.Contains(myTitle, "\n") {
errs = append(errs, fmt.Sprintf("%q: title.txt: Title can't contain new lines", edir)) errs = append(errs, fmt.Errorf("title.txt: Title can't contain new lines"))
} else { } else {
e.Title = myTitle e.Title = myTitle
} }
@ -128,20 +128,20 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
err = fmt.Errorf("Unable to find overview.txt nor overview.md") err = fmt.Errorf("Unable to find overview.txt nor overview.md")
} }
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: overview.txt: %s", edir, err)) errs = append(errs, fmt.Errorf("overview.txt: %s", err))
} else { } else {
e.Overview = fixnbsp(e.Overview) e.Overview = fixnbsp(e.Overview)
var buf bytes.Buffer var buf bytes.Buffer
err := goldmark.Convert([]byte(strings.Split(e.Overview, "\n")[0]), &buf) err := goldmark.Convert([]byte(strings.Split(e.Overview, "\n")[0]), &buf)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: overview.md: an error occurs during markdown formating of the headline: %s", edir, err)) errs = append(errs, fmt.Errorf("overview.md: an error occurs during markdown formating of the headline: %w", err))
} else { } else {
e.Headline = string(buf.Bytes()) e.Headline = string(buf.Bytes())
} }
if e.Overview, err = ProcessMarkdown(i, e.Overview, epath); err != nil { if e.Overview, err = ProcessMarkdown(i, e.Overview, epath); err != nil {
errs = append(errs, fmt.Sprintf("%q: overview.md: an error occurs during markdown formating: %s", edir, err)) errs = append(errs, fmt.Errorf("overview.md: an error occurs during markdown formating: %w", err))
} }
} }
@ -153,20 +153,20 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
err = fmt.Errorf("Unable to find statement.txt nor statement.md") err = fmt.Errorf("Unable to find statement.txt nor statement.md")
} }
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: statement.md: %s", edir, err)) errs = append(errs, fmt.Errorf("statement.md: %w", err))
} else { } else {
if e.Statement, err = ProcessMarkdown(i, fixnbsp(e.Statement), epath); err != nil { if e.Statement, err = ProcessMarkdown(i, fixnbsp(e.Statement), epath); err != nil {
errs = append(errs, fmt.Sprintf("%q: statement.md: an error occurs during markdown formating: %s", edir, err)) errs = append(errs, fmt.Errorf("statement.md: an error occurs during markdown formating: %w", err))
} }
} }
if i.exists(path.Join(epath, "finished.txt")) { if i.exists(path.Join(epath, "finished.txt")) {
e.Finished, err = GetFileContent(i, path.Join(epath, "finished.txt")) e.Finished, err = GetFileContent(i, path.Join(epath, "finished.txt"))
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: finished.txt: %s", edir, err)) errs = append(errs, fmt.Errorf("finished.txt: %w", err))
} else { } else {
if e.Finished, err = ProcessMarkdown(i, e.Finished, epath); err != nil { if e.Finished, err = ProcessMarkdown(i, e.Finished, epath); err != nil {
errs = append(errs, fmt.Sprintf("%q: finished.txt: an error occurs during markdown formating: %s", edir, err)) errs = append(errs, fmt.Errorf("finished.txt: an error occurs during markdown formating: %w", err))
} }
} }
} }
@ -175,19 +175,19 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
var md toml.MetaData var md toml.MetaData
p, md, err = parseExerciceParams(i, epath) p, md, err = parseExerciceParams(i, epath)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: %s", edir, err)) errs = append(errs, fmt.Errorf("challenge.txt: %w", err))
return return
} }
// Alert about unknown keys in challenge.txt // Alert about unknown keys in challenge.txt
if len(md.Undecoded()) > 0 { if len(md.Undecoded()) > 0 {
for _, k := range md.Undecoded() { for _, k := range md.Undecoded() {
errs = append(errs, fmt.Sprintf("%q: challenge.txt: unknown key %q found, check https://srs.nemunai.re/fic/files/challenge/", edir, k)) errs = append(errs, fmt.Errorf("challenge.txt: unknown key %q found, check https://srs.nemunai.re/fic/files/challenge/", k))
} }
} }
if p.Gain == 0 { if p.Gain == 0 {
errs = append(errs, fmt.Sprintf("%q: challenge.txt: Undefined gain for challenge", edir)) errs = append(errs, fmt.Errorf("challenge.txt: Undefined gain for challenge"))
} else { } else {
e.Gain = p.Gain e.Gain = p.Gain
} }
@ -195,11 +195,11 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
// Handle dependency // Handle dependency
if len(p.Dependencies) > 0 { if len(p.Dependencies) > 0 {
if len(p.Dependencies[0].Theme) > 0 && p.Dependencies[0].Theme != theme.Name { if len(p.Dependencies[0].Theme) > 0 && p.Dependencies[0].Theme != theme.Name {
errs = append(errs, fmt.Sprintf("%q: unable to treat dependency to another theme (%q): not implemented.", edir, p.Dependencies[0].Theme)) errs = append(errs, fmt.Errorf("unable to treat dependency to another theme (%q): not implemented.", p.Dependencies[0].Theme))
} else { } else {
if dmap == nil { if dmap == nil {
if dmap2, err := buildDependancyMap(i, theme); err != nil { if dmap2, err := buildDependancyMap(i, theme); err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to build dependency map: %s", edir, err)) errs = append(errs, fmt.Errorf("unable to build dependency map: %w", err))
} else { } else {
dmap = &dmap2 dmap = &dmap2
} }
@ -217,7 +217,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
for k, _ := range *dmap { for k, _ := range *dmap {
dmap_keys = append(dmap_keys, fmt.Sprintf("%d", k)) dmap_keys = append(dmap_keys, fmt.Sprintf("%d", k))
} }
errs = append(errs, fmt.Sprintf("%q: Unable to find required exercice dependancy %d (available at time of processing: %s)", edir, p.Dependencies[0].Id, strings.Join(dmap_keys, ","))) errs = append(errs, fmt.Errorf("Unable to find required exercice dependancy %d (available at time of processing: %s)", p.Dependencies[0].Id, strings.Join(dmap_keys, ",")))
} }
} }
} }
@ -230,10 +230,10 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
if !i.exists(e.VideoURI) { if !i.exists(e.VideoURI) {
e.VideoURI = "" e.VideoURI = ""
} else if size, err := getFileSize(i, e.VideoURI); err != nil { } else if size, err := getFileSize(i, e.VideoURI); err != nil {
errs = append(errs, fmt.Sprintf("%q: resolution.mp4: ", edir, err)) errs = append(errs, fmt.Errorf("resolution.mp4: %w", err))
e.VideoURI = "" e.VideoURI = ""
} else if size == 0 { } else if size == 0 {
errs = append(errs, fmt.Sprintf("%q: resolution.mp4: The file is empty!", edir)) errs = append(errs, fmt.Errorf("resolution.mp4: The file is empty!"))
e.VideoURI = "" e.VideoURI = ""
} else { } else {
e.VideoURI = strings.Replace(url.PathEscape(path.Join("$RFILES$", e.VideoURI)), "%2F", "/", -1) e.VideoURI = strings.Replace(url.PathEscape(path.Join("$RFILES$", e.VideoURI)), "%2F", "/", -1)
@ -247,13 +247,13 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
if i.exists(writeup) { if i.exists(writeup) {
if size, err := getFileSize(i, writeup); err != nil { if size, err := getFileSize(i, writeup); err != nil {
errs = append(errs, fmt.Sprintf("%q: resolution.md: %s", edir, err.Error())) errs = append(errs, fmt.Errorf("resolution.md: %w", err))
} else if size == 0 { } else if size == 0 {
errs = append(errs, fmt.Sprintf("%q: resolution.md: The file is empty!", edir)) errs = append(errs, fmt.Errorf("resolution.md: The file is empty!"))
} else if e.Resolution, err = GetFileContent(i, writeup); err != nil { } else if e.Resolution, err = GetFileContent(i, writeup); err != nil {
errs = append(errs, fmt.Sprintf("%q: resolution.md: %s", edir, err.Error())) errs = append(errs, fmt.Errorf("resolution.md: %w", err))
} else if e.Resolution, err = ProcessMarkdown(i, e.Resolution, epath); err != nil { } else if e.Resolution, err = ProcessMarkdown(i, e.Resolution, epath); err != nil {
errs = append(errs, fmt.Sprintf("%q: resolution.md: error during markdown processing: %s", edir, err.Error())) errs = append(errs, fmt.Errorf("resolution.md: error during markdown processing: %w", err))
} else { } else {
resolutionFound = true resolutionFound = true
} }
@ -263,7 +263,7 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
if LogMissingResolution { if LogMissingResolution {
log.Printf("%q: no resolution video or text file found in %s", edir, epath, epath) log.Printf("%q: no resolution video or text file found in %s", edir, epath, epath)
} else { } else {
errs = append(errs, fmt.Sprintf("%q: no resolution video or text file found in %s", edir, epath)) errs = append(errs, fmt.Errorf("no resolution video or text file found in %s", epath))
} }
} }
@ -271,28 +271,32 @@ func BuildExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*
} }
// SyncExercice imports new or updates existing given exercice. // SyncExercice imports new or updates existing given exercice.
func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice) (e *fic.Exercice, eid int, errs []string) { func SyncExercice(i Importer, theme *fic.Theme, epath string, dmap *map[int64]*fic.Exercice) (e *fic.Exercice, eid int, errs []error) {
var err error var err error
var edir string var edir string
var p ExerciceParams var p ExerciceParams
var berrors []error
e, p, eid, edir, errs = BuildExercice(i, theme, epath, dmap) e, p, eid, edir, berrors = BuildExercice(i, theme, epath, dmap)
for _, e := range berrors {
errs = append(errs, fmt.Errorf("%q: %w", edir, e))
}
if e != nil { if e != nil {
// Create or update the exercice // Create or update the exercice
err = theme.SaveNamedExercice(e) err = theme.SaveNamedExercice(e)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: error on exercice save: %s", edir, err)) errs = append(errs, fmt.Errorf("%q: error on exercice save: %w", edir, err))
return return
} }
// Import eercice tags // Import eercice tags
if _, err := e.WipeTags(); err != nil { if _, err := e.WipeTags(); err != nil {
errs = append(errs, fmt.Sprintf("%q: Unable to wipe tags: %s", edir, err)) errs = append(errs, fmt.Errorf("%q: Unable to wipe tags: %w", edir, err))
} }
for _, tag := range p.Tags { for _, tag := range p.Tags {
if _, err := e.AddTag(tag); err != nil { if _, err := e.AddTag(tag); err != nil {
errs = append(errs, fmt.Sprintf("%q: Unable to add tag: %s", edir, err)) errs = append(errs, fmt.Errorf("%q: Unable to add tag: %w", edir, err))
return return
} }
} }
@ -302,9 +306,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. // SyncExercices imports new or updates existing exercices, in a given theme.
func SyncExercices(i Importer, theme *fic.Theme) (errs []string) { func SyncExercices(i Importer, theme *fic.Theme) (errs []error) {
if exercices, err := GetExercices(i, theme); err != nil { if exercices, err := GetExercices(i, theme); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
} else { } else {
emap := map[string]int{} emap := map[string]int{}

View file

@ -2,7 +2,6 @@ package sync
import ( import (
"encoding/json" "encoding/json"
"fmt"
"log" "log"
"os" "os"
"sync" "sync"
@ -27,8 +26,19 @@ func avoidImporterSync() bool {
return DeepSyncProgress > 1 && DeepSyncProgress < 255 return DeepSyncProgress > 1 && DeepSyncProgress < 255
} }
type SyncReport struct {
DateStart time.Time `json:"_started"`
DateEnd time.Time `json:"_ended"`
DateUpdated []time.Time `json:"_updated"`
Regeneration []error `json:"_regeneration"`
SyncError error `json:"_sync,omitempty"`
SyncId string `json:"_id,omitempty"`
ThemesSync []error `json:"_themes,omitempty"`
Themes map[string][]error `json:"themes"`
}
// SpeedySyncDeep performs a recursive synchronisation without importing files. // SpeedySyncDeep performs a recursive synchronisation without importing files.
func SpeedySyncDeep(i Importer) (errs map[string][]string) { func SpeedySyncDeep(i Importer) (errs SyncReport) {
oneDeepSync.Lock() oneDeepSync.Lock()
defer func() { defer func() {
oneDeepSync.Unlock() oneDeepSync.Unlock()
@ -38,19 +48,19 @@ func SpeedySyncDeep(i Importer) (errs map[string][]string) {
}() }()
DeepSyncProgress = 1 DeepSyncProgress = 1
errs = map[string][]string{} errs.Themes = map[string][]error{}
startTime := time.Now() startTime := time.Now()
if err := i.Sync(); err != nil { if err := i.Sync(); err != nil {
errs["_sync"] = []string{err.Error()} errs.SyncError = err
if _id := i.Id(); _id != nil { if _id := i.Id(); _id != nil {
errs["_sync"] = append(errs["_sync"], *_id) errs.SyncId = *_id
} }
} }
errs["_date"] = []string{fmt.Sprintf("%v", startTime)} errs.DateStart = startTime
errs["_themes"] = SyncThemes(i) errs.ThemesSync = SyncThemes(i)
if themes, err := fic.GetThemes(); err == nil { if themes, err := fic.GetThemes(); err == nil {
DeepSyncProgress = 2 DeepSyncProgress = 2
@ -58,7 +68,7 @@ func SpeedySyncDeep(i Importer) (errs map[string][]string) {
for tid, theme := range themes { for tid, theme := range themes {
DeepSyncProgress = 3 + uint8(tid)*themeStep DeepSyncProgress = 3 + uint8(tid)*themeStep
errs[theme.Name] = SyncExercices(i, theme) errs.Themes[theme.Name] = SyncExercices(i, theme)
if exercices, err := theme.GetExercices(); err == nil { if exercices, err := theme.GetExercices(); err == nil {
if len(exercices) == 0 { if len(exercices) == 0 {
@ -70,18 +80,18 @@ func SpeedySyncDeep(i Importer) (errs map[string][]string) {
DeepSyncProgress = 3 + uint8(tid)*themeStep + uint8(eid)*exerciceStep DeepSyncProgress = 3 + uint8(tid)*themeStep + uint8(eid)*exerciceStep
flagsBindings, ferrs := SyncExerciceFlags(i, exercice) flagsBindings, ferrs := SyncExerciceFlags(i, exercice)
errs[theme.Name] = append(errs[theme.Name], ferrs...) errs.Themes[theme.Name] = append(errs.Themes[theme.Name], ferrs...)
DeepSyncProgress += exerciceStep / 2 DeepSyncProgress += exerciceStep / 2
_, herrs := SyncExerciceHints(i, exercice, flagsBindings) _, herrs := SyncExerciceHints(i, exercice, flagsBindings)
errs[theme.Name] = append(errs[theme.Name], herrs...) errs.Themes[theme.Name] = append(errs.Themes[theme.Name], herrs...)
} }
} }
} }
} }
DeepSyncProgress = 254 DeepSyncProgress = 254
errs["_date"] = append(errs["_date"], fmt.Sprintf("%v", time.Now())) errs.DateEnd = time.Now()
DeepSyncProgress = 255 DeepSyncProgress = 255
log.Println("Speedy synchronization done in", time.Since(startTime)) log.Println("Speedy synchronization done in", time.Since(startTime))
@ -89,7 +99,7 @@ func SpeedySyncDeep(i Importer) (errs map[string][]string) {
} }
// SyncDeep performs a recursive synchronisation: from themes to challenge items. // SyncDeep performs a recursive synchronisation: from themes to challenge items.
func SyncDeep(i Importer) (errs map[string][]string) { func SyncDeep(i Importer) (errs SyncReport) {
oneDeepSync.Lock() oneDeepSync.Lock()
defer func() { defer func() {
oneDeepSync.Unlock() oneDeepSync.Unlock()
@ -99,32 +109,32 @@ func SyncDeep(i Importer) (errs map[string][]string) {
}() }()
DeepSyncProgress = 1 DeepSyncProgress = 1
errs = map[string][]string{} errs.Themes = map[string][]error{}
startTime := time.Now() startTime := time.Now()
if err := i.Sync(); err != nil { if err := i.Sync(); err != nil {
errs["_sync"] = []string{err.Error()} errs.SyncError = err
} }
errs["_date"] = []string{fmt.Sprintf("%v", startTime)} errs.DateStart = startTime
errs["_themes"] = SyncThemes(i) errs.ThemesSync = SyncThemes(i)
if themes, err := fic.GetThemes(); err == nil && len(themes) > 0 { if themes, err := fic.GetThemes(); err == nil && len(themes) > 0 {
DeepSyncProgress = 2 DeepSyncProgress = 2
var themeStep uint8 = uint8(250) / uint8(len(themes)) var themeStep uint8 = uint8(250) / uint8(len(themes))
for tid, theme := range themes { for tid, theme := range themes {
errs[theme.Name] = SyncThemeDeep(i, theme, tid, themeStep) errs.Themes[theme.Name] = SyncThemeDeep(i, theme, tid, themeStep)
} }
} }
DeepSyncProgress = 254 DeepSyncProgress = 254
EditDeepReport(errs, true) EditDeepReport(&errs, true)
if err := settings.ForceRegeneration(); err != nil { if err := settings.ForceRegeneration(); err != nil {
errs["_regeneration"] = append(errs["_regeneration"], err.Error()) errs.Regeneration = append(errs.Regeneration, err)
} }
DeepSyncProgress = 255 DeepSyncProgress = 255
@ -132,7 +142,7 @@ func SyncDeep(i Importer) (errs map[string][]string) {
return return
} }
func readDeepReport() (ret map[string][]string, err error) { func readDeepReport() (ret *SyncReport, err error) {
if fdfrom, err := os.Open(DeepReportPath); err == nil { if fdfrom, err := os.Open(DeepReportPath); err == nil {
defer fdfrom.Close() defer fdfrom.Close()
@ -148,25 +158,23 @@ func readDeepReport() (ret map[string][]string, err error) {
return return
} }
func EditDeepReport(errs map[string][]string, erase bool) { func EditDeepReport(errs *SyncReport, erase bool) {
errs["_regeneration"] = []string{} errs.Regeneration = []error{}
if !erase { if !erase {
if in, err := readDeepReport(); err != nil { if in, err := readDeepReport(); err != nil {
errs["_regeneration"] = append(errs["_regeneration"], err.Error()) errs.Regeneration = append(errs.Regeneration, err)
log.Println(err) log.Println(err)
} else { } else {
for k, v := range errs { for k, v := range errs.Themes {
in[k] = v in.Themes[k] = v
} }
errs = in errs = in
} }
} }
if _, ok := errs["_date"]; ok { errs.DateUpdated = append(errs.DateUpdated, time.Now())
errs["_date"] = append(errs["_date"], fmt.Sprintf("%v", time.Now()))
}
if fdto, err := os.Create(DeepReportPath); err == nil { if fdto, err := os.Create(DeepReportPath); err == nil {
defer fdto.Close() defer fdto.Close()
@ -174,24 +182,24 @@ func EditDeepReport(errs map[string][]string, erase bool) {
if out, err := json.Marshal(errs); err == nil { if out, err := json.Marshal(errs); err == nil {
fdto.Write(out) fdto.Write(out)
} else { } else {
errs["_regeneration"] = append(errs["_regeneration"], err.Error()) errs.Regeneration = append(errs.Regeneration, err)
log.Println(err) log.Println(err)
} }
} else { } else {
errs["_regeneration"] = append(errs["_regeneration"], err.Error()) errs.Regeneration = append(errs.Regeneration, err)
log.Println(err) log.Println(err)
} }
} }
// SyncThemeDeep performs a recursive synchronisation: from challenges to challenge items. // SyncThemeDeep performs a recursive synchronisation: from challenges to challenge items.
func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8) (errs []string) { func SyncThemeDeep(i Importer, theme *fic.Theme, tid int, themeStep uint8) (errs []error) {
oneThemeDeepSync.Lock() oneThemeDeepSync.Lock()
defer oneThemeDeepSync.Unlock() defer oneThemeDeepSync.Unlock()
if !avoidImporterSync() { if !avoidImporterSync() {
if err := i.Sync(); err != nil { if err := i.Sync(); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
} }
} }

View file

@ -21,9 +21,7 @@ import (
) )
// GetThemes returns all theme directories in the base directory. // GetThemes returns all theme directories in the base directory.
func GetThemes(i Importer) ([]string, error) { func GetThemes(i Importer) (themes []string, err error) {
var themes []string
if dirs, err := i.listDir("/"); err != nil { if dirs, err := i.listDir("/"); err != nil {
return nil, err return nil, err
} else { } else {
@ -106,7 +104,7 @@ func getAuthors(i Importer, tname string) ([]string, error) {
} }
// BuildTheme creates a Theme from a given importer. // BuildTheme creates a Theme from a given importer.
func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []string) { func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []error) {
th = &fic.Theme{} th = &fic.Theme{}
th.Path = tdir th.Path = tdir
@ -122,7 +120,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []string) {
th.URLId = fic.ToURLid(th.Name) th.URLId = fic.ToURLid(th.Name)
if authors, err := getAuthors(i, tdir); err != nil { if authors, err := getAuthors(i, tdir); err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to get AUTHORS.txt: %s", th.Name, err)) errs = append(errs, fmt.Errorf("unable to get AUTHORS.txt: %w", err))
return nil, errs return nil, errs
} else { } else {
// Format authors // Format authors
@ -139,7 +137,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []string) {
err = fmt.Errorf("unable to find overview.txt nor overview.md") err = fmt.Errorf("unable to find overview.txt nor overview.md")
} }
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to get theme's overview: %s", th.Name, err)) errs = append(errs, fmt.Errorf("unable to get theme's overview: %w", err))
} else { } else {
// Split headline from intro // Split headline from intro
ovrvw := strings.Split(fixnbsp(intro), "\n") ovrvw := strings.Split(fixnbsp(intro), "\n")
@ -151,12 +149,12 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []string) {
// Format overview (markdown) // Format overview (markdown)
th.Intro, err = ProcessMarkdown(i, intro, tdir) th.Intro, err = ProcessMarkdown(i, intro, tdir)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: overview.txt: an error occurs during markdown formating: %s", tdir, err)) errs = append(errs, fmt.Errorf("overview.txt: an error occurs during markdown formating: %w", err))
} }
var buf bytes.Buffer var buf bytes.Buffer
err := goldmark.Convert([]byte(th.Headline), &buf) err := goldmark.Convert([]byte(th.Headline), &buf)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: overview.txt: an error occurs during markdown formating of the headline: %s", tdir, err)) errs = append(errs, fmt.Errorf("overview.txt: an error occurs during markdown formating of the headline: %w", err))
} else { } else {
th.Headline = string(buf.Bytes()) th.Headline = string(buf.Bytes())
} }
@ -167,7 +165,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []string) {
} else if i.exists(path.Join(tdir, "heading.png")) { } else if i.exists(path.Join(tdir, "heading.png")) {
th.Image = path.Join(tdir, "heading.png") th.Image = path.Join(tdir, "heading.png")
} else { } else {
errs = append(errs, fmt.Sprintf("%q: heading.jpg: No such file", tdir)) errs = append(errs, fmt.Errorf("heading.jpg: No such file"))
} }
if i.exists(path.Join(tdir, "partner.jpg")) { if i.exists(path.Join(tdir, "partner.jpg")) {
@ -178,11 +176,11 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []string) {
if i.exists(path.Join(tdir, "partner.txt")) { if i.exists(path.Join(tdir, "partner.txt")) {
if txt, err := GetFileContent(i, path.Join(tdir, "partner.txt")); err != nil { if txt, err := GetFileContent(i, path.Join(tdir, "partner.txt")); err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to get partner's text: %s", th.Name, err)) errs = append(errs, fmt.Errorf("unable to get partner's text: %w", err))
} else { } else {
th.PartnerText, err = ProcessMarkdown(i, txt, tdir) th.PartnerText, err = ProcessMarkdown(i, txt, tdir)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%q: partner.txt: an error occurs during markdown formating: %s", tdir, err)) errs = append(errs, fmt.Errorf("partner.txt: an error occurs during markdown formating: %w", err))
} }
} }
} }
@ -190,9 +188,9 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []string) {
} }
// SyncThemes imports new or updates existing themes. // SyncThemes imports new or updates existing themes.
func SyncThemes(i Importer) (errs []string) { func SyncThemes(i Importer) (errs []error) {
if themes, err := GetThemes(i); err != nil { if themes, err := GetThemes(i); err != nil {
errs = append(errs, err.Error()) errs = append(errs, fmt.Errorf("Unable to list themes: %w", err.Error()))
} else { } else {
rand.Shuffle(len(themes), func(i, j int) { rand.Shuffle(len(themes), func(i, j int) {
themes[i], themes[j] = themes[j], themes[i] themes[i], themes[j] = themes[j], themes[i]
@ -200,7 +198,9 @@ func SyncThemes(i Importer) (errs []string) {
for _, tdir := range themes { for _, tdir := range themes {
btheme, berrs := BuildTheme(i, tdir) btheme, berrs := BuildTheme(i, tdir)
errs = append(errs, berrs...) for _, e := range berrs {
errs = append(errs, fmt.Errorf("%q: %w", tdir, e))
}
if btheme == nil { if btheme == nil {
continue continue
@ -216,7 +216,7 @@ func SyncThemes(i Importer) (errs []string) {
btheme.Image = strings.TrimPrefix(filePath, fic.FilesDir) btheme.Image = strings.TrimPrefix(filePath, fic.FilesDir)
return nil, nil return nil, nil
}); err != nil { }); err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to import heading image: %s", tdir, err)) errs = append(errs, fmt.Errorf("%q: unable to import heading image: %w", tdir, err))
} }
} }
@ -226,14 +226,14 @@ func SyncThemes(i Importer) (errs []string) {
btheme.PartnerImage = strings.TrimPrefix(filePath, fic.FilesDir) btheme.PartnerImage = strings.TrimPrefix(filePath, fic.FilesDir)
return nil, nil return nil, nil
}); err != nil { }); err != nil {
errs = append(errs, fmt.Sprintf("%q: unable to import partner image: %s", tdir, err)) errs = append(errs, fmt.Errorf("%q: unable to import partner image: %w", tdir, err))
} }
} }
var theme *fic.Theme var theme *fic.Theme
if theme, err = fic.GetThemeByPath(btheme.Path); err != nil { if theme, err = fic.GetThemeByPath(btheme.Path); err != nil {
if _, err := fic.CreateTheme(btheme); err != nil { if _, err := fic.CreateTheme(btheme); err != nil {
errs = append(errs, fmt.Sprintf("%q: an error occurs during add: %s", tdir, err)) errs = append(errs, fmt.Errorf("%q: an error occurs during add: %w", tdir, err))
continue continue
} }
} }
@ -241,7 +241,7 @@ func SyncThemes(i Importer) (errs []string) {
if !fic.CmpTheme(theme, btheme) { if !fic.CmpTheme(theme, btheme) {
btheme.Id = theme.Id btheme.Id = theme.Id
if _, err := btheme.Update(); err != nil { if _, err := btheme.Update(); err != nil {
errs = append(errs, fmt.Sprintf("%q: an error occurs during update: %s", tdir, err)) errs = append(errs, fmt.Errorf("%q: an error occurs during update: %w", tdir, err))
continue continue
} }
} }

View file

@ -106,14 +106,14 @@ func searchBinaryInGit(edir string) (ret []string) {
return return
} }
func checkExercice(theme *fic.Theme, edir string, dmap *map[int64]*fic.Exercice) (errs []string) { func checkExercice(theme *fic.Theme, edir string, dmap *map[int64]*fic.Exercice) (errs []error) {
e, _, eid, _, berrs := sync.BuildExercice(sync.GlobalImporter, theme, path.Join(theme.Path, edir), dmap) e, _, eid, _, berrs := sync.BuildExercice(sync.GlobalImporter, theme, path.Join(theme.Path, edir), dmap)
errs = append(errs, berrs...) errs = append(errs, berrs...)
if e != nil { if e != nil {
// Files // Files
var files []string var files []string
var cerrs []string var cerrs []error
if !skipFileChecks { if !skipFileChecks {
files, cerrs = sync.CheckExerciceFiles(sync.GlobalImporter, e) files, cerrs = sync.CheckExerciceFiles(sync.GlobalImporter, e)
log.Printf("%d files checked.\n", len(files)) log.Printf("%d files checked.\n", len(files))
@ -249,7 +249,7 @@ func main() {
for _, edir := range exercices { for _, edir := range exercices {
for _, err := range checkExercice(theme, edir, &dmap) { for _, err := range checkExercice(theme, edir, &dmap) {
nberr += 1 nberr += 1
log.Println(err) log.Println(err.Error())
} }
log.Printf("================================== Exercice %q treated\n", edir) log.Printf("================================== Exercice %q treated\n", edir)
} }