at_coder_friends 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -2
  3. data/.rubocop.yml +1 -0
  4. data/.rubocop_todo.yml +4 -1
  5. data/Gemfile.lock +16 -14
  6. data/Rakefile +2 -0
  7. data/at_coder_friends.gemspec +1 -0
  8. data/lib/at_coder_friends.rb +13 -5
  9. data/lib/at_coder_friends/cli.rb +1 -2
  10. data/lib/at_coder_friends/config_loader.rb +2 -2
  11. data/lib/at_coder_friends/context.rb +3 -3
  12. data/lib/at_coder_friends/cxx_generator.rb +7 -13
  13. data/lib/at_coder_friends/emitter.rb +0 -4
  14. data/lib/at_coder_friends/parser/constraints_parser.rb +26 -0
  15. data/lib/at_coder_friends/parser/format_parser.rb +154 -0
  16. data/lib/at_coder_friends/parser/main.rb +16 -0
  17. data/lib/at_coder_friends/parser/page_parser.rb +119 -0
  18. data/lib/at_coder_friends/path_util.rb +10 -0
  19. data/lib/at_coder_friends/problem.rb +11 -14
  20. data/lib/at_coder_friends/scraping/agent.rb +77 -0
  21. data/lib/at_coder_friends/scraping/authentication.rb +71 -0
  22. data/lib/at_coder_friends/scraping/custom_test.rb +53 -0
  23. data/lib/at_coder_friends/scraping/session.rb +26 -0
  24. data/lib/at_coder_friends/scraping/submission.rb +31 -0
  25. data/lib/at_coder_friends/scraping/tasks.rb +39 -0
  26. data/lib/at_coder_friends/test_runner/base.rb +123 -0
  27. data/lib/at_coder_friends/test_runner/judge.rb +44 -0
  28. data/lib/at_coder_friends/test_runner/sample.rb +35 -0
  29. data/lib/at_coder_friends/verifier.rb +4 -2
  30. data/lib/at_coder_friends/version.rb +1 -1
  31. data/tasks/regression.rake +163 -0
  32. metadata +30 -7
  33. data/lib/at_coder_friends/format_parser.rb +0 -151
  34. data/lib/at_coder_friends/judge_test_runner.rb +0 -34
  35. data/lib/at_coder_friends/sample_test_runner.rb +0 -31
  36. data/lib/at_coder_friends/scraping_agent.rb +0 -265
  37. data/lib/at_coder_friends/test_runner.rb +0 -104
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
5
+ module AtCoderFriends
6
+ module Scraping
7
+ # fetch problems from tasks page
8
+ module Tasks
9
+ def fetch_all
10
+ puts "***** fetch_all #{contest} *****"
11
+ fetch_assignments.map do |q, url|
12
+ pbm = fetch_problem(q, url)
13
+ yield pbm if block_given?
14
+ pbm
15
+ end
16
+ end
17
+
18
+ def fetch_assignments
19
+ url = contest_url('tasks')
20
+ puts "fetch list from #{url} ..."
21
+ page = fetch_with_auth(url)
22
+ page
23
+ .search('//table[1]//td[1]//a')
24
+ .each_with_object({}) do |a, h|
25
+ h[a.text] = a[:href]
26
+ end
27
+ end
28
+
29
+ def fetch_problem(q, url)
30
+ puts "fetch problem from #{url} ..."
31
+ page = fetch_with_auth(url)
32
+ Problem.new(q) do |pbm|
33
+ page.search('br').each { |br| br.replace("\n") }
34
+ pbm.page = page
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+ require 'rbconfig'
5
+
6
+ module AtCoderFriends
7
+ module TestRunner
8
+ # run tests for the specified program.
9
+ class Base
10
+ include PathUtil
11
+
12
+ attr_reader :ctx, :path, :dir, :prg, :base, :ext, :q
13
+
14
+ def initialize(ctx)
15
+ @ctx = ctx
16
+ @path, @dir, @prg, @base, @ext, @q = split_prg_path(ctx.path)
17
+ end
18
+
19
+ def config
20
+ ctx.config
21
+ end
22
+
23
+ def test_cmd
24
+ @test_cmd ||= begin
25
+ cmds = config.dig('ext_settings', ext, 'test_cmd')
26
+ cmd = cmds && (cmds[which_os.to_s] || cmds['default'])
27
+ return nil unless cmd
28
+
29
+ cmd.gsub('{dir}', dir).gsub('{base}', base)
30
+ end
31
+ end
32
+
33
+ def test_loc
34
+ test_cmd ? 'local' : 'remote'
35
+ end
36
+
37
+ def test_mtd
38
+ test_cmd ? :local_test : :remote_test
39
+ end
40
+
41
+ def run_test(id, infile, outfile, expfile)
42
+ return false unless File.exist?(infile) && File.exist?(expfile)
43
+
44
+ puts "==== #{id} ===="
45
+
46
+ makedirs_unless(File.dirname(outfile))
47
+ is_success = send(test_mtd, infile, outfile)
48
+ show_result(
49
+ is_success,
50
+ File.read(infile),
51
+ File.read(outfile),
52
+ File.read(expfile)
53
+ )
54
+ true
55
+ end
56
+
57
+ def local_test(infile, outfile)
58
+ system("#{test_cmd} < #{infile} > #{outfile}")
59
+ end
60
+
61
+ def remote_test(infile, outfile)
62
+ is_success, result = call_remote_test(infile)
63
+ File.write(outfile, result)
64
+ is_success
65
+ end
66
+
67
+ def call_remote_test(infile)
68
+ res = ctx.scraping_agent.code_test(infile)
69
+ (res && res['Result']) || (return [false, 'Remote test failed.'])
70
+
71
+ puts "Exit code: #{res.dig('Result', 'ExitCode')}"
72
+ puts "Time: #{res.dig('Result', 'TimeConsumption')}ms"
73
+ puts "Memory: #{res.dig('Result', 'MemoryConsumption')}KB"
74
+
75
+ res.dig('Result', 'ExitCode').zero? || (return [false, res['Stderr']])
76
+ [true, res['Stdout']]
77
+ end
78
+
79
+ def show_result(is_success, input, result, expected)
80
+ print detail_str(input, result, expected)
81
+ puts result_str(is_success, result, expected)
82
+ end
83
+
84
+ def detail_str(input, result, expected)
85
+ ret = ''
86
+ ret += "-- input --\n"
87
+ ret += input
88
+ ret += "-- expected --\n"
89
+ ret += expected
90
+ ret += "-- result --\n"
91
+ ret += result
92
+ ret
93
+ end
94
+
95
+ def result_str(is_success, result, expected)
96
+ if !is_success
97
+ '!!!!! RE !!!!!'.red
98
+ elsif result != expected
99
+ '!!!!! WA !!!!!'.red
100
+ else
101
+ '<< OK >>'.green
102
+ end
103
+ end
104
+
105
+ def which_os
106
+ @which_os ||= begin
107
+ case RbConfig::CONFIG['host_os']
108
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
109
+ :windows
110
+ when /darwin|mac os/
111
+ :macosx
112
+ when /linux/
113
+ :linux
114
+ when /solaris|bsd/
115
+ :unix
116
+ else
117
+ :unknown
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ module TestRunner
5
+ # run test cases for the specified program with judge input/output.
6
+ class Judge < Base
7
+ include PathUtil
8
+
9
+ attr_reader :data_dir, :result_dir
10
+
11
+ def initialize(ctx)
12
+ super(ctx)
13
+ @data_dir = cases_dir(dir)
14
+ @result_dir = cases_dir(tmp_dir(path))
15
+ end
16
+
17
+ def judge_all
18
+ puts "***** judge_all #{prg} (#{test_loc}) *****"
19
+ Dir["#{data_dir}/#{q}/in/*.txt"].sort.each do |infile|
20
+ id = File.basename(infile, '.txt')
21
+ judge(id, false)
22
+ end
23
+ end
24
+
25
+ def judge_one(id)
26
+ puts "***** judge_one #{prg} (#{test_loc}) *****"
27
+ judge(id, true)
28
+ end
29
+
30
+ def judge(id, detail = true)
31
+ @detail = detail
32
+ infile = "#{data_dir}/#{q}/in/#{id}.txt"
33
+ outfile = "#{result_dir}/#{q}/result/#{id}.txt"
34
+ expfile = "#{data_dir}/#{q}/out/#{id}.txt"
35
+ run_test(id, infile, outfile, expfile)
36
+ end
37
+
38
+ def show_result(is_success, input, result, expected)
39
+ print detail_str(input, result, expected) if @detail
40
+ puts result_str(is_success, result, expected)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ module TestRunner
5
+ # run test cases for the specified program with sample input/output.
6
+ class Sample < Base
7
+ include PathUtil
8
+
9
+ attr_reader :data_dir
10
+
11
+ def initialize(ctx)
12
+ super(ctx)
13
+ @data_dir = smp_dir(dir)
14
+ end
15
+
16
+ def test_all
17
+ puts "***** test_all #{prg} (#{test_loc}) *****"
18
+ 1.upto(999) do |i|
19
+ break unless test(i)
20
+ end
21
+ end
22
+
23
+ def test_one(n)
24
+ puts "***** test_one #{prg} (#{test_loc}) *****"
25
+ test(n)
26
+ end
27
+
28
+ def test(n)
29
+ id = format('%<q>s_%<n>03d', q: q, n: n)
30
+ files = %w[in out exp].map { |ext| "#{data_dir}/#{id}.#{ext}" }
31
+ run_test(id, *files)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -5,19 +5,21 @@ require 'fileutils'
5
5
  module AtCoderFriends
