git-contest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,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
@@ -0,0 +1,14 @@
1
+ #
2
+ # contest.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
+
10
+ module Git
11
+ module Contest
12
+ # Your code goes here...
13
+ end
14
+ end
@@ -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
+