at_coder_friends 0.6.2 → 0.6.7
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 +4 -4
- data/.rubocop.yml +3 -2
- data/.travis.yml +2 -2
- data/CHANGELOG.md +53 -0
- data/Gemfile.lock +32 -36
- data/README.md +1 -1
- data/at_coder_friends.gemspec +5 -7
- data/config/default.yml +146 -72
- data/docs/CONFIGURATION.md +224 -140
- data/lib/at_coder_friends.rb +3 -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 +42 -0
- data/lib/at_coder_friends/generator/cxx_builtin.rb +196 -143
- data/lib/at_coder_friends/generator/main.rb +8 -2
- data/lib/at_coder_friends/generator/ruby_builtin.rb +97 -51
- data/lib/at_coder_friends/parser/constraints.rb +1 -0
- data/lib/at_coder_friends/parser/input_format.rb +389 -188
- data/lib/at_coder_friends/parser/input_type.rb +92 -0
- data/lib/at_coder_friends/parser/main.rb +1 -0
- 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/problem.rb +81 -6
- 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_interactive.cxx → cxx_builtin.cxx.erb} +26 -4
- data/templates/{ruby_builtin_interactive.rb → ruby_builtin.rb.erb} +17 -3
- metadata +11 -17
- data/tasks/regression/check_const.rake +0 -136
- data/tasks/regression/check_diff.rake +0 -30
- data/tasks/regression/check_fmt.rake +0 -42
- data/tasks/regression/check_parse.rake +0 -70
- data/tasks/regression/regression.rb +0 -72
- data/tasks/regression/section_list.rake +0 -53
- data/tasks/regression/setup.rake +0 -48
- data/templates/cxx_builtin_default.cxx +0 -26
- data/templates/ruby_builtin_default.rb +0 -7
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Parser
|
5
|
+
# parses input data types and updates input definitons
|
6
|
+
module InputType
|
7
|
+
module_function
|
8
|
+
|
9
|
+
NUMBER_PAT = /\A[+-]?[0-9]{1,19}\z/.freeze
|
10
|
+
DECIMAL_PAT = /\A[+-]?[0-9]{1,19}(\.[0-9]+)?\z/.freeze
|
11
|
+
TYPE_TBL = [
|
12
|
+
[:number, NUMBER_PAT],
|
13
|
+
[:decimal, DECIMAL_PAT]
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
def process(pbm)
|
17
|
+
parse(pbm.formats_src, pbm.samples)
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse(inpdefs, smps)
|
21
|
+
lines = max_smp(smps)&.split("\n")
|
22
|
+
lines && match_smp(inpdefs, lines)
|
23
|
+
end
|
24
|
+
|
25
|
+
def max_smp(smps)
|
26
|
+
smps
|
27
|
+
.select { |smp| smp.ext == :in }
|
28
|
+
.max_by { |smp| smp.txt.size }
|
29
|
+
&.txt
|
30
|
+
end
|
31
|
+
|
32
|
+
def match_smp(inpdefs, lines)
|
33
|
+
vars = {}
|
34
|
+
inpdefs.each do |inpdef|
|
35
|
+
break unless (k = get_line_cnt(inpdef))
|
36
|
+
|
37
|
+
k, parsed = parse_line_cnt(k, vars)
|
38
|
+
rows = lines.shift(k).map { |line| line.split(/[#{inpdef.delim} ]/) }
|
39
|
+
break if rows.empty?
|
40
|
+
|
41
|
+
inpdef.container == :single &&
|
42
|
+
vars.merge!(inpdef.names.zip(rows[0]).to_h)
|
43
|
+
inpdef.cols = detect_cols_type(rows)
|
44
|
+
break unless parsed
|
45
|
+
end
|
46
|
+
inpdefs
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_line_cnt(inpdef)
|
50
|
+
case inpdef.size.size
|
51
|
+
when 0
|
52
|
+
1
|
53
|
+
when 1
|
54
|
+
inpdef.container == :harray ? 1 : inpdef.size[0]
|
55
|
+
when 2
|
56
|
+
inpdef.size[0]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_line_cnt(k, vars)
|
61
|
+
if k.is_a?(Integer)
|
62
|
+
[k, true]
|
63
|
+
elsif k =~ NUMBER_PAT
|
64
|
+
[k.to_i, true]
|
65
|
+
elsif vars[k] =~ NUMBER_PAT
|
66
|
+
[vars[k].to_i, true]
|
67
|
+
elsif vars[(k2 = k.gsub(/-1\z/, ''))] =~ NUMBER_PAT
|
68
|
+
[vars[k2].to_i - 1, true]
|
69
|
+
else
|
70
|
+
[1, false]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def detect_cols_type(rows)
|
75
|
+
cols = fill_transpose(rows).map(&:compact)
|
76
|
+
cols.map { |col| detect_col_type(col) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def fill_transpose(arr)
|
80
|
+
Array.new(arr.map(&:size).max) { |i| arr.map { |e| e[i] } }
|
81
|
+
end
|
82
|
+
|
83
|
+
def detect_col_type(arr)
|
84
|
+
ret = :string
|
85
|
+
TYPE_TBL.any? do |type, pat|
|
86
|
+
arr.all? { |v| v =~ pat } && ret = type
|
87
|
+
end
|
88
|
+
ret
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -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
|
@@ -18,13 +18,84 @@ module AtCoderFriends
|
|
18
18
|
|
19
19
|
SampleData = Struct.new(:no, :ext, :txt)
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
# holds information about input format
|
22
|
+
class InputFormat
|
23
|
+
ITEM_RANK = { number: 1, decimal: 2, string: 3 }.freeze
|
24
|
+
|
25
|
+
attr_reader :container, :names, :size, :delim
|
26
|
+
attr_accessor :cols
|
27
|
+
|
28
|
+
def initialize(
|
29
|
+
container: nil,
|
30
|
+
item: nil,
|
31
|
+
names: [],
|
32
|
+
size: [],
|
33
|
+
delim: '',
|
34
|
+
cols: []
|
35
|
+
)
|
36
|
+
@container = container
|
37
|
+
@item = item
|
38
|
+
@names = names
|
39
|
+
@size = size
|
40
|
+
@delim = delim
|
41
|
+
@cols = cols
|
24
42
|
end
|
25
43
|
|
26
44
|
def to_s
|
27
|
-
|
45
|
+
if container == :unknown
|
46
|
+
"#{container} #{item}"
|
47
|
+
else
|
48
|
+
"#{container} #{item}(#{cols}) #{names} #{size} #{delim}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def item
|
53
|
+
@item || cols.max_by { |k| ITEM_RANK[k] } || :number
|
54
|
+
end
|
55
|
+
|
56
|
+
def vars
|
57
|
+
tmp = @item && [@item] || cols
|
58
|
+
names.zip(tmp).map { |(name, col)| [name, col || :number] }
|
59
|
+
end
|
60
|
+
|
61
|
+
def components
|
62
|
+
@components ||=
|
63
|
+
case container
|
64
|
+
when :varray_matrix
|
65
|
+
varray_matrix_components
|
66
|
+
when :matrix_varray
|
67
|
+
matrix_varray_components
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def varray_matrix_components
|
72
|
+
[
|
73
|
+
self.class.new(
|
74
|
+
container: :varray,
|
75
|
+
names: names[0..-2], size: size[0..0],
|
76
|
+
delim: delim, cols: cols[0..-2]
|
77
|
+
),
|
78
|
+
self.class.new(
|
79
|
+
container: :matrix, item: @item,
|
80
|
+
names: names[-1..-1], size: size,
|
81
|
+
delim: delim, cols: cols[-1..-1] || []
|
82
|
+
)
|
83
|
+
]
|
84
|
+
end
|
85
|
+
|
86
|
+
def matrix_varray_components
|
87
|
+
[
|
88
|
+
self.class.new(
|
89
|
+
container: :matrix, item: @item,
|
90
|
+
names: names[0..0], size: size,
|
91
|
+
delim: delim, cols: cols[0..0]
|
92
|
+
),
|
93
|
+
self.class.new(
|
94
|
+
container: :varray,
|
95
|
+
names: names[1..-1], size: size[0..0],
|
96
|
+
delim: delim, cols: cols[1..-1] || []
|
97
|
+
)
|
98
|
+
]
|
28
99
|
end
|
29
100
|
end
|
30
101
|
|
@@ -35,14 +106,14 @@ module AtCoderFriends
|
|
35
106
|
SourceCode = Struct.new(:ext, :txt)
|
36
107
|
|
37
108
|
attr_reader :q, :samples, :sources, :options
|
38
|
-
attr_accessor :page, :sections, :
|
109
|
+
attr_accessor :page, :sections, :formats_src, :constants
|
39
110
|
|
40
111
|
def initialize(q, page = Mechanize::Page.new)
|
41
112
|
@q = q
|
42
113
|
@page = page
|
43
114
|
@sections = {}
|
44
115
|
@samples = []
|
45
|
-
@
|
116
|
+
@formats_src = []
|
46
117
|
@constants = []
|
47
118
|
@options = Options.new
|
48
119
|
@sources = []
|
@@ -57,6 +128,10 @@ module AtCoderFriends
|
|
57
128
|
@body_content ||= page.search('body')[0]&.content
|
58
129
|
end
|
59
130
|
|
131
|
+
def formats
|
132
|
+
@formats ||= formats_src.reject { |f| f.container == :unknown }
|
133
|
+
end
|
134
|
+
|
60
135
|
def add_smp(no, ext, txt)
|
61
136
|
@samples << SampleData.new(no, ext, txt)
|
62
137
|
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
|
|