at_coder_friends 0.6.8 → 0.7.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +1 -0
  3. data/.github/workflows/codeql-analysis.yml +70 -0
  4. data/.github/workflows/ruby.yml +38 -0
  5. data/.gitignore +3 -0
  6. data/.rubocop.yml +8 -1
  7. data/CHANGELOG.md +23 -4
  8. data/Gemfile.lock +43 -43
  9. data/README.md +15 -92
  10. data/at_coder_friends.gemspec +4 -3
  11. data/lib/at_coder_friends/cli.rb +13 -28
  12. data/lib/at_coder_friends/config_loader.rb +2 -4
  13. data/lib/at_coder_friends/generator/any_builtin.rb +22 -0
  14. data/lib/at_coder_friends/generator/base.rb +55 -6
  15. data/lib/at_coder_friends/generator/c_builtin.rb +22 -0
  16. data/lib/at_coder_friends/generator/cxx_builtin.rb +9 -241
  17. data/lib/at_coder_friends/generator/fragment.rb +106 -0
  18. data/lib/at_coder_friends/generator/main.rb +20 -14
  19. data/lib/at_coder_friends/generator/ruby_builtin.rb +13 -166
  20. data/lib/at_coder_friends/parser/binary.rb +12 -5
  21. data/lib/at_coder_friends/parser/constraints.rb +8 -8
  22. data/lib/at_coder_friends/parser/input_format.rb +19 -15
  23. data/lib/at_coder_friends/parser/modulo.rb +5 -7
  24. data/lib/at_coder_friends/parser/section_wrapper.rb +3 -5
  25. data/lib/at_coder_friends/parser/sections.rb +2 -2
  26. data/lib/at_coder_friends/path_util.rb +1 -1
  27. data/lib/at_coder_friends/problem.rb +5 -5
  28. data/lib/at_coder_friends/scraping/agent.rb +1 -1
  29. data/lib/at_coder_friends/scraping/authentication.rb +3 -3
  30. data/lib/at_coder_friends/scraping/session.rb +1 -1
  31. data/lib/at_coder_friends/scraping/tasks.rb +6 -8
  32. data/lib/at_coder_friends/test_runner/base.rb +1 -2
  33. data/lib/at_coder_friends/test_runner/judge.rb +3 -3
  34. data/lib/at_coder_friends/version.rb +1 -1
  35. data/lib/at_coder_friends.rb +3 -0
  36. data/templates/any_builtin.md.erb +30 -0
  37. data/templates/any_builtin_fragments.yml +5 -0
  38. data/templates/c_builtin.c.erb +36 -0
  39. data/templates/c_builtin_fragments.yml +127 -0
  40. data/templates/csharp_sample.cs.erb +29 -0
  41. data/templates/csharp_sample_fragments.yml +159 -0
  42. data/templates/java_sample.java.erb +25 -0
  43. data/templates/java_sample_fragments.yml +98 -0
  44. data/templates/ruby_builtin_fragments.yml +93 -0
  45. metadata +26 -6
  46. data/.travis.yml +0 -16
  47. data/docs/CONFIGURATION.md +0 -421
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'yaml'
3
4
  require 'erb'
4
5
 
5
6
  module AtCoderFriends
6
7
  module Generator
7
- Attributes = Struct.new(:file_ext, :default_template)
8
+ Attributes = Struct.new(:file_ext, :template, :fragments)
8
9
 
9
10
  # common behavior of generators
10
11
  class Base
12
+ ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
13
+ TMPL_DIR = File.join(ACF_HOME, 'templates')
14
+
11
15
  attr_reader :cfg, :pbm
12
16
 
13
17
  def initialize(cfg = nil)
@@ -15,21 +19,36 @@ module AtCoderFriends
15
19
  end
16
20
 
17
21
  def process(pbm)
18
- pbm.add_src(attrs.file_ext, generate(pbm))
22
+ pbm.add_src(config_file_ext, generate(pbm))
19
23
  end
20
24
 
21
25
  def generate(pbm)
22
26
  @pbm = pbm
23
- src = File.read(select_template)
24
- src = ERB.new(src, safe_level = nil, trim_mode = '-').result(binding)
27
+ template = File.read(config_template)
28
+ src = ERB.new(template, trim_mode: '-').result(binding)
25
29
  src = render(src) if respond_to?(:render)
26
30
  src
27
31
  end
28
32
 
29
- def select_template
30
- cfg['default_template'] || attrs.default_template
33
+ def fragments
34
+ @fragments ||= YAML.load_file(config_fragments)
35
+ end
36
+
37
+ def config_file_ext
38
+ cfg['file_ext']&.to_sym || attrs.file_ext
31
39
  end
