aoj 0.0.3
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 +7 -0
- data/.aoj +8 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +90 -0
- data/Rakefile +1 -0
- data/aoj.gemspec +28 -0
- data/bin/aoj +4 -0
- data/lib/aoj.rb +7 -0
- data/lib/aoj/api.rb +150 -0
- data/lib/aoj/cli.rb +99 -0
- data/lib/aoj/cli/helper.rb +85 -0
- data/lib/aoj/conf.rb +42 -0
- data/lib/aoj/credential.rb +16 -0
- data/lib/aoj/error/api_error.rb +5 -0
- data/lib/aoj/error/fetch_result_error.rb +5 -0
- data/lib/aoj/error/file_open_error.rb +5 -0
- data/lib/aoj/error/invalid_problem_id_error.rb +5 -0
- data/lib/aoj/error/language_detection_error.rb +5 -0
- data/lib/aoj/error/no_problem_left_error.rb +5 -0
- data/lib/aoj/http.rb +35 -0
- data/lib/aoj/language.rb +57 -0
- data/lib/aoj/problem.rb +30 -0
- data/lib/aoj/solution.rb +5 -0
- data/lib/aoj/status.rb +12 -0
- data/lib/aoj/twitter.rb +57 -0
- data/lib/aoj/version.rb +3 -0
- data/spec/lib/aoj/api_spec.rb +88 -0
- data/spec/lib/aoj/conf_spec.rb +35 -0
- data/spec/lib/aoj/language_spec.rb +19 -0
- data/spec/spec_helper.rb +5 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b5e7eb9e935d083673c1106654ebf87b884edf9e
|
4
|
+
data.tar.gz: 58c4bf1e4f68be8f3aad5dd02d31c9885da9b82c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 31627f6169e330dbdb128c69d33a82ac4d958b3262d082f3c799e1421a18938a010656f51c21e5c9ebe6daf85aae609ca57d31d47648ba4b75a3955101f213c5
|
7
|
+
data.tar.gz: e505e9861441cd5c176d2e68c553cc38a4110a1cb8ccb9b4ddb4fc11ae919221e4df14ffa98a75295ebe1bf1664a64968e53aa1ab3c659de1506a876339f299c
|
data/.aoj
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
{
|
2
|
+
"username": "YOUR AOJ USERNAME",
|
3
|
+
"password": "YOUR AOJ PASSWORD",
|
4
|
+
"tw_consumer_key" : "YOUR TWITTER CONSUMER KEY",
|
5
|
+
"tw_consumer_secret" : "YOUR TWITTER CONSUMER SECRET",
|
6
|
+
"tw_access_token" : "YOUR TWITTER ACCESS TOKEN",
|
7
|
+
"tw_access_token_secret" : "YOUR TWITTER ACCESS TOKEN SECRET"
|
8
|
+
}
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 TODO: Write your name
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# AOJ
|
2
|
+
|
3
|
+
AOJ is a command-line tool for [AOJ(Aizu Online Judge)](http://judge.u-aizu.ac.jp/onlinejudge/). You can submit your source to AOJ and check the result of judgement without opening browser or copy & paste.
|
4
|
+
|
5
|
+
This program is forked from aizuzia's [AOJToolkit](http://d.hatena.ne.jp/Tayama/20101207/1291727425).
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Please make sure that Ruby (version 2.0 or later) is installed.
|
10
|
+
|
11
|
+
You can install AOJ via RubyGems.org
|
12
|
+
```
|
13
|
+
$ gem install aoj
|
14
|
+
```
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
Problem ID and Language are auto-detected by filename. You should make your source file be like `[ProblemID].(cpp|rb|...)`.
|
19
|
+
|
20
|
+
#### Submit simply
|
21
|
+
|
22
|
+
```
|
23
|
+
$ aoj submit 0001.cpp
|
24
|
+
```
|
25
|
+
|
26
|
+
#### Problem ID and Language can be manually specified
|
27
|
+
|
28
|
+
```
|
29
|
+
$ aoj submit mysource.cpp -p 0001 -l cpp11
|
30
|
+
```
|
31
|
+
|
32
|
+
#### Submit and Tweet result
|
33
|
+
|
34
|
+
```
|
35
|
+
$ aoj submit 0001.cpp -t
|
36
|
+
```
|
37
|
+
|
38
|
+
#### List available languages
|
39
|
+
|
40
|
+
```
|
41
|
+
$ aoj lang
|
42
|
+
List available languages:
|
43
|
+
c, cpp, java, cpp11, csharp, d, ruby, python, python3, php, js
|
44
|
+
|
45
|
+
Auto-detect extensions:
|
46
|
+
[ext] [lang]
|
47
|
+
.c c
|
48
|
+
.cpp cpp
|
49
|
+
.cc cpp
|
50
|
+
.C cpp
|
51
|
+
.java java
|
52
|
+
.rb ruby
|
53
|
+
.cs csharp
|
54
|
+
.d d
|
55
|
+
.py python
|
56
|
+
.php php
|
57
|
+
.js javascript
|
58
|
+
```
|
59
|
+
|
60
|
+
You can check all other commands and options by typing `aoj` and enter.
|
61
|
+
|
62
|
+
## Configuration
|
63
|
+
|
64
|
+
AOJ supports interactive configuration. Your account information once used will be stored at your home directory `~/.aojrc`.
|
65
|
+
|
66
|
+
If you need to use an HTTP proxy to access the Internet, export the HTTP_PROXY or http_proxy environment variable: `export $HTTP_PROXY="http://user:password@proxy:8080"`.
|
67
|
+
|
68
|
+
AOJ also support some extra configuration.
|
69
|
+
|
70
|
+
#### Custom mapping from file extension to language
|
71
|
+
|
72
|
+
To use custom mapping, please edit `~/.aojrc` directly.
|
73
|
+
|
74
|
+
For example:
|
75
|
+
|
76
|
+
```
|
77
|
+
extname:
|
78
|
+
".py": python3
|
79
|
+
".cpp": cpp11
|
80
|
+
```
|
81
|
+
|
82
|
+
which means, `hoge.py` file will be submitted as Python3 and `hoge.cpp` is C++11.
|
83
|
+
|
84
|
+
## Contributing
|
85
|
+
|
86
|
+
1. Fork it ( http://github.com/na-o-sss/aoj/fork )
|
87
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
88
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
89
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
90
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/aoj.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'aoj/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "aoj"
|
8
|
+
spec.version = AOJ::VERSION
|
9
|
+
spec.authors = ["na-o-ys"]
|
10
|
+
spec.email = ["naoyoshi0511@gmail.com"]
|
11
|
+
spec.summary = %q{aoj submitter}
|
12
|
+
spec.description = %q{This is a submitter program which submits your source to AOJ(Aizu Online Judge) and retrieves result of judgement.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.4"
|
23
|
+
spec.add_development_dependency "thor", "~> 0.19"
|
24
|
+
spec.add_development_dependency "activesupport", "~> 4.2"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
26
|
+
spec.add_development_dependency "oauth", "~> 0.4"
|
27
|
+
spec.add_development_dependency "twitter", "~> 5.14"
|
28
|
+
end
|
data/bin/aoj
ADDED
data/lib/aoj.rb
ADDED
data/lib/aoj/api.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require "rexml/document"
|
2
|
+
|
3
|
+
module AOJ
|
4
|
+
module API
|
5
|
+
SUBMISSION_DATE_GAP_SEC = 30 # to identify judge result of a submission
|
6
|
+
RETRY_SEC = 30 # max retry sec fetching result
|
7
|
+
FETCH_STATUS_WAIT_SEC = 5
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def problem_search(problem_id)
|
12
|
+
base_url = 'http://judge.u-aizu.ac.jp/onlinejudge/webservice/problem'
|
13
|
+
params = { id: problem_id }
|
14
|
+
response = AOJ::HTTP.get(
|
15
|
+
URI.parse("#{base_url}?#{params.to_query}")
|
16
|
+
)
|
17
|
+
doc = REXML::Document.new(response)
|
18
|
+
|
19
|
+
if doc.elements['problem/id'].nil?
|
20
|
+
raise AOJ::Error::APIError, "Problem id may be invalid (#{problem_id})"
|
21
|
+
end
|
22
|
+
|
23
|
+
AOJ::Problem.new.tap do |p|
|
24
|
+
v = ->(k) { doc.get_text(k).try(:value).try(:strip) }
|
25
|
+
p.id = v['problem/id']
|
26
|
+
p.name = v['problem/name']
|
27
|
+
p.available = v['problem/available'] == '1'
|
28
|
+
p.time_limit = v['problem/problemtimelimit'].to_i
|
29
|
+
p.memory_limit = v['problem/problemmemorylimit'].to_i
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def status_log_search(problem, credential)
|
34
|
+
base_url = 'http://judge.u-aizu.ac.jp/onlinejudge/webservice/status_log'
|
35
|
+
|
36
|
+
statuses = []
|
37
|
+
start = 0, limit = 100
|
38
|
+
loop do
|
39
|
+
params = {
|
40
|
+
'user_id' => credential.username,
|
41
|
+
'problem_id' => problem.id,
|
42
|
+
'start' => start,
|
43
|
+
'limit' => limit
|
44
|
+
}
|
45
|
+
# TODO: non-blocking
|
46
|
+
uri = URI.parse("#{base_url}?#{params.to_query}")
|
47
|
+
res =
|
48
|
+
REXML::Document.new(AOJ::HTTP.get(uri))
|
49
|
+
.get_elements('status_list/status')
|
50
|
+
statuses += res
|
51
|
+
|
52
|
+
break if res.size < limit
|
53
|
+
start += limit
|
54
|
+
end
|
55
|
+
|
56
|
+
statuses.map do |s|
|
57
|
+
v = -> (k) { s.get_text(k).try(:value).try(:strip) }
|
58
|
+
AOJ::Status.new.tap { |o|
|
59
|
+
o.run_id = v['run_id'].to_i
|
60
|
+
o.user_id = v['user_id']
|
61
|
+
o.problem_id = v['problem_id'].to_i
|
62
|
+
o.submission_date = Time.at(v['submission_date'].to_i/1000)
|
63
|
+
o.status = v['status']
|
64
|
+
o.language = v['language']
|
65
|
+
o.cputime = v['cputime']
|
66
|
+
o.memory = v['memory']
|
67
|
+
o.code_size = v['code_size']
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def user(user_id)
|
73
|
+
url = "http://judge.u-aizu.ac.jp/onlinejudge/webservice/user"
|
74
|
+
params = { "id" => user_id }
|
75
|
+
uri = URI.parse("#{url}?#{params.to_query}")
|
76
|
+
doc = REXML::Document.new(AOJ::HTTP.get(uri))
|
77
|
+
solved_problems = doc.get_elements('user/solved_list/problem')
|
78
|
+
{
|
79
|
+
id: doc.get_text('user/id').value.strip,
|
80
|
+
name: doc.get_text('user/name').value.strip,
|
81
|
+
solved_list: solved_problems.map { |item|
|
82
|
+
{
|
83
|
+
id: item.get_text('id').value.strip
|
84
|
+
}
|
85
|
+
}
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def problem_list(volume)
|
90
|
+
url = "http://judge.u-aizu.ac.jp/onlinejudge/webservice/problem_list"
|
91
|
+
params = { "volume" => volume }
|
92
|
+
uri = URI.parse("#{url}?#{params.to_query}")
|
93
|
+
doc = REXML::Document.new(AOJ::HTTP.get(uri))
|
94
|
+
problems = doc.get_elements('problem_list/problem')
|
95
|
+
problems.map do |problem|
|
96
|
+
{
|
97
|
+
id: problem.get_text('id').value.strip,
|
98
|
+
name: problem.get_text('name').value.strip
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def submit(solution, credential)
|
104
|
+
uri = URI.parse('http://judge.u-aizu.ac.jp/onlinejudge/servlet/Submit')
|
105
|
+
params = {
|
106
|
+
'userID' => credential.username,
|
107
|
+
'password' => credential.password,
|
108
|
+
'problemNO' => solution.problem.id,
|
109
|
+
'sourceCode' => solution.source,
|
110
|
+
'language' => solution.language.submit_name
|
111
|
+
}
|
112
|
+
|
113
|
+
if AOJ::HTTP.post_form(uri, params).code != '200'
|
114
|
+
raise AOJ::Error::APIError, 'Failed to submit'
|
115
|
+
end
|
116
|
+
# TODO: when userid or password is wrong
|
117
|
+
solution
|
118
|
+
end
|
119
|
+
|
120
|
+
def judge_result(solution, submission_date, credential)
|
121
|
+
start_time = Time.now
|
122
|
+
loop do
|
123
|
+
if Time.now - start_time > RETRY_SEC
|
124
|
+
raise AOJ::Error::FetchResultError, 'Failed to fetch result'
|
125
|
+
end
|
126
|
+
|
127
|
+
sleep FETCH_STATUS_WAIT_SEC
|
128
|
+
|
129
|
+
hit = status_log_search(solution.problem, credential)
|
130
|
+
.select { |s|
|
131
|
+
(s.submission_date - submission_date).abs < SUBMISSION_DATE_GAP_SEC
|
132
|
+
}
|
133
|
+
.max_by(&:submission_date)
|
134
|
+
|
135
|
+
return hit if hit
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def loginable?(credential)
|
140
|
+
uri = URI.parse("http://judge.u-aizu.ac.jp/onlinejudge/index.jsp")
|
141
|
+
params = {
|
142
|
+
'loginUserID' => credential.username,
|
143
|
+
'loginPassword' => credential.password
|
144
|
+
}
|
145
|
+
res = AOJ::HTTP.post_form(uri, params)
|
146
|
+
!res.body.include?('Wrong User ID.') and !res.body.include?('Wrong Password.')
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
data/lib/aoj/cli.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module AOJ
|
4
|
+
class CLI < Thor
|
5
|
+
include Helper
|
6
|
+
|
7
|
+
option :twitter, type: :boolean, aliases: :t
|
8
|
+
option :problem, aliases: :p
|
9
|
+
option :lang, aliases: :l
|
10
|
+
desc "submit SOURCE_FILE", "Submit your source code"
|
11
|
+
def submit(file)
|
12
|
+
unless conf.has_credential?
|
13
|
+
input_credentials
|
14
|
+
puts
|
15
|
+
end
|
16
|
+
|
17
|
+
solution = Solution.new.tap do |s|
|
18
|
+
s.source = read_file(file)
|
19
|
+
s.problem = detect_problem(file, options[:problem])
|
20
|
+
s.language = detect_language(file, options[:lang])
|
21
|
+
end
|
22
|
+
print_solution_info solution
|
23
|
+
puts
|
24
|
+
|
25
|
+
puts "Submitting..."
|
26
|
+
API.submit(solution, Credential.get)
|
27
|
+
puts
|
28
|
+
|
29
|
+
puts "Fetching judge result..."
|
30
|
+
result = API.judge_result(solution, Time.now, Credential.get)
|
31
|
+
puts
|
32
|
+
|
33
|
+
print_result result
|
34
|
+
|
35
|
+
if options[:twitter]
|
36
|
+
unless conf.has_twitter_credential?
|
37
|
+
puts
|
38
|
+
twitter_auth
|
39
|
+
end
|
40
|
+
Twitter.instance.post(solution, result)
|
41
|
+
end
|
42
|
+
rescue AOJ::Error::LanguageDetectionError,
|
43
|
+
AOJ::Error::APIError,
|
44
|
+
AOJ::Error::FetchResultError,
|
45
|
+
AOJ::Error::FileOpenError => e
|
46
|
+
puts e.message
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "omikuji", "今日の 1 問"
|
50
|
+
def omikuji
|
51
|
+
problem = AOJ::Problem.random_icpc(conf['username'])
|
52
|
+
title = "ID #{problem.id}"
|
53
|
+
width = title.size
|
54
|
+
line = "-" * (width + 4)
|
55
|
+
title_line = "| #{title} |"
|
56
|
+
lspace = " " * ((width + 1)/2)
|
57
|
+
rspace = " " * (width + 1 - (width + 1)/2)
|
58
|
+
body = [problem.name.split("")]
|
59
|
+
.transpose
|
60
|
+
.map { |s| "|#{lspace}#{s[0]}#{rspace}|" }
|
61
|
+
|
62
|
+
outer_width = problem.url.size
|
63
|
+
outer_lspace = " " * (outer_width/2 - line.size/2 - 2)
|
64
|
+
puts [line, title_line, line, body, line]
|
65
|
+
.flatten
|
66
|
+
.map { |s| outer_lspace + s }
|
67
|
+
.join("\n")
|
68
|
+
puts
|
69
|
+
puts problem.url
|
70
|
+
puts
|
71
|
+
rescue AOJ::Error::NoProblemLeftError
|
72
|
+
puts "No problem left. You are crazy!"
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "setting", "Setup login credentials"
|
76
|
+
def setting
|
77
|
+
input_credentials
|
78
|
+
end
|
79
|
+
|
80
|
+
desc "twitter", "Auth twitter account"
|
81
|
+
def twitter
|
82
|
+
twitter_auth
|
83
|
+
end
|
84
|
+
|
85
|
+
desc "langs", "List available languages"
|
86
|
+
def langs
|
87
|
+
puts "List available languages:"
|
88
|
+
print " "
|
89
|
+
puts Language.languages.map(&:key).join(", ")
|
90
|
+
|
91
|
+
puts
|
92
|
+
puts "Auto-detect extensions:"
|
93
|
+
puts " " + "[ext]".ljust(10) + "[lang]"
|
94
|
+
Language.extnames.each do |k, v|
|
95
|
+
puts " " + k.ljust(10) + v.to_s
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "io/console"
|
2
|
+
|
3
|
+
module AOJ
|
4
|
+
module CLI::Helper
|
5
|
+
def detect_language(file, opt)
|
6
|
+
language =
|
7
|
+
if opt
|
8
|
+
AOJ::Language.find(opt.to_sym)
|
9
|
+
else
|
10
|
+
AOJ::Language.find_by_ext(File.extname(file))
|
11
|
+
end
|
12
|
+
|
13
|
+
unless language
|
14
|
+
raise AOJ::Error::LanguageDetectionError, "Failed to detect language, check `aoj langs`"
|
15
|
+
end
|
16
|
+
language
|
17
|
+
end
|
18
|
+
|
19
|
+
def detect_problem(file, opt)
|
20
|
+
problem_id = opt || File.basename(file, ".*")
|
21
|
+
API.problem_search problem_id
|
22
|
+
end
|
23
|
+
|
24
|
+
def read_file(file)
|
25
|
+
File.read(file)
|
26
|
+
rescue Errno::ENOENT => e
|
27
|
+
raise AOJ::Error::FileOpenError, e.message
|
28
|
+
end
|
29
|
+
|
30
|
+
def print_solution_info(solution)
|
31
|
+
puts "Language: #{solution.language.submit_name}"
|
32
|
+
puts "Problem:"
|
33
|
+
puts " id: #{solution.problem.id}"
|
34
|
+
puts " name: #{solution.problem.name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def print_result(result)
|
38
|
+
puts "Judge result:"
|
39
|
+
puts " Status: #{result.status}" if result.status
|
40
|
+
puts " Code Size: #{result.code_size}" if result.code_size
|
41
|
+
puts " CPU Time: #{result.cputime}" if result.cputime
|
42
|
+
puts " Memory: #{result.memory}" if result.memory
|
43
|
+
puts " URL: #{result.review_url}" if result.review_url
|
44
|
+
end
|
45
|
+
|
46
|
+
def input_credentials
|
47
|
+
credential = Credential.new
|
48
|
+
|
49
|
+
loop do
|
50
|
+
puts "Input AOJ username:"
|
51
|
+
credential.username = STDIN.gets.strip
|
52
|
+
puts "Input AOJ password:"
|
53
|
+
credential.password = STDIN.noecho(&:gets).strip
|
54
|
+
puts
|
55
|
+
break if credential.valid?
|
56
|
+
puts "Invalid username/password."
|
57
|
+
end
|
58
|
+
|
59
|
+
conf['username'] = credential.username
|
60
|
+
conf['password'] = credential.password
|
61
|
+
conf.save
|
62
|
+
puts "Your credential is saved at #{conf.rcfile_path}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def twitter_auth
|
66
|
+
request_token = Twitter.instance.request_token
|
67
|
+
puts "Connect to twitter account. Please access to url."
|
68
|
+
puts request_token.authorize_url
|
69
|
+
puts "Input PIN:"
|
70
|
+
pin = STDIN.gets.strip
|
71
|
+
puts
|
72
|
+
access_token = request_token.get_access_token(oauth_verifier: pin)
|
73
|
+
conf['twitter'] = {
|
74
|
+
'access_token' => access_token.token,
|
75
|
+
'access_token_secret' => access_token.secret
|
76
|
+
}
|
77
|
+
conf.save
|
78
|
+
puts "Your credential is saved at #{conf.rcfile_path}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def conf
|
82
|
+
Conf.instance
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/aoj/conf.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "forwardable"
|
3
|
+
|
4
|
+
module AOJ
|
5
|
+
class Conf
|
6
|
+
include Singleton
|
7
|
+
extend Forwardable
|
8
|
+
RCFILE = ".aojrc"
|
9
|
+
|
10
|
+
def_delegators :@data, :[], :[]=
|
11
|
+
attr_accessor :data
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@data = load_file
|
15
|
+
end
|
16
|
+
|
17
|
+
def rcfile_path
|
18
|
+
File.join(Dir.home, RCFILE)
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_file
|
22
|
+
YAML.load_file rcfile_path
|
23
|
+
rescue Errno::ENOENT
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
|
27
|
+
def save
|
28
|
+
open(rcfile_path, "w", 0600) do |file|
|
29
|
+
file.write @data.to_yaml
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_credential?
|
34
|
+
self['username'] and self['password']
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_twitter_credential?
|
38
|
+
(twitter = self['twitter']) and
|
39
|
+
twitter['access_token'] and twitter['access_token_secret']
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module AOJ
|
2
|
+
class Credential
|
3
|
+
attr_accessor :username, :password
|
4
|
+
|
5
|
+
def valid?
|
6
|
+
AOJ::API.loginable?(self)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.get
|
10
|
+
Credential.new.tap do |c|
|
11
|
+
c.username = AOJ::Conf.instance['username']
|
12
|
+
c.password = AOJ::Conf.instance['password']
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/aoj/http.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module AOJ
|
4
|
+
|
5
|
+
HTTP = -> do
|
6
|
+
# set proxy if specified
|
7
|
+
# [http://][user:pass@]host:port
|
8
|
+
env = ENV["HTTP_PROXY"] || ENV["http_proxy"]
|
9
|
+
break ::Net::HTTP unless env
|
10
|
+
|
11
|
+
info = parse_proxy_info(env)
|
12
|
+
::Net::HTTP::Proxy(
|
13
|
+
info[:host],
|
14
|
+
info[:port],
|
15
|
+
info[:user],
|
16
|
+
info[:pass]
|
17
|
+
)
|
18
|
+
end.call
|
19
|
+
|
20
|
+
private
|
21
|
+
# TODO: test
|
22
|
+
def self.parse_proxy_info(str)
|
23
|
+
{}.tap do |i|
|
24
|
+
str.sub!(/http:\/\//, "")
|
25
|
+
str.sub!(/\/+$/, "")
|
26
|
+
if str.include?("@")
|
27
|
+
auth, str = str.split("@")
|
28
|
+
i[:user], i[:pass] = auth.split(":")
|
29
|
+
end
|
30
|
+
i[:host], port_str = str.split(":")
|
31
|
+
i[:port] = port_str.sub(/\//, "").to_i
|
32
|
+
i[:uri] = "http://" + str
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/aoj/language.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module AOJ
|
2
|
+
class Language
|
3
|
+
attr_accessor :key, :submit_name
|
4
|
+
|
5
|
+
langs = { # TODO: yml config
|
6
|
+
c: "C",
|
7
|
+
cpp: "C++",
|
8
|
+
java: "JAVA",
|
9
|
+
cpp11: "C++11",
|
10
|
+
csharp: "C#",
|
11
|
+
d: "D",
|
12
|
+
ruby: "Ruby",
|
13
|
+
python: "Python",
|
14
|
+
python3: "Python3",
|
15
|
+
php: "PHP",
|
16
|
+
js: "JavaScript"
|
17
|
+
}
|
18
|
+
|
19
|
+
@languages = langs.each.map { |key, submit_name|
|
20
|
+
Language.new.tap { |l|
|
21
|
+
l.key = key
|
22
|
+
l.submit_name = submit_name
|
23
|
+
}
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
@extnames = { #TODO: yml config
|
27
|
+
".c" => :c,
|
28
|
+
".cpp" => :cpp,
|
29
|
+
".cc" => :cpp,
|
30
|
+
".C" => :cpp,
|
31
|
+
".java" => :java,
|
32
|
+
".rb" => :ruby,
|
33
|
+
".cs" => :csharp,
|
34
|
+
".d" => :d,
|
35
|
+
".py" => :python,
|
36
|
+
".php" => :php,
|
37
|
+
".js" => :javascript
|
38
|
+
}
|
39
|
+
if custom_ext = AOJ::Conf.instance['extname']
|
40
|
+
custom_ext.transform_values!(&:to_sym)
|
41
|
+
@extnames.merge! custom_ext
|
42
|
+
end
|
43
|
+
@extnames.freeze
|
44
|
+
|
45
|
+
class << self
|
46
|
+
attr_reader :languages, :extnames
|
47
|
+
|
48
|
+
def find(key)
|
49
|
+
languages.find { |l| l.key == key }
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_by_ext(ext)
|
53
|
+
languages.find { |l| l.key == extnames[ext] }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/aoj/problem.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module AOJ
|
2
|
+
class Problem
|
3
|
+
attr_accessor :id, :name, :available, :time_limit, :memory_limit
|
4
|
+
|
5
|
+
def url
|
6
|
+
base_url = "http://judge.u-aizu.ac.jp/onlinejudge/description.jsp"
|
7
|
+
params = { "id" => id }
|
8
|
+
base_url + "?" + params.to_query
|
9
|
+
end
|
10
|
+
|
11
|
+
ICPC_VOLUMES = [
|
12
|
+
'10', '11', '12', '13', '20', '21', '22', '23', '24', '25', '26'
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def random_icpc(user_id)
|
17
|
+
volumes = ICPC_VOLUMES
|
18
|
+
solved_ids = AOJ::API.user(user_id)[:solved_list].map { |s| s[:id] }
|
19
|
+
volumes.shuffle.each do |volume|
|
20
|
+
problem_list = AOJ::API.problem_list(volume).map { |e| e[:id] } - solved_ids
|
21
|
+
unless problem_list.empty?
|
22
|
+
problem_id = problem_list.sample
|
23
|
+
return AOJ::API.problem_search(problem_id)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
raise AOJ::Error::NoProblemLeftError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/aoj/solution.rb
ADDED
data/lib/aoj/status.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module AOJ
|
2
|
+
class Status
|
3
|
+
attr_accessor :run_id, :user_id, :problem_id, :submission_date, :status, :language, :cputime, :memory, :code_size
|
4
|
+
|
5
|
+
def review_url
|
6
|
+
nil unless run_id
|
7
|
+
base_url = "http://judge.u-aizu.ac.jp/onlinejudge/review.jsp"
|
8
|
+
params = { "rid" => run_id }
|
9
|
+
base_url + "?" + params.to_query
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/aoj/twitter.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require "twitter"
|
2
|
+
require "oauth"
|
3
|
+
|
4
|
+
module AOJ
|
5
|
+
class Twitter
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
CONSUMER_KEY = "0T4d46LUm3jlf5xDPeRRQIUam"
|
9
|
+
CONSUMER_SECRET = "knA6biWCQ6oBpyVSEjHhFz3Mn8Z9UsJc7TD9h5DdZoRxBl0KBp"
|
10
|
+
|
11
|
+
def post(solution, status)
|
12
|
+
client.update create_text(solution, status)
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_token
|
16
|
+
consumer = OAuth::Consumer.new(
|
17
|
+
CONSUMER_KEY,
|
18
|
+
CONSUMER_SECRET,
|
19
|
+
{ site: "https://api.twitter.com" }
|
20
|
+
)
|
21
|
+
consumer.get_request_token
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def create_text(solution, status)
|
26
|
+
text = <<-"EOS"
|
27
|
+
#{status.status} #AOJ
|
28
|
+
[#{solution.problem.id}:%s] %s
|
29
|
+
SOURCE: %s
|
30
|
+
LANG: #{solution.language.submit_name}
|
31
|
+
EOS
|
32
|
+
|
33
|
+
# Shortening problem title
|
34
|
+
url_len = 2 * client.configuration.short_url_length
|
35
|
+
title = solution.problem.name
|
36
|
+
wordcount = text.length - 2 * 3 + url_len
|
37
|
+
if wordcount > 140
|
38
|
+
title = title[0, 140 - wordcount - 2] + ".."
|
39
|
+
end
|
40
|
+
|
41
|
+
text % [title, solution.problem.url, status.review_url]
|
42
|
+
end
|
43
|
+
|
44
|
+
def client
|
45
|
+
@client ||= ::Twitter::REST::Client.new do |c|
|
46
|
+
c.consumer_key = CONSUMER_KEY
|
47
|
+
c.consumer_secret = CONSUMER_SECRET
|
48
|
+
c.access_token = conf['twitter']['access_token']
|
49
|
+
c.access_token_secret = conf['twitter']['access_token_secret']
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def conf
|
54
|
+
AOJ::Conf.instance
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/aoj/version.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe AOJ::API do
|
4
|
+
describe 'problem_search' do
|
5
|
+
it 'should return valid problem' do
|
6
|
+
v = AOJ::API.problem_search('0000').instance_values.symbolize_keys
|
7
|
+
expect(v).to eq({
|
8
|
+
id: '0000',
|
9
|
+
name: 'QQ',
|
10
|
+
available: true,
|
11
|
+
time_limit: 1,
|
12
|
+
memory_limit: 65536
|
13
|
+
})
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should raise error' do
|
17
|
+
expect{AOJ::API.problem_search('114514')}.to raise_error(AOJ::Error::APIError)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'status_log_search' do
|
22
|
+
it 'should return an array of AOJ::Status' do
|
23
|
+
problem = AOJ::Problem.new.tap { |p| p.id = '0000' }
|
24
|
+
credential = AOJ::Credential.new.tap { |c| c.username = 'na_o_sss' }
|
25
|
+
statuses = AOJ::API.status_log_search(problem, credential)
|
26
|
+
expect(statuses[0]).to be_a AOJ::Status
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'user' do
|
31
|
+
it 'should return valid hash' do
|
32
|
+
h = AOJ::API.user('shioshiota')
|
33
|
+
expect(h[:solved_list].size).to be > 0
|
34
|
+
expect(h[:solved_list][0][:id]).to be_a String
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'problem_list' do
|
39
|
+
it 'should return valid array' do
|
40
|
+
a = AOJ::API.problem_list('10')
|
41
|
+
expect(a.size).to be > 0
|
42
|
+
expect(a[0][:id]).to be_a String
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'submit'
|
47
|
+
|
48
|
+
describe 'judge_result' do
|
49
|
+
context 'target status exists' do
|
50
|
+
it 'should return valid status' do
|
51
|
+
module AOJ::API
|
52
|
+
def self.status_log_search(problem, credential)
|
53
|
+
[
|
54
|
+
AOJ::Status.new.tap { |s| s.submission_date = Time.now.ago(15) }
|
55
|
+
]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
status = AOJ::API.judge_result(AOJ::Solution.new, Time.now, nil)
|
59
|
+
expect(status).to be_a AOJ::Status
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'target status does not exist' do
|
64
|
+
it 'should raise error' do
|
65
|
+
module AOJ::API
|
66
|
+
remove_const :RETRY_SEC
|
67
|
+
RETRY_SEC = 1
|
68
|
+
def self.status_log_search(problem, credential)
|
69
|
+
[
|
70
|
+
AOJ::Status.new.tap { |s| s.submission_date = Time.now.ago(45) }
|
71
|
+
]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
expect{AOJ::API.judge_result(AOJ::Solution.new, Time.now, nil)}.to raise_error(AOJ::Error::FetchResultError)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'loginable?' do
|
80
|
+
it 'should return false with invalid credential' do
|
81
|
+
credential = AOJ::Credential.new.tap { |c|
|
82
|
+
c.username = 'dummy_user'
|
83
|
+
c.password = '1111'
|
84
|
+
}
|
85
|
+
expect(AOJ::API.loginable?(credential)).to be false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe AOJ::Conf do
|
4
|
+
before { @conf = AOJ::Conf.instance }
|
5
|
+
|
6
|
+
describe 'has_credential?' do
|
7
|
+
it 'should return true' do
|
8
|
+
@conf['username'] = 'hoge'
|
9
|
+
@conf['password'] = 'fuga'
|
10
|
+
expect(@conf.has_credential?).to be_truthy
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should return false' do
|
14
|
+
@conf['username'] = 'hoge'
|
15
|
+
@conf.data.delete 'password'
|
16
|
+
expect(@conf.has_credential?).to be_falsy
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'has_twitter_credential?' do
|
21
|
+
it 'should return true' do
|
22
|
+
@conf['twitter'] = {
|
23
|
+
'access_token' => 'hoge',
|
24
|
+
'access_token_secret' => 'fuga'
|
25
|
+
}
|
26
|
+
expect(@conf.has_twitter_credential?).to be_truthy
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should return false' do
|
30
|
+
@conf['twitter'] = { 'access_token' => 'hoge' }
|
31
|
+
@conf['twitter'].delete 'access_token_secret'
|
32
|
+
expect(@conf.has_twitter_credential?).to be_falsy
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe AOJ::Language do
|
4
|
+
describe 'find' do
|
5
|
+
it 'should return valid language' do
|
6
|
+
l = AOJ::Language.find(:cpp)
|
7
|
+
expect(l.key).to eq(:cpp)
|
8
|
+
expect(l.submit_name).to eq("C++")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'find_by_ext' do
|
13
|
+
it 'should return valid language' do
|
14
|
+
l = AOJ::Language.find_by_ext(".rb")
|
15
|
+
expect(l.key).to eq(:ruby)
|
16
|
+
expect(l.submit_name).to eq("Ruby")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aoj
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- na-o-ys
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.4'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: thor
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.19'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.19'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: oauth
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.4'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.4'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: twitter
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '5.14'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '5.14'
|
111
|
+
description: This is a submitter program which submits your source to AOJ(Aizu Online
|
112
|
+
Judge) and retrieves result of judgement.
|
113
|
+
email:
|
114
|
+
- naoyoshi0511@gmail.com
|
115
|
+
executables:
|
116
|
+
- aoj
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- ".aoj"
|
121
|
+
- ".gitignore"
|
122
|
+
- Gemfile
|
123
|
+
- LICENSE.txt
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- aoj.gemspec
|
127
|
+
- bin/aoj
|
128
|
+
- lib/aoj.rb
|
129
|
+
- lib/aoj/api.rb
|
130
|
+
- lib/aoj/cli.rb
|
131
|
+
- lib/aoj/cli/helper.rb
|
132
|
+
- lib/aoj/conf.rb
|
133
|
+
- lib/aoj/credential.rb
|
134
|
+
- lib/aoj/error/api_error.rb
|
135
|
+
- lib/aoj/error/fetch_result_error.rb
|
136
|
+
- lib/aoj/error/file_open_error.rb
|
137
|
+
- lib/aoj/error/invalid_problem_id_error.rb
|
138
|
+
- lib/aoj/error/language_detection_error.rb
|
139
|
+
- lib/aoj/error/no_problem_left_error.rb
|
140
|
+
- lib/aoj/http.rb
|
141
|
+
- lib/aoj/language.rb
|
142
|
+
- lib/aoj/problem.rb
|
143
|
+
- lib/aoj/solution.rb
|
144
|
+
- lib/aoj/status.rb
|
145
|
+
- lib/aoj/twitter.rb
|
146
|
+
- lib/aoj/version.rb
|
147
|
+
- spec/lib/aoj/api_spec.rb
|
148
|
+
- spec/lib/aoj/conf_spec.rb
|
149
|
+
- spec/lib/aoj/language_spec.rb
|
150
|
+
- spec/spec_helper.rb
|
151
|
+
homepage: ''
|
152
|
+
licenses:
|
153
|
+
- MIT
|
154
|
+
metadata: {}
|
155
|
+
post_install_message:
|
156
|
+
rdoc_options: []
|
157
|
+
require_paths:
|
158
|
+
- lib
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 2.2.2
|
172
|
+
signing_key:
|
173
|
+
specification_version: 4
|
174
|
+
summary: aoj submitter
|
175
|
+
test_files:
|
176
|
+
- spec/lib/aoj/api_spec.rb
|
177
|
+
- spec/lib/aoj/conf_spec.rb
|
178
|
+
- spec/lib/aoj/language_spec.rb
|
179
|
+
- spec/spec_helper.rb
|