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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f97a6bf516dc7443c791b4e2ace991b34eaa47d5
4
- data.tar.gz: d48834719bdceceb66cd4f1476c54e7b1c5602af
3
+ metadata.gz: 7ea93eb5e8bef9fd743ff3f5767cc906264b878b
4
+ data.tar.gz: 6b41067f5105e20859e2a5e5c0d120ef52e29b9a
5
5
  SHA512:
6
- metadata.gz: b4ef816ece17840560b7b9bd39ac4ef56ae346fd859a3b1d737dd9a759faecf77a80244fdd56c97a7aa4542f3bdb65e4346f10239c1cd63e7163038cebc95271
7
- data.tar.gz: 044e53c76ac9789c2a049a3476141e0d81d103b0d66599bb18e13c0c0b66ec64c65b7fb6062e7ec761d6ba0c536f00d234aa060514eb833e69fc6b922692401e
6
+ metadata.gz: 963c99df359372cbc89cbe6efb4ccf8c3ffccfc50e68364dc62e7314574a1ad30cfa6e12086cb994a844f5ce71d93a1eb543fe76c50a8cf361b4f18cf7387669
7
+ data.tar.gz: 81468b9c45065d8c8de6ef3c6355e400ce56d784c0743ad2d44e50784c5ebc3529ab3d5f4808561feea5ec388d53697a5b5d34030360b85f8cd676627c4a7a3b
data/.gitignore CHANGED
@@ -7,7 +7,12 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  /vendor/
10
- /.at_coder_friends.yml
11
10
 
12
11
  # rspec failure tracking
13
12
  .rspec_status
13
+
14
+ # at_coder_friends
15
+ .at_coder_friends.yml
16
+
17
+ # rvm
18
+ .ruby-version
data/.rubocop.yml CHANGED
@@ -5,9 +5,6 @@ AllCops:
5
5
  - 'spec/fixtures/**/*'
6
6
  TargetRubyVersion: 2.3
7
7
 
8
- Layout/EndOfLine:
9
- EnforcedStyle: lf
10
-
11
8
  Lint/AmbiguousBlockAssociation:
12
9
  Exclude:
13
10
  - 'spec/**/*.rb'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- at_coder_friends (0.4.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 (12.3.3)
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.0)
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.3)
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 (~> 12.3)
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
- ### Notes
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 tasks to ```tasks.json```.
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": "af test-one",
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
- "group": "none",
128
+ "problemMatcher": [],
129
+ "group": "none"
125
130
  },
126
131
  {
127
- "label": "af test-all",
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": "af submit",
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
  }
@@ -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', '~> 12.3'
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'
@@ -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
- 設定ファイル 「.at_coder_friends.yml に記述します。
10
+ 設定ファイル ```.at_coder_friends.yml``` に記述します。
7
11
  このファイルは作業ディレクトリ(またはその上位ディレクトリ)に配置します。
8
12
 
9
13
  ## Default settings
10
14
 
11
- デフォルトは以下のような設定になっています。
12
- 「.at_coder_friends.yml」が見つかると、その内容がデフォルト設定にマージされるので、
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のログインユーザーID
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
- 未設定の場合はAtCoderの「コードテスト」ページでテストをします
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 available in test_cmd
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をローカル環境のPythonでテスト
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の提出言語をC++14(Clang)に変更
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':
@@ -14,4 +14,5 @@ require 'at_coder_friends/format_parser'
14
14
  require 'at_coder_friends/ruby_generator'
15
15
  require 'at_coder_friends/cxx_generator'
16
16
  require 'at_coder_friends/emitter'
17
+ require 'at_coder_friends/context'
17
18
  require 'at_coder_friends/cli'
@@ -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, id = nil)
67
+ def exec_command(command, path, *args)
68
+ @ctx = Context.new(@options, path)
65
69
  case command
66
70
  when 'setup'
67
- setup(path)
71
+ setup
68
72
  when 'test-one'
69
- test_one(path, id)
73
+ test_one(*args)
70
74
  when 'test-all'
71
- test_all(path)
75
+ test_all
72
76
  when 'submit'
73
- submit(path)
77
+ submit
74
78
  when 'judge-one'
75
- judge_one(path, id)
79
+ judge_one(*args)
76
80
  when 'judge-all'
77
- judge_all(path)
81
+ judge_all
78
82
  when 'open-contest'
79
- open_contest(path)
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(path)
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
- emitter = Emitter.new(path)
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(path, id)
103
- id ||= 1
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(path)
108
- SampleTestRunner.new(path, @config).test_all
109
- Verifier.new(path).verify
110
+ def test_all
111
+ ctx.sample_test_runner.test_all
112
+ ctx.verifier.verify
110
113
  end
111
114
 
