Skip to content

Instantly share code, notes, and snippets.

@grafi-tt
Forked from alpicola/report.rb
Created October 10, 2012 19:09
Show Gist options
  • Save grafi-tt/3867749 to your computer and use it in GitHub Desktop.
Save grafi-tt/3867749 to your computer and use it in GitHub Desktop.
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でコンパイルして普通に実行してください。
#!/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
@grafi-tt
Copy link
Author

execでユーザー入力を受け付けるように
簡易テストできるようにしてみたけど、そんなに使うことがあるか怪しかった

@grafi-tt
Copy link
Author

  • サーバー叩くのをクラスにまとめた
  • テスト実行関数で、close_writeしてEOF出すようにした
  • 色々出来るようにした
  • Hashの配列が未整形

@grafi-tt
Copy link
Author

sleepの秒数が統一されたなかったのと、余分な後置ifが一箇所あったのだけ修正

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment