├── .gitignore ├── LICENSE.txt ├── README.md ├── cf-languages.el ├── cf-main.el └── cf-mode.el /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.el 3 | !README.md 4 | !COPYING.txt 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cfparser 2 | 3 | Emacs plugin for participating in http://codeforces.com programming competitions. 4 | 5 | ## Installation 6 | - Install `curl` 7 | - Download cfparser 8 | - Add to your `~/.emacs` file the following: 9 | ``` 10 | (add-to-list 'load-path "/path/to/cfparser/") 11 | (require 'cf-mode) 12 | ``` 13 | - Put `cf-mode` minor mode to hook you wish. For example: 14 | ``` 15 | (add-hook 'find-file-hook 'cf-mode) ; enable cf-mode for all open files 16 | ``` 17 | 18 | ### Optional setup 19 | In `~/.emacs` file you can change variables: 20 | - `cf-cookies-file` - file, in which `curl` will store cookies 21 | - `cf-default-language` - language to be used when it was not recognized by extension 22 | - `cf-test-command` - shell command to compile and run your solution on sample tests. For example: 23 | ``` 24 | (setq cf-test-command 25 | (concat 26 | "g++ sol.cc; " 27 | "for i in `ls *.in | sed 's/.in//'`; do " 28 | "echo test $i; " 29 | "./a.out < $i.in | diff - $i.ans; " 30 | "done;")) 31 | ``` 32 | 33 | In file `cf-languages.el` you can adjust extension-to-language mappings. 34 | ## Usage 35 | - `C-c c w` - **W**ho am I 36 | - `C-c c s` - **S**ubmit currently open file 37 | - `C-c c i` - Log **I**n 38 | - `C-c c o` - Log **O**ut 39 | - `C-c c d` - **D**ownload sample tests to current folder (0.in, 0.ans, 1.in ...) 40 | - `C-c c t` - Execute `cf-`**t**`est-command` 41 | - `C-c c l` - **L**ist most recent submissions 42 | 43 | Submit and save functions "guess" the contest number, problem index and the programming language by the current file name in one of the following forms: 44 | - `directory/505/A/myfile.cpp` 45 | - `directory/505/a.c` 46 | - `directory/505a.cc` 47 | 48 | ## See also 49 | 50 | - [gabrielsimoes/cfparser.vim](https://github.com/gabrielsimoes/cfparser.vim) -- Similar plugin for *Vim*. It has more features than this one (e.g it can display a problem statement). 51 | -------------------------------------------------------------------------------- /cf-languages.el: -------------------------------------------------------------------------------- 1 | (setq cf-pl-gcc "10") 2 | (setq cf-pl-gcc11 "43") 3 | (setq cf-pl-g++ "1") 4 | (setq cf-pl-g++11 "42") 5 | (setq cf-pl-go "32") 6 | (setq cf-pl-haskel "12") 7 | (setq cf-pl-java-7 "23") 8 | (setq cf-pl-java-8 "36") 9 | (setq cf-pl-fpc "4") 10 | (setq cf-pl-perl "13") 11 | (setq cf-pl-php "6") 12 | (setq cf-pl-python-2 "7") 13 | (setq cf-pl-python-3 "31") 14 | (setq cf-pl-ruby "8") 15 | (setq cf-pl-scala "20") 16 | (setq cf-pl-js "34") 17 | 18 | (setq cf-pl-by-ext (make-hash-table :test 'equal)) 19 | (puthash ".cpp" cf-pl-g++ cf-pl-by-ext) 20 | (puthash ".cc" cf-pl-g++ cf-pl-by-ext) 21 | (puthash ".c" cf-pl-gcc cf-pl-by-ext) 22 | (puthash ".pas" cf-pl-fpc cf-pl-by-ext) 23 | (puthash ".php" cf-pl-php cf-pl-by-ext) 24 | (puthash ".java" cf-pl-java-7 cf-pl-by-ext) 25 | -------------------------------------------------------------------------------- /cf-main.el: -------------------------------------------------------------------------------- 1 | (load "cf-languages.el") 2 | (require 'json) 3 | 4 | (setq cf-default-language cf-pl-g++) 5 | (setq cf-host "codeforces.com") 6 | (setq cf-proto "http") 7 | (setq cf-cookies-file "~/.cf-cookies") 8 | 9 | (defun cf-get-csrf-token(page) 10 | (string-match "name='csrf_token' +value='\\([^\']+\\)'" page) 11 | (match-string 1 page)) 12 | 13 | (defun cf-logged-in-as () 14 | (setq cf-response 15 | (shell-command-to-string 16 | (format "curl --silent --cookie-jar %s --cookie %s '%s://%s/' " 17 | cf-cookies-file cf-cookies-file 18 | cf-proto cf-host))) 19 | (when (string-match "" cf-response) 20 | (string-match "" cf-response) 21 | (match-string 1 cf-response))) 22 | 23 | (defun cf-login (uname psswd remember) 24 | (setq cf-response 25 | (shell-command-to-string 26 | (format "curl --silent --cookie-jar %s '%s://%s/enter'" 27 | cf-cookies-file 28 | cf-proto cf-host))) 29 | (setq cf-csrf-token (cf-get-csrf-token cf-response)) 30 | (setq cf-response 31 | (shell-command-to-string 32 | (format "curl --location --silent --cookie-jar %s --cookie %s --data 'action=enter&handle=%s&password=%s&remember=%s&csrf_token=%s' '%s://%s/enter'" 33 | cf-cookies-file cf-cookies-file 34 | uname psswd remember cf-csrf-token 35 | cf-proto cf-host)) 36 | ) 37 | (if (string-match "\"error for__password\"" cf-response) 38 | 'nil 39 | 't)) 40 | 41 | (defun cf-submit(contest problem solution language) 42 | (setq cf-csrf-token 43 | (cf-get-csrf-token 44 | (shell-command-to-string 45 | (format "curl --silent --cookie-jar %s --cookie %s '%s://%s/contest/%s/submit'" 46 | cf-cookies-file cf-cookies-file 47 | cf-proto cf-host contest)))) 48 | 49 | (setq temp-file (make-temp-file "cfparser")) 50 | (with-temp-file temp-file (insert solution)) 51 | 52 | (setq cf-response 53 | (shell-command-to-string 54 | (format 55 | "curl --location --silent --cookie-jar %s --cookie %s -F 'csrf_token=%s' -F 'action=submitSolutionFormSubmitted' -F 'submittedProblemIndex=%s' -F 'programTypeId=%s' -F \"source=@%s\" '%s://%s/contest/%s/submit?csrf_token=%s'" 56 | cf-cookies-file cf-cookies-file 57 | cf-csrf-token 58 | problem 59 | language temp-file 60 | cf-proto cf-host contest cf-csrf-token 61 | )))) 62 | 63 | (defun cf-parse-tests(page) 64 | (let ((input_regex "
.*?
\\(.*?\\)
") 65 | (output_regex "
.*?
\\(.*?\\)
") 66 | (from 0) 67 | (input "") 68 | (output "") 69 | (result '())) 70 | (while (setq from (string-match input_regex page from)) 71 | (setq input (match-string 1 page)) 72 | (setq from (string-match output_regex page from)) 73 | (setq output (match-string 1 page)) 74 | (setq input (replace-regexp-in-string "]*?>" "\n" input)) 75 | (setq output (replace-regexp-in-string "]*?>" "\n" output)) 76 | (push (list input output) result)) 77 | result)) 78 | 79 | (defun cf-get-tests(contest problem) 80 | (setq cf-response 81 | (shell-command-to-string 82 | (format "curl --silent --cookie-jar %s --cookie %s '%s://%s/contest/%s/problem/%s'" 83 | cf-cookies-file cf-cookies-file 84 | cf-proto cf-host contest problem))) 85 | (cf-parse-tests cf-response)) 86 | 87 | (defun cf-logout () 88 | (when (file-exists-p cf-cookies-file) 89 | (delete-file cf-cookies-file))) 90 | 91 | (defun cf-submission-vector (handle) 92 | (setq cf-response 93 | (shell-command-to-string 94 | (format 95 | "curl --location --silent '%s://%s/api/user.status?handle=%s&from=1&count=5'" 96 | cf-proto cf-host handle))) 97 | 98 | (setq json-response 99 | (let ((json-key-type 'string)) 100 | (json-read-from-string cf-response))) 101 | 102 | (cdr (assoc '"result" json-response))) 103 | -------------------------------------------------------------------------------- /cf-mode.el: -------------------------------------------------------------------------------- 1 | (load "cf-main.el") 2 | 3 | (defun cf-login-i() 4 | "Login to Codeforces using handle/password." 5 | (interactive) 6 | (let ((cf-uname (read-string "usename: ")) 7 | (cf-psswd (read-passwd "password: ")) 8 | (cf-remember (if (y-or-n-p "remember? ") '"on" '""))) 9 | (message 10 | (if (cf-login cf-uname cf-psswd cf-remember) 11 | '"login: ok" 12 | '"login: fail")))) 13 | 14 | (defun cf-logout-i() 15 | "Logout from Codeforces." 16 | (interactive) 17 | (cf-logout) 18 | (message "logout: ok")) 19 | 20 | (defun cf-whoami-i() 21 | "Print handle." 22 | (interactive) 23 | (message (format "logged in as %s" (cf-logged-in-as)))) 24 | 25 | (setq cf-path-regexp "/\\([0-9]+\\)/?\\([a-zA-Z]\\)/?[^/.]*\\(\.[^.]+\\)$") 26 | (defun cf-submit-current-buffer-by-path-i() 27 | "Submit contents of the buffer." 28 | (interactive) 29 | (unless (cf-logged-in-as) 30 | (cf-login-i)) 31 | (let (contest problem extension language path) 32 | (setq path (buffer-file-name)) 33 | (if (string-match cf-path-regexp path) 34 | (progn 35 | (setq contest (match-string 1 path)) 36 | (setq problem (match-string 2 path)) 37 | (setq extension (match-string 3 path)) 38 | (setq language (gethash extension cf-pl-by-ext)) 39 | (unless language 40 | (setq language cf-default-language)) 41 | (message 42 | (if (cf-submit contest problem (buffer-substring-no-properties (buffer-end -1) (buffer-end 1)) language) 43 | (format "submit: ok [by %s to %s/%s]" (cf-logged-in-as) contest problem) 44 | '"submit: fail"))) 45 | (message "submit: file name not recognized")))) 46 | 47 | (defun cf-download-tests-i() 48 | "Save sample tests to the current directory. 0.in, 0.ans, 1.in ..." 49 | (interactive) 50 | (let (tests input output contest problem path i) 51 | (setq path (buffer-file-name)) 52 | (if (string-match cf-path-regexp path) 53 | (progn 54 | (setq contest (match-string 1 path)) 55 | (setq problem (match-string 2 path)) 56 | (message (format "downloading tests for %s/%s..." contest problem)) 57 | (setq tests (cf-get-tests contest problem)) 58 | (setq i 0) 59 | (dolist (test tests) 60 | (setq input (car test)) 61 | (setq output (cadr test)) 62 | (with-temp-buffer 63 | (insert input) 64 | (write-region (point-min) (point-max) (format "%d.in" i)) 65 | (erase-buffer) 66 | (insert output) 67 | (write-region (point-min) (point-max) (format "%d.out" i))) 68 | (setq i (+ 1 i))) 69 | (message (format "downloaded %d tests" i))) 70 | (message "download: file name not recognized")))) 71 | 72 | (define-minor-mode cf-mode 73 | "Minor mode of codeforces parser" 74 | :lighter " Codeforces" 75 | :keymap (list 76 | (cons (kbd "C-c c w") 'cf-whoami-i) 77 | (cons (kbd "C-c c s") 'cf-submit-current-buffer-by-path-i) 78 | (cons (kbd "C-c c i") 'cf-login-i) 79 | (cons (kbd "C-c c o") 'cf-logout-i) 80 | (cons (kbd "C-c c w") 'cf-whoami-i) 81 | (cons (kbd "C-c c t") 'cf-test-all-i) 82 | (cons (kbd "C-c c d") 'cf-download-tests-i) 83 | (cons (kbd "C-c c l") 'cf-last-submissions-i))) 84 | 85 | (setq cf-test-command nil) 86 | (defun cf-test-all-i() 87 | (interactive) 88 | (if cf-test-command 89 | (compile cf-test-command) 90 | (message "Please set cf-test-command"))) 91 | 92 | (defun cf-last-submissions-i() 93 | (interactive) 94 | (with-output-to-temp-buffer "*cf-lastsubmissions*" 95 | (setq v (cf-submission-vector (cf-logged-in-as))) 96 | (dotimes (i (length v)) 97 | (setq subinfo (elt v i)) 98 | (setq probinfo (cdr (assoc '"problem" subinfo))) 99 | (princ (format "%d%s - %s - %s - Last Test: %d - %dkB - %dms - %d pts\n" 100 | (cdr (assoc '"contestId" probinfo)) 101 | (cdr (assoc '"index" probinfo)) 102 | (cdr (assoc '"name" probinfo)) 103 | (cdr (assoc '"verdict" subinfo)) 104 | (cdr (assoc '"passedTestCount" subinfo)) 105 | (/ (cdr (assoc '"memoryConsumedBytes" subinfo)) 1000) 106 | (cdr (assoc '"timeConsumedMillis" subinfo)) 107 | (cdr (assoc '"points" probinfo))))))) 108 | (provide 'cf-mode) 109 | --------------------------------------------------------------------------------