atcoder_tools 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.envrc +1 -0
- data/.github/workflows/deploy.yml +54 -0
- data/.gitignore +3 -0
- data/README.md +31 -27
- data/atcoder_tools.gemspec +2 -0
- data/lib/atcoder_tools/atcoder.rb +72 -0
- data/lib/atcoder_tools/cli.rb +122 -16
- data/lib/atcoder_tools/contest.rb +28 -2
- data/lib/atcoder_tools/log.rb +25 -0
- data/lib/atcoder_tools/settings.rb +100 -0
- data/lib/atcoder_tools/sources/spec.rb.erb +4 -2
- data/lib/atcoder_tools/sources/task.cpp.erb +20 -0
- data/lib/atcoder_tools/sources/task.rb.erb +2 -1
- data/lib/atcoder_tools/task.rb +131 -79
- data/lib/atcoder_tools/version.rb +1 -1
- metadata +36 -4
- data/.travis.yml +0 -6
- data/Gemfile.lock +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e5d0c7a03c8491024c16d6ae87c288f04954180fe6bd3e189db5e3d8118faebf
|
4
|
+
data.tar.gz: 4967f7572872f9e90d1a30753a6c9c2c810392a5a78ee4936580de2b2016fe02
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b36e09e331d51158b46712cfa49e585b18e1be0e72385463b49fcc4bb0cc86060ffed4968469e9be0fd9dc48d8e1f4bae559ab98e526a0938e9b343c47510282
|
7
|
+
data.tar.gz: d758c5a868d0df29e73e9be659f6499eb383301dfca75835ccdcd210e34634d7307df61e7a5ac525a5f560a119dff2ebefa18858871aab27f5ca999c8ba8ccec
|
data/.envrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ATCODER_TOOLS_ENV=development
|
@@ -0,0 +1,54 @@
|
|
1
|
+
name: Publish Gem
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
tags:
|
6
|
+
- v*
|
7
|
+
jobs:
|
8
|
+
build:
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
|
11
|
+
steps:
|
12
|
+
- uses: actions/checkout@v2
|
13
|
+
- uses: actions/setup-ruby@v1
|
14
|
+
with:
|
15
|
+
ruby-version: '2.7'
|
16
|
+
|
17
|
+
- name: Bundle install
|
18
|
+
run: bundle
|
19
|
+
|
20
|
+
- name: Build Gem
|
21
|
+
run: rake build
|
22
|
+
|
23
|
+
- name: Publish gem
|
24
|
+
uses: dawidd6/action-publish-gem@v1
|
25
|
+
with:
|
26
|
+
api_key: ${{secrets.RUBYGEMS_API_KEY}}
|
27
|
+
|
28
|
+
# - name: mkdir gem
|
29
|
+
# run: mkdir ~/.gem
|
30
|
+
|
31
|
+
# - name: Add api key
|
32
|
+
# run: "echo :rubygems_api_key: $RUBYGEMS_API_KEY > ~/.gem/credentials"
|
33
|
+
# env:
|
34
|
+
# RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
35
|
+
|
36
|
+
# - name: test
|
37
|
+
# run: cat ~/.gem/credentials
|
38
|
+
|
39
|
+
# - name: test2
|
40
|
+
# run: git status
|
41
|
+
|
42
|
+
# - name: test3
|
43
|
+
# run: "echo $RUBYGEMS_API_KEY"
|
44
|
+
# env:
|
45
|
+
# RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
46
|
+
|
47
|
+
# - name: set git username
|
48
|
+
# run: git config user.name "Aitaro Chaya"
|
49
|
+
|
50
|
+
# - name: set git email
|
51
|
+
# run: git config user.email "aitaro.chaya@gmail.com"
|
52
|
+
|
53
|
+
# - name: Release Gem
|
54
|
+
# run: rake release
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,47 +1,51 @@
|
|
1
1
|
# AtcoderTools
|
2
|
+
Atcoderの競技環境を簡単にsetupするためのツールです。
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
TODO: Delete this and the text above, and describe your gem
|
8
|
-
|
9
|
-
## Installation
|
10
|
-
|
11
|
-
Add this line to your application's Gemfile:
|
12
|
-
|
13
|
-
```ruby
|
14
|
-
gem 'atcoder_tools'
|
4
|
+
## インストール
|
5
|
+
```bash
|
6
|
+
$ gem install atcoder_tools
|
15
7
|
```
|
16
8
|
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
## 使い方
|
10
|
+
### atcoderにログイン
|
11
|
+
```
|
12
|
+
$ atcoder_tools login
|
13
|
+
```
|
20
14
|
|
21
|
-
|
15
|
+
### コンテスト用ファイルの作成
|
16
|
+
```
|
17
|
+
$ atcoder_tools create abc170
|
18
|
+
```
|
22
19
|
|
23
|
-
|
20
|
+
### コンテスト用自動デバッグ
|
21
|
+
```
|
22
|
+
$ atcoder_tools start
|
23
|
+
```
|
24
24
|
|
25
|
-
|
25
|
+
### コンテストの提出
|
26
26
|
```
|
27
|
-
$ atcoder_tools abc170
|
27
|
+
$ atcoder_tools submit abc170
|
28
28
|
```
|
29
29
|
|
30
|
-
|
30
|
+
### コンテストの削除
|
31
|
+
```
|
32
|
+
$ atcoder_tools delete abc170
|
33
|
+
```
|
31
34
|
|
32
|
-
|
35
|
+
### MODEについて
|
36
|
+
- NONE: なにもおこらない
|
37
|
+
- DEBUG: 標準入力で、testcaseが渡されたら状態で実行される。
|
38
|
+
- RUN: 単純にファイルを実行。標準入力は自分で渡す。
|
39
|
+
- TEST: rspecを用いたテスト自動実行。どれが通っていてどれが通っていないかわかる。
|
33
40
|
|
34
|
-
|
41
|
+
## Development
|
42
|
+
本リポジトリをクローンし、path指定して、gemをインストールするとデバッグできる。
|
35
43
|
|
36
44
|
## Contributing
|
37
|
-
|
38
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/atcoder_tools. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/atcoder_tools/blob/master/CODE_OF_CONDUCT.md).
|
39
|
-
|
45
|
+
issueやプルリク適当に投げてくれてかまいません。
|
40
46
|
|
41
47
|
## License
|
42
|
-
|
43
48
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
44
49
|
|
45
50
|
## Code of Conduct
|
46
|
-
|
47
51
|
Everyone interacting in the AtcoderTools project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/atcoder_tools/blob/master/CODE_OF_CONDUCT.md).
|
data/atcoder_tools.gemspec
CHANGED
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative 'settings'
|
2
|
+
require_relative 'log'
|
3
|
+
|
4
|
+
class Atcoder
|
5
|
+
include Log
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@settings = Settings.new
|
9
|
+
@agent = Mechanize.new
|
10
|
+
@agent.user_agent_alias = 'Mac Mozilla'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.test_login(username, password)
|
14
|
+
atcoder = Atcoder.new
|
15
|
+
return atcoder.login(username=username, password=password)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.submit(task)
|
19
|
+
atcoder = Atcoder.new
|
20
|
+
atcoder.login
|
21
|
+
atcoder.submit(task)
|
22
|
+
end
|
23
|
+
|
24
|
+
# 引数はtaskクラス
|
25
|
+
def task_url(task)
|
26
|
+
"https://atcoder.jp/contests/#{task.contest.name}/tasks/#{task.contest.name}_#{task.name}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# 引数はcontest, taskクラス
|
30
|
+
def submit(task)
|
31
|
+
response = nil
|
32
|
+
@agent.get(task_url(task)) do |page|
|
33
|
+
# 1つ目のフォームはログアウト用なので2つ目を使用
|
34
|
+
form = page.forms.last
|
35
|
+
form.field_with(name: 'data.LanguageId').value = "4049"
|
36
|
+
form.field_with(name: 'sourceCode').value = task.code
|
37
|
+
response = @agent.submit(form)
|
38
|
+
end
|
39
|
+
|
40
|
+
if response.code.to_i == 200
|
41
|
+
return true
|
42
|
+
end
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
|
46
|
+
def login(username=nil, password=nil)
|
47
|
+
|
48
|
+
username ||= @settings.credentials.username
|
49
|
+
password ||= @settings.credentials.password
|
50
|
+
|
51
|
+
response = nil
|
52
|
+
log_debug "try login to atcoder"
|
53
|
+
@agent.get("https://atcoder.jp/login") do |page|
|
54
|
+
# 1つ目のフォームはログアウト用なので2つ目を使用
|
55
|
+
form = page.forms.last
|
56
|
+
form.field_with(name: 'username').value = username
|
57
|
+
form.field_with(name: 'password').value = password
|
58
|
+
response = @agent.submit(form)
|
59
|
+
end
|
60
|
+
|
61
|
+
# print(response.response)
|
62
|
+
res = response.body.include?("Welcome, #{username}.")
|
63
|
+
if res
|
64
|
+
log_debug "login secceed!"
|
65
|
+
else
|
66
|
+
log_warn "login falied!"
|
67
|
+
end
|
68
|
+
|
69
|
+
return res
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
data/lib/atcoder_tools/cli.rb
CHANGED
@@ -1,50 +1,156 @@
|
|
1
|
-
|
2
1
|
require "thor"
|
3
2
|
require 'fileutils'
|
4
3
|
require "listen"
|
4
|
+
require 'mechanize'
|
5
5
|
require_relative 'contest'
|
6
6
|
require_relative 'task'
|
7
|
+
require_relative 'settings'
|
8
|
+
require_relative 'atcoder'
|
9
|
+
require "tty-prompt"
|
10
|
+
require_relative 'log'
|
11
|
+
|
7
12
|
|
8
13
|
module AtcoderTools
|
9
14
|
class CLI < Thor
|
10
|
-
|
15
|
+
include Log
|
16
|
+
Prompt = TTY::Prompt.new
|
17
|
+
desc 'login', 'login to atcoder'
|
18
|
+
def login
|
19
|
+
# 認証情報を書き込むので、gitignoreを設定する
|
20
|
+
unless Dir.exist?('.atcoder')
|
21
|
+
Dir.mkdir('.atcoder')
|
22
|
+
end
|
23
|
+
File.open(".atcoder/.gitignore", mode = "w"){|f|
|
24
|
+
f.write("*") # ファイルに書き込む
|
25
|
+
}
|
26
|
+
loop do
|
27
|
+
prompt = TTY::Prompt.new
|
28
|
+
username = prompt.ask("What is your atcoder username?", required: true)
|
29
|
+
password = prompt.mask("What is your atcoder password?", required: true)
|
30
|
+
puts ""
|
31
|
+
log_info 'starting test login.'
|
32
|
+
|
33
|
+
if Atcoder.test_login(username, password)
|
34
|
+
settings = Settings.new
|
35
|
+
settings.username = username
|
36
|
+
settings.password = password
|
37
|
+
settings.save!
|
38
|
+
log_info 'OK!'
|
39
|
+
break
|
40
|
+
else
|
41
|
+
log_error "Your username or password is invalid! try again!"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'logout', 'logout from atcoder.'
|
47
|
+
def logout
|
48
|
+
settings = Settings.new
|
49
|
+
settings.destroy_credentials!
|
50
|
+
settings.save!
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'language [lang?]', '言語を選択できます。現在対応している言語は ruby, c++(gcc) です。'
|
54
|
+
def language(lang='')
|
55
|
+
unless ['ruby', 'c++(gcc)'].include?(lang)
|
56
|
+
lang = Prompt.select("choose your default language.", ['ruby', 'c++(gcc)'])
|
57
|
+
end
|
58
|
+
|
59
|
+
# コマンドが通っているかテスト
|
60
|
+
log_info "Checking path ..."
|
61
|
+
res = case lang
|
62
|
+
when 'ruby'
|
63
|
+
!!system('ruby -v')
|
64
|
+
when 'c++(gcc)'
|
65
|
+
!!system('c++ -v')
|
66
|
+
end
|
67
|
+
|
68
|
+
unless res
|
69
|
+
raise "Cannot find path of #{lang}. Is `$ #{lang} -v` working?"
|
70
|
+
end
|
71
|
+
|
72
|
+
settings = Settings.new
|
73
|
+
settings.language = lang
|
74
|
+
settings.save!
|
75
|
+
puts
|
76
|
+
log_info 'Default language successfully changed!'
|
77
|
+
end
|
78
|
+
|
79
|
+
desc 'create [contest name]', 'create contest workspace. contest name は actoderのurlより取得できます。(ex. abc173)'
|
11
80
|
def create(contest_name)
|
12
81
|
contest = Contest.new(contest_name)
|
82
|
+
contest.check_validity!
|
13
83
|
contest.create!
|
14
84
|
|
15
|
-
|
85
|
+
log_info 'successfully created'
|
16
86
|
end
|
17
87
|
|
18
88
|
desc 'start', 'start run'
|
19
89
|
def start
|
20
|
-
# system('bundle exec guard -d --guardfile .atcoder/Guardfile')
|
21
90
|
listener = Listen.to('.', ignore: /.atcoder\/.*/) do |modified, added, removed|
|
22
|
-
# puts "modified absolute path: #{modified}"
|
23
|
-
# puts "added absolute path: #{added}"
|
24
|
-
# puts "removed absolute path: #{removed}"
|
25
91
|
if modified[0]
|
26
92
|
contest_name ,task_name = modified[0].split('/')[-2..-1]
|
27
|
-
task_name = task_name[
|
28
|
-
|
29
|
-
puts("#{contest_name}/#{task_name}.rb was changed")
|
93
|
+
task_name = task_name.scan(/(.*)\..*/)[0][0]
|
30
94
|
|
31
95
|
contest = Contest.new(contest_name)
|
32
96
|
# thor と名前空間がかぶっているため
|
33
97
|
task = ::Task.new(contest, task_name)
|
98
|
+
# 最後に動かしたcontest, taskを記憶
|
99
|
+
settings = Settings.new
|
100
|
+
settings.current_contest = contest
|
101
|
+
settings.current_task = task
|
102
|
+
settings.save!
|
103
|
+
|
34
104
|
task.run
|
35
105
|
end
|
36
106
|
end
|
37
|
-
|
107
|
+
log_info 'started actoder_tools session.'
|
108
|
+
listener.start
|
38
109
|
sleep
|
39
110
|
end
|
40
111
|
|
41
|
-
desc 'submit
|
42
|
-
def submit
|
43
|
-
|
112
|
+
desc 'submit', 'ソースコードを提出します。提出する問題は対話形式で指定できます。'
|
113
|
+
def submit
|
114
|
+
prompt = TTY::Prompt.new
|
115
|
+
# contest_name = prompt.select("Choose your submit contest?", Contest.list.map(&:name))
|
116
|
+
contest_name = Prompt.select("Choose your submit contest?") do |submit|
|
117
|
+
Contest.list.each_with_index do |contest, i|
|
118
|
+
submit.choice contest.name
|
119
|
+
if Settings.new.current_contest&.name == contest.name
|
120
|
+
submit.default i + 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# contestによっては6種類ない可能性がある。
|
126
|
+
task_list = ['a', 'b', 'c', 'd', 'e', 'f']
|
127
|
+
task_name = prompt.select("Choose your tesk?") do |submit|
|
128
|
+
task_list.each_with_index do |task, i|
|
129
|
+
submit.choice task
|
130
|
+
if Settings.new.current_task&.name == task
|
131
|
+
submit.default i + 1
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
contest = Contest.new(contest_name)
|
137
|
+
# thor と名前空間がかぶっているため
|
138
|
+
task = ::Task.new(contest, task_name)
|
139
|
+
Atcoder.submit(task)
|
44
140
|
end
|
45
141
|
|
46
|
-
desc 'delete [
|
47
|
-
def delete(contest_name)
|
142
|
+
desc 'delete [contest_name?]', 'delete'
|
143
|
+
def delete(contest_name=nil)
|
144
|
+
unless contest_name
|
145
|
+
contest_name = Prompt.select("Choose your submit contest?") do |submit|
|
146
|
+
Contest.list.each_with_index do |contest, i|
|
147
|
+
submit.choice contest.name
|
148
|
+
if Settings.new.current_contest&.name == contest.name
|
149
|
+
submit.default i + 1
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
48
154
|
FileUtils.rm_rf(contest_name)
|
49
155
|
FileUtils.rm_rf(".atcoder/#{contest_name}")
|
50
156
|
puts 'successfully deleted'
|
@@ -3,6 +3,7 @@ require 'fileutils'
|
|
3
3
|
require 'nokogiri'
|
4
4
|
require 'erb'
|
5
5
|
require_relative 'task'
|
6
|
+
require_relative 'settings'
|
6
7
|
|
7
8
|
class Contest
|
8
9
|
attr_reader :name
|
@@ -11,14 +12,39 @@ class Contest
|
|
11
12
|
@name = name
|
12
13
|
end
|
13
14
|
|
14
|
-
def
|
15
|
-
|
15
|
+
def check_validity!
|
16
|
+
# 実在するかcheck
|
17
|
+
begin
|
18
|
+
URI.open(url)
|
19
|
+
rescue OpenURI::HTTPError
|
20
|
+
raise 'contest name が存在しません。'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def url
|
25
|
+
"https://atcoder.jp/contests/#{@name}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def submission_url
|
29
|
+
"https://atcoder.jp/contests/#{@name}/submissions/me"
|
16
30
|
end
|
17
31
|
|
18
32
|
def create!
|
19
33
|
FileUtils.mkdir_p @name
|
20
34
|
tasks = ['a','b','c','d','e','f'].map{|task_name| Task.new(self, task_name)}
|
21
35
|
tasks.map(&:create!)
|
36
|
+
settings = Settings.new
|
37
|
+
settings.current_contest = @name
|
38
|
+
settings.contest_language = self, settings.language
|
39
|
+
settings.save!
|
40
|
+
end
|
41
|
+
|
42
|
+
def language
|
43
|
+
Settings.new.contest_language(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.list
|
47
|
+
Dir.glob("./*").select{|dir| FileTest.directory?(dir)}.map{|dir| Contest.new dir[2..-1] }
|
22
48
|
end
|
23
49
|
|
24
50
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'logger'
|
2
|
+
module Log
|
3
|
+
LoggerIns = Logger.new(STDOUT)
|
4
|
+
if ENV['ATCODER_TOOLS_ENV'] == 'development'
|
5
|
+
LoggerIns.level = Logger::DEBUG
|
6
|
+
else
|
7
|
+
LoggerIns.level = Logger::INFO
|
8
|
+
end
|
9
|
+
|
10
|
+
def log_debug(msg)
|
11
|
+
LoggerIns.debug(msg)
|
12
|
+
end
|
13
|
+
|
14
|
+
def log_info(msg)
|
15
|
+
LoggerIns.info(msg)
|
16
|
+
end
|
17
|
+
|
18
|
+
def log_warn(msg)
|
19
|
+
LoggerIns.warn(msg)
|
20
|
+
end
|
21
|
+
|
22
|
+
def log_error(msg)
|
23
|
+
LoggerIns.error(msg)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'base64'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
class Settings
|
6
|
+
Credentials = Struct.new(:username, :password)
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
if File.exist?(".atcoder/settings.yml")
|
10
|
+
@settings = YAML.load_file(".atcoder/settings.yml")
|
11
|
+
else
|
12
|
+
@settings = {}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_contest
|
17
|
+
@settings['current_contest'] && Contest.new(@settings['current_contest'])
|
18
|
+
end
|
19
|
+
|
20
|
+
def ambiguous_contest(contest)
|
21
|
+
case contest
|
22
|
+
when String
|
23
|
+
Contest.new(contest)
|
24
|
+
when Contest
|
25
|
+
contest
|
26
|
+
else
|
27
|
+
raise ArgumentError, "引数は StringまたはContestです。"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def current_contest=(contest)
|
32
|
+
@settings['current_contest'] = ambiguous_contest(contest).name
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def current_task
|
37
|
+
current_contest && @settings['current_task'] && Task.new(current_contest, @settings['current_task'])
|
38
|
+
end
|
39
|
+
|
40
|
+
def current_task=(task)
|
41
|
+
case task
|
42
|
+
when String
|
43
|
+
task_name = task
|
44
|
+
when Task
|
45
|
+
task_name = task.name
|
46
|
+
else
|
47
|
+
raise ArgumentError, "引数は StringまたはTaskです。"
|
48
|
+
end
|
49
|
+
@settings['current_task'] = task_name
|
50
|
+
end
|
51
|
+
|
52
|
+
def language
|
53
|
+
@settings['language'] || 'ruby'
|
54
|
+
end
|
55
|
+
|
56
|
+
def language=(lang)
|
57
|
+
@settings['language'] = lang
|
58
|
+
end
|
59
|
+
|
60
|
+
def contest_language(contest)
|
61
|
+
if @settings[ambiguous_contest(contest).name]
|
62
|
+
@settings[ambiguous_contest(contest).name]['language']
|
63
|
+
else
|
64
|
+
language
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# contest, languageの順に代入
|
69
|
+
def contest_language=(data)
|
70
|
+
name = ambiguous_contest(data[0]).name
|
71
|
+
@settings[name] ||= {}
|
72
|
+
@settings[name]['language'] = data[1]
|
73
|
+
end
|
74
|
+
|
75
|
+
def password=(password)
|
76
|
+
@settings['password'] = Base64.strict_encode64(password)
|
77
|
+
end
|
78
|
+
|
79
|
+
def username=(username)
|
80
|
+
@settings['username'] = username
|
81
|
+
end
|
82
|
+
|
83
|
+
def destroy_credentials!
|
84
|
+
@settings.delete('username')
|
85
|
+
@settings.delete('password')
|
86
|
+
end
|
87
|
+
|
88
|
+
def credentials
|
89
|
+
unless @settings.key?('username') && @settings.key?('password')
|
90
|
+
raise "先にログインしてください!"
|
91
|
+
end
|
92
|
+
return Credentials.new(@settings['username'], Base64.decode64(@settings['password']))
|
93
|
+
end
|
94
|
+
|
95
|
+
def save!
|
96
|
+
@settings['updated_at'] = Time.now
|
97
|
+
FileUtils.mkdir_p '.atcoder'
|
98
|
+
open('.atcoder/settings.yml' , 'w') {|f| YAML.dump(@settings , f ) }
|
99
|
+
end
|
100
|
+
end
|
@@ -3,8 +3,10 @@ require 'rspec'
|
|
3
3
|
RSpec.describe 'test' do
|
4
4
|
<% @tests.each do |test| %>
|
5
5
|
it "test-<%= test[:no] %>" do
|
6
|
-
io = IO.popen("
|
7
|
-
|
6
|
+
io = IO.popen("<%= command %>", "w+")
|
7
|
+
<% test[:inputs].each do |input| %>
|
8
|
+
io.puts("<%= input %>")
|
9
|
+
<% end %>
|
8
10
|
io.close_write
|
9
11
|
expect(io.readlines.join).to eq("<%= test[:output] %>")
|
10
12
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
// ------------------------------------------------------------
|
2
|
+
// THIS IS METADATA FOR ATCODER_TOOLS
|
3
|
+
// BE CAREFULL WHEN YOU CHANGE THE VALUE
|
4
|
+
//
|
5
|
+
// METADATA::START<% inputs.each_with_index do |input, i| %>
|
6
|
+
// test-<%=i+1%>
|
7
|
+
// input: "<%= input.gsub(/\R/, '\\n') %>"
|
8
|
+
// output: "<%= outputs[i].gsub(/\R/, '\\n') %>"<% end %>
|
9
|
+
// MODE: TEST
|
10
|
+
// METADATA::FINISH
|
11
|
+
//
|
12
|
+
// you can add your original test case
|
13
|
+
// you can select MODE from NONE, DEBUG, RUN, TEST
|
14
|
+
// problem_url: <%= url %>
|
15
|
+
// submission_url: <%= contest.submission_url %>
|
16
|
+
// -------------------------------------------------------------
|
17
|
+
|
18
|
+
// snippets
|
19
|
+
// n = gets.to_i
|
20
|
+
// arr = gets.split(' ').map(&:to_i)
|
@@ -11,7 +11,8 @@
|
|
11
11
|
#
|
12
12
|
# you can add your original test case
|
13
13
|
# you can select MODE from NONE, DEBUG, RUN, TEST
|
14
|
-
# problem_url: <%=url%>
|
14
|
+
# problem_url: <%= url %>
|
15
|
+
# submission_url: <%= contest.submission_url %>
|
15
16
|
# -------------------------------------------------------------
|
16
17
|
|
17
18
|
# snippets
|
data/lib/atcoder_tools/task.rb
CHANGED
@@ -3,102 +3,154 @@ require 'fileutils'
|
|
3
3
|
require 'nokogiri'
|
4
4
|
require 'erb'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
class Task
|
7
|
+
attr_reader :contest, :name
|
8
|
+
|
9
|
+
# contest引数はcontestクラス
|
10
|
+
def initialize(contest, name)
|
11
|
+
@contest = contest
|
12
|
+
@name = name
|
13
|
+
@tests = []
|
14
|
+
@test_outputs = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def url
|
18
|
+
"https://atcoder.jp/contests/#{@contest.name}/tasks/#{@contest.name}_#{@name}"
|
19
|
+
end
|
14
20
|
|
15
|
-
|
16
|
-
|
21
|
+
def task_file_path
|
22
|
+
extension = nil
|
23
|
+
case @contest.language
|
24
|
+
when 'ruby'
|
25
|
+
extension = 'rb'
|
26
|
+
when 'c++(gcc)'
|
27
|
+
extension = 'cpp'
|
17
28
|
end
|
29
|
+
"#{@contest.name}/#{@name}.#{extension}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def compile_file_path
|
33
|
+
"#{@contest.name}/#{@name}"
|
34
|
+
end
|
18
35
|
|
19
|
-
|
20
|
-
|
36
|
+
def test_file_path
|
37
|
+
".atcoder/#{@contest.name}/#{@name}/spec.rb"
|
38
|
+
end
|
39
|
+
|
40
|
+
def template_task_file_path
|
41
|
+
extension = nil
|
42
|
+
case @contest.language
|
43
|
+
when 'ruby'
|
44
|
+
extension = 'rb'
|
45
|
+
when 'c++(gcc)'
|
46
|
+
extension = 'cpp'
|
21
47
|
end
|
48
|
+
File.dirname(__dir__)+"/atcoder_tools/sources/task.#{extension}.erb"
|
49
|
+
end
|
22
50
|
|
23
|
-
|
24
|
-
|
51
|
+
def create!
|
52
|
+
html = URI.open(url) do |f|
|
53
|
+
charset = f.charset
|
54
|
+
f.read
|
25
55
|
end
|
26
56
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
57
|
+
doc = Nokogiri::HTML.parse(html, nil, 'utf8')
|
58
|
+
samples = doc.css('.lang-ja > .part > section > pre').map { |e| e.children.text }
|
59
|
+
inputs, outputs = samples.partition.with_index { |_sample, i| i.even? }
|
60
|
+
|
61
|
+
erb = ERB.new(File.read(template_task_file_path))
|
62
|
+
File.write(task_file_path, erb.result(binding))
|
63
|
+
end
|
64
|
+
|
65
|
+
def run
|
66
|
+
read_meta_data
|
67
|
+
puts("#{@contest.name}/#{@name} is running in #{@mode} MODE")
|
68
|
+
update_spec
|
69
|
+
if @mode == "DEBUG"
|
70
|
+
compile
|
71
|
+
io = IO.popen(command, "w+")
|
72
|
+
@tests[0][:inputs].each do |input|
|
73
|
+
io.puts(input)
|
31
74
|
end
|
75
|
+
io.close_write
|
76
|
+
puts io.readlines
|
77
|
+
end
|
78
|
+
if @mode == "RUN"
|
79
|
+
compile
|
80
|
+
system("echo put your inputs")
|
81
|
+
system(command)
|
82
|
+
end
|
83
|
+
if @mode == "TEST"
|
84
|
+
compile
|
85
|
+
system("bundle exec rspec #{test_file_path}")
|
86
|
+
end
|
87
|
+
end
|
32
88
|
|
33
|
-
|
34
|
-
|
35
|
-
|
89
|
+
def compile
|
90
|
+
case contest.language
|
91
|
+
when 'c++(gcc)'
|
92
|
+
p 'hoge'
|
93
|
+
system("c++ #{task_file_path} -o #{@contest.name}/#{@name}")
|
94
|
+
end
|
95
|
+
end
|
36
96
|
|
37
|
-
|
38
|
-
|
97
|
+
def command
|
98
|
+
case contest.language
|
99
|
+
when 'ruby'
|
100
|
+
"ruby #{task_file_path}"
|
101
|
+
when 'c++(gcc)'
|
102
|
+
"#{compile_file_path}"
|
39
103
|
end
|
104
|
+
end
|
40
105
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
106
|
+
def read_meta_data
|
107
|
+
meta_flag = false
|
108
|
+
test_no = nil
|
109
|
+
tests = []
|
110
|
+
mode = nil
|
111
|
+
File.foreach(task_file_path) do |line|
|
112
|
+
line.chomp!
|
113
|
+
if line == "# METADATA::START" || line == "// METADATA::START"
|
114
|
+
meta_flag = true
|
115
|
+
next
|
49
116
|
end
|
50
|
-
if
|
51
|
-
|
52
|
-
|
117
|
+
if line == "# METADATA::FINISH" || line == "// METADATA::FINISH"
|
118
|
+
meta_flag = false
|
119
|
+
next
|
53
120
|
end
|
54
|
-
|
55
|
-
|
121
|
+
unless meta_flag
|
122
|
+
next
|
56
123
|
end
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
74
|
-
unless meta_flag
|
75
|
-
next
|
76
|
-
end
|
77
|
-
if line =~ /\#\stest-/
|
78
|
-
test_no = line.scan(/\#\stest-(\d+)/)[0][0].to_i
|
79
|
-
tests.push({ no: test_no })
|
80
|
-
end
|
81
|
-
if line =~ /\#\sinput:/
|
82
|
-
test = tests.find{|test| test[:no] == test_no}
|
83
|
-
test.merge!({ input: line.scan(/\#\sinput\:\s\"(.*)\"/)[0][0] })
|
84
|
-
end
|
85
|
-
if line =~ /\#\soutput:/
|
86
|
-
test = tests.find{|test| test[:no] == test_no}
|
87
|
-
test.merge!({ output: line.scan(/\#\soutput\:\s\"(.*)\"/)[0][0] })
|
88
|
-
end
|
89
|
-
if line =~ /\#\sMODE:/
|
90
|
-
mode = line.scan(/\#\sMODE:\s(.+)/)[0][0]
|
91
|
-
end
|
124
|
+
if line =~ /(\#|\/\/)\stest-/
|
125
|
+
test_no = line.scan(/(\#|\/\/)\stest-(\d+)/)[0][1].to_i
|
126
|
+
tests.push({ no: test_no })
|
127
|
+
end
|
128
|
+
if line =~ /(\#|\/\/)\sinput:/
|
129
|
+
test = tests.find{|test| test[:no] == test_no}
|
130
|
+
input_str = line.scan(/(\#|\/\/)\sinput\:\s\"(.*)\"/)[0][1]
|
131
|
+
inputs = input_str.chomp.split('\\n')
|
132
|
+
test.merge!({ inputs: inputs })
|
133
|
+
end
|
134
|
+
if line =~ /(\#|\/\/)\soutput:/
|
135
|
+
test = tests.find{|test| test[:no] == test_no}
|
136
|
+
test.merge!({ output: line.scan(/(\#|\/\/)\soutput\:\s\"(.*)\"/)[0][1] })
|
137
|
+
end
|
138
|
+
if line =~ /(\#|\/\/)\sMODE:/
|
139
|
+
mode = line.scan(/(\#|\/\/)\sMODE:\s(.+)/)[0][1]
|
92
140
|
end
|
93
|
-
@tests = tests
|
94
|
-
@mode = mode
|
95
141
|
end
|
142
|
+
@tests = tests
|
143
|
+
@mode = mode
|
144
|
+
end
|
96
145
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
File.write(test_file_path, erb.result(binding))
|
101
|
-
end
|
146
|
+
def code
|
147
|
+
File.read(task_file_path)
|
148
|
+
end
|
102
149
|
|
150
|
+
def update_spec
|
151
|
+
FileUtils.mkdir_p ".atcoder/#{@contest.name}/#{@name}"
|
152
|
+
erb = ERB.new(File.read(File.dirname(__dir__)+"/atcoder_tools/sources/spec.rb.erb"))
|
153
|
+
File.write(test_file_path, erb.result(binding))
|
103
154
|
end
|
104
|
-
|
155
|
+
|
156
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: atcoder_tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aitaro Chaya
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -66,6 +66,34 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mechanize
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: tty-prompt
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
description: Write a longer description or delete this line.
|
70
98
|
email:
|
71
99
|
- aitaro.chaya@gmail.com
|
@@ -74,12 +102,12 @@ executables:
|
|
74
102
|
extensions: []
|
75
103
|
extra_rdoc_files: []
|
76
104
|
files:
|
105
|
+
- ".envrc"
|
106
|
+
- ".github/workflows/deploy.yml"
|
77
107
|
- ".gitignore"
|
78
108
|
- ".rspec"
|
79
|
-
- ".travis.yml"
|
80
109
|
- CODE_OF_CONDUCT.md
|
81
110
|
- Gemfile
|
82
|
-
- Gemfile.lock
|
83
111
|
- LICENSE.txt
|
84
112
|
- README.md
|
85
113
|
- Rakefile
|
@@ -88,9 +116,13 @@ files:
|
|
88
116
|
- bin/setup
|
89
117
|
- exe/atcoder_tools
|
90
118
|
- lib/atcoder_tools.rb
|
119
|
+
- lib/atcoder_tools/atcoder.rb
|
91
120
|
- lib/atcoder_tools/cli.rb
|
92
121
|
- lib/atcoder_tools/contest.rb
|
122
|
+
- lib/atcoder_tools/log.rb
|
123
|
+
- lib/atcoder_tools/settings.rb
|
93
124
|
- lib/atcoder_tools/sources/spec.rb.erb
|
125
|
+
- lib/atcoder_tools/sources/task.cpp.erb
|
94
126
|
- lib/atcoder_tools/sources/task.rb.erb
|
95
127
|
- lib/atcoder_tools/task.rb
|
96
128
|
- lib/atcoder_tools/version.rb
|
data/.travis.yml
DELETED
data/Gemfile.lock
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
atcoder_tools (0.1.0)
|
5
|
-
listen
|
6
|
-
nokogiri
|
7
|
-
rspec
|
8
|
-
thor
|
9
|
-
|
10
|
-
GEM
|
11
|
-
remote: https://rubygems.org/
|
12
|
-
specs:
|
13
|
-
diff-lcs (1.3)
|
14
|
-
ffi (1.13.1)
|
15
|
-
listen (3.2.1)
|
16
|
-
rb-fsevent (~> 0.10, >= 0.10.3)
|
17
|
-
rb-inotify (~> 0.9, >= 0.9.10)
|
18
|
-
mini_portile2 (2.4.0)
|
19
|
-
nokogiri (1.10.9)
|
20
|
-
mini_portile2 (~> 2.4.0)
|
21
|
-
rake (12.3.3)
|
22
|
-
rb-fsevent (0.10.4)
|
23
|
-
rb-inotify (0.10.1)
|
24
|
-
ffi (~> 1.0)
|
25
|
-
rspec (3.9.0)
|
26
|
-
rspec-core (~> 3.9.0)
|
27
|
-
rspec-expectations (~> 3.9.0)
|
28
|
-
rspec-mocks (~> 3.9.0)
|
29
|
-
rspec-core (3.9.2)
|
30
|
-
rspec-support (~> 3.9.3)
|
31
|
-
rspec-expectations (3.9.2)
|
32
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
33
|
-
rspec-support (~> 3.9.0)
|
34
|
-
rspec-mocks (3.9.1)
|
35
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
36
|
-
rspec-support (~> 3.9.0)
|
37
|
-
rspec-support (3.9.3)
|
38
|
-
thor (1.0.1)
|
39
|
-
|
40
|
-
PLATFORMS
|
41
|
-
ruby
|
42
|
-
|
43
|
-
DEPENDENCIES
|
44
|
-
atcoder_tools!
|
45
|
-
listen (~> 3.2)
|
46
|
-
rake (~> 12.0)
|
47
|
-
rspec (~> 3.0)
|
48
|
-
|
49
|
-
BUNDLED WITH
|
50
|
-
2.1.4
|