diff --git a/.gitignore b/.gitignore index 6e894bf..0a1093a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,3 @@ # Go workspace file go.work /bin -/scoring.tex diff --git a/README.md b/README.md index b846ad3..2f8a22d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ | [ripper](#ripper) | change runs status | 🦍 | | ✅ | | [scalp](#scalp) | incremental scoring | | 🦍 | ✅ | | [valeria](#valeria) | valuer.cfg + tex scoring | | 🦍 | ✅ | -| [wooda](#wooda) | regexp problem upload | | 🦍 | 🧑‍💻 | +| [wooda](#wooda) | glob problem files upload | | 🦍 | 🧑‍💻 | | ⚙️ | move json config to ini | | | 🧑‍💻 | | 👻 | list/commit problems | | 🦍 | 🤔 | | 👻 | download/upload package | | 🦍 | 🤔 | @@ -50,13 +50,7 @@ Put your config file in `~/.config/algolymp/config.json`. "polygon": { "url": "https://polygon.codeforces.com", "apiKey": "", - "apiSecret": "", - "wooda": { - "polygon": { - "ignore": ".*\\.a$", - "test": "^tests/\\d+$" - } - } + "apiSecret": "" }, "system": { "editor": "nano" @@ -279,35 +273,37 @@ valeria -i 318882 | bat -l tex ![valeria logo](https://algolymp.ru/static/img/valeria.png) ## wooda -*Upload problem files filtered by regexp to Polygon using API.* +*Upload problem files filtered by glob to Polygon using API.* ### About **Now this is a proof of concept. Many more modes will be supported in the future.** -Match all files in directory with config regexp patterns. Upload recognized files to Polygon. +Match all files in directory with glob pattern. Upload recognized files to Polygon. Supported modes: -- `ignore` - `test` +- `tags` ### Flags - `-i` - problem id (required) -- `-m` - mode from config (required) -- `-d` - problem directory (required) +- `-m` - uploading mode (required) +- `-g` - problem files glob (required) + +You should know your shell and probably pass `-g ""`, not `-g ` ### Config - `polygon.url` - `polygon.apiKey` - `polygon.apiSecret` -- `wooda` ### Examples ```bash wooda --help -wooda -i 337320 -m polygon -d . +wooda -i 337320 -m test -g "tests/*[^.a]" +wooda -i 337320 -m tags -g tags ``` ![wooda logo](https://algolymp.ru/static/img/wooda.png) diff --git a/cmd/wooda/main.go b/cmd/wooda/main.go index c5301cd..960b858 100644 --- a/cmd/wooda/main.go +++ b/cmd/wooda/main.go @@ -12,33 +12,55 @@ import ( ) func main() { - cfg := config.NewConfig() - woodaKeys := make([]string, 0, len(cfg.Polygon.Wooda)) - for k := range cfg.Polygon.Wooda { - woodaKeys = append(woodaKeys, k) + woodaModes := []string{ + wooda.ModeTest, + wooda.ModeTags, } - parser := argparse.NewParser("wooda", "Upload problem files filtered by regexp to Polygon.") + parser := argparse.NewParser("wooda", "Upload problem files filtered by glob to Polygon.") pID := parser.Int("i", "pid", &argparse.Options{ Required: true, Help: "Polygon problem ID", }) - mode := parser.Selector("m", "mode", woodaKeys, &argparse.Options{ + mode := parser.Selector("m", "mode", woodaModes, &argparse.Options{ Required: true, - Help: "Problem mode (from config)", + Help: "Uploading mode", }) - pDir := parser.String("d", "directory", &argparse.Options{ + glob := parser.String("g", "glob", &argparse.Options{ Required: true, - Help: "Problem directory", + Help: "Problem files glob", }) if err := parser.Parse(os.Args); err != nil { logrus.WithError(err).Fatal("bad arguments") } + cfg := config.NewConfig() pClient := polygon.NewPolygon(&cfg.Polygon) - woodaCfg := cfg.Polygon.Wooda[*mode] // mode is good argparse.Selector - wooda := wooda.NewWooda(pClient, *pID, &woodaCfg) - if err := filepath.Walk(*pDir, wooda.DirWalker); err != nil { - logrus.WithError(err).Fatal("failed wooda matching") + wooda := wooda.NewWooda(pClient, *pID, *mode) + + files, err := filepath.Glob(*glob) + if err != nil { + logrus.WithError(err).Fatal("failed to match glob") + } + if len(files) == 0 { + logrus.WithField("glob", *glob).Warn("no files matched glob") + + return + } + logrus.WithFields(logrus.Fields{"glob": *glob, "count": len(files)}). + Info("glob match result") + + errCount := 0 + for _, path := range files { + if err := wooda.Resolve(path); err != nil { + errCount++ + logrus.WithError(err).WithField("path", path).Error("failed to resolve") + } + } + + if errCount == 0 { + logrus.Info("success resolve all files") + } else { + logrus.WithField("count", errCount).Warn("some errors happened") } } diff --git a/polygon/api.go b/polygon/api.go index ded99f5..904aa3d 100644 --- a/polygon/api.go +++ b/polygon/api.go @@ -29,15 +29,9 @@ var ( ) type Config struct { - URL string `json:"url"` - APIKey string `json:"apiKey"` - APISecret string `json:"apiSecret"` - Wooda map[string]WoodaConfig `json:"wooda"` -} - -type WoodaConfig struct { - Ignore string `json:"ignore"` - Test string `json:"test"` + URL string `json:"url"` + APIKey string `json:"apiKey"` + APISecret string `json:"apiSecret"` } type Polygon struct { @@ -200,3 +194,13 @@ func (p *Polygon) SaveTest(tReq TestRequest) error { return err } + +func (p *Polygon) SaveTags(pID int, tags string) error { + link, params := p.buildURL("problem.saveTags", url.Values{ + "problemId": []string{strconv.Itoa(pID)}, + "tags": []string{tags}, + }) + _, err := p.makeQuery(http.MethodPost, link, params) + + return err +} diff --git a/polygon/wooda/wooda.go b/polygon/wooda/wooda.go index 3ef1cdd..af9ec77 100644 --- a/polygon/wooda/wooda.go +++ b/polygon/wooda/wooda.go @@ -1,59 +1,58 @@ package wooda import ( + "errors" "fmt" - "io/fs" "os" "path/filepath" - "regexp" + "strings" "github.com/Gornak40/algolymp/polygon" "github.com/sirupsen/logrus" ) +const ( + ModeTest = "test" + ModeTags = "tags" +) + +var ( + ErrUnknownMode = errors.New("unknown wooda mode") +) + type Wooda struct { client *polygon.Polygon pID int - config *polygon.WoodaConfig + mode string testIndex int } -func NewWooda(pClient *polygon.Polygon, pID int, wCfg *polygon.WoodaConfig) *Wooda { +func NewWooda(pClient *polygon.Polygon, pID int, mode string) *Wooda { return &Wooda{ client: pClient, pID: pID, - config: wCfg, + mode: mode, testIndex: 1, } } -func pathMatch(pattern, path string) bool { - res, err := regexp.MatchString(pattern, path) - if err != nil { - logrus.WithError(err).Error("failed match filepath") - - return false - } - - return res -} - -func getData(mode, path string) (string, error) { - logrus.WithFields(logrus.Fields{"mode": mode, "path": path}).Info("resolve file") +func (w *Wooda) Resolve(path string) error { + logrus.WithFields(logrus.Fields{"mode": w.mode, "path": path}).Info("resolve file") data, err := os.ReadFile(path) - if err != nil { - return "", err - } - - return string(data), nil -} - -func (w *Wooda) resolveTest(path string) error { - data, err := getData("test", path) if err != nil { return err } + switch w.mode { + case ModeTest: + return w.resolveTest(path, string(data)) + case ModeTags: + return w.resolveTags(string(data)) + default: + return fmt.Errorf("%w: %s", ErrUnknownMode, w.mode) + } +} +func (w *Wooda) resolveTest(path, data string) error { tr := polygon.NewTestRequest(w.pID, w.testIndex). Input(data). Description(fmt.Sprintf("File \"%s\"", filepath.Base(path))) @@ -65,28 +64,8 @@ func (w *Wooda) resolveTest(path string) error { return nil } -func (w *Wooda) matcher(path string) error { - switch { - case pathMatch(w.config.Ignore, path): // silent ignore is the best practice - break - case pathMatch(w.config.Test, path): - if err := w.resolveTest(path); err != nil { - return err - } - default: - logrus.WithField("path", path).Warn("no valid matching") - } - - return nil -} - -func (w *Wooda) DirWalker(path string, info fs.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } +func (w *Wooda) resolveTags(data string) error { + tags := strings.Join(strings.Split(data, "\n"), ",") - return w.matcher(path) + return w.client.SaveTags(w.pID, tags) }