git-contest 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|