class Chooser

Copyright 2014 Maxine Red <maxine@furfind.net>

This file is part of toy-chooser.

toy-chooser is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

toy-chooser is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with toy-chooser. If not, see <www.gnu.org/licenses/>.

Public Class Methods

new() click to toggle source
# File lib/chooser.rb, line 28
def initialize
  @db_file = File.expand_path("~/.toys.sqlite")
  if !File.exist?(@db_file) then
    database_create
  end
end

Public Instance Methods

add(toys) click to toggle source

Add a toy to the 'owned' list. Toys are in the format sku/size.

# File lib/chooser.rb, line 44
def add(toys)
  toys.each do |toy|
    sku, size = toy.split("/")
    sku, size = SQLite3::Database.quote(sku), SQLite3::Database.quote(size.upcase)
    SQLite3::Database.new(@db_file) do |db|
      query   = "SELECT id, name FROM toys WHERE sku = '#{sku}'"
      query  += " AND size = '#{size}';"
      id, name = db.execute(query).flatten
      if !id then
        $stderr.puts "Unknown toy or size \"#{toy}\"."
        next
      end
      query  = "INSERT INTO owned (sku, size, toy_id) VALUES"
      query += " ('#{sku}','#{size}',#{id});"
      db.execute(query)
      puts "Added \"#{name}\" (#{size}) to owned list."
    end
  end
