gettc 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +41 -0
  3. data/bin/gettc +63 -0
  4. data/core/lib/topcoder.rb +3 -0
  5. data/core/lib/topcoder/download.rb +89 -0
  6. data/core/lib/topcoder/generate.rb +131 -0
  7. data/core/lib/topcoder/parse.rb +231 -0
  8. data/core/lib/topcoder/print.rb +56 -0
  9. data/core/lib/topcoder/problem.rb +33 -0
  10. data/core/lib/topcoder/signature.rb +55 -0
  11. data/core/lib/topcoder/types.rb +62 -0
  12. data/core/test/topcoder/download_test.rb +29 -0
  13. data/core/test/topcoder/generate_test.rb +31 -0
  14. data/core/test/topcoder/parse_test.rb +104 -0
  15. data/core/test/topcoder/signature_test.rb +52 -0
  16. data/core/test/topcoder/types_test.rb +24 -0
  17. data/dist/config.yml +2 -0
  18. data/dist/include/cpp/engine.rb +90 -0
  19. data/dist/include/cpp/topcoder +212 -0
  20. data/dist/include/haskell/TopCoder.hs +82 -0
  21. data/dist/include/haskell/engine.rb +122 -0
  22. data/dist/include/java/TopCoder.jar +0 -0
  23. data/dist/include/java/engine.rb +207 -0
  24. data/dist/template/bin/runner.sh +113 -0
  25. data/dist/template/data/demo/{examples_d} +0 -0
  26. data/dist/template/data/sys/{systests_d} +0 -0
  27. data/dist/template/prob/images/{images_d} +0 -0
  28. data/dist/template/prob/{name}.html +8 -0
  29. data/dist/template/prob/{name}.md +1 -0
  30. data/dist/template/solve/cpp/Makefile +30 -0
  31. data/dist/template/solve/cpp/{name}.cpp +11 -0
  32. data/dist/template/solve/cpp/{name}Runner.cpp +26 -0
  33. data/dist/template/solve/cpp/{name}Test.cpp +8 -0
  34. data/dist/template/solve/haskell/Makefile +30 -0
  35. data/dist/template/solve/haskell/{name}.hs +7 -0
  36. data/dist/template/solve/haskell/{name}Runner.hs +27 -0
  37. data/dist/template/solve/haskell/{name}Test.hs +10 -0
  38. data/dist/template/solve/java/build.xml +78 -0
  39. data/dist/template/solve/java/{name}.java +9 -0
  40. data/dist/template/solve/java/{name}Runner.java +32 -0
  41. data/dist/template/solve/java/{name}Test.java +8 -0
  42. data/dist/template/util/check/Makefile +5 -0
  43. data/dist/template/util/check/check.cpp +26 -0
  44. metadata +121 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c80f45fecd9b96df8986e44dc295099b2d3dfa04
