ojagent 0.0.1 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/quicksubmit +9 -5
- data/examples/hdoj3337.rb +57 -0
- data/examples/rosalind.rb +66 -0
- data/lib/ojagent.rb +12 -3
- data/lib/ojagent/hdoj_agent.rb +41 -0
- data/lib/ojagent/livearchive_agent.rb +8 -21
- data/lib/ojagent/ojagent.rb +58 -2
- data/lib/ojagent/rosalind_agent.rb +44 -0
- data/lib/ojagent/timus_agent.rb +13 -25
- data/lib/ojagent/version.rb +1 -1
- metadata +6 -2
data/bin/quicksubmit
CHANGED
@@ -11,10 +11,13 @@ require 'highline'
|
|
11
11
|
|
12
12
|
ME = 'quicksubmit'
|
13
13
|
|
14
|
-
Agents = {
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
Agents = {}
|
15
|
+
OJAgent.all.each do |agent|
|
16
|
+
name = agent.name.downcase
|
17
|
+
name.sub!(/.*::/, '')
|
18
|
+
name.sub!(/agent$/, '')
|
19
|
+
Agents[name.to_sym] = agent
|
20
|
+
end
|
18
21
|
|
19
22
|
Ext2Lang = {
|
20
23
|
'.pas' => :pascal,
|
@@ -138,7 +141,8 @@ $*.uniq.each do |path|
|
|
138
141
|
path.sub!(/#{ext}$/, ".#{Time.now.to_i}#{ext}") if File.exists? path
|
139
142
|
IO.write(path, code)
|
140
143
|
|
141
|
-
|
144
|
+
level = status[:status] =~ /correct|accepted/i ? Logger::INFO : Logger::WARN
|
145
|
+
LOG.add level, status
|
142
146
|
rescue Exception => ex
|
143
147
|
LOG.error ex
|
144
148
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'ojagent'
|
4
|
+
|
5
|
+
# Change these two lines
|
6
|
+
USER = ENV['OJ_USERNAME'] || 'ojagent'
|
7
|
+
PASS = ENV['OJ_PASSWORD'] || 'ppnn13%dkstFeb.1st'
|
8
|
+
|
9
|
+
HDOJ = OJAgent::HDOJAgent.new
|
10
|
+
HDOJ.login USER, PASS
|
11
|
+
|
12
|
+
def code(off)
|
13
|
+
return <<-CODE
|
14
|
+
#include <time.h>
|
15
|
+
#include <stdio.h>
|
16
|
+
#include <stdlib.h>
|
17
|
+
#include <string.h>
|
18
|
+
|
19
|
+
int main(void) {
|
20
|
+
int n = #{off};
|
21
|
+
clock_t cpu;
|
22
|
+
size_t mem;
|
23
|
+
char* p = NULL;
|
24
|
+
|
25
|
+
while (n-- > 0) {
|
26
|
+
getchar();
|
27
|
+
}
|
28
|
+
n = getchar();
|
29
|
+
if (n == EOF) {
|
30
|
+
n = 0;
|
31
|
+
}
|
32
|
+
|
33
|
+
cpu = ((n >> 4) * 100 + 10) * (CLOCKS_PER_SEC / 1000);
|
34
|
+
mem = ((n & 0xF) * 1000 + 10) * 1024;
|
35
|
+
p = malloc(mem);
|
36
|
+
memset(p, 0xff, mem);
|
37
|
+
while (clock() < cpu) {
|
38
|
+
p[rand() % mem] = (char)cpu;
|
39
|
+
}
|
40
|
+
putchar(p[0]);
|
41
|
+
free(p);
|
42
|
+
|
43
|
+
return 0;
|
44
|
+
}
|
45
|
+
CODE
|
46
|
+
end
|
47
|
+
|
48
|
+
input = ''
|
49
|
+
50.times do
|
50
|
+
id = HDOJ.submit! '3337', code(input.size), :c
|
51
|
+
status = HDOJ.status! id
|
52
|
+
ascii = (status[:time].to_i / 100) * 16 + (status[:mem].to_i / 1000)
|
53
|
+
break if ascii.zero?
|
54
|
+
input << ascii.chr
|
55
|
+
puts input
|
56
|
+
end
|
57
|
+
p input
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
BEGIN {
|
4
|
+
require 'ojagent'
|
5
|
+
|
6
|
+
$filename = 'rosalind.cookie'
|
7
|
+
$rosalind = OJAgent::RosalindAgent.new
|
8
|
+
$rosalind.agent.cookie_jar.load $filename if File.readable? $filename
|
9
|
+
}
|
10
|
+
|
11
|
+
END {
|
12
|
+
$rosalind.agent.cookie_jar.save_as $filename
|
13
|
+
FileUtils.chmod 0600, $filename
|
14
|
+
}
|
15
|
+
|
16
|
+
require 'highline'
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'wirble'
|
20
|
+
def pp(status)
|
21
|
+
puts Wirble::Colorize.colorize(status.inspect)
|
22
|
+
end
|
23
|
+
rescue LoadError
|
24
|
+
alias :pp :p
|
25
|
+
end
|
26
|
+
|
27
|
+
def usage
|
28
|
+
print <<-USAGE
|
29
|
+
Usage:
|
30
|
+
rosalind.rb login
|
31
|
+
rosalind.rb $pid.pl dl
|
32
|
+
rosalind.rb $pid.pl up
|
33
|
+
USAGE
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
|
37
|
+
$pl, $op = *$*
|
38
|
+
|
39
|
+
if $pl =~ /login/i
|
40
|
+
hl = HighLine.new
|
41
|
+
user = hl.ask("Username: ")
|
42
|
+
pass = hl.ask("Password: "){|q| q.echo = false}
|
43
|
+
$rosalind.login user, pass
|
44
|
+
elsif $pl =~ /[a-z]*/
|
45
|
+
id = $&
|
46
|
+
if $op =~ /dl|download|open/i
|
47
|
+
input = $rosalind.open! id
|
48
|
+
IO.write input.filename, input.content
|
49
|
+
system %{perl #{$pl} #{input.filename} | tee output}
|
50
|
+
elsif $op =~ /up|upload|submit/i
|
51
|
+
output = IO.read('output')
|
52
|
+
source = IO.read($pl)
|
53
|
+
status = $rosalind.judge! id, [output, 'output'], [source, $pl]
|
54
|
+
pp status
|
55
|
+
if status && status[:status] == 'Correct'
|
56
|
+
FileUtils.mv $pl, 'pl/', :verbose => true
|
57
|
+
FileUtils.mv "rosalind_#{id}.txt", 'txt/', :verbose => true
|
58
|
+
FileUtils.rm "rosalind_#{id}.txt.index", :force => true
|
59
|
+
end
|
60
|
+
else
|
61
|
+
usage
|
62
|
+
end
|
63
|
+
else
|
64
|
+
usage
|
65
|
+
end
|
66
|
+
|
data/lib/ojagent.rb
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
require 'ojagent/version'
|
2
2
|
require 'ojagent/ojagent.rb'
|
3
|
-
|
4
|
-
|
3
|
+
|
4
|
+
libdir = File.join(File.expand_path(File.dirname(__FILE__)), 'ojagent')
|
5
|
+
Dir.entries(libdir).each do |filename|
|
6
|
+
require File.join(libdir, filename) if filename.end_with? '_agent.rb'
|
7
|
+
end
|
5
8
|
|
6
9
|
module OJAgent
|
7
|
-
#
|
10
|
+
# Return all avialbe agents.
|
11
|
+
def self.all
|
12
|
+
ObjectSpace.each_object(Class).select{|agent| agent < OJAgent}
|
13
|
+
end
|
14
|
+
|
15
|
+
# All avialbe agents at initial time.
|
16
|
+
StandardAgents = all
|
8
17
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module OJAgent
|
2
|
+
class HDOJAgent < OJAgent
|
3
|
+
def initialize
|
4
|
+
super 'http://acm.hdu.edu.cn',
|
5
|
+
:cpp => 0,
|
6
|
+
:c => 1,
|
7
|
+
:mscpp => 2,
|
8
|
+
:msc => 3,
|
9
|
+
:pascal => 4,
|
10
|
+
:java => 5
|
11
|
+
end
|
12
|
+
|
13
|
+
def login(user, pass)
|
14
|
+
@user = user
|
15
|
+
page = get '/'
|
16
|
+
login_form = page.form_with :action => '/userloginex.php?action=login'
|
17
|
+
login_form.set_fields :username => user,
|
18
|
+
:userpass => pass
|
19
|
+
login_form.submit
|
20
|
+
end
|
21
|
+
|
22
|
+
def submit(pid, code, lang)
|
23
|
+
page = get '/submit.php'
|
24
|
+
submit_form = page.form_with :action => './submit.php?action=submit'
|
25
|
+
submit_form.set_fields :problemid => pid,
|
26
|
+
:language => languages[lang],
|
27
|
+
:usercode => code
|
28
|
+
submit_form.submit
|
29
|
+
pid.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
@@selector = '#fixed_table table.table_text tr'
|
33
|
+
@@thead = [:id, :date, :status, :pid, :time, :mem, :length, :lang, :user]
|
34
|
+
|
35
|
+
def status(pid)
|
36
|
+
parse_status '/status.php?user=' + user, @@selector, @@thead do |tr|
|
37
|
+
tr[3] && tr[3].inner_text == pid
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -9,6 +9,7 @@ module OJAgent
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def login(user, pass)
|
12
|
+
@user = user
|
12
13
|
page = get '/'
|
13
14
|
login_form = page.form_with :id => 'mod_loginform'
|
14
15
|
login_form.set_fields :username => user,
|
@@ -17,9 +18,6 @@ module OJAgent
|
|
17
18
|
result = login_form.submit
|
18
19
|
end
|
19
20
|
|
20
|
-
def logout
|
21
|
-
end
|
22
|
-
|
23
21
|
def submit(pid, code, lang)
|
24
22
|
page = get '/index.php?option=com_onlinejudge&Itemid=25'
|
25
23
|
action = 'index.php?option=com_onlinejudge&Itemid=25&page=save_submission'
|
@@ -38,25 +36,14 @@ module OJAgent
|
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
status = (page / '.maincontent table tr').
|
44
|
-
map{|tr| tr / 'td'}.
|
45
|
-
find{|td| !td.empty? && td[0].inner_text == id}
|
39
|
+
@@selector = '.maincontent table tr'
|
40
|
+
@@thead = [:id, :pid, :pname, :status, :lang, :time, :data]
|
46
41
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
:status => status[3].inner_text.strip,
|
53
|
-
:lang => status[4].inner_text,
|
54
|
-
:time => status[5].inner_text,
|
55
|
-
:date => status[6].inner_text
|
56
|
-
}
|
57
|
-
url = status[3] / 'a[href]'
|
58
|
-
ret[:url] = url.map{|a| a['href']} unless url.empty?
|
59
|
-
ret
|
42
|
+
def status(id)
|
43
|
+
url = '/index.php?option=com_onlinejudge&Itemid=9'
|
44
|
+
parse_status url, @@selector, @@thead do |tr|
|
45
|
+
!tr.empty? && tr[0].inner_text == id
|
46
|
+
end
|
60
47
|
end
|
61
48
|
end
|
62
49
|
end
|
data/lib/ojagent/ojagent.rb
CHANGED
@@ -2,6 +2,14 @@ require 'forwardable'
|
|
2
2
|
require 'mechanize'
|
3
3
|
require 'nokogiri'
|
4
4
|
|
5
|
+
class Mechanize::Form::FileUpload
|
6
|
+
def file=(file, default_name = 'noname')
|
7
|
+
data, name = *file
|
8
|
+
self.file_data = data
|
9
|
+
self.file_name = name || default_name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
5
13
|
module OJAgent
|
6
14
|
class LoginFailureError < RuntimeError
|
7
15
|
end
|
@@ -16,9 +24,9 @@ module OJAgent
|
|
16
24
|
|
17
25
|
attr_accessor :retries, :duration
|
18
26
|
|
19
|
-
attr_reader :agent, :base_uri, :languages
|
27
|
+
attr_reader :agent, :base_uri, :languages, :user
|
20
28
|
|
21
|
-
def initialize(base_uri, languages)
|
29
|
+
def initialize(base_uri = nil, languages = {})
|
22
30
|
@agent = Mechanize.new
|
23
31
|
@agent.open_timeout = 20
|
24
32
|
@agent.read_timeout = 60
|
@@ -44,6 +52,10 @@ module OJAgent
|
|
44
52
|
raise NotImplementedError
|
45
53
|
end
|
46
54
|
|
55
|
+
def open(pid)
|
56
|
+
# noop
|
57
|
+
end
|
58
|
+
|
47
59
|
def submit(pid, code, lang)
|
48
60
|
raise NotImplementedError
|
49
61
|
end
|
@@ -52,6 +64,43 @@ module OJAgent
|
|
52
64
|
raise NotImplementedError
|
53
65
|
end
|
54
66
|
|
67
|
+
def parse_status(url, selector, thead, &block)
|
68
|
+
tbody = get(url) / selector
|
69
|
+
tr = tbody.map{|tr| tr / 'td'}.find{|tr| yield tr}
|
70
|
+
return nil unless tr
|
71
|
+
|
72
|
+
ret = {}
|
73
|
+
thead.zip(tr) do |th, td|
|
74
|
+
next unless th && td
|
75
|
+
ret[th] = td.inner_text.strip.gsub(/\s+/, ' ')
|
76
|
+
if th == :status
|
77
|
+
url = td / 'a[href]'
|
78
|
+
if url.size > 0
|
79
|
+
ret[:url] = (ret[:url] || []) + url.map{|a| a['href']}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
ret
|
84
|
+
end
|
85
|
+
|
86
|
+
# Open the problem or download the input. For online judges that ask
|
87
|
+
# users to download the input and upload the output in time limit,
|
88
|
+
# user must open (download the input) before submit (upload the output).
|
89
|
+
# For other online judges, it does nothing.
|
90
|
+
def open!(pid, retries = 1)
|
91
|
+
retries ||= self.retries
|
92
|
+
retries.times do
|
93
|
+
begin
|
94
|
+
ret = open pid
|
95
|
+
return ret if ret
|
96
|
+
rescue
|
97
|
+
end
|
98
|
+
end
|
99
|
+
raise OperationFailureError, "Fail to open"
|
100
|
+
end
|
101
|
+
|
102
|
+
# Submit solution and retry on failure. The return value is a hint for
|
103
|
+
# getting status and varys at different online judges.
|
55
104
|
def submit!(pid, code, lang, retries = nil)
|
56
105
|
retries ||= self.retries
|
57
106
|
retries.times do
|
@@ -64,6 +113,7 @@ module OJAgent
|
|
64
113
|
raise OperationFailureError, "Fail to submit"
|
65
114
|
end
|
66
115
|
|
116
|
+
# Get status and retry on failure.
|
67
117
|
def status!(id, retries = nil, duration = nil)
|
68
118
|
retries ||= self.retries
|
69
119
|
duration ||= self.duration
|
@@ -84,5 +134,11 @@ module OJAgent
|
|
84
134
|
return ret if ret
|
85
135
|
raise OperationFailureError, "Fail to get status"
|
86
136
|
end
|
137
|
+
|
138
|
+
# Submit solution and get the corresponding status.
|
139
|
+
def judge!(pid, code, lang, retries = nil, duration = nil)
|
140
|
+
id = submit!(pid, code, lang, retries)
|
141
|
+
status!(id, retries, duration)
|
142
|
+
end
|
87
143
|
end
|
88
144
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module OJAgent
|
2
|
+
class RosalindAgent < OJAgent
|
3
|
+
def initialize
|
4
|
+
super 'http://rosalind.info'
|
5
|
+
end
|
6
|
+
|
7
|
+
def login(user, pass)
|
8
|
+
@user = user
|
9
|
+
page = get '/accounts/login/'
|
10
|
+
login_form = page.form_with :id => 'id_form_login'
|
11
|
+
login_form.set_fields :username => user,
|
12
|
+
:password => pass
|
13
|
+
login_form.submit
|
14
|
+
end
|
15
|
+
|
16
|
+
def open(pid)
|
17
|
+
get "/problems/#{pid}/dataset/"
|
18
|
+
end
|
19
|
+
|
20
|
+
def submit(pid, ans, src = nil)
|
21
|
+
page = get "/problems/#{pid}/"
|
22
|
+
submit_form = page.form_with :id => 'id_form_submission'
|
23
|
+
submit_form.file_upload_with(:name => 'output_file').file = ans
|
24
|
+
submit_form.file_upload_with(:name => 'code').file = src if src
|
25
|
+
submit_form.submit
|
26
|
+
end
|
27
|
+
|
28
|
+
@@close = "\xc3\x97".force_encoding('utf-8')
|
29
|
+
|
30
|
+
def status(page)
|
31
|
+
status = {}
|
32
|
+
[:error, :warning, :info, :success].each do |type|
|
33
|
+
messages = []
|
34
|
+
(page / "div.main div.alert-#{type}.flash-message").each do |alert|
|
35
|
+
message = alert.inner_text.strip.gsub(@@close, '').gsub(/\s+/, ' ')
|
36
|
+
messages << message unless message.empty?
|
37
|
+
end
|
38
|
+
status[type] = messages unless messages.empty?
|
39
|
+
end
|
40
|
+
status[:status] = status[:success] && !status[:error] ? 'Correct' : 'Wrong'
|
41
|
+
status
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/ojagent/timus_agent.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
module OJAgent
|
2
2
|
class TimusAgent < OJAgent
|
3
|
-
attr_accessor :user, :pass
|
4
|
-
|
5
3
|
def initialize
|
6
4
|
super 'http://acm.timus.ru',
|
7
5
|
:pascal => '3',
|
@@ -12,43 +10,33 @@ module OJAgent
|
|
12
10
|
end
|
13
11
|
|
14
12
|
def login(user, pass)
|
15
|
-
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
def logout
|
13
|
+
@user = user
|
14
|
+
@pass = pass
|
20
15
|
end
|
21
16
|
|
22
17
|
def submit(pid, code, lang)
|
23
18
|
page = get '/submit.aspx'
|
24
19
|
submit_form = page.form_with :action => 'submit.aspx?space=1'
|
25
|
-
submit_form.set_fields :JudgeID => user + pass,
|
20
|
+
submit_form.set_fields :JudgeID => user + @pass,
|
26
21
|
:Language => languages[lang],
|
27
22
|
:ProblemNum => pid,
|
28
23
|
:Source => code
|
29
24
|
submit_form.submit
|
30
|
-
pid
|
25
|
+
pid.to_s
|
31
26
|
end
|
32
27
|
|
33
|
-
|
34
|
-
|
35
|
-
status = (page / 'table.status tr').
|
36
|
-
map{|tr| tr / 'td'}.
|
37
|
-
find{|td| td[3] && td[3].inner_text.start_with?(pid)}
|
28
|
+
@@selector = 'table.status tr'
|
29
|
+
@@thead = [:id, :date, :user, :pname, :lang, :status, :testid, :time, :mem]
|
38
30
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
ret[k] = status[i].inner_text.strip.gsub('\s+', ' ')
|
31
|
+
def status(pid)
|
32
|
+
url = '/status.aspx?author=' + user
|
33
|
+
status = parse_status url, @@selector, @@thead do |tr|
|
34
|
+
tr[3] && tr[3].inner_text.start_with?(pid)
|
44
35
|
end
|
45
|
-
if
|
46
|
-
|
47
|
-
ret[:pname] = $2
|
36
|
+
if status[:pname] =~ /^(\d+).\s*(.*)$/
|
37
|
+
status[:pid], status[:pname] = $1, $2
|
48
38
|
end
|
49
|
-
|
50
|
-
ret[:url] = url.map{|a| a['href']} unless url.empty?
|
51
|
-
ret
|
39
|
+
status
|
52
40
|
end
|
53
41
|
end
|
54
42
|
end
|
data/lib/ojagent/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ojagent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mechanize
|
@@ -94,9 +94,13 @@ files:
|
|
94
94
|
- README.md
|
95
95
|
- Rakefile
|
96
96
|
- bin/quicksubmit
|
97
|
+
- examples/hdoj3337.rb
|
98
|
+
- examples/rosalind.rb
|
97
99
|
- lib/ojagent.rb
|
100
|
+
- lib/ojagent/hdoj_agent.rb
|
98
101
|
- lib/ojagent/livearchive_agent.rb
|
99
102
|
- lib/ojagent/ojagent.rb
|
103
|
+
- lib/ojagent/rosalind_agent.rb
|
100
104
|
- lib/ojagent/timus_agent.rb
|
101
105
|
- lib/ojagent/version.rb
|
102
106
|
- ojagent.gemspec
|