-
-
Save grafi-tt/3867749 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Wikiに書こうとしたけど、オフトピな気がしたので。 | |
課題出すやつは、ターミナルからコマンドを叩いて課題を提出するためのRubyスクリプトです。ブラウザでファイル選ぶのめんどいとか、ターミナルから出るのがめんどいとかあればどうぞ。 | |
動かすにはRubyが動いて、ThorとMechanizeというライブラリが入った環境が必要です。rubygemsが使える環境で、 | |
#highlight(sh){ | |
gem install thor mechanize | |
} | |
をターミナルで実行すればいけると思います。Ruby1.9系、Ruby1.8系で多分動きますが、動かなかったらどこかにコメントください。サーバーに迷惑かもしれないので、あんまり極端な回数実行しないでください。同様に、サーバーの仕様が変わって使えなくなった気がするときは、あんまり繰り返し実行しないでください。 | |
スクリプト内のUSER,PASS定数それぞれに、学生証番号、レポート提出システムのパスワードを文字列として入れてください(ダブルクォートでもシングルクォートでも可)。平文で保存したパスワードが十分安全に保たれない環境では使わないでください。 | |
課題を送信する前に、課題のファイル名の先頭を、課題番号にしてください。1-helloworld.cみたいな感じに。1.cだけでもいいです。 | |
#highlight(sh){ | |
#課題送信 | |
report.rb submit 課題1 課題2 ... | |
#課題一覧 | |
report.rb tasks | |
#提出一覧 | |
report.rb report | |
#提出取り消し(レポートIDは、提出一覧で表示される5桁くらいのidです) | |
report.rb revert レポートID1 レポートID2 ... | |
} | |
Cの簡易テスト機能も一応あります。これはOSXやLinuxなどのUnix系環境じゃないとちゃんと動かない気がします。cygwinは良く分かりません。 | |
#highlight(sh){ | |
#課題コンパイル&実行 | |
report.rb exec 課題1.c 課題2.c ... | |
} | |
スクリプトを実行する際のカレントディレクトリに、課題-<好きな文字列>.input というファイル名でファイルを作っておくと、そのファイルを入力として読み取って実行して、実行結果を画面に出力すると同時に 課題-<好きな文字列>.outputに出力します。どういう出力が正解なのか分かる場合は、 課題-<好きな文字列>.expect というファイルを作っておくと、出力がそのファイルと完全に一致するかどうかをテストできます。 | |
inputファイルを作って無ければ、その場で適当に入力した内容を入力として実行します。その際、自動的にその入力を 課題-1.input というファイルに保存して、次回の実行の際に使えるようにできます。なお、Ctrl-Dをタイプすることで入力は終了できます。最終行の後に、余分な改行を一つ入れた方が良いような気がします。 | |
余計なことをせずにその場で入力してその場で出力して欲しいだけなら、 | |
#highlight(sh){ | |
report.rb exec 課題1.c 課題2.c ... -i | |
} | |
とすればいいです。 | |
何かおかしくなったらCtrl-Cで強制終了して、普通にgccでコンパイルして普通に実行してください。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# coding: utf-8 | |
require 'thor' | |
require 'mechanize' | |
USER = '' | |
PASS = '' | |
class Report < Thor | |
desc 'submit src1 src2 ...', 'Submit tasks' | |
def submit(*srcs) | |
srcs.each do |src| | |
unless File.exists?(src) | |
STDERR.puts "#{src} not exist" | |
exit 1 | |
end | |
end | |
client = HagiyaRss.new(USER, PASS) | |
srcs.each do |src| | |
task_number = File.basename(src, '.c').to_i.to_s | |
client.submit(src, task_number) | |
sleep 5 | |
result = client.reports.find{|r|r[:number] == task_number} | |
if result | |
puts "#{src}: #{result[:status]}" | |
else | |
STDERR.puts "#{src}: unknown (please confirm the status of reports by web browser)" | |
end | |
end | |
end | |
desc 'submitsingle src comment(optional)', 'Submit task and comment' | |
def submitsingle(src, comment = '') | |
unless File.exists?(src) | |
STDERR.puts "#{src} not exist" | |
exit 1 | |
end | |
client = HagiyaRss.new(USER, PASS) | |
task_number = File.basename(src, '.c').to_i.to_s | |
client.submit(src, task_number, comment) | |
sleep 5 | |
result = client.reports.find{|r|r[:number] == task_number} | |
if result | |
puts "#{src}: #{result[:status]}" | |
else | |
STDERR.puts "#{src}: unknown (please confirm the status of reports by web browser)" | |
end | |
end | |
desc 'revert report_id1 report_id2 ...', 'Revert specified reports (you can confirm id by typing `report.rb reports`)' | |
def revert(*ids) | |
client = HagiyaRss.new(USER, PASS) | |
ids.each do |report_id| | |
msg = client.revert(report_id) | |
if msg | |
puts "結果: #{msg}" | |
else | |
STDERR.puts "結果: unknown (please confirm by web browser)" | |
end | |
end | |
end | |
=begin | |
desc 'attend number comment(optional)', 'Attend' | |
def attend(number, comment = '') | |
client = HagiyaRss.new(USER, PASS) | |
msg = client.attend(number, comment) | |
if msg | |
puts "結果: #{msg}" | |
else | |
STDERR.puts "結果: unknown (please confirm by web browser)" | |
end | |
end | |
=end | |
desc 'reports', 'Show submitted reports list' | |
def reports() | |
client = HagiyaRss.new(USER, PASS) | |
reports = client.reports() | |
puts reports #TODO pretty output | |
end | |
desc 'tasks', 'Show tasks list' | |
def tasks() | |
client = HagiyaRss.new(USER, PASS) | |
tasks = client.tasks() | |
puts tasks #TODO pretty output | |
end | |
desc 'exec src1 src2 ...', 'Execute code' | |
method_option :ignoreinputfiles, :aliases => "-i", :type => :boolean, :default => false, :desc => 'ignore input files named <basename>-<num>.input' | |
def exec(*srcs) | |
STDERR.puts 'This command perhaps not work properly on Windows' if RUBY_PLATFORM =~ /mswin/ | |
srcs.each do |src| | |
puts "exec: #{src}" | |
basename = File.basename(src, '.c') | |
executable = basename | |
default_input = "#{basename}-1.input" | |
test_inputs = Dir.glob("#{basename}-*.input") | |
test_numbers = test_inputs.map do |fn| | |
fn.match(/#{Regexp.escape(basename)}-(.*)\.input/)[1] | |
end | |
default_output = "#{basename}-1.output" | |
test_outputs = test_numbers.map{|num|"#{basename}-#{num}.output"} | |
test_expects = test_numbers.map{|num|"#{basename}-#{num}.expect"} | |
tests = test_numbers.zip(test_inputs, test_outputs, test_expects) | |
build_success = system "gcc -Wall #{src} -o #{executable}" | |
if build_success | |
run_executable(executable, tests, !options[:ignoreinputfiles], default_input, default_output) | |
else | |
STDERR.puts "compiling #{src} failed" | |
end | |
end | |
end | |
end | |
class HagiyaRss | |
class InvalidLayoutException < StandardError; end | |
class NoTaskException < StandardError; end | |
class NoReportException < StandardError; end | |
def initialize(user, pass) | |
if user.empty? || pass.empty? | |
raise "you must fill USER and PASS in the script" | |
end | |
@agent = Mechanize.new | |
login_page = @agent.get('http://hagi.is.s.u-tokyo.ac.jp/rss/') | |
login_page.form_with(:method => 'POST') do |form| | |
form['account[user]'] = USER | |
form['account[password]'] = PASS | |
end.submit | |
end | |
def submit(src, task_number, comment = '') | |
tasks = tasks() | |
task = tasks.find{|t|t[:number] == task_number} | |
raise NoTaskException unless task | |
upload_page = rss_get("/reports/new?task_id=#{task[:id]}") | |
filetypes = upload_page.search('//*[@id="report_filetype"]/option').map{|o|[o['value'], o.text()]} # id() XPath function cannot be used inside search() | |
type_pair = filetypes.find{|id, type|task[:type] == type} | |
raise InvalidLayoutException unless type_pair | |
upload_page.form_with(:method => 'POST') do |form| | |
form.file_uploads.first.file_name = src | |
form['report[filetype]'] = type_pair[0] | |
form['report[comment]'] = comment | |
end.submit | |
end | |
def revert(report_id) | |
reports = reports(true) | |
report = reports.find{|r|r[:id] == report_id} | |
raise NoReportException unless report | |
# depending on implicit state of @agent set by the call of reports(true), it's not smart... | |
result_page = @agent.current_page.form_with(:action => "/rss/reports/#{report_id}").submit | |
result = result_page.at('//p[@class="notice"]').text | |
result && result.empty?() ? nil : result | |
end | |
#TODO this function is not tested | |
def attend(number, comment = '') | |
attend_page = rss_get('/attendances/new') | |
result_page = attend_page.form_with(:method => 'POST') do |form| | |
form['report[number]'] = number | |
form['report[comment]'] = comment | |
end.submit | |
result = result_page.at('//p[@class="notice"]').text | |
result && result.empty?() ? nil : result | |
end | |
def reports(force_load = false) | |
return @reports_cache if !force_load && @reports_cache | |
reports_page = rss_get('/reports') | |
rows = reports_page.search("//tr") | |
raise InvalidLayoutException unless rows[0].search('./th').map{|r|r.text} == ["提出日時", "問題番号", "テスト結果"] | |
reports = rows[1..-1].map do |row| | |
date_col, num_col, status_col, form_col = *row.search('./td').to_a | |
{ | |
:id => date_col.at('./a')['href'].match(%r!^/rss/reports/(\d+)$!)[1], | |
:date => date_col.text, | |
:number => num_col.text, | |
:status => status_col.text, | |
} | |
end | |
#revert_token = form_col.at('.//input[@name="authenticity_token"]')['value'] | |
raise InvalidLayoutException unless reports.all?{|r| r.all?{|k,v|v} } | |
@reports_cache = reports | |
end | |
def tasks(force_load = false) | |
return @tasks_cache if !force_load && @tasks_cache | |
tasks_page = rss_get('/tasks') | |
rows = tasks_page.search('//tr') | |
raise InvalidLayoutException unless rows[0].search('./th').map{|r|r.text} == ["問題番号", "締切", "ファイルの種類", "提出"] | |
tasks = rows[1..-1].map do |row| | |
num_col, limit_col, type_col, status_col = *row.search('./td').to_a | |
{ | |
:id => num_col.at('./a')['href'].match(%r!^/rss/reports/new\?task_id=(\d+)$!)[1], | |
:number => num_col.text, | |
:limit => limit_col.text, | |
:type => type_col.text, | |
:status => status_col.text | |
} | |
end | |
@tasks_cache = tasks | |
end | |
private | |
def rss_get(resource) | |
@agent.get("/rss#{resource}") | |
end | |
end | |
def run_executable(executable, tests, enable_tests, default_input_file, default_output_file) | |
if enable_tests && !tests.empty? | |
tests.each do |num, input_file, output_file, expect_file| | |
input = File.read(input_file) | |
output = '' | |
if File.exists?(expect_file) | |
except = File.read(expect_file) | |
else | |
except = nil | |
end | |
if except | |
puts "test #{num}" | |
else | |
puts "input #{num}" | |
puts input | |
end | |
IO.popen("./#{executable}", "r+"){|io| | |
io.write input | |
io.close_write | |
puts "output #{num}" | |
File.open(output_file, 'w') {|f| | |
while (line = io.gets) | |
puts line if !except | |
f.write line | |
output << line | |
end | |
} | |
} | |
if except | |
if except == output | |
puts "test #{num} success" | |
else | |
puts "test #{num} failed" | |
end | |
end | |
end | |
else | |
puts "type test input and Ctrl-D" | |
input = STDIN.read | |
output = '' | |
log = false | |
if enable_tests | |
puts "save the test input and test output? [y]/n" | |
ans = STDIN.getc | |
log = ans != 'n' && ans != 'N' | |
File.open(default_input_file,'w') {|f|f.write input} if log | |
end | |
IO.popen("./#{executable}", "r+"){|io| | |
io.write input | |
io.close_write | |
puts "output" | |
if log | |
File.open(default_output_file, 'w') {|f| | |
while (line = io.gets) | |
puts line | |
f.write line | |
output << line | |
end | |
} | |
else | |
while (line = io.gets) | |
puts line | |
output << line | |
end | |
end | |
} | |
end | |
end | |
Report.start |
- サーバー叩くのをクラスにまとめた
- テスト実行関数で、close_writeしてEOF出すようにした
- 色々出来るようにした
- Hashの配列が未整形
sleepの秒数が統一されたなかったのと、余分な後置ifが一箇所あったのだけ修正
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
execでユーザー入力を受け付けるように
簡易テストできるようにしてみたけど、そんなに使うことがあるか怪しかった