git-contest 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +15 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +24 -0
- data/README.md +217 -0
- data/Rakefile +4 -0
- data/bin/git-contest +43 -0
- data/bin/git-contest-finish +163 -0
- data/bin/git-contest-init +77 -0
- data/bin/git-contest-rebase +76 -0
- data/bin/git-contest-start +68 -0
- data/bin/git-contest-submit +188 -0
- data/git-contest.gemspec +34 -0
- data/lib/contest/driver.rb +15 -0
- data/lib/contest/driver/aizu_online_judge.rb +159 -0
- data/lib/contest/driver/base.rb +103 -0
- data/lib/contest/driver/codeforces.rb +196 -0
- data/lib/contest/driver/common.rb +72 -0
- data/lib/contest/driver/driver_event.rb +30 -0
- data/lib/contest/driver/uva_online_judge.rb +131 -0
- data/lib/git/contest.rb +14 -0
- data/lib/git/contest/common.rb +48 -0
- data/lib/git/contest/git.rb +189 -0
- data/lib/git/contest/test.rb +10 -0
- data/lib/git/contest/version.rb +12 -0
- data/spec/bin/t004_git_contest_submit_spec.rb +143 -0
- data/spec/bin/t005_git_contest_branching_spec.rb +83 -0
- data/spec/bin/t007_git_contest_start_spec.rb +121 -0
- data/spec/bin/t008_git_contest_finish_spec.rb +229 -0
- data/spec/bin/t009_git_contest_init_spec.rb +82 -0
- data/spec/lib/contest/driver/t001_aizu_online_judge_spec.rb +177 -0
- data/spec/lib/contest/driver/t002_codeforces_spec.rb +34 -0
- data/spec/lib/contest/driver/t003_uva_online_judge_spec.rb +33 -0
- data/spec/mock/default_config/config.yml +0 -0
- data/spec/mock/default_config/plugins/driver_dummy.rb +113 -0
- data/spec/mock/t001/002.status_log.xml +55 -0
- data/spec/mock/t001/config.yml +5 -0
- data/spec/mock/t001/description.html +48 -0
- data/spec/mock/t001/status.html +49 -0
- data/spec/mock/t001/status_log.xml +29 -0
- data/spec/mock/t002/my_submissions.html +58 -0
- data/spec/mock/t003/after_submit.html +28 -0
- data/spec/mock/t003/my_submissions.compile_error.html +160 -0
- data/spec/mock/t003/my_submissions.sent_to_judge.html +358 -0
- data/spec/mock/t004/config.yml +17 -0
- data/spec/mock/t005/001/config.yml +5 -0
- data/spec/mock/t006/001/001/001/config.yml +8 -0
- data/spec/mock/t006/001/001/002/config.yml +8 -0
- data/spec/mock/t006/001/001/003/config.yml +8 -0
- data/spec/mock/t006/001/002/001/config.yml +8 -0
- data/spec/mock/t006/001/002/002/config.yml +8 -0
- data/spec/mock/t006/001/002/003/config.yml +8 -0
- data/spec/mock/t006/001/003/001/config.yml +9 -0
- data/spec/mock/t006/001/003/002/config.yml +9 -0
- data/spec/mock/t006/001/003/003/config.yml +9 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/spec_list.txt +1 -0
- data/spec/t006_config_spec.rb +168 -0
- metadata +269 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
#
|
2
|
+
# common.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
|
+
# Licensed under the MIT-License.
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'mechanize'
|
9
|
+
require 'nokogiri'
|
10
|
+
require 'trollop'
|
11
|
+
require 'contest/driver/base'
|
12
|
+
|
13
|
+
module Contest
|
14
|
+
module Driver
|
15
|
+
module Utils
|
16
|
+
def self.resolve_path path
|
17
|
+
path = `ls #{path} | cat | head -n 1`
|
18
|
+
path.strip
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.resolve_language path
|
22
|
+
regexp = /\.([a-z0-9]+)$/
|
23
|
+
if path.match(regexp)
|
24
|
+
return path.match(regexp)[1]
|
25
|
+
else
|
26
|
+
return nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.normalize_language label
|
31
|
+
case label
|
32
|
+
when "c", "C"
|
33
|
+
return "clang"
|
34
|
+
when "cpp", "C++", "c++"
|
35
|
+
return "cpp"
|
36
|
+
when "c++11", "C++11"
|
37
|
+
return "cpp11"
|
38
|
+
when "cs", "c#", "C#"
|
39
|
+
return "cs"
|
40
|
+
when "d", "D", "dlang"
|
41
|
+
return "dlang"
|
42
|
+
when "go", "golang"
|
43
|
+
return "golang"
|
44
|
+
when "hs", "haskell", "Haskell"
|
45
|
+
return "haskell"
|
46
|
+
when "java", "Java"
|
47
|
+
return "java"
|
48
|
+
when "ocaml", "ml", "OCaml"
|
49
|
+
return "ocaml"
|
50
|
+
when "Delphi", "delphi"
|
51
|
+
return "delphi"
|
52
|
+
when "pascal", "Pascal"
|
53
|
+
return "pascal"
|
54
|
+
when "perl", "Perl", "pl"
|
55
|
+
return "perl"
|
56
|
+
when "php", "PHP"
|
57
|
+
return "php"
|
58
|
+
when "python2"
|
59
|
+
return "python2"
|
60
|
+
when "python3", "python", "Python", "py"
|
61
|
+
return "python3"
|
62
|
+
when "ruby", "rb", "Ruby"
|
63
|
+
return "ruby"
|
64
|
+
when "scala", "Scala"
|
65
|
+
return "scala"
|
66
|
+
else
|
67
|
+
abort "unknown language @ normalize language"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#
|
2
|
+
# driver_event.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
|
+
# Licensed under the MIT-License.
|
6
|
+
#
|
7
|
+
|
8
|
+
module Contest
|
9
|
+
module Driver
|
10
|
+
class DriverEvent
|
11
|
+
def initialize
|
12
|
+
@callbacks = {}
|
13
|
+
end
|
14
|
+
def on(type, proc)
|
15
|
+
@callbacks[type] = [] unless @callbacks.has_key?(type)
|
16
|
+
@callbacks[type].push proc
|
17
|
+
end
|
18
|
+
def off(type, proc)
|
19
|
+
@callbacks[type] = [] unless @callbacks.has_key?(type)
|
20
|
+
@callbacks[type].delete proc
|
21
|
+
end
|
22
|
+
def trigger(type, *params)
|
23
|
+
@callbacks[type] = [] unless @callbacks.has_key?(type)
|
24
|
+
@callbacks[type].each do |proc|
|
25
|
+
proc.call *params
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
#
|
2
|
+
# uva_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
|
+
|
10
|
+
module Contest
|
11
|
+
module Driver
|
12
|
+
class UvaOnlineJudge < DriverBase
|
13
|
+
def get_opts_ext
|
14
|
+
define_options do
|
15
|
+
opt(
|
16
|
+
:problem_id,
|
17
|
+
"Problem ID (Ex: 100, 200, etc...)",
|
18
|
+
:type => :string,
|
19
|
+
:required => true,
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_site_name
|
25
|
+
"UVa"
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_desc
|
29
|
+
"UVa Online Judge (URL: http://uva.onlinejudge.org/)"
|
30
|
+
end
|
31
|
+
|
32
|
+
def resolve_language(label)
|
33
|
+
case label
|
34
|
+
when "c"
|
35
|
+
return "1"
|
36
|
+
when "cpp"
|
37
|
+
return "3"
|
38
|
+
when "java"
|
39
|
+
return "2"
|
40
|
+
when "pascal"
|
41
|
+
return "4"
|
42
|
+
else
|
43
|
+
abort "unknown language"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def submit_ext(config, source_path, options)
|
48
|
+
trigger 'start'
|
49
|
+
problem_id = options[:problem_id]
|
50
|
+
|
51
|
+
@client = Mechanize.new {|agent|
|
52
|
+
agent.user_agent_alias = 'Windows IE 7'
|
53
|
+
}
|
54
|
+
|
55
|
+
# submit
|
56
|
+
trigger 'before_login'
|
57
|
+
login_page = @client.get 'http://uva.onlinejudge.org/'
|
58
|
+
login_page.form_with(:id => 'mod_loginform') do |form|
|
59
|
+
form.username = config["user"]
|
60
|
+
form.passwd = config["password"]
|
61
|
+
end.submit
|
62
|
+
trigger 'after_login'
|
63
|
+
|
64
|
+
trigger 'before_submit', options
|
65
|
+
submit_page = @client.get 'http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=25'
|
66
|
+
res_page = submit_page.form_with(:action => 'index.php?option=com_onlinejudge&Itemid=25&page=save_submission') do |form|
|
67
|
+
form.localid = problem_id
|
68
|
+
form.radiobutton_with(:name => 'language', :value => options[:language]).check
|
69
|
+
form.code = File.read(source_path)
|
70
|
+
end.submit
|
71
|
+
trigger 'after_submit'
|
72
|
+
|
73
|
+
# <div class="message">Submission received with ID 12499981</div>
|
74
|
+
trigger 'before_wait'
|
75
|
+
submission_id = get_submission_id(res_page.body)
|
76
|
+
status = get_status_wait(submission_id)
|
77
|
+
trigger(
|
78
|
+
'after_wait',
|
79
|
+
{
|
80
|
+
:submission_id => submission_id,
|
81
|
+
:status => status,
|
82
|
+
:result => get_commit_message($config["submit_rules"]["message"], status, options),
|
83
|
+
}
|
84
|
+
)
|
85
|
+
|
86
|
+
trigger 'finish'
|
87
|
+
get_commit_message($config["submit_rules"]["message"], status, options)
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_status_wait(submission_id)
|
91
|
+
submission_id = submission_id.to_s
|
92
|
+
# wait result
|
93
|
+
12.times do
|
94
|
+
sleep 10
|
95
|
+
my_page = @client.get 'http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=9'
|
96
|
+
status = get_submission_status(submission_id, my_page.body)
|
97
|
+
return status unless status == 'Sent to judge' || status == ''
|
98
|
+
trigger 'retry'
|
99
|
+
end
|
100
|
+
trigger 'timeout'
|
101
|
+
return 'timeout'
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_submission_id(body)
|
105
|
+
doc = Nokogiri::HTML(body)
|
106
|
+
text = doc.xpath('//div[@class="message"]')[0].text().strip
|
107
|
+
# Submission received with ID 12500010
|
108
|
+
text.match(/Submission received with ID ([0-9]+)/)[1]
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_submission_status(submission_id, body)
|
112
|
+
doc = Nokogiri::HTML(body)
|
113
|
+
doc.xpath('//tr[@class="sectiontableheader"]/following-sibling::node()').search('tr').each do |elm|
|
114
|
+
td_list = elm.search('td')
|
115
|
+
item_submission_id = td_list[0].text.strip
|
116
|
+
if item_submission_id == submission_id
|
117
|
+
item_problem_id = td_list[1].text.strip
|
118
|
+
item_status = td_list[3].text.strip
|
119
|
+
return item_status
|
120
|
+
end
|
121
|
+
end
|
122
|
+
'timeout'
|
123
|
+
end
|
124
|
+
|
125
|
+
if is_test_mode?
|
126
|
+
attr_writer :client
|
127
|
+
else
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/lib/git/contest.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#
|
2
|
+
# common.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
|
+
# Licensed under the MIT-License.
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'git/contest/version'
|
9
|
+
require 'git/contest/test'
|
10
|
+
require 'git/contest/git'
|
11
|
+
require 'contest/driver'
|
12
|
+
require 'yaml'
|
13
|
+
|
14
|
+
def init
|
15
|
+
init_global
|
16
|
+
init_home
|
17
|
+
end
|
18
|
+
|
19
|
+
def init_global
|
20
|
+
$GIT_CONTEST_HOME = File.expand_path(ENV['GIT_CONTEST_HOME'] || "~/.git-contest")
|
21
|
+
$GIT_CONTEST_CONFIG = File.expand_path(ENV['GIT_CONTEST_CONFIG'] || "#{$GIT_CONTEST_HOME}/config.yml")
|
22
|
+
if git_do_no_echo 'branch'
|
23
|
+
$MASTER = git_do 'config --get git.contest.branch.master'
|
24
|
+
$PREFIX = git_do 'config --get git.contest.branch.prefix'
|
25
|
+
$ORIGIN = git_do 'config --get git.contest.origin'
|
26
|
+
if $ORIGIN == ''
|
27
|
+
$ORIGIN = 'origin'
|
28
|
+
end
|
29
|
+
$GIT_CONTEST_GIT_OK = true
|
30
|
+
else
|
31
|
+
$GIT_CONTEST_GIT_OK = false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def init_home
|
36
|
+
if ! FileTest.exists? $GIT_CONTEST_HOME
|
37
|
+
FileUtils.mkdir $GIT_CONTEST_HOME
|
38
|
+
end
|
39
|
+
if ! FileTest.exists? $GIT_CONTEST_CONFIG
|
40
|
+
FileUtils.touch $GIT_CONTEST_CONFIG
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_config
|
45
|
+
config_path = File.expand_path($GIT_CONTEST_CONFIG)
|
46
|
+
YAML.load_file config_path
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,189 @@
|
|
1
|
+
#
|
2
|
+
# git.rb
|
3
|
+
#
|
4
|
+
# Copyright (c) 2013 Hiroyuki Sano <sh19910711 at gmail.com>
|
5
|
+
# Licensed under the MIT-License.
|
6
|
+
#
|
7
|
+
|
8
|
+
def git_do(*args)
|
9
|
+
puts "git #{args.join(' ')}" if ENV['GIT_CONTEST_DEBUG'] == 'ON'
|
10
|
+
return `git #{args.join(' ')} 2>&1`.strip
|
11
|
+
end
|
12
|
+
|
13
|
+
# use return value
|
14
|
+
def git_do_no_echo(*args)
|
15
|
+
puts "git #{args.join(' ')}" if ENV['GIT_CONTEST_DEBUG'] == 'ON'
|
16
|
+
system "git #{args.join(' ')} >/dev/null 2>&1"
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
def git_contest_is_initialized
|
21
|
+
git_contest_has_master_configured &&
|
22
|
+
git_contest_has_prefix_configured &&
|
23
|
+
git_do('config --get git.contest.branch.master') != git_do('config --get git.contest.branch.develop')
|
24
|
+
end
|
25
|
+
|
26
|
+
def git_contest_has_master_configured
|
27
|
+
master = (git_do 'config --get git.contest.branch.master').strip
|
28
|
+
master != '' && git_local_branches().include?(master)
|
29
|
+
end
|
30
|
+
|
31
|
+
def git_contest_has_develop_configured
|
32
|
+
develop = (git_do 'config --get git.contest.branch.develop').strip
|
33
|
+
develop != '' && git_local_branches().include?(develop)
|
34
|
+
end
|
35
|
+
|
36
|
+
def git_contest_has_prefix_configured
|
37
|
+
git_do_no_echo 'config --get git.contest.branch.prefix'
|
38
|
+
end
|
39
|
+
|
40
|
+
def git_contest_resolve_nameprefix name, prefix
|
41
|
+
if git_local_branch_exists "#{prefix}/#{name}"
|
42
|
+
return name
|
43
|
+
end
|
44
|
+
branches = git_local_branches().select {|branch| branch.start_with? "#{prefix}/#{name}" }
|
45
|
+
if branches.size == 0
|
46
|
+
abort "No branch matches prefix '#{name}'"
|
47
|
+
else
|
48
|
+
if branches.size == 1
|
49
|
+
return branches[0][prefix.length..-1]
|
50
|
+
else
|
51
|
+
abort "Multiple branches match prefix '#{name}'"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
def git_remote_branch_exists(branch_name)
|
58
|
+
git_remote_branches().include?(branch_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def git_local_branch_exists(branch_name)
|
62
|
+
git_local_branches().include?(branch_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def git_branch_exists(branch_name)
|
66
|
+
git_all_branches().include?(branch_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def git_remote_branches
|
70
|
+
cmd_ret = git_do 'branch -r --no-color'
|
71
|
+
cmd_ret.lines.map {|line|
|
72
|
+
line.gsub(/^[*]?\s*/, '').gsub(/\s*$/, '').strip
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def git_local_branches
|
77
|
+
cmd_ret = git_do 'branch --no-color'
|
78
|
+
cmd_ret.lines.map {|line|
|
79
|
+
line.gsub(/^[*]?\s*/, '').gsub(/\s*$/, '').strip
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def git_all_branches
|
84
|
+
cmd_ret1 = git_do 'branch --no-color'
|
85
|
+
cmd_ret2 = git_do 'branch -r --no-color'
|
86
|
+
lines = ( cmd_ret1 + cmd_ret2 ).lines
|
87
|
+
lines.map {|line|
|
88
|
+
line.gsub(/^[*]?\s*/, '').gsub(/\s*$/, '').strip
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def git_current_branch
|
93
|
+
ret = git_do('branch --no-color').lines
|
94
|
+
ret = ret.grep /^\*/
|
95
|
+
ret[0].gsub(/^[* ] /, '').strip
|
96
|
+
end
|
97
|
+
|
98
|
+
def git_is_clean_working_tree
|
99
|
+
if ! git_do_no_echo 'diff --no-ext-diff --ignore-submodules --quiet --exit-code'
|
100
|
+
return 1
|
101
|
+
elsif ! git_do_no_echo 'diff-index --cached --quiet --ignore-submodules HEAD --'
|
102
|
+
return 2
|
103
|
+
else
|
104
|
+
return 0
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def git_repo_is_headless
|
109
|
+
! git_do_no_echo 'rev-parse --quiet --verify HEAD'
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# 0: same
|
114
|
+
# 1: first branch needs ff
|
115
|
+
# 2: second branch needs ff
|
116
|
+
# 3: branch needs merge
|
117
|
+
# 4: there is no merge
|
118
|
+
#
|
119
|
+
def git_compare_branches first, second
|
120
|
+
commit1 = git_do "rev-parse \"#{first}\""
|
121
|
+
commit2 = git_do "rev-parse \"#{second}\""
|
122
|
+
if commit1 != commit2
|
123
|
+
if git_do_no_echo("merge-base \"#{commit1}\" \"#{commit2}\"") > 0
|
124
|
+
return 4
|
125
|
+
else
|
126
|
+
base = git_do "merge-base \"#{commit1}\" \"#{commit2}\""
|
127
|
+
if commit1 == base
|
128
|
+
return 1
|
129
|
+
elsif commit2 == base
|
130
|
+
return 2
|
131
|
+
else
|
132
|
+
return 3
|
133
|
+
end
|
134
|
+
end
|
135
|
+
else
|
136
|
+
return 0
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def require_branch(branch)
|
141
|
+
if ! git_all_branches().include?(branch)
|
142
|
+
abort "Branch #{branch} does not exist."
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def require_branch_absent(branch)
|
147
|
+
if git_all_branches().include?(branch)
|
148
|
+
abort "Branch #{branch} already exists. Pick another name."
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def require_clean_working_tree
|
153
|
+
ret = git_is_clean_working_tree
|
154
|
+
if ret == 1
|
155
|
+
abort "fatal: Working tree contains unstaged changes. Aborting."
|
156
|
+
end
|
157
|
+
if ret == 2
|
158
|
+
abort "fatal: Index contains uncommited changes. Aborting."
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def require_local_branch branch
|
163
|
+
if ! git_local_branch_exists branch
|
164
|
+
abort "fatal: Local branch '#{branch}' does not exist and is required."
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def require_remote_branch branch
|
169
|
+
if ! git_remote_branch_exists branch
|
170
|
+
abort "fatal: Remote branch '#{branch}' does not exist and is required."
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def require_branches_equal local, remote
|
175
|
+
require_local_branch local
|
176
|
+
require_remote_branch remote
|
177
|
+
ret = git_compare_branches local, remote
|
178
|
+
if ret > 0
|
179
|
+
puts "Branches '#{local}' and '#{remote}' have diverged."
|
180
|
+
if ret == 1
|
181
|
+
abort "And branch #{local} may be fast-forwarded."
|
182
|
+
elsif ret == 2
|
183
|
+
puts "And local branch #{local} is ahead of #{remote}"
|
184
|
+
else
|
185
|
+
abort "Branches need merging first."
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|