gettc 1.2.2

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