git-contest 0.0.3 → 0.1.0

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