32
40
 
41
+ def config_template
42
+ template = cfg['template'] || cfg['default_template'] || attrs.template
43
+ template.sub(/\A@/, TMPL_DIR)
44
+ end
45
+
46
+ def config_fragments
47
+ fragments = cfg['fragments'] || attrs.fragments
48
+ fragments.sub(/\A@/, TMPL_DIR)
49
+ end
50
+
51
+ # deprecated, use ERB syntax
33
52
  def embed_lines(src, pat, lines)
34
53
  re = Regexp.escape(pat)
35
54
  src.gsub(
@@ -38,5 +57,35 @@ module AtCoderFriends
38
57
  )
39
58
  end
40
59
  end
60
+
61
+ module ConstFragmentMixin
62
+ def gen_consts
63
+ pbm.constants.map { |c| gen_const(c) }
64
+ end
65
+
66
+ def gen_const(c)
67
+ ConstFragment.new(c, fragments['constant']).generate
68
+ end
69
+ end
70
+
71
+ module DeclFragmentMixin
72
+ def gen_decls
73
+ pbm.formats.map { |inpdef| gen_decl(inpdef).split("\n") }.flatten
74
+ end
75
+
76
+ def gen_decl(inpdef)
77
+ InputFormatFragment.new(inpdef, fragments['declaration']).generate
78
+ end
79
+ end
80
+
81
+ module InputFragmentMixin
82
+ def gen_inputs
83
+ pbm.formats.map { |inpdef| gen_input(inpdef).split("\n") }.flatten
84
+ end
85
+
86
+ def gen_input(inpdef)
87
+ InputFormatFragment.new(inpdef, fragments['input']).generate
88
+ end
89
+ end
41
90
  end
42
91
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ module Generator
5
+ # generates C source from problem description
6
+ class CBuiltin < Base
7
+ include ConstFragmentMixin
8
+ include DeclFragmentMixin
9
+ include InputFragmentMixin
10
+
11
+ ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
12
+ TMPL_DIR = File.join(ACF_HOME, 'templates')
13
+ TEMPLATE = File.join(TMPL_DIR, 'c_builtin.c.erb')
14
+ FRAGMENTS = File.join(TMPL_DIR, 'c_builtin_fragments.yml')
15
+ ATTRS = Attributes.new(:c, TEMPLATE, FRAGMENTS)
16
+
17
+ def attrs
18
+ ATTRS
19
+ end
20
+ end
21
+ end
22
+ end
@@ -2,256 +2,24 @@
2
2
 
3
3
  module AtCoderFriends
4
4
  module Generator
