at_coder_friends 0.6.1 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 54e37c0175b9736f4e99b6243af3b8970f34bef3
4
- data.tar.gz: 588075eda25ca77b7f5fcef1f7b6158e97274af5
3
+ metadata.gz: b8025722558e20114e202913981972cc89885a7f
4
+ data.tar.gz: 52975c30a2ac79ca4b459b2466de8c8b244f2296
5
5
  SHA512:
6
- metadata.gz: e2f1ef0a2dc869c89ff693c884fa23a61675d472cc3d3ef55f0d4f3d7faf880114584e0b9fde3207493502963a8d8b2cab4ef64aaca830dd1b580514c4bf1e6c
7
- data.tar.gz: 2e707b0b63b1dedbd9df0f93bd7cd9cfaf4614cf7c731c63d2f4cd5382ad1944baed2bbac6bf829d1362c61b8761a37e6299c35e42e80b0d903edc3ce2c582c6
6
+ metadata.gz: 14a7f27ec8de72d1ecafe5e27810ac781aedc23c5604e33005856398250e07617ed803dc6641ad17e45b7de8f8ae09c20fffd9997f64a34e1f4dc3f10beb63d6
7
+ data.tar.gz: 0fb4f2e1afb371a7ad49ad69e9ae22fe169856a7268daf4fc78054192bba532181b82e9290d5965d5cf608ea92d85b23bb4c71f976c814adac32e704e4311eeb
@@ -0,0 +1,52 @@
1
+ # Change log
2
+
3
+ ## master (unreleased)
4
+
5
+ ## 0.6.2 (2019-11-18)
6
+ ### Added
7
+ - add ```check-and-go``` command.
8
+
9
+ ### Changed
10
+ - Enhancement in input format parser.
11
+
12
+ ## 0.6.1 (2019-10-28)
13
+ ### Added
14
+ - Extract MOD values from problem description.
15
+
16
+ ### Changed
17
+ - Enhancement in MAX value parser.
18
+
19
+ ## 0.6.0 (2019-10-21)
20
+ ### Added
21
+ - Output problem url to generated sources.
22
+ - Interactive problem support.
23
+ - Binary problem support.
24
+ - Add settings about source generation.
25
+
26
+ ### Changed
27
+ - Treat all ```A_*.in``` format files as sample input data
28
+
29
+ ## 0.5.2 (2019-10-14)
30
+ ### Fixed
31
+ - Fix input format parser.
32
+
33
+ ## 0.5.1 (2019-10-14)
34
+ ### Added
35
+ - Colored test results.
36
+
37
+ ### Changed
38
+ - Enhancement in sample data parser.
39
+ - Enhancement in input format parser.
40
+
41
+ ## 0.5.0 (2019-10-04)
42
+ ### Added
43
+ - User/password setting in ```.at_coder_friends.yml``` is no longer required.
44
+ - Saving and restoring session feature.
45
+
46
+ ## 0.4.0 (2019-09-16)
47
+ ### Added
48
+ - Test and submission are now available in 36 languages.
49
+
50
+ ## 0.3.3 (2019-09-01)
51
+ ### Added
52
+ - Add ```open-contest-page``` command.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- at_coder_friends (0.6.1)
4
+ at_coder_friends (0.6.2)
5
5
  colorize (~> 0.8.1)
6
6
  launchy (~> 2.4.3)
7
7
  mechanize (~> 2.0)
@@ -36,7 +36,7 @@ GEM
36
36
  webrobots (>= 0.0.9, < 0.2)
37
37
  mime-types (3.3)
38
38
  mime-types-data (~> 3.2015)
39
- mime-types-data (3.2019.0904)
39
+ mime-types-data (3.2019.1009)
40
40
  mini_portile2 (2.4.0)
41
41
  net-http-digest_auth (1.4.1)
42
42
  net-http-persistent (3.1.0)
@@ -45,7 +45,7 @@ GEM
45
45
  mini_portile2 (~> 2.4.0)
46
46
  ntlm-http (0.1.1)
47
47
  public_suffix (4.0.1)
48
- rake (13.0.0)
48
+ rake (13.0.1)
49
49
  rspec (3.9.0)
50
50
  rspec-core (~> 3.9.0)
51
51
  rspec-expectations (~> 3.9.0)
data/README.md CHANGED
@@ -40,7 +40,7 @@ Or install it yourself as:
40
40
  ### Setup
41
41
 
42
42
  ```
43
- at_coder_friends setup /path/to/contest
43
+ at_coder_friends setup /path/to/contest
44
44
  ```
45
45
 
46
46
  Creates contest folder, and generates example data and source skeletons into the folder.
@@ -56,19 +56,25 @@ Opens the contet page which the contest folder linked to.
56
56
  ### Run first test case
57
57
 
