aoj 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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