toychooser 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []