git-contest 0.0.1
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 +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
|