Newer
Older
sys.usr-koeki / share / qexam / qexam.rb
@HIROSE Yuuji HIROSE Yuuji on 16 Jan 2022 4 KB quiz JD version added
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'csv'
require 'sequel'
require 'etc'
user = ENV["QEUSER"] || Etc.getlogin
gecos = ""
tbl = "exam"
cls=""
timeout=30
hostname=(`hostname`||"local").sub(/\..*/, "").chomp
showrank = false
mailto	 = false
while /^-([a-z])/i =~ ARGV[0]
  ARGV.shift
  case $1
  when "m"				# -m email@add.ress.domain
    mailto = ARGV.shift
  when "M"				# Send Email to $0:h/MAILTO
    mailto = IO.readlines(File.expand_path("MAILTO", File.dirname($0))).join
    mailto.strip!
  when "c"
    cls = ARGV.shift.gsub(/\D/, "")
  when "t"
    timeout = ARGV.shift.to_i
  when "T"
    tbl = ARGV.shift.gsub(/[^a-zA-Z0-9_]/, "")
  when "r"
    showrank = true
  end
end

files = ARGV[0]&&ARGV[0]>"" ? ARGV : ["html", "css"]
point = tp = 0
mydir=File.dirname($0)
stime = Time.now
File.umask(2)

# Setup Database
# db = Sequel.connect("postgres://ruby_qexam@broy/ruby_qexam")
db = Sequel.connect("sqlite://./score.sq3")
sctbl, gtbl = db.from(tbl), db[:gecos]
db.transaction do
  db.create_table?(tbl) {
    String	:user
    Timestamp 	:time
    String	:host
    Integer	:score
    Integer	:mistake
    Numeric	:elapse
  }
  db.create_table?(:gecos) {
    String	:user
    String	:gecos
    unique	[:user]
  }
  begin
    gecos = Etc.getpwnam(user).gecos.to_s
  rescue
    gecos = user
  end
  gtbl.where(user: user).delete
  gtbl.insert(user: user, gecos: (gecos>'' ? gecos : user))
  if showrank && tbl
    i = 0
    if cls > ""
      # cls cannot have non-digit (cls.gsub(/\D/, "") was done)
      mems = "(SELECT \"user\" FROM lects WHERE scode LIKE \"#{cls}%\")"
    else
      mems = "(SELECT DISTINCT \"user\" from #{tbl})"
    end
    if false then
      r = db["SELECT c.user,gecos,coalesce(mine,9999) scr,coalesce(cnt,0)
	FROM (SELECT a.user,mine,cnt
	      FROM #{mems} a
		 LEFT JOIN
		   (SELECT \"user\", min(elapse) mine, count(elapse) cnt
		    FROM #{tbl} GROUP BY user) b
		 ON a.user=b.user ORDER by mine) c
	    JOIN gecos g ON c.user=g.user ORDER BY scr"]
    else
      migi = sctbl.select(Sequel.as(:user, :muser),
        Sequel.as(Sequel.function(:min, :elapse), :mine),
        Sequel.as(Sequel.function(:count, :elapse), :cnt)).group(:user)
      hidari = sctbl.select(:user).distinct
      # r = sctbl.select(:user).distinct.join_table(:left, :exam)
      # p migi.all, hidari.all
      # p hidari.join(migi)
      # Cannot use aliasing on lefter side of join
      r = hidari.left_join(migi, :muser => :user).natural_join(gtbl).
        select(:user, :gecos,
        Sequel.as(Sequel.function(:coalesce, :mine, 9999), :scr),
        Sequel.as(Sequel.function(:coalesce, :cnt, 0), :cnt)).order(:scr)
    end
    r.each do |row|
      u=row[:user]
      user==u and print("\e[33m")
      printf("%3d %-10s|%-32s%8.2f%8d\n", i+=1, *row.values)
      user==u and print("\e[m")
    end
    exit 0
  end
end

miss = 0
for f in files
  pt = 0
  printf("====> %sに関する問題です <====\n", f)
  qf = File.expand_path(f, mydir)
  d = CSV.read(qf+".txt", :col_sep => ":", :quote_char => "'",
               :encoding => 'utf-8')
  d = d.shuffle
  nQ = d.length
  start = Time.now
  limit = start + timeout*nQ
  tp += nQ
  while d[0]
    q = d[0][0]
    printf("[%d/%d] %s\n==> ", pt, nQ, d[0][1])
    a = STDIN.gets.strip
    ## ptn = %r,^/, =~ a ? a : Regexp.quote(a)
    ptn = Regexp.quote(a.gsub(/\s/, ""))
    if Regexp.new("^"+ ptn +"$", true) =~ q.gsub(/\s/, "") then
      puts "正解!"
      d.shift
      pt += 1
      sleep 1
    else
      miss += 1
      printf("不正解です。\n正解は %s です。\n(Enterで次に進む)", q)
      STDIN.gets
    end
    if Time.now > limit then
      puts("時間切れです。")
      break
    end
    print("\e[2J\e[1;1H")
    d.shuffle!
  end
  point += pt
end
etime	= Time.now
now	= Time.at(etime.to_i)
elapse	= etime-stime
el2	= elapse.round(2)

sctbl.insert({
    user: user, time: now, host: hostname, score: point,
    mistake: miss, elapse: el2})
trial = sctbl.where(user: user).count
rank  = sctbl.where(user: user).where(Sequel.expr(:elapse) < el2).count
		# or .where{elapse() < el2}
		# or .where{self.elapse < el2}
qname = files.join("+")

printf("問題: %s - ", qname)
printf("%d回中 %d番目の記録\n", trial, rank+1)
printf("得点: %d/%d\n", point, tp)
printf("時間: %.2f杪\n", elapse)
printf("ミス: %d回\n", miss)

db.disconnect
if mailto
  open("| mail -s \"quiz-record of #{qname} by #{gecos}\" #{mailto}", "w") do |m|
    m.print(<<~EOF)
	#{user},#{point},#{elapse}
	EOF
  end
end