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.
@@ -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
+ }
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in aoj.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require "aoj"
3
+
4
+ AOJ::CLI.start
@@ -0,0 +1,7 @@
1
+ require "active_support"
2
+ require "active_support/core_ext"
3
+ require "active_support/dependencies"
4
+ require "active_support/rescuable"
5
+ ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
6
+
7
+ module AOJ; end
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,5 @@
1
+ module AOJ
2
+ module Error
3
+ class APIError < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module AOJ
2
+ module Error
3
+ class FetchResultError < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module AOJ
2
+ module Error
3
+ class FileOpenError < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module AOJ
2
+ module Error
3
+ class InvalidProblemIdError < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module AOJ
2
+ module Error
3
+ class LanguageDetectionError < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module AOJ
2
+ module Error
3
+ class NoProblemLeftError < StandardError; end
4
+ end
5
+ end
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,5 @@
1
+ module AOJ
2
+ class Solution
3
+ attr_accessor :problem, :source, :language
4
+ end
5
+ end
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module AOJ
2
+ VERSION = "0.0.3"
3
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require "rspec"
4
+ require "active_support"
5
+ require File.join(File.dirname(__FILE__), "..", "lib/aoj.rb")
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