at_coder_friends 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +1 -4
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/config/default.yml +3 -0
- data/docs/CONFIGURATION.md +74 -9
- data/lib/at_coder_friends.rb +10 -5
- data/lib/at_coder_friends/cli.rb +2 -5
- data/lib/at_coder_friends/context.rb +4 -0
- data/lib/at_coder_friends/emitter.rb +2 -2
- data/lib/at_coder_friends/generator/cxx_builtin.rb +191 -0
- data/lib/at_coder_friends/generator/main.rb +53 -0
- data/lib/at_coder_friends/generator/ruby_builtin.rb +128 -0
- data/lib/at_coder_friends/parser/binary.rb +39 -0
- data/lib/at_coder_friends/parser/constraints.rb +36 -0
- data/lib/at_coder_friends/parser/{format_parser.rb → input_format.rb} +42 -30
- data/lib/at_coder_friends/parser/interactive.rb +29 -0
- data/lib/at_coder_friends/parser/main.rb +6 -3
- data/lib/at_coder_friends/parser/sample_data.rb +24 -0
- data/lib/at_coder_friends/parser/section_wrapper.rb +49 -0
- data/lib/at_coder_friends/parser/{page_parser.rb → sections.rb} +44 -50
- data/lib/at_coder_friends/problem.rb +40 -24
- data/lib/at_coder_friends/scraping/agent.rb +1 -5
- data/lib/at_coder_friends/scraping/authentication.rb +2 -2
- data/lib/at_coder_friends/scraping/session.rb +1 -1
- data/lib/at_coder_friends/scraping/tasks.rb +2 -6
- data/lib/at_coder_friends/test_runner/base.rb +36 -31
- data/lib/at_coder_friends/test_runner/judge.rb +2 -6
- data/lib/at_coder_friends/test_runner/sample.rb +8 -6
- data/lib/at_coder_friends/version.rb +1 -1
- data/tasks/regression/check_diff.rake +29 -0
- data/tasks/regression/check_parse.rake +56 -0
- data/tasks/regression/regression.rb +67 -0
- data/tasks/regression/section_list.rake +41 -0
- data/tasks/regression/setup.rake +48 -0
- data/templates/cxx_builtin_default.cxx +26 -0
- data/templates/cxx_builtin_interactive.cxx +61 -0
- data/templates/ruby_builtin_default.rb +5 -0
- data/templates/ruby_builtin_interactive.rb +32 -0
- metadata +21 -8
- data/lib/at_coder_friends/cxx_generator.rb +0 -169
- data/lib/at_coder_friends/parser/constraints_parser.rb +0 -26
- data/lib/at_coder_friends/ruby_generator.rb +0 -97
- data/tasks/regression.rake +0 -163
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Generator
|
5
|
+
# generates C++ source code from problem description
|
6
|
+
class RubyBuiltin
|
7
|
+
ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
|
8
|
+
TMPL_DIR = File.join(ACF_HOME, 'templates')
|
9
|
+
DEFAULT_TMPL = File.join(TMPL_DIR, 'ruby_builtin_default.rb')
|
10
|
+
INTERACTIVE_TMPL = File.join(TMPL_DIR, 'ruby_builtin_interactive.rb')
|
11
|
+
|
12
|
+
attr_reader :cfg, :pbm
|
13
|
+
|
14
|
+
def initialize(cfg = nil)
|
15
|
+
@cfg = cfg || {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def process(pbm)
|
19
|
+
src = generate(pbm)
|
20
|
+
pbm.add_src(:rb, src)
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate(pbm)
|
24
|
+
@pbm = pbm
|
25
|
+
File
|
26
|
+
.read(select_template)
|
27
|
+
.gsub('### URL ###', pbm.url)
|
28
|
+
.gsub('### DCLS ###', gen_decls.join("\n"))
|
29
|
+
.gsub('### OUTPUT ###', gen_output)
|
30
|
+
end
|
31
|
+
|
32
|
+
def select_template(interactive = pbm.options.interactive)
|
33
|
+
interactive ? interactive_template : default_template
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_template
|
37
|
+
cfg['default_template'] || DEFAULT_TMPL
|
38
|
+
end
|
39
|
+
|
40
|
+
def interactive_template
|
41
|
+
cfg['interactive_template'] || INTERACTIVE_TMPL
|
42
|
+
end
|
43
|
+
|
44
|
+
def gen_decls(inpdefs = pbm.formats)
|
45
|
+
inpdefs.map { |inpdef| gen_decl(inpdef) }.flatten
|
46
|
+
end
|
47
|
+
|
48
|
+
def gen_decl(inpdef)
|
49
|
+
case inpdef.container
|
50
|
+
when :single
|
51
|
+
gen_single_decl(inpdef)
|
52
|
+
when :harray
|
53
|
+
gen_harray_decl(inpdef)
|
54
|
+
when :varray
|
55
|
+
if inpdef.names.size == 1
|
56
|
+
gen_varray_1_decl(inpdef)
|
57
|
+
else
|
58
|
+
gen_varray_n_decl(inpdef)
|
59
|
+
end
|
60
|
+
when :matrix
|
61
|
+
gen_matrix_decl(inpdef)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def gen_single_decl(inpdef)
|
66
|
+
names = inpdef.names
|
67
|
+
dcl = names.join(', ')
|
68
|
+
expr = gen_expr(inpdef.item, names.size > 1)
|
69
|
+
"#{dcl} = #{expr}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def gen_harray_decl(inpdef)
|
73
|
+
v = inpdef.names[0]
|
74
|
+
dcl = "#{v}s"
|
75
|
+
expr = gen_expr(inpdef.item, true)
|
76
|
+
"#{dcl} = #{expr}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def gen_varray_1_decl(inpdef)
|
80
|
+
v = inpdef.names[0]
|
81
|
+
sz = inpdef.size[0]
|
82
|
+
dcl = "#{v}s"
|
83
|
+
expr = gen_expr(inpdef.item, false)
|
84
|
+
"#{dcl} = Array.new(#{sz}) { #{expr} }"
|
85
|
+
end
|
86
|
+
|
87
|
+
def gen_varray_n_decl(inpdef)
|
88
|
+
names = inpdef.names
|
89
|
+
sz = inpdef.size[0]
|
90
|
+
dcl = names.map { |v| "#{v}s[i]" }.join(', ')
|
91
|
+
expr = gen_expr(inpdef.item, true)
|
92
|
+
ret = []
|
93
|
+
ret += names.map { |v| "#{v}s = Array.new(#{sz})" }
|
94
|
+
ret << "#{sz}.times do |i|"
|
95
|
+
ret << " #{dcl} = #{expr}"
|
96
|
+
ret << 'end'
|
97
|
+
ret
|
98
|
+
end
|
99
|
+
|
100
|
+
def gen_matrix_decl(inpdef)
|
101
|
+
v = inpdef.names[0]
|
102
|
+
sz = inpdef.size[0]
|
103
|
+
decl = "#{v}ss"
|
104
|
+
expr = gen_expr(inpdef.item, true)
|
105
|
+
"#{decl} = Array.new(#{sz}) { #{expr} }"
|
106
|
+
end
|
107
|
+
|
108
|
+
def gen_expr(item, split)
|
109
|
+
case item
|
110
|
+
when :number
|
111
|
+
split ? 'gets.split.map(&:to_i)' : 'gets.to_i'
|
112
|
+
when :string
|
113
|
+
split ? 'gets.chomp.split' : 'gets.chomp'
|
114
|
+
when :char
|
115
|
+
'gets.chomp'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def gen_output(vs = pbm.options.binary_values)
|
120
|
+
if vs
|
121
|
+
"puts cond ? '#{vs[0]}' : '#{vs[1]}'"
|
122
|
+
else
|
123
|
+
'puts ans'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Parser
|
5
|
+
# detect binary problem
|
6
|
+
module Binary
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def process(pbm)
|
10
|
+
vs = exp_values(pbm)
|
11
|
+
return unless vs.size == 2
|
12
|
+
return if vs.any? { |v| v.include?("\n") }
|
13
|
+
return if vs.any? { |v| v =~ /^[0-9\s]*$/ }
|
14
|
+
|
15
|
+
out_fmt = ouput_format(pbm)
|
16
|
+
re1, re2 = vs.map { |v| Regexp.escape(v) }
|
17
|
+
|
18
|
+
pbm.options.binary_values =
|
19
|
+
if out_fmt =~ /#{re1}.+#{re2}/m
|
20
|
+
vs
|
21
|
+
elsif out_fmt =~ /#{re2}.+#{re1}/m
|
22
|
+
vs.reverse
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def exp_values(pbm)
|
27
|
+
pbm
|
28
|
+
.samples
|
29
|
+
.select { |smp| smp.ext == :exp }
|
30
|
+
.map { |smp| smp.txt.chomp }
|
31
|
+
.uniq
|
32
|
+
end
|
33
|
+
|
34
|
+
def ouput_format(pbm)
|
35
|
+
pbm.sections[Problem::SECTION_OUT_FMT]&.content || ''
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Parser
|
5
|
+
# parses constraints
|
6
|
+
module Constraints
|
7
|
+
module_function
|
8
|
+
|
9
|
+
SECTIONS = [
|
10
|
+
Problem::SECTION_IN_FMT,
|
11
|
+
Problem::SECTION_IO_FMT,
|
12
|
+
Problem::SECTION_CONSTRAINTS
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
def process(pbm)
|
16
|
+
str = SECTIONS.reduce('') do |m, key|
|
17
|
+
m + (pbm.sections[key]&.content || '')
|
18
|
+
end
|
19
|
+
constraints = parse(str)
|
20
|
+
pbm.constraints = constraints
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse(str)
|
24
|
+
str
|
25
|
+
.gsub(/[,\\(){}|]/, '')
|
26
|
+
.gsub(/(≤|leq?)/i, '≦')
|
27
|
+
.scan(/([\da-z_]+)\s*≦\s*(\d+)(?:\^(\d+))?/i)
|
28
|
+
.map do |v, sz, k|
|
29
|
+
sz = sz.to_i
|
30
|
+
sz **= k.to_i if k
|
31
|
+
Problem::Constraint.new(v, :max, sz)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -2,28 +2,11 @@
|
|
2
2
|
|
3
3
|
module AtCoderFriends
|
4
4
|
module Parser
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
class Iterator
|
11
|
-
def initialize(array)
|
12
|
-
@array = array
|
13
|
-
@i = 0
|
14
|
-
end
|
15
|
-
|
16
|
-
def next?
|
17
|
-
@i < @array.size
|
18
|
-
end
|
19
|
-
|
20
|
-
def next
|
21
|
-
ret = @array[@i]
|
22
|
-
@i += 1
|
23
|
-
ret
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
5
|
+
module InputFormatConstants
|
6
|
+
SECTIONS = [
|
7
|
+
Problem::SECTION_IN_FMT,
|
8
|
+
Problem::SECTION_IO_FMT
|
9
|
+
].freeze
|
27
10
|
PARSERS = [
|
28
11
|
{
|
29
12
|
container: :harray,
|
@@ -77,18 +60,47 @@ module AtCoderFriends
|
|
77
60
|
size: ->(_) { [] }
|
78
61
|
}
|
79
62
|
].freeze
|
63
|
+
end
|
64
|
+
|
65
|
+
# parses input data format and generates input definitons
|
66
|
+
module InputFormat
|
67
|
+
include InputFormatConstants
|
68
|
+
|
69
|
+
module_function
|
70
|
+
|
71
|
+
# Iterates through elements of an array
|
72
|
+
class Iterator
|
73
|
+
def initialize(array)
|
74
|
+
@array = array
|
75
|
+
@i = 0
|
76
|
+
end
|
77
|
+
|
78
|
+
def next?
|
79
|
+
@i < @array.size
|
80
|
+
end
|
81
|
+
|
82
|
+
def next
|
83
|
+
ret = @array[@i]
|
84
|
+
@i += 1
|
85
|
+
ret
|
86
|
+
end
|
87
|
+
end
|
80
88
|
|
81
89
|
def process(pbm)
|
82
|
-
|
83
|
-
|
90
|
+
str =
|
91
|
+
SECTIONS
|
92
|
+
.map { |key| pbm.sections[key]&.code_block }
|
93
|
+
.find(&:itself) || ''
|
94
|
+
inpdefs = parse(str, pbm.samples)
|
95
|
+
pbm.formats = inpdefs
|
84
96
|
end
|
85
97
|
|
86
|
-
def parse(
|
87
|
-
lines = normalize(
|
88
|
-
|
98
|
+
def parse(str, smps)
|
99
|
+
lines = normalize(str)
|
100
|
+
inpdefs = parse_fmt(lines)
|
89
101
|
smpx = max_smp(smps)
|
90
|
-
smpx && match_smp!(
|
91
|
-
|
102
|
+
smpx && match_smp!(inpdefs, smpx)
|
103
|
+
inpdefs
|
92
104
|
end
|
93
105
|
|
94
106
|
def normalize(fmt)
|
@@ -125,7 +137,7 @@ module AtCoderFriends
|
|
125
137
|
break unless pat2 && pat2 =~ cur
|
126
138
|
end
|
127
139
|
size = parser[:size].call(prv)
|
128
|
-
y <<
|
140
|
+
y << Problem::InputFormat.new(container, item, names, size)
|
129
141
|
end
|
130
142
|
end.to_a
|
131
143
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Parser
|
5
|
+
# detect interactive problem
|
6
|
+
module Interactive
|
7
|
+
module_function
|
8
|
+
|
9
|
+
INTERACTIVE_PAT = '(インタラクティブ|interactive|リアクティブ|reactive)'
|
10
|
+
FLUSH_PAT = 'flush'
|
11
|
+
|
12
|
+
def process(pbm)
|
13
|
+
pbm.options.interactive = false
|
14
|
+
|
15
|
+
body = pbm.page_body
|
16
|
+
f_int = body =~ /#{INTERACTIVE_PAT}/i
|
17
|
+
f_flush = body =~ /#{FLUSH_PAT}/i
|
18
|
+
f_io = pbm.sections[Problem::SECTION_IO_FMT]
|
19
|
+
f_tbl =
|
20
|
+
pbm
|
21
|
+
.sections[Problem::SECTION_IO_SMP]
|
22
|
+
&.find_element(%w[table])
|
23
|
+
return unless [f_int, f_flush, f_io, f_tbl].count(&:itself) > 1
|
24
|
+
|
25
|
+
pbm.options.interactive = true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -7,9 +7,12 @@ module AtCoderFriends
|
|
7
7
|
module_function
|
8
8
|
|
9
9
|
def process(pbm)
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
Sections.process(pbm)
|
11
|
+
SampleData.process(pbm)
|
12
|
+
InputFormat.process(pbm)
|
13
|
+
Constraints.process(pbm)
|
14
|
+
Interactive.process(pbm)
|
15
|
+
Binary.process(pbm)
|
13
16
|
end
|
14
17
|
end
|
15
18
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
|
5
|
+
module AtCoderFriends
|
6
|
+
module Parser
|
7
|
+
# parses sample data and sets to problem
|
8
|
+
module SampleData
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def process(pbm)
|
12
|
+
pbm.sections.each do |key, section|
|
13
|
+
ext =
|
14
|
+
if key =~ Problem::SECTION_IN_SMP_PAT
|
15
|
+
:in
|
16
|
+
elsif key =~ Problem::SECTION_OUT_SMP_PAT
|
17
|
+
:exp
|
18
|
+
end
|
19
|
+
ext && pbm.add_smp($LAST_MATCH_INFO[:no], ext, section.code_block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Parser
|
5
|
+
# holds section in problrem page
|
6
|
+
class SectionWrapper
|
7
|
+
attr_reader :h
|
8
|
+
|
9
|
+
def initialize(h)
|
10
|
+
@h = h
|
11
|
+
end
|
12
|
+
|
13
|
+
def siblings
|
14
|
+
@siblings ||= begin
|
15
|
+
ret = []
|
16
|
+
nx = h.next
|
17
|
+
while nx && nx.name != h.name
|
18
|
+
ret << nx
|
19
|
+
nx = nx.next
|
20
|
+
end
|
21
|
+
ret
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def content
|
26
|
+
@content ||= begin
|
27
|
+
siblings.reduce('') { |m, node| m + node.content.gsub("\r\n", "\n") }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_element(tags)
|
32
|
+
siblings.each do |node|
|
33
|
+
tags.each do |tag|
|
34
|
+
elem = node.name == tag ? node : node.search(tag)[0]
|
35
|
+
return elem if elem
|
36
|
+
end
|
37
|
+
end
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def code_block
|
42
|
+
@code_block ||= begin
|
43
|
+
elem = find_element(%w[pre blockquote])
|
44
|
+
(elem&.content || '').lstrip.gsub("\r\n", "\n")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -2,27 +2,39 @@
|
|
2
2
|
|
3
3
|
module AtCoderFriends
|
4
4
|
module Parser
|
5
|
-
|
6
|
-
|
7
|
-
module_function
|
8
|
-
|
9
|
-
SECTION_TYPES = [
|
5
|
+
module SectionsConstants
|
6
|
+
SECTION_DEFS = [
|
10
7
|
{
|
11
|
-
key:
|
8
|
+
key: Problem::SECTION_CONSTRAINTS,
|
12
9
|
patterns: [
|
13
10
|
'^制約$',
|
11
|
+
'^入力制限$',
|
14
12
|
'^Constraints$'
|
15
13
|
]
|
16
14
|
},
|
17
15
|
{
|
18
|
-
key:
|
16
|
+
key: Problem::SECTION_IN_FMT,
|
19
17
|
patterns: [
|
20
|
-
'
|
21
|
-
'^Inputs?\s*(
|
18
|
+
'^入力(形式)?$',
|
19
|
+
'^Inputs?\s*(Format)?$'
|
22
20
|
]
|
23
21
|
},
|
24
22
|
{
|
25
|
-
key:
|
23
|
+
key: Problem::SECTION_OUT_FMT,
|
24
|
+
patterns: [
|
25
|
+
'^出力(形式)?$',
|
26
|
+
'^Outputs?\s*(Format)?$'
|
27
|
+
]
|
28
|
+
},
|
29
|
+
{
|
30
|
+
key: Problem::SECTION_IO_FMT,
|
31
|
+
patterns: [
|
32
|
+
'^入出力(形式)?$',
|
33
|
+
'^Input\s*(and)?\s*Output\s*(Format)?$'
|
34
|
+
]
|
35
|
+
},
|
36
|
+
{
|
37
|
+
key: Problem::SECTION_IN_SMP,
|
26
38
|
patterns: [
|
27
39
|
'^入力例\s*(?<no>\d+)?$',
|
28
40
|
'^入力\s*(?<no>\d+)$',
|
@@ -32,7 +44,7 @@ module AtCoderFriends
|
|
32
44
|
]
|
33
45
|
},
|
34
46
|
{
|
35
|
-
key:
|
47
|
+
key: Problem::SECTION_OUT_SMP,
|
36
48
|
patterns: [
|
37
49
|
'^出力例\s*(?<no>\d+)?$',
|
38
50
|
'^出力\s*(?<no>\d+)$',
|
@@ -42,33 +54,46 @@ module AtCoderFriends
|
|
42
54
|
'^Output\s*(?<no>\d+)$',
|
43
55
|
'^Output\s*for\s*(the)?\s*Sample\s*Input\s*(?<no>\d+)?$'
|
44
56
|
]
|
57
|
+
},
|
58
|
+
{
|
59
|
+
key: Problem::SECTION_IO_SMP,
|
60
|
+
patterns: [
|
61
|
+
'^入出力の?例\s*(\d+)?$',
|
62
|
+
'^サンプル\s*(\d+)?$',
|
63
|
+
'^Sample\s*Input\s*(and)?\s*Output\s*(\d+)?$',
|
64
|
+
'^Samples?\s*(\d+)?$'
|
65
|
+
]
|
45
66
|
}
|
46
67
|
].freeze
|
68
|
+
end
|
69
|
+
|
70
|
+
# parses problem page and builds section table
|
71
|
+
module Sections
|
72
|
+
include SectionsConstants
|
73
|
+
|
74
|
+
module_function
|
47
75
|
|
48
76
|
def process(pbm)
|
49
77
|
sections = collect_sections(pbm.page)
|
50
|
-
|
78
|
+
pbm.sections = sections
|
51
79
|
end
|
52
80
|
|
53
81
|
def collect_sections(page)
|
54
|
-
|
55
|
-
%w[h2 h3].each do |tag|
|
82
|
+
%w[h2 h3].each_with_object({}) do |tag, sections|
|
56
83
|
page
|
57
84
|
.search(tag)
|
58
85
|
.each do |h|
|
59
86
|
key = find_key(h)
|
60
|
-
key && sections[key] ||=
|
87
|
+
key && sections[key] ||= SectionWrapper.new(h)
|
61
88
|
end
|
62
89
|
end
|
63
|
-
sections
|
64
90
|
end
|
65
91
|
|
66
92
|
def find_key(h)
|
67
93
|
title = normalize(h.content)
|
68
|
-
|
94
|
+
SECTION_DEFS.each do |grp|
|
69
95
|
grp[:patterns].each do |pat|
|
70
|
-
m = title.match(/#{pat}/i)
|
71
|
-
next unless m
|
96
|
+
next unless (m = title.match(/#{pat}/i))
|
72
97
|
|
73
98
|
no = m.names.include?('no') && m['no'] || '1'
|
74
99
|
return format(grp[:key], no: no)
|
@@ -77,37 +102,6 @@ module AtCoderFriends
|
|
77
102
|
nil
|
78
103
|
end
|
79
104
|
|
80
|
-
def parse_section(h)
|
81
|
-
text = ''
|
82
|
-
pre = nil
|
83
|
-
nx = h.next
|
84
|
-
while nx && nx.name != h.name
|
85
|
-
text += nx.content.gsub("\r\n", "\n")
|
86
|
-
%w[pre blockquote].each do |tag|
|
87
|
-
pre ||= (nx.name == tag ? nx : nx.search(tag)[0])
|
88
|
-
end
|
89
|
-
nx = nx.next
|
90
|
-
end
|
91
|
-
code = (pre&.text || '').lstrip.gsub("\r\n", "\n")
|
92
|
-
[text, code]
|
93
|
-
end
|
94
|
-
|
95
|
-
def apply_sections(pbm, sections)
|
96
|
-
sections.each do |key, (text, code)|
|
97
|
-
case key
|
98
|
-
when 'constraints'
|
99
|
-
pbm.desc += text
|
100
|
-
when 'input format'
|
101
|
-
pbm.desc += text
|
102
|
-
pbm.fmt = code
|
103
|
-
when /^sample input (?<no>\d+)$/
|
104
|
-
pbm.add_smp($LAST_MATCH_INFO[:no], :in, code)
|
105
|
-
when /^sample output (?<no>\d+)$/
|
106
|
-
pbm.add_smp($LAST_MATCH_INFO[:no], :exp, code)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
105
|
def normalize(s)
|
112
106
|
s
|
113
107
|
.tr(' 0-9A-Za-z', ' 0-9A-Za-z')
|