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 +4 -4
- data/CHANGELOG.md +52 -0
- data/Gemfile.lock +3 -3
- data/README.md +28 -9
- data/at_coder_friends.gemspec +10 -2
- data/docs/CONFIGURATION.md +7 -5
- data/lib/at_coder_friends/cli.rb +15 -0
- data/lib/at_coder_friends/generator/cxx_builtin.rb +2 -2
- data/lib/at_coder_friends/generator/ruby_builtin.rb +1 -1
- data/lib/at_coder_friends/parser/constraints.rb +6 -3
- data/lib/at_coder_friends/parser/input_format.rb +224 -106
- data/lib/at_coder_friends/parser/sample_data.rb +3 -1
- data/lib/at_coder_friends/parser/section_wrapper.rb +13 -7
- data/lib/at_coder_friends/problem.rb +4 -0
- data/lib/at_coder_friends/scraping/authentication.rb +3 -4
- data/lib/at_coder_friends/version.rb +1 -1
- data/tasks/regression/check_fmt.rake +42 -0
- data/tasks/regression/check_parse.rake +35 -21
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8025722558e20114e202913981972cc89885a7f
|
4
|
+
data.tar.gz: 52975c30a2ac79ca4b459b2466de8c8b244f2296
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14a7f27ec8de72d1ecafe5e27810ac781aedc23c5604e33005856398250e07617ed803dc6641ad17e45b7de8f8ae09c20fffd9997f64a34e1f4dc3f10beb63d6
|
7
|
+
data.tar.gz: 0fb4f2e1afb371a7ad49ad69e9ae22fe169856a7268daf4fc78054192bba532181b82e9290d5965d5cf608ea92d85b23bb4c71f976c814adac32e704e4311eeb
|
data/CHANGELOG.md
ADDED
@@ -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.
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
at_coder_friends (0.6.
|
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.
|
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.
|
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
|
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
|
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
|
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
|
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
|
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": [
|
data/at_coder_friends.gemspec
CHANGED
@@ -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 =
|
25
|
-
|
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'
|
data/docs/CONFIGURATION.md
CHANGED
@@ -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
|
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)_
|
data/lib/at_coder_friends/cli.rb
CHANGED
@@ -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
|
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
|
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
|
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\
|
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('&', '&')
|
57
56
|
.gsub(/<("[^"]*"|'[^']*'|[^'"<>])*>/, '')
|
58
|
-
.gsub(
|
57
|
+
.gsub('&', '&')
|
58
|
+
.gsub(/(<|≦|≤|<|&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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
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 =
|
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
|
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
|
-
.
|
109
|
-
.gsub(
|
110
|
-
.gsub(
|
111
|
-
.gsub(
|
112
|
-
.gsub(
|
113
|
-
.gsub(
|
114
|
-
.gsub(
|
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('&', '&')
|
171
|
+
.gsub('>', '>')
|
172
|
+
.gsub('<', '<')
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
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(
|
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
|
48
|
-
@
|
49
|
-
|
50
|
-
|
51
|
-
|
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,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('
|
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("
|
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 "
|
61
|
+
puts "logged in as #{@username}"
|
63
62
|
end
|
64
63
|
|
65
64
|
def username_link(page)
|
@@ -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
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
29
|
-
|
36
|
+
fmt = Parser::InputFormat.find_fmt(pbm)
|
37
|
+
fmt && !fmt.empty?
|
30
38
|
end
|
31
39
|
|
32
|
-
def report(list)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
53
|
-
|
54
|
-
|
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.
|
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-
|
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:
|