4
+ data.tar.gz: 78336c74d9779a92f2c6b7121b534f5e79fe5f3c
5
+ SHA512:
6
+ metadata.gz: 7962d7609c319a030aa2cfbb1cb30f05a8cfc5f0461948ed6738109dabd76473828b8c4182d7590588f2f49366c42909866edb5080903d4fb43689006d1406e0
7
+ data.tar.gz: e35c776a854fb551e71aa9bc87b872467b942b211fe6df7201ef17fcfee60540f6415b21be0ce79c5885439d00d8f2c2996baf6907604707d2bfec24adae8f38
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ require 'rake/testtask'
2
+ require 'rubygems/package_task'
3
+ require 'rubygems/installer'
4
+ require 'fileutils'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'core/lib'
8
+ t.pattern = 'core/test/**/*_test.rb'
9
+ end
10
+
11
+ spec = Gem::Specification.new do |s|
12
+ s.platform = Gem::Platform::RUBY
13
+ s.name = 'gettc'
14
+ s.summary = 'Download TopCoder problem and generate a skeleton solution'
15
+ s.description = 'Given a TopCoder problem ID, gettc downloads the problem specification, parses the whole thing into a Markdown file, generates inputs/outputs based on the Examples and System Tests given, and finally generates basic solution files for you to get started.'
16
+ s.version = '1.2.2'
17
+
18
+ s.author = 'Seri'
19
+ s.email = 'seritrinh@gmail.com'
20
+ s.homepage = 'http://seriessays.blogspot.com'
21
+
22
+ s.files = FileList["{bin,dist,core/lib}/**/*"].to_a + ['Rakefile']
23
+ s.test_files = FileList["core/test/**/*_test.rb"].to_a
24
+ s.require_path = 'core/lib'
25
+ s.has_rdoc = false
26
+
27
+ s.bindir = 'bin'
28
+ s.executables = ['gettc']
29
+
30
+ s.add_dependency 'hpricot'
31
+ s.add_dependency 'bluecloth'
32
+ end
33
+ Gem::PackageTask.new spec do |pkg|
34
+ pkg.need_tar = true
35
+ end
36
+
37
+ task :clean do
38
+ if File.exists? 'pkg' then
39
+ FileUtils.rm_rf 'pkg'
40
+ end
41
+ end
data/bin/gettc ADDED
@@ -0,0 +1,63 @@
1
+ #! /usr/bin/ruby
2
+
3
+ require 'topcoder'
4
+ include TopCoder
5
+
6
+ require 'rubygems'
7
+ require 'fileutils'
8
+ require 'yaml'
9
+
10
+ def root_d
11
+ gem_d = Gem.loaded_specs['gettc']
12
+ if gem_d.nil? then
13
+ return File.join(File.basename(__FILE__), '../')
14
+ else
15
+ return gem_d.full_gem_path
16
+ end
17
+ end
18
+
19
+ def load_config
20
+ config_d = File.join File.expand_path('~'), '.gettc'
21
+ unless Dir.exists? config_d then
22
+ FileUtils.mkdir config_d
23
+ default_d = File.join root_d, 'dist/.'
24
+ FileUtils.cp_r default_d, config_d
25
+ end
26
+ config = YAML.load_file File.join(config_d, 'config.yml')
27
+ account = Account.new config['username'], config['password']
28
+ source_d = File.join config_d, 'template'
29
+ return account, source_d
30
+ end
31
+
32
+ def main
33
+ prog = File.basename __FILE__
34
+ usage = "Usage: #{prog} <id>\nWhere <id> is the problem ID, eg 11138"
35
+ if ARGV.empty?
36
+ puts usage
37
+ else
38
+ id = ARGV[0].to_i
39
+ puts "You have given ID = #{id}"
40
+ begin
41
+ account, source_d = load_config
42
+ robot = Downloader.new account
43
+ parser = Parser.new robot
44
+ generator = Generator.new source_d, Dir.getwd
45
+
46
+ print 'Downloading problem to raw HTML ... '
47
+ html = robot.download_problem id
48
+ puts 'Done'
49
+
50
+ print 'Parsing problem from raw HTML ... '
51
+ prob = parser.parse html
52
+ puts 'Done'
53
+
54
+ print "Generating problem diectory for #{prob.name} ... "
55
+ generator.generate prob
56
+ puts 'Done'
57
+ rescue => err
58
+ $stderr.puts err
59
+ exit -1
60
+ end
61
+ end
62
+ end
63
+ main
@@ -0,0 +1,3 @@
1
+ require 'topcoder/download'
2
+ require 'topcoder/parse'
3
+ require 'topcoder/generate'
@@ -0,0 +1,89 @@
1
+ require 'net/https'
2
+ require 'cgi'
3
+ require 'uri'
4
+
5
+ module TopCoder
6
+ class Account
7
+ attr_accessor :username, :password
8
+ def initialize username, password
9
+ @username = username
10
+ @password = password
11
+ end
12
+ def to_s
13
+ return "#{@username}|#{@password}"
14
+ end
15
+ end
16
+ class DownloadError < StandardError
17
+ end
18
+ class LoginFailed < DownloadError
19
+ attr_accessor :account, :cookie
20
+ def initialize account, cookie, msg = 'Wrong username or password'
21
+ @account = account
22
+ @cookie = cookie
23
+ super "#{msg}\nAccount: #{@account}\nCookie: #{@cookie}\n"
24
+ end
25
+ end
26
+ class IDNotAvailable < DownloadError
27
+ attr_accessor :id
28
+ def initialize id, msg = 'ID not available'
29
+ @id = id
30
+ super "#{msg} (#{id})"
31
+ end
32
+ end
33
+ class Downloader
34
+ ROOT = 'http://community.topcoder.com'
35
+ LIMIT = 10
36
+ def initialize account
37
+ @account = account
38
+ @raw = get_cookie
39
+ end
40
+ def get_cookie
41
+ uri = URI.join(ROOT, 'tc?&module=Login')
42
+
43
+ req = Net::HTTP::Post.new uri.request_uri
44
+ req.set_form_data({'username' => @account.username,
45
+ 'password' => @account.password,
46
+ 'rem' => 'on' })
47
+
48
+ http = Net::HTTP.new uri.host, uri.port
49
+ res = http.request req
50
+ raw = res['set-cookie']
51
+
52
+ cookie = CGI::Cookie.parse raw
53
+ if cookie['main_user_id_1'].empty? or
54
+ cookie['main_tcsso_1'].empty? then
55
+ raise LoginFailed.new @account, cookie
56
+ end
57
+
58
+ return raw
59
+ end
60
+ def download url
61
+ uri = url
62
+ unless uri.is_a? URI then
63
+ uri = url.start_with?('http') ? URI.parse(url) : URI.join(ROOT, url)
64
+ end
65
+ LIMIT.times do
66
+ req = Net::HTTP::Get.new uri.request_uri
67
+ req['cookie'] = @raw
68
+
69
+ http = Net::HTTP.new uri.host, uri.port
70
+ res = http.request req
71
+
72
+ return res.body if res.is_a? Net::HTTPSuccess
73
+ unless res.is_a? Net::HTTPMovedPermanently then
74
+ raise DownloadError.new res.class.to_s
75
+ end
76
+ uri = URI.parse res['location']
77
+ end
78
+ raise DownloadError.new "Tried #{LIMIT} times without success"
79
+ end
80
+ def download_problem id
81
+ url = "/stat?c=problem_statement&pm=#{id}"
82
+ body = download url
83
+ if body.match('<h3>Problem Statement</h3>').nil? then
84
+ raise IDNotAvailable.new id
85
+ end
86
+ return body
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,131 @@
1
+ require 'fileutils'
2
+ require 'erb'
3
+
4
+ require 'topcoder/problem'
5
+ require 'topcoder/signature'
6
+ require 'topcoder/print'
7
+
8
+ module TopCoder
9
+ class GenerateError < StandardError
10
+ end
11
+ class ProblemDirExists < GenerateError
12
+ attr_accessor :dir
13
+ def initialize dir, msg = nil
14
+ if msg.nil? then
15
+ msg = 'Cannot create problem directory because it already exists'
16
+ end
17
+ @dir = dir
18
+ super "#{msg} (#{dir})"
19
+ end
20
+ end
21
+ class SourceDirNotExist < GenerateError
22
+ attr_accessor :dir
23
+ def initialize dir, msg = 'Source directory does not exist'
24
+ @dir = dir
25
+ super "#{msg} (#{dir})"
26
+ end
27
+ end
28
+ class TargetDirNotExist < GenerateError
29
+ attr_accessor :dir
30
+ def initialize dir, msg = 'Target directory does not exist'
31
+ @dir = dir
32
+ super "#{msg} (#{dir})"
33
+ end
34
+ end
35
+ class Generator
36
+ def initialize source_d, target_d
37
+ raise SourceDirNotExist.new source_d if not File.directory? source_d
38
+ raise TargetDirNotExist.new target_d if not File.directory? target_d
39
+ @source_d = source_d
40
+ @target_d = target_d
41
+ end
42
+ def gen_images images, images_d
43
+ images.each do |image|
44
+ filename = File.join images_d, image.name
45
+ File.open filename, 'wb' do |f| f.write image.content end
46
+ end
47
+ end
48
+ def gen_cases cases, data_d
49
+ cases.each_index do |i|
50
+ c = cases[i]
51
+ File.open File.join(data_d, "#{i.to_s}.in"), 'w' do |f|
52
+ f.write c.input
53
+ end
54
+ File.open File.join(data_d, "#{i.to_s}.out"), 'w' do |f|
55
+ f.write c.output
56
+ end
57
+ end
58
+ end
59
+ def gen_template source, target
60
+ before = File.open source, 'r' do |f| f.read end
61
+ begin
62
+ after = ERB.new(before).result @context
63
+ rescue StandardError => err
64
+ puts "Template error (#{File.expand_path source}): "
65
+ puts err.backtrace.join "\n"
66
+ end
67
+ File.open target, 'w' do |f| f.write after end
68
+ end
69
+ def filter target_d, name
70
+ if name == '{images_d}' then
71
+ gen_images @prob.images, target_d
72
+ elsif name == '{examples_d}' then
73
+ gen_cases @prob.examples, target_d
74
+ elsif name == '{systests_d}' then
75
+ gen_cases @prob.systests, target_d
76
+ else
77
+ target_n = name.gsub /\{(\w*)\}/ do |match|
78
+ @prob.name if $1 == 'name'
79
+ end
80
+ return target_n
81
+ end
82
+ return nil
83
+ end
84
+ def load_engines
85
+ include_d = File.join File.expand_path('~'), '.gettc/include'
86
+ return if not File.exists? include_d
87
+ Dir.foreach include_d do |name|
88
+ child = File.join include_d, name
89
+ if File.directory? child then
90
+ engine = File.join child, 'engine.rb'
91
+ require engine if File.exists? engine
92
+ end
93
+ end
94
+ end
95
+ def walk source_d, target_d
96
+ Dir.foreach source_d do |name|
97
+ if name != '.' and name != '..' then
98
+ source_p = File.join source_d, name
99
+ target_n = filter target_d, name
100
+ if not target_n.nil? then
101
+ target_p = File.join target_d, target_n
102
+ if File.directory? source_p then
103
+ FileUtils.mkdir target_p if not File.exists? target_p
104
+ walk source_p, target_p
105
+ elsif File.file? source_p then
106
+ gen_template source_p, target_p
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ def generate prob
113
+ @prob = prob
114
+ @prob_d = File.join @target_d, prob.name
115
+ raise ProblemDirExists.new @prob_d if File.exists? @prob_d
116
+ FileUtils.mkdir @prob_d
117
+
118
+ method_sig = @prob.definitions['Method signature']
119
+ if method_sig.nil? then
120
+ $stderr.puts '[Warning] No definition for method signature found'
121
+ else
122
+ vars = parse_method_signature method_sig
123
+ func = vars.shift
124
+ end
125
+ @context = binding
126
+
127
+ load_engines
128
+ walk @source_d, @prob_d
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,231 @@
1
+ require 'topcoder/problem'
2
+ require 'topcoder/download'
3
+
4
+ require 'uri'
5
+ require 'pathname'
6
+ require 'hpricot'
7
+
8
+ module TopCoder
9
+ class Parser
10
+ def initialize downloader
11
+ @downloader = downloader
12
+ @images = []
13
+ end
14
+
15
+ ## @section Utils
16
+
17
+ def indexes str, substr
18
+ from = str.index substr
19
+ return nil if from.nil?
20
+ to = from + substr.size
21
+ return from - 1, to
22
+ end
23
+ def filter html
24
+ unless html.valid_encoding? then
25
+ html = html.encode Encoding::UTF_8, invaid: :replace, undef: :replace, replace: ''
26
+ end
27
+ html.gsub! /<b>(\w*)<\/b>/ do |match| "*#{$1}*" end
28
+ html.gsub! /<sup>(\w*)<\/sup>/ do |match| "^(#{$1})" end
29
+ html.gsub! '&#160;', ''
30
+ html.gsub! '&nbsp;', ' '
31
+ text = Hpricot(html).to_plain_text
32
+ text.gsub! /\[img:(http:\/\/[^\]]*)\]/ do |match|
33
+ url = $1
34
+ image = Image.new
35
+ image.name = Pathname.new(url).basename
36
+ begin
37
+ image.content = @downloader.download url
38
+ @images << image
39
+ "![image](images/#{image.name})"
40
+ rescue StandardError
41
+ "![image](#{url})"
42
+ end
43
+ end
44
+ return text
45
+ end
46
+ def h3 tag
47
+ return "<h3>#{tag}</h3>"
48
+ end
49
+
50
+ ## @section Parse problem parts
51
+
52
+ def parse_name html
53
+ html.sub! 'Problem Statement for', ''
54
+ return filter html
55
+ end
56
+ def parse_statement html
57
+ return filter html
58
+ end
59
+ def parse_definitions html
60
+ defs = { }
61
+ Hpricot(html).search '/tr/td.statText/table/tr' do |tr|
62
+ tds = tr.search '/td.statText'
63
+ if tds.size == 2 then
64
+ key = tds[0].to_plain_text[0 .. -2]
65
+ value = tds[1].to_plain_text
66
+ defs[key] = value
67
+ end
68
+ end
69
+ return defs
70
+ end
71
+ def parse_notes html
72
+ notes = []
73
+ Hpricot(html).search '/tr' do |tr|
74
+ tds = tr.search '/td.statText'
75
+ notes << filter(tds[1].html) if tds.size == 2
76
+ end
77
+ return notes
78
+ end
79
+ def parse_constraints html
80
+ return parse_notes html
81
+ end
82
+
83
+ ## @section Parse cases
84
+
85
+ def filter_inout text
86
+ text.gsub! '{', '['
87
+ text.gsub! '}', ']'
88
+ return text.strip
89
+ end
90
+ def parse_input html
91
+ text = nil
92
+ Hpricot(html).search '/table/tr/td.statText' do |td|
93
+ input = td.to_plain_text.strip
94
+ if text.nil? then
95
+ text = input
96
+ else
97
+ text << ",\n" << input
98
+ end
99
+ end
100
+ return filter_inout text
101
+ end
102
+ def parse_output html
103
+ text = Hpricot(html).to_plain_text
104
+ text.sub! 'Returns: ', ''
105
+ return filter_inout text
106
+ end
107
+ def parse_reason html
108
+ return filter html
109
+ end
110
+ def parse_examples html
111
+ examples = []
112
+ tds = Hpricot(html).search('/tr/td.statText/table/tr/td.statText')
113
+ i = 0
114
+ while i < tds.size do
115
+ example = Case.new
116
+ example.input = parse_input tds[i].html
117
+ example.output = parse_output tds[i += 1].html
118
+ example.reason = parse_reason tds[i += 1].html
119
+ examples << example
120
+ i += 1
121
+ end
122
+ return examples
123
+ end
124
+ def parse_systests html
125
+ systests = []
126
+ _, y = indexes html, '<!-- System Testing -->'
127
+ z, _ = indexes html, '<!-- End System Testing -->'
128
+ return systests if not y or not z
129
+ Hpricot(html[y .. z]).search '/table/tr[@valign=top]' do |tr|
130
+ tds = tr.search '/td.statText'
131
+ if tds.size == 3 then
132
+ test = Case.new
133
+ test.input = filter_inout tds[0].to_plain_text
134
+ test.output = filter_inout tds[1].to_plain_text
135
+ systests << test
136
+ end
137
+ end
138
+ return systests
139
+ end
140
+ def download_systests detail_url
141
+ detail = @downloader.download detail_url
142
+ Hpricot(detail).search 'a[@href^=/stat?c=problem_solution]' do |url|
143
+ solution = @downloader.download url.attributes['href']
144
+ systests = parse_systests solution
145
+ return systests if not systests.empty?
146
+ end
147
+ return []
148
+ end
149
+ def parse_details doc
150
+ url, source, systests = '', '', []
151
+ doc.search 'a[@href^=/tc?module=ProblemDetail]' do |elem|
152
+ url = URI.join(Downloader::ROOT, elem.attributes['href']).to_s
153
+ source = filter elem.html
154
+ begin
155
+ systests = download_systests url
156
+ unless systests.empty? then
157
+ return url, source, systests
158
+ end
159
+ rescue DownloadError
160
+ end
161
+ end
162
+ return url, source, systests
163
+ end
164
+
165
+ ## @section Main method
166
+
167
+ def parse html
168
+ @images = []
169
+ prob = Problem.new
170
+ doc = Hpricot(html)
171
+
172
+ prob.name = parse_name doc.search('tr/td.statTextBig').html
173
+ prob.notes = nil
174
+ prob.constraints = nil
175
+ prob.examples = nil
176
+
177
+ html = doc.search('td.problemText/table').html
178
+
179
+ _, x = indexes html, h3('Problem Statement')
180
+ y, z = indexes html, h3('Definition')
181
+ prob.statement = parse_statement html[x .. y]
182
+
183
+ x, y = indexes html, h3('Notes')
184
+ if x.nil? then
185
+ prob.notes = []
186
+ x, y = indexes html, h3('Constraints')
187
+ if x.nil? then
188
+ prob.constraints = []
189
+ x, y = indexes html, h3('Examples')
190
+ if x.nil? then
191
+ prob.examples = []
192
+ x = -2
193
+ end
194
+ end
195
+ end
196
+ prob.definitions = parse_definitions html[z .. x]
197
+
198
+ if prob.notes.nil? then
199
+ z, x = indexes html, h3('Constraints')
200
+ if z.nil? then
201
+ prob.constraints = []
202
+ z, x = indexes html, h3('Examples')
203
+ if z.nil? then
204
+ prob.examples = []
205
+ z = - 2
206
+ end
207
+ end
208
+ prob.notes = parse_notes html[y .. z]
209
+ x, y = z, x
210
+ end
211
+
212
+ if prob.constraints.nil? then
213
+ z, x = indexes html, h3('Examples')
214
+ if z.nil? then
215
+ prob.examples = []
216
+ z = -2
217
+ end
218
+ prob.constraints = parse_constraints html[y .. z]
219
+ end
220
+
221
+ if prob.examples.nil? then
222
+ prob.examples = parse_examples html[x .. -2]
223
+ end
224
+
225
+ prob.images = @images
226
+ prob.url, prob.source, prob.systests = parse_details doc
227
+
228
+ return prob
229
+ end
230
+ end
231
+ end