58
58
  ```
59
- at_coder_friends test-one /path/to/contest/source_file
59
+ at_coder_friends test-one /path/to/contest/source_file
60
60
  ```
61
61
 
62
62
  ### Run all test cases
63
63
 
64
64
  ```
65
- at_coder_friends test-all /path/to/contest/source_file
65
+ at_coder_friends test-all /path/to/contest/source_file
66
66
  ```
67
67
 
68
68
  ### Submit code
69
69
 
70
70
  ```
71
- at_coder_friends submit /path/to/contest/source_file
71
+ at_coder_friends submit /path/to/contest/source_file
72
+ ```
73
+
74
+ ### Submit code automatically if all tests passed
75
+
76
+ ```
77
+ at_coder_friends check-and-go /path/to/contest/source_file
72
78
  ```
73
79
 
74
80
  ### Naming convention
@@ -80,14 +86,16 @@ at_coder_friends submit /path/to/contest/source_file
80
86
  - Suffixes (start with underscore) may be added to the file name (e.g. ```A_v2.rb```),
81
87
  so that you can try multiple codes for one problem.
82
88
 
83
- ## Configuration
84
-
85
- See [CONFIGURATION.md](docs/CONFIGURATION.md) for details.
86
-
87
89
  ## Notes
88
90
 
89
91
  - Compilation is not supported.
90
- - Source generator supports only Ruby and C++ in default.
92
+ - Source generator supports Ruby and C++ in default, and can be added by plugin.
93
+ - Test runner and code submission are supported in 36 languages.
94
+
95
+
96
+ ## Configuration
97
+
98
+ See [CONFIGURATION.md](docs/CONFIGURATION.md) for details.
91
99
 
92
100
  ## For Sublime Text user
93
101
 
@@ -150,6 +158,17 @@ It is convenient to use AtCoderFriends from Sublime Text plugin.
150
158
  "problemMatcher": [],
151
159
  "group": "none",
152
160
  },
161
+ {
162
+ "label": "AtCoderFriends:Check & Go",
163
+ "type": "shell",
164
+ "command": "at_coder_friends",
165
+ "args": [
166
+ "check-and-go",
167
+ "${file}"
168
+ ],
169
+ "problemMatcher": [],
170
+ "group": "none",
171
+ },
153
172
  ...
154
173
  ],
155
174
  "inputs": [
@@ -21,8 +21,10 @@ Gem::Specification.new do |spec|
21
21
  spec.homepage = 'https://github.com/nejiko96/at_coder_friends'
22
22
  spec.license = 'MIT'
23
23
 
24
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
25
- f.match(%r{^(test|spec|features)/})
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(test|spec|features)/})
27
+ end
26
28
  end
27
29
  spec.bindir = 'exe'
28
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -30,6 +32,12 @@ Gem::Specification.new do |spec|
30
32
 
31
33
  spec.required_ruby_version = '>= 2.3.0'
32
34
 
35
+ spec.metadata = {
36
+ 'homepage_uri' => spec.homepage,
37
+ 'source_code_uri' => spec.homepage,
38
+ 'changelog_uri' => 'https://github.com/nejiko96/at_coder_friends/blob/master/CHANGELOG.md'
39
+ }
40
+
33
41
  spec.add_dependency 'colorize', '~> 0.8.1'
34
42
  spec.add_dependency 'launchy', '~> 2.4.3'
35
43
  spec.add_dependency 'mechanize', '~> 2.0'
@@ -133,12 +133,12 @@ ext_settings:
133
133
  - generators
134
134
  List of source generator class names
135
135
  In default, ```RubyBuiltin``` and ```CxxBuiltin``` are available.
136
- For those other than above, corresponding plugin will be used
137
- if it has been installed (?).
138
- ソーススケルトンを生成するジェネレータのクラス名(リスト形式)
136
+ For other generators, corresponding plugin will be used
137
+ if it has been installed.
138
+ ソースジェネレータのクラス名(リスト形式)
139
139
  既定の状態では「RubyBuiltin」と「CxxBuiltin」が利用でき、
140
- 上記以外については、対応するプラグインがインストールされていれば
141
- それが使われます(?)
140
+ その他のジェネレータは、対応するプラグインがインストールされていれば
141
+ 利用できます
142
142
 
143
143
  For example, if ```RubyAlt``` is specified as generator name,
144
144
  following plugin will be used:
@@ -148,6 +148,8 @@ ext_settings:
148
148
  |Gem Name |```at_coder_friends-generator-ruby_alt``` |
149
149
  |Require Statement|```require 'at_coder_friends/generator/ruby_alt'```|
150
150
  |Main Class Name |```AtCoderFriends::Generator::RubyAlt``` |
151
+
152
+ [search generator in GitHub](https://github.com/search?q=at_coder_friends-generator)
151
153
 
152
154
  - generator_settings
153
155
  - _(generator name)_
@@ -14,6 +14,7 @@ module AtCoderFriends
14
14
  at_coder_friends test-one path/contest/src # run 1st test case
15
15
  at_coder_friends test-all path/contest/src # run all test cases
16
16
  at_coder_friends submit path/contest/src # submit source code
17
+ at_coder_friends check-and-go path/contest/src # submit source if all tests passed
17
18
  at_coder_friends open-contest path/contest/src # open contest page
18
19
  Options:
19
20
  TEXT
@@ -75,6 +76,8 @@ module AtCoderFriends
75
76
  test_all
76
77
  when 'submit'
77
78
  submit
79
+ when 'check-and-go'
80
+ check_and_go
78
81
  when 'judge-one'
79
82
  judge_one(*args)
80
83
  when 'judge-all'
@@ -116,6 +119,18 @@ module AtCoderFriends
116
119
  vf.unverify
117
120
  end
118
121
 
122
+ def check_and_go
123
+ vf = ctx.verifier
124
+ if ctx.sample_test_runner.test_all
125
+ # submit automatically
126
+ ctx.scraping_agent.submit
127
+ vf.unverify
128
+ else
129
+ # enable manual submit
130
+ vf.verify
131
+ end
132
+ end
133
+
119
134
  def judge_one(id = '')
120
135
  ctx.judge_test_runner.judge_one(id)
121
136
  end
@@ -43,7 +43,7 @@ module AtCoderFriends
43
43
  TEXT
44
44
  end
45
45
 
46
- # generates C++ source code from problem description
46
+ # generates C++ source from problem description
47
47
  class CxxBuiltin
48
48
  include CxxBuiltinConstants
49
49
 
@@ -174,7 +174,7 @@ module AtCoderFriends
174
174
  end
175
175
 
176
176
  def gen_arr_size(szs)
177
- szs.map { |sz| sz =~ /\D/ ? "#{sz.upcase}_MAX" : sz }
177
+ szs.map { |sz| sz.gsub(/([a-z][a-z0-9_]*)/i, '\1_MAX').upcase }
178
178
  end
179
179
 
180
180
  def gen_inputs(inpdefs = pbm.formats)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module AtCoderFriends
4
4
  module Generator
5
- # generates C++ source code from problem description
5
+ # generates Ruby source from problem description
6
6
  class RubyBuiltin
7
7
  ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
8
8
  TMPL_DIR = File.join(ACF_HOME, 'templates')
@@ -49,13 +49,13 @@ module AtCoderFriends
49
49
  # 2) {i, j}->{i,j} {N-1}->{N} shortest match
50
50
  s
51
51
  .tr('0-9A-Za-z', '0-9A-Za-z')
52
- .gsub(/[[:space:]]/) { |c| c.gsub(/[^\t\r\n]/, ' ') } # 1)
52
+ .gsub(/[[:space:]]/) { |c| c.gsub(/[^\t\n]/, ' ') } # 1)
53
53
  .gsub(%r{</?var>}i, "\t")
54
54
  .gsub(%r{<sup>([^<>]+)</sup>}i, '^\1')
55
55
  .gsub(%r{<sub>([^<>]+)</sub>}i, '_{\1}')
56
- .gsub('&amp;', '&')
57
56
  .gsub(/<("[^"]*"|'[^']*'|[^'"<>])*>/, '')
58
- .gsub(/(<|≦|≤|&lt;|&leq?;|\\lt|\\leq?q?)(\{\})?/i, '<')
57
+ .gsub('&amp;', '&')
58
+ .gsub(/(<|≦|≤|&lt;|&leq?;|\\lt|\\leq?q?)/i, '<')
59
59
  .gsub('\\ ', ' ')
60
60
  .gsub('\\,', ',')
61
61
  .gsub('\\|', '|')
@@ -65,6 +65,9 @@ module AtCoderFriends
65
65
  .gsub('\\rvert', '|')
66
66
  .gsub('\\mathit', '')
67
67
  .gsub('\\times', '*')
68
+ .gsub(/\\begin(\{[^{}]*\})*/, '')
69
+ .gsub(/\\end(\{[^{}]*\})*/, '')
70
+ .gsub(/\{\}/, ' ')
68
71
  .gsub('|', '')
69
72
  .gsub(/\{.*?\}/) { |w| w.delete(' ()').gsub(/{(.+)-1}\z/, '\1') } # 2)
70
73
  end
@@ -2,63 +2,126 @@
2
2
 
3
3
  module AtCoderFriends
4
4
  module Parser