112
- def submit(path)
113
- vf = Verifier.new(path)
115
+ def submit
116
+ vf = ctx.verifier
114
117
  raise AppError, "#{vf.file} has not been tested." unless vf.verified?
115
118
 
116
- ScrapingAgent.new(contest_name(path), @config).submit(path)
119
+ ctx.scraping_agent.submit
117
120
  vf.unverify
118
121
  end
119
122
 
120
- def judge_one(path, id)
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(path)
126
- JudgeTestRunner.new(path, @config).judge_all
127
+ def judge_all
128
+ ctx.judge_test_runner.judge_all
127
129
  end
128
130
 
129
- def open_contest(path)
130
- ScrapingAgent.new(contest_name(path), @config).open_contest
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(File.dirname(__FILE__), '..', '..'))
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(target_dir)
15
- path = config_file_for(target_dir)
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
@@ -8,9 +8,9 @@ module AtCoderFriends
8
8
  class Emitter
9
9
  include PathUtil
10
10
 
11
- def initialize(dir)
12
- @src_dir = dir
13
- @smp_dir = smp_dir(dir)
11
+ def initialize(ctx)
12
+ @src_dir = ctx.path
13
+ @smp_dir = smp_dir(@src_dir)
14
14
  end
15
15
 
16
16
  def emit(pbm)
@@ -5,8 +5,8 @@ module AtCoderFriends
5
5
  class JudgeTestRunner < TestRunner
6
6
  include PathUtil
7
7
 
8
- def initialize(path, config)
9
- super(path, config)
8
+ def initialize(ctx)
9
+ super(ctx)
10
10
  @cases_dir = cases_dir(@dir)
11
11
  @smp_dir = smp_dir(@dir)
12
12
  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]
@@ -5,8 +5,8 @@ module AtCoderFriends
5
5
  class SampleTestRunner < TestRunner
6
6
  include PathUtil
7
7
 
8
- def initialize(path, config)
9
- super(path, config)
8
+ def initialize(ctx)
9
+ super(ctx)
10
10
  @smp_dir = smp_dir(@dir)
11
11
  end
12
12
 
@@ -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 'launchy'
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 = 'https://atcoder.jp/'
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 :contest, :config, :agent
24
+ attr_reader :ctx, :agent
20
25
 
21
- def initialize(contest, config)
22
- @contest = contest
23
- @config = config
26
+ def initialize(ctx)
27
+ @ctx = ctx
24
28
  @agent = Mechanize.new
25
- @agent.pre_connect_hooks << proc { sleep 0.1 }
26
- # @agent.log = Logger.new(STDERR)
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 fetch_all
54
- puts "***** fetch_all #{@contest} *****"
55
- login
56
- fetch_assignments.map do |q, url|
57
- pbm = fetch_problem(q, url)
58
- yield pbm if block_given?
59
- pbm
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 submit(path)
64
- path, _dir, prg, _base, ext, q = split_prg_path(path)
65
- puts "***** submit #{prg} *****"
66
- src = File.read(path, encoding: Encoding::UTF_8)
67
- login
68
- post_src(q, ext, src)
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 code_test(path, infile)
72
- path, _dir, _prg, _base, ext, _q = split_prg_path(path)
73
- src = File.read(path, encoding: Encoding::UTF_8)
74
- data = File.read(infile)
75
- login
76
- code_test_loop(ext, src, data)
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 login
80
- return unless config['user'] && !config['user'].empty?
81
- return unless config['password'] && !config['password'].empty?
123
+ def username_link(page)
124
+ link = page.search(XPATH_USERNAME)[0]
125
+ link && link[:href] == '#' && link
126
+ end
82
127
 
83
- page = agent.get(common_url('login'))
84
- form = page.forms[1]
85
- form.field_with(name: 'username').value = config['user']
86
- form.field_with(name: 'password').value = config['password']
87
- form.submit
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 = agent.get(url)
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 = agent.get(url)
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 post_src(q, ext, src)
140
- page = agent.get(contest_url('submit'))
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 code_test_loop(ext, src, data)
152
- page = agent.get(contest_url('custom_test'))
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
- payload = {
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 = agent.get(contest_url('custom_test'))
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
- def initialize(path, config)
11
- @contest = contest_name(path)
12
- @path, @dir, @prg, @base, @ext, @q = split_prg_path(path)
13
- @config = config
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
- agent = ScrapingAgent.new(@contest, @config)
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 = @config.dig('ext_settings', @ext, 'test_cmd')
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
- def initialize(path)
10
- @path = File.expand_path(path)
11
- @file = File.basename(@path)
12
- @vdir = File.join(File.dirname(@path), '.tmp')
13
- @vpath = File.join(@vdir, "#{@file}.verified")
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AtCoderFriends
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  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.4.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-09-16 00:00:00.000000000 Z
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: '12.3'
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: '12.3'
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