git-contest 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +15 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +24 -0
- data/README.md +217 -0
- data/Rakefile +4 -0
- data/bin/git-contest +43 -0
- data/bin/git-contest-finish +163 -0
- data/bin/git-contest-init +77 -0
- data/bin/git-contest-rebase +76 -0
- data/bin/git-contest-start +68 -0
- data/bin/git-contest-submit +188 -0
- data/git-contest.gemspec +34 -0
- data/lib/contest/driver.rb +15 -0
- data/lib/contest/driver/aizu_online_judge.rb +159 -0
- data/lib/contest/driver/base.rb +103 -0
- data/lib/contest/driver/codeforces.rb +196 -0
- data/lib/contest/driver/common.rb +72 -0
- data/lib/contest/driver/driver_event.rb +30 -0
- data/lib/contest/driver/uva_online_judge.rb +131 -0
- data/lib/git/contest.rb +14 -0
- data/lib/git/contest/common.rb +48 -0
- data/lib/git/contest/git.rb +189 -0
- data/lib/git/contest/test.rb +10 -0
- data/lib/git/contest/version.rb +12 -0
- data/spec/bin/t004_git_contest_submit_spec.rb +143 -0
- data/spec/bin/t005_git_contest_branching_spec.rb +83 -0
- data/spec/bin/t007_git_contest_start_spec.rb +121 -0
- data/spec/bin/t008_git_contest_finish_spec.rb +229 -0
- data/spec/bin/t009_git_contest_init_spec.rb +82 -0
- data/spec/lib/contest/driver/t001_aizu_online_judge_spec.rb +177 -0
- data/spec/lib/contest/driver/t002_codeforces_spec.rb +34 -0
- data/spec/lib/contest/driver/t003_uva_online_judge_spec.rb +33 -0
- data/spec/mock/default_config/config.yml +0 -0
- data/spec/mock/default_config/plugins/driver_dummy.rb +113 -0
- data/spec/mock/t001/002.status_log.xml +55 -0
- data/spec/mock/t001/config.yml +5 -0
- data/spec/mock/t001/description.html +48 -0
- data/spec/mock/t001/status.html +49 -0
- data/spec/mock/t001/status_log.xml +29 -0
- data/spec/mock/t002/my_submissions.html +58 -0
- data/spec/mock/t003/after_submit.html +28 -0
- data/spec/mock/t003/my_submissions.compile_error.html +160 -0
- data/spec/mock/t003/my_submissions.sent_to_judge.html +358 -0
- data/spec/mock/t004/config.yml +17 -0
- data/spec/mock/t005/001/config.yml +5 -0
- data/spec/mock/t006/001/001/001/config.yml +8 -0
- data/spec/mock/t006/001/001/002/config.yml +8 -0
- data/spec/mock/t006/001/001/003/config.yml +8 -0
- data/spec/mock/t006/001/002/001/config.yml +8 -0
- data/spec/mock/t006/001/002/002/config.yml +8 -0
- data/spec/mock/t006/001/002/003/config.yml +8 -0
- data/spec/mock/t006/001/003/001/config.yml +9 -0
- data/spec/mock/t006/001/003/002/config.yml +9 -0
- data/spec/mock/t006/001/003/003/config.yml +9 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/spec_list.txt +1 -0
- data/spec/t006_config_spec.rb +168 -0
- metadata +269 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
#
|
2
|
+
# driver.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
|
+
# Licensed under the MIT-License.
|
6
|
+
#
|
7
|
+
|
8
|
+
# http://judge.u-aizu.ac.jp/onlinejudge/
|
9
|
+
require 'contest/driver/aizu_online_judge'
|
10
|
+
|
11
|
+
# http://codeforces.com/
|
12
|
+
require 'contest/driver/codeforces'
|
13
|
+
|
14
|
+
# http://uva.onlinejudge.org/
|
15
|
+
require 'contest/driver/uva_online_judge'
|
@@ -0,0 +1,159 @@
|
|
1
|
+
#
|
2
|
+
# aizu_online_judge.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
|
+
# Licensed under the MIT-License.
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'contest/driver/common'
|
9
|
+
require 'rexml/document'
|
10
|
+
|
11
|
+
module Contest
|
12
|
+
module Driver
|
13
|
+
class AizuOnlineJudge < DriverBase
|
14
|
+
def get_opts_ext
|
15
|
+
define_options do
|
16
|
+
opt(
|
17
|
+
:problem_id,
|
18
|
+
"Problem ID (Ex: 1000, 123, 0123, etc...)",
|
19
|
+
:type => :string,
|
20
|
+
:required => true,
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_site_name
|
26
|
+
"AOJ"
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_problem_id(options)
|
30
|
+
"#{options[:problem_id]}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_desc
|
34
|
+
"Aizu Online Judge (URL: http://judge.u-aizu.ac.jp)"
|
35
|
+
end
|
36
|
+
|
37
|
+
def resolve_language(label)
|
38
|
+
case label
|
39
|
+
when "clang"
|
40
|
+
return "C"
|
41
|
+
when "cpp"
|
42
|
+
return "C++"
|
43
|
+
when "java"
|
44
|
+
return "JAVA"
|
45
|
+
when "cpp11"
|
46
|
+
return "C++11"
|
47
|
+
when "cs"
|
48
|
+
return "C#"
|
49
|
+
when "dlang"
|
50
|
+
return "D"
|
51
|
+
when "ruby"
|
52
|
+
return "Ruby"
|
53
|
+
when "python"
|
54
|
+
return "Python"
|
55
|
+
when "php"
|
56
|
+
return "PHP"
|
57
|
+
when "javascript"
|
58
|
+
return "JavaScript"
|
59
|
+
else
|
60
|
+
abort "unknown languag"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def submit_ext(config, source_path, options)
|
65
|
+
# start
|
66
|
+
trigger 'start'
|
67
|
+
problem_id = "%04d" % options[:problem_id]
|
68
|
+
|
69
|
+
@client = Mechanize.new {|agent|
|
70
|
+
agent.user_agent_alias = 'Windows IE 7'
|
71
|
+
}
|
72
|
+
|
73
|
+
# submit
|
74
|
+
trigger 'before_submit', options
|
75
|
+
submit_page = @client.get "http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0000"
|
76
|
+
submit_page.parser.encoding = "SHIFT_JIS"
|
77
|
+
res_page = submit_page.form_with(:action => '/onlinejudge/servlet/Submit') do |form|
|
78
|
+
form.userID = config["user"]
|
79
|
+
form.password = config["password"]
|
80
|
+
form.problemNO = problem_id
|
81
|
+
form.language = options[:language]
|
82
|
+
form.sourceCode = File.read(source_path)
|
83
|
+
end.submit
|
84
|
+
trigger 'after_submit'
|
85
|
+
|
86
|
+
# need to get the newest waiting submissionId
|
87
|
+
trigger 'before_wait'
|
88
|
+
status_page = @client.get "http://judge.u-aizu.ac.jp/onlinejudge/status.jsp"
|
89
|
+
submission_id = get_submission_id status_page.body
|
90
|
+
|
91
|
+
# wait result
|
92
|
+
status = get_status_wait config["user"], submission_id
|
93
|
+
trigger(
|
94
|
+
'after_wait',
|
95
|
+
{
|
96
|
+
:submission_id => submission_id,
|
97
|
+
:status => status,
|
98
|
+
:result => get_commit_message($config["submit_rules"]["message"], status, options),
|
99
|
+
}
|
100
|
+
)
|
101
|
+
trigger 'finish'
|
102
|
+
|
103
|
+
return "AOJ %s: %s\nhttp://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=#{submission_id}" % [problem_id, status]
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_status_wait(user_id, submission_id)
|
107
|
+
# wait result
|
108
|
+
5.times do
|
109
|
+
sleep 3
|
110
|
+
status_page = @client.get "http://judge.u-aizu.ac.jp/onlinejudge/webservice/status_log?user_id=#{user_id}&limit=1"
|
111
|
+
status = get_status(submission_id, status_page.body)
|
112
|
+
return status unless is_waiting(submission_id, status_page.body)
|
113
|
+
trigger 'retry'
|
114
|
+
end
|
115
|
+
trigger 'timeout'
|
116
|
+
return 'request timed out'
|
117
|
+
end
|
118
|
+
|
119
|
+
def status_loop(doc)
|
120
|
+
doc.root.elements['//status_list'].elements.each('status') do |elm|
|
121
|
+
run_id = elm.elements['run_id'].text.to_s.strip
|
122
|
+
status = elm.elements['status'].text.to_s.strip
|
123
|
+
yield run_id, status
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def is_waiting(submission_id, body)
|
128
|
+
doc = REXML::Document.new body
|
129
|
+
status_loop(doc) do |run_id|
|
130
|
+
return false if run_id == submission_id
|
131
|
+
end
|
132
|
+
return true
|
133
|
+
end
|
134
|
+
|
135
|
+
def get_status(submission_id, body)
|
136
|
+
doc = REXML::Document.new body
|
137
|
+
status_loop(doc) do |run_id, status|
|
138
|
+
return status if run_id == submission_id
|
139
|
+
end
|
140
|
+
return ''
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_submission_id(body)
|
144
|
+
doc = Nokogiri::HTML body
|
145
|
+
doc.xpath('//table[@id="tableRanking"]//tr[@class="dat"]')[0].search('td')[0].text.strip
|
146
|
+
end
|
147
|
+
|
148
|
+
if is_test_mode?
|
149
|
+
attr_writer :client
|
150
|
+
else
|
151
|
+
private :status_loop
|
152
|
+
private :get_status_wait
|
153
|
+
private :is_waiting
|
154
|
+
private :get_status
|
155
|
+
private :get_submission_id
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
#
|
2
|
+
# base.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
|
+
# Licensed under the MIT-License.
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'contest/driver/common'
|
9
|
+
require 'rexml/document'
|
10
|
+
require 'contest/driver/driver_event'
|
11
|
+
|
12
|
+
module Contest
|
13
|
+
module Driver
|
14
|
+
class DriverBase < DriverEvent
|
15
|
+
def get_opts_ext
|
16
|
+
# Example:
|
17
|
+
# define_options do
|
18
|
+
# opt(
|
19
|
+
# :problem_id,
|
20
|
+
# "Problem ID (Ex: 1000, 123, 0123, etc...)",
|
21
|
+
# :type => :string,
|
22
|
+
# :required => true,
|
23
|
+
# )
|
24
|
+
# end
|
25
|
+
raise 'TODO: Implement'
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_site_name
|
29
|
+
raise 'TODO: Implement'
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_problem_id(options)
|
33
|
+
raise 'TODO: Implement'
|
34
|
+
end
|
35
|
+
|
36
|
+
def resolve_language(label)
|
37
|
+
raise 'TODO: Implement'
|
38
|
+
end
|
39
|
+
|
40
|
+
def submit_ext(config, source_path, options)
|
41
|
+
raise 'TODO: Implement'
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_desc
|
45
|
+
''
|
46
|
+
end
|
47
|
+
|
48
|
+
def define_options &block
|
49
|
+
@blocks ||= []
|
50
|
+
@blocks.push(block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_opts
|
54
|
+
get_opts_ext
|
55
|
+
define_options do
|
56
|
+
opt(
|
57
|
+
:source,
|
58
|
+
"Specify submitted code (Ex: main.cpp)",
|
59
|
+
:type => :string,
|
60
|
+
:required => false,
|
61
|
+
)
|
62
|
+
opt(
|
63
|
+
:language,
|
64
|
+
"Specify programming language (Ex: C++, Java or etc...)",
|
65
|
+
:type => :string,
|
66
|
+
:required => false,
|
67
|
+
)
|
68
|
+
end
|
69
|
+
Trollop::options ARGV, @blocks do |blocks|
|
70
|
+
version "git-contest driver"
|
71
|
+
blocks.each do |b|
|
72
|
+
instance_eval &b
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_commit_message_ext
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_commit_message rule, status, options
|
82
|
+
message = rule
|
83
|
+
message = message.gsub '${site}', get_site_name
|
84
|
+
message = message.gsub '${problem-id}', get_problem_id(options)
|
85
|
+
message = message.gsub '${status}', status
|
86
|
+
message = "\n#{get_commit_message_ext}" unless get_commit_message_ext.nil?
|
87
|
+
return message
|
88
|
+
end
|
89
|
+
|
90
|
+
def submit(config, source_path, options)
|
91
|
+
$config = get_config
|
92
|
+
$config["submit_rules"] ||= {}
|
93
|
+
$config["submit_rules"]["message"] ||= "${site} ${problem-id}: ${status}"
|
94
|
+
source_path = Utils.resolve_path(options[:source] || $config["submit_rules"]["source"] || source_path)
|
95
|
+
options[:source] = source_path
|
96
|
+
options[:language] ||= Utils.resolve_language(source_path)
|
97
|
+
options[:language] = resolve_language Utils.normalize_language(options[:language])
|
98
|
+
status = submit_ext(config, source_path, options)
|
99
|
+
get_commit_message($config["submit_rules"]["message"], status, options)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
#
|
2
|
+
# codeforces.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
|
+
# Licensed under the MIT-License.
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'contest/driver/common'
|
9
|
+
|
10
|
+
module Contest
|
11
|
+
module Driver
|
12
|
+
class Codeforces < DriverBase
|
13
|
+
def get_opts_ext
|
14
|
+
define_options do
|
15
|
+
opt(
|
16
|
+
:contest_id,
|
17
|
+
"Contest ID (Ex: 100, 234, etc...)",
|
18
|
+
:type => :string,
|
19
|
+
:required => true,
|
20
|
+
)
|
21
|
+
opt(
|
22
|
+
:problem_id,
|
23
|
+
"Problem ID (Ex: A, B, etc...)",
|
24
|
+
:type => :string,
|
25
|
+
:required => true,
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_site_name
|
31
|
+
"Codeforces"
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_problem_id(options)
|
35
|
+
"#{options[:contest_id]}#{options[:problem_id]}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_desc
|
39
|
+
"Codeforces (URL: http://codeforces.com/)"
|
40
|
+
end
|
41
|
+
|
42
|
+
def resolve_language(label)
|
43
|
+
case label
|
44
|
+
when "clang"
|
45
|
+
return "10"
|
46
|
+
when "cpp"
|
47
|
+
return "1"
|
48
|
+
when "cpp11"
|
49
|
+
return "16"
|
50
|
+
when "cs"
|
51
|
+
return "9"
|
52
|
+
when "dlang"
|
53
|
+
return "28"
|
54
|
+
when "golang"
|
55
|
+
return "32"
|
56
|
+
when "haskell"
|
57
|
+
return "12"
|
58
|
+
when "java"
|
59
|
+
return "23"
|
60
|
+
when "ocaml"
|
61
|
+
return "19"
|
62
|
+
when "delphi"
|
63
|
+
return "3"
|
64
|
+
when "pascal"
|
65
|
+
return "4"
|
66
|
+
when "perl"
|
67
|
+
return "13"
|
68
|
+
when "php"
|
69
|
+
return "6"
|
70
|
+
when "python2"
|
71
|
+
return "7"
|
72
|
+
when "python3"
|
73
|
+
return "31"
|
74
|
+
when "ruby"
|
75
|
+
return "8"
|
76
|
+
when "scala"
|
77
|
+
return "20"
|
78
|
+
else
|
79
|
+
abort "unknown languag"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def submit_ext(config, source_path, options)
|
84
|
+
# start
|
85
|
+
trigger 'start'
|
86
|
+
contest_id = options[:contest_id]
|
87
|
+
problem_id = options[:problem_id]
|
88
|
+
|
89
|
+
@client = Mechanize.new {|agent|
|
90
|
+
agent.user_agent_alias = 'Windows IE 7'
|
91
|
+
}
|
92
|
+
|
93
|
+
# login
|
94
|
+
trigger 'before_login'
|
95
|
+
login_page = @client.get 'http://codeforces.com/enter'
|
96
|
+
login_page.form_with(:action => '') do |form|
|
97
|
+
form.handle = config["user"]
|
98
|
+
form.password = config["password"]
|
99
|
+
end.submit
|
100
|
+
trigger 'after_login'
|
101
|
+
|
102
|
+
# submit
|
103
|
+
trigger 'before_submit', options
|
104
|
+
# retry once
|
105
|
+
retries = 1
|
106
|
+
begin
|
107
|
+
submit_page = @client.get "http://codeforces.com/contest/#{contest_id}/submit"
|
108
|
+
res_page = submit_page.form_with(:class => 'submit-form') do |form|
|
109
|
+
form.submittedProblemIndex = problem_id
|
110
|
+
form.programTypeId = options[:language]
|
111
|
+
form.source = File.read(source_path)
|
112
|
+
end.submit
|
113
|
+
rescue => e
|
114
|
+
raise if retries == 0
|
115
|
+
retries -= 1
|
116
|
+
# may not be registered practice
|
117
|
+
if /submittedProblemIndex/ =~ e.to_s
|
118
|
+
contest_page = @client.get "http://codeforces.com/contest/#{contest_id}"
|
119
|
+
contest_page.form_with(:action => "") do |form|
|
120
|
+
flag_need_to_register = false
|
121
|
+
form.field_with(:value => "registerForPractice") do |field|
|
122
|
+
flag_need_to_register = true
|
123
|
+
end
|
124
|
+
form.click_button if flag_need_to_register
|
125
|
+
end
|
126
|
+
sleep 3
|
127
|
+
retry
|
128
|
+
end
|
129
|
+
raise
|
130
|
+
end
|
131
|
+
trigger 'after_submit'
|
132
|
+
|
133
|
+
# need to get the newest waiting submissionId
|
134
|
+
trigger 'before_wait',
|
135
|
+
submission_id = get_submission_id(res_page.body)
|
136
|
+
|
137
|
+
# wait result
|
138
|
+
status = get_status_wait(contest_id, submission_id)
|
139
|
+
trigger(
|
140
|
+
'after_wait',
|
141
|
+
{
|
142
|
+
:submission_id => submission_id,
|
143
|
+
:status => status,
|
144
|
+
:result => get_commit_message($config["submit_rules"]["message"], status, options),
|
145
|
+
}
|
146
|
+
)
|
147
|
+
|
148
|
+
trigger 'finish'
|
149
|
+
get_commit_message($config["submit_rules"]["message"], status, options)
|
150
|
+
end
|
151
|
+
|
152
|
+
def get_status_wait(contest_id, submission_id)
|
153
|
+
contest_id = contest_id.to_s
|
154
|
+
submission_id = submission_id.to_s
|
155
|
+
# wait result
|
156
|
+
5.times do
|
157
|
+
sleep 3
|
158
|
+
my_page = @client.get "http://codeforces.com/contest/#{contest_id}/my"
|
159
|
+
status = get_status(submission_id, my_page.body)
|
160
|
+
return status unless is_waiting(submission_id, my_page.body)
|
161
|
+
trigger 'retry'
|
162
|
+
end
|
163
|
+
trigger 'timeout'
|
164
|
+
return 'timeout'
|
165
|
+
end
|
166
|
+
|
167
|
+
def is_waiting(submission_id, body)
|
168
|
+
doc = Nokogiri::HTML(body)
|
169
|
+
element = doc.xpath('//td[@submissionid="' + submission_id + '"]')[0]
|
170
|
+
element["waiting"] == "true"
|
171
|
+
end
|
172
|
+
|
173
|
+
def get_status(submission_id, body)
|
174
|
+
doc = Nokogiri::HTML(body)
|
175
|
+
element = doc.xpath('//td[@submissionid="' + submission_id + '"]')[0]
|
176
|
+
element.text().strip
|
177
|
+
end
|
178
|
+
|
179
|
+
def get_submission_id(body)
|
180
|
+
doc = Nokogiri::HTML(body)
|
181
|
+
# get td.status-cell
|
182
|
+
elements = doc.xpath('//td[contains(concat(" ",@class," "), " status-cell ")][@waiting="true"]')
|
183
|
+
elements[0].attributes()["submissionid"].value.strip
|
184
|
+
end
|
185
|
+
|
186
|
+
if is_test_mode?
|
187
|
+
attr_writer :client
|
188
|
+
else
|
189
|
+
private :get_status_wait
|
190
|
+
private :is_waiting
|
191
|
+
private :get_status
|
192
|
+
private :get_submission_id
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|