5
- # generates C++ constants
6
- module CxxBuiltinConstGen
7
- def gen_const(c)
8
- v = cnv_const_value(c.value)
9
- if c.type == :max
10
- "const int #{c.name.upcase}_MAX = #{v};"
11
- else
12
- "const int MOD = #{v};"
13
- end
14
- end
15
-
16
- def cnv_const_value(v)
17
- v
18
- .sub(/\b10\^/, '1e')
19
- .sub(/\b2\^/, '1<<')
20
- .gsub(',', "'")
21
- end
22
- end
23
-
24
- # generates C++ variable declarations
25
- module CxxBuiltinDeclGen
26
- TYPE_TBL = {
27
- number: 'int',
28
- decimal: 'double',
29
- string: 'char',
30
- char: 'char'
31
- }.tap { |h| h.default = 'int' }
32
- def gen_decl(inpdef)
33
- if inpdef.components
34
- inpdef.components.map { |cmp| gen_decl(cmp) }
35
- else
36
- case inpdef.container
37
- when :single
38
- gen_single_decl(inpdef)
39
- when :harray
40
- gen_harray_decl(inpdef)
41
- when :varray
42
- gen_varray_decl(inpdef)
43
- when :matrix, :vmatrix, :hmatrix
44
- gen_matrix_decl(inpdef)
45
- end
46
- end
47
- end
48
-
49
- def gen_single_decl(inpdef)
50
- names, cols = inpdef.vars.transpose
51
- if cols.uniq.size == 1 && cols[0] != :string
52
- type = TYPE_TBL[cols[0]]
53
- dcl = names.join(', ')
54
- "#{type} #{dcl};"
55
- else
56
- inpdef.vars.map do |v, item|
57
- type = TYPE_TBL[item]
58
- dcl = v
59
- dcl += "[#{v.upcase}_MAX + 1]" if item == :string
60
- "#{type} #{dcl};"
61
- end
62
- end
63
- end
64
-
65
- def gen_harray_decl(inpdef)
66
- type = TYPE_TBL[inpdef.item]
67
- v = inpdef.names[0]
68
- sz = gen_arr_size(inpdef.size)[0]
69
- case inpdef.item
70
- when :number, :decimal
71
- "#{type} #{v}[#{sz}];"
72
- when :string
73
- "#{type} #{v}[#{sz}][#{v.upcase}_MAX + 1];"
74
- when :char
75
- "#{type} #{v}[#{sz} + 1];"
76
- end
77
- end
78
-
79
- def gen_varray_decl(inpdef)
80
- sz = gen_arr_size(inpdef.size)[0]
81
- inpdef.vars.map do |v, item|
82
- type = TYPE_TBL[item]
83
- dcl = "#{v}[#{sz}]"
84
- dcl += "[#{v.upcase}_MAX + 1]" if item == :string
85
- "#{type} #{dcl};"
86
- end
87
- end
88
-
89
- def gen_matrix_decl(inpdef)
90
- sz1, sz2 = gen_arr_size(inpdef.size)
91
- inpdef.vars.map do |v, item|
92
- type = TYPE_TBL[item]
93
- dcl = "#{v}[#{sz1}]"
94
- dcl += item == :char ? "[#{sz2} + 1]" : "[#{sz2}]"
95
- dcl += "[#{v.upcase}_MAX + 1]" if item == :string
96
- "#{type} #{dcl};"
97
- end
98
- end
99
-
100
- def gen_arr_size(szs)
101
- szs.map { |sz| sz.gsub(/([a-z][a-z0-9_]*)/i, '\1_MAX').upcase }
102
- end
103
- end
104
-
105
- # generates C++ input source
106
- module CxxBuiltinInputGen
107
- SCANF_FMTS = [
108
- 'scanf("%<fmt>s", %<addr>s);',
109
- 'REP(i, %<sz1>s) scanf("%<fmt>s", %<addr>s);',
110
- 'REP(i, %<sz1>s) REP(j, %<sz2>s) scanf("%<fmt>s", %<addr>s);'
111
- ].freeze
112
- SCANF_FMTS_CMB = {
113
- varray_matrix:
114
- [
115
- <<~TEXT,
116
- REP(i, %<sz1>s) {
117
- scanf("%<fmt1>s", %<addr1>s);
118
- scanf("%<fmt2>s", %<addr2>s);
119
- }
120
- TEXT
121
- <<~TEXT
122
- REP(i, %<sz1>s) {
123
- scanf("%<fmt1>s", %<addr1>s);
124
- REP(j, %<sz2>s[i]) scanf("%<fmt2>s", %<addr2>s);
125
- }
126
- TEXT
127
- ],
128
- matrix_varray:
129
- [
130
- <<~TEXT,
131
- REP(i, %<sz1>s) {
132
- scanf("%<fmt1>s", %<addr1>s);
133
- scanf("%<fmt2>s", %<addr2>s);
134
- }
135
- TEXT
136
- <<~TEXT
137
- REP(i, %<sz1>s) {
138
- REP(j, %<sz2>s) scanf("%<fmt1>s", %<addr1>s);
139
- scanf("%<fmt2>s", %<addr2>s);
140
- }
141
- TEXT
142
- ]
143
- }.tap { |h| h.default = h[:varray_matrix] }
144
- FMT_FMTS = {
145
- number: '%d',
146
- decimal: '%lf',
147
- string: '%s',
148
- char: '%s'
149
- }.tap { |h| h.default = h[:number] }
150
- SINGLE_ADDR_FMTS = {
151
- number: '&%<v>s',
152
- decimal: '&%<v>s',
153
- string: '%<v>s'
154
- }.tap { |h| h.default = h[:number] }
155
- ARRAY_ADDR_FMTS = {
156
- number: '%<v>s + i',
157
- decimal: '%<v>s + i',
158
- string: '%<v>s[i]',
159
- char: '%<v>s'
160
- }.tap { |h| h.default = h[:number] }
161
- MATRIX_ADDR_FMTS = {
162
- number: '&%<v>s[i][j]',
163
- decimal: '&%<v>s[i][j]',
164
- string: '%<v>s[i][j]',
165
- char: '%<v>s[i]'
166
- }.tap { |h| h.default = h[:number] }
167
- ADDR_FMTS = {
168
- single: SINGLE_ADDR_FMTS,
169
- harray: ARRAY_ADDR_FMTS,
170
- varray: ARRAY_ADDR_FMTS,
171
- matrix: MATRIX_ADDR_FMTS,
172
- vmatrix: MATRIX_ADDR_FMTS,
173
- hmatrix: MATRIX_ADDR_FMTS
174
- }.tap { |h| h.default = h[:single] }
175
-
176
- def gen_input(inpdef)
177
- if inpdef.components
178
- gen_cmb_input(inpdef)
179
- else
180
- gen_plain_input(inpdef)
181
- end
182
- end
183
-
184
- def gen_plain_input(inpdef)
185
- scanf = SCANF_FMTS[inpdef.size.size - (inpdef.item == :char ? 1 : 0)]
186
- sz1, sz2 = inpdef.size
187
- fmt, addr = scanf_params(inpdef)
188
- format(scanf, sz1: sz1, sz2: sz2, fmt: fmt, addr: addr)
189
- end
190
-
191
- def gen_cmb_input(inpdef)
192
- scanf = SCANF_FMTS_CMB.dig(
193
- inpdef.container, inpdef.item == :char ? 0 : 1
194
- )
195
- sz1 = inpdef.size[0]
196
- sz2 = inpdef.size[1].split('_')[0]
197
- fmt1, addr1, fmt2, addr2 =
198
- inpdef.components.map { |cmp| scanf_params(cmp) }.flatten
199
- format(
200
- scanf,
201
- sz1: sz1, sz2: sz2,
202
- fmt1: fmt1, addr1: addr1,
203
- fmt2: fmt2, addr2: addr2
204
- ).split("\n")
205
- end
206
-
207
- def scanf_params(inpdef)
208
- [scanf_fmt(inpdef), scanf_addr(inpdef)]
209
- end
210
-
211
- def scanf_fmt(inpdef)
212
- inpdef.vars.map { |(_v, item)| FMT_FMTS[item] }.join
213
- end
214
-
215
- def scanf_addr(inpdef)
216
- inpdef.vars.map do |(v, item)|
217
- addr_fmt = ADDR_FMTS.dig(inpdef.container, item)
218
- format(addr_fmt, v: v)
219
- end.join(', ')
220
- end
221
- end
222
-
223
5
  # generates C++ source from problem description
