toychooser 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/bin/toychooser ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ =begin
4
+ Copyright 2014 Maxine Red <maxine@furfind.net>
5
+
6
+ This file is part of toy-chooser.
7
+
8
+ toy-chooser is free software: you can redistribute it and/or modify
9
+ it under the terms of the GNU General Public License as published by
10
+ the Free Software Foundation, either version 3 of the License, or
11
+ (at your option) any later version.
12
+
13
+ toy-chooser is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License
19
+ along with toy-chooser. If not, see <http://www.gnu.org/licenses/>.
20
+ =end
21
+
22
+ $:.unshift File.dirname(__FILE__)+"/../lib"
23
+ require "toychooser"
24
+
25
+ commands = ["add","remove","update","list","suggest", "show"]
26
+
27
+ args = ARGV
28
+ options = args.grep(/^-\w$/)
29
+ options.each do |opt|
30
+ case opt
31
+ when "-h" then
32
+ puts "usage:",
33
+ "toychooser -[h|v|V] {#{commands.join("|")}} [TOYDATA]",
34
+ "-h\t\t\tDisplay this help message",
35
+ "-v\t\t\tIncrease verbosity. Can be used more than once.",
36
+ "-V\t\t\tDisplay version information."
37
+ exit
38
+ when "-v" then
39
+ when "-V" then
40
+ puts "#{Toychooser::Name} v#{Toychooser::Version}"
41
+ exit
42
+ else
43
+ $stderr.puts "Option #{opt} unkown."
44
+ abort
45
+ end
46
+ end
47
+
48
+ command = args.grep(/^(#{commands.join("|")})$/).first.to_sym
49
+
50
+ args.reject!{|x|x.match(/^-\w|(#{commands.join("|")})$/)}
51
+
52
+ chooser = Chooser.new
53
+ chooser.__send__(command,args)
data/lib/chooser.rb ADDED
@@ -0,0 +1,191 @@
1
+ =begin
2
+ Copyright 2014 Maxine Red <maxine@furfind.net>
3
+
4
+ This file is part of toy-chooser.
5
+
6
+ toy-chooser is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ toy-chooser is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with toy-chooser. If not, see <http://www.gnu.org/licenses/>.
18
+ =end
19
+
20
+ require "sqlite3"
21
+ require "net/http"
22
+ require "json"
23
+ require "nokogiri"
24
+
25
+ require "manufacturer/bad-dragon"
26
+
27
+ class Chooser
28
+ def initialize
29
+ @db_file = File.expand_path("~/.toys.sqlite")
30
+ if !File.exist?(@db_file) then
31
+ database_create
32
+ end
33
+ end
34
+ # Update database for given manufacturer.
35
+ def update(manu)
36
+ case manu.first
37
+ when "bad-dragon" then fetch_bd
38
+ else
39
+ $stderr.puts "Manufacturer unkown.","Known manufacturers are: bad-dragon"
40
+ abort
41
+ end
42
+ end
43
+ # Add a toy to the 'owned' list. Toys are in the format sku/size.
44
+ def add(toys)
45
+ toys.each do |toy|
46
+ sku, size = toy.split("/")
47
+ sku, size = SQLite3::Database.quote(sku), SQLite3::Database.quote(size.upcase)
48
+ SQLite3::Database.new(@db_file) do |db|
49
+ query = "SELECT id, name FROM toys WHERE sku = '#{sku}'"
50
+ query += " AND size = '#{size}';"
51
+ id, name = db.execute(query).flatten
52
+ if !id then
53
+ $stderr.puts "Unknown toy or size \"#{toy}\"."
54
+ next
55
+ end
56
+ query = "INSERT INTO owned (sku, size, toy_id) VALUES"
57
+ query += " ('#{sku}','#{size}',#{id});"
58
+ db.execute(query)
59
+ puts "Added \"#{name}\" (#{size}) to owned list."
60
+ end
61
+ end
62
+ end
63
+ # Show measurements of toys.
64
+ def show(toys)
65
+ toys.each do |toy|
66
+ sku, size = toy.split("/")
67
+ sku, size = SQLite3::Database.quote(sku), SQLite3::Database.quote(size.to_s.upcase)
68
+ SQLite3::Database.new(@db_file) do |db|
69
+ query = "SELECT sku, size, price, dia_head, dia_shaft, dia_knot, "
70
+ query += "circ_head, circ_shaft, circ_knot, usable_length, "
71
+ query += "total_length FROM toys WHERE sku = '#{sku}';"
72
+ draw_show(db.execute(query))
73
+ end
74
+ end
75
+ end
76
+ # Suggests toys for future purchases guessed from what is owned already.
77
+ def suggest(noargs=[])
78
+ i, sizes = 0, Array.new(7) {0}
79
+ ranges = [0.5, 0.4, 0.3, 0.6, 0.5, 0.4, 0.9]
80
+ cols = ["dia_head","dia_shaft","dia_knot","circ_head","circ_shaft",
81
+ "circ_knot","usable_length"]
82
+ SQLite3::Database.new(@db_file) do |db|
83
+ query = "SELECT #{cols.join(", ")} FROM toys, owned "
84
+ query += "WHERE toys.id = owned.toy_id;"
85
+ db.execute(query) do |row|
86
+ row.each_with_index do |c,id|
87
+ sizes[id] += c.to_i
88
+ end
89
+ i += 1
90
+ end
91
+ sizes = sizes.map.with_index do |x,id|
92
+ avg = (x/i.to_f).round.to_i
93
+ line = "((#{cols[id]} >= #{(avg*(1-ranges[id])).round.to_i} "
94
+ line += "AND #{cols[id]} <= #{(avg*(1+ranges[id])).round.to_i}) "
95
+ line += "OR #{cols[id]} IS NULL)"
96
+ line
97
+ end
98
+ query = "SELECT sku, size FROM toys WHERE #{sizes.join(" AND ")};"
99
+ draw_list(db.execute(query))
100
+ end
101
+ end
102
+ # List existing toys.
103
+ def list(toy_list="toys")
104
+ toy_list = toy_list.first
105
+ toy_list = "toys" unless toy_list
106
+ if !["toys","owned"].include?(toy_list) then
107
+ $stderr.puts "No such list."
108
+ abort
109
+ end
110
+ SQLite3::Database.new(@db_file) do |db|
111
+ draw_list(db.execute("SELECT sku, size FROM #{toy_list};"))
112
+ end
113
+ end
114
+ private
115
+ def draw_list(sql_list)
116
+ toys = Hash.new
117
+ sizes = ["XS","S ","M ","L ","XL","O "]
118
+ deli = "+-"+"-"*15+"-+"+"----+"*sizes.count+""+"\n"
119
+ print deli
120
+ puts "| #{" "*15} | #{sizes.join(" | ")} |"
121
+ print deli
122
+ sql_list.each do |t|
123
+ if !toys.has_key?(t[0]) then
124
+ toys.store(t[0],[t[1]])
125
+ else
126
+ toys[t[0]] << t[1]
127
+ end
128
+ end
129
+ toys.each do |k,v|
130
+ cols = sizes.map do |c|
131
+ v.include?(c.sub(" ","")) ? c : " "
132
+ end
133
+ puts "| #{k.pad(15)} | #{cols.join(" | ")} |"
134
+ end
135
+ print deli
136
+ end
137
+ def draw_show(sql_list)
138
+ toys = Hash.new
139
+ sizes = [" XS "," S "," M "," L "," XL "," O "]
140
+ measurements = ["Price", "Dia. of head", "Dia. of shaft",
141
+ "Dia. of speccial", "Circ. of head",
142
+ "Circ. of shaft", "Circ. of special",
143
+ "Usable length", "Total length"]
144
+ deli = "+-"+"-"*15+"-+"+"---------+"*sizes.count+""+"\n"
145
+ print deli
146
+ puts "| #{sql_list.first.first.pad(15)} | #{sizes.join(" | ")} |"
147
+ print deli
148
+ sql_list.map{|c|c.shift;c}.each do |t|
149
+ toys.store(t.shift,t)
150
+ end
151
+ measurements.each_with_index do |m,id|
152
+ cols = sizes.map do |c|
153
+ c.gsub!(" ","")
154
+ toys[c] ? toys[c][id].to_i : 0
155
+ end
156
+ if m == "Price" then
157
+ cols.map!{|c|c.pad(4," ").sub(/\s(\d)/,"$\\1")+".00"}
158
+ else
159
+ cols.map!{|c|(c/100.0).pad(7," ")}
160
+ end
161
+ puts "| #{m.pad(15)} | #{cols.join(" | ")} |"
162
+ print deli if m == "Price"
163
+ end
164
+ print deli
165
+ end
166
+ def database_create
167
+ SQLite3::Database.new(@db_file) do |db|
168
+ db.execute("CREATE TABLE toys (
169
+ id INTEGER PRIMARY KEY,
170
+ sku VARCHAR(127),
171
+ name VARCHAR(255),
172
+ size VARCHAR(3),
173
+ price INTEGER,
174
+ dia_head INTEGER,
175
+ dia_shaft INTEGER,
176
+ dia_knot INTEGER,
177
+ circ_head INTEGER,
178
+ circ_shaft INTEGER,
179
+ circ_knot INTEGER,
180
+ usable_length INTEGER,
181
+ total_length INTEGER
182
+ );")
183
+ db.execute("CREATE TABLE owned (
184
+ id INTEGER PRIMARY KEY,
185
+ sku VARCHAR(127),
186
+ size VARCHAR(3),
187
+ toy_id INTEGER REFERENCES toys(id)
188
+ );")
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,88 @@
1
+ =begin
2
+ Copyright 2014 Maxine Red <maxine@furfind.net>
3
+
4
+ This file is part of toy-chooser.
5
+
6
+ toy-chooser is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ toy-chooser is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with toy-chooser. If not, see <http://www.gnu.org/licenses/>.
18
+ =end
19
+
20
+ class Chooser
21
+ def fetch_bd
22
+ net = Net::HTTP.new("bad-dragon.com",80)
23
+ toys = JSON.parser.new(net.get("/products/getproducts").body).parse
24
+ toys.reject!{|c|c["cat_id"].to_i!=1}
25
+ toys.map!{|c|c["link_url"].sub(/.+bad-dragon.com/,"")}
26
+ toys.each do |t|
27
+ body = net.get(t).body
28
+ File.open("cole.html","w"){|f|f.print body}
29
+ html = Nokogiri::HTML.parse(body)
30
+ id = html.css("span[itemprop=productID]").first.text
31
+ name = html.css("span[itemprop=model]").first.text
32
+ sizes = html.css("table.divided th").map{|th|th.text}
33
+ sizes.shift # Just drop the first item.
34
+ table = html.css("table.divided tr").map do |tr|
35
+ tr.css("td").map{|td|td.text}
36
+ end
37
+ table.shift
38
+ sql_query = "INSERT INTO toys";
39
+ sizes.count.times do |i|
40
+ size = sizes[i][0,1].sub("E","XL")
41
+ size = "XS" if sizes[i] == "Mini"
42
+ query = Hash.new
43
+ query.store("sku",id)
44
+ query.store("name",SQLite3::Database.quote(name))
45
+ query.store("size",size)
46
+ price = html.css("#size_inputs label").map do |x|
47
+ if x.text.match(/^#{sizes[i]}/i) then
48
+ x.css("i").text.sub("$","").to_i
49
+ end
50
+ end.compact.first
51
+ price = html.css("span[itemprop=price]").first.text.to_i if !price
52
+ query.store("price",price)
53
+ table.each do |t|
54
+ next if t[0].match(/thinnest|smallest/i)
55
+ # We look only for biggest measures.
56
+ col = t[0].downcase.sub(/meter of /,"_").sub(/umference of /,"_")
57
+ col.gsub!(" ","_")
58
+ col.gsub!(/_\(.+\)/,"")
59
+ inches = (t[i+1].to_f*100).round.to_i
60
+ query.store(col,inches)
61
+ end
62
+ if i == 0 then
63
+ sql_query += " (#{query.keys.join(",")}) VALUES\n"
64
+ end
65
+ sql_query += " ('#{query.values.join("','")}'),\n"
66
+ end
67
+ sql_query[-2,2] = ";"
68
+ SQLite3::Database.new(@db_file) do |db|
69
+ begin
70
+ db.execute(sql_query)
71
+ rescue
72
+ p sql_query
73
+ raise
74
+ end
75
+ end
76
+ puts "Updated \"#{name}\" successfully."
77
+ end
78
+ end
79
+ def remove_aliases(column)
80
+ column = column.sub("usuable","usable").sub("avg._","")
81
+ column = column.sub(/(thickest|largest)_part_of_/,"")
82
+ column = column.sub(/_(base)/,"")
83
+ # Other common aliases
84
+ column = column.sub("tip","head").sub("middle","shaft","bottom","knot")
85
+ column = column.sub("shaft_ridges","knot").sub("medial_ring","knot")
86
+ column = column.sub("bulb","knot")
87
+ end
88
+ end
@@ -0,0 +1,31 @@
1
+ =begin
2
+ Copyright 2014 Maxine Red <maxine@furfind.net>
3
+
4
+ This file is part of toy-chooser.
5
+
6
+ toy-chooser is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ toy-chooser is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with toy-chooser. If not, see <http://www.gnu.org/licenses/>.
18
+ =end
19
+
20
+ class Float
21
+ def pad(c,s=0)
22
+ float = self.to_s
23
+ float.sub!(/\.\d$/,"\\00")
24
+ "#{s}"*(c-float.length)+float
25
+ end
26
+ end
27
+ class Fixnum
28
+ def pad(c,s=0)
29
+ "#{s}"*(c-self.to_s.length)+self.to_s
30
+ end
31
+ end
@@ -0,0 +1,87 @@
1
+ =begin
2
+ Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
3
+
4
+ This file is part of rubyhexagon.
5
+
6
+ rubyhexagon is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ rubyhexagon is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with rubyhexagon. If not, see <http://www.gnu.org/licenses/>.
18
+ =end
19
+
20
+ require "json"
21
+
22
+ class String
23
+ # Make json parsing a lot easier and less to write.
24
+ def parse
25
+ JSON.parser.new(self).parse
26
+ end
27
+ def to_ascii
28
+ self.gsub("_"," ").encode("us-ascii", :invalid => :replace, :undef => :replace, :replace => "")
29
+ end
30
+ # Remove all tags inside of a string.
31
+ def clean
32
+ self.gsub(/<.+?>/,"")
33
+ end
34
+ # Trim or expand a string to a certain length.
35
+ def pad(c,s=" ")
36
+ if self.length > c then
37
+ self[0,c-3]+"..."
38
+ elsif self.length < c then
39
+ self+s.to_s*(c-self.length)
40
+ else
41
+ self
42
+ end
43
+ end
44
+ # Make a string bold and colorful, with colors a user can understand. Not
45
+ # those cryptic numbers.
46
+ def bold(color=nil)
47
+ c = case color
48
+ when "black" then "\e[30;1m"
49
+ when "red" then "\e[31;1m"
50
+ when "green" then "\e[32;1m"
51
+ when "yellow" then "\e[33;1m"
52
+ when "blue" then "\e[34;1m"
53
+ when "purple" then "\e[35;1m"
54
+ when "cyan" then "\e[36;1m"
55
+ else "\e[37;1m"
56
+ end
57
+ c+self+"\e[0m"
58
+ end
59
+ # Same as bold, but just make a string colorful.
60
+ def color(color=nil)
61
+ c = case color
62
+ when "black" then "\e[30;0m"
63
+ when "red" then "\e[31;0m"
64
+ when "green" then "\e[32;0m"
65
+ when "yellow" then "\e[33;0m"
66
+ when "blue" then "\e[34;0m"
67
+ when "purple" then "\e[35;0m"
68
+ when "cyan" then "\e[36;0m"
69
+ else "\e[37;0m"
70
+ end
71
+ c+self+"\e[0m"
72
+ end
73
+ # indent a string and all lines that belong to it
74
+ def indent(indent)
75
+ s = self.split("#$/")
76
+ " "*indent+s.join("#$/"+" "*indent)
77
+ end
78
+ # It is just convenient to have a custom to_bool function. Only "true" gets
79
+ # turned into true, anything else is false.
80
+ def to_bool
81
+ if self.downcase == "true" then
82
+ true
83
+ else
84
+ false
85
+ end
86
+ end
87
+ end
data/lib/toychooser.rb ADDED
@@ -0,0 +1,27 @@
1
+ =begin
2
+ Copyright 2014 Maxine Red <maxine@furfind.net>
3
+
4
+ This file is part of toy-chooser.
5
+
6
+ toy-chooser is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ toy-chooser is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with toy-chooser. If not, see <http://www.gnu.org/licenses/>.
18
+ =end
19
+
20
+ require "standard/string"
21
+ require "standard/int"
22
+ require "chooser.rb"
23
+
24
+ module Toychooser
25
+ Name = "toychooser"
26
+ Version = "1.0.0"
27
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: toychooser
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Maxine Red
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-06-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pg
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.18'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.18'
30
+ description: A simple little script that takes in data from toy manufacturers, your
31
+ current toy collection and suggests new toys for you.
32
+ email: maxine@furfind.net
33
+ executables:
34
+ - toychooser
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - lib/toychooser.rb
39
+ - lib/standard/int.rb
40
+ - lib/standard/string.rb
41
+ - lib/chooser.rb
42
+ - lib/manufacturer/bad-dragon.rb
43
+ - bin/toychooser
44
+ homepage: https://github.com/maxine-red/toy-chooser
45
+ licenses:
46
+ - GPL-3.0
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: 1.9.2
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.23
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: A toy chooser for adult toy manufacturers
69
+ test_files: []