6
6
  # marks and checks if the source has been verified.
7
7
  class Verifier
8
+ include PathUtil
9
+
8
10
  attr_reader :path, :file, :vdir, :vpath
9
11
 
10
12
  def initialize(ctx)
11
13
  @path = ctx.path
12
14
  @file = File.basename(path)
13
- @vdir = File.join(File.dirname(path), '.tmp')
15
+ @vdir = tmp_dir(path)
14
16
  @vpath = File.join(vdir, "#{file}.verified")
15
17
  end
16
18
 
17
19
  def verify
18
20
  return unless File.exist?(path)
19
21
 
20
- FileUtils.makedirs(vdir) unless Dir.exist?(vdir)
22
+ makedirs_unless(vdir)
21
23
  FileUtils.touch(vpath)
22
24
  end
23
25
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AtCoderFriends
4
- VERSION = '0.5.0'
4
+ VERSION = '0.5.1'
5
5
  end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+ require 'mechanize'
7
+ require 'at_coder_friends'
8
+
9
+ module AtCoderFriends
10
+ # tasks for regression test
11
+ module Regression
12
+ module_function
13
+
14
+ CONTEST_LIST_URL = 'https://kenkoooo.com/atcoder/resources/contests.json'
15
+ ACF_HOME = File.realpath(File.join(__dir__, '..'))
16
+ REGRESSION_HOME = File.join(ACF_HOME, 'regression')
17
+ PAGES_DIR = File.join(REGRESSION_HOME, 'pages')
18
+ EMIT_ORG_DIR = File.join(REGRESSION_HOME, 'emit_org')
19
+ EMIT_DIR_FMT = File.join(REGRESSION_HOME, 'emit_%<now>s')
20
+
21
+ def setup
22
+ rmdir_force(PAGES_DIR)
23
+ rmdir_force(EMIT_ORG_DIR)
24
+ contest_id_list.each do |contest|
25
+ begin
26
+ ctx = context(EMIT_ORG_DIR, contest)
27
+ ctx.scraping_agent.fetch_all do |pbm|
28
+ begin
29
+ html_path = File.join(PAGES_DIR, contest, "#{pbm.q}.html")
30
+ save_file(html_path, pbm.page.body)
31
+ pipeline(ctx, pbm)
32
+ rescue StandardError => e
33
+ p e
34
+ end
35
+ end
36
+ rescue StandardError => e
37
+ p e
38
+ end
39
+ sleep 3
40
+ end
41
+ end
42
+
43
+ def check_diff
44
+ emit_dir = format(EMIT_DIR_FMT, now: Time.now.strftime('%Y%m%d%H%M%S'))
45
+ rmdir_force(emit_dir)
46
+
47
+ local_pbm_list.each do |contest, q, url|
48
+ ctx = context(emit_dir, contest)
49
+ pbm = ctx.scraping_agent.fetch_problem(q, url)
50
+ pipeline(ctx, pbm)
51
+ end
52
+
53
+ system("diff -r #{EMIT_ORG_DIR} #{emit_dir}")
54
+ end
55
+
56
+ def section_list
57
+ agent = Mechanize.new
58
+ list = local_pbm_list.flat_map do |contest, q, url|
59
+ page = agent.get(url)
60
+ %w[h2 h3].flat_map do
61
+ page.search('h3').map do |h3|
62
+ { contest: contest, q: q, text: normalize(h3.content) }
63
+ end
64
+ end
65
+ end
66
+ list.group_by { |sec| sec[:text] }.each do |k, vs|
67
+ puts [k, vs.size, vs[0][:contest], vs[0][:q]].join("\t")
68
+ end
69
+ end
70
+
71
+ def check_parse
72
+ parse_list
73
+ .select { |_, _, no_fmt, no_smp| no_fmt || no_smp }
74
+ .map { |c, q, no_fmt, no_smp| [c, q, f_to_s(no_fmt), f_to_s(no_smp)] }
75
+ .sort
76
+ .each { |args| puts args.join("\t") }
77
+ end
78
+
79
+ def parse_list
80
+ local_pbm_list.map do |contest, q, url|
81
+ ctx = context('', contest)
82
+ pbm = ctx.scraping_agent.fetch_problem(q, url)
83
+ Parser::Main.process(pbm)
84
+ no_fmt = pbm.fmt.empty?
85
+ no_smp = pbm.smps.all? { |smp| smp.txt.empty? }
86
+ [contest, q, no_fmt, no_smp]
87
+ end
88
+ end
89
+
90
+ def f_to_s(f)
91
+ f ? '◯' : '-'
92
+ end
93
+
94
+ def contest_id_list
95
+ uri = URI.parse(CONTEST_LIST_URL)
96
+ json = Net::HTTP.get(uri)
97
+ contests = JSON.parse(json)
98
+ puts "Total #{contests.size} contests"
99
+ contests.map { |h| h['id'] }
100
+ end
101
+
102
+ def local_pbm_list
103
+ Dir.glob(PAGES_DIR + '/**/*.html').map do |pbm_path|
104
+ contest = File.basename(File.dirname(pbm_path))
105
+ q = File.basename(pbm_path, '.html')
106
+ url = "file://#{pbm_path}"
107
+ [contest, q, url]
108
+ end
109
+ end
110
+
111
+ def context(root, contest)
112
+ Context.new({}, File.join(root, contest))
113
+ end
114
+
115
+ def rmdir_force(dir)
116
+ FileUtils.rm_r(dir) if Dir.exist?(dir)
117
+ end
118
+
119
+ def save_file(path, content)
120
+ dir = File.dirname(path)
121
+ FileUtils.makedirs(dir) unless Dir.exist?(dir)
122
+ File.binwrite(path, content)
123
+ end
124
+
125
+ def pipeline(ctx, pbm)
126
+ @rb_gen ||= RubyGenerator.new
127
+ @cxx_gen ||= CxxGenerator.new
128
+ Parser::Main.process(pbm)
129
+ @rb_gen.process(pbm)
130
+ @cxx_gen.process(pbm)
131
+ ctx.emitter.emit(pbm)
132
+ end
133
+
134
+ def normalize(s)
135
+ s
136
+ .tr(' 0-9A-Za-z', ' 0-9A-Za-z')
137
+ .gsub(/[^一-龠_ぁ-ん_ァ-ヶーa-zA-Z0-9 ]/, '')
138
+ .strip
139
+ end
140
+ end
141
+ end
142
+
143
+ namespace :regression do
144
+ desc 'setup regression environment'
145
+ task :setup do
146
+ AtCoderFriends::Regression.setup
147
+ end
148
+
149
+ desc 'run regression check'
150
+ task :check_diff do
151
+ AtCoderFriends::Regression.check_diff
152
+ end
153
+
154
+ desc 'list all section titles'
155
+ task :section_list do
156
+ AtCoderFriends::Regression.section_list
157
+ end
158
+
159
+ desc 'checks page parse result'
160
+ task :check_parse do
161
+ AtCoderFriends::Regression.check_parse
162
+ end
163
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: at_coder_friends
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - nejiko96
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-10-04 00:00:00.000000000 Z
11
+ date: 2019-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.8.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.8.1
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: launchy
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -145,16 +159,25 @@ files:
145
159
  - lib/at_coder_friends/cxx_generator.rb