end
fetch_bd() click to toggle source
# File lib/manufacturer/bad-dragon.rb, line 21
def fetch_bd
  net = Net::HTTP.new("bad-dragon.com",80)
  toys = JSON.parser.new(net.get("/products/getproducts").body).parse
  toys.reject!{|c|c["cat_id"].to_i!=1}
  toys = toys.sort{|t1,t2|t1["release_date"].to_i <=> t2["release_date"].to_i}
  toys.map!{|c|c["link_url"].sub(/.+bad-dragon.com/,"")}
  toys.each do |t|
    body = net.get(t).body
    html = Nokogiri::HTML.parse(body)
    id = html.css("span[itemprop=productID]").first.text
    name = html.css("span[itemprop=model]").first.text
    sizes = html.css("table.divided th").map{|th|th.text}
    sizes.shift # Just drop the first item.
    table = html.css("table.divided tr").map do |tr|
      tr.css("td").map{|td|td.text}
    end
    table.shift
    sql_query = "INSERT INTO toys";
    sizes.count.times do |i|
      size = sizes[i][0,1].sub("E","XL")
      size = "XS" if sizes[i] == "Mini"
      query = Hash.new
      query.store("sku",id)
      query.store("name",SQLite3::Database.quote(name))
      query.store("size",size)
      price = html.css("#size_inputs label").map do |x|
        if x.text.match(/^#{sizes[i]}/i) then
          x.css("i").text.sub("$","").to_i
        end
      end.compact.first
      price = html.css("span[itemprop=price]").first.text.to_i if !price
      query.store("price",price)
      table.each do |t|
        next if t[0].match(/thinnest|smallest/i)
        # We look only for biggest measures.
        col = t[0].downcase.sub(/meter of /,"_").sub(/umference of /,"_")
        col.gsub!(" ","_")
        col.gsub!(/_\(.+\)/,"")
        col = remove_aliases(col)
        inches = (t[i+1].to_f*100).round.to_i
        query.store(col,inches)
      end
      if i == 0 then
        sql_query += " (#{query.keys.join(",")}) VALUES\n"
      end
      sql_query += " ('#{query.values.join("','")}'),\n"
    end
    sql_query[-2,2] = ";"
    SQLite3::Database.new(@db_file) do |db|
      begin
        db.execute(sql_query)
      rescue
        p sql_query
        raise
      end
    end
    puts "Updated \"#{name}\" successfully."
  end
end
list(toy_list="toys") click to toggle source

List existing toys.

# File lib/chooser.rb, line 103
def list(toy_list="toys")
  toy_list = toy_list.first
  toy_list = "toys" unless toy_list
  if !["toys","owned"].include?(toy_list) then
    $stderr.puts "No such list."
    abort
  end
  SQLite3::Database.new(@db_file) do |db|
    draw_list(db.execute("SELECT sku, size FROM #{toy_list};"))
  end
end
remove_aliases(column) click to toggle source
# File lib/manufacturer/bad-dragon.rb, line 80
def remove_aliases(column)
  column = column.sub("usuable","usable").sub("avg._","")
  column = column.sub(/(thickest|largest)_part_of_/,"")
  column = column.sub(/_base/,"")
  # Other common aliases
  column = column.sub("tip","head").sub("middle","shaft").sub("bottom","knot")
  column = column.sub("shaft_ridges","knot").sub("medial_ring","knot")
  column = column.sub("bulb","knot")
  return column
end
show(toys) click to toggle source

Show measurements of toys.

# File lib/chooser.rb, line 64
def show(toys)
  toys.each do |toy|
    sku, size = toy.split("/")
    sku, size = SQLite3::Database.quote(sku), SQLite3::Database.quote(size.to_s.upcase)
    SQLite3::Database.new(@db_file) do |db|
      query  = "SELECT sku, size, price, dia_head, dia_shaft, dia_knot, "
      query += "circ_head, circ_shaft, circ_knot, usable_length, "
      query += "total_length FROM toys WHERE sku = '#{sku}';"
      draw_show(db.execute(query))
    end
  end
end
suggest(noargs=[]) click to toggle source

Suggests toys for future purchases guessed from what is owned already.

# File lib/chooser.rb, line 77
def suggest(noargs=[])
  i, sizes = 0, Array.new(7) {0}
  ranges = [0.5, 0.4, 0.3, 0.6, 0.5, 0.4, 0.9]
  cols = ["dia_head","dia_shaft","dia_knot","circ_head","circ_shaft",
          "circ_knot","usable_length"]
  SQLite3::Database.new(@db_file) do |db|
    query  = "SELECT #{cols.join(", ")} FROM toys, owned "
    query += "WHERE toys.id = owned.toy_id;"
    db.execute(query) do |row|
      row.each_with_index do |c,id|
        sizes[id] += c.to_i
      end
      i += 1
    end
    sizes = sizes.map.with_index do |x,id|
      avg = (x/i.to_f).round.to_i
      line  = "((#{cols[id]} >= #{(avg*(1-ranges[id])).round.to_i} "
      line += "AND #{cols[id]} <= #{(avg*(1+ranges[id])).round.to_i}) "
      line += "OR #{cols[id]} IS NULL)"
      line
    end
    query  = "SELECT sku, size FROM toys WHERE #{sizes.join(" AND ")};"
    draw_list(db.execute(query))
  end
end
update(manu) click to toggle source

Update database for given manufacturer.

# File lib/chooser.rb, line 35
def update(manu)
  case manu.first
  when "bad-dragon" then fetch_bd
  else
    $stderr.puts "Manufacturer unkown.","Known manufacturers are: bad-dragon"
    abort
  end
end

Private Instance Methods

database_create() click to toggle source
# File lib/chooser.rb, line 166
def database_create
  SQLite3::Database.new(@db_file) do |db|
    db.execute("CREATE TABLE toys (
               id INTEGER PRIMARY KEY,
               sku VARCHAR(127),
               name VARCHAR(255),
               size VARCHAR(3),
               price INTEGER,
               dia_head INTEGER,
               dia_shaft INTEGER,
               dia_knot INTEGER,
               circ_head INTEGER,
               circ_shaft INTEGER,
               circ_knot INTEGER,
               usable_length INTEGER,
               total_length INTEGER
              );")
    db.execute("CREATE TABLE owned (
               id INTEGER PRIMARY KEY,
               sku VARCHAR(127),
               size VARCHAR(3),
               toy_id INTEGER REFERENCES toys(id)
               );")
  end
end
draw_list(sql_list) click to toggle source
# File lib/chooser.rb, line 115
def draw_list(sql_list)
  toys = Hash.new
  sizes = ["XS","S ","M ","L ","XL","O "]
  deli = "+-"+"-"*15+"-+"+"----+"*sizes.count+""+"\n"
  print deli
  puts "| #{" "*15} | #{sizes.join(" | ")} |"
  print deli
  sql_list.each do |t|
    if !toys.has_key?(t[0]) then
      toys.store(t[0],[t[1]])
    else
      toys[t[0]] << t[1]
    end
  end
  toys.each do |k,v|
    cols = sizes.map do |c|
      v.include?(c.sub(" ","")) ? c : "  "
    end
    puts "| #{k.pad(15)} | #{cols.join(" | ")} |"
  end
  print deli
end
draw_show(sql_list) click to toggle source
# File lib/chooser.rb, line 137
def draw_show(sql_list)
  toys = Hash.new
  sizes = ["  XS   ","   S   ","   M   ","   L   ","  XL   ","   O   "]
  measurements = ["Price", "Dia. of head", "Dia. of shaft",
                  "Dia. of speccial", "Circ. of head",
                  "Circ. of shaft", "Circ. of special",
                  "Usable length", "Total length"]
  deli = "+-"+"-"*15+"-+"+"---------+"*sizes.count+""+"\n"
  print deli
  puts "| #{sql_list.first.first.pad(15)} | #{sizes.join(" | ")} |"
  print deli
  sql_list.map{|c|c.shift;c}.each do |t|
    toys.store(t.shift,t)
  end
  measurements.each_with_index do |m,id|
    cols = sizes.map do |c|
      c.gsub!(" ","")
      toys[c] ? toys[c][id].to_i : 0
    end
    if m == "Price" then
      cols.map!{|c|c.pad(4," ").sub(/\s(\d)/,"$\\1")+".00"}
    else
      cols.map!{|c|(c/100.0).pad(7," ")}
    end
    puts "| #{m.pad(15)} | #{cols.join(" | ")} |"
    print deli if m == "Price"
  end
  print deli
end