git-contest 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/.travis.yml +1 -0
- data/LICENSE.txt +1 -1
- data/README.md +22 -25
- data/bin/git-contest +2 -2
- data/bin/git-contest-finish +2 -2
- data/bin/git-contest-init +2 -2
- data/bin/git-contest-rebase +2 -2
- data/bin/git-contest-start +2 -2
- data/bin/git-contest-submit +34 -17
- data/git-contest.gemspec +2 -2
- data/lib/contest/driver.rb +3 -0
- data/lib/contest/driver/aizu_online_judge.rb +12 -12
- data/lib/contest/driver/base.rb +44 -19
- data/lib/contest/driver/codeforces.rb +21 -15
- data/lib/contest/driver/common.rb +21 -5
- data/lib/contest/driver/driver_event.rb +1 -1
- data/lib/contest/driver/kattis.rb +168 -0
- data/lib/contest/driver/uva_online_judge.rb +14 -11
- data/lib/git/contest/version.rb +2 -2
- data/spec/bin/t004_git_contest_submit_spec.rb +143 -69
- data/spec/lib/contest/driver/t001_aizu_online_judge_spec.rb +46 -29
- data/spec/lib/contest/driver/t002_codeforces_spec.rb +185 -20
- data/spec/lib/contest/driver/t003_uva_online_judge_spec.rb +157 -10
- data/spec/lib/contest/driver/t010_kattis_spec.rb +207 -0
- data/spec/mock/default_config/plugins/driver_dummy.rb +31 -11
- data/spec/mock/t002/codeforces_after_submit.html +45 -0
- data/spec/mock/t002/codeforces_enter.html +67 -0
- data/spec/mock/t002/codeforces_submit.html +110 -0
- data/spec/mock/t002/codeforces_wait_result.html +58 -0
- data/spec/mock/t003/uva_after_quick_submit.html +17 -0
- data/spec/mock/t003/uva_home.html +39 -0
- data/spec/mock/t003/uva_my_submissions.html +111 -0
- data/spec/mock/t003/uva_quick_submit.html +67 -0
- data/spec/mock/t010/open_kattis_com_login.html +20 -0
- data/spec/mock/t010/open_kattis_com_submit.html +54 -0
- data/spec/mock/t010/open_kattis_com_user_submissions.html +28 -0
- data/spec/mock/t010/user_submission_111111.html +30 -0
- data/spec/mock/t010/user_submission_222222.html +79 -0
- data/spec/mock/t010/user_submissions.html +39 -0
- metadata +58 -27
data/lib/contest/driver/base.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# base.rb
|
3
3
|
#
|
4
|
-
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
4
|
+
# Copyright (c) 2013-2014 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
5
|
# Licensed under the MIT-License.
|
6
6
|
#
|
7
7
|
|
@@ -11,7 +11,22 @@ require 'contest/driver/driver_event'
|
|
11
11
|
|
12
12
|
module Contest
|
13
13
|
module Driver
|
14
|
+
DEFAULT_SOURCE_PATH = "main.*"
|
15
|
+
DEFAULT_COMMIT_MESSAGE ="${site} ${problem-id}: ${status}"
|
16
|
+
|
14
17
|
class DriverBase < DriverEvent
|
18
|
+
attr_accessor :config, :options
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
# submit options
|
22
|
+
@options ||= {}
|
23
|
+
# site config
|
24
|
+
@config ||= {}
|
25
|
+
@config["submit_rules"] ||= {}
|
26
|
+
# call DriverEvent#initialize
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
15
30
|
def get_opts_ext
|
16
31
|
# Example:
|
17
32
|
# define_options do
|
@@ -51,7 +66,7 @@ module Contest
|
|
51
66
|
end
|
52
67
|
|
53
68
|
def get_opts
|
54
|
-
get_opts_ext
|
69
|
+
get_opts_ext()
|
55
70
|
define_options do
|
56
71
|
opt(
|
57
72
|
:source,
|
@@ -65,9 +80,16 @@ module Contest
|
|
65
80
|
:type => :string,
|
66
81
|
:required => false,
|
67
82
|
)
|
83
|
+
opt(
|
84
|
+
:message,
|
85
|
+
"Set git-commit message",
|
86
|
+
:type => :string,
|
87
|
+
:required => false,
|
88
|
+
)
|
68
89
|
end
|
69
90
|
Trollop::options ARGV, @blocks do |blocks|
|
70
91
|
version "git-contest driver"
|
92
|
+
# set driver options
|
71
93
|
blocks.each do |b|
|
72
94
|
instance_eval &b
|
73
95
|
end
|
@@ -78,25 +100,28 @@ module Contest
|
|
78
100
|
nil
|
79
101
|
end
|
80
102
|
|
81
|
-
def get_commit_message
|
82
|
-
message
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
103
|
+
def get_commit_message status
|
104
|
+
if @options[:message].nil?
|
105
|
+
message = @config["submit_rules"]["message"] || DEFAULT_COMMIT_MESSAGE
|
106
|
+
message = message.gsub '${site}', get_site_name
|
107
|
+
message = message.gsub '${problem-id}', get_problem_id(options)
|
108
|
+
message = message.gsub '${status}', status
|
109
|
+
message = "\n#{get_commit_message_ext}" unless get_commit_message_ext.nil?
|
110
|
+
else
|
111
|
+
message = @options[:message]
|
112
|
+
end
|
113
|
+
message
|
88
114
|
end
|
89
115
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
options[:
|
96
|
-
options[:language]
|
97
|
-
|
98
|
-
|
99
|
-
get_commit_message($config["submit_rules"]["message"], status, options)
|
116
|
+
# submit a solution
|
117
|
+
def submit
|
118
|
+
@options[:source] = Utils.resolve_path(
|
119
|
+
@options[:source] || @config["submit_rules"]["source"] || DEFAULT_SOURCE_PATH
|
120
|
+
)
|
121
|
+
@options[:language] ||= Utils.resolve_language(@options[:source])
|
122
|
+
@options[:language] = resolve_language Utils.normalize_language(@options[:language])
|
123
|
+
|
124
|
+
submit_ext()
|
100
125
|
end
|
101
126
|
end
|
102
127
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# codeforces.rb
|
3
3
|
#
|
4
|
-
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
4
|
+
# Copyright (c) 2013-2014 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
5
|
# Licensed under the MIT-License.
|
6
6
|
#
|
7
7
|
|
@@ -80,11 +80,11 @@ module Contest
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
-
def submit_ext(
|
83
|
+
def submit_ext()
|
84
84
|
# start
|
85
85
|
trigger 'start'
|
86
|
-
contest_id = options[:contest_id]
|
87
|
-
problem_id = options[:problem_id]
|
86
|
+
contest_id = @options[:contest_id]
|
87
|
+
problem_id = @options[:problem_id]
|
88
88
|
|
89
89
|
@client = Mechanize.new {|agent|
|
90
90
|
agent.user_agent_alias = 'Windows IE 7'
|
@@ -94,21 +94,21 @@ module Contest
|
|
94
94
|
trigger 'before_login'
|
95
95
|
login_page = @client.get 'http://codeforces.com/enter'
|
96
96
|
login_page.form_with(:action => '') do |form|
|
97
|
-
form.handle = config["user"]
|
98
|
-
form.password = config["password"]
|
97
|
+
form.handle = @config["user"]
|
98
|
+
form.password = @config["password"]
|
99
99
|
end.submit
|
100
100
|
trigger 'after_login'
|
101
101
|
|
102
102
|
# submit
|
103
|
-
trigger 'before_submit', options
|
103
|
+
trigger 'before_submit', @options
|
104
104
|
# retry once
|
105
105
|
retries = 1
|
106
106
|
begin
|
107
107
|
submit_page = @client.get "http://codeforces.com/contest/#{contest_id}/submit"
|
108
108
|
res_page = submit_page.form_with(:class => 'submit-form') do |form|
|
109
109
|
form.submittedProblemIndex = problem_id
|
110
|
-
form.programTypeId = options[:language]
|
111
|
-
form.source = File.read(
|
110
|
+
form.programTypeId = @options[:language]
|
111
|
+
form.source = File.read(@options[:source])
|
112
112
|
end.submit
|
113
113
|
rescue => e
|
114
114
|
raise if retries == 0
|
@@ -131,8 +131,13 @@ module Contest
|
|
131
131
|
trigger 'after_submit'
|
132
132
|
|
133
133
|
# need to get the newest waiting submissionId
|
134
|
-
|
135
|
-
|
134
|
+
submission_id = get_submission_id(res_page.body)
|
135
|
+
trigger(
|
136
|
+
'before_wait',
|
137
|
+
{
|
138
|
+
:submission_id => submission_id,
|
139
|
+
}
|
140
|
+
)
|
136
141
|
|
137
142
|
# wait result
|
138
143
|
status = get_status_wait(contest_id, submission_id)
|
@@ -141,12 +146,12 @@ module Contest
|
|
141
146
|
{
|
142
147
|
:submission_id => submission_id,
|
143
148
|
:status => status,
|
144
|
-
:result => get_commit_message(
|
149
|
+
:result => get_commit_message(status),
|
145
150
|
}
|
146
151
|
)
|
147
152
|
|
148
153
|
trigger 'finish'
|
149
|
-
get_commit_message(
|
154
|
+
get_commit_message(status)
|
150
155
|
end
|
151
156
|
|
152
157
|
def get_status_wait(contest_id, submission_id)
|
@@ -178,8 +183,9 @@ module Contest
|
|
178
183
|
|
179
184
|
def get_submission_id(body)
|
180
185
|
doc = Nokogiri::HTML(body)
|
181
|
-
# get td.status-cell
|
182
|
-
|
186
|
+
# get first td.status-cell
|
187
|
+
line = doc.xpath('//td/a[contains(./text() , "' + @config["user"] + '")]').xpath('../..')[0]
|
188
|
+
elements = line.xpath('//td[contains(concat(" ",@class," "), " status-cell ")]')
|
183
189
|
elements[0].attributes()["submissionid"].value.strip
|
184
190
|
end
|
185
191
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# common.rb
|
3
3
|
#
|
4
|
-
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
4
|
+
# Copyright (c) 2013-2014 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
5
|
# Licensed under the MIT-License.
|
6
6
|
#
|
7
7
|
|
@@ -13,12 +13,26 @@ require 'contest/driver/base'
|
|
13
13
|
module Contest
|
14
14
|
module Driver
|
15
15
|
module Utils
|
16
|
-
def self.
|
17
|
-
|
18
|
-
|
16
|
+
def self.resolve_wild_card path
|
17
|
+
`ls #{path} | cat | head -n 1`.strip
|
18
|
+
end
|
19
|
+
|
20
|
+
# resolve wild card
|
21
|
+
def self.resolve_path src
|
22
|
+
if src.match ','
|
23
|
+
src.split(',').map do |path|
|
24
|
+
resolve_wild_card(path)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
resolve_wild_card(src)
|
28
|
+
end
|
19
29
|
end
|
20
30
|
|
21
31
|
def self.resolve_language path
|
32
|
+
# set first element if path is array
|
33
|
+
if path.is_a? Array
|
34
|
+
path = path[0]
|
35
|
+
end
|
22
36
|
regexp = /\.([a-z0-9]+)$/
|
23
37
|
if path.match(regexp)
|
24
38
|
return path.match(regexp)[1]
|
@@ -31,7 +45,7 @@ module Contest
|
|
31
45
|
case label
|
32
46
|
when "c", "C"
|
33
47
|
return "clang"
|
34
|
-
when "cpp", "C++", "c++"
|
48
|
+
when "cpp", "C++", "c++", "cc", "cxx"
|
35
49
|
return "cpp"
|
36
50
|
when "c++11", "C++11"
|
37
51
|
return "cpp11"
|
@@ -45,6 +59,8 @@ module Contest
|
|
45
59
|
return "haskell"
|
46
60
|
when "java", "Java"
|
47
61
|
return "java"
|
62
|
+
when "objc", "m"
|
63
|
+
return "objc"
|
48
64
|
when "ocaml", "ml", "OCaml"
|
49
65
|
return "ocaml"
|
50
66
|
when "Delphi", "delphi"
|
@@ -0,0 +1,168 @@
|
|
1
|
+
#
|
2
|
+
# kattis.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2013-2014 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
|
+
# Copyright (c) 2014 Oskar Sundström <oskar.sundstrom@gmail.com>
|
6
|
+
# Licensed under the MIT-License.
|
7
|
+
#
|
8
|
+
# An official Python submission script can be found at
|
9
|
+
# https://open.kattis.com/doc/submit
|
10
|
+
#
|
11
|
+
# Using the Python script you would write:
|
12
|
+
# $ python3 submit3.py -p aaah Main.java
|
13
|
+
#
|
14
|
+
# To do the same thing using git-contest you instead write:
|
15
|
+
# $ git contest submit kattis -p aaah -s Main.java
|
16
|
+
|
17
|
+
require 'contest/driver/common'
|
18
|
+
|
19
|
+
module Contest
|
20
|
+
module Driver
|
21
|
+
class Kattis < DriverBase
|
22
|
+
def get_opts_ext
|
23
|
+
define_options do
|
24
|
+
opt(
|
25
|
+
:contest_id,
|
26
|
+
"Contest ID (Ex: open, kth, liu, etc...)",
|
27
|
+
:type => :string,
|
28
|
+
:required => false,
|
29
|
+
)
|
30
|
+
opt(
|
31
|
+
:problem_id,
|
32
|
+
"Problem ID (Ex: aaah, listgame2, etc...)",
|
33
|
+
:type => :string,
|
34
|
+
:required => true,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_site_name
|
40
|
+
"Kattis"
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_desc
|
44
|
+
"Kattis (URL: https://open.kattis.com/)"
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_problem_id(options)
|
48
|
+
"#{options[:problem_id]}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve_language(label)
|
52
|
+
case label
|
53
|
+
when 'cpp'
|
54
|
+
return '1'
|
55
|
+
when 'c'
|
56
|
+
return '2'
|
57
|
+
when 'java'
|
58
|
+
return '3'
|
59
|
+
when 'python2'
|
60
|
+
return '6'
|
61
|
+
when 'python3'
|
62
|
+
return '8'
|
63
|
+
when 'cs'
|
64
|
+
return '9'
|
65
|
+
when 'golang'
|
66
|
+
return '10'
|
67
|
+
when 'objc'
|
68
|
+
return '11'
|
69
|
+
else
|
70
|
+
abort 'unknown language'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def submit_ext
|
75
|
+
trigger 'start'
|
76
|
+
problem_id = @options[:problem_id]
|
77
|
+
|
78
|
+
if (@options[:contest_id])
|
79
|
+
subdomain = @options[:contest_id]
|
80
|
+
else
|
81
|
+
subdomain = 'open'
|
82
|
+
end
|
83
|
+
|
84
|
+
@client = Mechanize.new {|agent|
|
85
|
+
agent.user_agent_alias = 'Windows IE 7'
|
86
|
+
}
|
87
|
+
|
88
|
+
# Login
|
89
|
+
trigger 'before_login'
|
90
|
+
login_page = @client.get "https://#{subdomain}.kattis.com/login?email_login=true"
|
91
|
+
login_page.form_with(:action => 'login?email_login=true') do |form|
|
92
|
+
form.user = @config['user']
|
93
|
+
form.password = @config['password']
|
94
|
+
end.submit
|
95
|
+
trigger 'after_login'
|
96
|
+
|
97
|
+
# Submit
|
98
|
+
trigger 'before_submit', @options
|
99
|
+
submit_page = @client.get "https://#{subdomain}.kattis.com/submit"
|
100
|
+
res_page = submit_page.form_with(:name => 'upload') do |form|
|
101
|
+
form.problem = problem_id
|
102
|
+
form['lang'] = @options[:language]
|
103
|
+
form.sub_code = File.read(@options[:source])
|
104
|
+
# Use file name as main class for Java
|
105
|
+
if (@options[:language] == resolve_language('java'))
|
106
|
+
form['mainclass'] = @options[:source].rpartition('.')[0]
|
107
|
+
end
|
108
|
+
form.submit(form.button_with(:name => 'submit'))
|
109
|
+
end.submit
|
110
|
+
trigger 'after_submit'
|
111
|
+
|
112
|
+
# Result
|
113
|
+
trigger 'before_wait'
|
114
|
+
user = @config['user']
|
115
|
+
doc = Nokogiri::HTML(res_page.body)
|
116
|
+
# Check for error messages
|
117
|
+
error = doc.xpath('//p[@class="error"]')[0];
|
118
|
+
if ((/Problem ID not found in database./ =~ error) ||
|
119
|
+
(/Problem-id inte funnet i databasen./ =~ error))
|
120
|
+
abort "Problem ID not found in database."
|
121
|
+
end
|
122
|
+
submissions_page = @client.get "https://#{subdomain}.kattis.com/users/#{user}?show=submissions"
|
123
|
+
submission_id = get_submission_id(submissions_page.body)
|
124
|
+
status = get_status_wait(submission_id, subdomain)
|
125
|
+
trigger(
|
126
|
+
'after_wait',
|
127
|
+
{
|
128
|
+
:submission_id => submission_id,
|
129
|
+
:status => status,
|
130
|
+
:result => get_commit_message(status),
|
131
|
+
}
|
132
|
+
)
|
133
|
+
|
134
|
+
trigger 'finish'
|
135
|
+
get_commit_message(status)
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_status_wait(submission_id, subdomain)
|
139
|
+
submission_id = submission_id.to_s
|
140
|
+
# Wait for result
|
141
|
+
12.times do
|
142
|
+
sleep 10
|
143
|
+
submission_page = @client.get "https://#{subdomain}.kattis.com/submission?id=#{submission_id}"
|
144
|
+
status = get_submission_status(submission_id, submission_page.body)
|
145
|
+
return status unless status == 'Running'
|
146
|
+
trigger 'retry'
|
147
|
+
end
|
148
|
+
trigger 'timeout'
|
149
|
+
return 'timeout'
|
150
|
+
end
|
151
|
+
|
152
|
+
def get_submission_id(body)
|
153
|
+
doc = Nokogiri::HTML(body)
|
154
|
+
return doc.xpath('//a[starts-with(@href,"submission")]')[0].inner_text().strip
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_submission_status(submission_id, body)
|
158
|
+
doc = Nokogiri::HTML(body)
|
159
|
+
return doc.xpath('//td[@class="status"]/span').inner_text().strip
|
160
|
+
end
|
161
|
+
|
162
|
+
if is_test_mode?
|
163
|
+
attr_writer :client
|
164
|
+
else
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# uva_online_judge.rb
|
3
3
|
#
|
4
|
-
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
4
|
+
# Copyright (c) 2013-2014 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
5
|
# Licensed under the MIT-License.
|
6
6
|
#
|
7
7
|
|
@@ -21,6 +21,10 @@ module Contest
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def get_problem_id(options)
|
25
|
+
"#{options[:problem_id]}"
|
26
|
+
end
|
27
|
+
|
24
28
|
def get_site_name
|
25
29
|
"UVa"
|
26
30
|
end
|
@@ -44,9 +48,9 @@ module Contest
|
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
47
|
-
def submit_ext
|
51
|
+
def submit_ext
|
48
52
|
trigger 'start'
|
49
|
-
problem_id = options[:problem_id]
|
53
|
+
problem_id = @options[:problem_id]
|
50
54
|
|
51
55
|
@client = Mechanize.new {|agent|
|
52
56
|
agent.user_agent_alias = 'Windows IE 7'
|
@@ -56,17 +60,17 @@ module Contest
|
|
56
60
|
trigger 'before_login'
|
57
61
|
login_page = @client.get 'http://uva.onlinejudge.org/'
|
58
62
|
login_page.form_with(:id => 'mod_loginform') do |form|
|
59
|
-
form.username = config["user"]
|
60
|
-
form.passwd = config["password"]
|
63
|
+
form.username = @config["user"]
|
64
|
+
form.passwd = @config["password"]
|
61
65
|
end.submit
|
62
66
|
trigger 'after_login'
|
63
67
|
|
64
|
-
trigger 'before_submit', options
|
68
|
+
trigger 'before_submit', @options
|
65
69
|
submit_page = @client.get 'http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=25'
|
66
70
|
res_page = submit_page.form_with(:action => 'index.php?option=com_onlinejudge&Itemid=25&page=save_submission') do |form|
|
67
71
|
form.localid = problem_id
|
68
|
-
form.radiobutton_with(:name => 'language', :value => options[:language]).check
|
69
|
-
form.code = File.read(
|
72
|
+
form.radiobutton_with(:name => 'language', :value => @options[:language]).check
|
73
|
+
form.code = File.read(@options[:source])
|
70
74
|
end.submit
|
71
75
|
trigger 'after_submit'
|
72
76
|
|
@@ -79,12 +83,12 @@ module Contest
|
|
79
83
|
{
|
80
84
|
:submission_id => submission_id,
|
81
85
|
:status => status,
|
82
|
-
:result => get_commit_message(
|
86
|
+
:result => get_commit_message(status),
|
83
87
|
}
|
84
88
|
)
|
85
89
|
|
86
90
|
trigger 'finish'
|
87
|
-
get_commit_message(
|
91
|
+
get_commit_message(status)
|
88
92
|
end
|
89
93
|
|
90
94
|
def get_status_wait(submission_id)
|
@@ -104,7 +108,6 @@ module Contest
|
|
104
108
|
def get_submission_id(body)
|
105
109
|
doc = Nokogiri::HTML(body)
|
106
110
|
text = doc.xpath('//div[@class="message"]')[0].text().strip
|
107
|
-
# Submission received with ID 12500010
|
108
111
|
text.match(/Submission received with ID ([0-9]+)/)[1]
|
109
112
|
end
|
110
113
|
|