atcoder_tools 0.1.0 → 0.2.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/.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
|