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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -2
  3. data/.travis.yml +2 -2
  4. data/CHANGELOG.md +53 -0
  5. data/Gemfile.lock +32 -36
  6. data/README.md +1 -1
  7. data/at_coder_friends.gemspec +5 -7
  8. data/config/default.yml +146 -72
  9. data/docs/CONFIGURATION.md +224 -140
  10. data/lib/at_coder_friends.rb +3 -0
  11. data/lib/at_coder_friends/cli.rb +8 -0
  12. data/lib/at_coder_friends/config_loader.rb +11 -3
  13. data/lib/at_coder_friends/context.rb +10 -6
  14. data/lib/at_coder_friends/emitter.rb +2 -2
  15. data/lib/at_coder_friends/generator/base.rb +42 -0
  16. data/lib/at_coder_friends/generator/cxx_builtin.rb +196 -143
  17. data/lib/at_coder_friends/generator/main.rb +8 -2
  18. data/lib/at_coder_friends/generator/ruby_builtin.rb +97 -51
  19. data/lib/at_coder_friends/parser/constraints.rb +1 -0
  20. data/lib/at_coder_friends/parser/input_format.rb +389 -188
  21. data/lib/at_coder_friends/parser/input_type.rb +92 -0
  22. data/lib/at_coder_friends/parser/main.rb +1 -0
  23. data/lib/at_coder_friends/parser/modulo.rb +1 -1
  24. data/lib/at_coder_friends/parser/sections.rb +3 -3
  25. data/lib/at_coder_friends/path_info.rb +51 -0
  26. data/lib/at_coder_friends/path_util.rb +0 -31
  27. data/lib/at_coder_friends/problem.rb +81 -6
  28. data/lib/at_coder_friends/scraping/agent.rb +11 -2
  29. data/lib/at_coder_friends/scraping/custom_test.rb +5 -6
  30. data/lib/at_coder_friends/scraping/submission.rb +6 -7
  31. data/lib/at_coder_friends/scraping/tasks.rb +8 -3
  32. data/lib/at_coder_friends/test_runner/base.rb +17 -4
  33. data/lib/at_coder_friends/test_runner/judge.rb +7 -9
  34. data/lib/at_coder_friends/test_runner/sample.rb +2 -4
  35. data/lib/at_coder_friends/verifier.rb +2 -3
  36. data/lib/at_coder_friends/version.rb +1 -1
  37. data/templates/{cxx_builtin_interactive.cxx → cxx_builtin.cxx.erb} +26 -4
  38. data/templates/{ruby_builtin_interactive.rb → ruby_builtin.rb.erb} +17 -3
  39. metadata +11 -17
  40. data/tasks/regression/check_const.rake +0 -136
  41. data/tasks/regression/check_diff.rake +0 -30
  42. data/tasks/regression/check_fmt.rake +0 -42
  43. data/tasks/regression/check_parse.rake +0 -70
  44. data/tasks/regression/regression.rb +0 -72
  45. data/tasks/regression/section_list.rake +0 -53
  46. data/tasks/regression/setup.rake +0 -48
  47. data/templates/cxx_builtin_default.cxx +0 -26
  48. 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
@@ -10,6 +10,7 @@ module AtCoderFriends
10
10
  Sections.process(pbm)
11
11
  SampleData.process(pbm)
12
12
  InputFormat.process(pbm)
13
+ InputType.process(pbm)
13
14
  Constraints.process(pbm)
14
15
  Modulo.process(pbm)
15
16
  Interactive.process(pbm)
@@ -21,7 +21,7 @@ module AtCoderFriends
21
21
  |\\\(([^()]+)\\\)
22
22
  |\$([^$]+)\$
23
23
  |\{([^{}]+)\}
24
- |([\d,]+)
24
+ |([\d,^+]+)
25
25
  |([一二三四五六七八九十百千万億]+)
26
26
  )
27
27
  }x.freeze
@@ -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
- InputFormat = Struct.new(:container, :item, :names, :size) do
22
- def initialize(container, item, names, size = [])
23
- super(container, item, names, size)
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
- "#{container} #{item} #{names} #{size}"
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, :formats, :constants
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
- @formats = []
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 ||= contest_name(ctx.path)
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 = split_prg_path(ctx.path)
13
- lang = lang_id(ext)
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(lang, src, data)
15
+ post_custom_test(langs, src, data)
18
16
  check_custom_test
19
17
  end
20
18
 
21
- def post_custom_test(lang, src, data)
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 = split_prg_path(ctx.path)
8
+ path, _dir, prg, _base, ext, q = ctx.path_info.components
11
9
  puts "***** submit #{prg} *****"
12
- lang = lang_id(ext)
10
+ langs = lang_id(ext)
13
11
  src = File.read(path, encoding: Encoding::UTF_8)
14
12
 
15
- post_submit(q, lang, src)
13
+ post_submit(q, langs, src)
16
14
  end
17
15
 
18
- def post_submit(q, lang, src)
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.start_with?(q) }
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
- pbm = fetch_problem(q, url)
11
- yield pbm if block_given?
12
- pbm
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