git-contest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +15 -0
  5. data/Gemfile +5 -0
  6. data/LICENSE.txt +24 -0
  7. data/README.md +217 -0
  8. data/Rakefile +4 -0
  9. data/bin/git-contest +43 -0
  10. data/bin/git-contest-finish +163 -0
  11. data/bin/git-contest-init +77 -0
  12. data/bin/git-contest-rebase +76 -0
  13. data/bin/git-contest-start +68 -0
  14. data/bin/git-contest-submit +188 -0
  15. data/git-contest.gemspec +34 -0
  16. data/lib/contest/driver.rb +15 -0
  17. data/lib/contest/driver/aizu_online_judge.rb +159 -0
  18. data/lib/contest/driver/base.rb +103 -0
  19. data/lib/contest/driver/codeforces.rb +196 -0
  20. data/lib/contest/driver/common.rb +72 -0
  21. data/lib/contest/driver/driver_event.rb +30 -0
  22. data/lib/contest/driver/uva_online_judge.rb +131 -0
  23. data/lib/git/contest.rb +14 -0
  24. data/lib/git/contest/common.rb +48 -0
  25. data/lib/git/contest/git.rb +189 -0
  26. data/lib/git/contest/test.rb +10 -0
  27. data/lib/git/contest/version.rb +12 -0
  28. data/spec/bin/t004_git_contest_submit_spec.rb +143 -0
  29. data/spec/bin/t005_git_contest_branching_spec.rb +83 -0
  30. data/spec/bin/t007_git_contest_start_spec.rb +121 -0
  31. data/spec/bin/t008_git_contest_finish_spec.rb +229 -0
  32. data/spec/bin/t009_git_contest_init_spec.rb +82 -0
  33. data/spec/lib/contest/driver/t001_aizu_online_judge_spec.rb +177 -0
  34. data/spec/lib/contest/driver/t002_codeforces_spec.rb +34 -0
  35. data/spec/lib/contest/driver/t003_uva_online_judge_spec.rb +33 -0
  36. data/spec/mock/default_config/config.yml +0 -0
  37. data/spec/mock/default_config/plugins/driver_dummy.rb +113 -0
  38. data/spec/mock/t001/002.status_log.xml +55 -0
  39. data/spec/mock/t001/config.yml +5 -0
  40. data/spec/mock/t001/description.html +48 -0
  41. data/spec/mock/t001/status.html +49 -0
  42. data/spec/mock/t001/status_log.xml +29 -0
  43. data/spec/mock/t002/my_submissions.html +58 -0
  44. data/spec/mock/t003/after_submit.html +28 -0
  45. data/spec/mock/t003/my_submissions.compile_error.html +160 -0
  46. data/spec/mock/t003/my_submissions.sent_to_judge.html +358 -0
  47. data/spec/mock/t004/config.yml +17 -0
  48. data/spec/mock/t005/001/config.yml +5 -0
  49. data/spec/mock/t006/001/001/001/config.yml +8 -0
  50. data/spec/mock/t006/001/001/002/config.yml +8 -0
  51. data/spec/mock/t006/001/001/003/config.yml +8 -0
  52. data/spec/mock/t006/001/002/001/config.yml +8 -0
  53. data/spec/mock/t006/001/002/002/config.yml +8 -0
  54. data/spec/mock/t006/001/002/003/config.yml +8 -0
  55. data/spec/mock/t006/001/003/001/config.yml +9 -0
  56. data/spec/mock/t006/001/003/002/config.yml +9 -0
  57. data/spec/mock/t006/001/003/003/config.yml +9 -0
  58. data/spec/spec_helper.rb +45 -0
  59. data/spec/spec_list.txt +1 -0
  60. data/spec/t006_config_spec.rb +168 -0
  61. 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