at_coder_friends 0.5.0 → 0.5.1

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