From 033328cc7e47fdfb57f3561e9782e6d437922cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 27 Nov 2019 20:39:36 +0200 Subject: [PATCH 01/16] Add isUrl util function --- util/util.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/util/util.go b/util/util.go index 5ae2330e..d50aa545 100644 --- a/util/util.go +++ b/util/util.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "math/rand" + "net/url" "os" "strconv" "strings" @@ -87,3 +88,11 @@ func DebugSave(data interface{}) { log.Fatal(err) } } + +// Returns true if a given string is an url +func IsUrl(str string) bool { + if _, err := url.ParseRequestURI(str); err == nil { + return true + } + return false +} From 065b334b83b2ba8dc0c8800e70f50a6cce72738d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 27 Nov 2019 20:45:34 +0200 Subject: [PATCH 02/16] Add parseUrl cmd function and general args parsing instead of contestID/problemID --- cmd/cmd.go | 111 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 89 insertions(+), 22 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index 3033d527..189d756a 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -50,39 +50,106 @@ func Eval(args map[string]interface{}) error { return nil } -func getContestID(args map[string]interface{}) (string, error) { - if c, ok := args[""].(string); ok { - if _, err := strconv.Atoi(c); err == nil { - return c, nil - } - return "", fmt.Errorf(`Contest should be a number instead of "%v"`, c) - } +func parseArgs(args map[string]interface{}, required map[string]bool) (map[string]string, error) { + result := make(map[string]string) + contestID, problemID, lastDir := "", "", "" path, err := os.Getwd() if err != nil { - return "", err + return nil, err } + result["contestRootPath"] = path for { c := filepath.Base(path) if _, err := strconv.Atoi(c); err == nil { - return c, nil + contestID, problemID = c, strings.ToLower(lastDir) + if _, ok := args[""].(string); !ok { + result["contestRootPath"] = filepath.Dir(path) + } + break } if filepath.Dir(path) == path { break } - path = filepath.Dir(path) + path, lastDir = filepath.Dir(path), c + } + if p, ok := args[""].(string); ok { + problemID = strings.ToLower(p) + } + if c, ok := args[""].(string); ok { + if util.IsUrl(c) { + parsed, err := parseUrl(c) + if err != nil { + return nil, err + } + if value, ok := parsed["contestID"]; ok { + contestID = value + } + if value, ok := parsed["problemID"]; ok { + problemID = strings.ToLower(value) + } + } else if _, err := strconv.Atoi(c); err == nil { + contestID = c + } + } + if req, ok := required[""]; ok { + result[""] = contestID + if contestID == "" && req { + return nil, errors.New("Unable to find ") + } } - return "", errors.New("Cannot find any valid contest id") + if req, ok := required[""]; ok { + result[""] = problemID + if problemID == "" && req { + return nil, errors.New("Unable to find ") + } + } + for key, req := range required { + if _, ok := result[key]; ok { + continue + } + value, ok := args[key].(string) + if req && !ok { + return nil, errors.New("Unable to find " + key) + } + result[key] = value + } + return result, nil } -func getProblemID(args map[string]interface{}) (string, error) { - if p, ok := args[""].(string); ok { - return strings.ToLower(p), nil +func parseUrl(url string) (map[string]string, error) { + reg := regexp.MustCompile(`(https?:\/\/)?(www\.)?([a-zA-Z\d\-\.]+)\/(?Pproblemset|gym|contest|group)`) + url_type := "" + for i, val := range reg.FindStringSubmatch(url) { + if reg.SubexpNames()[i] == "type" { + url_type = val + break + } } - path, err := os.Getwd() - if err != nil { - return "", err + + reg_str := "" + switch url_type { + case "contest": + reg_str = `(https?:\/\/)?(www\.)?([a-zA-Z\d\-\.]+)\/contest\/(?P\d+)(\/problem\/(?P[\w\d]+))?` + case "gym": + reg_str = `(https?:\/\/)?(www\.)?([a-zA-Z\d\-\.]+)\/gym\/(?P\d+)(\/problem\/(?P[\w\d]+))?` + case "problemset": + reg_str = `(https?:\/\/)?(www\.)?([a-zA-Z\d\-\.]+)\/problemset\/problem\/(?P\d+)\/(?P[\w\d]+)?` + case "group": + return nil, errors.New("Groups are not supported") + default: + return nil, errors.New("Invalid url") + } + + output := make(map[string]string) + reg = regexp.MustCompile(reg_str) + names := reg.SubexpNames() + for i, val := range reg.FindStringSubmatch(url) { + if names[i] != "" && val != "" { + output[names[i]] = val + } } - return strings.ToLower(filepath.Base(path)), nil + output["type"] = url_type + return output, nil } func getSampleID() (samples []string) { @@ -115,7 +182,7 @@ type CodeList struct { Index []int } -func getCode(args map[string]interface{}, templates []config.CodeTemplate) (codes []CodeList) { +func getCode(filename string, templates []config.CodeTemplate) (codes []CodeList) { mp := make(map[string][]int) for i, temp := range templates { suffixMap := map[string]bool{} @@ -128,7 +195,7 @@ func getCode(args map[string]interface{}, templates []config.CodeTemplate) (code } } - if filename, ok := args[""].(string); ok { + if filename != "" { ext := filepath.Ext(filename) if idx, ok := mp[ext]; ok { return []CodeList{CodeList{filename, idx}} @@ -156,8 +223,8 @@ func getCode(args map[string]interface{}, templates []config.CodeTemplate) (code return codes } -func getOneCode(args map[string]interface{}, templates []config.CodeTemplate) (name string, index int, err error) { - codes := getCode(args, templates) +func getOneCode(filename string, templates []config.CodeTemplate) (name string, index int, err error) { + codes := getCode(filename, templates) if len(codes) < 1 { return "", 0, errors.New("Cannot find any code.\nMaybe you should add a new template by `cf config`") } From 540c4e263f7ab676b3c0cd20c30cf1b8c9ee6b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 27 Nov 2019 20:46:14 +0200 Subject: [PATCH 03/16] Update usage to include urls --- cf.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cf.go b/cf.go index 6047dacd..aabfec5d 100644 --- a/cf.go +++ b/cf.go @@ -28,17 +28,17 @@ If you want to compete, the best command is "cf race 1111" where "1111" is the c Usage: cf config - cf submit [( )] [] - cf list [] - cf parse [] [] + cf submit [] [] [] + cf list [] + cf parse [] [] cf gen [] cf test [] - cf watch [all] [] [] - cf open [] [] - cf stand [] - cf sid [] [] - cf race - cf pull [ac] [] [] + cf watch [all] [] [] + cf open [] [] + cf stand [] + cf sid [] [] + cf race [] + cf pull [ac] [] [] cf clone [ac] cf upgrade From 270266013a42af89713fe4241e9c90b6a0e85553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 27 Nov 2019 20:48:55 +0200 Subject: [PATCH 04/16] Update Open, Sid and Stand commands to support urls --- cmd/browser.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/cmd/browser.go b/cmd/browser.go index 6c0c6c70..0caba738 100644 --- a/cmd/browser.go +++ b/cmd/browser.go @@ -11,15 +11,12 @@ import ( // Open command func Open(args map[string]interface{}) error { - contestID, err := getContestID(args) + parsedArgs, err := parseArgs(args, map[string]bool{"": true, "": false}) if err != nil { return err } - problemID, err := getProblemID(args) - if err != nil { - return err - } - if problemID == contestID { + contestID, problemID := parsedArgs[""], parsedArgs[""] + if problemID == "" { return open.Run(client.ToGym(fmt.Sprintf(config.Instance.Host+"/contest/%v", contestID), contestID)) } return open.Run(client.ToGym(fmt.Sprintf(config.Instance.Host+"/contest/%v/problem/%v", contestID, problemID), contestID)) @@ -27,20 +24,21 @@ func Open(args map[string]interface{}) error { // Stand command func Stand(args map[string]interface{}) error { - contestID, err := getContestID(args) + parsedArgs, err := parseArgs(args, map[string]bool{"": true}) if err != nil { return err } + contestID := parsedArgs[""] return open.Run(client.ToGym(fmt.Sprintf(config.Instance.Host+"/contest/%v/standings", contestID), contestID)) } // Sid command func Sid(args map[string]interface{}) error { - contestID := "" - submissionID := "" + parsedArgs, err := parseArgs(args, map[string]bool{"": false, "": false}) + contestID, submissionID := parsedArgs[""], parsedArgs[""] cfg := config.Instance cln := client.Instance - if args[""] == nil { + if submissionID == "" { if cln.LastSubmission != nil { contestID = cln.LastSubmission.ContestID submissionID = cln.LastSubmission.SubmissionID @@ -48,12 +46,9 @@ func Sid(args map[string]interface{}) error { return fmt.Errorf(`You have not submitted any problem yet`) } } else { - var err error - contestID, err = getContestID(args) if err != nil { return err } - submissionID, _ = args[""].(string) if _, err = strconv.Atoi(submissionID); err != nil { return err } From dececdebc1d85c1e01f5aa53dbc62804b2ba6742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 27 Nov 2019 20:49:26 +0200 Subject: [PATCH 05/16] Update List command to support urls --- cmd/list.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/list.go b/cmd/list.go index c92763c3..7440f998 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -15,10 +15,11 @@ import ( // List command func List(args map[string]interface{}) error { - contestID, err := getContestID(args) + parsedArgs, err := parseArgs(args, map[string]bool{"": true}) if err != nil { return err } + contestID := parsedArgs[""] cfg := config.Instance cln := client.Instance problems, err := cln.StatisContest(contestID) From 11885e38c136be75dca04252a41f6580fbb1fa83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 27 Nov 2019 20:49:53 +0200 Subject: [PATCH 06/16] Update Pull command to support urls --- cmd/pull.go | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/cmd/pull.go b/cmd/pull.go index 3883b79d..39cf9f4a 100644 --- a/cmd/pull.go +++ b/cmd/pull.go @@ -1,9 +1,7 @@ package cmd import ( - "os" "path/filepath" - "strings" "github.com/xalanq/cf-tool/client" "github.com/xalanq/cf-tool/config" @@ -11,37 +9,17 @@ import ( // Pull command func Pull(args map[string]interface{}) error { - currentPath, err := os.Getwd() - if err != nil { - return err - } cfg := config.Instance cln := client.Instance ac := args["ac"].(bool) + var err error work := func() error { - contestID := "" - problemID := "" - path := currentPath - var ok bool - if contestID, ok = args[""].(string); ok { - if problemID, ok = args[""].(string); !ok { - return cln.PullContest(contestID, "", filepath.Join(currentPath, contestID), ac) - } - problemID = strings.ToLower(problemID) - path = filepath.Join(currentPath, contestID, problemID) - } else { - contestID, err = getContestID(args) - if err != nil { - return err - } - problemID, err = getProblemID(args) - if err != nil { - return err - } - if problemID == contestID { - return cln.PullContest(contestID, "", currentPath, ac) - } + parsedArgs, err := parseArgs(args, map[string]bool{"": true, "": false}) + if err != nil { + return err } + contestID, problemID := parsedArgs[""], parsedArgs[""] + path := filepath.Join(parsedArgs["contestRootPath"], contestID, problemID) return cln.PullContest(contestID, problemID, path, ac) } if err = work(); err != nil { From 624f908a2462c8c0e56732ef02556459a474aa52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 27 Nov 2019 20:50:07 +0200 Subject: [PATCH 07/16] Update Race command to support urls --- cmd/race.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/race.go b/cmd/race.go index d2787341..1ee85069 100644 --- a/cmd/race.go +++ b/cmd/race.go @@ -11,10 +11,11 @@ import ( // Race command func Race(args map[string]interface{}) error { - contestID, err := getContestID(args) + parsedArgs, err := parseArgs(args, map[string]bool{"": true}) if err != nil { return err } + contestID := parsedArgs[""] cfg := config.Instance cln := client.Instance if err = cln.RaceContest(contestID); err != nil { @@ -28,5 +29,5 @@ func Race(args map[string]interface{}) error { time.Sleep(1) open.Run(client.ToGym(fmt.Sprintf(cfg.Host+"/contest/%v", contestID), contestID)) open.Run(client.ToGym(fmt.Sprintf(cfg.Host+"/contest/%v/problems", contestID), contestID)) - return Parse(args) + return _Parse(contestID, "", parsedArgs["contestRootPath"]) } From 94fe3f95f4c70d32f53bcbea7bc94b915af22e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 27 Nov 2019 20:50:34 +0200 Subject: [PATCH 08/16] Update Test command to support urls --- cmd/test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/test.go b/cmd/test.go index e884c929..147cc3db 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -160,8 +160,8 @@ func Test(args map[string]interface{}) error { color.Red("There is no sample data") return nil } - - filename, index, err := getOneCode(args, cfg.Template) + parsedArgs, _ := parseArgs(args, map[string]bool{"": false}) + filename, index, err := getOneCode(parsedArgs[""], cfg.Template) if err != nil { return err } From 48ce46640db0edce87ebcb4bb495f03cab0bb111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 27 Nov 2019 20:50:50 +0200 Subject: [PATCH 09/16] Update Watch command to support urls --- cmd/watch.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cmd/watch.go b/cmd/watch.go index 30e37126..fd88eac1 100644 --- a/cmd/watch.go +++ b/cmd/watch.go @@ -1,20 +1,17 @@ package cmd import ( - "strings" - "github.com/xalanq/cf-tool/client" "github.com/xalanq/cf-tool/config" ) // Watch command func Watch(args map[string]interface{}) error { - contestID, err := getContestID(args) + parsedArgs, err := parseArgs(args, map[string]bool{"": true, "": false}) if err != nil { return err } - problemID, _ := args[""].(string) - problemID = strings.ToLower(problemID) + contestID, problemID := parsedArgs[""], parsedArgs[""] cfg := config.Instance cln := client.Instance n := 10 From 7ca13f479bfbccb8f97c028a60094d3210e0b7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 27 Nov 2019 20:51:40 +0200 Subject: [PATCH 10/16] Update Parse command and add wrapper to support urls --- cmd/parse.go | 43 ++++++++++++++----------------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/cmd/parse.go b/cmd/parse.go index 6c40500c..6d01319e 100644 --- a/cmd/parse.go +++ b/cmd/parse.go @@ -2,7 +2,6 @@ package cmd import ( "errors" - "os" "path/filepath" "strings" @@ -12,16 +11,12 @@ import ( "github.com/xalanq/cf-tool/config" ) -// Parse command -func Parse(args map[string]interface{}) error { - currentPath, err := os.Getwd() - if err != nil { - return err - } +func _Parse(contestID string, problemID string, contestRootPath string) error { cfg := config.Instance cln := client.Instance source := "" ext := "" + var err error if cfg.GenAfterParse { if len(cfg.Template) == 0 { return errors.New("You have to add at least one code template by `cf config`") @@ -44,29 +39,10 @@ func Parse(args map[string]interface{}) error { return err } work := func() error { - contestID := "" - problemID := "" - path := currentPath - var ok bool - if contestID, ok = args[""].(string); ok { - if problemID, ok = args[""].(string); !ok { - return parseContest(contestID, filepath.Join(currentPath, contestID)) - } - problemID = strings.ToLower(problemID) - path = filepath.Join(currentPath, contestID, problemID) - } else { - contestID, err = getContestID(args) - if err != nil { - return err - } - problemID, err = getProblemID(args) - if err != nil { - return err - } - if problemID == contestID { - return parseContest(contestID, currentPath) - } + if problemID == "" { + return parseContest(contestID, filepath.Join(contestRootPath, contestID)) } + path := filepath.Join(contestRootPath, contestID, problemID) samples, standardIO, err := cln.ParseContestProblem(contestID, problemID, path) if err != nil { color.Red("Failed %v %v", contestID, problemID) @@ -90,3 +66,12 @@ func Parse(args map[string]interface{}) error { } return err } + +// Parse command +func Parse(args map[string]interface{}) error { + parsedArgs, err := parseArgs(args, map[string]bool{"": true, "": false}) + if err != nil { + return err + } + return _Parse(parsedArgs[""], parsedArgs[""], parsedArgs["contestRootPath"]) +} From 7de9b9afd123aca0c8df2833b4e6e7d90f170e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 27 Nov 2019 20:52:16 +0200 Subject: [PATCH 11/16] Update Submit command with more complex args parsing to support urls --- cmd/submit.go | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/cmd/submit.go b/cmd/submit.go index 567abd1a..b82fe207 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -1,30 +1,23 @@ package cmd import ( - "fmt" "io/ioutil" "os" "strings" "github.com/xalanq/cf-tool/client" "github.com/xalanq/cf-tool/config" + "github.com/xalanq/cf-tool/util" ) // Submit command func Submit(args map[string]interface{}) error { - contestID, err := getContestID(args) + contestID, problemID, userFile, err := parseSubmitArgs(args) if err != nil { return err } - problemID, err := getProblemID(args) - if err != nil { - return err - } - if problemID == contestID { - return fmt.Errorf("contestID: %v, problemID: %v is not valid", contestID, problemID) - } cfg := config.Instance - filename, index, err := getOneCode(args, cfg.Template) + filename, index, err := getOneCode(userFile, cfg.Template) if err != nil { return err } @@ -54,3 +47,37 @@ func Submit(args map[string]interface{}) error { return err } + +func parseSubmitArgs(args map[string]interface{}) (string, string, string, error) { + isFilename := func(str string) bool { + if str == "" || util.IsUrl(str) { + return false + } + if _, ok := os.Stat(str); strings.Contains(str, ".") || ok == nil { + return true + } + return false + } + var newArgs = make(map[string]interface{}) + for key, value := range args { + newArgs[key] = value + } + if _, ok := args[""].(string); !ok { + if p, ok := args[""].(string); ok { + if isFilename(p) { + newArgs[""] = p + newArgs[""] = nil + } + } else if c, ok := args[""].(string); ok { + if isFilename(c) { + newArgs[""] = c + newArgs[""] = nil + } + } + } + parsedArgs, err := parseArgs(newArgs, map[string]bool{"": true, "": true, "": false}) + if err != nil { + return "", "", "", err + } + return parsedArgs[""], parsedArgs[""], parsedArgs[""], nil +} From 33a5bd591a28abd3de6e30dcebca6cbd491f0ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Sat, 30 Nov 2019 17:05:36 +0200 Subject: [PATCH 12/16] Add more info about url usage --- cf.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cf.go b/cf.go index aabfec5d..9b0a0422 100644 --- a/cf.go +++ b/cf.go @@ -47,7 +47,8 @@ Examples: cf submit If current path is "/", cf will find the code which can be submitted. Then submit to . cf submit a.cpp - cf submit 100 a + cf submit https://codeforces.com/contest/100 a + cf submit https://codeforces.com/problemset/problem/100/A a.cpp cf submit 100 a a.cpp cf list List all problems' stats of a contest. cf list 1119 @@ -77,8 +78,8 @@ Examples: Notes: "a" or "A", case-insensitive. - A number. You can find it in codeforces contest url. E.g. "1119" in - "https://codeforces.com/contest/1119". + An url or a number. The url may contain additional information, + such as . E. g. "https://codeforces.com/contest/1116" or "1116." Template's alias. File: From a1e345e06c72cad7e70db40784f7795ea1beaa5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Mon, 9 Dec 2019 20:29:34 +0200 Subject: [PATCH 13/16] Clean up URL parsing regex --- cmd/cmd.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index 189d756a..343245e6 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -117,7 +117,7 @@ func parseArgs(args map[string]interface{}, required map[string]bool) (map[strin } func parseUrl(url string) (map[string]string, error) { - reg := regexp.MustCompile(`(https?:\/\/)?(www\.)?([a-zA-Z\d\-\.]+)\/(?Pproblemset|gym|contest|group)`) + reg := regexp.MustCompile(`/(?Pproblemset|gym|contest|group)`) url_type := "" for i, val := range reg.FindStringSubmatch(url) { if reg.SubexpNames()[i] == "type" { @@ -129,11 +129,11 @@ func parseUrl(url string) (map[string]string, error) { reg_str := "" switch url_type { case "contest": - reg_str = `(https?:\/\/)?(www\.)?([a-zA-Z\d\-\.]+)\/contest\/(?P\d+)(\/problem\/(?P[\w\d]+))?` + reg_str = `/contest/(?P\d+)(/problem/(?P[\w\d]+))?` case "gym": - reg_str = `(https?:\/\/)?(www\.)?([a-zA-Z\d\-\.]+)\/gym\/(?P\d+)(\/problem\/(?P[\w\d]+))?` + reg_str = `/gym/(?P\d+)(/problem/(?P[\w\d]+))?` case "problemset": - reg_str = `(https?:\/\/)?(www\.)?([a-zA-Z\d\-\.]+)\/problemset\/problem\/(?P\d+)\/(?P[\w\d]+)?` + reg_str = `/problemset/problem/(?P\d+)/(?P[\w\d]+)?` case "group": return nil, errors.New("Groups are not supported") default: From e0d3ef84ca93c17aa6c38bb5f3d9b3ee722d10c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Wed, 11 Dec 2019 16:34:43 +0200 Subject: [PATCH 14/16] Update docopt-go to master branch --- Gopkg.lock | 6 +- Gopkg.toml | 2 +- .../github.com/docopt/docopt-go/.travis.yml | 7 +- vendor/github.com/docopt/docopt-go/LICENSE | 1 + vendor/github.com/docopt/docopt-go/README.md | 80 +- vendor/github.com/docopt/docopt-go/doc.go | 49 ++ vendor/github.com/docopt/docopt-go/docopt.go | 830 ++---------------- vendor/github.com/docopt/docopt-go/error.go | 49 ++ vendor/github.com/docopt/docopt-go/opts.go | 264 ++++++ vendor/github.com/docopt/docopt-go/pattern.go | 550 ++++++++++++ vendor/github.com/docopt/docopt-go/token.go | 126 +++ 11 files changed, 1184 insertions(+), 780 deletions(-) create mode 100644 vendor/github.com/docopt/docopt-go/doc.go create mode 100644 vendor/github.com/docopt/docopt-go/error.go create mode 100644 vendor/github.com/docopt/docopt-go/opts.go create mode 100644 vendor/github.com/docopt/docopt-go/pattern.go create mode 100644 vendor/github.com/docopt/docopt-go/token.go diff --git a/Gopkg.lock b/Gopkg.lock index 9dbee9d4..6233421b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -26,12 +26,12 @@ version = "v1.0.0" [[projects]] - digest = "1:abaaa7489a2f0f3afb2adc8ea1a282a5bd52350b87b26da220c94fc778d6d63b" + branch = "master" + digest = "1:289fa52f4d9e9c817a003324bc14e9339b996dbe02b9f6cfc57a9383e5365287" name = "github.com/docopt/docopt-go" packages = ["."] pruneopts = "UT" - revision = "784ddc588536785e7299f7272f39101f7faccc3f" - version = "0.6.2" + revision = "ee0de3bc6815ee19d4a46c7eb90f829db0e014b1" [[projects]] digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a" diff --git a/Gopkg.toml b/Gopkg.toml index 2fb16447..fbd22456 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -31,7 +31,7 @@ [[constraint]] name = "github.com/docopt/docopt-go" - version = "0.6.2" + branch = "master" [[constraint]] name = "github.com/fatih/color" diff --git a/vendor/github.com/docopt/docopt-go/.travis.yml b/vendor/github.com/docopt/docopt-go/.travis.yml index 65fad599..db820dc3 100644 --- a/vendor/github.com/docopt/docopt-go/.travis.yml +++ b/vendor/github.com/docopt/docopt-go/.travis.yml @@ -7,15 +7,17 @@ language: go go: - 1.4 - 1.5 + - 1.6 + - 1.7 + - 1.8 + - 1.9 - tip matrix: fast_finish: true before_install: - - go get golang.org/x/tools/cmd/vet - go get golang.org/x/tools/cmd/cover - - go get github.com/golang/lint/golint - go get github.com/mattn/goveralls install: @@ -23,7 +25,6 @@ install: script: - go vet -x ./... - - $HOME/gopath/bin/golint ./... - go test -v ./... - go test -covermode=count -coverprofile=profile.cov . diff --git a/vendor/github.com/docopt/docopt-go/LICENSE b/vendor/github.com/docopt/docopt-go/LICENSE index 8841af16..5e51f73e 100644 --- a/vendor/github.com/docopt/docopt-go/LICENSE +++ b/vendor/github.com/docopt/docopt-go/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) Copyright (c) 2013 Keith Batten +Copyright (c) 2016 David Irvine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/vendor/github.com/docopt/docopt-go/README.md b/vendor/github.com/docopt/docopt-go/README.md index 71c92aae..d03f8da5 100644 --- a/vendor/github.com/docopt/docopt-go/README.md +++ b/vendor/github.com/docopt/docopt-go/README.md @@ -2,11 +2,10 @@ docopt-go ========= [![Build Status](https://travis-ci.org/docopt/docopt.go.svg?branch=master)](https://travis-ci.org/docopt/docopt.go) -[![Coverage Status](https://coveralls.io/repos/docopt/docopt.go/badge.png)](https://coveralls.io/r/docopt/docopt.go) -[![GoDoc](https://godoc.org/github.com/docopt/docopt.go?status.png)](https://godoc.org/github.com/docopt/docopt.go) +[![Coverage Status](https://coveralls.io/repos/github/docopt/docopt.go/badge.svg)](https://coveralls.io/github/docopt/docopt.go) +[![GoDoc](https://godoc.org/github.com/docopt/docopt.go?status.svg)](https://godoc.org/github.com/docopt/docopt.go) -An implementation of [docopt](http://docopt.org/) in the -[Go](http://golang.org/) programming language. +An implementation of [docopt](http://docopt.org/) in the [Go](http://golang.org/) programming language. **docopt** helps you create *beautiful* command-line interfaces easily: @@ -36,24 +35,22 @@ Options: --moored Moored (anchored) mine. --drifting Drifting mine.` - arguments, _ := docopt.Parse(usage, nil, true, "Naval Fate 2.0", false) + arguments, _ := docopt.ParseDoc(usage) fmt.Println(arguments) } ``` -**docopt** parses command-line arguments based on a help message. Don't -write parser code: a good help message already has all the necessary -information in it. +**docopt** parses command-line arguments based on a help message. Don't write parser code: a good help message already has all the necessary information in it. ## Installation -⚠ Use the alias “docopt-go”. To use docopt in your Go code: +⚠ Use the alias "docopt-go". To use docopt in your Go code: ```go import "github.com/docopt/docopt-go" ``` -To install docopt according to your `$GOPATH`: +To install docopt in your `$GOPATH`: ```console $ go get github.com/docopt/docopt-go @@ -61,28 +58,59 @@ $ go get github.com/docopt/docopt-go ## API +Given a conventional command-line help message, docopt processes the arguments. See https://github.com/docopt/docopt#help-message-format for a description of the help message format. + +This package exposes three different APIs, depending on the level of control required. The first, simplest way to parse your docopt usage is to just call: + +```go +docopt.ParseDoc(usage) +``` + +This will use `os.Args[1:]` as the argv slice, and use the default parser options. If you want to provide your own version string and args, then use: + ```go -func Parse(doc string, argv []string, help bool, version string, - optionsFirst bool, exit ...bool) (map[string]interface{}, error) +docopt.ParseArgs(usage, argv, "1.2.3") +``` + +If the last parameter (version) is a non-empty string, it will be printed when `--version` is given in the argv slice. Finally, we can instantiate our own `docopt.Parser` which gives us control over how things like help messages are printed and whether to exit after displaying usage messages, etc. + +```go +parser := &docopt.Parser{ + HelpHandler: docopt.PrintHelpOnly, + OptionsFirst: true, +} +opts, err := parser.ParseArgs(usage, argv, "") +``` + +In particular, setting your own custom `HelpHandler` function makes unit testing your own docs with example command line invocations much more enjoyable. + +All three of these return a map of option names to the values parsed from argv, and an error or nil. You can get the values using the helpers, or just treat it as a regular map: + +```go +flag, _ := opts.Bool("--flag") +secs, _ := opts.Int("") +``` + +Additionally, you can `Bind` these to a struct, assigning option values to the +exported fields of that struct, all at once. + +```go +var config struct { + Command string `docopt:""` + Tries int `docopt:"-n"` + Force bool // Gets the value of --force +} +opts.Bind(&config) ``` -Parse `argv` based on the command-line interface described in `doc`. -Given a conventional command-line help message, docopt creates a parser and -processes the arguments. See -https://github.com/docopt/docopt#help-message-format for a description of the -help message format. If `argv` is `nil`, `os.Args[1:]` is used. +More documentation is available at [godoc.org](https://godoc.org/github.com/docopt/docopt-go). -docopt returns a map of option names to the values parsed from `argv`, and an -error or `nil`. +## Unit Testing -More documentation for docopt is available at -[GoDoc.org](https://godoc.org/github.com/docopt/docopt.go). +Unit testing your own usage docs is recommended, so you can be sure that for a given command line invocation, the expected options are set. An example of how to do this is [in the examples folder](examples/unit_test/unit_test.go). -## Testing +## Tests -All tests from the Python version are implemented and passing -at [Travis CI](https://travis-ci.org/docopt/docopt.go). New -language-agnostic tests have been added -to [test_golang.docopt](test_golang.docopt). +All tests from the Python version are implemented and passing at [Travis CI](https://travis-ci.org/docopt/docopt-go). New language-agnostic tests have been added to [test_golang.docopt](test_golang.docopt). To run tests for docopt-go, use `go test`. diff --git a/vendor/github.com/docopt/docopt-go/doc.go b/vendor/github.com/docopt/docopt-go/doc.go new file mode 100644 index 00000000..c56ee12a --- /dev/null +++ b/vendor/github.com/docopt/docopt-go/doc.go @@ -0,0 +1,49 @@ +/* +Package docopt parses command-line arguments based on a help message. + +Given a conventional command-line help message, docopt processes the arguments. +See https://github.com/docopt/docopt#help-message-format for a description of +the help message format. + +This package exposes three different APIs, depending on the level of control +required. The first, simplest way to parse your docopt usage is to just call: + + docopt.ParseDoc(usage) + +This will use os.Args[1:] as the argv slice, and use the default parser +options. If you want to provide your own version string and args, then use: + + docopt.ParseArgs(usage, argv, "1.2.3") + +If the last parameter (version) is a non-empty string, it will be printed when +--version is given in the argv slice. Finally, we can instantiate our own +docopt.Parser which gives us control over how things like help messages are +printed and whether to exit after displaying usage messages, etc. + + parser := &docopt.Parser{ + HelpHandler: docopt.PrintHelpOnly, + OptionsFirst: true, + } + opts, err := parser.ParseArgs(usage, argv, "") + +In particular, setting your own custom HelpHandler function makes unit testing +your own docs with example command line invocations much more enjoyable. + +All three of these return a map of option names to the values parsed from argv, +and an error or nil. You can get the values using the helpers, or just treat it +as a regular map: + + flag, _ := opts.Bool("--flag") + secs, _ := opts.Int("") + +Additionally, you can `Bind` these to a struct, assigning option values to the +exported fields of that struct, all at once. + + var config struct { + Command string `docopt:""` + Tries int `docopt:"-n"` + Force bool // Gets the value of --force + } + opts.Bind(&config) +*/ +package docopt diff --git a/vendor/github.com/docopt/docopt-go/docopt.go b/vendor/github.com/docopt/docopt-go/docopt.go index d929fc39..c22feb7f 100644 --- a/vendor/github.com/docopt/docopt-go/docopt.go +++ b/vendor/github.com/docopt/docopt-go/docopt.go @@ -1,71 +1,113 @@ // Licensed under terms of MIT license (see LICENSE-MIT) // Copyright (c) 2013 Keith Batten, kbatten@gmail.com +// Copyright (c) 2016 David Irvine -/* -Package docopt parses command-line arguments based on a help message. - -⚠ Use the alias “docopt-go”: - import "github.com/docopt/docopt-go" -or - $ go get github.com/docopt/docopt-go -*/ package docopt import ( "fmt" "os" - "reflect" "regexp" "strings" - "unicode" ) -/* -Parse `argv` based on the command-line interface described in `doc`. +type Parser struct { + // HelpHandler is called when we encounter bad user input, or when the user + // asks for help. + // By default, this calls os.Exit(0) if it handled a built-in option such + // as -h, --help or --version. If the user errored with a wrong command or + // options, we exit with a return code of 1. + HelpHandler func(err error, usage string) + // OptionsFirst requires that option flags always come before positional + // arguments; otherwise they can overlap. + OptionsFirst bool + // SkipHelpFlags tells the parser not to look for -h and --help flags and + // call the HelpHandler. + SkipHelpFlags bool +} + +var PrintHelpAndExit = func(err error, usage string) { + if err != nil { + fmt.Fprintln(os.Stderr, usage) + os.Exit(1) + } else { + fmt.Println(usage) + os.Exit(0) + } +} + +var PrintHelpOnly = func(err error, usage string) { + if err != nil { + fmt.Fprintln(os.Stderr, usage) + } else { + fmt.Println(usage) + } +} + +var NoHelpHandler = func(err error, usage string) {} + +var DefaultParser = &Parser{ + HelpHandler: PrintHelpAndExit, + OptionsFirst: false, + SkipHelpFlags: false, +} -Given a conventional command-line help message, docopt creates a parser and -processes the arguments. See -https://github.com/docopt/docopt#help-message-format for a description of the -help message format. If `argv` is `nil`, `os.Args[1:]` is used. +// ParseDoc parses os.Args[1:] based on the interface described in doc, using the default parser options. +func ParseDoc(doc string) (Opts, error) { + return ParseArgs(doc, nil, "") +} -docopt returns a map of option names to the values parsed from `argv`, and an -error or `nil`. +// ParseArgs parses custom arguments based on the interface described in doc. If you provide a non-empty version +// string, then this will be displayed when the --version flag is found. This method uses the default parser options. +func ParseArgs(doc string, argv []string, version string) (Opts, error) { + return DefaultParser.ParseArgs(doc, argv, version) +} -Set `help` to `false` to disable automatic help messages on `-h` or `--help`. -If `version` is a non-empty string, it will be printed when `--version` is -specified. Set `optionsFirst` to `true` to require that options always come -before positional arguments; otherwise they can overlap. +// ParseArgs parses custom arguments based on the interface described in doc. If you provide a non-empty version +// string, then this will be displayed when the --version flag is found. +func (p *Parser) ParseArgs(doc string, argv []string, version string) (Opts, error) { + return p.parse(doc, argv, version) +} -By default, docopt calls `os.Exit(0)` if it handled a built-in option such as -`-h` or `--version`. If the user errored with a wrong command or options, -docopt exits with a return code of 1. To stop docopt from calling `os.Exit()` -and to handle your own return codes, pass an optional last parameter of `false` -for `exit`. -*/ -func Parse(doc string, argv []string, help bool, version string, - optionsFirst bool, exit ...bool) (map[string]interface{}, error) { - // if "false" was the (optional) last arg, don't call os.Exit() +// Deprecated: Parse is provided for backward compatibility with the original docopt.go package. +// Please rather make use of ParseDoc, ParseArgs, or use your own custom Parser. +func Parse(doc string, argv []string, help bool, version string, optionsFirst bool, exit ...bool) (map[string]interface{}, error) { exitOk := true if len(exit) > 0 { exitOk = exit[0] } - args, output, err := parse(doc, argv, help, version, optionsFirst) + p := &Parser{ + OptionsFirst: optionsFirst, + SkipHelpFlags: !help, + } + if exitOk { + p.HelpHandler = PrintHelpAndExit + } else { + p.HelpHandler = PrintHelpOnly + } + return p.parse(doc, argv, version) +} + +func (p *Parser) parse(doc string, argv []string, version string) (map[string]interface{}, error) { + if argv == nil { + argv = os.Args[1:] + } + if p.HelpHandler == nil { + p.HelpHandler = DefaultParser.HelpHandler + } + args, output, err := parse(doc, argv, !p.SkipHelpFlags, version, p.OptionsFirst) if _, ok := err.(*UserError); ok { // the user gave us bad input - fmt.Fprintln(os.Stderr, output) - if exitOk { - os.Exit(1) - } + p.HelpHandler(err, output) } else if len(output) > 0 && err == nil { - // the user asked for help or `--version` - fmt.Println(output) - if exitOk { - os.Exit(0) - } + // the user asked for help or --version + p.HelpHandler(err, output) } return args, err } +// ----------------------------------------------------------------------------- + // parse and return a map of args, output and all errors func parse(doc string, argv []string, help bool, version string, optionsFirst bool) (args map[string]interface{}, output string, err error) { if argv == nil && len(os.Args) > 1 { @@ -484,39 +526,6 @@ func parseShorts(tokens *tokenList, options *patternList) (patternList, error) { return parsed, nil } -func newTokenList(source []string, err errorType) *tokenList { - errorFunc := newError - if err == errorUser { - errorFunc = newUserError - } else if err == errorLanguage { - errorFunc = newLanguageError - } - return &tokenList{source, errorFunc, err} -} - -func tokenListFromString(source string) *tokenList { - return newTokenList(strings.Fields(source), errorUser) -} - -func tokenListFromPattern(source string) *tokenList { - p := regexp.MustCompile(`([\[\]\(\)\|]|\.\.\.)`) - source = p.ReplaceAllString(source, ` $1 `) - p = regexp.MustCompile(`\s+|(\S*<.*?>)`) - split := p.Split(source, -1) - match := p.FindAllStringSubmatch(source, -1) - var result []string - l := len(split) - for i := 0; i < l; i++ { - if len(split[i]) > 0 { - result = append(result, split[i]) - } - if i < l-1 && len(match[i][1]) > 0 { - result = append(result, match[i][1]) - } - } - return newTokenList(result, errorLanguage) -} - func formalUsage(section string) (string, error) { _, _, section = stringPartition(section, ":") // drop "usage:" pu := strings.Fields(section) @@ -556,665 +565,6 @@ func extras(help bool, version string, options patternList, doc string) string { return "" } -type errorType int - -const ( - errorUser errorType = iota - errorLanguage -) - -func (e errorType) String() string { - switch e { - case errorUser: - return "errorUser" - case errorLanguage: - return "errorLanguage" - } - return "" -} - -// UserError records an error with program arguments. -type UserError struct { - msg string - Usage string -} - -func (e UserError) Error() string { - return e.msg -} -func newUserError(msg string, f ...interface{}) error { - return &UserError{fmt.Sprintf(msg, f...), ""} -} - -// LanguageError records an error with the doc string. -type LanguageError struct { - msg string -} - -func (e LanguageError) Error() string { - return e.msg -} -func newLanguageError(msg string, f ...interface{}) error { - return &LanguageError{fmt.Sprintf(msg, f...)} -} - -var newError = fmt.Errorf - -type tokenList struct { - tokens []string - errorFunc func(string, ...interface{}) error - err errorType -} -type token string - -func (t *token) eq(s string) bool { - if t == nil { - return false - } - return string(*t) == s -} -func (t *token) match(matchNil bool, tokenStrings ...string) bool { - if t == nil && matchNil { - return true - } else if t == nil && !matchNil { - return false - } - - for _, tok := range tokenStrings { - if tok == string(*t) { - return true - } - } - return false -} -func (t *token) hasPrefix(prefix string) bool { - if t == nil { - return false - } - return strings.HasPrefix(string(*t), prefix) -} -func (t *token) hasSuffix(suffix string) bool { - if t == nil { - return false - } - return strings.HasSuffix(string(*t), suffix) -} -func (t *token) isUpper() bool { - if t == nil { - return false - } - return isStringUppercase(string(*t)) -} -func (t *token) String() string { - if t == nil { - return "" - } - return string(*t) -} - -func (tl *tokenList) current() *token { - if len(tl.tokens) > 0 { - return (*token)(&(tl.tokens[0])) - } - return nil -} - -func (tl *tokenList) length() int { - return len(tl.tokens) -} - -func (tl *tokenList) move() *token { - if len(tl.tokens) > 0 { - t := tl.tokens[0] - tl.tokens = tl.tokens[1:] - return (*token)(&t) - } - return nil -} - -type patternType uint - -const ( - // leaf - patternArgument patternType = 1 << iota - patternCommand - patternOption - - // branch - patternRequired - patternOptionAL - patternOptionSSHORTCUT // Marker/placeholder for [options] shortcut. - patternOneOrMore - patternEither - - patternLeaf = patternArgument + - patternCommand + - patternOption - patternBranch = patternRequired + - patternOptionAL + - patternOptionSSHORTCUT + - patternOneOrMore + - patternEither - patternAll = patternLeaf + patternBranch - patternDefault = 0 -) - -func (pt patternType) String() string { - switch pt { - case patternArgument: - return "argument" - case patternCommand: - return "command" - case patternOption: - return "option" - case patternRequired: - return "required" - case patternOptionAL: - return "optional" - case patternOptionSSHORTCUT: - return "optionsshortcut" - case patternOneOrMore: - return "oneormore" - case patternEither: - return "either" - case patternLeaf: - return "leaf" - case patternBranch: - return "branch" - case patternAll: - return "all" - case patternDefault: - return "default" - } - return "" -} - -type pattern struct { - t patternType - - children patternList - - name string - value interface{} - - short string - long string - argcount int -} - -type patternList []*pattern - -func newBranchPattern(t patternType, pl ...*pattern) *pattern { - var p pattern - p.t = t - p.children = make(patternList, len(pl)) - copy(p.children, pl) - return &p -} - -func newRequired(pl ...*pattern) *pattern { - return newBranchPattern(patternRequired, pl...) -} - -func newEither(pl ...*pattern) *pattern { - return newBranchPattern(patternEither, pl...) -} - -func newOneOrMore(pl ...*pattern) *pattern { - return newBranchPattern(patternOneOrMore, pl...) -} - -func newOptional(pl ...*pattern) *pattern { - return newBranchPattern(patternOptionAL, pl...) -} - -func newOptionsShortcut() *pattern { - var p pattern - p.t = patternOptionSSHORTCUT - return &p -} - -func newLeafPattern(t patternType, name string, value interface{}) *pattern { - // default: value=nil - var p pattern - p.t = t - p.name = name - p.value = value - return &p -} - -func newArgument(name string, value interface{}) *pattern { - // default: value=nil - return newLeafPattern(patternArgument, name, value) -} - -func newCommand(name string, value interface{}) *pattern { - // default: value=false - var p pattern - p.t = patternCommand - p.name = name - p.value = value - return &p -} - -func newOption(short, long string, argcount int, value interface{}) *pattern { - // default: "", "", 0, false - var p pattern - p.t = patternOption - p.short = short - p.long = long - if long != "" { - p.name = long - } else { - p.name = short - } - p.argcount = argcount - if value == false && argcount > 0 { - p.value = nil - } else { - p.value = value - } - return &p -} - -func (p *pattern) flat(types patternType) (patternList, error) { - if p.t&patternLeaf != 0 { - if types == patternDefault { - types = patternAll - } - if p.t&types != 0 { - return patternList{p}, nil - } - return patternList{}, nil - } - - if p.t&patternBranch != 0 { - if p.t&types != 0 { - return patternList{p}, nil - } - result := patternList{} - for _, child := range p.children { - childFlat, err := child.flat(types) - if err != nil { - return nil, err - } - result = append(result, childFlat...) - } - return result, nil - } - return nil, newError("unknown pattern type: %d, %d", p.t, types) -} - -func (p *pattern) fix() error { - err := p.fixIdentities(nil) - if err != nil { - return err - } - p.fixRepeatingArguments() - return nil -} - -func (p *pattern) fixIdentities(uniq patternList) error { - // Make pattern-tree tips point to same object if they are equal. - if p.t&patternBranch == 0 { - return nil - } - if uniq == nil { - pFlat, err := p.flat(patternDefault) - if err != nil { - return err - } - uniq = pFlat.unique() - } - for i, child := range p.children { - if child.t&patternBranch == 0 { - ind, err := uniq.index(child) - if err != nil { - return err - } - p.children[i] = uniq[ind] - } else { - err := child.fixIdentities(uniq) - if err != nil { - return err - } - } - } - return nil -} - -func (p *pattern) fixRepeatingArguments() { - // Fix elements that should accumulate/increment values. - var either []patternList - - for _, child := range p.transform().children { - either = append(either, child.children) - } - for _, cas := range either { - casMultiple := patternList{} - for _, e := range cas { - if cas.count(e) > 1 { - casMultiple = append(casMultiple, e) - } - } - for _, e := range casMultiple { - if e.t == patternArgument || e.t == patternOption && e.argcount > 0 { - switch e.value.(type) { - case string: - e.value = strings.Fields(e.value.(string)) - case []string: - default: - e.value = []string{} - } - } - if e.t == patternCommand || e.t == patternOption && e.argcount == 0 { - e.value = 0 - } - } - } -} - -func (p *pattern) match(left *patternList, collected *patternList) (bool, *patternList, *patternList) { - if collected == nil { - collected = &patternList{} - } - if p.t&patternRequired != 0 { - l := left - c := collected - for _, p := range p.children { - var matched bool - matched, l, c = p.match(l, c) - if !matched { - return false, left, collected - } - } - return true, l, c - } else if p.t&patternOptionAL != 0 || p.t&patternOptionSSHORTCUT != 0 { - for _, p := range p.children { - _, left, collected = p.match(left, collected) - } - return true, left, collected - } else if p.t&patternOneOrMore != 0 { - if len(p.children) != 1 { - panic("OneOrMore.match(): assert len(p.children) == 1") - } - l := left - c := collected - var lAlt *patternList - matched := true - times := 0 - for matched { - // could it be that something didn't match but changed l or c? - matched, l, c = p.children[0].match(l, c) - if matched { - times++ - } - if lAlt == l { - break - } - lAlt = l - } - if times >= 1 { - return true, l, c - } - return false, left, collected - } else if p.t&patternEither != 0 { - type outcomeStruct struct { - matched bool - left *patternList - collected *patternList - length int - } - outcomes := []outcomeStruct{} - for _, p := range p.children { - matched, l, c := p.match(left, collected) - outcome := outcomeStruct{matched, l, c, len(*l)} - if matched { - outcomes = append(outcomes, outcome) - } - } - if len(outcomes) > 0 { - minLen := outcomes[0].length - minIndex := 0 - for i, v := range outcomes { - if v.length < minLen { - minIndex = i - } - } - return outcomes[minIndex].matched, outcomes[minIndex].left, outcomes[minIndex].collected - } - return false, left, collected - } else if p.t&patternLeaf != 0 { - pos, match := p.singleMatch(left) - var increment interface{} - if match == nil { - return false, left, collected - } - leftAlt := make(patternList, len((*left)[:pos]), len((*left)[:pos])+len((*left)[pos+1:])) - copy(leftAlt, (*left)[:pos]) - leftAlt = append(leftAlt, (*left)[pos+1:]...) - sameName := patternList{} - for _, a := range *collected { - if a.name == p.name { - sameName = append(sameName, a) - } - } - - switch p.value.(type) { - case int, []string: - switch p.value.(type) { - case int: - increment = 1 - case []string: - switch match.value.(type) { - case string: - increment = []string{match.value.(string)} - default: - increment = match.value - } - } - if len(sameName) == 0 { - match.value = increment - collectedMatch := make(patternList, len(*collected), len(*collected)+1) - copy(collectedMatch, *collected) - collectedMatch = append(collectedMatch, match) - return true, &leftAlt, &collectedMatch - } - switch sameName[0].value.(type) { - case int: - sameName[0].value = sameName[0].value.(int) + increment.(int) - case []string: - sameName[0].value = append(sameName[0].value.([]string), increment.([]string)...) - } - return true, &leftAlt, collected - } - collectedMatch := make(patternList, len(*collected), len(*collected)+1) - copy(collectedMatch, *collected) - collectedMatch = append(collectedMatch, match) - return true, &leftAlt, &collectedMatch - } - panic("unmatched type") -} - -func (p *pattern) singleMatch(left *patternList) (int, *pattern) { - if p.t&patternArgument != 0 { - for n, pat := range *left { - if pat.t&patternArgument != 0 { - return n, newArgument(p.name, pat.value) - } - } - return -1, nil - } else if p.t&patternCommand != 0 { - for n, pat := range *left { - if pat.t&patternArgument != 0 { - if pat.value == p.name { - return n, newCommand(p.name, true) - } - break - } - } - return -1, nil - } else if p.t&patternOption != 0 { - for n, pat := range *left { - if p.name == pat.name { - return n, pat - } - } - return -1, nil - } - panic("unmatched type") -} - -func (p *pattern) String() string { - if p.t&patternOption != 0 { - return fmt.Sprintf("%s(%s, %s, %d, %+v)", p.t, p.short, p.long, p.argcount, p.value) - } else if p.t&patternLeaf != 0 { - return fmt.Sprintf("%s(%s, %+v)", p.t, p.name, p.value) - } else if p.t&patternBranch != 0 { - result := "" - for i, child := range p.children { - if i > 0 { - result += ", " - } - result += child.String() - } - return fmt.Sprintf("%s(%s)", p.t, result) - } - panic("unmatched type") -} - -func (p *pattern) transform() *pattern { - /* - Expand pattern into an (almost) equivalent one, but with single Either. - - Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d) - Quirks: [-a] => (-a), (-a...) => (-a -a) - */ - result := []patternList{} - groups := []patternList{patternList{p}} - parents := patternRequired + - patternOptionAL + - patternOptionSSHORTCUT + - patternEither + - patternOneOrMore - for len(groups) > 0 { - children := groups[0] - groups = groups[1:] - var child *pattern - for _, c := range children { - if c.t&parents != 0 { - child = c - break - } - } - if child != nil { - children.remove(child) - if child.t&patternEither != 0 { - for _, c := range child.children { - r := patternList{} - r = append(r, c) - r = append(r, children...) - groups = append(groups, r) - } - } else if child.t&patternOneOrMore != 0 { - r := patternList{} - r = append(r, child.children.double()...) - r = append(r, children...) - groups = append(groups, r) - } else { - r := patternList{} - r = append(r, child.children...) - r = append(r, children...) - groups = append(groups, r) - } - } else { - result = append(result, children) - } - } - either := patternList{} - for _, e := range result { - either = append(either, newRequired(e...)) - } - return newEither(either...) -} - -func (p *pattern) eq(other *pattern) bool { - return reflect.DeepEqual(p, other) -} - -func (pl patternList) unique() patternList { - table := make(map[string]bool) - result := patternList{} - for _, v := range pl { - if !table[v.String()] { - table[v.String()] = true - result = append(result, v) - } - } - return result -} - -func (pl patternList) index(p *pattern) (int, error) { - for i, c := range pl { - if c.eq(p) { - return i, nil - } - } - return -1, newError("%s not in list", p) -} - -func (pl patternList) count(p *pattern) int { - count := 0 - for _, c := range pl { - if c.eq(p) { - count++ - } - } - return count -} - -func (pl patternList) diff(l patternList) patternList { - lAlt := make(patternList, len(l)) - copy(lAlt, l) - result := make(patternList, 0, len(pl)) - for _, v := range pl { - if v != nil { - match := false - for i, w := range lAlt { - if w.eq(v) { - match = true - lAlt[i] = nil - break - } - } - if match == false { - result = append(result, v) - } - } - } - return result -} - -func (pl patternList) double() patternList { - l := len(pl) - result := make(patternList, l*2) - copy(result, pl) - copy(result[l:2*l], pl) - return result -} - -func (pl *patternList) remove(p *pattern) { - (*pl) = pl.diff(patternList{p}) -} - -func (pl patternList) dictionary() map[string]interface{} { - dict := make(map[string]interface{}) - for _, a := range pl { - dict[a.name] = a.value - } - return dict -} - func stringPartition(s, sep string) (string, string, string) { sepPos := strings.Index(s, sep) if sepPos == -1 { // no seperator found @@ -1223,17 +573,3 @@ func stringPartition(s, sep string) (string, string, string) { split := strings.SplitN(s, sep, 2) return split[0], sep, split[1] } - -// returns true if all cased characters in the string are uppercase -// and there are there is at least one cased charcter -func isStringUppercase(s string) bool { - if strings.ToUpper(s) != s { - return false - } - for _, c := range []rune(s) { - if unicode.IsUpper(c) { - return true - } - } - return false -} diff --git a/vendor/github.com/docopt/docopt-go/error.go b/vendor/github.com/docopt/docopt-go/error.go new file mode 100644 index 00000000..bd26460f --- /dev/null +++ b/vendor/github.com/docopt/docopt-go/error.go @@ -0,0 +1,49 @@ +package docopt + +import ( + "fmt" +) + +type errorType int + +const ( + errorUser errorType = iota + errorLanguage +) + +func (e errorType) String() string { + switch e { + case errorUser: + return "errorUser" + case errorLanguage: + return "errorLanguage" + } + return "" +} + +// UserError records an error with program arguments. +type UserError struct { + msg string + Usage string +} + +func (e UserError) Error() string { + return e.msg +} +func newUserError(msg string, f ...interface{}) error { + return &UserError{fmt.Sprintf(msg, f...), ""} +} + +// LanguageError records an error with the doc string. +type LanguageError struct { + msg string +} + +func (e LanguageError) Error() string { + return e.msg +} +func newLanguageError(msg string, f ...interface{}) error { + return &LanguageError{fmt.Sprintf(msg, f...)} +} + +var newError = fmt.Errorf diff --git a/vendor/github.com/docopt/docopt-go/opts.go b/vendor/github.com/docopt/docopt-go/opts.go new file mode 100644 index 00000000..36320fbc --- /dev/null +++ b/vendor/github.com/docopt/docopt-go/opts.go @@ -0,0 +1,264 @@ +package docopt + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "unicode" +) + +func errKey(key string) error { + return fmt.Errorf("no such key: %q", key) +} +func errType(key string) error { + return fmt.Errorf("key: %q failed type conversion", key) +} +func errStrconv(key string, convErr error) error { + return fmt.Errorf("key: %q failed type conversion: %s", key, convErr) +} + +// Opts is a map of command line options to their values, with some convenience +// methods for value type conversion (bool, float64, int, string). For example, +// to get an option value as an int: +// +// opts, _ := docopt.ParseDoc("Usage: sleep ") +// secs, _ := opts.Int("") +// +// Additionally, Opts.Bind allows you easily populate a struct's fields with the +// values of each option value. See below for examples. +// +// Lastly, you can still treat Opts as a regular map, and do any type checking +// and conversion that you want to yourself. For example: +// +// if s, ok := opts[""].(string); ok { +// if val, err := strconv.ParseUint(s, 2, 64); err != nil { ... } +// } +// +// Note that any non-boolean option / flag will have a string value in the +// underlying map. +type Opts map[string]interface{} + +func (o Opts) String(key string) (s string, err error) { + v, ok := o[key] + if !ok { + err = errKey(key) + return + } + s, ok = v.(string) + if !ok { + err = errType(key) + } + return +} + +func (o Opts) Bool(key string) (b bool, err error) { + v, ok := o[key] + if !ok { + err = errKey(key) + return + } + b, ok = v.(bool) + if !ok { + err = errType(key) + } + return +} + +func (o Opts) Int(key string) (i int, err error) { + s, err := o.String(key) + if err != nil { + return + } + i, err = strconv.Atoi(s) + if err != nil { + err = errStrconv(key, err) + } + return +} + +func (o Opts) Float64(key string) (f float64, err error) { + s, err := o.String(key) + if err != nil { + return + } + f, err = strconv.ParseFloat(s, 64) + if err != nil { + err = errStrconv(key, err) + } + return +} + +// Bind populates the fields of a given struct with matching option values. +// Each key in Opts will be mapped to an exported field of the struct pointed +// to by `v`, as follows: +// +// abc int // Unexported field, ignored +// Abc string // Mapped from `--abc`, ``, or `abc` +// // (case insensitive) +// A string // Mapped from `-a`, `` or `a` +// // (case insensitive) +// Abc int `docopt:"XYZ"` // Mapped from `XYZ` +// Abc bool `docopt:"-"` // Mapped from `-` +// Abc bool `docopt:"-x,--xyz"` // Mapped from `-x` or `--xyz` +// // (first non-zero value found) +// +// Tagged (annotated) fields will always be mapped first. If no field is tagged +// with an option's key, Bind will try to map the option to an appropriately +// named field (as above). +// +// Bind also handles conversion to bool, float, int or string types. +func (o Opts) Bind(v interface{}) error { + structVal := reflect.ValueOf(v) + if structVal.Kind() != reflect.Ptr { + return newError("'v' argument is not pointer to struct type") + } + for structVal.Kind() == reflect.Ptr { + structVal = structVal.Elem() + } + if structVal.Kind() != reflect.Struct { + return newError("'v' argument is not pointer to struct type") + } + structType := structVal.Type() + + tagged := make(map[string]int) // Tagged field tags + untagged := make(map[string]int) // Untagged field names + + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if isUnexportedField(field) || field.Anonymous { + continue + } + tag := field.Tag.Get("docopt") + if tag == "" { + untagged[field.Name] = i + continue + } + for _, t := range strings.Split(tag, ",") { + tagged[t] = i + } + } + + // Get the index of the struct field to use, based on the option key. + // Second argument is true/false on whether something was matched. + getFieldIndex := func(key string) (int, bool) { + if i, ok := tagged[key]; ok { + return i, true + } + if i, ok := untagged[guessUntaggedField(key)]; ok { + return i, true + } + return -1, false + } + + indexMap := make(map[string]int) // Option keys to field index + + // Pre-check that option keys are mapped to fields and fields are zero valued, before populating them. + for k := range o { + i, ok := getFieldIndex(k) + if !ok { + if k == "--help" || k == "--version" { // Don't require these to be mapped. + continue + } + return newError("mapping of %q is not found in given struct, or is an unexported field", k) + } + fieldVal := structVal.Field(i) + zeroVal := reflect.Zero(fieldVal.Type()) + if !reflect.DeepEqual(fieldVal.Interface(), zeroVal.Interface()) { + return newError("%q field is non-zero, will be overwritten by value of %q", structType.Field(i).Name, k) + } + indexMap[k] = i + } + + // Populate fields with option values. + for k, v := range o { + i, ok := indexMap[k] + if !ok { + continue // Not mapped. + } + field := structVal.Field(i) + if !reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface()) { + // The struct's field is already non-zero (by our doing), so don't change it. + // This happens with comma separated tags, e.g. `docopt:"-h,--help"` which is a + // convenient way of checking if one of multiple boolean flags are set. + continue + } + optVal := reflect.ValueOf(v) + // Option value is the zero Value, so we can't get its .Type(). No need to assign anyway, so move along. + if !optVal.IsValid() { + continue + } + if !field.CanSet() { + return newError("%q field cannot be set", structType.Field(i).Name) + } + // Try to assign now if able. bool and string values should be assignable already. + if optVal.Type().AssignableTo(field.Type()) { + field.Set(optVal) + continue + } + // Try to convert the value and assign if able. + switch field.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if x, err := o.Int(k); err == nil { + field.SetInt(int64(x)) + continue + } + case reflect.Float32, reflect.Float64: + if x, err := o.Float64(k); err == nil { + field.SetFloat(x) + continue + } + } + // TODO: Something clever (recursive?) with non-string slices. + // case reflect.Slice: + // if optVal.Kind() == reflect.Slice { + // for i := 0; i < optVal.Len(); i++ { + // sliceVal := optVal.Index(i) + // fmt.Printf("%v", sliceVal) + // } + // fmt.Printf("\n") + // } + return newError("value of %q is not assignable to %q field", k, structType.Field(i).Name) + } + + return nil +} + +// isUnexportedField returns whether the field is unexported. +// isUnexportedField is to avoid the bug in versions older than Go1.3. +// See following links: +// https://code.google.com/p/go/issues/detail?id=7247 +// http://golang.org/ref/spec#Exported_identifiers +func isUnexportedField(field reflect.StructField) bool { + return !(field.PkgPath == "" && unicode.IsUpper(rune(field.Name[0]))) +} + +// Convert a string like "--my-special-flag" to "MySpecialFlag". +func titleCaseDashes(key string) string { + nextToUpper := true + mapFn := func(r rune) rune { + if r == '-' { + nextToUpper = true + return -1 + } + if nextToUpper { + nextToUpper = false + return unicode.ToUpper(r) + } + return r + } + return strings.Map(mapFn, key) +} + +// Best guess which field.Name in a struct to assign for an option key. +func guessUntaggedField(key string) string { + switch { + case strings.HasPrefix(key, "--") && len(key[2:]) > 1: + return titleCaseDashes(key[2:]) + case strings.HasPrefix(key, "-") && len(key[1:]) == 1: + return titleCaseDashes(key[1:]) + case strings.HasPrefix(key, "<") && strings.HasSuffix(key, ">"): + key = key[1 : len(key)-1] + } + return strings.Title(strings.ToLower(key)) +} diff --git a/vendor/github.com/docopt/docopt-go/pattern.go b/vendor/github.com/docopt/docopt-go/pattern.go new file mode 100644 index 00000000..0a296671 --- /dev/null +++ b/vendor/github.com/docopt/docopt-go/pattern.go @@ -0,0 +1,550 @@ +package docopt + +import ( + "fmt" + "reflect" + "strings" +) + +type patternType uint + +const ( + // leaf + patternArgument patternType = 1 << iota + patternCommand + patternOption + + // branch + patternRequired + patternOptionAL + patternOptionSSHORTCUT // Marker/placeholder for [options] shortcut. + patternOneOrMore + patternEither + + patternLeaf = patternArgument + + patternCommand + + patternOption + patternBranch = patternRequired + + patternOptionAL + + patternOptionSSHORTCUT + + patternOneOrMore + + patternEither + patternAll = patternLeaf + patternBranch + patternDefault = 0 +) + +func (pt patternType) String() string { + switch pt { + case patternArgument: + return "argument" + case patternCommand: + return "command" + case patternOption: + return "option" + case patternRequired: + return "required" + case patternOptionAL: + return "optional" + case patternOptionSSHORTCUT: + return "optionsshortcut" + case patternOneOrMore: + return "oneormore" + case patternEither: + return "either" + case patternLeaf: + return "leaf" + case patternBranch: + return "branch" + case patternAll: + return "all" + case patternDefault: + return "default" + } + return "" +} + +type pattern struct { + t patternType + + children patternList + + name string + value interface{} + + short string + long string + argcount int +} + +type patternList []*pattern + +func newBranchPattern(t patternType, pl ...*pattern) *pattern { + var p pattern + p.t = t + p.children = make(patternList, len(pl)) + copy(p.children, pl) + return &p +} + +func newRequired(pl ...*pattern) *pattern { + return newBranchPattern(patternRequired, pl...) +} + +func newEither(pl ...*pattern) *pattern { + return newBranchPattern(patternEither, pl...) +} + +func newOneOrMore(pl ...*pattern) *pattern { + return newBranchPattern(patternOneOrMore, pl...) +} + +func newOptional(pl ...*pattern) *pattern { + return newBranchPattern(patternOptionAL, pl...) +} + +func newOptionsShortcut() *pattern { + var p pattern + p.t = patternOptionSSHORTCUT + return &p +} + +func newLeafPattern(t patternType, name string, value interface{}) *pattern { + // default: value=nil + var p pattern + p.t = t + p.name = name + p.value = value + return &p +} + +func newArgument(name string, value interface{}) *pattern { + // default: value=nil + return newLeafPattern(patternArgument, name, value) +} + +func newCommand(name string, value interface{}) *pattern { + // default: value=false + var p pattern + p.t = patternCommand + p.name = name + p.value = value + return &p +} + +func newOption(short, long string, argcount int, value interface{}) *pattern { + // default: "", "", 0, false + var p pattern + p.t = patternOption + p.short = short + p.long = long + if long != "" { + p.name = long + } else { + p.name = short + } + p.argcount = argcount + if value == false && argcount > 0 { + p.value = nil + } else { + p.value = value + } + return &p +} + +func (p *pattern) flat(types patternType) (patternList, error) { + if p.t&patternLeaf != 0 { + if types == patternDefault { + types = patternAll + } + if p.t&types != 0 { + return patternList{p}, nil + } + return patternList{}, nil + } + + if p.t&patternBranch != 0 { + if p.t&types != 0 { + return patternList{p}, nil + } + result := patternList{} + for _, child := range p.children { + childFlat, err := child.flat(types) + if err != nil { + return nil, err + } + result = append(result, childFlat...) + } + return result, nil + } + return nil, newError("unknown pattern type: %d, %d", p.t, types) +} + +func (p *pattern) fix() error { + err := p.fixIdentities(nil) + if err != nil { + return err + } + p.fixRepeatingArguments() + return nil +} + +func (p *pattern) fixIdentities(uniq patternList) error { + // Make pattern-tree tips point to same object if they are equal. + if p.t&patternBranch == 0 { + return nil + } + if uniq == nil { + pFlat, err := p.flat(patternDefault) + if err != nil { + return err + } + uniq = pFlat.unique() + } + for i, child := range p.children { + if child.t&patternBranch == 0 { + ind, err := uniq.index(child) + if err != nil { + return err + } + p.children[i] = uniq[ind] + } else { + err := child.fixIdentities(uniq) + if err != nil { + return err + } + } + } + return nil +} + +func (p *pattern) fixRepeatingArguments() { + // Fix elements that should accumulate/increment values. + var either []patternList + + for _, child := range p.transform().children { + either = append(either, child.children) + } + for _, cas := range either { + casMultiple := patternList{} + for _, e := range cas { + if cas.count(e) > 1 { + casMultiple = append(casMultiple, e) + } + } + for _, e := range casMultiple { + if e.t == patternArgument || e.t == patternOption && e.argcount > 0 { + switch e.value.(type) { + case string: + e.value = strings.Fields(e.value.(string)) + case []string: + default: + e.value = []string{} + } + } + if e.t == patternCommand || e.t == patternOption && e.argcount == 0 { + e.value = 0 + } + } + } +} + +func (p *pattern) match(left *patternList, collected *patternList) (bool, *patternList, *patternList) { + if collected == nil { + collected = &patternList{} + } + if p.t&patternRequired != 0 { + l := left + c := collected + for _, p := range p.children { + var matched bool + matched, l, c = p.match(l, c) + if !matched { + return false, left, collected + } + } + return true, l, c + } else if p.t&patternOptionAL != 0 || p.t&patternOptionSSHORTCUT != 0 { + for _, p := range p.children { + _, left, collected = p.match(left, collected) + } + return true, left, collected + } else if p.t&patternOneOrMore != 0 { + if len(p.children) != 1 { + panic("OneOrMore.match(): assert len(p.children) == 1") + } + l := left + c := collected + var lAlt *patternList + matched := true + times := 0 + for matched { + // could it be that something didn't match but changed l or c? + matched, l, c = p.children[0].match(l, c) + if matched { + times++ + } + if lAlt == l { + break + } + lAlt = l + } + if times >= 1 { + return true, l, c + } + return false, left, collected + } else if p.t&patternEither != 0 { + type outcomeStruct struct { + matched bool + left *patternList + collected *patternList + length int + } + outcomes := []outcomeStruct{} + for _, p := range p.children { + matched, l, c := p.match(left, collected) + outcome := outcomeStruct{matched, l, c, len(*l)} + if matched { + outcomes = append(outcomes, outcome) + } + } + if len(outcomes) > 0 { + minLen := outcomes[0].length + minIndex := 0 + for i, v := range outcomes { + if v.length < minLen { + minIndex = i + } + } + return outcomes[minIndex].matched, outcomes[minIndex].left, outcomes[minIndex].collected + } + return false, left, collected + } else if p.t&patternLeaf != 0 { + pos, match := p.singleMatch(left) + var increment interface{} + if match == nil { + return false, left, collected + } + leftAlt := make(patternList, len((*left)[:pos]), len((*left)[:pos])+len((*left)[pos+1:])) + copy(leftAlt, (*left)[:pos]) + leftAlt = append(leftAlt, (*left)[pos+1:]...) + sameName := patternList{} + for _, a := range *collected { + if a.name == p.name { + sameName = append(sameName, a) + } + } + + switch p.value.(type) { + case int, []string: + switch p.value.(type) { + case int: + increment = 1 + case []string: + switch match.value.(type) { + case string: + increment = []string{match.value.(string)} + default: + increment = match.value + } + } + if len(sameName) == 0 { + match.value = increment + collectedMatch := make(patternList, len(*collected), len(*collected)+1) + copy(collectedMatch, *collected) + collectedMatch = append(collectedMatch, match) + return true, &leftAlt, &collectedMatch + } + switch sameName[0].value.(type) { + case int: + sameName[0].value = sameName[0].value.(int) + increment.(int) + case []string: + sameName[0].value = append(sameName[0].value.([]string), increment.([]string)...) + } + return true, &leftAlt, collected + } + collectedMatch := make(patternList, len(*collected), len(*collected)+1) + copy(collectedMatch, *collected) + collectedMatch = append(collectedMatch, match) + return true, &leftAlt, &collectedMatch + } + panic("unmatched type") +} + +func (p *pattern) singleMatch(left *patternList) (int, *pattern) { + if p.t&patternArgument != 0 { + for n, pat := range *left { + if pat.t&patternArgument != 0 { + return n, newArgument(p.name, pat.value) + } + } + return -1, nil + } else if p.t&patternCommand != 0 { + for n, pat := range *left { + if pat.t&patternArgument != 0 { + if pat.value == p.name { + return n, newCommand(p.name, true) + } + break + } + } + return -1, nil + } else if p.t&patternOption != 0 { + for n, pat := range *left { + if p.name == pat.name { + return n, pat + } + } + return -1, nil + } + panic("unmatched type") +} + +func (p *pattern) String() string { + if p.t&patternOption != 0 { + return fmt.Sprintf("%s(%s, %s, %d, %+v)", p.t, p.short, p.long, p.argcount, p.value) + } else if p.t&patternLeaf != 0 { + return fmt.Sprintf("%s(%s, %+v)", p.t, p.name, p.value) + } else if p.t&patternBranch != 0 { + result := "" + for i, child := range p.children { + if i > 0 { + result += ", " + } + result += child.String() + } + return fmt.Sprintf("%s(%s)", p.t, result) + } + panic("unmatched type") +} + +func (p *pattern) transform() *pattern { + /* + Expand pattern into an (almost) equivalent one, but with single Either. + + Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d) + Quirks: [-a] => (-a), (-a...) => (-a -a) + */ + result := []patternList{} + groups := []patternList{patternList{p}} + parents := patternRequired + + patternOptionAL + + patternOptionSSHORTCUT + + patternEither + + patternOneOrMore + for len(groups) > 0 { + children := groups[0] + groups = groups[1:] + var child *pattern + for _, c := range children { + if c.t&parents != 0 { + child = c + break + } + } + if child != nil { + children.remove(child) + if child.t&patternEither != 0 { + for _, c := range child.children { + r := patternList{} + r = append(r, c) + r = append(r, children...) + groups = append(groups, r) + } + } else if child.t&patternOneOrMore != 0 { + r := patternList{} + r = append(r, child.children.double()...) + r = append(r, children...) + groups = append(groups, r) + } else { + r := patternList{} + r = append(r, child.children...) + r = append(r, children...) + groups = append(groups, r) + } + } else { + result = append(result, children) + } + } + either := patternList{} + for _, e := range result { + either = append(either, newRequired(e...)) + } + return newEither(either...) +} + +func (p *pattern) eq(other *pattern) bool { + return reflect.DeepEqual(p, other) +} + +func (pl patternList) unique() patternList { + table := make(map[string]bool) + result := patternList{} + for _, v := range pl { + if !table[v.String()] { + table[v.String()] = true + result = append(result, v) + } + } + return result +} + +func (pl patternList) index(p *pattern) (int, error) { + for i, c := range pl { + if c.eq(p) { + return i, nil + } + } + return -1, newError("%s not in list", p) +} + +func (pl patternList) count(p *pattern) int { + count := 0 + for _, c := range pl { + if c.eq(p) { + count++ + } + } + return count +} + +func (pl patternList) diff(l patternList) patternList { + lAlt := make(patternList, len(l)) + copy(lAlt, l) + result := make(patternList, 0, len(pl)) + for _, v := range pl { + if v != nil { + match := false + for i, w := range lAlt { + if w.eq(v) { + match = true + lAlt[i] = nil + break + } + } + if match == false { + result = append(result, v) + } + } + } + return result +} + +func (pl patternList) double() patternList { + l := len(pl) + result := make(patternList, l*2) + copy(result, pl) + copy(result[l:2*l], pl) + return result +} + +func (pl *patternList) remove(p *pattern) { + (*pl) = pl.diff(patternList{p}) +} + +func (pl patternList) dictionary() map[string]interface{} { + dict := make(map[string]interface{}) + for _, a := range pl { + dict[a.name] = a.value + } + return dict +} diff --git a/vendor/github.com/docopt/docopt-go/token.go b/vendor/github.com/docopt/docopt-go/token.go new file mode 100644 index 00000000..cc18ec9f --- /dev/null +++ b/vendor/github.com/docopt/docopt-go/token.go @@ -0,0 +1,126 @@ +package docopt + +import ( + "regexp" + "strings" + "unicode" +) + +type tokenList struct { + tokens []string + errorFunc func(string, ...interface{}) error + err errorType +} +type token string + +func newTokenList(source []string, err errorType) *tokenList { + errorFunc := newError + if err == errorUser { + errorFunc = newUserError + } else if err == errorLanguage { + errorFunc = newLanguageError + } + return &tokenList{source, errorFunc, err} +} + +func tokenListFromString(source string) *tokenList { + return newTokenList(strings.Fields(source), errorUser) +} + +func tokenListFromPattern(source string) *tokenList { + p := regexp.MustCompile(`([\[\]\(\)\|]|\.\.\.)`) + source = p.ReplaceAllString(source, ` $1 `) + p = regexp.MustCompile(`\s+|(\S*<.*?>)`) + split := p.Split(source, -1) + match := p.FindAllStringSubmatch(source, -1) + var result []string + l := len(split) + for i := 0; i < l; i++ { + if len(split[i]) > 0 { + result = append(result, split[i]) + } + if i < l-1 && len(match[i][1]) > 0 { + result = append(result, match[i][1]) + } + } + return newTokenList(result, errorLanguage) +} + +func (t *token) eq(s string) bool { + if t == nil { + return false + } + return string(*t) == s +} +func (t *token) match(matchNil bool, tokenStrings ...string) bool { + if t == nil && matchNil { + return true + } else if t == nil && !matchNil { + return false + } + + for _, tok := range tokenStrings { + if tok == string(*t) { + return true + } + } + return false +} +func (t *token) hasPrefix(prefix string) bool { + if t == nil { + return false + } + return strings.HasPrefix(string(*t), prefix) +} +func (t *token) hasSuffix(suffix string) bool { + if t == nil { + return false + } + return strings.HasSuffix(string(*t), suffix) +} +func (t *token) isUpper() bool { + if t == nil { + return false + } + return isStringUppercase(string(*t)) +} +func (t *token) String() string { + if t == nil { + return "" + } + return string(*t) +} + +func (tl *tokenList) current() *token { + if len(tl.tokens) > 0 { + return (*token)(&(tl.tokens[0])) + } + return nil +} + +func (tl *tokenList) length() int { + return len(tl.tokens) +} + +func (tl *tokenList) move() *token { + if len(tl.tokens) > 0 { + t := tl.tokens[0] + tl.tokens = tl.tokens[1:] + return (*token)(&t) + } + return nil +} + +// returns true if all cased characters in the string are uppercase +// and there are there is at least one cased charcter +func isStringUppercase(s string) bool { + if strings.ToUpper(s) != s { + return false + } + for _, c := range []rune(s) { + if unicode.IsUpper(c) { + return true + } + } + return false +} From 14523b836c8640690a3c567413a6368f280f6126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Thu, 12 Dec 2019 21:41:50 +0200 Subject: [PATCH 15/16] Refactor argument parsing to use custom structs --- cf.go | 4 +- cmd/browser.go | 18 +++--- cmd/clone.go | 7 ++- cmd/cmd.go | 149 ++++++++++++++++++++++++++++++------------------- cmd/config.go | 2 +- cmd/gen.go | 7 ++- cmd/list.go | 6 +- cmd/parse.go | 8 +-- cmd/pull.go | 11 ++-- cmd/race.go | 8 +-- cmd/submit.go | 35 +++++++----- cmd/test.go | 6 +- cmd/watch.go | 8 +-- 13 files changed, 154 insertions(+), 115 deletions(-) diff --git a/cf.go b/cf.go index 9b0a0422..40bf1b04 100644 --- a/cf.go +++ b/cf.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "strings" "github.com/fatih/color" @@ -122,8 +123,7 @@ Options: -h --help --version` usage = strings.Replace(usage, `$%version%$`, version, 1) - - args, _ := docopt.Parse(usage, nil, true, fmt.Sprintf("Codeforces Tool (cf) %v", version), false) + args, _ := docopt.ParseArgs(usage, os.Args[1:], fmt.Sprintf("Codeforces Tool (cf) %v", version)) args[`{version}`] = version color.Output = ansi.NewAnsiStdout() configPath, _ = homedir.Expand(configPath) diff --git a/cmd/browser.go b/cmd/browser.go index 0caba738..7d93db36 100644 --- a/cmd/browser.go +++ b/cmd/browser.go @@ -10,12 +10,12 @@ import ( ) // Open command -func Open(args map[string]interface{}) error { - parsedArgs, err := parseArgs(args, map[string]bool{"": true, "": false}) +func Open(args interface{}) error { + parsedArgs, err := parseArgs(args, ParseRequirement{ContestID: true}) if err != nil { return err } - contestID, problemID := parsedArgs[""], parsedArgs[""] + contestID, problemID := parsedArgs.ProblemID, parsedArgs.ProblemID if problemID == "" { return open.Run(client.ToGym(fmt.Sprintf(config.Instance.Host+"/contest/%v", contestID), contestID)) } @@ -23,19 +23,19 @@ func Open(args map[string]interface{}) error { } // Stand command -func Stand(args map[string]interface{}) error { - parsedArgs, err := parseArgs(args, map[string]bool{"": true}) +func Stand(args interface{}) error { + parsedArgs, err := parseArgs(args, ParseRequirement{ContestID: true}) if err != nil { return err } - contestID := parsedArgs[""] + contestID := parsedArgs.ContestID return open.Run(client.ToGym(fmt.Sprintf(config.Instance.Host+"/contest/%v/standings", contestID), contestID)) } // Sid command -func Sid(args map[string]interface{}) error { - parsedArgs, err := parseArgs(args, map[string]bool{"": false, "": false}) - contestID, submissionID := parsedArgs[""], parsedArgs[""] +func Sid(args interface{}) error { + parsedArgs, err := parseArgs(args, ParseRequirement{}) + contestID, submissionID := parsedArgs.ContestID, parsedArgs.SubmissionID cfg := config.Instance cln := client.Instance if submissionID == "" { diff --git a/cmd/clone.go b/cmd/clone.go index d64b4684..88d5cc26 100644 --- a/cmd/clone.go +++ b/cmd/clone.go @@ -8,15 +8,16 @@ import ( ) // Clone command -func Clone(args map[string]interface{}) error { +func Clone(args interface{}) error { currentPath, err := os.Getwd() if err != nil { return err } cfg := config.Instance cln := client.Instance - ac := args["ac"].(bool) - handle := args[""].(string) + parsedArgs, _ := parseArgs(args, ParseRequirement{}) + ac := parsedArgs.Accepted + handle := parsedArgs.Handle if err = cln.Clone(handle, currentPath, ac); err != nil { if err = loginAgain(cfg, cln, err); err == nil { diff --git a/cmd/cmd.go b/cmd/cmd.go index 343245e6..77776d82 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -3,6 +3,7 @@ package cmd import ( "errors" "fmt" + "github.com/docopt/docopt-go" "io/ioutil" "os" "path/filepath" @@ -17,53 +18,91 @@ import ( ) // Eval args -func Eval(args map[string]interface{}) error { - if args["config"].(bool) { - return Config(args) - } else if args["submit"].(bool) { +func Eval(args docopt.Opts) error { + parsed := ParsedArgs{} + args.Bind(&parsed) + if parsed.Config { + return Config() + } else if parsed.Submit { return Submit(args) - } else if args["list"].(bool) { + } else if parsed.List { return List(args) - } else if args["parse"].(bool) { + } else if parsed.Parse { return Parse(args) - } else if args["gen"].(bool) { + } else if parsed.Generate { return Gen(args) - } else if args["test"].(bool) { + } else if parsed.Test { return Test(args) - } else if args["watch"].(bool) { + } else if parsed.Watch { return Watch(args) - } else if args["open"].(bool) { + } else if parsed.Open { return Open(args) - } else if args["stand"].(bool) { + } else if parsed.Standings { return Stand(args) - } else if args["sid"].(bool) { + } else if parsed.Sid { return Sid(args) - } else if args["race"].(bool) { + } else if parsed.Race { return Race(args) - } else if args["pull"].(bool) { + } else if parsed.Pull { return Pull(args) - } else if args["clone"].(bool) { + } else if parsed.Clone { return Clone(args) - } else if args["upgrade"].(bool) { - return Upgrade(args["{version}"].(string)) + } else if parsed.Upgrade { + return Upgrade(parsed.Version) } return nil } -func parseArgs(args map[string]interface{}, required map[string]bool) (map[string]string, error) { - result := make(map[string]string) +type ParseRequirement struct { + ContestID, ProblemID, SubmissionID, Filename, Alias, Username bool +} + +type ParsedArgs struct { + ContestID string `docopt:""` + ProblemID string `docopt:""` + SubmissionID string `docopt:""` + Filename string `docopt:""` + Alias string `docopt:""` + Accepted bool `docopt:"ac"` + All bool `docopt:"all"` + Handle string `docopt:""` + Version string `docopt:"{version}"` + Config bool `docopt:"config"` + Submit bool `docopt:"submit"` + List bool `docopt:"list"` + Parse bool `docopt:"parse"` + Generate bool `docopt:"gen"` + Test bool `docopt:"test"` + Watch bool `docopt:"watch"` + Open bool `docopt:"open"` + Standings bool `docopt:"stand"` + Sid bool `docopt:"sid"` + Race bool `docopt:"race"` + Pull bool `docopt:"pull"` + Clone bool `docopt:"clone"` + Upgrade bool `docopt:"upgrade"` + ContestRootPath string +} + +func parseArgs(args interface{}, required ParseRequirement) (ParsedArgs, error) { + opts, ok := args.(docopt.Opts) + result := ParsedArgs{} + if !ok { + return result, errors.New("args must be docopt.Opts type") + } + opts.Bind(&result) contestID, problemID, lastDir := "", "", "" path, err := os.Getwd() if err != nil { - return nil, err + return result, err } - result["contestRootPath"] = path + result.ContestRootPath = path for { c := filepath.Base(path) if _, err := strconv.Atoi(c); err == nil { contestID, problemID = c, strings.ToLower(lastDir) - if _, ok := args[""].(string); !ok { - result["contestRootPath"] = filepath.Dir(path) + if result.ContestID == "" { + result.ContestRootPath = filepath.Dir(path) } break } @@ -72,47 +111,41 @@ func parseArgs(args map[string]interface{}, required map[string]bool) (map[strin } path, lastDir = filepath.Dir(path), c } - if p, ok := args[""].(string); ok { - problemID = strings.ToLower(p) + if result.ProblemID != "" { + problemID = strings.ToLower(result.ProblemID) } - if c, ok := args[""].(string); ok { - if util.IsUrl(c) { - parsed, err := parseUrl(c) - if err != nil { - return nil, err - } - if value, ok := parsed["contestID"]; ok { - contestID = value - } - if value, ok := parsed["problemID"]; ok { - problemID = strings.ToLower(value) - } - } else if _, err := strconv.Atoi(c); err == nil { - contestID = c + if util.IsUrl(result.ContestID) { + parsed, err := parseUrl(result.ContestID) + if err != nil { + return result, err } - } - if req, ok := required[""]; ok { - result[""] = contestID - if contestID == "" && req { - return nil, errors.New("Unable to find ") + if value, ok := parsed["contestID"]; ok { + contestID = value } - } - if req, ok := required[""]; ok { - result[""] = problemID - if problemID == "" && req { - return nil, errors.New("Unable to find ") + if value, ok := parsed["problemID"]; ok { + problemID = strings.ToLower(value) } + } else if _, err := strconv.Atoi(result.ContestID); err == nil { + contestID = result.ContestID } - for key, req := range required { - if _, ok := result[key]; ok { - continue - } - value, ok := args[key].(string) - if req && !ok { - return nil, errors.New("Unable to find " + key) - } - result[key] = value + result.ContestID = contestID + result.ProblemID = problemID + if required.ContestID && contestID == "" { + return result, errors.New("Unable to find ") + } + if required.ProblemID && problemID == "" { + return result, errors.New("Unable to find ") } + if required.SubmissionID && result.SubmissionID == "" { + return result, errors.New("Unable to find ") + } + if required.Alias && result.Alias == "" { + return result, errors.New("Unable to find ") + } + if required.Filename && result.Filename == "" { + return result, errors.New("Unable to find ") + } + return result, nil } diff --git a/cmd/config.go b/cmd/config.go index d49ec5cb..ac578a7e 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -9,7 +9,7 @@ import ( ) // Config command -func Config(args map[string]interface{}) error { +func Config() error { cfg := config.Instance cln := client.Instance color.Cyan("Configure the tool") diff --git a/cmd/gen.go b/cmd/gen.go index 438e8a6c..bd927351 100644 --- a/cmd/gen.go +++ b/cmd/gen.go @@ -56,16 +56,17 @@ func gen(source, currentPath, ext string) error { } // Gen command -func Gen(args map[string]interface{}) error { +func Gen(args interface{}) error { cfg := config.Instance cln := client.Instance if len(cfg.Template) == 0 { return errors.New("You have to add at least one code template by `cf config`") } - + parsedArgs, _ := parseArgs(args, ParseRequirement{}) + alias := parsedArgs.Alias var path string - if alias, ok := args[""].(string); ok { + if alias != "" { templates := cfg.TemplateByAlias(alias) if len(templates) < 1 { return fmt.Errorf("Cannot find any template with alias %v", alias) diff --git a/cmd/list.go b/cmd/list.go index 7440f998..836d073e 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -14,12 +14,12 @@ import ( ) // List command -func List(args map[string]interface{}) error { - parsedArgs, err := parseArgs(args, map[string]bool{"": true}) +func List(args interface{}) error { + parsedArgs, err := parseArgs(args, ParseRequirement{ContestID: true}) if err != nil { return err } - contestID := parsedArgs[""] + contestID := parsedArgs.ContestID cfg := config.Instance cln := client.Instance problems, err := cln.StatisContest(contestID) diff --git a/cmd/parse.go b/cmd/parse.go index 6d01319e..73b37505 100644 --- a/cmd/parse.go +++ b/cmd/parse.go @@ -11,7 +11,7 @@ import ( "github.com/xalanq/cf-tool/config" ) -func _Parse(contestID string, problemID string, contestRootPath string) error { +func _parse(contestID string, problemID string, contestRootPath string) error { cfg := config.Instance cln := client.Instance source := "" @@ -68,10 +68,10 @@ func _Parse(contestID string, problemID string, contestRootPath string) error { } // Parse command -func Parse(args map[string]interface{}) error { - parsedArgs, err := parseArgs(args, map[string]bool{"": true, "": false}) +func Parse(args interface{}) error { + parsedArgs, err := parseArgs(args, ParseRequirement{ContestID: true, ProblemID: false}) if err != nil { return err } - return _Parse(parsedArgs[""], parsedArgs[""], parsedArgs["contestRootPath"]) + return _parse(parsedArgs.ContestID, parsedArgs.ProblemID, parsedArgs.Filename) } diff --git a/cmd/pull.go b/cmd/pull.go index 39cf9f4a..08db4bfa 100644 --- a/cmd/pull.go +++ b/cmd/pull.go @@ -8,19 +8,18 @@ import ( ) // Pull command -func Pull(args map[string]interface{}) error { +func Pull(args interface{}) error { cfg := config.Instance cln := client.Instance - ac := args["ac"].(bool) var err error work := func() error { - parsedArgs, err := parseArgs(args, map[string]bool{"": true, "": false}) + parsedArgs, err := parseArgs(args, ParseRequirement{ContestID: true}) if err != nil { return err } - contestID, problemID := parsedArgs[""], parsedArgs[""] - path := filepath.Join(parsedArgs["contestRootPath"], contestID, problemID) - return cln.PullContest(contestID, problemID, path, ac) + contestID, problemID := parsedArgs.ContestID, parsedArgs.ProblemID + path := filepath.Join(parsedArgs.ContestRootPath, contestID, problemID) + return cln.PullContest(contestID, problemID, path, parsedArgs.Accepted) } if err = work(); err != nil { if err = loginAgain(cfg, cln, err); err == nil { diff --git a/cmd/race.go b/cmd/race.go index 1ee85069..7b0bd27b 100644 --- a/cmd/race.go +++ b/cmd/race.go @@ -10,12 +10,12 @@ import ( ) // Race command -func Race(args map[string]interface{}) error { - parsedArgs, err := parseArgs(args, map[string]bool{"": true}) +func Race(args interface{}) error { + parsedArgs, err := parseArgs(args, ParseRequirement{ContestID: true}) if err != nil { return err } - contestID := parsedArgs[""] + contestID := parsedArgs.ContestID cfg := config.Instance cln := client.Instance if err = cln.RaceContest(contestID); err != nil { @@ -29,5 +29,5 @@ func Race(args map[string]interface{}) error { time.Sleep(1) open.Run(client.ToGym(fmt.Sprintf(cfg.Host+"/contest/%v", contestID), contestID)) open.Run(client.ToGym(fmt.Sprintf(cfg.Host+"/contest/%v/problems", contestID), contestID)) - return _Parse(contestID, "", parsedArgs["contestRootPath"]) + return _parse(contestID, "", parsedArgs.ContestRootPath) } diff --git a/cmd/submit.go b/cmd/submit.go index b82fe207..38cb0161 100644 --- a/cmd/submit.go +++ b/cmd/submit.go @@ -1,6 +1,8 @@ package cmd import ( + "errors" + "github.com/docopt/docopt-go" "io/ioutil" "os" "strings" @@ -11,7 +13,7 @@ import ( ) // Submit command -func Submit(args map[string]interface{}) error { +func Submit(args interface{}) error { contestID, problemID, userFile, err := parseSubmitArgs(args) if err != nil { return err @@ -48,7 +50,11 @@ func Submit(args map[string]interface{}) error { return err } -func parseSubmitArgs(args map[string]interface{}) (string, string, string, error) { +func parseSubmitArgs(args interface{}) (string, string, string, error) { + opts, ok := args.(docopt.Opts) + if !ok { + return "", "", "", errors.New("args must be docopt.Opts type") + } isFilename := func(str string) bool { if str == "" || util.IsUrl(str) { return false @@ -58,26 +64,25 @@ func parseSubmitArgs(args map[string]interface{}) (string, string, string, error } return false } - var newArgs = make(map[string]interface{}) - for key, value := range args { - newArgs[key] = value - } - if _, ok := args[""].(string); !ok { - if p, ok := args[""].(string); ok { + if _, ok := opts[""].(string); !ok { + if p, ok := opts[""].(string); ok { if isFilename(p) { - newArgs[""] = p - newArgs[""] = nil + opts[""] = p + opts[""] = nil } - } else if c, ok := args[""].(string); ok { + } else if c, ok := opts[""].(string); ok { if isFilename(c) { - newArgs[""] = c - newArgs[""] = nil + opts[""] = c + opts[""] = nil } } } - parsedArgs, err := parseArgs(newArgs, map[string]bool{"": true, "": true, "": false}) + parsedArgs, err := parseArgs(opts, ParseRequirement{ + ContestID: true, + ProblemID: true, + }) if err != nil { return "", "", "", err } - return parsedArgs[""], parsedArgs[""], parsedArgs[""], nil + return parsedArgs.ContestID, parsedArgs.ProblemID, parsedArgs.Filename, nil } diff --git a/cmd/test.go b/cmd/test.go index 147cc3db..019464e4 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -149,7 +149,7 @@ func judge(sampleID, command string) error { } // Test command -func Test(args map[string]interface{}) error { +func Test(args interface{}) error { cfg := config.Instance if len(cfg.Template) == 0 { return errors.New("You have to add at least one code template by `cf config`") @@ -160,8 +160,8 @@ func Test(args map[string]interface{}) error { color.Red("There is no sample data") return nil } - parsedArgs, _ := parseArgs(args, map[string]bool{"": false}) - filename, index, err := getOneCode(parsedArgs[""], cfg.Template) + parsedArgs, _ := parseArgs(args, ParseRequirement{}) + filename, index, err := getOneCode(parsedArgs.Filename, cfg.Template) if err != nil { return err } diff --git a/cmd/watch.go b/cmd/watch.go index fd88eac1..75cecc0a 100644 --- a/cmd/watch.go +++ b/cmd/watch.go @@ -6,16 +6,16 @@ import ( ) // Watch command -func Watch(args map[string]interface{}) error { - parsedArgs, err := parseArgs(args, map[string]bool{"": true, "": false}) +func Watch(args interface{}) error { + parsedArgs, err := parseArgs(args, ParseRequirement{ContestID: true}) if err != nil { return err } - contestID, problemID := parsedArgs[""], parsedArgs[""] + contestID, problemID := parsedArgs.ContestID, parsedArgs.ProblemID cfg := config.Instance cln := client.Instance n := 10 - if args["all"].(bool) { + if parsedArgs.All { n = -1 } _, err = cln.WatchSubmission(contestID, problemID, n, false) From 598969124a1be4c0df1ec5fcd4808781ae682d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nojus=20Gudinavi=C4=8Dius?= Date: Thu, 27 Feb 2020 22:01:50 +0200 Subject: [PATCH 16/16] Fix open command --- cmd/browser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/browser.go b/cmd/browser.go index 7d93db36..dec3ae05 100644 --- a/cmd/browser.go +++ b/cmd/browser.go @@ -15,7 +15,7 @@ func Open(args interface{}) error { if err != nil { return err } - contestID, problemID := parsedArgs.ProblemID, parsedArgs.ProblemID + contestID, problemID := parsedArgs.ContestID, parsedArgs.ProblemID if problemID == "" { return open.Run(client.ToGym(fmt.Sprintf(config.Instance.Host+"/contest/%v", contestID), contestID)) }