git-contest 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +13 -5
  2. data/.travis.yml +1 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +22 -25
  5. data/bin/git-contest +2 -2
  6. data/bin/git-contest-finish +2 -2
  7. data/bin/git-contest-init +2 -2
  8. data/bin/git-contest-rebase +2 -2
  9. data/bin/git-contest-start +2 -2
  10. data/bin/git-contest-submit +34 -17
  11. data/git-contest.gemspec +2 -2
  12. data/lib/contest/driver.rb +3 -0
  13. data/lib/contest/driver/aizu_online_judge.rb +12 -12
  14. data/lib/contest/driver/base.rb +44 -19
  15. data/lib/contest/driver/codeforces.rb +21 -15
  16. data/lib/contest/driver/common.rb +21 -5
  17. data/lib/contest/driver/driver_event.rb +1 -1
  18. data/lib/contest/driver/kattis.rb +168 -0
  19. data/lib/contest/driver/uva_online_judge.rb +14 -11
  20. data/lib/git/contest/version.rb +2 -2
  21. data/spec/bin/t004_git_contest_submit_spec.rb +143 -69
  22. data/spec/lib/contest/driver/t001_aizu_online_judge_spec.rb +46 -29
  23. data/spec/lib/contest/driver/t002_codeforces_spec.rb +185 -20
  24. data/spec/lib/contest/driver/t003_uva_online_judge_spec.rb +157 -10
  25. data/spec/lib/contest/driver/t010_kattis_spec.rb +207 -0
  26. data/spec/mock/default_config/plugins/driver_dummy.rb +31 -11
  27. data/spec/mock/t002/codeforces_after_submit.html +45 -0
  28. data/spec/mock/t002/codeforces_enter.html +67 -0
  29. data/spec/mock/t002/codeforces_submit.html +110 -0
  30. data/spec/mock/t002/codeforces_wait_result.html +58 -0
  31. data/spec/mock/t003/uva_after_quick_submit.html +17 -0
  32. data/spec/mock/t003/uva_home.html +39 -0
  33. data/spec/mock/t003/uva_my_submissions.html +111 -0
  34. data/spec/mock/t003/uva_quick_submit.html +67 -0
  35. data/spec/mock/t010/open_kattis_com_login.html +20 -0
  36. data/spec/mock/t010/open_kattis_com_submit.html +54 -0
  37. data/spec/mock/t010/open_kattis_com_user_submissions.html +28 -0
  38. data/spec/mock/t010/user_submission_111111.html +30 -0
  39. data/spec/mock/t010/user_submission_222222.html +79 -0
  40. data/spec/mock/t010/user_submissions.html +39 -0
  41. metadata +58 -27
@@ -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 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
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
- 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)
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(config, source_path, options)
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(source_path)
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
- trigger 'before_wait',
135
- submission_id = get_submission_id(res_page.body)
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($config["submit_rules"]["message"], status, options),
149
+ :result => get_commit_message(status),
145
150
  }
146
151
  )
147
152
 
148
153
  trigger 'finish'
149
- get_commit_message($config["submit_rules"]["message"], status, options)
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
- elements = doc.xpath('//td[contains(concat(" ",@class," "), " status-cell ")][@waiting="true"]')
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.resolve_path path
17
- path = `ls #{path} | cat | head -n 1`
18
- path.strip
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"
@@ -1,7 +1,7 @@
1
1
  #
2
2
  # driver_event.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
 
@@ -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(config, source_path, options)
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(source_path)
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($config["submit_rules"]["message"], status, options),
86
+ :result => get_commit_message(status),
83
87
  }
84
88
  )
85
89
 
86
90
  trigger 'finish'
87
- get_commit_message($config["submit_rules"]["message"], status, options)
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