at_coder_friends 0.6.3 → 0.6.8
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.
- checksums.yaml +5 -5
- data/.rubocop.yml +3 -2
- data/.travis.yml +1 -3
- data/CHANGELOG.md +37 -0
- data/Gemfile.lock +53 -48
- data/README.md +1 -1
- data/at_coder_friends.gemspec +5 -7
- data/config/default.yml +146 -72
- data/docs/CONFIGURATION.md +222 -136
- data/lib/at_coder_friends.rb +1 -0
- data/lib/at_coder_friends/cli.rb +8 -0
- data/lib/at_coder_friends/config_loader.rb +11 -3
- data/lib/at_coder_friends/context.rb +10 -6
- data/lib/at_coder_friends/emitter.rb +2 -2
- data/lib/at_coder_friends/generator/base.rb +3 -2
- data/lib/at_coder_friends/generator/main.rb +2 -1
- data/lib/at_coder_friends/parser/constraints.rb +1 -0
- data/lib/at_coder_friends/parser/input_format.rb +2 -0
- data/lib/at_coder_friends/parser/input_type.rb +3 -2
- data/lib/at_coder_friends/parser/modulo.rb +1 -1
- data/lib/at_coder_friends/parser/sections.rb +3 -3
- data/lib/at_coder_friends/path_info.rb +51 -0
- data/lib/at_coder_friends/path_util.rb +0 -31
- data/lib/at_coder_friends/scraping/agent.rb +11 -2
- data/lib/at_coder_friends/scraping/custom_test.rb +5 -6
- data/lib/at_coder_friends/scraping/submission.rb +6 -7
- data/lib/at_coder_friends/scraping/tasks.rb +8 -3
- data/lib/at_coder_friends/test_runner/base.rb +17 -4
- data/lib/at_coder_friends/test_runner/judge.rb +7 -9
- data/lib/at_coder_friends/test_runner/sample.rb +2 -4
- data/lib/at_coder_friends/verifier.rb +2 -3
- data/lib/at_coder_friends/version.rb +1 -1
- data/templates/cxx_builtin.cxx.erb +26 -35
- data/templates/ruby_builtin.rb.erb +17 -18
- metadata +7 -16
- data/tasks/regression/check_const.rake +0 -137
- data/tasks/regression/check_diff.rake +0 -30
- data/tasks/regression/check_fmt.rake +0 -45
- data/tasks/regression/check_parse.rake +0 -69
- data/tasks/regression/list_handler.rb +0 -46
- data/tasks/regression/regression.rb +0 -38
- data/tasks/regression/report_handler.rb +0 -20
- data/tasks/regression/section_list.rake +0 -53
- data/tasks/regression/setup.rake +0 -48
@@ -7,11 +7,15 @@ module AtCoderFriends
|
|
7
7
|
# - configuration
|
8
8
|
# - application modules
|
9
9
|
class Context
|
10
|
-
attr_reader :options, :
|
10
|
+
attr_reader :options, :path_info
|
11
11
|
|
12
12
|
def initialize(options, path)
|
13
13
|
@options = options
|
14
|
-
@
|
14
|
+
@path_info = PathInfo.new(File.expand_path(path))
|
15
|
+
end
|
16
|
+
|
17
|
+
def path
|
18
|
+
path_info.path
|
15
19
|
end
|
16
20
|
|
17
21
|
def config
|
@@ -26,6 +30,10 @@ module AtCoderFriends
|
|
26
30
|
@generator ||= Generator::Main.new(self)
|
27
31
|
end
|
28
32
|
|
33
|
+
def emitter
|
34
|
+
@emitter ||= Emitter.new(self)
|
35
|
+
end
|
36
|
+
|
29
37
|
def sample_test_runner
|
30
38
|
@sample_test_runner ||= TestRunner::Sample.new(self)
|
31
39
|
end
|
@@ -38,10 +46,6 @@ module AtCoderFriends
|
|
38
46
|
@verifier ||= Verifier.new(self)
|
39
47
|
end
|
40
48
|
|
41
|
-
def emitter
|
42
|
-
@emitter ||= Emitter.new(self)
|
43
|
-
end
|
44
|
-
|
45
49
|
def post_process
|
46
50
|
@scraping_agent&.save_session
|
47
51
|
end
|
@@ -21,8 +21,9 @@ module AtCoderFriends
|
|
21
21
|
def generate(pbm)
|
22
22
|
@pbm = pbm
|
23
23
|
src = File.read(select_template)
|
24
|
-
src = ERB.new(src).result(binding)
|
25
|
-
render(src)
|
24
|
+
src = ERB.new(src, safe_level = nil, trim_mode = '-').result(binding)
|
25
|
+
src = render(src) if respond_to?(:render)
|
26
|
+
src
|
26
27
|
end
|
27
28
|
|
28
29
|
def select_template
|
@@ -56,11 +56,13 @@ module AtCoderFriends
|
|
56
56
|
.gsub('>', '>')
|
57
57
|
.gsub('<', '<')
|
58
58
|
.gsub('\\ ', ' ')
|
59
|
+
.gsub(/\\hspace\{\d+pt\}/, ' ')
|
59
60
|
.gsub('\\(', '')
|
60
61
|
.gsub('\\)', '')
|
61
62
|
.gsub('\\lvert', '|')
|
62
63
|
.gsub('\\rvert', '|')
|
63
64
|
.gsub('\\mathit', '')
|
65
|
+
.gsub('\\mathrm', '')
|
64
66
|
.gsub('\\times', '*')
|
65
67
|
.gsub(/\\begin(\{[^{}]*\})*/, '')
|
66
68
|
.gsub(/\\end(\{[^{}]*\})*/, '')
|
@@ -6,10 +6,11 @@ module AtCoderFriends
|
|
6
6
|
module InputType
|
7
7
|
module_function
|
8
8
|
|
9
|
-
NUMBER_PAT = /\A[+-]?[0-9]
|
9
|
+
NUMBER_PAT = /\A[+-]?[0-9]{1,19}\z/.freeze
|
10
|
+
DECIMAL_PAT = /\A[+-]?[0-9]{1,19}(\.[0-9]+)?\z/.freeze
|
10
11
|
TYPE_TBL = [
|
11
12
|
[:number, NUMBER_PAT],
|
12
|
-
[:decimal,
|
13
|
+
[:decimal, DECIMAL_PAT]
|
13
14
|
].freeze
|
14
15
|
|
15
16
|
def process(pbm)
|
@@ -63,7 +63,7 @@ module AtCoderFriends
|
|
63
63
|
{
|
64
64
|
key: Problem::SECTION_IN_SMP,
|
65
65
|
pattern: /
|
66
|
-
\A(
|
66
|
+
\A(?:
|
67
67
|
入力例\s*(?<no>\d+)?
|
68
68
|
|入力\s*(?<no>\d+)
|
69
69
|
|Sample\s*Input\s*(?<no>\d+)?
|
@@ -75,14 +75,14 @@ module AtCoderFriends
|
|
75
75
|
{
|
76
76
|
key: Problem::SECTION_OUT_SMP,
|
77
77
|
pattern: /
|
78
|
-
\A(
|
78
|
+
\A(?:
|
79
79
|
出力例\s*(?<no>\d+)?
|
80
80
|
|出力\s*(?<no>\d+)
|
81
81
|
|入力例\s*(?<no>\d+)?\s*に対する出力例
|
82
82
|
|Sample\s*Output\s*(?<no>\d+)?
|
83
83
|
|Output\s*Example\s*(?<no>\d+)?
|
84
84
|
|Output\s*(?<no>\d+)
|
85
|
-
|Output\s*for\s*(the)?\s*Sample\s*Input\s*(?<no>\d+)?
|
85
|
+
|Output\s*for\s*(?:the)?\s*Sample\s*Input\s*(?<no>\d+)?
|
86
86
|
)\z
|
87
87
|
/xi
|
88
88
|
},
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
# holds target path information
|
5
|
+
class PathInfo
|
6
|
+
SMP_DIR = 'data'
|
7
|
+
CASES_DIR = 'cases'
|
8
|
+
TMP_DIR = '.tmp'
|
9
|
+
|
10
|
+
attr_reader :path, :dir
|
11
|
+
|
12
|
+
def initialize(path)
|
13
|
+
@path = path
|
14
|
+
# in setup command, path is directory name (existent/non-existent)
|
15
|
+
# in other commands(test, submit, verify), path is existent file name
|
16
|
+
@dir = File.file?(path) ? File.dirname(path) : path
|
17
|
+
end
|
18
|
+
|
19
|
+
def contest_name
|
20
|
+
File.basename(dir).delete('#').downcase
|
21
|
+
end
|
22
|
+
|
23
|
+
def components
|
24
|
+
# overwrites @dir here for non-existent files (test purpose)
|
25
|
+
@dir, prg = File.split(path)
|
26
|
+
base, ext = prg.split('.')
|
27
|
+
q = base.gsub(/_[^#_]+\z/, '')
|
28
|
+
[path, dir, prg, base, ext, q]
|
29
|
+
end
|
30
|
+
|
31
|
+
def src_dir
|
32
|
+
dir
|
33
|
+
end
|
34
|
+
|
35
|
+
def smp_dir
|
36
|
+
File.join(dir, SMP_DIR)
|
37
|
+
end
|
38
|
+
|
39
|
+
def cases_dir
|
40
|
+
File.join(dir, CASES_DIR)
|
41
|
+
end
|
42
|
+
|
43
|
+
def cases_out_dir
|
44
|
+
File.join(dir, TMP_DIR, CASES_DIR)
|
45
|
+
end
|
46
|
+
|
47
|
+
def tmp_dir
|
48
|
+
File.join(dir, TMP_DIR)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -3,37 +3,6 @@
|
|
3
3
|
module AtCoderFriends
|
4
4
|
# Common methods and behaviors for dealing with paths.
|
5
5
|
module PathUtil
|
6
|
-
module_function
|
7
|
-
|
8
|
-
SMP_DIR = 'data'
|
9
|
-
CASES_DIR = 'cases'
|
10
|
-
TMP_DIR = '.tmp'
|
11
|
-
|
12
|
-
def contest_name(path)
|
13
|
-
dir = File.file?(path) ? File.dirname(path) : path
|
14
|
-
File.basename(dir).delete('#').downcase
|
15
|
-
end
|
16
|
-
|
17
|
-
def split_prg_path(path)
|
18
|
-
dir, prg = File.split(path)
|
19
|
-
base, ext = prg.split('.')
|
20
|
-
q = base.split('_')[0]
|
21
|
-
[path, dir, prg, base, ext, q]
|
22
|
-
end
|
23
|
-
|
24
|
-
def smp_dir(dir)
|
25
|
-
File.join(dir, SMP_DIR)
|
26
|
-
end
|
27
|
-
|
28
|
-
def cases_dir(dir)
|
29
|
-
File.join(dir, CASES_DIR)
|
30
|
-
end
|
31
|
-
|
32
|
-
def tmp_dir(path)
|
33
|
-
dir = File.dirname(path)
|
34
|
-
File.join(dir, '.tmp')
|
35
|
-
end
|
36
|
-
|
37
6
|
def makedirs_unless(dir)
|
38
7
|
FileUtils.makedirs(dir) unless Dir.exist?(dir)
|
39
8
|
end
|
@@ -7,7 +7,6 @@ module AtCoderFriends
|
|
7
7
|
module Scraping
|
8
8
|
# common functions for scraping
|
9
9
|
class Agent
|
10
|
-
include AtCoderFriends::PathUtil
|
11
10
|
include Session
|
12
11
|
include Authentication
|
13
12
|
include Tasks
|
@@ -29,7 +28,7 @@ module AtCoderFriends
|
|
29
28
|
end
|
30
29
|
|
31
30
|
def contest
|
32
|
-
@contest ||=
|
31
|
+
@contest ||= ctx.path_info.contest_name
|
33
32
|
end
|
34
33
|
|
35
34
|
def common_url(path)
|
@@ -41,6 +40,10 @@ module AtCoderFriends
|
|
41
40
|
end
|
42
41
|
|
43
42
|
def lang_id(ext)
|
43
|
+
[lang_id_conf(ext)].flatten
|
44
|
+
end
|
45
|
+
|
46
|
+
def lang_id_conf(ext)
|
44
47
|
ctx.config.dig('ext_settings', ext, 'submit_lang') || (
|
45
48
|
msg = <<~MSG
|
46
49
|
submit_lang for .#{ext} is not specified.
|
@@ -51,6 +54,12 @@ module AtCoderFriends
|
|
51
54
|
)
|
52
55
|
end
|
53
56
|
|
57
|
+
def find_lang(page, langs)
|
58
|
+
langs.find do |lng|
|
59
|
+
page.search("div#select-lang select option[value=#{lng}]")[0]
|
60
|
+
end || langs[0]
|
61
|
+
end
|
62
|
+
|
54
63
|
def lang_list_txt
|
55
64
|
lang_list
|
56
65
|
&.map { |opt| "#{opt[:v]} - #{opt[:t]}" }
|
@@ -6,20 +6,19 @@ module AtCoderFriends
|
|
6
6
|
module Scraping
|
7
7
|
# run tests on custom_test page
|
8
8
|
module CustomTest
|
9
|
-
include AtCoderFriends::PathUtil
|
10
|
-
|
11
9
|
def code_test(infile)
|
12
|
-
path, _dir, _prg, _base, ext, _q =
|
13
|
-
|
10
|
+
path, _dir, _prg, _base, ext, _q = ctx.path_info.components
|
11
|
+
langs = lang_id(ext)
|
14
12
|
src = File.read(path, encoding: Encoding::UTF_8)
|
15
13
|
data = File.read(infile)
|
16
14
|
|
17
|
-
post_custom_test(
|
15
|
+
post_custom_test(langs, src, data)
|
18
16
|
check_custom_test
|
19
17
|
end
|
20
18
|
|
21
|
-
def post_custom_test(
|
19
|
+
def post_custom_test(langs, src, data)
|
22
20
|
page = fetch_with_auth(contest_url('custom_test'))
|
21
|
+
lang = find_lang(page, langs)
|
23
22
|
script = page.search('script').text
|
24
23
|
csrf_token = script.scan(/var csrfToken = "(.*)"/)[0][0]
|
25
24
|
|
@@ -4,22 +4,21 @@ module AtCoderFriends
|
|
4
4
|
module Scraping
|
5
5
|
# submit sources on submit page
|
6
6
|
module Submission
|
7
|
-
include AtCoderFriends::PathUtil
|
8
|
-
|
9
7
|
def submit
|
10
|
-
path, _dir, prg, _base, ext, q =
|
8
|
+
path, _dir, prg, _base, ext, q = ctx.path_info.components
|
11
9
|
puts "***** submit #{prg} *****"
|
12
|
-
|
10
|
+
langs = lang_id(ext)
|
13
11
|
src = File.read(path, encoding: Encoding::UTF_8)
|
14
12
|
|
15
|
-
post_submit(q,
|
13
|
+
post_submit(q, langs, src)
|
16
14
|
end
|
17
15
|
|
18
|
-
def post_submit(q,
|
16
|
+
def post_submit(q, langs, src)
|
19
17
|
page = fetch_with_auth(contest_url('submit'))
|
18
|
+
lang = find_lang(page, langs)
|
20
19
|
form = page.forms[1]
|
21
20
|
form.field_with(name: 'data.TaskScreenName') do |sel|
|
22
|
-
option = sel.options.find { |op| op.text
|
21
|
+
option = sel.options.find { |op| op.text =~ /\A#{q}\W/ }
|
23
22
|
option&.select || (raise AppError, "unknown problem:#{q}.")
|
24
23
|
end
|
25
24
|
form.add_field!('data.LanguageId', lang)
|
@@ -7,9 +7,14 @@ module AtCoderFriends
|
|
7
7
|
def fetch_all
|
8
8
|
puts "***** fetch_all #{contest} *****"
|
9
9
|
fetch_assignments.map do |q, url|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
begin
|
11
|
+
pbm = fetch_problem(q, url)
|
12
|
+
yield pbm if block_given?
|
13
|
+
pbm
|
14
|
+
rescue StandardError => e
|
15
|
+
puts e.to_s
|
16
|
+
puts e.backtrace
|
17
|
+
end
|
13
18
|
end
|
14
19
|
end
|
15
20
|
|
@@ -8,6 +8,7 @@ module AtCoderFriends
|
|
8
8
|
# run tests for the specified program.
|
9
9
|
class Base
|
10
10
|
include PathUtil
|
11
|
+
|
11
12
|
STATUS_STR = {
|
12
13
|
OK: '<< OK >>'.green,
|
13
14
|
WA: '!!!!! WA !!!!!'.red,
|
@@ -19,7 +20,7 @@ module AtCoderFriends
|
|
19
20
|
|
20
21
|
def initialize(ctx)
|
21
22
|
@ctx = ctx
|
22
|
-
@path, @dir, @prg, @base, @ext, @q =
|
23
|
+
@path, @dir, @prg, @base, @ext, @q = ctx.path_info.components
|
23
24
|
@detail = true
|
24
25
|
end
|
25
26
|
|
@@ -32,11 +33,23 @@ module AtCoderFriends
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def test_loc
|
35
|
-
test_cmd
|
36
|
+
if test_cmd
|
37
|
+
'local'
|
38
|
+
elsif ctx.scraping_agent.respond_to?(:code_test)
|
39
|
+
'remote'
|
40
|
+
else
|
41
|
+
raise AppError, "test_cmd for .#{ext} is not specified."
|
42
|
+
end
|
36
43
|
end
|
37
44
|
|
38
45
|
def test_mtd
|
39
|
-
test_cmd
|
46
|
+
if test_cmd
|
47
|
+
:local_test
|
48
|
+
elsif ctx.scraping_agent.respond_to?(:code_test)
|
49
|
+
:remote_test
|
50
|
+
else
|
51
|
+
raise AppError, "test_cmd for .#{ext} is not specified."
|
52
|
+
end
|
40
53
|
end
|
41
54
|
|
42
55
|
def run_test(id, infile, outfile, expfile)
|
@@ -63,7 +76,7 @@ module AtCoderFriends
|
|
63
76
|
end
|
64
77
|
|
65
78
|
def local_test(infile, outfile)
|
66
|
-
system("#{test_cmd} < #{infile} > #{outfile}")
|
79
|
+
system("#{test_cmd} < \"#{infile}\" > \"#{outfile}\"")
|
67
80
|
end
|
68
81
|
|
69
82
|
def remote_test(infile, outfile)
|
@@ -4,20 +4,18 @@ module AtCoderFriends
|
|
4
4
|
module TestRunner
|
5
5
|
# run test cases for the specified program with judge input/output.
|
6
6
|
class Judge < Base
|
7
|
-
include PathUtil
|
8
|
-
|
9
7
|
attr_reader :data_dir, :result_dir
|
10
8
|
|
11
9
|
def initialize(ctx)
|
12
10
|
super(ctx)
|
13
|
-
@data_dir = cases_dir
|
14
|
-
@result_dir =
|
11
|
+
@data_dir = ctx.path_info.cases_dir
|
12
|
+
@result_dir = ctx.path_info.cases_out_dir
|
15
13
|
end
|
16
14
|
|
17
15
|
def judge_all
|
18
16
|
puts "***** judge_all #{prg} (#{test_loc}) *****"
|
19
|
-
results = Dir["#{data_dir}/#{q}/in
|
20
|
-
id = File.basename(infile
|
17
|
+
results = Dir["#{data_dir}/#{q}/in/*"].sort.map do |infile|
|
18
|
+
id = File.basename(infile)
|
21
19
|
judge(id, false)
|
22
20
|
end
|
23
21
|
!results.empty? && results.all?
|
@@ -30,9 +28,9 @@ module AtCoderFriends
|
|
30
28
|
|
31
29
|
def judge(id, detail = true)
|
32
30
|
@detail = detail
|
33
|
-
infile = "#{data_dir}/#{q}/in/#{id}
|
34
|
-
outfile = "#{result_dir}/#{q}/result/#{id}
|
35
|
-
expfile = "#{data_dir}/#{q}/out/#{id}
|
31
|
+
infile = "#{data_dir}/#{q}/in/#{id}"
|
32
|
+
outfile = "#{result_dir}/#{q}/result/#{id}"
|
33
|
+
expfile = "#{data_dir}/#{q}/out/#{id}"
|
36
34
|
run_test(id, infile, outfile, expfile)
|
37
35
|
end
|
38
36
|
end
|