at_coder_friends 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|