diff --git a/admin/sync/exercice_hints.go b/admin/sync/exercice_hints.go index 30f427a2..9b2f2ddd 100644 --- a/admin/sync/exercice_hints.go +++ b/admin/sync/exercice_hints.go @@ -15,72 +15,92 @@ import ( _ "golang.org/x/crypto/blake2b" ) -// SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge. -func SyncExerciceHints(i Importer, exercice fic.Exercice) (errs []string) { +func buildExerciceHints(i Importer, exercice fic.Exercice) (hints []fic.EHint, errs []string) { params, err := parseExerciceParams(i, exercice.Path) if err != nil { errs = append(errs, fmt.Sprintf("%q: challenge.txt: %s", path.Base(exercice.Path), err)) + return } + for n, hint := range params.Hints { + h := fic.EHint{} + if hint.Title == "" { + h.Title = fmt.Sprintf("Astuce #%d", n+1) + } else { + h.Title = fixnbsp(hint.Title) + } + if hint.Cost <= 0 { + h.Cost = exercice.Gain / 4 + } else { + h.Cost = hint.Cost + } + + if hint.Filename != "" { + 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)) + continue + } 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)) + continue + } else { + // Handle files as downloadable content + if res, err := i.importFile(path.Join(exercice.Path, "hints", hint.Filename), func(filePath string, origin string) (interface{}, error) { + // Calculate hash + hash512 := crypto.BLAKE2b_512.New() + if fd, err := os.Open(filePath); err != nil { + return nil, err + } else { + defer fd.Close() + + reader := bufio.NewReader(fd) + if _, err := io.Copy(hash512, reader); err != nil { + return nil, err + } + } + result512 := hash512.Sum(nil) + + // 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, fmt.Sprintf("%q: unable to import hint file %q: %s", path.Base(exercice.Path), hint.Filename, err)) + continue + } 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)) + continue + } else { + h.Content = s + } + } + } 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)) + continue + } 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)) + } + + hints = append(hints, h) + } + + return +} + +// CheckExerciceHints checks if all hints are corrects.. +func CheckExerciceHints(i Importer, exercice fic.Exercice) ([]fic.EHint, []string) { + return buildExerciceHints(i, exercice) +} + +// SyncExerciceHints reads the content of hints/ directories and import it as EHint for the given challenge. +func SyncExerciceHints(i Importer, exercice fic.Exercice) (errs []string) { if _, err := exercice.WipeHints(); err != nil { errs = append(errs, err.Error()) } else { - for n, hint := range params.Hints { - if hint.Title == "" { - hint.Title = fmt.Sprintf("Astuce #%d", n+1) - } else { - hint.Title = fixnbsp(hint.Title) - } - if hint.Cost <= 0 { - hint.Cost = exercice.Gain / 4 - } - - if hint.Filename != "" { - 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)) - continue - } 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)) - continue - } else { - // Handle files as downloadable content - if res, err := i.importFile(path.Join(exercice.Path, "hints", hint.Filename), func(filePath string, origin string) (interface{}, error) { - // Calculate hash - hash512 := crypto.BLAKE2b_512.New() - if fd, err := os.Open(filePath); err != nil { - return nil, err - } else { - defer fd.Close() - - reader := bufio.NewReader(fd) - if _, err := io.Copy(hash512, reader); err != nil { - return nil, err - } - } - result512 := hash512.Sum(nil) - - // 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, fmt.Sprintf("%q: unable to import hint file %q: %s", path.Base(exercice.Path), hint.Filename, err)) - continue - } 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)) - continue - } else { - hint.Content = s - } - } - } 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)) - continue - } else if hint.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)) - } + hints, berrs := buildExerciceHints(i, exercice) + errs = append(errs, berrs...) + for n, hint := range hints { // Import hint if _, err := exercice.AddHint(hint.Title, hint.Content, hint.Cost); err != nil { - errs = append(errs, fmt.Sprintf("%q: challenge.txt: hint %s (%d): %s", path.Base(exercice.Path), hint.Title, n+1, err)) + errs = append(errs, fmt.Sprintf("%q: hint #%d %s: %s", path.Base(exercice.Path), n+1, hint.Title, err)) } } }