5
+ InputFormatMatcher = Struct.new(:container, :item, :pat, :gen_names, :gen_pat2) do
6
+ attr_reader :names, :size
7
+
8
+ def match(str)
9
+ return false unless (m1 = pat.match(str))
10
+
11
+ @names = gen_names.call(m1)
12
+ @pat2 = gen_pat2&.call(names)
13
+ @size = m1.names.include?('sz') && m1['sz'] || ''
14
+ true
15
+ end
16
+
17
+ def match2(str)
18
+ return false unless @pat2
19
+ return true if /\A\.+\z/ =~ str
20
+ return false unless (m2 = @pat2.match(str))
21
+
22
+ m2.names.include?('sz') && @size = m2['sz']
23
+ true
24
+ end
25
+ end
26
+
5
27
  module InputFormatConstants
6
28
  SECTIONS = [
7
29
  Problem::SECTION_IN_FMT,
8
30
  Problem::SECTION_IO_FMT
9
31
  ].freeze
10
- PARSERS = [
11
- {
12
- container: :harray,
13
- item: :number,
14
- pat: /^(?<v>[a-z]+)[01](\s+\k<v>.)*(\s+\.+)?(\s+\k<v>.)+$/i,
15
- names: ->(m) { [m[:v]] },
16
- pat2: ->(_) { nil },
17
- size: ->(f) { [f[-1]] }
18
- },
19
- {
20
- container: :harray,
21
- item: :char,
22
- pat: /^(?<v>[a-z]+)[01](\k<v>.)*(\s*\.+\s*)?(\k<v>.)+$/i,
23
- names: ->(m) { [m[:v]] },
24
- pat2: ->(_) { nil },
25
- size: ->(f) { [f[-1]] }
26
- },
27
- {
28
- container: :matrix,
29
- item: :number,
30
- pat: /^(?<v>[a-z]+)[01][01](\s+\k<v>..)*(\s+\.+)?(\s+\k<v>..)+$/i,
31
- names: ->(m) { [m[:v]] },
32
- pat2: ->(v) { /(^#{v}..(\s+#{v}..)*(\s+\.+)?(\s+#{v}..)+|\.+)$/ },
33
- size: ->(f) { f[-2..-1].chars.to_a }
34
- },
35
- {
36
- container: :matrix,
37
- item: :char,
38
- pat: /^(?<v>[a-z]+)[01][01](\k<v>..)*(\s*\.+\s*)?(\k<v>..)+$/i,
39
- names: ->(m) { [m[:v]] },
40
- pat2: ->(v) { /(^#{v}..(#{v}..)*(\s*\.+\s*)?(#{v}..)+|\.+)$/ },
41
- size: ->(f) { f[-2..-1].chars.to_a }
42
- },
43
- {
44
- container: :varray,
45
- item: :number,
46
- pat: /^[a-z]+(?<i>[0-9])(\s+[a-z]+\k<i>)*$/i,
47
- names: ->(m) { m[0].split.map { |w| w[0..-2] } },
48
- pat2: lambda { |vs|
49
- pat = vs.map { |v| v + '.+' }.join('\s+')
50
- /^(#{pat}|\.+)$/
51
- },
52
- size: ->(f) { /(?<sz>\d+)$/ =~ f ? [sz] : [f[-1]] }
53
- },
54
- {
55
- container: :single,
56
- item: :number,
57
- pat: /^[a-z]+(\s+[a-z]+)*$/i,
58
- names: ->(m) { m[0].split },
59
- pat2: ->(_) { nil },
60
- size: ->(_) { [] }
61
- }
32
+ ITEM_PAT = /\{*[A-Za-z]+(?:_[A-Za-z]+)*\}*/.freeze
33
+ SZ = '(_(?<sz>\S+)|{(?<sz>\S+)})'
34
+ SINGLE_PAT = /([A-Za-z{][A-Za-z_0-9{}]*)/.freeze
35
+ MATCHERS = [
36
+ InputFormatMatcher.new(
37
+ :matrix, :number,
38
+ /
39
+ \A
40
+ (?<v>#{ITEM_PAT})[_{]\{*[01][,_]?[01]\}*
41
+ (\s+\k<v>[_{]\S+)*
42
+ (\s+\.+)?
43
+ (\s+\k<v>#{SZ})+
44
+ \z
45
+ /x,
46
+ ->(m) { [m[:v]] },
47
+ lambda { |((v))|
48
+ /
49
+ \A
50
+ #{v}[_{]\S+
51
+ (\s+#{v}#{SZ})*
52
+ (\s+\.+)?
53
+ (\s+#{v}#{SZ})*
54
+ \z
55
+ /x
56
+ }
57
+ ),
58
+ InputFormatMatcher.new(
59
+ :matrix, :char,
60
+ /
61
+ \A
62
+ (?<v>#{ITEM_PAT})[_{]\{*[01][,_]?[01]\}*
63
+ (\k<v>[_{]\S+)*
64
+ (\s*\.+\s*)?
65
+ (\k<v>#{SZ})+
66
+ \z
67
+ /x,
68
+ ->(m) { [m[:v]] },
69
+ lambda { |((v))|
70
+ /
71
+ \A
72
+ (#{v}[_{]\S+)+
73
+ (\s*\.+\s*)?
74
+ (#{v}#{SZ})+
75
+ \z
76
+ /x
77
+ }
78
+ ),
79
+ InputFormatMatcher.new(
80
+ :harray, :number,
81
+ /
82
+ \A
83
+ (?<v>#{ITEM_PAT})[_{]\{*[0-9]\}*
84
+ (\s+\k<v>[_{]\S+)*
85
+ (\s+\.+)?
86
+ (\s+\k<v>#{SZ})+
87
+ \z
88
+ /x,
89
+ ->(m) { [m[:v]] },
90
+ nil
91
+ ),
92
+ InputFormatMatcher.new(
93
+ :harray, :char,
94
+ /
95
+ \A
96
+ (?<v>#{ITEM_PAT})[_{]\{*[0-9]\}*
97
+ (\k<v>[_{]\S+)*
98
+ (\s*\.+\s*)?
99
+ (\k<v>#{SZ})+
100
+ \z
101
+ /x,
102
+ ->(m) { [m[:v]] },
103
+ nil
104
+ ),
105
+ InputFormatMatcher.new(
106
+ :varray, :number,
107
+ /
108
+ \A
109
+ #{ITEM_PAT}[_{]\{*(?<sz>[0-9]+)\}*
110
+ (\s+#{ITEM_PAT}[_{]\{*\k<sz>\}*)*
111
+ \z
112
+ /x,
113
+ ->(m) { m[0].split.map { |w| w.scan(ITEM_PAT)[0] } },
114
+ lambda { |vs|
115
+ pat2 = vs.map { |v| v + SZ }.join('\s+')
116
+ /\A#{pat2}\z/
117
+ }
118
+ ),
119
+ InputFormatMatcher.new(
120
+ :single, :number,
121
+ /\A(.*\s)?#{SINGLE_PAT}(\s.*)?\z/,
122
+ ->(m) { m[0].split.select { |w| w =~ /\A#{SINGLE_PAT}\z/ } },
123
+ nil
124
+ )
62
125
  ].freeze
63
126
  end
64
127
 
@@ -68,80 +131,135 @@ module AtCoderFriends
68
131
 
69
132
  module_function
70
133
 
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
88
-
89
134
  def process(pbm)
90
- str =
91
- SECTIONS
92
- .map { |key| pbm.sections[key]&.code_block }
93
- .find(&:itself) || ''
135
+ return unless (str = find_fmt(pbm))
136
+
94
137
  inpdefs = parse(str, pbm.samples)
95
138
  pbm.formats = inpdefs
96
139
  end
97
140
 
141
+ def find_fmt(pbm)
142
+ str = nil
143
+ SECTIONS.any? do |key|
144
+ str = pbm.sections[key]&.code_block_html
145
+ str && !str.empty?
146
+ end
147
+ str
148
+ end
149
+
98
150
  def parse(str, smps)
99
- lines = normalize(str)
151
+ lines = normalize_fmt(str)
100
152
  inpdefs = parse_fmt(lines)
153
+ normalize_defs!(inpdefs)
101
154
  smpx = max_smp(smps)
102
155
  smpx && match_smp!(inpdefs, smpx)
103
156
  inpdefs
104
157
  end
105
158
 
106
- def normalize(fmt)
159
+ def normalize_fmt(fmt)
160
+ # 1) &npsp; , fill-width space -> half width space
161
+ # 2) {i, j}->{i,j} for nested {}
107
162
  fmt
108
- .gsub(/[+*-]\d+/, '') # N-1, N+1 -> N
109
- .gsub(%r{[-/ ]}, ' ') # a-b, a/b -> a b
110
- .gsub(/\{.*?\}/) { |w| w.delete(' ') } # {1, 1}->{1,1} shortest match
111
- .gsub(/[_,'\\(){}|$]/, '')
112
- .gsub(/[・::…‥]+/, '..')
113
- .gsub(/[clv]?dots/, '..')
114
- .gsub(/^\s*\.[.\s]*$/, '..')
163
+ .tr('0-9A-Za-z', '0-9A-Za-z')
164
+ .gsub(/[[:space:]]/) { |c| c.gsub(/[^\n]/, ' ') } # 1)
165
+ .gsub(%r{<var>([^<>]+)</var>}i, '\1') # <sub><var>N</var></sub>
166
+ .gsub(%r{<sup>([^<>]+)</sup>}i, '^\1')
167
+ .gsub(%r{<sub>([^<>]+)</sub>}i, '_{\1}')
168
+ .gsub(%r{<sub>([^<>]+)</sub>}i, '_{\1}') # for nested<sub>
169
+ .gsub(/<("[^"]*"|'[^']*'|[^'"<>])*>/, '')
170
+ .gsub('&amp;', '&')
171
+ .gsub('&gt;', '>')
172
+ .gsub('&lt;', '<')
173
+ .gsub('\\ ', ' ')
174
+ .gsub('\\(', '')
175
+ .gsub('\\)', '')
176
+ .gsub('\\lvert', '|')
177
+ .gsub('\\rvert', '|')
178
+ .gsub('\\mathit', '')
179
+ .gsub('\\times', '*')
180
+ .gsub(/\\begin(\{[^{}]*\})*/, '')
181
+ .gsub(/\\end(\{[^{}]*\})*/, '')
182
+ .gsub(/\\[cdlv]?dots/, '..')
183
+ .gsub(/\{\}/, ' ')
184
+ .gsub('−', '-') # fill width hyphen
185
+ .gsub(/[・:‥⋮︙…]+/, '..')
186
+ .gsub(/[\\$']/, '') # s' -> s
187
+ .gsub(/[&~|]/, ' ') # |S| -> S
188
+ .gsub(%r{[-/:](#{SINGLE_PAT})}, ' \1') # a-b, a/b, a:b -> a b
189
+ .gsub(/^\s*[.:][\s.:]*$/, '..')
190
+ .tr('()', '{}')
191
+ .gsub(/(?<bl>\{(?:[^{}]|\g<bl>)*\})/) { |w| w.delete(' ') } # 2)
115
192
  .split("\n")
116
193
  .map(&:strip)
117
194
  end
118
195
 
119
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
120
196
  def parse_fmt(lines)
121
- it = Iterator.new(lines + ['']) # sentinel
122
- prv = nil
123
- cur = it.next
124
- Enumerator.new do |y|
125
- loop do
126
- unless (parser = PARSERS.find { |ps| ps[:pat] =~ cur })
127
- puts "unknown format: #{cur}" unless cur.empty?
128
- (cur = it.next) ? next : break
129
- end
130
- container, item = parser.values_at(:container, :item)
131
- m = parser[:pat].match(cur)
132
- names = parser[:names].call(m)
133
- pat2 = parser[:pat2].call(names)
134
- loop do
135
- prv = cur
136
- cur = it.next
137
- break unless pat2 && pat2 =~ cur
138
- end
139
- size = parser[:size].call(prv)
140
- y << Problem::InputFormat.new(container, item, names, size)
197
+ matcher = nil
198
+ (lines + ['']).each_with_object([]) do |line, ret|
199
+ if matcher
200
+ next if matcher.match2(line)
201
+
202
+ ret.last.size = matcher.size
141
203
  end
142
- end.to_a
204
+ if (matcher = MATCHERS.find { |m| m.match(line) })
205
+ ret << Problem::InputFormat.new(
206
+ matcher.container, matcher.item, matcher.names, ''
207
+ )
208
+ elsif !line.empty?
209
+ puts "unknown format: #{line}"
210
+ # ret << Problem::InputFormat.new(:unknown, nil, line)
211
+ end
212
+ end
213
+ end
214
+
215
+ def normalize_defs!(inpdefs)
216
+ inpdefs.each do |inpdef|
217
+ inpdef.names = normalize_names(inpdef.names)
218
+ inpdef.size = normalize_size(inpdef.container, inpdef.size)
219
+ end
220
+ end
221
+
222
+ def normalize_names(names)
223
+ return names unless names.is_a?(Array)
224
+
225
+ names.map { |nm| nm.delete('{}').gsub(/(\A_+|_+\z)/, '') }
226
+ end
227
+
228
+ def normalize_size(container, size)
229
+ sz =
230
+ case container
231
+ when :matrix
232
+ matrix_size(size)
233
+ when :harray, :varray
234
+ [size]
235
+ else
236
+ []
237
+ end
238
+ sz.map do |w|
239
+ w
240
+ .delete('{},')
241
+ .gsub(/(\A_+|(_|-1)+\z)/, '') # extra underscores, N-1 -> N
242
+ end
243
+ end
244
+
245
+ def matrix_size(str)
246
+ sz = str.scan(/([^{}]+|\{[^{}]+\}})/).flatten
247
+ return sz if sz.size == 2
248
+
249
+ sz = str.split(',')
250
+ return sz if sz.size == 2
251
+
252
+ sz = str.split('_')
253
+ return sz if sz.size == 2
254
+
255
+ str = str.delete('{},')
256
+ len = str.size
257
+ if len.positive? && len.even?
258
+ return str.chars.each_slice(len / 2).map(&:join)
259
+ end
260
+
261
+ [str[0] || '_', str[1..-1] || '_']
143
262
  end
144
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
145
263
 
146
264
  def max_smp(smps)
147
265
  smps
@@ -16,7 +16,9 @@ module AtCoderFriends
16
16
  elsif key =~ Problem::SECTION_OUT_SMP_PAT
17
17
  :exp
18
18
  end
19
- ext && pbm.add_smp($LAST_MATCH_INFO[:no], ext, section.code_block)
19
+ ext && pbm.add_smp(
20
+ $LAST_MATCH_INFO[:no], ext, section.code_block_content
21
+ )
20
22
  end
21
23
  end
22
24
  end
@@ -24,13 +24,13 @@ module AtCoderFriends
24
24
 
25
25
  def content
26
26
  @content ||= begin
27
- siblings.reduce('') { |m, node| m + node.content.gsub("\r\n", "\n") }
27
+ siblings.reduce('') { |m, node| m + node.content }.gsub("\r\n", "\n")
28
28
  end
29
29
  end
30
30
 
31
31
  def html
32
32
  @html ||= begin
33
- siblings.reduce('') { |m, node| m + node.to_html.gsub("\r\n", "\n") }
33
+ siblings.reduce('') { |m, node| m + node.to_html }.gsub("\r\n", "\n")
34
34
  end
35
35
  end
36
36
 
@@ -44,11 +44,17 @@ module AtCoderFriends
44
44
  elem
45
45
  end
46
46
 
47
- def code_block
48
- @code_block ||= begin
49
- elem = find_element(%w[pre blockquote])
50
- (elem&.content || '').lstrip.gsub("\r\n", "\n")
51
- end
47
+ def code_block_content
48
+ @code_block_content ||= code_block(:content)
49
+ end
50
+
51
+ def code_block_html
52
+ @code_block_html ||= code_block(:to_html)
53
+ end
54
+
55
+ def code_block(mtd)
56
+ elem = find_element(%w[pre blockquote])
57
+ elem && elem.send(mtd).lstrip.gsub("\r\n", "\n") || ''
52
58
  end
53
59
  end
54
60
  end
@@ -22,6 +22,10 @@ module AtCoderFriends
22
22
  def initialize(container, item, names, size = [])
23
23
  super(container, item, names, size)
24
24
  end
25
+
26
+ def to_s
27
+ "#{container} #{item} #{names} #{size}"
28
+ end
25
29
  end
26
30
 
27
31
  Constant = Struct.new(:name, :type, :value)
@@ -22,7 +22,6 @@ module AtCoderFriends
22
22
  begin
23
23
  return agent.get(url)
24
24
  rescue Mechanize::ResponseCodeError => e
25
- raise e unless e.response_code == '404'
26
25
  raise e if username_link(e.page)
27
26
  end
28
27
 
@@ -40,13 +39,13 @@ module AtCoderFriends
40
39
  def read_auth
41
40
  user = ctx.config['user'].to_s
42
41
  if user.empty?
43
- print('Enter username:')
42
+ print('enter username:')
44
43
  user = STDIN.gets.chomp
45
44
  end
46
45
 
47
46
  pass = ctx.config['password'].to_s
48
47
  if pass.empty?
49
- print("Enter password for #{user}:")
48
+ print("enter password for #{user}:")
50
49
  pass = STDIN.noecho(&:gets).chomp
51
50
  puts
52
51
  end
@@ -59,7 +58,7 @@ module AtCoderFriends
59
58
  @username = (link ? link.text.strip : '-')
60
59
  return if @username == username_bak || @username == '-'
61
60
 
62
- puts "Logged in as #{@username}"
61
+ puts "logged in as #{@username}"
63
62
  end
64
63
 
65
64
  def username_link(page)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AtCoderFriends
4
- VERSION = '0.6.1'
4
+ VERSION = '0.6.2'
5
5
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'regression'
4
+
5
+ module AtCoderFriends
6
+ # tasks for regression
7
+ module Regression
8
+ module_function
9
+
10
+ def check_fmt
11
+ File.open(log_path('check_fmt.txt'), 'w') do |f|
12
+ local_pbm_list.sort.each do |contest, q, url|
13
+ pbm = scraping_agent(nil, contest).fetch_problem(q, url)
14
+ Parser::Sections.process(pbm)
15
+ fmt = Parser::InputFormat.find_fmt(pbm)
16
+ next unless fmt && !fmt.empty?
17
+
18
+ n_fmt = Parser::InputFormat.normalize_fmt(fmt).join("\n")
19
+ Parser::InputFormat.process(pbm)
20
+ res = pbm.formats.map(&:to_s).join("\n")
21
+ f.puts [
22
+ contest, q,
23
+ tsv_escape(fmt),
24
+ tsv_escape(n_fmt),
25
+ tsv_escape(res)
26
+ ].join("\t")
27
+ end
28
+ end
29
+ end
30
+
31
+ def tsv_escape(str)
32
+ '"' + str.gsub('"', '""').gsub("\t", ' ') + '"'
33
+ end
34
+ end
35
+ end
36
+
37
+ namespace :regression do
38
+ desc 'checks input format patterns'
39
+ task :check_fmt do
40
+ AtCoderFriends::Regression.check_fmt
41
+ end
42
+ end
@@ -8,33 +8,43 @@ module AtCoderFriends
8
8
  module Regression
9
9
  module_function
10
10
 
11
- def check_parse(arg)
12
- arg ||= 'fmt,smp,int'
11
+ def check_parse
13
12
  list = local_pbm_list.map do |contest, q, url|
14
13
  pbm = scraping_agent(nil, contest).fetch_problem(q, url)
15
14
  Parser::Main.process(pbm)
16
- tbl = {
17
- 'fmt' => !fmt?(pbm),
18
- 'smp' => pbm.samples.all? { |smp| smp.txt.empty? },
19
- 'int' => pbm.options.interactive,
20
- 'bin' => pbm.options.binary_values
21
- }
22
- [contest, q, tbl.values_at(*arg.split(','))]
15
+ flags = [
16
+ !fmt?(pbm),
17
+ pbm.samples.all? { |smp| smp.txt.empty? },
18
+ pbm.options.interactive
19
+ ]
20
+ [contest, q, flags]
23
21
  end
24
- report(list)
22
+ report(list, 'check_parse.txt')
23
+ end
24
+
25
+ def check_bin
26
+ list = local_pbm_list.map do |contest, q, url|
27
+ pbm = scraping_agent(nil, contest).fetch_problem(q, url)
28
+ Parser::Main.process(pbm)
29
+ flags = [pbm.options.binary_values]
30
+ [contest, q, flags]
31
+ end
32
+ report(list, 'check_bin.txt')
25
33
  end
26
34
 
27
35
  def fmt?(pbm)
28
- [Problem::SECTION_IN_FMT, Problem::SECTION_IO_FMT]
29
- .any? { |key| pbm.sections[key]&.code_block&.size&.positive? }
36
+ fmt = Parser::InputFormat.find_fmt(pbm)
37
+ fmt && !fmt.empty?
30
38
  end
31
39
 
32
- def report(list)
33
- list
34
- .select { |_, _, flags| flags.any? }
35
- .map { |c, q, flags| [c, q, flags.map { |f| f_to_s(f) }] }
36
- .sort
37
- .each { |args| puts args.flatten.join("\t") }
40
+ def report(list, file)
41
+ File.open(log_path(file), 'w') do |f|
42
+ list
43
+ .select { |_, _, flags| flags.any? }
44
+ .map { |c, q, flags| [c, q, flags.map { |flg| f_to_s(flg) }] }
45
+ .sort
46
+ .each { |args| f.puts args.flatten.join("\t") }
47
+ end
38
48
  end
39
49
 
40
50
  def f_to_s(f)
@@ -49,8 +59,12 @@ end
49
59
 
50
60
  namespace :regression do
51
61
  desc 'checks page parse result'
52
- task :check_parse, ['flags'] do |_, args|
53
- flags = args[:flags]
54
- AtCoderFriends::Regression.check_parse flags
62
+ task :check_parse do
63
+ AtCoderFriends::Regression.check_parse
64
+ end
65
+
66
+ desc 'checks binary problem parse result'
67
+ task :check_bin do
68
+ AtCoderFriends::Regression.check_bin
55
69
  end
56
70
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: at_coder_friends
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - nejiko96
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-10-28 00:00:00.000000000 Z
11
+ date: 2019-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -140,6 +140,7 @@ files:
140
140
  - ".rubocop.yml"
141
141
  - ".rubocop_todo.yml"
142
142
  - ".travis.yml"
143
+ - CHANGELOG.md
143
144
  - CODE_OF_CONDUCT.md
144
145
  - Gemfile
145
146
  - Gemfile.lock
@@ -186,6 +187,7 @@ files:
186
187
  - lib/at_coder_friends/version.rb
187
188
  - tasks/regression/check_const.rake
188
189
  - tasks/regression/check_diff.rake
190
+ - tasks/regression/check_fmt.rake
189
191
  - tasks/regression/check_parse.rake
190
192
  - tasks/regression/regression.rb
191
193
  - tasks/regression/section_list.rake
@@ -197,7 +199,10 @@ files:
197
199
  homepage: https://github.com/nejiko96/at_coder_friends
198
200
  licenses:
199
201
  - MIT
200
- metadata: {}
202
+ metadata:
203
+ homepage_uri: https://github.com/nejiko96/at_coder_friends
204
+ source_code_uri: https://github.com/nejiko96/at_coder_friends
205
+ changelog_uri: https://github.com/nejiko96/at_coder_friends/blob/master/CHANGELOG.md
201
206
  post_install_message:
202
207
  rdoc_options: []
203
208
  require_paths: