at_coder_friends 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +6 -1
- data/.rubocop.yml +0 -3
- data/Gemfile.lock +5 -5
- data/README.md +33 -17
- data/at_coder_friends.gemspec +1 -1
- data/docs/CONFIGURATION.md +48 -17
- data/lib/at_coder_friends.rb +1 -0
- data/lib/at_coder_friends/cli.rb +35 -33
- data/lib/at_coder_friends/config_loader.rb +4 -4
- data/lib/at_coder_friends/context.rb +45 -0
- data/lib/at_coder_friends/emitter.rb +3 -3
- data/lib/at_coder_friends/judge_test_runner.rb +2 -2
- data/lib/at_coder_friends/path_util.rb +0 -2
- data/lib/at_coder_friends/sample_test_runner.rb +2 -2
- data/lib/at_coder_friends/scraping_agent.rb +103 -52
- data/lib/at_coder_friends/test_runner.rb +11 -7
- data/lib/at_coder_friends/verifier.rb +6 -5
- data/lib/at_coder_friends/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ea93eb5e8bef9fd743ff3f5767cc906264b878b
|
4
|
+
data.tar.gz: 6b41067f5105e20859e2a5e5c0d120ef52e29b9a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 963c99df359372cbc89cbe6efb4ccf8c3ffccfc50e68364dc62e7314574a1ad30cfa6e12086cb994a844f5ce71d93a1eb543fe76c50a8cf361b4f18cf7387669
|
7
|
+
data.tar.gz: 81468b9c45065d8c8de6ef3c6355e400ce56d784c0743ad2d44e50784c5ebc3529ab3d5f4808561feea5ec388d53697a5b5d34030360b85f8cd676627c4a7a3b
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
at_coder_friends (0.
|
4
|
+
at_coder_friends (0.5.0)
|
5
5
|
launchy (~> 2.4.3)
|
6
6
|
mechanize (~> 2.0)
|
7
7
|
|
@@ -43,7 +43,7 @@ GEM
|
|
43
43
|
mini_portile2 (~> 2.4.0)
|
44
44
|
ntlm-http (0.1.1)
|
45
45
|
public_suffix (4.0.1)
|
46
|
-
rake (
|
46
|
+
rake (13.0.0)
|
47
47
|
rspec (3.8.0)
|
48
48
|
rspec-core (~> 3.8.0)
|
49
49
|
rspec-expectations (~> 3.8.0)
|
@@ -58,7 +58,7 @@ GEM
|
|
58
58
|
rspec-support (~> 3.8.0)
|
59
59
|
rspec-support (3.8.0)
|
60
60
|
safe_yaml (1.0.5)
|
61
|
-
simplecov (0.17.
|
61
|
+
simplecov (0.17.1)
|
62
62
|
docile (~> 1.1)
|
63
63
|
json (>= 1.8, < 3)
|
64
64
|
simplecov-html (~> 0.10.0)
|
@@ -66,7 +66,7 @@ GEM
|
|
66
66
|
unf (0.1.4)
|
67
67
|
unf_ext
|
68
68
|
unf_ext (0.0.7.6)
|
69
|
-
webmock (3.7.
|
69
|
+
webmock (3.7.6)
|
70
70
|
addressable (>= 2.3.6)
|
71
71
|
crack (>= 0.3.2)
|
72
72
|
hashdiff (>= 0.4.0, < 2.0.0)
|
@@ -79,7 +79,7 @@ PLATFORMS
|
|
79
79
|
DEPENDENCIES
|
80
80
|
at_coder_friends!
|
81
81
|
bundler (~> 2.0)
|
82
|
-
rake (~>
|
82
|
+
rake (~> 13.0)
|
83
83
|
rspec (~> 3.0)
|
84
84
|
simplecov (~> 0.10)
|
85
85
|
webmock (~> 3.0)
|
data/README.md
CHANGED
@@ -35,17 +35,6 @@ Or install it yourself as:
|
|
35
35
|
|
36
36
|
$ gem install at_coder_friends
|
37
37
|
|
38
|
-
## Configuration
|
39
|
-
|
40
|
-
Create ```.at_coder_friends.yml``` and place it in the working directory (or parent of working directory).
|
41
|
-
|
42
|
-
```yaml
|
43
|
-
user: <user>
|
44
|
-
password: <password>
|
45
|
-
```
|
46
|
-
|
47
|
-
See [CONFIGURATION.md](docs/CONFIGURATION.md) for details.
|
48
|
-
|
49
38
|
## Usage
|
50
39
|
|
51
40
|
### Setup
|
@@ -91,7 +80,11 @@ at_coder_friends submit /path/to/contest/source_file
|
|
91
80
|
- Suffixes (start with underscore) may be added to the file name (e.g. ```A_v2.rb```),
|
92
81
|
so that you can try multiple codes for one problem.
|
93
82
|
|
94
|
-
|
83
|
+
## Configuration
|
84
|
+
|
85
|
+
See [CONFIGURATION.md](docs/CONFIGURATION.md) for details.
|
86
|
+
|
87
|
+
## Notes
|
95
88
|
|
96
89
|
- Compilation is not supported.
|
97
90
|
- Source generator supports only ruby and C++.
|
@@ -107,41 +100,64 @@ It is convenient to use AtCoderFriends from Sublime Text plugin.
|
|
107
100
|
- Run **Configure Tasks** from the global Terminal menu.
|
108
101
|
- Select the **Create tasks.json file from template** entry.
|
109
102
|
- Select **Others** from the list.
|
110
|
-
- Add following
|
103
|
+
- Add following settings to ```tasks.json```.
|
111
104
|
|
112
105
|
```JSON
|
113
106
|
{
|
114
107
|
"version": "2.0.0",
|
115
108
|
"tasks": [
|
116
109
|
{
|
117
|
-
"label": "
|
110
|
+
"label": "AtCoderFriends:New Contest",
|
111
|
+
"type": "shell",
|
112
|
+
"command": "at_coder_friends",
|
113
|
+
"args": [
|
114
|
+
"setup",
|
115
|
+
"${input:contestName}"
|
116
|
+
],
|
117
|
+
"problemMatcher": [],
|
118
|
+
"group": "none"
|
119
|
+
},
|
120
|
+
{
|
121
|
+
"label": "AtCoderFriends:Test One",
|
118
122
|
"type": "shell",
|
119
123
|
"command": "at_coder_friends",
|
120
124
|
"args": [
|
121
125
|
"test-one",
|
122
126
|
"${file}"
|
123
127
|
],
|
124
|
-
"
|
128
|
+
"problemMatcher": [],
|
129
|
+
"group": "none"
|
125
130
|
},
|
126
131
|
{
|
127
|
-
"label": "
|
132
|
+
"label": "AtCoderFriends:Test All",
|
128
133
|
"type": "shell",
|
129
134
|
"command": "at_coder_friends",
|
130
135
|
"args": [
|
131
136
|
"test-all",
|
132
137
|
"${file}"
|
133
138
|
],
|
139
|
+
"problemMatcher": [],
|
134
140
|
"group": "none"
|
135
141
|
},
|
136
142
|
{
|
137
|
-
"label": "
|
143
|
+
"label": "AtCoderFriends:Submit",
|
138
144
|
"type": "shell",
|
139
145
|
"command": "at_coder_friends",
|
140
146
|
"args": [
|
141
147
|
"submit",
|
142
148
|
"${file}"
|
143
149
|
],
|
150
|
+
"problemMatcher": [],
|
144
151
|
"group": "none",
|
152
|
+
},
|
153
|
+
...
|
154
|
+
],
|
155
|
+
"inputs": [
|
156
|
+
{
|
157
|
+
"id": "contestName",
|
158
|
+
"type": "promptString",
|
159
|
+
"default": "",
|
160
|
+
"description": "Contest Name"
|
145
161
|
}
|
146
162
|
]
|
147
163
|
}
|
data/at_coder_friends.gemspec
CHANGED
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency 'mechanize', '~> 2.0'
|
35
35
|
|
36
36
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
37
|
-
spec.add_development_dependency 'rake', '~>
|
37
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
38
38
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
39
39
|
spec.add_development_dependency 'simplecov', '~> 0.10'
|
40
40
|
spec.add_development_dependency 'webmock', '~> 3.0'
|
data/docs/CONFIGURATION.md
CHANGED
@@ -2,15 +2,23 @@
|
|
2
2
|
|
3
3
|
## Configuration file
|
4
4
|
|
5
|
+
You can specify AtCoderFriends settings
|
6
|
+
in the configuration file ```.at_coder_friends.yml```.
|
7
|
+
Place it in your working directory (or higher).
|
8
|
+
|
5
9
|
AtCoderFriends の動作に関する設定は
|
6
|
-
設定ファイル
|
10
|
+
設定ファイル ```.at_coder_friends.yml``` に記述します。
|
7
11
|
このファイルは作業ディレクトリ(またはその上位ディレクトリ)に配置します。
|
8
12
|
|
9
13
|
## Default settings
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
15
|
+
The default settings are as follows.
|
16
|
+
If you add new settings or change existing settings in ```.at_coder_friends.yml```,
|
17
|
+
Its contents are merged with the default settings.
|
18
|
+
|
19
|
+
デフォルトの設定は以下のようになっています。
|
20
|
+
```.at_coder_friends.yml```で新しい設定項目の追加や、既存の設定項目の変更をすると、
|
21
|
+
その内容がデフォルト設定にマージされます。
|
14
22
|
|
15
23
|
```YAML
|
16
24
|
user: ~
|
@@ -106,34 +114,49 @@ ext_settings:
|
|
106
114
|
## Configuration options
|
107
115
|
|
108
116
|
- user
|
109
|
-
AtCoder
|
117
|
+
AtCoder username
|
118
|
+
If omitted, you are prompted to enter it on the first run.
|
119
|
+
AtCoderのユーザ名
|
120
|
+
省略した場合、初回の実行時に入力を求められます
|
110
121
|
|
111
122
|
- password
|
112
|
-
AtCoder
|
123
|
+
AtCoder password
|
124
|
+
If omitted, you are prompted to enter it on the first run.
|
125
|
+
AtCoderのパスワード
|
126
|
+
省略した場合、初回の実行時に入力を求められます
|
113
127
|
|
114
128
|
- ext_settings
|
129
|
+
Extension specific settings
|
115
130
|
拡張子ごとの設定
|
116
131
|
|
117
132
|
- _'ext'_
|
133
|
+
Target extension name
|
118
134
|
対象となる拡張子
|
119
135
|
|
120
136
|
- submit_lang
|
137
|
+
Language ID of submission language
|
121
138
|
提出言語の言語ID
|
122
139
|
|
123
140
|
- test_cmd
|
141
|
+
Execution command for test
|
142
|
+
If not set, the test will run using AtCoder "Custom Test" page.
|
124
143
|
テストで使用する実行コマンドの設定
|
125
|
-
|
144
|
+
未設定の場合、テストはAtCoderの「コードテスト」ページを使用して行われます
|
126
145
|
|
127
146
|
- default
|
147
|
+
Default execution command
|
128
148
|
デフォルトの実行コマンド
|
129
149
|
|
130
150
|
- wndows
|
151
|
+
Execution command for Windows(optional)
|
131
152
|
Windows専用実行コマンド(あれば設定)
|
132
153
|
|
133
154
|
- macosx
|
155
|
+
Execution command for MacOS(optional)
|
134
156
|
MacOS専用実行コマンド(あれば設定)
|
135
157
|
|
136
158
|
- linux
|
159
|
+
Execution command for Linux(optional)
|
137
160
|
Linux専用実行コマンド(あれば設定)
|
138
161
|
|
139
162
|
### Language ID list (2019/09/16)
|
@@ -197,19 +220,24 @@ ext_settings:
|
|
197
220
|
|3525|COBOL - Fixed (OpenCOBOL 1.1.0)|
|
198
221
|
|3526|COBOL - Free (OpenCOBOL 1.1.0)|
|
199
222
|
|
200
|
-
### Variables
|
223
|
+
### Variables in test_cmd string
|
224
|
+
|
225
|
+
The following variables in test_cmd string
|
226
|
+
is substituted with the path information of the target file
|
227
|
+
during execution.
|
201
228
|
|
202
|
-
test_cmd
|
203
|
-
|
229
|
+
test_cmd 文字列中の以下の変数には、
|
230
|
+
実行時に対象ファイルのパス情報が展開されます。
|
204
231
|
|
205
|
-
|
232
|
+
| Variable | Descripton |
|
206
233
|
|---|---|
|
207
|
-
|{dir}
|
208
|
-
|{base}
|
234
|
+
|{dir}|the directory name of the file<br>ファイルの置かれているディレクトリ名|
|
235
|
+
|{base}|the file name excluding the extension<br>拡張子を除いたファイル名|
|
209
236
|
|
210
237
|
## Examples of overwriting settings
|
211
238
|
|
212
|
-
- .py
|
239
|
+
- Test .py with local Python
|
240
|
+
.pyをローカル環境のPythonでテスト
|
213
241
|
```YAML
|
214
242
|
ext_settings:
|
215
243
|
'py':
|
@@ -217,21 +245,24 @@ test_cmd の文字列中の以下のパターンは、
|
|
217
245
|
default: 'python "{dir}/{base}.py"'
|
218
246
|
```
|
219
247
|
|
220
|
-
- .rb
|
248
|
+
- Test .rb with "Custom Test" page
|
249
|
+
.rb を「コードテスト」ページでテスト
|
221
250
|
```YAML
|
222
251
|
ext_settings:
|
223
252
|
'rb':
|
224
253
|
test_cmd: ~
|
225
254
|
```
|
226
255
|
|
227
|
-
- .pl6(Perl6)
|
256
|
+
- Add .pl6(Perl6) settings
|
257
|
+
.pl6(Perl6)の設定を追加
|
228
258
|
```YAML
|
229
259
|
ext_settings:
|
230
260
|
'pl6':
|
231
261
|
submit_lang: '3522' # Perl6
|
232
262
|
```
|
233
263
|
|
234
|
-
- .cxx
|
264
|
+
- Change submission language of .cxx to C++14(Clang)
|
265
|
+
.cxxの提出言語をC++14(Clang)に変更
|
235
266
|
```YAML
|
236
267
|
ext_settings:
|
237
268
|
'cxx':
|
data/lib/at_coder_friends.rb
CHANGED
data/lib/at_coder_friends/cli.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'optparse'
|
4
|
+
require 'launchy'
|
4
5
|
|
5
6
|
module AtCoderFriends
|
6
7
|
# command line interface
|
7
8
|
class CLI
|
8
|
-
include PathUtil
|
9
|
-
|
10
9
|
EXITING_OPTIONS = %i[version].freeze
|
11
|
-
OPTION_BANNER
|
10
|
+
OPTION_BANNER =
|
12
11
|
<<~TEXT
|
13
12
|
Usage:
|
14
13
|
at_coder_friends setup path/contest # setup contest folder
|
@@ -21,12 +20,13 @@ module AtCoderFriends
|
|
21
20
|
STATUS_SUCCESS = 0
|
22
21
|
STATUS_ERROR = 1
|
23
22
|
|
23
|
+
attr_reader :ctx
|
24
|
+
|
24
25
|
def run(args = ARGV)
|
25
26
|
parse_options!(args)
|
26
27
|
handle_exiting_option
|
27
28
|
raise ParamError, 'command or path is not specified.' if args.size < 2
|
28
29
|
|
29
|
-
@config = ConfigLoader.load_config(args[1])
|
30
30
|
exec_command(*args)
|
31
31
|
STATUS_SUCCESS
|
32
32
|
rescue AtCoderFriends::ParamError => e
|
@@ -46,6 +46,9 @@ module AtCoderFriends
|
|
46
46
|
opts.on('-v', '--version', 'Display version.') do
|
47
47
|
@options[:version] = true
|
48
48
|
end
|
49
|
+
opts.on('-d', '--debug', 'Display debug info.') do
|
50
|
+
@options[:debug] = true
|
51
|
+
end
|
49
52
|
end
|
50
53
|
@usage = op.to_s
|
51
54
|
@options = {}
|
@@ -61,73 +64,72 @@ module AtCoderFriends
|
|
61
64
|
exit STATUS_SUCCESS
|
62
65
|
end
|
63
66
|
|
64
|
-
def exec_command(command, path,
|
67
|
+
def exec_command(command, path, *args)
|
68
|
+
@ctx = Context.new(@options, path)
|
65
69
|
case command
|
66
70
|
when 'setup'
|
67
|
-
setup
|
71
|
+
setup
|
68
72
|
when 'test-one'
|
69
|
-
test_one(
|
73
|
+
test_one(*args)
|
70
74
|
when 'test-all'
|
71
|
-
test_all
|
75
|
+
test_all
|
72
76
|
when 'submit'
|
73
|
-
submit
|
77
|
+
submit
|
74
78
|
when 'judge-one'
|
75
|
-
judge_one(
|
79
|
+
judge_one(*args)
|
76
80
|
when 'judge-all'
|
77
|
-
judge_all
|
81
|
+
judge_all
|
78
82
|
when 'open-contest'
|
79
|
-
open_contest
|
83
|
+
open_contest
|
80
84
|
else
|
81
85
|
raise ParamError, "unknown command: #{command}"
|
82
86
|
end
|
87
|
+
ctx.post_process
|
83
88
|
end
|
84
89
|
|
85
|
-
def setup
|
90
|
+
def setup
|
91
|
+
path = ctx.path
|
86
92
|
raise AppError, "#{path} is not empty." \
|
87
93
|
if Dir.exist?(path) && !Dir["#{path}/*"].empty?
|
88
94
|
|
89
|
-
agent = ScrapingAgent.new(contest_name(path), @config)
|
90
95
|
parser = FormatParser.new
|
91
96
|
rb_gen = RubyGenerator.new
|
92
97
|
cxx_gen = CxxGenerator.new
|
93
|
-
|
94
|
-
agent.fetch_all do |pbm|
|
98
|
+
ctx.scraping_agent.fetch_all do |pbm|
|
95
99
|
parser.process(pbm)
|
96
100
|
rb_gen.process(pbm)
|
97
101
|
cxx_gen.process(pbm)
|
98
|
-
emitter.emit(pbm)
|
102
|
+
ctx.emitter.emit(pbm)
|
99
103
|
end
|
100
104
|
end
|
101
105
|
|
102
|
-
def test_one(
|
103
|
-
id
|
104
|
-
SampleTestRunner.new(path, @config).test_one(id)
|
106
|
+
def test_one(id = 1)
|
107
|
+
ctx.sample_test_runner.test_one(id)
|
105
108
|
end
|
106
109
|
|
107
|
-
def test_all
|
108
|
-
|
109
|
-
|
110
|
+
def test_all
|
111
|
+
ctx.sample_test_runner.test_all
|
112
|
+
ctx.verifier.verify
|
110
113
|
end
|
111
114
|
|
112
|
-
def submit
|
113
|
-
vf =
|
115
|
+
def submit
|
116
|
+
vf = ctx.verifier
|
114
117
|
raise AppError, "#{vf.file} has not been tested." unless vf.verified?
|
115
118
|
|
116
|
-
|
119
|
+
ctx.scraping_agent.submit
|
117
120
|
vf.unverify
|
118
121
|
end
|
119
122
|
|
120
|
-
def judge_one(
|
121
|
-
id
|
122
|
-
JudgeTestRunner.new(path, @config).judge_one(id)
|
123
|
+
def judge_one(id = '')
|
124
|
+
ctx.judge_test_runner.judge_one(id)
|
123
125
|
end
|
124
126
|
|
125
|
-
def judge_all
|
126
|
-
|
127
|
+
def judge_all
|
128
|
+
ctx.judge_test_runner.judge_all
|
127
129
|
end
|
128
130
|
|
129
|
-
def open_contest
|
130
|
-
|
131
|
+
def open_contest
|
132
|
+
Launchy.open(ctx.scraping_agent.contest_url)
|
131
133
|
end
|
132
134
|
end
|
133
135
|
end
|
@@ -7,12 +7,12 @@ module AtCoderFriends
|
|
7
7
|
# loads configuration file from the specified directory.
|
8
8
|
class ConfigLoader
|
9
9
|
DOTFILE = '.at_coder_friends.yml'
|
10
|
-
ACF_HOME = File.realpath(File.join(
|
10
|
+
ACF_HOME = File.realpath(File.join(__dir__, '..', '..'))
|
11
11
|
DEFAULT_FILE = File.join(ACF_HOME, 'config', 'default.yml')
|
12
12
|
|
13
13
|
class << self
|
14
|
-
def load_config(
|
15
|
-
path = config_file_for(
|
14
|
+
def load_config(ctx)
|
15
|
+
path = config_file_for(ctx.path)
|
16
16
|
config = load_yaml(path)
|
17
17
|
return config if path == DEFAULT_FILE
|
18
18
|
|
@@ -43,7 +43,7 @@ module AtCoderFriends
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def merge(base_hash, derived_hash)
|
46
|
-
res = base_hash.merge(derived_hash) do |_, base_val, derived_val|
|
46
|
+
res = base_hash.merge(derived_hash || {}) do |_, base_val, derived_val|
|
47
47
|
if base_val.is_a?(Hash) && derived_val.is_a?(Hash)
|
48
48
|
merge(base_val, derived_val)
|
49
49
|
else
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
# Holds applicaion global information
|
5
|
+
# - command line options
|
6
|
+
# - target path
|
7
|
+
# - configuration
|
8
|
+
# - application modules
|
9
|
+
class Context
|
10
|
+
attr_reader :options, :path
|
11
|
+
|
12
|
+
def initialize(options, path)
|
13
|
+
@options = options
|
14
|
+
@path = File.expand_path(path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def config
|
18
|
+
@config ||= ConfigLoader.load_config(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def scraping_agent
|
22
|
+
@scraping_agent ||= ScrapingAgent.new(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def sample_test_runner
|
26
|
+
@sample_test_runner ||= SampleTestRunner.new(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def judge_test_runner
|
30
|
+
@judge_test_runner ||= JudgeTestRunner.new(self)
|
31
|
+
end
|
32
|
+
|
33
|
+
def verifier
|
34
|
+
@verifier ||= Verifier.new(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
def emitter
|
38
|
+
@emitter ||= Emitter.new(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
def post_process
|
42
|
+
@scraping_agent&.save_session
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -9,13 +9,11 @@ module AtCoderFriends
|
|
9
9
|
CASES_DIR = 'cases'
|
10
10
|
|
11
11
|
def contest_name(path)
|
12
|
-
path = File.expand_path(path)
|
13
12
|
dir = File.file?(path) ? File.dirname(path) : path
|
14
13
|
File.basename(dir).delete('#').downcase
|
15
14
|
end
|
16
15
|
|
17
16
|
def split_prg_path(path)
|
18
|
-
path = File.expand_path(path)
|
19
17
|
dir, prg = File.split(path)
|
20
18
|
base, ext = prg.split('.')
|
21
19
|
q = base.split('_')[0]
|
@@ -1,39 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'uri'
|
4
|
+
require 'cgi'
|
5
|
+
require 'json'
|
4
6
|
require 'mechanize'
|
5
7
|
require 'logger'
|
6
8
|
require 'English'
|
7
|
-
require '
|
8
|
-
require 'json'
|
9
|
+
require 'io/console'
|
9
10
|
|
10
11
|
module AtCoderFriends
|
11
12
|
# scrapes AtCoder contest site and
|
12
13
|
# - fetches problems
|
13
14
|
# - submits sources
|
15
|
+
# - runs tests on custom_test page
|
14
16
|
class ScrapingAgent
|
15
17
|
include PathUtil
|
16
|
-
BASE_URL
|
18
|
+
BASE_URL = 'https://atcoder.jp/'
|
17
19
|
XPATH_SECTION = '//h3[.="%<title>s"]/following-sibling::section'
|
20
|
+
XPATH_USERNAME = '//*[@id="navbar-collapse"]/ul[2]/li[2]/a'
|
21
|
+
SESSION_STORE =
|
22
|
+
File.join(Dir.home, '.at_coder_friends', '%<user>s_session.yml')
|
18
23
|
|
19
|
-
attr_reader :
|
24
|
+
attr_reader :ctx, :agent
|
20
25
|
|
21
|
-
def initialize(
|
22
|
-
@
|
23
|
-
@config = config
|
26
|
+
def initialize(ctx)
|
27
|
+
@ctx = ctx
|
24
28
|
@agent = Mechanize.new
|
25
|
-
|
26
|
-
|
29
|
+
agent.pre_connect_hooks << proc { sleep 0.1 }
|
30
|
+
agent.log = Logger.new(STDERR) if ctx.options[:debug]
|
31
|
+
agent.cookie_jar.load(session_store) if File.exist?(session_store)
|
32
|
+
end
|
33
|
+
|
34
|
+
def save_session
|
35
|
+
dir = File.dirname(session_store)
|
36
|
+
Dir.mkdir(dir) unless Dir.exist?(dir)
|
37
|
+
agent.cookie_jar.save_as(session_store)
|
38
|
+
end
|
39
|
+
|
40
|
+
def contest
|
41
|
+
@contest ||= contest_name(ctx.path)
|
42
|
+
end
|
43
|
+
|
44
|
+
def config
|
45
|
+
ctx.config
|
27
46
|
end
|
28
47
|
|
29
48
|
def common_url(path)
|
30
49
|
File.join(BASE_URL, path)
|
31
50
|
end
|
32
51
|
|
33
|
-
def contest_url(path)
|
52
|
+
def contest_url(path = '')
|
34
53
|
File.join(BASE_URL, 'contests', contest, path)
|
35
54
|
end
|
36
55
|
|
56
|
+
def session_store
|
57
|
+
@session_store ||= format(SESSION_STORE, user: config['user'])
|
58
|
+
end
|
59
|
+
|
37
60
|
def constraints_pat
|
38
61
|
config['constraints_pat'] || '^制約$'
|
39
62
|
end
|
@@ -50,47 +73,71 @@ module AtCoderFriends
|
|
50
73
|
config['output_smp_pat'] || '^出力例\s*(?<no>[\d0-9]+)$'
|
51
74
|
end
|
52
75
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
76
|
+
def fetch_with_auth(url)
|
77
|
+
begin
|
78
|
+
page = agent.get(url)
|
79
|
+
rescue Mechanize::ResponseCodeError => e
|
80
|
+
raise e unless e.response_code == '404'
|
81
|
+
raise e if username_link(e.page)
|
82
|
+
|
83
|
+
page = agent.get(common_url('login') + '?continue=' + CGI.escape(url))
|
84
|
+
end
|
85
|
+
|
86
|
+
if page.uri.path == '/login'
|
87
|
+
user, pass = read_auth
|
88
|
+
form = page.forms[1]
|
89
|
+
form.field_with(name: 'username').value = user
|
90
|
+
form.field_with(name: 'password').value = pass
|
91
|
+
page = form.submit
|
60
92
|
end
|
93
|
+
|
94
|
+
page.uri.path == '/login' && (raise AppError, 'Authentication failed.')
|
95
|
+
show_username(page)
|
96
|
+
page
|
61
97
|
end
|
62
98
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
99
|
+
def read_auth
|
100
|
+
user = config['user'].to_s
|
101
|
+
if user.empty?
|
102
|
+
print('Enter username:')
|
103
|
+
user = STDIN.gets.chomp
|
104
|
+
end
|
105
|
+
pass = config['password'].to_s
|
106
|
+
if pass.empty?
|
107
|
+
print("Enter password for #{user}:")
|
108
|
+
pass = STDIN.noecho(&:gets).chomp
|
109
|
+
puts
|
110
|
+
end
|
111
|
+
[user, pass]
|
69
112
|
end
|
70
113
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
114
|
+
def show_username(page)
|
115
|
+
username_old = @username
|
116
|
+
link = username_link(page)
|
117
|
+
@username = (link ? link.text.strip : '-')
|
118
|
+
return if @username == username_old || @username == '-'
|
119
|
+
|
120
|
+
puts "Logged in as #{@username}"
|
77
121
|
end
|
78
122
|
|
79
|
-
def
|
80
|
-
|
81
|
-
|
123
|
+
def username_link(page)
|
124
|
+
link = page.search(XPATH_USERNAME)[0]
|
125
|
+
link && link[:href] == '#' && link
|
126
|
+
end
|
82
127
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
128
|
+
def fetch_all
|
129
|
+
puts "***** fetch_all #{contest} *****"
|
130
|
+
fetch_assignments.map do |q, url|
|
131
|
+
pbm = fetch_problem(q, url)
|
132
|
+
yield pbm if block_given?
|
133
|
+
pbm
|
134
|
+
end
|
88
135
|
end
|
89
136
|
|
90
137
|
def fetch_assignments
|
91
138
|
url = contest_url('tasks')
|
92
139
|
puts "fetch list from #{url} ..."
|
93
|
-
page =
|
140
|
+
page = fetch_with_auth(url)
|
94
141
|
page
|
95
142
|
.search('//table[1]//td[1]//a')
|
96
143
|
.each_with_object({}) do |a, h|
|
@@ -100,7 +147,7 @@ module AtCoderFriends
|
|
100
147
|
|
101
148
|
def fetch_problem(q, url)
|
102
149
|
puts "fetch problem from #{url} ..."
|
103
|
-
page =
|
150
|
+
page = fetch_with_auth(url)
|
104
151
|
Problem.new(q) do |pbm|
|
105
152
|
pbm.html = page.body
|
106
153
|
if contest == 'arc001'
|
@@ -136,8 +183,12 @@ module AtCoderFriends
|
|
136
183
|
end
|
137
184
|
end
|
138
185
|
|
139
|
-
def
|
140
|
-
|
186
|
+
def submit
|
187
|
+
path, _dir, prg, _base, ext, q = split_prg_path(ctx.path)
|
188
|
+
puts "***** submit #{prg} *****"
|
189
|
+
src = File.read(path, encoding: Encoding::UTF_8)
|
190
|
+
|
191
|
+
page = fetch_with_auth(contest_url('submit'))
|
141
192
|
form = page.forms[1]
|
142
193
|
form.field_with(name: 'data.TaskScreenName') do |sel|
|
143
194
|
option = sel.options.find { |op| op.text.start_with?(q) }
|
@@ -148,18 +199,22 @@ module AtCoderFriends
|
|
148
199
|
form.submit
|
149
200
|
end
|
150
201
|
|
151
|
-
def
|
152
|
-
|
202
|
+
def code_test(infile)
|
203
|
+
path, _dir, _prg, _base, ext, _q = split_prg_path(ctx.path)
|
204
|
+
src = File.read(path, encoding: Encoding::UTF_8)
|
205
|
+
data = File.read(infile)
|
206
|
+
|
207
|
+
page = fetch_with_auth(contest_url('custom_test'))
|
153
208
|
script = page.search('script').text
|
154
209
|
csrf_token = script.scan(/var csrfToken = "(.*)"/)[0][0]
|
155
|
-
|
210
|
+
|
211
|
+
page = agent.post(
|
212
|
+
contest_url('custom_test/submit/json'),
|
156
213
|
'data.LanguageId' => lang_id(ext),
|
157
214
|
'sourceCode' => src,
|
158
215
|
'input' => data,
|
159
216
|
'csrf_token' => csrf_token
|
160
|
-
|
161
|
-
|
162
|
-
page = agent.post(contest_url('custom_test/submit/json'), payload)
|
217
|
+
)
|
163
218
|
msg = page.body
|
164
219
|
raise AppError, msg unless msg.empty?
|
165
220
|
|
@@ -178,7 +233,7 @@ module AtCoderFriends
|
|
178
233
|
|
179
234
|
def lang_list
|
180
235
|
@lang_list ||= begin
|
181
|
-
page =
|
236
|
+
page = fetch_with_auth(contest_url('custom_test'))
|
182
237
|
form = page.forms[1]
|
183
238
|
sel = form.field_with(name: 'data.LanguageId')
|
184
239
|
sel && sel
|
@@ -206,9 +261,5 @@ module AtCoderFriends
|
|
206
261
|
raise AppError, msg
|
207
262
|
)
|
208
263
|
end
|
209
|
-
|
210
|
-
def open_contest
|
211
|
-
Launchy.open(contest_url(''))
|
212
|
-
end
|
213
264
|
end
|
214
265
|
end
|
@@ -7,10 +7,15 @@ module AtCoderFriends
|
|
7
7
|
class TestRunner
|
8
8
|
include PathUtil
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@
|
10
|
+
attr_reader :ctx
|
11
|
+
|
12
|
+
def initialize(ctx)
|
13
|
+
@ctx = ctx
|
14
|
+
@path, @dir, @prg, @base, @ext, @q = split_prg_path(ctx.path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def config
|
18
|
+
ctx.config
|
14
19
|
end
|
15
20
|
|
16
21
|
# rubocop:disable Metrics/MethodLength
|
@@ -53,8 +58,7 @@ module AtCoderFriends
|
|
53
58
|
end
|
54
59
|
|
55
60
|
def remote_test(infile, outfile)
|
56
|
-
|
57
|
-
res = agent.code_test(@path, infile)
|
61
|
+
res = ctx.scraping_agent.code_test(infile)
|
58
62
|
unless res && res['Result']
|
59
63
|
File.write(outfile, 'Remote test failed.')
|
60
64
|
return false
|
@@ -72,7 +76,7 @@ module AtCoderFriends
|
|
72
76
|
|
73
77
|
def test_cmd
|
74
78
|
@test_cmd ||= begin
|
75
|
-
cmds =
|
79
|
+
cmds = config.dig('ext_settings', @ext, 'test_cmd')
|
76
80
|
cmd = cmds && (cmds[which_os.to_s] || cmds['default'])
|
77
81
|
return nil unless cmd
|
78
82
|
|
@@ -6,11 +6,12 @@ module AtCoderFriends
|
|
6
6
|
# marks and checks if the source has been verified.
|
7
7
|
class Verifier
|
8
8
|
attr_reader :path, :file, :vdir, :vpath
|
9
|
-
|
10
|
-
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
9
|
+
|
10
|
+
def initialize(ctx)
|
11
|
+
@path = ctx.path
|
12
|
+
@file = File.basename(path)
|
13
|
+
@vdir = File.join(File.dirname(path), '.tmp')
|
14
|
+
@vpath = File.join(vdir, "#{file}.verified")
|
14
15
|
end
|
15
16
|
|
16
17
|
def verify
|
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.
|
4
|
+
version: 0.5.0
|
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-10-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: launchy
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '13.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '13.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -141,6 +141,7 @@ files:
|
|
141
141
|
- lib/at_coder_friends.rb
|
142
142
|
- lib/at_coder_friends/cli.rb
|
143
143
|
- lib/at_coder_friends/config_loader.rb
|
144
|
+
- lib/at_coder_friends/context.rb
|
144
145
|
- lib/at_coder_friends/cxx_generator.rb
|
145
146
|
- lib/at_coder_friends/emitter.rb
|
146
147
|
- lib/at_coder_friends/errors.rb
|