224
- class CxxBuiltin < Base
225
- include CxxBuiltinConstGen
226
- include CxxBuiltinDeclGen
227
- include CxxBuiltinInputGen
228
-
229
- ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
230
- TMPL_DIR = File.join(ACF_HOME, 'templates')
231
- DEFAULT_TMPL = File.join(TMPL_DIR, 'cxx_builtin.cxx.erb')
232
- ATTRS = Attributes.new(:cxx, DEFAULT_TMPL)
6
+ class CxxBuiltin < CBuiltin
7
+ TEMPLATE = File.join(TMPL_DIR, 'cxx_builtin.cxx.erb')
8
+ ATTRS = Attributes.new(:cxx, TEMPLATE, FRAGMENTS)
233
9
 
234
10
  def attrs
235
11
  ATTRS
236
12
  end
237
13
 
14
+ def gen_const(c)
15
+ ConstFragment.new(c, fragments['cxx_constant']).generate
16
+ end
17
+
18
+ # deprecated, use ERB syntax
238
19
  def render(src)
239
20
  src = embed_lines(src, '/*** CONSTS ***/', gen_consts)
240
21
  src = embed_lines(src, '/*** DCLS ***/', gen_decls)
241
- src = embed_lines(src, '/*** INPUTS ***/', gen_inputs)
242
- src
243
- end
244
-
245
- def gen_consts(constants = pbm.constants)
246
- constants.map { |c| gen_const(c) }
247
- end
248
-
249
- def gen_decls(inpdefs = pbm.formats)
250
- inpdefs.map { |inpdef| gen_decl(inpdef) }.flatten
251
- end
252
-
253
- def gen_inputs(inpdefs = pbm.formats)
254
- inpdefs.map { |inpdef| gen_input(inpdef) }.flatten
22
+ embed_lines(src, '/*** INPUTS ***/', gen_inputs)
255
23
  end
256
24
  end
257
25
  end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+
