at_coder_friends 0.3.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.
- 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
|