From ff5c6e4407d1ac66c08ce20bc91798b56c4ac7ad Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Sun, 1 Mar 2020 14:53:58 +0700 Subject: [PATCH 01/12] More configurable path `cf clone` is incomplete. --- client/clone.go | 3 ++ client/info.go | 9 +++- cmd/args.go | 106 +++++++++++++++++++++++++++++++++++++++++++---- cmd/config.go | 2 +- config/config.go | 15 ++++++- config/misc.go | 4 +- 6 files changed, 125 insertions(+), 14 deletions(-) diff --git a/client/clone.go b/client/clone.go index 7eb769cb..b6930bdb 100644 --- a/client/clone.go +++ b/client/clone.go @@ -155,7 +155,10 @@ func (c *Client) Clone(handle, rootPath string, ac bool) (err error) { testCount := int64(submission["passedTestCount"].(float64)) filename = fmt.Sprintf("%v_%v_%v", submissionID, strings.ToLower(verdict), testCount) } + /* info.RootPath = filepath.Join(rootPath, handle, info.ProblemType) + // NOTE this path scheme is incompatible with other commands. "handle/cf/contest/..." would be compatible, however. + */ URL, _ := info.SubmissionURL(c.host) data := cloneData{URL, filepath.Join(info.Path(), filename), "." + ext} ch <- data diff --git a/client/info.go b/client/info.go index 2a954c71..d24cda49 100644 --- a/client/info.go +++ b/client/info.go @@ -3,7 +3,7 @@ package client import ( "errors" "fmt" - "path/filepath" + //"path/filepath" "strings" ) @@ -22,7 +22,8 @@ type Info struct { GroupID string `json:"group_id"` ProblemID string `json:"problem_id"` SubmissionID string `json:"submission_id"` - RootPath string + PathField string + //RootPath string } // ErrorNeedProblemID error @@ -77,6 +78,9 @@ func (info *Info) Hint() string { // Path path func (info *Info) Path() string { + fmt.Println(info) + return info.PathField + /* path := info.RootPath if info.GroupID != "" { path = filepath.Join(path, info.GroupID) @@ -88,6 +92,7 @@ func (info *Info) Path() string { path = filepath.Join(path, strings.ToLower(info.ProblemID)) } return path + */ } // ProblemSetURL parse problem set url diff --git a/cmd/args.go b/cmd/args.go index 9e9c8b6b..e819df79 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -3,8 +3,10 @@ package cmd import ( "fmt" "os" + "strings" "path/filepath" "regexp" + "errors" "github.com/docopt/docopt-go" "github.com/xalanq/cf-tool/client" @@ -117,8 +119,35 @@ func parseArgs(opts docopt.Opts) error { } info.ContestID = "99999" } - root := cfg.FolderName["root"] - info.RootPath = filepath.Join(path, root) + + info.PathField = "" + for _, value := range cfg.PathSpecifier { + if value[0] == info.ProblemType { + specifier := value[1] + expectedPath := strings.NewReplacer( + "%%", "%", + "%problemID%", info.ContestID, + "%contestID%", info.ProblemID, + "%groupID%", info.GroupID, + ).Replace(specifier) + var components []string = strings.Split(expectedPath, "/") + for length := len(components); length >= 0; length-- { + if strings.HasSuffix(path, filepath.Join(components[:length]...)) { + //info.PathField = filepath.Join(string(path), components[length:]...) + info.PathField = filepath.Join(append([]string{path}, components[length:]...)...) + break + } + } + break + } + } + if info.PathField == "" { + return errors.New("Invalid configuration! Need to specify path specifier for " + info.ProblemType) + } + + /* + //root := cfg.FolderName["root"] + //info.RootPath = filepath.Join(path, root) for { base := filepath.Base(path) if base == root { @@ -130,7 +159,8 @@ func parseArgs(opts docopt.Opts) error { } path = filepath.Dir(path) } - info.RootPath = filepath.Join(info.RootPath, cfg.FolderName[info.ProblemType]) + //info.RootPath = filepath.Join(info.RootPath, cfg.FolderName[info.ProblemType] + */ Args.Info = info // util.DebugJSON(Args) return nil @@ -170,6 +200,7 @@ var ArgRegStr = [...]string{ fmt.Sprintf(`^(?P%v)$`, GroupRegStr), } +/* // ArgTypePathRegStr path var ArgTypePathRegStr = [...]string{ fmt.Sprintf("%v/%v/((?P%v)/((?P%v)/)?)?", "%v", "%v", ContestRegStr, ProblemRegStr), @@ -177,6 +208,7 @@ var ArgTypePathRegStr = [...]string{ fmt.Sprintf("%v/%v/((?P%v)/((?P%v)/((?P%v)/)?)?)?", "%v", "%v", GroupRegStr, ContestRegStr, ProblemRegStr), fmt.Sprintf("%v/%v/((?P%v)/)?", "%v", "%v", ProblemRegStr), } +*/ // ArgType type var ArgType = [...]string{ @@ -217,11 +249,64 @@ func parseArg(arg string) map[string]string { return output } -func parsePath(path string) map[string]string { - path = filepath.ToSlash(path) + "/" - output := make(map[string]string) +var specifierToRegex = strings.NewReplacer( + "%%", "%", + "%problemID%", "(?P" + ProblemRegStr + ")", + "%contestID%", "(?P" + ContestRegStr + ")", + "%groupID%", "(?P" + GroupRegStr + ")", +) + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func parsePath(path string) (output map[string]string) { + //path = filepath.ToSlash(path) + "/" + components := filepath.SplitList(path) + + // output := make(map[string]string) cfg := config.Instance - for k, problemType := range client.ProblemTypes { + for _, value := range cfg.PathSpecifier { + problemType := value[0] + var specifier []string + for _, value := range strings.Split(value[1], "/") { + specifier = append(specifier, specifierToRegex.Replace(regexp.QuoteMeta(value))) + } + // note that both the path separator "/" and the variable separator "%" must not be + // regex meta character for this approach to work + + outer: for length := min(len(specifier), len(components)); length > 0; length-- { + reg := regexp.MustCompile("^" + strings.Join(specifier[:length], "/") + "$") + names := reg.SubexpNames() + output = make(map[string]string) + for i, val := range reg.FindStringSubmatch( + strings.Join(components[len(components)-length:], "/")) { + if names[i] != "" && val != "" { + // (how can val be empty anyway?) + // it's possible to use noncapturing group to avoid having to check this + if existing, ok := output[names[i]]; ok { + if existing != val { + continue outer + } + } else { + output[names[i]] = val + } + } + } + // Full match. + output["problemType"] = problemType + return nil + /* + for index, component := range components[len(components) - length] { + specifier[index] + } + */ + } + + /* reg := regexp.MustCompile(fmt.Sprintf(ArgTypePathRegStr[k], cfg.FolderName["root"], cfg.FolderName[problemType])) names := reg.SubexpNames() for i, val := range reg.FindStringSubmatch(path) { @@ -230,7 +315,10 @@ func parsePath(path string) map[string]string { } output["problemType"] = problemType } + */ } + + /* if (output["problemType"] != "" && output["problemType"] != "group") || output["groupID"] == output["contestID"] || output["groupID"] == fmt.Sprintf("%v%v", output["contestID"], output["problemID"]) { @@ -239,5 +327,7 @@ func parsePath(path string) map[string]string { if output["groupID"] != "" && output["problemType"] == "" { output["problemType"] = "group" } - return output + */ + + return } diff --git a/cmd/config.go b/cmd/config.go index 22133516..497526ed 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -37,7 +37,7 @@ func Config() (err error) { } else if index == 6 { return cfg.SetProxy() } else if index == 7 { - return cfg.SetFolderName() + //return cfg.SetFolderName() } return } diff --git a/config/config.go b/config/config.go index 5d2809b4..5e2b6271 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,7 @@ import ( "path/filepath" "github.com/fatih/color" - "github.com/xalanq/cf-tool/client" + //"github.com/xalanq/cf-tool/client" ) // CodeTemplate config parse code template @@ -28,7 +28,8 @@ type Config struct { GenAfterParse bool `json:"gen_after_parse"` Host string `json:"host"` Proxy string `json:"proxy"` - FolderName map[string]string `json:"folder_name"` + //FolderName map[string]string `json:"folder_name"` + PathSpecifier [][2]string `json:"path_specifier"` path string } @@ -45,6 +46,7 @@ func Init(path string) { if c.Default < 0 || c.Default >= len(c.Template) { c.Default = 0 } + /* if c.FolderName == nil { c.FolderName = map[string]string{} } @@ -56,6 +58,15 @@ func Init(path string) { c.FolderName[problemType] = problemType } } + */ + if c.PathSpecifier == nil { + c.PathSpecifier = [][2]string{ + [2]string{ "contest", "cf/contest/%contestID%/%problemID%" }, + [2]string{ "gym", "cf/gym/%contestID%/%problemID%" }, + [2]string{ "group", "cf/group/%groupID%/%contestID%/%problemID%" }, + [2]string{ "acmsguru", "cf/acmsguru/%problemID%" }, + } + } c.save() Instance = c } diff --git a/config/misc.go b/config/misc.go index 523ea194..2b3a8a24 100644 --- a/config/misc.go +++ b/config/misc.go @@ -5,7 +5,7 @@ import ( "regexp" "github.com/fatih/color" - "github.com/xalanq/cf-tool/client" + //"github.com/xalanq/cf-tool/client" "github.com/xalanq/cf-tool/util" ) @@ -85,6 +85,7 @@ func (c *Config) SetProxy() (err error) { return c.save() } +/* // SetFolderName set folder name func (c *Config) SetFolderName() (err error) { color.Cyan(`Set folders' name`) @@ -101,3 +102,4 @@ func (c *Config) SetFolderName() (err error) { } return c.save() } +*/ From 803f44167df3b2bb0dcf61ad32debf6ec4bd8c32 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Mon, 2 Mar 2020 18:02:55 +0700 Subject: [PATCH 02/12] Bug fix --- cmd/args.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/cmd/args.go b/cmd/args.go index e819df79..6a65f12f 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -265,7 +265,7 @@ func min(a, b int) int { func parsePath(path string) (output map[string]string) { //path = filepath.ToSlash(path) + "/" - components := filepath.SplitList(path) + components := strings.Split(path, string(filepath.Separator)) // output := make(map[string]string) cfg := config.Instance @@ -282,23 +282,24 @@ func parsePath(path string) (output map[string]string) { reg := regexp.MustCompile("^" + strings.Join(specifier[:length], "/") + "$") names := reg.SubexpNames() output = make(map[string]string) - for i, val := range reg.FindStringSubmatch( - strings.Join(components[len(components)-length:], "/")) { - if names[i] != "" && val != "" { - // (how can val be empty anyway?) - // it's possible to use noncapturing group to avoid having to check this - if existing, ok := output[names[i]]; ok { - if existing != val { - continue outer + match := reg.FindStringSubmatch(strings.Join(components[len(components)-length:], "/")) + if match != nil { + for i, val := range match { + if names[i] != "" && val != "" { + // (how can val be empty anyway?) + // it's possible to use noncapturing group to avoid having to check this + if existing, ok := output[names[i]]; ok { + if existing != val { + continue outer + } + } else { + output[names[i]] = val } - } else { - output[names[i]] = val } } + output["problemType"] = problemType + return } - // Full match. - output["problemType"] = problemType - return nil /* for index, component := range components[len(components) - length] { specifier[index] From 9c1fab4d4f8fcaea4f71d2aa9d3b713465c65bb3 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Tue, 3 Mar 2020 11:21:21 +0700 Subject: [PATCH 03/12] Fix another bug (typo) --- cmd/args.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/args.go b/cmd/args.go index 6a65f12f..b8ee4d5f 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -126,8 +126,8 @@ func parseArgs(opts docopt.Opts) error { specifier := value[1] expectedPath := strings.NewReplacer( "%%", "%", - "%problemID%", info.ContestID, - "%contestID%", info.ProblemID, + "%contestID%", info.ContestID, + "%problemID%", info.ProblemID, "%groupID%", info.GroupID, ).Replace(specifier) var components []string = strings.Split(expectedPath, "/") From 58eef176ed58f037e868802f05884d7dcce086a8 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Tue, 3 Mar 2020 11:46:50 +0700 Subject: [PATCH 04/12] Use a struct for path specifier instead of array Note that this change is config-incompatible with the previous commit, so it may be necessary to hand-edit the config file. --- cmd/args.go | 10 ++++------ config/config.go | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/cmd/args.go b/cmd/args.go index b8ee4d5f..94e2a32c 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -122,14 +122,13 @@ func parseArgs(opts docopt.Opts) error { info.PathField = "" for _, value := range cfg.PathSpecifier { - if value[0] == info.ProblemType { - specifier := value[1] + if value.Type == info.ProblemType { expectedPath := strings.NewReplacer( "%%", "%", "%contestID%", info.ContestID, "%problemID%", info.ProblemID, "%groupID%", info.GroupID, - ).Replace(specifier) + ).Replace(value.Pattern) var components []string = strings.Split(expectedPath, "/") for length := len(components); length >= 0; length-- { if strings.HasSuffix(path, filepath.Join(components[:length]...)) { @@ -270,9 +269,8 @@ func parsePath(path string) (output map[string]string) { // output := make(map[string]string) cfg := config.Instance for _, value := range cfg.PathSpecifier { - problemType := value[0] var specifier []string - for _, value := range strings.Split(value[1], "/") { + for _, value := range strings.Split(value.Pattern, "/") { specifier = append(specifier, specifierToRegex.Replace(regexp.QuoteMeta(value))) } // note that both the path separator "/" and the variable separator "%" must not be @@ -297,7 +295,7 @@ func parsePath(path string) (output map[string]string) { } } } - output["problemType"] = problemType + output["problemType"] = value.Type return } /* diff --git a/config/config.go b/config/config.go index 5e2b6271..8c925ac3 100644 --- a/config/config.go +++ b/config/config.go @@ -21,6 +21,12 @@ type CodeTemplate struct { AfterScript string `json:"after_script"` } +// PathSpecifier path pattern for some problem type +type PathSpecifier struct { + Type string `json:"type"` // must be an element of ProblemTypes + Pattern string `json:"pattern"` +} + // Config load and save configuration type Config struct { Template []CodeTemplate `json:"template"` @@ -28,8 +34,7 @@ type Config struct { GenAfterParse bool `json:"gen_after_parse"` Host string `json:"host"` Proxy string `json:"proxy"` - //FolderName map[string]string `json:"folder_name"` - PathSpecifier [][2]string `json:"path_specifier"` + PathSpecifier []PathSpecifier `json:"path_specifier"` path string } @@ -60,11 +65,11 @@ func Init(path string) { } */ if c.PathSpecifier == nil { - c.PathSpecifier = [][2]string{ - [2]string{ "contest", "cf/contest/%contestID%/%problemID%" }, - [2]string{ "gym", "cf/gym/%contestID%/%problemID%" }, - [2]string{ "group", "cf/group/%groupID%/%contestID%/%problemID%" }, - [2]string{ "acmsguru", "cf/acmsguru/%problemID%" }, + c.PathSpecifier = []PathSpecifier{ + PathSpecifier{ "contest", "cf/contest/%contestID%/%problemID%" }, + PathSpecifier{ "gym", "cf/gym/%contestID%/%problemID%" }, + PathSpecifier{ "group", "cf/group/%groupID%/%contestID%/%problemID%" }, + PathSpecifier{ "acmsguru", "cf/acmsguru/%problemID%" }, } } c.save() From cc5c317218ced64366b3a7bc80b0eae745a09185 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Tue, 3 Mar 2020 13:39:24 +0700 Subject: [PATCH 05/12] Refactor code: merge ArgRegStr and ArgType to make code more readable --- cmd/args.go | 77 ++++++++++++++++++----------------------------------- 1 file changed, 26 insertions(+), 51 deletions(-) diff --git a/cmd/args.go b/cmd/args.go index 94e2a32c..864141bb 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -171,7 +171,7 @@ const ProblemRegStr = `\w+` // StrictProblemRegStr strict problem const StrictProblemRegStr = `[a-zA-Z]+\d*` -// ContestRegStr contest +// ContestRegStr regex to match a contest or gym ID const ContestRegStr = `\d+` // GroupRegStr group @@ -180,65 +180,40 @@ const GroupRegStr = `\w{10}` // SubmissionRegStr submission const SubmissionRegStr = `\d+` -// ArgRegStr for parsing arg -var ArgRegStr = [...]string{ - `^[cC][oO][nN][tT][eE][sS][tT][sS]?$`, - `^[gG][yY][mM][sS]?$`, - `^[gG][rR][oO][uU][pP][sS]?$`, - `^[aA][cC][mM][sS][gG][uU][rR][uU]$`, - fmt.Sprintf(`/contest/(?P%v)(/problem/(?P%v))?`, ContestRegStr, ProblemRegStr), - fmt.Sprintf(`/gym/(?P%v)(/problem/(?P%v))?`, ContestRegStr, ProblemRegStr), - fmt.Sprintf(`/problemset/problem/(?P%v)/(?P%v)`, ContestRegStr, ProblemRegStr), - fmt.Sprintf(`/group/(?P%v)(/contest/(?P%v)(/problem/(?P%v))?)?`, GroupRegStr, ContestRegStr, ProblemRegStr), - fmt.Sprintf(`/problemsets/acmsguru/problem/(?P%v)/(?P%v)`, ContestRegStr, ProblemRegStr), - fmt.Sprintf(`/problemsets/acmsguru/submission/(?P%v)/(?P%v)`, ContestRegStr, SubmissionRegStr), - fmt.Sprintf(`/submission/(?P%v)`, SubmissionRegStr), - fmt.Sprintf(`^(?P%v)(?P%v)$`, ContestRegStr, StrictProblemRegStr), - fmt.Sprintf(`^(?P%v)$`, ContestRegStr), - fmt.Sprintf(`^(?P%v)$`, StrictProblemRegStr), - fmt.Sprintf(`^(?P%v)$`, GroupRegStr), -} - -/* -// ArgTypePathRegStr path -var ArgTypePathRegStr = [...]string{ - fmt.Sprintf("%v/%v/((?P%v)/((?P%v)/)?)?", "%v", "%v", ContestRegStr, ProblemRegStr), - fmt.Sprintf("%v/%v/((?P%v)/((?P%v)/)?)?", "%v", "%v", ContestRegStr, ProblemRegStr), - fmt.Sprintf("%v/%v/((?P%v)/((?P%v)/((?P%v)/)?)?)?", "%v", "%v", GroupRegStr, ContestRegStr, ProblemRegStr), - fmt.Sprintf("%v/%v/((?P%v)/)?", "%v", "%v", ProblemRegStr), +type pattern struct { + ProblemType string + Regex regexp.Regexp } -*/ -// ArgType type -var ArgType = [...]string{ - "contest", - "gym", - "group", - "acmsguru", - "contest", - "gym", - "contest", - "group", - "acmsguru", - "acmsguru", - "", - "", - "", - "", - "", +// ArgRegStr for parsing arg +var ArgRegStr = [...]pattern{ + pattern{"contest", *regexp.MustCompile(`^[cC][oO][nN][tT][eE][sS][tT][sS]?$`)}, + pattern{"gym", *regexp.MustCompile(`^[gG][yY][mM][sS]?$`)}, + pattern{"group", *regexp.MustCompile(`^[gG][rR][oO][uU][pP][sS]?$`)}, + pattern{"acmsguru", *regexp.MustCompile(`^[aA][cC][mM][sS][gG][uU][rR][uU]$`)}, + pattern{"contest", *regexp.MustCompile(fmt.Sprintf(`/contest/(?P%v)(/problem/(?P%v))?`, ContestRegStr, ProblemRegStr))}, + pattern{"gym", *regexp.MustCompile(fmt.Sprintf(`/gym/(?P%v)(/problem/(?P%v))?`, ContestRegStr, ProblemRegStr))}, + pattern{"contest", *regexp.MustCompile(fmt.Sprintf(`/problemset/problem/(?P%v)/(?P%v)`, ContestRegStr, ProblemRegStr))}, + pattern{"group", *regexp.MustCompile(fmt.Sprintf(`/group/(?P%v)(/contest/(?P%v)(/problem/(?P%v))?)?`, GroupRegStr, ContestRegStr, ProblemRegStr))}, + pattern{"acmsguru", *regexp.MustCompile(fmt.Sprintf(`/problemsets/acmsguru/problem/(?P%v)/(?P%v)`, ContestRegStr, ProblemRegStr))}, + pattern{"acmsguru", *regexp.MustCompile(fmt.Sprintf(`/problemsets/acmsguru/submission/(?P%v)/(?P%v)`, ContestRegStr, SubmissionRegStr))}, + pattern{"", *regexp.MustCompile(fmt.Sprintf(`/submission/(?P%v)`, SubmissionRegStr))}, + pattern{"", *regexp.MustCompile(fmt.Sprintf(`^(?P%v)(?P%v)$`, ContestRegStr, StrictProblemRegStr))}, + pattern{"", *regexp.MustCompile(fmt.Sprintf(`^(?P%v)$`, ContestRegStr))}, + pattern{"", *regexp.MustCompile(fmt.Sprintf(`^(?P%v)$`, StrictProblemRegStr))}, + pattern{"", *regexp.MustCompile(fmt.Sprintf(`^(?P%v)$`, GroupRegStr))}, } func parseArg(arg string) map[string]string { output := make(map[string]string) - for k, regStr := range ArgRegStr { - reg := regexp.MustCompile(regStr) - names := reg.SubexpNames() - for i, val := range reg.FindStringSubmatch(arg) { + for k, pattern := range ArgRegStr { + names := pattern.Regex.SubexpNames() + for i, val := range pattern.Regex.FindStringSubmatch(arg) { if names[i] != "" && val != "" { output[names[i]] = val } - if ArgType[k] != "" { - output["problemType"] = ArgType[k] + if pattern.ProblemType != "" { + output["problemType"] = pattern.ProblemType if k < 4 { return output } From e851812ca071faac5bca3d33338a0524c72e0e51 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Tue, 3 Mar 2020 14:13:07 +0700 Subject: [PATCH 06/12] Better detection of problem type Path-based detection is not completely reliable because the user may set patterns arbitrarily. --- cmd/args.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/args.go b/cmd/args.go index 864141bb..7c1ea3b0 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -91,6 +91,13 @@ func parseArgs(opts docopt.Opts) error { info.SubmissionID = value } } + if info.ProblemType == "" || info.ProblemType == "contest" || info.ProblemType == "gym" { + if len(info.ContestID) < 6 { + info.ProblemType = "contest" + } else { + info.ProblemType = "gym" + } + } if info.ProblemType == "" { parsed := parsePath(path) if value, ok := parsed["problemType"]; ok { @@ -106,13 +113,6 @@ func parseArgs(opts docopt.Opts) error { info.ProblemID = value } } - if info.ProblemType == "" || info.ProblemType == "contest" { - if len(info.ContestID) < 6 { - info.ProblemType = "contest" - } else { - info.ProblemType = "gym" - } - } if info.ProblemType == "acmsguru" { if info.ContestID != "99999" && info.ContestID != "" { info.ProblemID = info.ContestID @@ -201,7 +201,7 @@ var ArgRegStr = [...]pattern{ pattern{"", *regexp.MustCompile(fmt.Sprintf(`^(?P%v)(?P%v)$`, ContestRegStr, StrictProblemRegStr))}, pattern{"", *regexp.MustCompile(fmt.Sprintf(`^(?P%v)$`, ContestRegStr))}, pattern{"", *regexp.MustCompile(fmt.Sprintf(`^(?P%v)$`, StrictProblemRegStr))}, - pattern{"", *regexp.MustCompile(fmt.Sprintf(`^(?P%v)$`, GroupRegStr))}, + pattern{"group", *regexp.MustCompile(fmt.Sprintf(`^(?P%v)$`, GroupRegStr))}, } func parseArg(arg string) map[string]string { From 7a41feffd3addb2caa61f5f004370958f23a2ff3 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 4 Mar 2020 14:55:47 +0700 Subject: [PATCH 07/12] Bug fix for problem type detection --- cmd/args.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cmd/args.go b/cmd/args.go index 7c1ea3b0..d23eb307 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -91,12 +91,8 @@ func parseArgs(opts docopt.Opts) error { info.SubmissionID = value } } - if info.ProblemType == "" || info.ProblemType == "contest" || info.ProblemType == "gym" { - if len(info.ContestID) < 6 { - info.ProblemType = "contest" - } else { - info.ProblemType = "gym" - } + if info.ContestID != "" && len(info.ContestID) < 6 { + info.ProblemType = "contest" } if info.ProblemType == "" { parsed := parsePath(path) @@ -113,6 +109,13 @@ func parseArgs(opts docopt.Opts) error { info.ProblemID = value } } + if info.ProblemType == "" || info.ProblemType == "contest" || info.ProblemType == "gym" { + if len(info.ContestID) < 6 { + info.ProblemType = "contest" + } else { + info.ProblemType = "gym" + } + } if info.ProblemType == "acmsguru" { if info.ContestID != "99999" && info.ContestID != "" { info.ProblemID = info.ContestID From 5f55708a25560e65e73507ef009f9c1e16c950fb Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 4 Mar 2020 14:17:28 +0700 Subject: [PATCH 08/12] Move Langs and LangsExt from client to util to avoid circular dependency --- client/clone.go | 2 +- client/langs.go | 103 --------------------------------------------- client/pull.go | 2 +- cmd/cmd.go | 2 +- config/template.go | 5 +-- util/util.go | 102 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 109 deletions(-) delete mode 100644 client/langs.go diff --git a/client/clone.go b/client/clone.go index b6930bdb..275a6527 100644 --- a/client/clone.go +++ b/client/clone.go @@ -142,7 +142,7 @@ func (c *Client) Clone(handle, rootPath string, ac bool) (err error) { mu.Unlock() return } - ext, ok := LangsExt[lang] + ext, ok := util.LangsExt[lang] if !ok { mu.Lock() count++ diff --git a/client/langs.go b/client/langs.go deleted file mode 100644 index b09c69fd..00000000 --- a/client/langs.go +++ /dev/null @@ -1,103 +0,0 @@ -package client - -// Langs generated by -// ^[\s\S]*?value="(.+?)"[\s\S]*?>([\s\S]+?)<[\s\S]*?$ -// "\1": "\2", -var Langs = map[string]string{ - "43": "GNU GCC C11 5.1.0", - "52": "Clang++17 Diagnostics", - "42": "GNU G++11 5.1.0", - "50": "GNU G++14 6.4.0", - "54": "GNU G++17 7.3.0", - "2": "Microsoft Visual C++ 2010", - "59": "Microsoft Visual C++ 2017", - "9": "C# Mono 5.18", - "28": "D DMD32 v2.086.0", - "32": "Go 1.12.6", - "12": "Haskell GHC 8.6.3", - "60": "Java 11.0.5", - "36": "Java 1.8.0_162", - "48": "Kotlin 1.3.10", - "19": "OCaml 4.02.1", - "3": "Delphi 7", - "4": "Free Pascal 3.0.2", - "51": "PascalABC.NET 3.4.2", - "13": "Perl 5.20.1", - "6": "PHP 7.2.13", - "7": "Python 2.7.15", - "31": "Python 3.7.2", - "40": "PyPy 2.7 (7.2.0)", - "41": "PyPy 3.6 (7.2.0)", - "8": "Ruby 2.0.0p645", - "49": "Rust 1.35.0", - "20": "Scala 2.12.8", - "34": "JavaScript V8 4.8.0", - "55": "Node.js 9.4.0", - "14": "ActiveTcl 8.5", - "15": "Io-2008-01-07 (Win32)", - "17": "Pike 7.8", - "18": "Befunge", - "22": "OpenCobol 1.0", - "25": "Factor", - "26": "Secret_171", - "27": "Roco", - "33": "Ada GNAT 4", - "38": "Mysterious Language", - "39": "FALSE", - "44": "Picat 0.9", - "45": "GNU C++11 5 ZIP", - "46": "Java 8 ZIP", - "47": "J", - "56": "Microsoft Q#", -} - -// LangsExt language's ext -var LangsExt = map[string]string{ - "GNU C11": "c", - "Clang++17 Diagnostics": "cpp", - "GNU C++0x": "cpp", - "GNU C++": "cpp", - "GNU C++11": "cpp", - "GNU C++14": "cpp", - "GNU C++17": "cpp", - "MS C++": "cpp", - "MS C++ 2017": "cpp", - "Mono C#": "cs", - "D": "d", - "Go": "go", - "Haskell": "hs", - "Kotlin": "kt", - "Ocaml": "ml", - "Delphi": "pas", - "FPC": "pas", - "PascalABC.NET": "pas", - "Perl": "pl", - "PHP": "php", - "Python 2": "py", - "Python 3": "py", - "PyPy 2": "py", - "PyPy 3": "py", - "Ruby": "rb", - "Rust": "rs", - "JavaScript": "js", - "Node.js": "js", - "Q#": "qs", - "Java": "java", - "Java 6": "java", - "Java 7": "java", - "Java 8": "java", - "Java 9": "java", - "Java 10": "java", - "Java 11": "java", - "Tcl": "tcl", - "F#": "fs", - "Befunge": "bf", - "Pike": "pike", - "Io": "io", - "Factor": "factor", - "Cobol": "cbl", - "Secret_171": "secret_171", - "Ada": "adb", - "FALSE": "f", - "": "txt", -} diff --git a/client/pull.go b/client/pull.go index d0e3e924..b1ac14a8 100644 --- a/client/pull.go +++ b/client/pull.go @@ -101,7 +101,7 @@ func (c *Client) Pull(info Info, rootPath string, ac bool) (err error) { if ac && !(strings.Contains(submission.status, "Accepted") || strings.Contains(submission.status, "Pretests passed")) { continue } - ext, ok := LangsExt[submission.lang] + ext, ok := util.LangsExt[submission.lang] if !ok { continue } diff --git a/cmd/cmd.go b/cmd/cmd.go index 5dd29c92..351364d0 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -145,7 +145,7 @@ func getOneCode(filename string, templates []config.CodeTemplate) (name string, if len(codes[0].Index) > 1 { color.Cyan("There are multiple languages match the file.") for i, idx := range codes[0].Index { - fmt.Printf("%3v: %v\n", i, client.Langs[templates[idx].Lang]) + fmt.Printf("%3v: %v\n", i, util.Langs[templates[idx].Lang]) } i := util.ChooseIndex(len(codes[0].Index)) codes[0].Index[0] = codes[0].Index[i] diff --git a/config/template.go b/config/template.go index d5a53b56..b3b4a6c7 100644 --- a/config/template.go +++ b/config/template.go @@ -10,7 +10,6 @@ import ( "github.com/fatih/color" ansi "github.com/k0kubun/go-ansi" homedir "github.com/mitchellh/go-homedir" - "github.com/xalanq/cf-tool/client" "github.com/xalanq/cf-tool/util" ) @@ -22,7 +21,7 @@ func (c *Config) AddTemplate() (err error) { K, V string } langs := []kv{} - for k, v := range client.Langs { + for k, v := range util.Langs { langs = append(langs, kv{k, v}) } sort.Slice(langs, func(i, j int) bool { return langs[i].V < langs[j].V }) @@ -33,7 +32,7 @@ func (c *Config) AddTemplate() (err error) { lang := "" for { lang = util.ScanlineTrim() - if val, ok := client.Langs[lang]; ok { + if val, ok := util.Langs[lang]; ok { color.Green(val) break } diff --git a/util/util.go b/util/util.go index e8e0210f..7688b941 100644 --- a/util/util.go +++ b/util/util.go @@ -19,6 +19,108 @@ import ( // CHA map const CHA = "abcdefghijklmnopqrstuvwxyz0123456789" +// Langs generated by +// ^[\s\S]*?value="(.+?)"[\s\S]*?>([\s\S]+?)<[\s\S]*?$ +// "\1": "\2", +var Langs = map[string]string{ + "43": "GNU GCC C11 5.1.0", + "52": "Clang++17 Diagnostics", + "42": "GNU G++11 5.1.0", + "50": "GNU G++14 6.4.0", + "54": "GNU G++17 7.3.0", + "2": "Microsoft Visual C++ 2010", + "59": "Microsoft Visual C++ 2017", + "9": "C# Mono 5.18", + "28": "D DMD32 v2.086.0", + "32": "Go 1.12.6", + "12": "Haskell GHC 8.6.3", + "60": "Java 11.0.5", + "36": "Java 1.8.0_162", + "48": "Kotlin 1.3.10", + "19": "OCaml 4.02.1", + "3": "Delphi 7", + "4": "Free Pascal 3.0.2", + "51": "PascalABC.NET 3.4.2", + "13": "Perl 5.20.1", + "6": "PHP 7.2.13", + "7": "Python 2.7.15", + "31": "Python 3.7.2", + "40": "PyPy 2.7 (7.2.0)", + "41": "PyPy 3.6 (7.2.0)", + "8": "Ruby 2.0.0p645", + "49": "Rust 1.35.0", + "20": "Scala 2.12.8", + "34": "JavaScript V8 4.8.0", + "55": "Node.js 9.4.0", + "14": "ActiveTcl 8.5", + "15": "Io-2008-01-07 (Win32)", + "17": "Pike 7.8", + "18": "Befunge", + "22": "OpenCobol 1.0", + "25": "Factor", + "26": "Secret_171", + "27": "Roco", + "33": "Ada GNAT 4", + "38": "Mysterious Language", + "39": "FALSE", + "44": "Picat 0.9", + "45": "GNU C++11 5 ZIP", + "46": "Java 8 ZIP", + "47": "J", + "56": "Microsoft Q#", +} + +// LangsExt language's ext +var LangsExt = map[string]string{ + "GNU C11": "c", + "Clang++17 Diagnostics": "cpp", + "GNU C++0x": "cpp", + "GNU C++": "cpp", + "GNU C++11": "cpp", + "GNU C++14": "cpp", + "GNU C++17": "cpp", + "MS C++": "cpp", + "MS C++ 2017": "cpp", + "Mono C#": "cs", + "D": "d", + "Go": "go", + "Haskell": "hs", + "Kotlin": "kt", + "Ocaml": "ml", + "Delphi": "pas", + "FPC": "pas", + "PascalABC.NET": "pas", + "Perl": "pl", + "PHP": "php", + "Python 2": "py", + "Python 3": "py", + "PyPy 2": "py", + "PyPy 3": "py", + "Ruby": "rb", + "Rust": "rs", + "JavaScript": "js", + "Node.js": "js", + "Q#": "qs", + "Java": "java", + "Java 6": "java", + "Java 7": "java", + "Java 8": "java", + "Java 9": "java", + "Java 10": "java", + "Java 11": "java", + "Tcl": "tcl", + "F#": "fs", + "Befunge": "bf", + "Pike": "pike", + "Io": "io", + "Factor": "factor", + "Cobol": "cbl", + "Secret_171": "secret_171", + "Ada": "adb", + "FALSE": "f", + "": "txt", +} + // RandString n is the length. a-z 0-9 func RandString(n int) string { b := make([]byte, n) From 611bf774d816f23d830565ce83552fa672235fae Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 4 Mar 2020 15:02:34 +0700 Subject: [PATCH 09/12] Raise error on invalid specifier --- cmd/args.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/args.go b/cmd/args.go index d23eb307..239cf3ba 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -60,6 +60,9 @@ func parseArgs(opts docopt.Opts) error { info := client.Info{} for _, arg := range Args.Specifier { parsed := parseArg(arg) + if len(parsed) == 0 { + return fmt.Errorf("Invalid specifier: %v", arg) + } if value, ok := parsed["problemType"]; ok { if info.ProblemType != "" && info.ProblemType != value { return fmt.Errorf("Problem Type conflicts: %v %v", info.ProblemType, value) From 8dbdb93dc1bd892c2e97777ca2afbb1df05af7e1 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 4 Mar 2020 14:44:40 +0700 Subject: [PATCH 10/12] Fix cf parse --- client/info.go | 59 ++++++++++++++++++++++++++++++++++++------------- client/parse.go | 5 +++-- cmd/args.go | 26 ---------------------- 3 files changed, 47 insertions(+), 43 deletions(-) diff --git a/client/info.go b/client/info.go index d24cda49..21671def 100644 --- a/client/info.go +++ b/client/info.go @@ -3,8 +3,11 @@ package client import ( "errors" "fmt" - //"path/filepath" + "os" + "path/filepath" "strings" + + "github.com/xalanq/cf-tool/config" ) // ProblemTypes problem types @@ -22,7 +25,6 @@ type Info struct { GroupID string `json:"group_id"` ProblemID string `json:"problem_id"` SubmissionID string `json:"submission_id"` - PathField string //RootPath string } @@ -76,23 +78,50 @@ func (info *Info) Hint() string { return text } -// Path path -func (info *Info) Path() string { - fmt.Println(info) - return info.PathField - /* - path := info.RootPath - if info.GroupID != "" { - path = filepath.Join(path, info.GroupID) +// PathMayError get directory for problem, check for configuration error +func (info *Info) PathMayError() (path string, err error) { + // this function must recompute the result every time it's called, + // because `Parse` is implemented by modifying the info struct and recompute the path + cfg := config.Instance + + currentDirectory, err := os.Getwd() + if err != nil { return } + + path = "" + if info.ProblemID == "" { + panic("Internal error: cannot get problem path from incomplete info") } - if info.ProblemType != "acmsguru" && info.ContestID != "" { - path = filepath.Join(path, info.ContestID) + for _, value := range cfg.PathSpecifier { + if value.Type == info.ProblemType { + expectedPath := strings.NewReplacer( + "%%", "%", + "%contestID%", info.ContestID, + "%problemID%", info.ProblemID, + "%groupID%", info.GroupID, + ).Replace(value.Pattern) + components := strings.Split(expectedPath, "/") + for length := len(components); length >= 0; length-- { + if strings.HasSuffix(currentDirectory, filepath.Join(components[:length]...)) { + path = filepath.Join(append([]string {currentDirectory}, components[length:]...)...) + break + } + } + break + } } - if info.ProblemID != "" { - path = filepath.Join(path, strings.ToLower(info.ProblemID)) + if path == "" { + return "", errors.New("Invalid configuration! Need to specify path specifier for " + info.ProblemType) + } + return +} + +// Path get directory for problem, panic if the configuration is incorrect +func (info *Info) Path() string { + path, err := info.PathMayError() + if err != nil { + panic(err) } return path - */ } // ProblemSetURL parse problem set url diff --git a/client/parse.go b/client/parse.go index 382b7345..7dc52edf 100644 --- a/client/parse.go +++ b/client/parse.go @@ -98,7 +98,7 @@ func (c *Client) Parse(info Info) (problems []string, paths []string, err error) if err != nil { return } - info.ProblemID = "" + info.ProblemID = "" if problemID == "" { statics, err := c.Statis(info) if err != nil { @@ -119,7 +119,8 @@ func (c *Client) Parse(info Info) (problems []string, paths []string, err error) mu := sync.Mutex{} paths = make([]string, len(problems)) for i, problemID := range problems { - paths[i] = filepath.Join(contestPath, strings.ToLower(problemID)) + info.ProblemID = strings.ToLower(problemID) + paths[i] = info.Path() go func(problemID, path string) { defer wg.Done() mu.Lock() diff --git a/cmd/args.go b/cmd/args.go index 239cf3ba..7baf3d85 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -6,7 +6,6 @@ import ( "strings" "path/filepath" "regexp" - "errors" "github.com/docopt/docopt-go" "github.com/xalanq/cf-tool/client" @@ -43,7 +42,6 @@ type ParsedArgs struct { var Args *ParsedArgs func parseArgs(opts docopt.Opts) error { - cfg := config.Instance cln := client.Instance path, err := os.Getwd() if err != nil { @@ -126,30 +124,6 @@ func parseArgs(opts docopt.Opts) error { info.ContestID = "99999" } - info.PathField = "" - for _, value := range cfg.PathSpecifier { - if value.Type == info.ProblemType { - expectedPath := strings.NewReplacer( - "%%", "%", - "%contestID%", info.ContestID, - "%problemID%", info.ProblemID, - "%groupID%", info.GroupID, - ).Replace(value.Pattern) - var components []string = strings.Split(expectedPath, "/") - for length := len(components); length >= 0; length-- { - if strings.HasSuffix(path, filepath.Join(components[:length]...)) { - //info.PathField = filepath.Join(string(path), components[length:]...) - info.PathField = filepath.Join(append([]string{path}, components[length:]...)...) - break - } - } - break - } - } - if info.PathField == "" { - return errors.New("Invalid configuration! Need to specify path specifier for " + info.ProblemType) - } - /* //root := cfg.FolderName["root"] //info.RootPath = filepath.Join(path, root) From 9743ac69e66244f450fc93c9133a2ab7e02e7065 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 6 Mar 2020 09:56:26 +0700 Subject: [PATCH 11/12] cf clone should put all files in handle/ folder --- client/clone.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client/clone.go b/client/clone.go index 275a6527..1bd0bc91 100644 --- a/client/clone.go +++ b/client/clone.go @@ -5,6 +5,7 @@ import ( "path/filepath" "strings" "sync" + "os" "time" "github.com/fatih/color" @@ -110,6 +111,10 @@ func (c *Client) Clone(handle, rootPath string, ac bool) (err error) { } }() } + + os.MkdirAll(handle, os.ModePerm) + os.Chdir(handle) + for _, _submission := range submissions { func() { defer func() { @@ -155,10 +160,7 @@ func (c *Client) Clone(handle, rootPath string, ac bool) (err error) { testCount := int64(submission["passedTestCount"].(float64)) filename = fmt.Sprintf("%v_%v_%v", submissionID, strings.ToLower(verdict), testCount) } - /* - info.RootPath = filepath.Join(rootPath, handle, info.ProblemType) - // NOTE this path scheme is incompatible with other commands. "handle/cf/contest/..." would be compatible, however. - */ + URL, _ := info.SubmissionURL(c.host) data := cloneData{URL, filepath.Join(info.Path(), filename), "." + ext} ch <- data From afb2c3ea9654fe1f381d3c5a5a57fe36fd8e549d Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 6 Mar 2020 10:00:50 +0700 Subject: [PATCH 12/12] Comment cleanup --- client/info.go | 1 - cmd/args.go | 42 ------------------------------------------ config/config.go | 14 -------------- config/misc.go | 1 - 4 files changed, 58 deletions(-) diff --git a/client/info.go b/client/info.go index 21671def..2fdcceb6 100644 --- a/client/info.go +++ b/client/info.go @@ -25,7 +25,6 @@ type Info struct { GroupID string `json:"group_id"` ProblemID string `json:"problem_id"` SubmissionID string `json:"submission_id"` - //RootPath string } // ErrorNeedProblemID error diff --git a/cmd/args.go b/cmd/args.go index 7baf3d85..2f025fdb 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -124,22 +124,6 @@ func parseArgs(opts docopt.Opts) error { info.ContestID = "99999" } - /* - //root := cfg.FolderName["root"] - //info.RootPath = filepath.Join(path, root) - for { - base := filepath.Base(path) - if base == root { - info.RootPath = path - break - } - if filepath.Dir(path) == path { - break - } - path = filepath.Dir(path) - } - //info.RootPath = filepath.Join(info.RootPath, cfg.FolderName[info.ProblemType] - */ Args.Info = info // util.DebugJSON(Args) return nil @@ -253,35 +237,9 @@ func parsePath(path string) (output map[string]string) { output["problemType"] = value.Type return } - /* - for index, component := range components[len(components) - length] { - specifier[index] - } - */ } - /* - reg := regexp.MustCompile(fmt.Sprintf(ArgTypePathRegStr[k], cfg.FolderName["root"], cfg.FolderName[problemType])) - names := reg.SubexpNames() - for i, val := range reg.FindStringSubmatch(path) { - if names[i] != "" && val != "" { - output[names[i]] = val - } - output["problemType"] = problemType - } - */ - } - - /* - if (output["problemType"] != "" && output["problemType"] != "group") || - output["groupID"] == output["contestID"] || - output["groupID"] == fmt.Sprintf("%v%v", output["contestID"], output["problemID"]) { - output["groupID"] = "" - } - if output["groupID"] != "" && output["problemType"] == "" { - output["problemType"] = "group" } - */ return } diff --git a/config/config.go b/config/config.go index 8c925ac3..07978948 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,6 @@ import ( "path/filepath" "github.com/fatih/color" - //"github.com/xalanq/cf-tool/client" ) // CodeTemplate config parse code template @@ -51,19 +50,6 @@ func Init(path string) { if c.Default < 0 || c.Default >= len(c.Template) { c.Default = 0 } - /* - if c.FolderName == nil { - c.FolderName = map[string]string{} - } - if _, ok := c.FolderName["root"]; !ok { - c.FolderName["root"] = "cf" - } - for _, problemType := range client.ProblemTypes { - if _, ok := c.FolderName[problemType]; !ok { - c.FolderName[problemType] = problemType - } - } - */ if c.PathSpecifier == nil { c.PathSpecifier = []PathSpecifier{ PathSpecifier{ "contest", "cf/contest/%contestID%/%problemID%" }, diff --git a/config/misc.go b/config/misc.go index 2b3a8a24..f305f999 100644 --- a/config/misc.go +++ b/config/misc.go @@ -5,7 +5,6 @@ import ( "regexp" "github.com/fatih/color" - //"github.com/xalanq/cf-tool/client" "github.com/xalanq/cf-tool/util" )