at_coder_friends 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +27 -0
- data/.rubocop_todo.yml +8 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +83 -0
- data/LICENSE.txt +21 -0
- data/README.md +114 -0
- data/Rakefile +8 -0
- data/at_coder_friends.gemspec +38 -0
- data/bin/console +15 -0
- data/bin/setup +10 -0
- data/config/.at_coder_friends.yml.sample +2 -0
- data/exe/at_coder_friends +8 -0
- data/lib/at_coder_friends.rb +17 -0
- data/lib/at_coder_friends/cli.rb +122 -0
- data/lib/at_coder_friends/config_loader.rb +35 -0
- data/lib/at_coder_friends/cxx_generator.rb +179 -0
- data/lib/at_coder_friends/emitter.rb +43 -0
- data/lib/at_coder_friends/errors.rb +7 -0
- data/lib/at_coder_friends/format_parser.rb +148 -0
- data/lib/at_coder_friends/judge_test_runner.rb +34 -0
- data/lib/at_coder_friends/path_util.rb +33 -0
- data/lib/at_coder_friends/problem.rb +48 -0
- data/lib/at_coder_friends/ruby_generator.rb +97 -0
- data/lib/at_coder_friends/sample_test_runner.rb +31 -0
- data/lib/at_coder_friends/scraping_agent.rb +135 -0
- data/lib/at_coder_friends/test_runner.rb +82 -0
- data/lib/at_coder_friends/verifier.rb +33 -0
- data/lib/at_coder_friends/version.rb +5 -0
- metadata +166 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'at_coder_friends/version'
|
4
|
+
require 'at_coder_friends/errors'
|
5
|
+
require 'at_coder_friends/path_util'
|
6
|
+
require 'at_coder_friends/config_loader'
|
7
|
+
require 'at_coder_friends/verifier'
|
8
|
+
require 'at_coder_friends/test_runner'
|
9
|
+
require 'at_coder_friends/sample_test_runner'
|
10
|
+
require 'at_coder_friends/judge_test_runner'
|
11
|
+
require 'at_coder_friends/problem'
|
12
|
+
require 'at_coder_friends/scraping_agent'
|
13
|
+
require 'at_coder_friends/format_parser'
|
14
|
+
require 'at_coder_friends/ruby_generator'
|
15
|
+
require 'at_coder_friends/cxx_generator'
|
16
|
+
require 'at_coder_friends/emitter'
|
17
|
+
require 'at_coder_friends/cli'
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module AtCoderFriends
|
6
|
+
# command line interface
|
7
|
+
class CLI
|
8
|
+
include PathUtil
|
9
|
+
|
10
|
+
EXITING_OPTIONS = %i[version].freeze
|
11
|
+
OPTION_BANNER =
|
12
|
+
<<~TEXT
|
13
|
+
Usage:
|
14
|
+
at_coder_friends setup path/contest # setup contest folder
|
15
|
+
at_coder_friends test-one path/contest/src # run 1st test case
|
16
|
+
at_coder_friends test-all path/contest/src # run all test cases
|
17
|
+
at_coder_friends submit path/contest/src # submit source code
|
18
|
+
Options:
|
19
|
+
TEXT
|
20
|
+
STATUS_SUCCESS = 0
|
21
|
+
STATUS_ERROR = 1
|
22
|
+
|
23
|
+
def run(args = ARGV)
|
24
|
+
parse_options!(args)
|
25
|
+
handle_exiting_option
|
26
|
+
raise ParamError, 'command or path is not specified.' if args.size < 2
|
27
|
+
@config = ConfigLoader.load_config(args[1])
|
28
|
+
exec_command(*args)
|
29
|
+
STATUS_SUCCESS
|
30
|
+
rescue AtCoderFriends::ParamError => e
|
31
|
+
warn @usage
|
32
|
+
warn "error: #{e.message}"
|
33
|
+
STATUS_ERROR
|
34
|
+
rescue AtCoderFriends::AppError => e
|
35
|
+
warn e.message
|
36
|
+
STATUS_ERROR
|
37
|
+
rescue SystemExit => e
|
38
|
+
e.status
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_options!(args)
|
42
|
+
op = OptionParser.new do |opts|
|
43
|
+
opts.banner = OPTION_BANNER
|
44
|
+
opts.on('-v', '--version', 'Display version.') do
|
45
|
+
@options[:version] = true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@usage = op.to_s
|
49
|
+
@options = {}
|
50
|
+
op.parse!(args)
|
51
|
+
rescue OptionParser::InvalidOption => e
|
52
|
+
raise ParamError, e.message
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_exiting_option
|
56
|
+
return unless EXITING_OPTIONS.any? { |o| @options.key? o }
|
57
|
+
puts AtCoderFriends::VERSION if @options[:version]
|
58
|
+
exit STATUS_SUCCESS
|
59
|
+
end
|
60
|
+
|
61
|
+
def exec_command(command, path, id = nil)
|
62
|
+
case command
|
63
|
+
when 'setup'
|
64
|
+
setup(path)
|
65
|
+
when 'test-one'
|
66
|
+
test_one(path, id)
|
67
|
+
when 'test-all'
|
68
|
+
test_all(path)
|
69
|
+
when 'submit'
|
70
|
+
submit(path)
|
71
|
+
when 'judge-one'
|
72
|
+
judge_one(path, id)
|
73
|
+
when 'judge-all'
|
74
|
+
judge_all(path)
|
75
|
+
else
|
76
|
+
raise ParamError, "unknown command: #{command}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def setup(path)
|
81
|
+
raise AppError, "#{path} is not empty." \
|
82
|
+
if Dir.exist?(path) && !Dir["#{path}/*"].empty?
|
83
|
+
agent = ScrapingAgent.new(contest_name(path), @config)
|
84
|
+
parser = FormatParser.new
|
85
|
+
rb_gen = RubyGenerator.new
|
86
|
+
cxx_gen = CxxGenerator.new
|
87
|
+
emitter = Emitter.new(path)
|
88
|
+
agent.fetch_all do |pbm|
|
89
|
+
parser.process(pbm)
|
90
|
+
rb_gen.process(pbm)
|
91
|
+
cxx_gen.process(pbm)
|
92
|
+
emitter.emit(pbm)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_one(path, id)
|
97
|
+
id ||= 1
|
98
|
+
SampleTestRunner.new(path).test_one(id)
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_all(path)
|
102
|
+
SampleTestRunner.new(path).test_all
|
103
|
+
Verifier.new(path).verify
|
104
|
+
end
|
105
|
+
|
106
|
+
def submit(path)
|
107
|
+
vf = Verifier.new(path)
|
108
|
+
raise AppError, "#{vf.file} has not been tested." unless vf.verified?
|
109
|
+
ScrapingAgent.new(contest_name(path), @config).submit(path)
|
110
|
+
vf.unverify
|
111
|
+
end
|
112
|
+
|
113
|
+
def judge_one(path, id)
|
114
|
+
id ||= ''
|
115
|
+
JudgeTestRunner.new(path).judge_one(id)
|
116
|
+
end
|
117
|
+
|
118
|
+
def judge_all(path)
|
119
|
+
JudgeTestRunner.new(path).judge_all
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module AtCoderFriends
|
7
|
+
# loads configuration file from the specified directory.
|
8
|
+
class ConfigLoader
|
9
|
+
DOTFILE = '.at_coder_friends.yml'
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def load_config(target_dir)
|
13
|
+
path = find_file_upwards(DOTFILE, target_dir)
|
14
|
+
load_yaml(path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_file_upwards(filename, start_dir)
|
18
|
+
Pathname.new(start_dir).expand_path.ascend do |dir|
|
19
|
+
file = dir + filename
|
20
|
+
return file.to_s if file.exist?
|
21
|
+
end
|
22
|
+
raise ConfigNotFoundError,
|
23
|
+
"Configuration file not found: #{start_dir}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_yaml(path)
|
27
|
+
yaml = IO.read(path, encoding: Encoding::UTF_8)
|
28
|
+
YAML.safe_load(yaml, [], [], false, path)
|
29
|
+
rescue Errno::ENOENT
|
30
|
+
raise ConfigNotFoundError,
|
31
|
+
"Configuration file not found: #{path}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
# generates C++ source code from definition
|
5
|
+
class CxxGenerator
|
6
|
+
# rubocop:disable Style/FormatStringToken
|
7
|
+
TEMPLATE = <<~TEXT
|
8
|
+
#include <cstdio>
|
9
|
+
|
10
|
+
using namespace std;
|
11
|
+
|
12
|
+
#define REP(i,n) for(int i=0; i<(int)(n); i++)
|
13
|
+
#define FOR(i,b,e) for(int i=(b); i<=(int)(e); i++)
|
14
|
+
|
15
|
+
/*** CONSTS ***/
|
16
|
+
|
17
|
+
/*** DCLS ***/
|
18
|
+
|
19
|
+
void solve() {
|
20
|
+
int ans = 0;
|
21
|
+
printf("%d\\n", ans);
|
22
|
+
}
|
23
|
+
|
24
|
+
void input() {
|
25
|
+
/*** READS ***/
|
26
|
+
}
|
27
|
+
|
28
|
+
int main() {
|
29
|
+
input();
|
30
|
+
solve();
|
31
|
+
return 0;
|
32
|
+
}
|
33
|
+
TEXT
|
34
|
+
# rubocop:enable Style/FormatStringToken
|
35
|
+
|
36
|
+
SCANF_FMTS = [
|
37
|
+
'scanf("%<fmt>s", %<addr>s);',
|
38
|
+
'REP(i, %<sz1>s) scanf("%<fmt>s", %<addr>s);',
|
39
|
+
'REP(i, %<sz1>s) REP(j, %<sz2>s) scanf("%<fmt>s", %<addr>s);'
|
40
|
+
].freeze
|
41
|
+
|
42
|
+
# rubocop:disable Style/FormatStringToken
|
43
|
+
FMT_FMTS = { number: '%d', string: '%s', char: '%s' }.freeze
|
44
|
+
# rubocop:enable Style/FormatStringToken
|
45
|
+
|
46
|
+
ADDR_FMTS = {
|
47
|
+
single: {
|
48
|
+
number: '&%<v>s',
|
49
|
+
string: '%<v>s'
|
50
|
+
},
|
51
|
+
harray: {
|
52
|
+
number: '%<v>s + i',
|
53
|
+
string: '%<v>s[i]',
|
54
|
+
char: '%<v>s'
|
55
|
+
},
|
56
|
+
varray: {
|
57
|
+
number: '%<v>s + i',
|
58
|
+
string: '%<v>s[i]'
|
59
|
+
},
|
60
|
+
matrix: {
|
61
|
+
number: '&%<v>s[i][j]',
|
62
|
+
string: '%<v>s[i][j]',
|
63
|
+
char: '%<v>s[i]'
|
64
|
+
}
|
65
|
+
}.freeze
|
66
|
+
|
67
|
+
def process(pbm)
|
68
|
+
src = generate(pbm.defs, pbm.desc)
|
69
|
+
pbm.add_src(:cxx, src)
|
70
|
+
end
|
71
|
+
|
72
|
+
def generate(defs, desc)
|
73
|
+
consts = gen_consts(desc)
|
74
|
+
dcls = gen_decls(defs)
|
75
|
+
reads = gen_reads(defs)
|
76
|
+
TEMPLATE
|
77
|
+
.sub('/*** CONSTS ***/', consts.join("\n"))
|
78
|
+
.sub('/*** DCLS ***/', dcls.join("\n"))
|
79
|
+
.sub('/*** READS ***/', reads.map { |s| ' ' + s }.join("\n"))
|
80
|
+
end
|
81
|
+
|
82
|
+
def gen_consts(desc)
|
83
|
+
desc
|
84
|
+
.gsub(/[,\\\(\)\{\}\|]/, '')
|
85
|
+
.gsub(/(≤|leq)/i, '≦')
|
86
|
+
.scan(/([\da-z_]+)\s*≦\s*(\d+)(?:\^(\d+))?/i)
|
87
|
+
.map do |v, sz, k|
|
88
|
+
sz = sz.to_i
|
89
|
+
sz **= k.to_i if k
|
90
|
+
"const int #{v.upcase}_MAX = #{sz};"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def gen_decls(defs)
|
95
|
+
defs.map { |inpdef| gen_decl(inpdef) }.flatten
|
96
|
+
end
|
97
|
+
|
98
|
+
def gen_decl(inpdef)
|
99
|
+
case inpdef.container
|
100
|
+
when :single
|
101
|
+
gen_single_decl(inpdef)
|
102
|
+
when :harray
|
103
|
+
gen_harray_decl(inpdef)
|
104
|
+
when :varray
|
105
|
+
gen_varray_decl(inpdef)
|
106
|
+
when :matrix
|
107
|
+
gen_matrix_decl(inpdef)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def gen_single_decl(inpdef)
|
112
|
+
names = inpdef.names
|
113
|
+
case inpdef.item
|
114
|
+
when :number
|
115
|
+
dcl = names.join(', ')
|
116
|
+
"int #{dcl};"
|
117
|
+
when :string
|
118
|
+
names.map { |v| "char #{v}[#{v.upcase}_MAX + 1];" }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def gen_harray_decl(inpdef)
|
123
|
+
v = inpdef.names[0]
|
124
|
+
sz = gen_arr_size(inpdef.size)[0]
|
125
|
+
case inpdef.item
|
126
|
+
when :number
|
127
|
+
"int #{v}[#{sz}];"
|
128
|
+
when :string
|
129
|
+
"char #{v}[#{sz}][#{v.upcase}_MAX + 1];"
|
130
|
+
when :char
|
131
|
+
"char #{v}[#{sz} + 1];"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def gen_varray_decl(inpdef)
|
136
|
+
names = inpdef.names
|
137
|
+
sz = gen_arr_size(inpdef.size)[0]
|
138
|
+
case inpdef.item
|
139
|
+
when :number
|
140
|
+
names.map { |v| "int #{v}[#{sz}];" }
|
141
|
+
when :string
|
142
|
+
names.map { |v| "char #{v}[#{sz}][#{v.upcase}_MAX + 1];" }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def gen_matrix_decl(inpdef)
|
147
|
+
v = inpdef.names[0]
|
148
|
+
sz1, sz2 = gen_arr_size(inpdef.size)
|
149
|
+
case inpdef.item
|
150
|
+
when :number
|
151
|
+
"int #{v}[#{sz1}][#{sz2}];"
|
152
|
+
when :string
|
153
|
+
"char #{v}[#{sz1}][#{sz2}][#{v.upcase}_MAX + 1];"
|
154
|
+
when :char
|
155
|
+
"char #{v}[#{sz1}][#{sz2} + 1];"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def gen_arr_size(szs)
|
160
|
+
szs.map { |sz| sz =~ /\D/ ? "#{sz.upcase}_MAX" : sz }
|
161
|
+
end
|
162
|
+
|
163
|
+
def gen_reads(defs)
|
164
|
+
defs.map { |inpdef| gen_read(inpdef) }.flatten
|
165
|
+
end
|
166
|
+
|
167
|
+
# rubocop:disable Metrics/AbcSize
|
168
|
+
def gen_read(inpdef)
|
169
|
+
dim = inpdef.size.size - (inpdef.item == :char ? 1 : 0)
|
170
|
+
scanf = SCANF_FMTS[dim]
|
171
|
+
sz1, sz2 = inpdef.size
|
172
|
+
fmt = FMT_FMTS[inpdef.item] * inpdef.names.size
|
173
|
+
addr_fmt = ADDR_FMTS[inpdef.container][inpdef.item]
|
174
|
+
addr = inpdef.names.map { |v| format(addr_fmt, v: v) }.join(', ')
|
175
|
+
format(scanf, sz1: sz1, sz2: sz2, fmt: fmt, addr: addr)
|
176
|
+
end
|
177
|
+
# rubocop:enable Metrics/AbcSize
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module AtCoderFriends
|
6
|
+
# emits source skeletons and sample input/output(s)
|
7
|
+
# of a problem to the specified directory.
|
8
|
+
class Emitter
|
9
|
+
include PathUtil
|
10
|
+
|
11
|
+
def initialize(dir)
|
12
|
+
@src_dir = dir
|
13
|
+
@smp_dir = smp_dir(dir)
|
14
|
+
end
|
15
|
+
|
16
|
+
def emit(pbm)
|
17
|
+
pbm.smps.each { |smp| emit_sample(pbm, smp) }
|
18
|
+
pbm.srcs.each { |src| emit_source(pbm, src) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def emit_sample(pbm, smp)
|
22
|
+
makedirs_unless @smp_dir
|
23
|
+
smp_file = format(
|
24
|
+
'%<q>s_%<n>03d.%<ext>s', q: pbm.q, n: smp.no, ext: smp.ext
|
25
|
+
)
|
26
|
+
smp_path = File.join(@smp_dir, smp_file)
|
27
|
+
File.write(smp_path, smp.txt)
|
28
|
+
puts smp_file
|
29
|
+
end
|
30
|
+
|
31
|
+
def emit_source(pbm, src)
|
32
|
+
makedirs_unless @src_dir
|
33
|
+
src_file = format('%<q>s.%<ext>s', q: pbm.q, ext: src.ext)
|
34
|
+
src_path = File.join(@src_dir, src_file)
|
35
|
+
File.write(src_path, src.txt)
|
36
|
+
puts src_file
|
37
|
+
end
|
38
|
+
|
39
|
+
def makedirs_unless(dir)
|
40
|
+
FileUtils.makedirs(dir) unless Dir.exist?(dir)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
# Iterates through elements of an array
|
5
|
+
class Iterator
|
6
|
+
def initialize(array)
|
7
|
+
@array = array
|
8
|
+
@i = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def next?
|
12
|
+
@i < @array.size
|
13
|
+
end
|
14
|
+
|
15
|
+
def next
|
16
|
+
ret = @array[@i]
|
17
|
+
@i += 1
|
18
|
+
ret
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# parses input data format and generates input definitons
|
23
|
+
class FormatParser
|
24
|
+
PARSERS = [
|
25
|
+
{
|
26
|
+
container: :harray,
|
27
|
+
item: :number,
|
28
|
+
pat: /^(?<v>[a-z]+)[01](\s+\k<v>.)*(\s+\.+)?(\s+\k<v>.)+$/i,
|
29
|
+
names: ->(m) { [m[:v]] },
|
30
|
+
pat2: ->(_) { nil },
|
31
|
+
size: ->(f) { [f[-1]] }
|
32
|
+
},
|
33
|
+
{
|
34
|
+
container: :harray,
|
35
|
+
item: :char,
|
36
|
+
pat: /^(?<v>[a-z]+)[01](\k<v>.)*(\s*\.+\s*)?(\k<v>.)+$/i,
|
37
|
+
names: ->(m) { [m[:v]] },
|
38
|
+
pat2: ->(_) { nil },
|
39
|
+
size: ->(f) { [f[-1]] }
|
40
|
+
},
|
41
|
+
{
|
42
|
+
container: :matrix,
|
43
|
+
item: :number,
|
44
|
+
pat: /^(?<v>[a-z]+)[01][01](\s+\k<v>..)*(\s+\.+)?(\s+\k<v>..)+$/i,
|
45
|
+
names: ->(m) { [m[:v]] },
|
46
|
+
pat2: ->(v) { /(^#{v}..(\s+#{v}..)*(\s+\.+)?(\s+#{v}..)+|\.+)$/ },
|
47
|
+
size: ->(f) { f[-2..-1].chars.to_a }
|
48
|
+
},
|
49
|
+
{
|
50
|
+
container: :matrix,
|
51
|
+
item: :char,
|
52
|
+
pat: /^(?<v>[a-z]+)[01][01](\k<v>..)*(\s*\.+\s*)?(\k<v>..)+$/i,
|
53
|
+
names: ->(m) { [m[:v]] },
|
54
|
+
pat2: ->(v) { /(^#{v}..(#{v}..)*(\s*\.+\s*)?(#{v}..)+|\.+)$/ },
|
55
|
+
size: ->(f) { f[-2..-1].chars.to_a }
|
56
|
+
},
|
57
|
+
{
|
58
|
+
container: :varray,
|
59
|
+
item: :number,
|
60
|
+
pat: /^[a-z]+(?<i>[0-9])(\s+[a-z]+\k<i>)*$/i,
|
61
|
+
names: ->(m) { m[0].split.map { |w| w[0..-2] } },
|
62
|
+
pat2: lambda { |vs|
|
63
|
+
pat = vs.map { |v| v + '.+' }.join('\s+')
|
64
|
+
/^(#{pat}|\.+)$/
|
65
|
+
},
|
66
|
+
size: ->(f) { /(?<sz>\d+)$/ =~ f ? [sz] : [f[-1]] }
|
67
|
+
},
|
68
|
+
{
|
69
|
+
container: :single,
|
70
|
+
item: :number,
|
71
|
+
pat: /^[a-z]+(\s+[a-z]+)*$/i,
|
72
|
+
names: ->(m) { m[0].split },
|
73
|
+
pat2: ->(_) { nil },
|
74
|
+
size: ->(_) { [] }
|
75
|
+
}
|
76
|
+
].freeze
|
77
|
+
|
78
|
+
def process(pbm)
|
79
|
+
defs = parse(pbm.fmt, pbm.smps)
|
80
|
+
pbm.defs = defs
|
81
|
+
end
|
82
|
+
|
83
|
+
def parse(fmt, smps)
|
84
|
+
lines = split_trim(fmt)
|
85
|
+
defs = parse_fmt(lines)
|
86
|
+
smpx = max_smp(smps)
|
87
|
+
return defs unless smpx
|
88
|
+
match_smp!(defs, smpx)
|
89
|
+
end
|
90
|
+
|
91
|
+
def split_trim(fmt)
|
92
|
+
fmt
|
93
|
+
.gsub(/[+-]1/, '') # N-1, N+1 -> N
|
94
|
+
.gsub(%r{[-/ ]}, ' ') # a-b, a/b -> a b
|
95
|
+
.gsub(/\{.*?\}/) { |w| w.delete(' ') } # {1, 1} -> {1,1} shortest match
|
96
|
+
.gsub(/[_,'\\\(\)\{\}]/, '')
|
97
|
+
.gsub(/[::…‥]+/, '..')
|
98
|
+
.gsub(/^[\.\s]+$/, '..')
|
99
|
+
.split("\n")
|
100
|
+
.map(&:strip)
|
101
|
+
end
|
102
|
+
|
103
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
104
|
+
def parse_fmt(lines)
|
105
|
+
it = Iterator.new(lines + ['']) # sentinel
|
106
|
+
prv = nil
|
107
|
+
cur = it.next
|
108
|
+
Enumerator.new do |y|
|
109
|
+
loop do
|
110
|
+
unless (parser = PARSERS.find { |ps| ps[:pat] =~ cur })
|
111
|
+
puts "unknown format: #{cur}" unless cur.empty?
|
112
|
+
(cur = it.next) ? next : break
|
113
|
+
end
|
114
|
+
container, item = parser.values_at(:container, :item)
|
115
|
+
m = parser[:pat].match(cur)
|
116
|
+
names = parser[:names].call(m)
|
117
|
+
pat2 = parser[:pat2].call(names)
|
118
|
+
loop do
|
119
|
+
prv = cur
|
120
|
+
cur = it.next
|
121
|
+
break unless pat2 && pat2 =~ cur
|
122
|
+
end
|
123
|
+
size = parser[:size].call(prv)
|
124
|
+
y << InputDef.new(container, item, names, size)
|
125
|
+
end
|
126
|
+
end.to_a
|
127
|
+
end
|
128
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
129
|
+
|
130
|
+
def max_smp(smps)
|
131
|
+
smps
|
132
|
+
.select { |smp| smp.ext == :in }
|
133
|
+
.max_by { |smp| smp.txt.size }
|
134
|
+
&.txt
|
135
|
+
end
|
136
|
+
|
137
|
+
def match_smp!(inpdefs, smp)
|
138
|
+
lines = smp.split("\n")
|
139
|
+
inpdefs.each_with_index do |inpdef, i|
|
140
|
+
break if i > lines.size
|
141
|
+
next if inpdef.item != :number
|
142
|
+
inpdef.item = :string if lines[i].split[0] =~ /[^\-0-9]/
|
143
|
+
break if %i[varray matrix].include?(inpdef.container)
|
144
|
+
end
|
145
|
+
inpdefs
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|