146
160
  - lib/at_coder_friends/emitter.rb
147
161
  - lib/at_coder_friends/errors.rb
148
- - lib/at_coder_friends/format_parser.rb
149
- - lib/at_coder_friends/judge_test_runner.rb
162
+ - lib/at_coder_friends/parser/constraints_parser.rb
163
+ - lib/at_coder_friends/parser/format_parser.rb
164
+ - lib/at_coder_friends/parser/main.rb
165
+ - lib/at_coder_friends/parser/page_parser.rb
150
166
  - lib/at_coder_friends/path_util.rb
151
167
  - lib/at_coder_friends/problem.rb
152
168
  - lib/at_coder_friends/ruby_generator.rb
153
- - lib/at_coder_friends/sample_test_runner.rb
154
- - lib/at_coder_friends/scraping_agent.rb
155
- - lib/at_coder_friends/test_runner.rb
169
+ - lib/at_coder_friends/scraping/agent.rb
170
+ - lib/at_coder_friends/scraping/authentication.rb
171
+ - lib/at_coder_friends/scraping/custom_test.rb
172
+ - lib/at_coder_friends/scraping/session.rb
173
+ - lib/at_coder_friends/scraping/submission.rb
174
+ - lib/at_coder_friends/scraping/tasks.rb
175
+ - lib/at_coder_friends/test_runner/base.rb
176
+ - lib/at_coder_friends/test_runner/judge.rb
177
+ - lib/at_coder_friends/test_runner/sample.rb
156
178
  - lib/at_coder_friends/verifier.rb
157
179
  - lib/at_coder_friends/version.rb
180
+ - tasks/regression.rake
158
181
  homepage: https://github.com/nejiko96/at_coder_friends
159
182
  licenses:
160
183
  - MIT