5
+ module AtCoderFriends
6
+ module Generator
7
+ # base class for code fragment generator
8
+ class FragmentBase
9
+ attr_reader :templates
10
+
11
+ def initialize(obj, templates)
12
+ @obj = obj
13
+ @templates = templates
14
+ end
15
+
16
+ def render(*keys)
17
+ keys = keys.map(&:to_s)
18
+ template = templates.dig(*keys) || (raise AppError, "fragment key #{keys} not found")
19
+
20
+ return ERB.new(template, trim_mode: '-').result(binding) if template.is_a?(String)
21
+
22
+ if template.is_a?(Hash)
23
+ sub_key_props = template['__key'] || (raise AppError, "'__key' not found in fragment hash #{keys}")
24
+ sub_keys = sub_key_props.map { |k| send(k) }
25
+ return render(*keys, *sub_keys)
26
+ end
27
+
28
+ raise AppError, "can't render fragment #{keys}"
29
+ end
30
+
31
+ # delegate method calls to obj
32
+ def method_missing(name, *args, &block)
33
+ if @obj.respond_to?(name)
34
+ @obj.send(name, *args, &block)
35
+ elsif templates.key?(name.to_s)
36
+ render(name)
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ def respond_to_missing?(name, include_private = false)
43
+ @obj.respond_to?(name, include_private) ||
44
+ templates.key?(name.to_s) ||
45
+ super
46
+ end
47
+
48
+ def to_s
49
+ @obj.to_s
50
+ end
51
+ end
52
+
53
+ # base class for constant declaration generator
54
+ class ConstFragment < FragmentBase
55
+ def generate
56
+ render(type)
57
+ end
58
+ end
59
+
60
+ # base class for variable declaration generator
61
+ class InputFormatFragment < FragmentBase
62
+ def generate
63
+ render(:main)
64
+ end
65
+
66
+ def vs
67
+ names
68
+ end
69
+
70
+ def v
71
+ vs[0]
72
+ end
73
+
74
+ def sz1
75
+ size[0]
76
+ end
77
+ alias sz sz1
78
+
79
+ def sz2
80
+ size[1]
81
+ end
82
+
83
+ def delims
84
+ delim.chars
85
+ end
86
+
87
+ def vars
88
+ @vars ||= super.map do |v, item|
89
+ var = Problem::InputFormat.new(
90
+ container: container,
91
+ names: [v],
92
+ item: item,
93
+ size: size
94
+ )
95
+ self.class.new(var, templates)
96
+ end
97
+ end
98
+
99
+ def components
100
+ @components ||= super&.map do |cmp|
101
+ self.class.new(cmp, templates)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -12,34 +12,40 @@ module AtCoderFriends
12
12
  end
13
13
 
14
14
  def process(pbm)
15
- generators = ctx.config.dig('generators') || []
15
+ generators = ctx.config['generators'] || []
16
16
  generators.each do |gen_name|
17
- begin
18
- gen_obj = load_obj(gen_name)
19
- gen_obj.process(pbm)
20
- rescue StandardError => e
21
- puts "an error occurred in generator:#{gen_name}."
22
- puts e.to_s
23
- puts e.backtrace
24
- end
17
+ gen_obj = load_obj(gen_name)
18
+ gen_obj.process(pbm)
19
+ rescue StandardError => e
20
+ puts "an error occurred in generator:#{gen_name}."
21
+ puts e
22
+ puts e.backtrace
25
23
  end
26
24
  end
27
25
 
28
26
  def load_obj(gen_name)
29
27
  @cache[gen_name] ||= begin
30
- gen_class = load_class(gen_name)
28
+ cls_name = gen_name.split('_')[0]
29
+ gen_class = load_class(cls_name)
31
30
  gen_cnf = config_for(gen_name)
32
31
  gen_class.new(gen_cnf)
33
32
  end
34
33
  end
35
34
 
36
35
  def load_class(gen_name)
37
- unless AtCoderFriends::Generator.const_defined?(gen_name)
38
- require "at_coder_friends/generator/#{to_snake(gen_name)}"
39
- end
36
+ snake_gen_name = to_snake(gen_name)
37
+ require "at_coder_friends/generator/#{snake_gen_name}" unless AtCoderFriends::Generator.const_defined?(gen_name)
40
38
  AtCoderFriends::Generator.const_get(gen_name)
41
39
  rescue LoadError
42
- raise AppError, "plugin load error : generator #{gen_name} not found."
40
+ raise AppError, <<~MSG
41
+ Error: Failed to load plugin.
42
+ The '#{gen_name}' plugin could not be found. To use this plugin, please install the required gem by following these steps:
43
+
44
+ 1. Open a terminal or command prompt.
45
+ 2. Run the following command:
46
+ gem install at_coder_friends-generator-#{snake_gen_name}
47
+ 3. Once the above command completes, please run the program again.
48
+ MSG
43
49
  end
44
50
 
45
51
  def config_for(gen_name)