sync: Report custom errors
This commit is contained in:
parent
08ea1bac0d
commit
c78545c18b
|
@ -22,6 +22,7 @@ steps:
|
||||||
- go get -v -d srs.epita.fr/fic-server/frontend
|
- go get -v -d srs.epita.fr/fic-server/frontend
|
||||||
- go get -v -d srs.epita.fr/fic-server/dashboard
|
- go get -v -d srs.epita.fr/fic-server/dashboard
|
||||||
- go get -v -d srs.epita.fr/fic-server/repochecker
|
- go get -v -d srs.epita.fr/fic-server/repochecker
|
||||||
|
- go get -v -d srs.epita.fr/fic-server/repochecker/epita
|
||||||
- go get -v -d srs.epita.fr/fic-server/qa
|
- go get -v -d srs.epita.fr/fic-server/qa
|
||||||
- mkdir deploy
|
- mkdir deploy
|
||||||
|
|
||||||
|
@ -39,6 +40,7 @@ steps:
|
||||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/frontend
|
- go vet -v -buildvcs=false srs.epita.fr/fic-server/frontend
|
||||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/dashboard
|
- go vet -v -buildvcs=false srs.epita.fr/fic-server/dashboard
|
||||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/repochecker
|
- go vet -v -buildvcs=false srs.epita.fr/fic-server/repochecker
|
||||||
|
- go vet -v -buildvcs=false srs.epita.fr/fic-server/repochecker/epita
|
||||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/qa
|
- go vet -v -buildvcs=false srs.epita.fr/fic-server/qa
|
||||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/settings
|
- go vet -v -buildvcs=false srs.epita.fr/fic-server/settings
|
||||||
|
|
||||||
|
@ -93,7 +95,9 @@ steps:
|
||||||
- name: build repochecker
|
- name: build repochecker
|
||||||
image: golang:alpine
|
image: golang:alpine
|
||||||
commands:
|
commands:
|
||||||
|
- apk --no-cache add build-base
|
||||||
- go build -buildvcs=false --tags checkupdate -v -o deploy/repochecker-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/repochecker
|
- go build -buildvcs=false --tags checkupdate -v -o deploy/repochecker-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/repochecker
|
||||||
|
- go build -buildvcs=false -buildmode=plugin -v -o deploy/repochecker-epita-rules-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}.so srs.epita.fr/fic-server/repochecker/epita
|
||||||
- grep "const version" repochecker/update.go | sed -r 's/^.*=\s*(\S.*)$/\1/' > deploy/repochecker.version
|
- grep "const version" repochecker/update.go | sed -r 's/^.*=\s*(\S.*)$/\1/' > deploy/repochecker.version
|
||||||
environment:
|
environment:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
@ -332,6 +336,7 @@ steps:
|
||||||
- name: build admin
|
- name: build admin
|
||||||
image: golang:alpine
|
image: golang:alpine
|
||||||
commands:
|
commands:
|
||||||
|
- apk --no-cache add build-base
|
||||||
- go build -v -buildvcs=false -o deploy/admin-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/admin
|
- go build -v -buildvcs=false -o deploy/admin-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/admin
|
||||||
environment:
|
environment:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
@ -376,6 +381,7 @@ steps:
|
||||||
- name: build repochecker
|
- name: build repochecker
|
||||||
image: golang:alpine
|
image: golang:alpine
|
||||||
commands:
|
commands:
|
||||||
|
- apk --no-cache add build-base
|
||||||
- go build -buildvcs=false --tags checkupdate -v -o deploy/repochecker-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/repochecker
|
- go build -buildvcs=false --tags checkupdate -v -o deploy/repochecker-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/repochecker
|
||||||
environment:
|
environment:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
|
@ -8,9 +8,12 @@ COPY go.mod go.sum ./
|
||||||
COPY settings settings/
|
COPY settings settings/
|
||||||
COPY libfic ./libfic/
|
COPY libfic ./libfic/
|
||||||
COPY admin ./admin/
|
COPY admin ./admin/
|
||||||
|
COPY repochecker ./repochecker/
|
||||||
|
|
||||||
RUN go get -d -v ./admin && \
|
RUN apk add --no-cache build-base && \
|
||||||
go build -v -buildvcs=false -o admin/admin ./admin
|
go get -d -v ./admin && \
|
||||||
|
go build -v -buildvcs=false -o admin/admin ./admin && \
|
||||||
|
go build -v -buildmode=plugin -o repochecker/epita-rules.so ./repochecker/epita
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.16
|
FROM alpine:3.16
|
||||||
|
@ -29,3 +32,4 @@ WORKDIR /srv
|
||||||
ENTRYPOINT ["/srv/admin", "-bind=:8081", "-baseurl=/admin/"]
|
ENTRYPOINT ["/srv/admin", "-bind=:8081", "-baseurl=/admin/"]
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/admin/admin /srv/admin
|
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/admin/admin /srv/admin
|
||||||
|
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/epita-rules.so /srv/epita-rules.so
|
||||||
|
|
|
@ -10,14 +10,17 @@ COPY libfic ./libfic/
|
||||||
COPY admin ./admin/
|
COPY admin ./admin/
|
||||||
COPY repochecker ./repochecker/
|
COPY repochecker ./repochecker/
|
||||||
|
|
||||||
RUN go get -d -v ./repochecker && \
|
RUN apk add --no-cache build-base && \
|
||||||
go build -v -o repochecker/repochecker ./repochecker
|
go get -d -v ./repochecker && \
|
||||||
|
go build -v -o repochecker/repochecker ./repochecker && \
|
||||||
|
go build -v -buildmode=plugin -o repochecker/epita-rules.so ./repochecker/epita
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.16
|
FROM alpine:3.16
|
||||||
|
|
||||||
RUN apk add --no-cache git
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/repochecker"]
|
ENTRYPOINT ["/usr/bin/repochecker", "--checks-plugins=/usr/lib/epita-rules.so"]
|
||||||
|
|
||||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/repochecker /usr/bin/repochecker
|
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/repochecker /usr/bin/repochecker
|
||||||
|
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/repochecker/epita-rules.so /usr/lib/epita-rules.so
|
||||||
|
|
|
@ -68,6 +68,7 @@ func main() {
|
||||||
gitImporterRemote := ""
|
gitImporterRemote := ""
|
||||||
localImporterSymlink := false
|
localImporterSymlink := false
|
||||||
baseURL := "/"
|
baseURL := "/"
|
||||||
|
checkplugins := sync.CheckPluginList{}
|
||||||
|
|
||||||
// Read paremeters from environment
|
// Read paremeters from environment
|
||||||
if v, exists := os.LookupEnv("FICOIDC_SECRET"); exists {
|
if v, exists := os.LookupEnv("FICOIDC_SECRET"); exists {
|
||||||
|
@ -119,6 +120,7 @@ func main() {
|
||||||
flag.BoolVar(&fic.OptionalDigest, "optionaldigest", fic.OptionalDigest, "Is the digest required when importing files?")
|
flag.BoolVar(&fic.OptionalDigest, "optionaldigest", fic.OptionalDigest, "Is the digest required when importing files?")
|
||||||
flag.BoolVar(&fic.StrongDigest, "strongdigest", fic.StrongDigest, "Are BLAKE2b digests required or is SHA-1 good enough?")
|
flag.BoolVar(&fic.StrongDigest, "strongdigest", fic.StrongDigest, "Are BLAKE2b digests required or is SHA-1 good enough?")
|
||||||
flag.BoolVar(&api.IsProductionEnv, "4real", api.IsProductionEnv, "Set this flag when running for a real challenge (it disallows or avoid most of mass user progression deletion)")
|
flag.BoolVar(&api.IsProductionEnv, "4real", api.IsProductionEnv, "Set this flag when running for a real challenge (it disallows or avoid most of mass user progression deletion)")
|
||||||
|
flag.Var(&checkplugins, "rules-plugins", "List of libraries containing others rules to checks")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
log.SetPrefix("[admin] ")
|
log.SetPrefix("[admin] ")
|
||||||
|
@ -211,6 +213,15 @@ func main() {
|
||||||
os.MkdirAll(api.DashboardDir, 0777)
|
os.MkdirAll(api.DashboardDir, 0777)
|
||||||
os.MkdirAll(settings.SettingsDir, 0777)
|
os.MkdirAll(settings.SettingsDir, 0777)
|
||||||
|
|
||||||
|
// Load rules plugins
|
||||||
|
for _, p := range checkplugins {
|
||||||
|
if err := sync.LoadChecksPlugin(p); err != nil {
|
||||||
|
log.Fatalf("Unable to load rule plugin %q: %s", p, err.Error())
|
||||||
|
} else {
|
||||||
|
log.Printf("Rules plugin %q successfully loaded", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize settings and load them
|
// Initialize settings and load them
|
||||||
if !settings.ExistsSettings(path.Join(settings.SettingsDir, settings.SettingsFile)) {
|
if !settings.ExistsSettings(path.Join(settings.SettingsDir, settings.SettingsFile)) {
|
||||||
if err = api.ResetSettings(); err != nil {
|
if err = api.ResetSettings(); err != nil {
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrResolutionNotFound = fmt.Errorf("no resolution video or text file found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type ThemeError struct {
|
||||||
|
error
|
||||||
|
ThemeId int64
|
||||||
|
ThemePath string
|
||||||
|
ThemeName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewThemeError(theme *fic.Theme, err error) *ThemeError {
|
||||||
|
return &ThemeError{
|
||||||
|
error: err,
|
||||||
|
ThemeId: theme.Id,
|
||||||
|
ThemePath: path.Base(theme.Path),
|
||||||
|
ThemeName: theme.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ThemeError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %s", e.ThemePath, e.error.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ThemeError) GetError() error {
|
||||||
|
return e.error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExerciceError struct {
|
||||||
|
*ThemeError
|
||||||
|
ExerciceId int64
|
||||||
|
ExercicePath string
|
||||||
|
ExerciceName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExerciceError(exercice *fic.Exercice, err error, theme ...*fic.Theme) *ExerciceError {
|
||||||
|
ltheme := len(theme)
|
||||||
|
if ltheme > 1 {
|
||||||
|
log.Fatal("Only 1 variadic arg is accepted in NewExerciceError")
|
||||||
|
return nil
|
||||||
|
} else if ltheme == 1 {
|
||||||
|
return &ExerciceError{
|
||||||
|
ThemeError: NewThemeError(theme[0], err),
|
||||||
|
ExerciceId: exercice.Id,
|
||||||
|
ExercicePath: path.Base(exercice.Path),
|
||||||
|
ExerciceName: exercice.Title,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return &ExerciceError{
|
||||||
|
ThemeError: &ThemeError{error: err},
|
||||||
|
ExerciceId: exercice.Id,
|
||||||
|
ExercicePath: path.Base(exercice.Path),
|
||||||
|
ExerciceName: exercice.Title,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ExerciceError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %s", e.ExercicePath, e.ThemeError.error.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileError struct {
|
||||||
|
*ExerciceError
|
||||||
|
Filename string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileError(exercice *fic.Exercice, filename string, err error, theme ...*fic.Theme) *FileError {
|
||||||
|
return &FileError{
|
||||||
|
ExerciceError: NewExerciceError(exercice, err, theme...),
|
||||||
|
Filename: filename,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *FileError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: file %q: %s", e.ExercicePath, e.Filename, e.ThemeError.error.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChallengeTxtError struct {
|
||||||
|
*ExerciceError
|
||||||
|
ChallengeTxtLine uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChallengeTxtError(exercice *fic.Exercice, line uint, err error, theme ...*fic.Theme) *ChallengeTxtError {
|
||||||
|
return &ChallengeTxtError{
|
||||||
|
ExerciceError: NewExerciceError(exercice, err, theme...),
|
||||||
|
ChallengeTxtLine: line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ChallengeTxtError) Error() string {
|
||||||
|
if e.ChallengeTxtLine != 0 {
|
||||||
|
return fmt.Sprintf("%s:%d: %s", path.Join(e.ExercicePath, "challenge.txt"), e.ChallengeTxtLine, e.ThemeError.error.Error())
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%s: %s", path.Join(e.ExercicePath, "challenge.txt"), e.ThemeError.error.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HintError struct {
|
||||||
|
*ChallengeTxtError
|
||||||
|
HintId int
|
||||||
|
HintTitle string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHintError(exercice *fic.Exercice, hint *fic.EHint, line int, err error, theme ...*fic.Theme) *HintError {
|
||||||
|
return &HintError{
|
||||||
|
ChallengeTxtError: NewChallengeTxtError(exercice, 0, err, theme...),
|
||||||
|
HintId: line + 1,
|
||||||
|
HintTitle: hint.Title,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HintError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: hint#%d (%s): %s", path.Join(e.ExercicePath, "challenge.txt"), e.HintId, e.HintTitle, e.ThemeError.error.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlagError struct {
|
||||||
|
*ChallengeTxtError
|
||||||
|
FlagId int
|
||||||
|
FlagTitle string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFlagError(exercice *fic.Exercice, flag *ExerciceFlag, line int, err error, theme ...*fic.Theme) *FlagError {
|
||||||
|
return &FlagError{
|
||||||
|
ChallengeTxtError: NewChallengeTxtError(exercice, 0, err, theme...),
|
||||||
|
FlagId: line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *FlagError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: flag#%d: %s", path.Join(e.ExercicePath, "challenge.txt"), e.FlagId, e.ThemeError.error.Error())
|
||||||
|
}
|
|
@ -90,9 +90,9 @@ func parseExerciceParams(i Importer, exPath string) (p ExerciceParams, md toml.M
|
||||||
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
|
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.Errorf("%q: challenge.txt: %w", path.Base(exercice.Path), err))
|
errs = append(errs, NewChallengeTxtError(exercice, 0, 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.Errorf("%q: has no flag", path.Base(exercice.Path)))
|
errs = append(errs, NewChallengeTxtError(exercice, 0, fmt.Errorf("has no flag")))
|
||||||
} else {
|
} else {
|
||||||
// Treat legacy UCQ flags as ExerciceFlag
|
// Treat legacy UCQ flags as ExerciceFlag
|
||||||
for _, flag := range params.FlagsUCQ {
|
for _, flag := range params.FlagsUCQ {
|
||||||
|
|
|
@ -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.Errorf("%q: unable to read DIGESTS.txt: %w", path.Base(exercice.Path), err))
|
errs = append(errs, NewExerciceError(exercice, fmt.Errorf("unable to read %s: %w", path.Join(into, "DIGESTS.txt"), 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.Errorf("%q: unable to parse DIGESTS.txt line %d: invalid format", path.Base(exercice.Path), nline+1))
|
errs = append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: invalid format", path.Join(into, "DIGESTS.txt"), 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.Errorf("%q: unable to parse DIGESTS.txt line %d: %w", path.Base(exercice.Path), nline+1, err))
|
errs = append(errs, NewExerciceError(exercice, fmt.Errorf("unable to parse %s line %d: %w", path.Join(into, "DIGESTS.txt"), 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)
|
errs = append(errs, NewExerciceError(exercice, err))
|
||||||
} else {
|
} else {
|
||||||
for _, fname := range flist {
|
for _, fname := range flist {
|
||||||
if fname == "DIGESTS.txt" || fname == ".gitattributes" {
|
if fname == "DIGESTS.txt" || fname == ".gitattributes" {
|
||||||
|
@ -79,9 +79,9 @@ func CheckExerciceFilesPresence(i Importer, exercice *fic.Exercice) (files []str
|
||||||
|
|
||||||
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.Errorf("%q: unable to read file %q: No such file or directory", path.Base(exercice.Path), fname))
|
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("No such file or directory")))
|
||||||
} else if _, ok := digests[fname]; !ok {
|
} else if _, ok := digests[fname]; !ok {
|
||||||
errs = append(errs, fmt.Errorf("%q: unable to import file %q: No digest given", path.Base(exercice.Path), fname))
|
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to import file: No digest given")))
|
||||||
} 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.Errorf("%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, NewFileError(exercice, fname, fmt.Errorf("unable to read file: No such file or directory. Check your DIGESTS.txt for legacy entries.")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,10 +105,21 @@ 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.Errorf("%q: unable to read file %q: %w", path.Base(exercice.Path), fname, err))
|
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("unable to read file: %w", 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.Errorf("%q: %s: %w", path.Base(exercice.Path), fname, err))
|
errs = 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))
|
||||||
|
} else {
|
||||||
|
file := exercice.NewDummyFile(path.Join(exercice.Path, "files", fname), getDestinationFilePath(path.Join(exercice.Path, "files", fname)), (*hash512).Sum(nil), size)
|
||||||
|
|
||||||
|
// Call checks hooks
|
||||||
|
for _, h := range hooks.fileHooks {
|
||||||
|
for _, e := range h(file) {
|
||||||
|
errs = append(errs, NewFileError(exercice, fname, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
files = append(files, fname)
|
files = append(files, fname)
|
||||||
|
@ -128,19 +139,21 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice) (errs []error) {
|
||||||
|
|
||||||
// Import standard files
|
// Import standard files
|
||||||
for _, fname := range files {
|
for _, fname := range files {
|
||||||
// Enforce file format
|
|
||||||
if path.Ext(fname) == "rar" || path.Ext(fname) == "7z" {
|
|
||||||
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.Errorf("%q: unable to import file %q: %w", path.Base(exercice.Path), fname, err))
|
errs = append(errs, NewFileError(exercice, fname, err))
|
||||||
continue
|
continue
|
||||||
} else if f.(*fic.EFile).Size == 0 {
|
} else if f.(*fic.EFile).Size == 0 {
|
||||||
errs = append(errs, fmt.Errorf("%q: WARNING imported file %q is empty!", path.Base(exercice.Path), fname))
|
errs = append(errs, NewFileError(exercice, fname, fmt.Errorf("imported file is empty!")))
|
||||||
|
} else {
|
||||||
|
// Call checks hooks
|
||||||
|
for _, h := range hooks.fileHooks {
|
||||||
|
for _, e := range h(f.(*fic.EFile)) {
|
||||||
|
errs = append(errs, NewFileError(exercice, fname, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
@ -19,19 +19,19 @@ import (
|
||||||
|
|
||||||
type importHint struct {
|
type importHint struct {
|
||||||
Line int
|
Line int
|
||||||
Hint fic.EHint
|
Hint *fic.EHint
|
||||||
FlagsDeps []int64
|
FlagsDeps []int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildExerciceHints(i Importer, exercice *fic.Exercice) (hints []importHint, errs []error) {
|
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.Errorf("%q: challenge.txt: %w", path.Base(exercice.Path), err))
|
errs = append(errs, NewChallengeTxtError(exercice, 0, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for n, hint := range params.Hints {
|
for n, hint := range params.Hints {
|
||||||
h := fic.EHint{}
|
h := &fic.EHint{}
|
||||||
if hint.Title == "" {
|
if hint.Title == "" {
|
||||||
h.Title = fmt.Sprintf("Astuce #%d", n+1)
|
h.Title = fmt.Sprintf("Astuce #%d", n+1)
|
||||||
} else {
|
} else {
|
||||||
|
@ -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.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))
|
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be filled at the same time")))
|
||||||
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.Errorf("%q: challenge.txt: hint %s (%d): %s: File not found", path.Base(exercice.Path), hint.Title, n+1, hint.Filename))
|
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: File not found", hint.Filename)))
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
// Handle files as downloadable content
|
// Handle files as downloadable content
|
||||||
|
@ -70,20 +70,27 @@ 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.Errorf("%q: unable to import hint file %q: %w", path.Base(exercice.Path), hint.Filename, err))
|
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: %w", hint.Filename, err)))
|
||||||
continue
|
continue
|
||||||
} else if s, ok := res.(string); !ok {
|
} else if s, ok := res.(string); !ok {
|
||||||
errs = append(errs, fmt.Errorf("%q: unable to import hint file %q: invalid string returned as filename", path.Base(exercice.Path), hint.Filename))
|
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("%q: unable to import hint file: invalid string returned as filename", hint.Filename)))
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
h.Content = s
|
h.Content = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if hint.Content == "" {
|
} else if hint.Content == "" {
|
||||||
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))
|
errs = append(errs, NewHintError(exercice, h, n, fmt.Errorf("content and filename can't be empty at the same time")))
|
||||||
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.Errorf("%q: challenge.txt: hint %s (%d): error during markdown formating: %w", path.Base(exercice.Path), hint.Title, n+1, err))
|
errs = 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) {
|
||||||
|
errs = append(errs, NewHintError(exercice, h, n, e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newHint := importHint{
|
newHint := importHint{
|
||||||
|
@ -120,7 +127,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.Errorf("%q: hint #%d %s: %w", path.Base(exercice.Path), hint.Line, hint.Hint.Title, err))
|
errs = append(errs, NewHintError(exercice, hint.Hint, hint.Line, err))
|
||||||
} else {
|
} else {
|
||||||
hintsBindings[hint.Line] = h
|
hintsBindings[hint.Line] = h
|
||||||
|
|
||||||
|
@ -128,10 +135,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.Errorf("%q: error hint #%d dependency to flag #%d: %w", path.Base(exercice.Path), hint.Line, nf, herr))
|
errs = append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: %w", nf, herr)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errs = append(errs, fmt.Errorf("%q: error hint #%d dependency to flag #%d: Unexistant flag", path.Base(exercice.Path), hint.Line, nf))
|
errs = append(errs, NewHintError(exercice, hint.Hint, hint.Line, fmt.Errorf("error hint dependency to flag #%d: Unexistant flag", nf)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,16 +98,16 @@ func getRawKey(input interface{}, validatorRe string, ordered bool, showLines bo
|
||||||
|
|
||||||
func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int) (f *fic.FlagLabel, errs []error) {
|
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.Errorf("Label cannot be empty."))
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("Label cannot be empty.")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if flag.Raw != nil {
|
if flag.Raw != nil {
|
||||||
errs = append(errs, fmt.Errorf("raw cannot be defined."))
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("raw cannot be defined.")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(flag.Choice) != 0 {
|
if len(flag.Choice) != 0 {
|
||||||
errs = append(errs, fmt.Errorf("choices cannot be defined."))
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("choices cannot be defined.")))
|
||||||
}
|
}
|
||||||
|
|
||||||
f = &fic.FlagLabel{
|
f = &fic.FlagLabel{
|
||||||
|
@ -115,6 +115,13 @@ func buildLabelFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int) (f
|
||||||
Label: flag.Label,
|
Label: flag.Label,
|
||||||
Variant: flag.Variant,
|
Variant: flag.Variant,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call checks hooks
|
||||||
|
for _, h := range hooks.flagLabelHooks {
|
||||||
|
for _, e := range h(f) {
|
||||||
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,43 +131,35 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(flag.Variant) != 0 {
|
if len(flag.Variant) != 0 {
|
||||||
errs = append(errs, fmt.Errorf("variant is not defined for this kind of flag."))
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, fmt.Errorf("variant is not defined for this kind of flag.")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if flag.Label[0] == '`' {
|
if flag.Label[0] == '`' {
|
||||||
errs = append(errs, fmt.Errorf("Label should not begin with `."))
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, 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') ||
|
|
||||||
(flag.Label[0] == 'W' || flag.Label[0] == 'w') && (flag.Label[1] == 'H' || flag.Label[1] == 'h') {
|
|
||||||
errs = append(errs, fmt.Errorf("Label should not begin with %s. This seem to be a question. Reword your label as a description of the expected flag, `:` are automatically appended.", flag.Label[0:2]))
|
|
||||||
flag.Label = flag.Label[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if flag.Label[len(flag.Label)-1] != ')' && flag.Label[len(flag.Label)-1] != '©' && !unicode.IsLetter(rune(flag.Label[len(flag.Label)-1])) && !unicode.IsDigit(rune(flag.Label[len(flag.Label)-1])) {
|
|
||||||
errs = append(errs, fmt.Errorf("Label should not end with punct (%q). Reword your label as a description of the expected flag, `:` are automatically appended.", flag.Label[len(flag.Label)-1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
errs = append(errs, terrs...)
|
for _, terr := range terrs {
|
||||||
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, terr))
|
||||||
|
}
|
||||||
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.Errorf("WARNING non-printable characters in flag, is this really expected?"))
|
errs = 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.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, err)
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fl := fic.Flag(&fic.FlagKey{
|
fk := &fic.FlagKey{
|
||||||
Type: flag.Type,
|
Type: flag.Type,
|
||||||
IdExercice: exercice.Id,
|
IdExercice: exercice.Id,
|
||||||
Order: int8(flagline),
|
Order: int8(flagline),
|
||||||
|
@ -175,7 +174,16 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
||||||
Checksum: hashedFlag[:],
|
Checksum: hashedFlag[:],
|
||||||
ChoicesCost: flag.ChoicesCost,
|
ChoicesCost: flag.ChoicesCost,
|
||||||
BonusGain: flag.BonusGain,
|
BonusGain: flag.BonusGain,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Call checks hooks
|
||||||
|
for _, h := range hooks.flagKeyHooks {
|
||||||
|
for _, e := range h(fk, raw) {
|
||||||
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fl := fic.Flag(fk)
|
||||||
f = &fl
|
f = &fl
|
||||||
|
|
||||||
if len(flag.Choice) > 0 || (flag.Type == "ucq" || flag.Type == "radio") {
|
if len(flag.Choice) > 0 || (flag.Type == "ucq" || flag.Type == "radio") {
|
||||||
|
@ -191,7 +199,9 @@ 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 {
|
||||||
errs = append(errs, terrs...)
|
for _, terr := range terrs {
|
||||||
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, terr))
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,13 +214,22 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
||||||
val = strings.ToLower(val)
|
val = strings.ToLower(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
choices = append(choices, &fic.FlagChoice{
|
fc := &fic.FlagChoice{
|
||||||
Label: choice.Label,
|
Label: choice.Label,
|
||||||
Value: val,
|
Value: val,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Call checks hooks
|
||||||
|
for _, h := range hooks.flagChoiceHooks {
|
||||||
|
for _, e := range h(fc) {
|
||||||
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
choices = append(choices, fc)
|
||||||
|
|
||||||
if val == "true" || val == "false" {
|
if val == "true" || val == "false" {
|
||||||
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))
|
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)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if val == raw || (!flag.CaseSensitive && val == strings.ToLower(raw)) {
|
if val == raw || (!flag.CaseSensitive && val == strings.ToLower(raw)) {
|
||||||
|
@ -218,7 +237,14 @@ func buildKeyFlag(exercice *fic.Exercice, flag ExerciceFlag, flagline int, defau
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasOne {
|
if !hasOne {
|
||||||
errs = append(errs, fmt.Errorf("no valid answer defined."))
|
errs = 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) {
|
||||||
|
errs = append(errs, NewFlagError(exercice, &flag, flagline, e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -259,17 +285,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.Errorf("%q: flag #%d: min %s.", path.Base(exercice.Path), nline+1, err.Error()))
|
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("min %w", err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = iface2Number(flag.NumberMax, &smax)
|
err = iface2Number(flag.NumberMax, &smax)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, fmt.Errorf("%q: flag #%d: max %s.", path.Base(exercice.Path), nline+1, err.Error()))
|
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("max %w", err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = iface2Number(flag.NumberStep, &sstep)
|
err = iface2Number(flag.NumberStep, &sstep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, fmt.Errorf("%q: flag #%d: step %s.", path.Base(exercice.Path), nline+1, err.Error()))
|
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("step %w", err)))
|
||||||
}
|
}
|
||||||
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":
|
||||||
|
@ -283,23 +309,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.Errorf("%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, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("invalid type of flag: should be 'key', 'number', 'text', 'mcq', 'ucq', 'radio' or 'vector'")))
|
||||||
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.Errorf("%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, 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 {
|
} else if flag.NumberMax != nil {
|
||||||
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))
|
errs = 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 {
|
} else if flag.NumberStep != nil {
|
||||||
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))
|
errs = 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 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.Errorf("%q: flag #%d: unable to parse property help as Markdown: %w", path.Base(exercice.Path), nline+1, err))
|
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("unable to parse property help as Markdown: %w", err)))
|
||||||
} else {
|
} else {
|
||||||
flag.Help = mdhelp[3 : len(mdhelp)-4]
|
flag.Help = mdhelp[3 : len(mdhelp)-4]
|
||||||
}
|
}
|
||||||
|
@ -307,9 +333,7 @@ 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)
|
||||||
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{
|
||||||
Line: nline + 1,
|
Line: nline + 1,
|
||||||
|
@ -318,9 +342,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" {
|
} 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")
|
||||||
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{
|
||||||
Line: nline + 1,
|
Line: nline + 1,
|
||||||
|
@ -340,7 +362,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.Errorf("%q: flag #%d: variant is not defined for this kind of flag.", path.Base(exercice.Path), nline+1))
|
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("variant is not defined for this kind of flag")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !flag.NoShuffle {
|
if !flag.NoShuffle {
|
||||||
|
@ -354,7 +376,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.Errorf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline+1))
|
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,13 +385,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.Errorf("%q: error MCQ #%d: all true items has to be justified in this MCQ.", path.Base(exercice.Path), nline+1))
|
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("all true items has to be justified in this MCQ")))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else if choice.Value == nil {
|
} else if choice.Value == nil {
|
||||||
val = false
|
val = false
|
||||||
} else {
|
} else {
|
||||||
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))
|
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("choice %d: incorrect type for value: %T is not boolean.", cid, choice.Value)))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,6 +415,13 @@ 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) {
|
||||||
|
errs = append(errs, NewFlagError(exercice, &flag, nline+1, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ret = append(ret, importFlag{
|
ret = append(ret, importFlag{
|
||||||
Line: nline + 1,
|
Line: nline + 1,
|
||||||
Flag: &addedFlag,
|
Flag: &addedFlag,
|
||||||
|
@ -418,7 +447,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.Errorf("%q: flag #%d: identifier already used (%d), using a random one.", path.Base(exercice.Path), nline+1, flag.Id))
|
errs = append(errs, NewFlagError(exercice, &flag, nline+1, fmt.Errorf("identifier already used (%d), using a random one.", flag.Id)))
|
||||||
flag.Id = rand.Int63()
|
flag.Id = rand.Int63()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,7 +491,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.Errorf("%q: error flag #%d dependency to flag id=%d: id not defined", path.Base(exercice.Path), flag.Line, nf))
|
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on flag id=%d: id not defined", nf)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,7 +505,7 @@ func CheckExerciceFlags(i Importer, exercice *fic.Exercice, files []string) (rf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
errs = append(errs, fmt.Errorf("%q: error flag #%d dependency to %s: No such file", path.Base(exercice.Path), flag.Line, lf))
|
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("flag depend on %s: No such file", lf)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,12 +549,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.Errorf("%q: error flag #%d: %w", path.Base(exercice.Path), flag.Line, err))
|
errs = append(errs, NewFlagError(exercice, nil, 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.Errorf("%q: error in flag #%d choice #FIXME: %w", path.Base(exercice.Path), flag.Line, err))
|
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("choice #FIXME: %w", err)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -535,18 +564,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.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))
|
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)))
|
||||||
} else if err := addedFlag.AddDepend(rf); err != nil {
|
} else if err := addedFlag.AddDepend(rf); err != nil {
|
||||||
errs = append(errs, fmt.Errorf("%q: error flag #%d dependency to id=%d: %w", path.Base(exercice.Path), flag.Line, nf, err))
|
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to id=%d: %w", 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.Errorf("%q: error flag #%d dependency to %s: %w", path.Base(exercice.Path), flag.Line, lf, err))
|
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
|
||||||
} else if err := rf.AddDepend(addedFlag); err != nil {
|
} else if err := rf.AddDepend(addedFlag); err != nil {
|
||||||
errs = append(errs, fmt.Errorf("%q: error flag #%d dependency to %s: %w", path.Base(exercice.Path), flag.Line, lf, err))
|
errs = append(errs, NewFlagError(exercice, nil, flag.Line, fmt.Errorf("dependency to %s: %w", lf, err)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package sync
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
@ -17,9 +16,6 @@ import (
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogMissingResolution logs the absence of resolution.mp4 instead of returning an error.
|
|
||||||
var LogMissingResolution = false
|
|
||||||
|
|
||||||
func fixnbsp(s string) string {
|
func fixnbsp(s string) string {
|
||||||
return strings.Replace(strings.Replace(strings.Replace(s, " ?", " ?", -1), " !", " !", -1), " :", " :", -1)
|
return strings.Replace(strings.Replace(strings.Replace(s, " ?", " ?", -1), " !", " !", -1), " :", " :", -1)
|
||||||
}
|
}
|
||||||
|
@ -102,7 +98,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.Errorf("unable to parse exercice directory: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to parse exercice directory: %w", err), theme))
|
||||||
return nil, p, eid, edir, errs
|
return nil, p, eid, edir, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +106,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.Errorf("title.txt: Title can't contain new lines"))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("title.txt: Title can't contain new lines"), theme))
|
||||||
} else {
|
} else {
|
||||||
e.Title = myTitle
|
e.Title = myTitle
|
||||||
}
|
}
|
||||||
|
@ -128,20 +124,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.Errorf("overview.txt: %s", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.txt: %s", err), theme))
|
||||||
} 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.Errorf("overview.md: an error occurs during markdown formating of the headline: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating of the headline: %w", err), theme))
|
||||||
} 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.Errorf("overview.md: an error occurs during markdown formating: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("overview.md: an error occurs during markdown formating: %w", err), theme))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,20 +149,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.Errorf("statement.md: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("statement.md: %w", err), theme))
|
||||||
} 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.Errorf("statement.md: an error occurs during markdown formating: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("statement.md: an error occurs during markdown formating: %w", err), theme))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.Errorf("finished.txt: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("finished.txt: %w", err), theme))
|
||||||
} 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.Errorf("finished.txt: an error occurs during markdown formating: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("finished.txt: an error occurs during markdown formating: %w", err), theme))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,19 +171,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.Errorf("challenge.txt: %w", err))
|
errs = append(errs, NewChallengeTxtError(e, 0, err, theme))
|
||||||
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.Errorf("challenge.txt: unknown key %q found, check https://srs.nemunai.re/fic/files/challenge/", k))
|
errs = append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("unknown key %q found, check https://srs.nemunai.re/fic/files/challenge/", k), theme))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Gain == 0 {
|
if p.Gain == 0 {
|
||||||
errs = append(errs, fmt.Errorf("challenge.txt: Undefined gain for challenge"))
|
errs = append(errs, NewChallengeTxtError(e, 0, fmt.Errorf("Undefined gain for challenge"), theme))
|
||||||
} else {
|
} else {
|
||||||
e.Gain = p.Gain
|
e.Gain = p.Gain
|
||||||
}
|
}
|
||||||
|
@ -195,11 +191,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.Errorf("unable to treat dependency to another theme (%q): not implemented.", p.Dependencies[0].Theme))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to treat dependency to another theme (%q): not implemented.", p.Dependencies[0].Theme), 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.Errorf("unable to build dependency map: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to build dependency map: %w", err), theme))
|
||||||
} else {
|
} else {
|
||||||
dmap = &dmap2
|
dmap = &dmap2
|
||||||
}
|
}
|
||||||
|
@ -217,7 +213,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.Errorf("Unable to find required exercice dependancy %d (available at time of processing: %s)", p.Dependencies[0].Id, strings.Join(dmap_keys, ",")))
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,10 +226,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.Errorf("resolution.mp4: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: %w", err), theme))
|
||||||
e.VideoURI = ""
|
e.VideoURI = ""
|
||||||
} else if size == 0 {
|
} else if size == 0 {
|
||||||
errs = append(errs, fmt.Errorf("resolution.mp4: The file is empty!"))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.mp4: The file is empty!"), theme))
|
||||||
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,24 +243,20 @@ 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.Errorf("resolution.md: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
|
||||||
} else if size == 0 {
|
} else if size == 0 {
|
||||||
errs = append(errs, fmt.Errorf("resolution.md: The file is empty!"))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: The file is empty!"), theme))
|
||||||
} else if e.Resolution, err = GetFileContent(i, writeup); err != nil {
|
} else if e.Resolution, err = GetFileContent(i, writeup); err != nil {
|
||||||
errs = append(errs, fmt.Errorf("resolution.md: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: %w", err), theme))
|
||||||
} 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.Errorf("resolution.md: error during markdown processing: %w", err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("resolution.md: error during markdown processing: %w", err), theme))
|
||||||
} else {
|
} else {
|
||||||
resolutionFound = true
|
resolutionFound = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !resolutionFound {
|
if !resolutionFound {
|
||||||
if LogMissingResolution {
|
errs = append(errs, NewExerciceError(e, ErrResolutionNotFound, theme))
|
||||||
log.Printf("%q: no resolution video or text file found in %s", edir, epath)
|
|
||||||
} else {
|
|
||||||
errs = append(errs, fmt.Errorf("no resolution video or text file found in %s", epath))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -273,30 +265,29 @@ 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 []error) {
|
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 p ExerciceParams
|
var p ExerciceParams
|
||||||
var berrors []error
|
var berrors []error
|
||||||
|
|
||||||
e, p, eid, edir, berrors = BuildExercice(i, theme, epath, dmap)
|
e, p, eid, _, berrors = BuildExercice(i, theme, epath, dmap)
|
||||||
for _, e := range berrors {
|
for _, e := range berrors {
|
||||||
errs = append(errs, fmt.Errorf("%q: %w", edir, e))
|
errs = append(errs, 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.Errorf("%q: error on exercice save: %w", edir, err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("error on exercice save: %w", err), theme))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import eercice tags
|
// Import eercice tags
|
||||||
if _, err := e.WipeTags(); err != nil {
|
if _, err := e.WipeTags(); err != nil {
|
||||||
errs = append(errs, fmt.Errorf("%q: Unable to wipe tags: %w", edir, err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to wipe tags: %w", err), theme))
|
||||||
}
|
}
|
||||||
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.Errorf("%q: Unable to add tag: %w", edir, err))
|
errs = append(errs, NewExerciceError(e, fmt.Errorf("unable to add tag: %w", err), theme))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"plugin"
|
||||||
|
|
||||||
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var hooks = &CheckHooks{}
|
||||||
|
|
||||||
|
type CheckFlagChoiceHook func(*fic.FlagChoice) []error
|
||||||
|
type CheckFlagKeyHook func(*fic.FlagKey, string) []error
|
||||||
|
type CheckFlagKeyWithChoicesHook func(*fic.FlagKey, string, []*fic.FlagChoice) []error
|
||||||
|
type CheckFlagLabelHook func(*fic.FlagLabel) []error
|
||||||
|
type CheckFlagMCQHook func(*fic.MCQ, []*fic.MCQ_entry) []error
|
||||||
|
type CheckFileHook func(*fic.EFile) []error
|
||||||
|
type CheckHintHook func(*fic.EHint) []error
|
||||||
|
|
||||||
|
type CheckHooks struct {
|
||||||
|
flagChoiceHooks []CheckFlagChoiceHook
|
||||||
|
flagKeyHooks []CheckFlagKeyHook
|
||||||
|
flagKeyWithChoicesHooks []CheckFlagKeyWithChoicesHook
|
||||||
|
flagLabelHooks []CheckFlagLabelHook
|
||||||
|
flagMCQHooks []CheckFlagMCQHook
|
||||||
|
fileHooks []CheckFileHook
|
||||||
|
hintHooks []CheckHintHook
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CheckHooks) RegisterFlagChoiceHook(f CheckFlagChoiceHook) {
|
||||||
|
h.flagChoiceHooks = append(h.flagChoiceHooks, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CheckHooks) RegisterFlagKeyHook(f CheckFlagKeyHook) {
|
||||||
|
h.flagKeyHooks = append(h.flagKeyHooks, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CheckHooks) RegisterFlagKeyWithChoicesHook(f CheckFlagKeyWithChoicesHook) {
|
||||||
|
h.flagKeyWithChoicesHooks = append(h.flagKeyWithChoicesHooks, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CheckHooks) RegisterFlagLabelHook(f CheckFlagLabelHook) {
|
||||||
|
h.flagLabelHooks = append(h.flagLabelHooks, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CheckHooks) RegisterFlagMCQHook(f CheckFlagMCQHook) {
|
||||||
|
h.flagMCQHooks = append(h.flagMCQHooks, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CheckHooks) RegisterFileHook(f CheckFileHook) {
|
||||||
|
h.fileHooks = append(h.fileHooks, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CheckHooks) RegisterHintHook(f CheckHintHook) {
|
||||||
|
h.hintHooks = append(h.hintHooks, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadChecksPlugin(fname string) error {
|
||||||
|
p, err := plugin.Open(fname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
register, err := p.Lookup("RegisterChecksHooks")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
register.(func(*CheckHooks))(hooks)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckPluginList []string
|
||||||
|
|
||||||
|
func (l *CheckPluginList) String() string {
|
||||||
|
return fmt.Sprintf("%v", *l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CheckPluginList) Set(value string) error {
|
||||||
|
*l = append(*l, value)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -120,7 +120,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []error) {
|
||||||
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.Errorf("unable to get AUTHORS.txt: %w", err))
|
errs = append(errs, NewThemeError(th, fmt.Errorf("unable to get AUTHORS.txt: %w", err)))
|
||||||
return nil, errs
|
return nil, errs
|
||||||
} else {
|
} else {
|
||||||
// Format authors
|
// Format authors
|
||||||
|
@ -137,7 +137,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []error) {
|
||||||
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.Errorf("unable to get theme's overview: %w", err))
|
errs = append(errs, NewThemeError(th, 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")
|
||||||
|
@ -149,12 +149,12 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []error) {
|
||||||
// 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.Errorf("overview.txt: an error occurs during markdown formating: %w", err))
|
errs = append(errs, NewThemeError(th, 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.Errorf("overview.txt: an error occurs during markdown formating of the headline: %w", err))
|
errs = append(errs, NewThemeError(th, 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())
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []error) {
|
||||||
} 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.Errorf("heading.jpg: No such file"))
|
errs = append(errs, NewThemeError(th, fmt.Errorf("heading.jpg: No such file")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.exists(path.Join(tdir, "partner.jpg")) {
|
if i.exists(path.Join(tdir, "partner.jpg")) {
|
||||||
|
@ -176,11 +176,11 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, errs []error) {
|
||||||
|
|
||||||
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.Errorf("unable to get partner's text: %w", err))
|
errs = append(errs, NewThemeError(th, 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.Errorf("partner.txt: an error occurs during markdown formating: %w", err))
|
errs = append(errs, NewThemeError(th, fmt.Errorf("partner.txt: an error occurs during markdown formating: %w", err)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,7 +199,7 @@ func SyncThemes(i Importer) (errs []error) {
|
||||||
for _, tdir := range themes {
|
for _, tdir := range themes {
|
||||||
btheme, berrs := BuildTheme(i, tdir)
|
btheme, berrs := BuildTheme(i, tdir)
|
||||||
for _, e := range berrs {
|
for _, e := range berrs {
|
||||||
errs = append(errs, fmt.Errorf("%q: %w", tdir, e))
|
errs = append(errs, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
if btheme == nil {
|
if btheme == nil {
|
||||||
|
@ -216,7 +216,7 @@ func SyncThemes(i Importer) (errs []error) {
|
||||||
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.Errorf("%q: unable to import heading image: %w", tdir, err))
|
errs = append(errs, NewThemeError(btheme, fmt.Errorf("unable to import heading image: %w", err)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,14 +226,14 @@ func SyncThemes(i Importer) (errs []error) {
|
||||||
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.Errorf("%q: unable to import partner image: %w", tdir, err))
|
errs = append(errs, NewThemeError(btheme, fmt.Errorf("unable to import partner image: %w", 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.Errorf("%q: an error occurs during add: %w", tdir, err))
|
errs = append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during add: %w", err)))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,7 +241,7 @@ func SyncThemes(i Importer) (errs []error) {
|
||||||
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.Errorf("%q: an error occurs during update: %w", tdir, err))
|
errs = append(errs, NewThemeError(btheme, fmt.Errorf("an error occurs during update: %w", err)))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,20 @@ type EFile struct {
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDummyFile creates an EFile, without any link to an actual Exercice File.
|
||||||
|
// It is used to check the file validity
|
||||||
|
func (e *Exercice) NewDummyFile(origin string, dest string, checksum []byte, size int64) *EFile {
|
||||||
|
return &EFile{
|
||||||
|
Id: 0,
|
||||||
|
origin: origin,
|
||||||
|
Path: dest,
|
||||||
|
IdExercice: e.Id,
|
||||||
|
Name: path.Base(origin),
|
||||||
|
Checksum: checksum,
|
||||||
|
Size: size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetFiles returns a list of all files living in the database.
|
// GetFiles returns a list of all files living in the database.
|
||||||
func GetFiles() ([]*EFile, error) {
|
func GetFiles() ([]*EFile, error) {
|
||||||
if rows, err := DBQuery("SELECT id_file, id_exercice, origin, path, name, cksum, size FROM exercice_files"); err != nil {
|
if rows, err := DBQuery("SELECT id_file, id_exercice, origin, path, name, cksum, size FROM exercice_files"); err != nil {
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EPITACheckFile(file *fic.EFile) (errs []error) {
|
||||||
|
// Enforce file format
|
||||||
|
if path.Ext(file.Name) == "rar" || path.Ext(file.Name) == "7z" {
|
||||||
|
errs = append(errs, fmt.Errorf("this file use a forbidden archive type."))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EPITACheckKeyFlag(flag *fic.FlagKey, raw string) (errs []error) {
|
||||||
|
if (flag.Label[0] == 'Q' || flag.Label[0] == 'q') && (flag.Label[1] == 'U' || flag.Label[1] == 'u') ||
|
||||||
|
(flag.Label[0] == 'W' || flag.Label[0] == 'w') && (flag.Label[1] == 'H' || flag.Label[1] == 'h') {
|
||||||
|
errs = append(errs, fmt.Errorf("Label should not begin with %s. This seem to be a question. Reword your label as a description of the expected flag, `:` are automatically appended.", flag.Label[0:2]))
|
||||||
|
flag.Label = flag.Label[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if flag.Label[len(flag.Label)-1] != ')' && flag.Label[len(flag.Label)-1] != '©' && !unicode.IsLetter(rune(flag.Label[len(flag.Label)-1])) && !unicode.IsDigit(rune(flag.Label[len(flag.Label)-1])) {
|
||||||
|
errs = append(errs, fmt.Errorf("Label should not end with punct (%q). Reword your label as a description of the expected flag, `:` are automatically appended.", flag.Label[len(flag.Label)-1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"srs.epita.fr/fic-server/admin/sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterChecksHooks(h *sync.CheckHooks) {
|
||||||
|
h.RegisterFlagKeyHook(EPITACheckKeyFlag)
|
||||||
|
h.RegisterFileHook(EPITACheckFile)
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -22,6 +23,7 @@ var (
|
||||||
ignoreBinaryFileUnder = 1500000
|
ignoreBinaryFileUnder = 1500000
|
||||||
skipFileChecks = false
|
skipFileChecks = false
|
||||||
skipBinaryFileCheck = false
|
skipBinaryFileCheck = false
|
||||||
|
logMissingResolution = false
|
||||||
)
|
)
|
||||||
|
|
||||||
func formatFileSize(size int) string {
|
func formatFileSize(size int) string {
|
||||||
|
@ -145,6 +147,7 @@ func main() {
|
||||||
cloudUsername := "fic"
|
cloudUsername := "fic"
|
||||||
cloudPassword := ""
|
cloudPassword := ""
|
||||||
localImporterDirectory := ""
|
localImporterDirectory := ""
|
||||||
|
checkplugins := sync.CheckPluginList{}
|
||||||
|
|
||||||
// Read paremeters from environment
|
// Read paremeters from environment
|
||||||
if v, exists := os.LookupEnv("FICCLOUD_URL"); exists {
|
if v, exists := os.LookupEnv("FICCLOUD_URL"); exists {
|
||||||
|
@ -167,9 +170,10 @@ func main() {
|
||||||
flag.BoolVar(&fic.OptionalDigest, "optionaldigest", fic.OptionalDigest, "Is the digest required when importing files?")
|
flag.BoolVar(&fic.OptionalDigest, "optionaldigest", fic.OptionalDigest, "Is the digest required when importing files?")
|
||||||
flag.BoolVar(&fic.StrongDigest, "strongdigest", fic.StrongDigest, "Are BLAKE2b digests required or is SHA-1 good enough?")
|
flag.BoolVar(&fic.StrongDigest, "strongdigest", fic.StrongDigest, "Are BLAKE2b digests required or is SHA-1 good enough?")
|
||||||
flag.BoolVar(&skipFileChecks, "skipfiledigests", skipFileChecks, "Don't perform DIGESTS checks on file to speed up the checks")
|
flag.BoolVar(&skipFileChecks, "skipfiledigests", skipFileChecks, "Don't perform DIGESTS checks on file to speed up the checks")
|
||||||
flag.BoolVar(&sync.LogMissingResolution, "skipresolution", sync.LogMissingResolution, "Don't fail if resolution.mp4 is absent")
|
flag.BoolVar(&logMissingResolution, "skipresolution", logMissingResolution, "Don't fail if resolution.mp4 is absent")
|
||||||
flag.BoolVar(&skipBinaryFileCheck, "skip-binary-file", skipBinaryFileCheck, "In Git-LFS check, don't warn files")
|
flag.BoolVar(&skipBinaryFileCheck, "skip-binary-file", skipBinaryFileCheck, "In Git-LFS check, don't warn files")
|
||||||
flag.IntVar(&ignoreBinaryFileUnder, "skip-binary-files-under", ignoreBinaryFileUnder, "In Git-LFS check, don't warn files under this size")
|
flag.IntVar(&ignoreBinaryFileUnder, "skip-binary-files-under", ignoreBinaryFileUnder, "In Git-LFS check, don't warn files under this size")
|
||||||
|
flag.Var(&checkplugins, "rules-plugins", "List of libraries containing others rules to checks")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Do not display timestamp
|
// Do not display timestamp
|
||||||
|
@ -211,6 +215,15 @@ func main() {
|
||||||
log.Fatal("No importer nor path given!")
|
log.Fatal("No importer nor path given!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load rules plugins
|
||||||
|
for _, p := range checkplugins {
|
||||||
|
if err := sync.LoadChecksPlugin(p); err != nil {
|
||||||
|
log.Fatalf("Unable to load rule plugin %q: %s", p, err.Error())
|
||||||
|
} else {
|
||||||
|
log.Printf("Rules plugin %q successfully loaded", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Variable that handles the exit status
|
// Variable that handles the exit status
|
||||||
hasError := false
|
hasError := false
|
||||||
|
|
||||||
|
@ -248,8 +261,17 @@ 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
|
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
|
|
||||||
|
if logMissingResolution {
|
||||||
|
if e, ok := err.(*sync.ExerciceError); ok {
|
||||||
|
if errors.Is(e.GetError(), sync.ErrResolutionNotFound) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nberr += 1
|
||||||
}
|
}
|
||||||
log.Printf("================================== Exercice %q treated\n", edir)
|
log.Printf("================================== Exercice %q treated\n", edir)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue