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.
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