diff --git a/client/clone.go b/client/clone.go index 7eb769cb..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() { @@ -142,7 +147,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++ @@ -155,7 +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) + 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..2fdcceb6 100644 --- a/client/info.go +++ b/client/info.go @@ -3,8 +3,11 @@ package client import ( "errors" "fmt" + "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"` - RootPath string } // ErrorNeedProblemID error @@ -75,17 +77,48 @@ func (info *Info) Hint() string { return text } -// Path path -func (info *Info) Path() string { - 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 } 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/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/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/args.go b/cmd/args.go index 9e9c8b6b..2f025fdb 100644 --- a/cmd/args.go +++ b/cmd/args.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "os" + "strings" "path/filepath" "regexp" @@ -41,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 { @@ -58,6 +58,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) @@ -89,6 +92,9 @@ func parseArgs(opts docopt.Opts) error { info.SubmissionID = value } } + if info.ContestID != "" && len(info.ContestID) < 6 { + info.ProblemType = "contest" + } if info.ProblemType == "" { parsed := parsePath(path) if value, ok := parsed["problemType"]; ok { @@ -104,7 +110,7 @@ func parseArgs(opts docopt.Opts) error { info.ProblemID = value } } - if info.ProblemType == "" || info.ProblemType == "contest" { + if info.ProblemType == "" || info.ProblemType == "contest" || info.ProblemType == "gym" { if len(info.ContestID) < 6 { info.ProblemType = "contest" } else { @@ -117,20 +123,7 @@ 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 @@ -142,7 +135,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 @@ -151,63 +144,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{"group", *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 } @@ -217,27 +187,59 @@ 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 := strings.Split(path, string(filepath.Separator)) + + // output := make(map[string]string) cfg := config.Instance - for k, problemType := range client.ProblemTypes { - 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 + for _, value := range cfg.PathSpecifier { + var specifier []string + 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 + // 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) + 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 + } + } + } + output["problemType"] = value.Type + return } - 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 output + + return } 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/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..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 @@ -21,6 +20,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,7 +33,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 []PathSpecifier `json:"path_specifier"` path string } @@ -45,15 +50,12 @@ 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%" }, + PathSpecifier{ "gym", "cf/gym/%contestID%/%problemID%" }, + PathSpecifier{ "group", "cf/group/%groupID%/%contestID%/%problemID%" }, + PathSpecifier{ "acmsguru", "cf/acmsguru/%problemID%" }, } } c.save() diff --git a/config/misc.go b/config/misc.go index 523ea194..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" ) @@ -85,6 +84,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 +101,4 @@ func (c *Config) SetFolderName() (err error) { } return c.save() } +*/ 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)