Skip to content
This repository has been archived by the owner on Dec 14, 2024. It is now read-only.

Commit

Permalink
Fix parse path's problem.
Browse files Browse the repository at this point in the history
Add watch all command.
Add pull command, close issue #3.
  • Loading branch information
xalanq committed Apr 28, 2019
1 parent dfc0653 commit d172e3f
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 29 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ It's fast, small, cross-platorm and powerful.
* Watch submissions' status dynamically.
* List all problems' stats of a contest.
* Fetch all problems' samples of a contest (parallel).
* Fetch the latest or "Accepted" codes of a contest.
* Generate a code from the specified template (including timestamp, author, etc.)
* Test samples and feedback.
* Use default web browser to open problems, the standing page.
Expand Down Expand Up @@ -54,13 +55,14 @@ Usage:
cf submit [<filename>]
cf submit [(<contest-id> <problem-id>)] [<filename>]
cf list [<contest-id>]
cf parse <contest-id> [<problem-id>]
cf parse [<contest-id>] [<problem-id>]
cf gen [<alias>]
cf test [<filename>]
cf watch [<contest-id>]
cf watch [all] [<contest-id>]
cf open [<contest-id>] [<problem-id>]
cf stand [<contest-id>]
cf race <contest-id>
cf pull [ac] [<contest-id>] [<problem-id>]
Examples:
cf config login Config your username and password.
Expand All @@ -75,17 +77,23 @@ Examples:
cf list List all problems' stats of a contest.
cf list 1119
cf parse 100 Fetch all problems' samples of contest 100 into "./100/<problem-id>".
cf parse 100 a Fetch samples of problem "a" of contest 100 into current path.
cf parse 100 a Fetch samples of problem "a" of contest 100 into "./100/a".
cf parse Fetch samples of current problem into current path.
cf gen Generate a code from default template.
cf gen cpp Generate a code from the template which's alias is "cpp" into current path.
cf test Run the commands of a template in current path. Then test all samples.
cf watch Watch the first 10 submissions of current contest.
cf watch all Watch all submissions of current contest.
cf open 1136 a Use default web browser to open the page of contest 1136, problem a.
cf open 1136 Use default web browser to open the page of contest 1136.
cf stand Use default web browser to open the standing page.
cf race 1136 If the contest 1136 has not started yet, it will countdown. After the
countdown ends, it will run 'cf open 1136 a', 'cf open 1136 b', ...,
'cf open 1136 e', 'cf parse 1136'.
cf pull 100 Pull all problems' latest codes of contest 100 into "./100/<problem-id>".
cf pull 100 a Pull the latest code of problem "a" of contest 100 into "./100/<problem-id>".
cf pull ac 100 a Pull the "Accepted" or "Pretests passed" code of problem "a" of contest 100.
cf pull Pull the latest code of current problem into current path.
Notes:
<problem-id> "a" or "A", case-insensitive.
Expand Down
14 changes: 11 additions & 3 deletions README_zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Codeforces Tool 是 [Codeforces](https://codeforces.com) 的命令行界面的
* 查看提交后的情况(动态刷新)
* 列出某场比赛的所有题目的整体信息
* 并行地获取某场比赛所有题目(或者某道题)的样例
* 获取某场比赛所有的最新代码或者AC代码
* 根据你事先准备好的模板代码,生成一份带有时间戳、作者等信息的代码
* 全自动测试样例是否通过,若有错还会给出对比信息
* 用默认的网页浏览器打开题目页面、榜单
Expand Down Expand Up @@ -52,13 +53,14 @@ Codeforces Tool (cf). https://github.com/xalanq/cf-tool
cf submit [<filename>]
cf submit [(<contest-id> <problem-id>)] [<filename>]
cf list [<contest-id>]
cf parse <contest-id> [<problem-id>]
cf parse [<contest-id>] [<problem-id>]
cf gen [<alias>]
cf test [<filename>]
cf watch [<contest-id>]
cf watch [all] [<contest-id>]
cf open [<contest-id>] [<problem-id>]
cf stand [<contest-id>]
cf race <contest-id>
cf pull [ac] [<contest-id>] [<problem-id>]
例子:
cf config login 配置你的用户名和密码。
Expand All @@ -73,16 +75,22 @@ Codeforces Tool (cf). https://github.com/xalanq/cf-tool
cf list 列出当前比赛的题目通过、时限等信息。
cf list 1119
cf parse 100 获取比赛 id 为 100 的所有题目的样例到文件夹 "./100/<problem-id>" 下。
cf parse 100 a 获取比赛 id 为 100 的题目 a 的样例到当前文件夹下。
cf parse 100 a 获取比赛 id 为 100 的题目 a 的样例到文件夹 "./100/a" 下。
cf parse 获取当前比赛的当前题目到当前文件夹下。
cf gen 用默认的模板生成一份代码到当前文件夹下。
cf gen cpp 用名字为 "cpp" 的模板来生成一份代码到当前文件夹下。
cf test 在当前目录下执行模板里的命令,并测试全部样例。
cf watch 查看自己在当前比赛的最后 10 次提交结果。
cf watch all 查看自己在当前比赛的全部提交结果
cf open 1136 a 用默认的浏览器打开比赛 id 为 1136 的题目 a。
cf open 1136 用默认的浏览器打开比赛 id 为 1136 的总览页面。
cf stand 用默认的浏览器当前比赛的榜单。
cf race 1136 如果比赛还未开始且进入倒计时,则该命令会倒计时。当倒计时完后,会自动执行
'cf open 1136 a', 'cf open 1136 b', ..., 'cf open 1136 e', 'cf parse 1136'
cf pull 100 拉取比赛 id 为 100 每道题的最新代码到文件夹 "./100/<problem-id>" 下。
cf pull 100 a 拉取比赛 id 为 100 的题目 a 的最新代码到文件夹 "./100/a" 下。
cf pull ac 100 a 拉取比赛 id 为 100 的题目 a 的 AC 代码。
cf pull 拉取当前题目的最新代码到当前文件夹下。
注意:
<problem-id> 表示题目的 id,比如 "a" 或者 "A",不区分大小写。
Expand Down
13 changes: 10 additions & 3 deletions cf.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ Usage:
cf submit [<filename>]
cf submit [(<contest-id> <problem-id>)] [<filename>]
cf list [<contest-id>]
cf parse <contest-id> [<problem-id>]
cf parse [<contest-id>] [<problem-id>]
cf gen [<alias>]
cf test [<filename>]
cf watch [<contest-id>]
cf watch [all] [<contest-id>]
cf open [<contest-id>] [<problem-id>]
cf stand [<contest-id>]
cf race <contest-id>
cf pull [ac] [<contest-id>] [<problem-id>]
Examples:
cf config login Config your username and password.
Expand All @@ -42,17 +43,23 @@ Examples:
cf list List all problems' stats of a contest.
cf list 1119
cf parse 100 Fetch all problems' samples of contest 100 into "./100/<problem-id>".
cf parse 100 a Fetch samples of problem "a" of contest 100 into current path.
cf parse 100 a Fetch samples of problem "a" of contest 100 into "./100/a".
cf parse Fetch samples of current problem into current path.
cf gen Generate a code from default template.
cf gen cpp Generate a code from the template which's alias is "cpp" into current path.
cf test Run the commands of a template in current path. Then test all samples.
cf watch Watch the first 10 submissions of current contest.
cf watch all Watch all submissions of current contest.
cf open 1136 a Use default web browser to open the page of contest 1136, problem a.
cf open 1136 Use default web browser to open the page of contest 1136.
cf stand Use default web browser to open the standing page.
cf race 1136 If the contest 1136 has not started yet, it will countdown. After the
countdown ends, it will run 'cf open 1136 a', 'cf open 1136 b', ...,
'cf open 1136 e', 'cf parse 1136'.
cf pull 100 Pull all problems' latest codes of contest 100 into "./100/<problem-id>".
cf pull 100 a Pull the latest code of problem "a" of contest 100 into "./100/<problem-id>".
cf pull ac 100 a Pull the "Accepted" or "Pretests passed" code of problem "a" of contest 100.
cf pull Pull the latest code of current problem into current path.
Notes:
<problem-id> "a" or "A", case-insensitive.
Expand Down
30 changes: 30 additions & 0 deletions client/langs.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,33 @@ var Langs = map[string]string{
"34": "JavaScript V8 4.8.0",
"55": "Node.js 9.4.0",
}

// LangsExt language's ext
var LangsExt = map[string]string{
"GNU C11": "c",
"Clang++17 Diagnostics": "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",
}
2 changes: 1 addition & 1 deletion client/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (c *Client) ParseContest(contestID, rootPath string) (err error) {
fmt.Printf("Parsing %v %v\n", contestID, problem.ID)
mu.Unlock()
problemID := strings.ToLower(problem.ID)
path := filepath.Join(rootPath, contestID, problemID)
path := filepath.Join(rootPath, problemID)
samples, err := c.ParseContestProblem(contestID, problem.ID, path)
mu.Lock()
if err != nil {
Expand Down
117 changes: 117 additions & 0 deletions client/pull.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package client

import (
"errors"
"fmt"
"html"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/fatih/color"
)

func findCode(body []byte) (string, error) {
reg := regexp.MustCompile(`<pre[\s\S]*?>([\s\S]*?)</pre>`)
tmp := reg.FindSubmatch(body)
if tmp == nil {
return "", errors.New("Cannot find code")
}
return html.UnescapeString(string(tmp[1])), nil
}

// PullCode pull problem's code to path
func (c *Client) PullCode(codeURL, path, ext string) (filename string, err error) {
client := &http.Client{Jar: c.Jar}
resp, err := client.Get(codeURL)
if err != nil {
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}

code, err := findCode(body)
if err != nil {
return
}

filename = path + ext
i := 1
for _, err := os.Stat(filename); err == nil; _, err = os.Stat(filename) {
tmpPath := fmt.Sprintf("%v%v%v", path, i, ext)
fmt.Printf("%v is existed. Rename to %v\n", filepath.Base(filename), filepath.Base(tmpPath))
filename = tmpPath
i++
}

err = os.MkdirAll(filepath.Dir(filename), os.ModePerm)
if err != nil {
return
}

err = ioutil.WriteFile(filename, []byte(code), 0644)
return
}

// PullContest pull all latest codes or ac codes of contest's problem
func (c *Client) PullContest(contestID, problemID, rootPath string, ac bool) (err error) {
color.Cyan("Pull code from %v%v, accepted: %v", contestID, problemID, ac)
submissons, _, err := c.getSubmissions(fmt.Sprintf("https://codeforces.com/contest/%v/my", contestID), -1)
if err != nil {
return err
}

saved := map[string](map[string]bool){}
used := []Submission{}

for _, submission := range submissons {
pid := strings.ToLower(strings.Split(submission.name, " ")[0])
if problemID != "" && problemID != pid {
continue
}
if ac && !(strings.Contains(submission.status, "Accepted") || strings.Contains(submission.status, "Pretests passed")) {
continue
}
ext, ok := LangsExt[submission.lang]
if !ok {
continue
}
if _, ok = saved[pid]; !ok {
saved[pid] = map[string]bool{}
}
if _, ok = saved[pid][ext]; ok {
continue
}
path := ""
if problemID == "" {
path = filepath.Join(rootPath, pid, pid)
} else {
path = filepath.Join(rootPath, strings.ToLower(problemID))
}
filename, err := c.PullCode(
fmt.Sprintf("https://codeforces.com/contest/%v/submission/%v", contestID, submission.id),
path,
"."+ext,
)
if err == nil {
saved[pid][ext] = true
color.Green(fmt.Sprintf(`Downloaded code of %v %v into %v`, contestID, problemID, filepath.Base(filename)))
used = append(used, submission)
}
}

if len(used) == 0 {
return errors.New("Cannot find any code to save")
}

color.Cyan("These submissions' codes have been saved.")
maxline := 0
display(used, true, &maxline, false)
return nil
}
17 changes: 11 additions & 6 deletions client/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ func watch(url string, channel string, submissions []Submission, maxWidth *int,
return nil
}

func (c *Client) getSubmissions(myURL string, n int, line bool) (submissions []Submission, channels []string, err error) {
func (c *Client) getSubmissions(myURL string, n int) (submissions []Submission, channels []string, err error) {
client := &http.Client{Jar: c.Jar}
resp, err := client.Get(myURL)
if err != nil {
Expand Down Expand Up @@ -392,7 +392,7 @@ func (c *Client) getSubmissions(myURL string, n int, line bool) (submissions []S

// WatchSubmission n is the number of submissions
func (c *Client) WatchSubmission(myURL string, n int, line bool) (err error) {
submissions, channels, err := c.getSubmissions(myURL, n, line)
submissions, channels, err := c.getSubmissions(myURL, n)
if err != nil {
return err
}
Expand All @@ -404,8 +404,13 @@ func (c *Client) WatchSubmission(myURL string, n int, line bool) (err error) {
strings.Join(channels[:], "/"), time.Now().UTC().Format("20060102150405"))

if err = watch(url, channels[0], submissions, &maxWidth, line); err != nil {
ansi.CursorUp(7)
refreshLine(7, maxWidth)
if line {
ansi.CursorUp(7)
refreshLine(7, maxWidth)
} else {
ansi.CursorUp(len(submissions) + 2)
refreshLine(len(submissions)+2, maxWidth)
}
color.Red("Websocket got a problem:\n%v\nNow auto-refresh the status page\n", err.Error())
}

Expand All @@ -421,15 +426,15 @@ func (c *Client) WatchSubmission(myURL string, n int, line bool) (err error) {
if endCount == len(submissions) {
break
}
submissions, channels, err = c.getSubmissions(myURL, n, line)
submissions, channels, err = c.getSubmissions(myURL, n)
if err != nil {
return err
}

display(submissions, first, &maxWidth, line)
first = false
sub := time.Now().Sub(st)
if sub.Seconds() < 1.0 {
if sub < time.Second {
time.Sleep(time.Duration(time.Second - sub))
}
}
Expand Down
7 changes: 5 additions & 2 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"path/filepath"
"regexp"
"strconv"
"strings"

"github.com/fatih/color"
"github.com/xalanq/cf-tool/client"
Expand Down Expand Up @@ -37,6 +38,8 @@ func Eval(args map[string]interface{}) error {
return Stand(args)
} else if args["race"].(bool) {
return Race(args)
} else if args["pull"].(bool) {
return Pull(args)
}
return nil
}
Expand Down Expand Up @@ -67,13 +70,13 @@ func getContestID(args map[string]interface{}) (string, error) {

func getProblemID(args map[string]interface{}) (string, error) {
if p, ok := args["<problem-id>"].(string); ok {
return p, nil
return strings.ToLower(p), nil
}
path, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Base(path), nil
return strings.ToLower(filepath.Base(path)), nil
}

func getSampleID() (samples []string) {
Expand Down
Loading

0 comments on commit d172e3f

Please sign in to comment.