pyradise 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/bin/pyradise +14 -5
- data/lib/pyradise/cli.rb +164 -0
- data/lib/pyradise/i18n.rb +28 -0
- data/lib/pyradise/product.rb +3 -1
- data/lib/pyradise.rb +24 -135
- data/pyradise.gemspec +7 -4
- metadata +4 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/bin/pyradise
CHANGED
@@ -15,7 +15,6 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
15
15
|
require 'pyradise'
|
16
16
|
include Pyradise
|
17
17
|
|
18
|
-
OPTIONS = {}
|
19
18
|
MANDATORY_OPTIONS = %w( )
|
20
19
|
|
21
20
|
parser = OptionParser.new do |opts|
|
@@ -26,11 +25,21 @@ Usage: #{File.basename($0)} command [args]
|
|
26
25
|
|
27
26
|
Options are:
|
28
27
|
|
29
|
-
fetch
|
30
|
-
list/search
|
31
|
-
|
28
|
+
fetch - Start/refresh database.
|
29
|
+
list/search - List and filter items
|
30
|
+
-o --order - Order by: name, price, store.
|
31
|
+
info/show - More info about an item
|
32
|
+
clear - Wipe out the database
|
33
|
+
|
34
|
+
Common usage:
|
35
|
+
|
36
|
+
pyradise fetch
|
37
|
+
" list mb i7 - Searches for Intel i7 mobos.
|
38
|
+
" list mem -o name - Searches for 'mem' ordered by name.
|
39
|
+
" info <sid> - Show price history of the item.
|
32
40
|
|
33
41
|
BANNER
|
42
|
+
opts.on("-o", "--order ORDER", String, "Table order" ) { |order| Options[:order] = order }
|
34
43
|
opts.separator ""
|
35
44
|
opts.parse!(ARGV)
|
36
45
|
end
|
@@ -39,5 +48,5 @@ if ARGV.empty?
|
|
39
48
|
puts parser.banner
|
40
49
|
exit
|
41
50
|
else
|
42
|
-
Pyradise.run! ARGV
|
51
|
+
Pyradise::Cli.run! ARGV
|
43
52
|
end
|
data/lib/pyradise/cli.rb
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
module Pyradise
|
2
|
+
class Cli
|
3
|
+
|
4
|
+
class << self
|
5
|
+
include I18n
|
6
|
+
|
7
|
+
def run! comm
|
8
|
+
if respond_to? comm[0]
|
9
|
+
send(*comm)
|
10
|
+
else
|
11
|
+
puts "Can't do that..."
|
12
|
+
exit
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def fetch
|
17
|
+
create from_stores
|
18
|
+
end
|
19
|
+
|
20
|
+
def list(*query)
|
21
|
+
p query
|
22
|
+
t = Time.now
|
23
|
+
w = terminal_size[0]
|
24
|
+
s = w - 36
|
25
|
+
puts "\n#{t(:search)} #{'"' + green(query.join(" ")) + '"' if query[0]}... #{t(:order)}: #{green(Options[:order])}"
|
26
|
+
# puts "_" * w
|
27
|
+
puts
|
28
|
+
prods = Product.filter(:name.like("%#{query.join("%")}%")).order(Options[:order])
|
29
|
+
prods.each_with_index do |prod, i|
|
30
|
+
name = prod.name.length > s ? prod.name[0..s] + ".. " : prod.name + " "
|
31
|
+
for q in query
|
32
|
+
b = i % 2 == 0 ? "\e[0m\e[2m" : "\e[0m\e[0m"
|
33
|
+
replaces = name.scan(/#{q}/i).length
|
34
|
+
name.gsub!(/#{q}/i, "\e[32m\\0#{b}")
|
35
|
+
name += "." until name.length > w - 23
|
36
|
+
end
|
37
|
+
total_price = prod.price * Options[:rate] * Options[:tax]
|
38
|
+
out = sprintf("%-6s | %-6s | %-#{s}s %-3d | R$ %s", prod.store, prod.sid, name, prod.price, yellow(total_price.to_i))
|
39
|
+
|
40
|
+
puts i % 2 == 0 ? bold(out) : out
|
41
|
+
end
|
42
|
+
puts "_" * w
|
43
|
+
puts red("Total: #{prods.all.length} (#{Time.now - t}s)")
|
44
|
+
end
|
45
|
+
alias :search :list
|
46
|
+
|
47
|
+
def info(sid=nil)
|
48
|
+
if !sid
|
49
|
+
puts red("Use: pyradise view <ID>")
|
50
|
+
elsif !prod = Product.filter(:sid => sid.to_i).first
|
51
|
+
puts yellow("Product not found.")
|
52
|
+
else
|
53
|
+
w = terminal_size[0] - 20
|
54
|
+
prices = prod.prices
|
55
|
+
max = prices.values.max.to_f
|
56
|
+
prices.keys.sort.each do |k|
|
57
|
+
rel = "=" * (prices[k] / max * w)
|
58
|
+
puts "#{Time.at(k).strftime('%M/%d')} #{rel}| #{prices[k]}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
alias :show :info
|
63
|
+
|
64
|
+
#
|
65
|
+
# Wipe all data
|
66
|
+
def clear
|
67
|
+
print t(:delete)
|
68
|
+
return puts unless read_char =~ /y/
|
69
|
+
Product.dataset.delete
|
70
|
+
puts "\nDB cleared."
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def read_char
|
76
|
+
system "stty raw -echo"
|
77
|
+
out = STDIN.getc
|
78
|
+
RUBY_PLATFORM =~ /1.9/ ? out : out.chr
|
79
|
+
ensure
|
80
|
+
system "stty -raw echo"
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Create records from txt files
|
85
|
+
def create txts
|
86
|
+
products = []
|
87
|
+
for txt in txts
|
88
|
+
print "Parsing #{txt[:store]}..."
|
89
|
+
c = Product.all.length
|
90
|
+
parse(txt[:txt], txt[:delimiter]).each do |t|
|
91
|
+
next if t[:price] == 0
|
92
|
+
create_product(t, txt[:store].to_s)
|
93
|
+
end
|
94
|
+
puts "#{Product.all.length - c} #{t(:created)}."
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def create_product(t, store)
|
99
|
+
if prod = Product.filter(:name => t[:name], :store => store).first
|
100
|
+
prod.new_price!(t[:price]) if t[:price] != prod.price
|
101
|
+
else
|
102
|
+
Product.create(t.merge(:store => store))
|
103
|
+
end
|
104
|
+
rescue => e
|
105
|
+
puts "SQLITE Err => #{e}"
|
106
|
+
p t[:name]
|
107
|
+
puts t.inspect
|
108
|
+
end
|
109
|
+
|
110
|
+
def from_stores
|
111
|
+
stores = []
|
112
|
+
for store in YAML.load(File.new(File.dirname(__FILE__) + '/../stores.yml'))[:stores]
|
113
|
+
data = {}
|
114
|
+
data[:store] = store[0]
|
115
|
+
data[:delimiter] = store[1][:delimiter]
|
116
|
+
|
117
|
+
# Open URL
|
118
|
+
next unless data[:txt] = fetch_store(store)
|
119
|
+
|
120
|
+
# Open local file to write
|
121
|
+
open("#{HOME}#{store[0]}-#{Time.now.to_i}.txt", "wb") do |dump|
|
122
|
+
dump.write(data[:txt].gsub(/\t/, ""))
|
123
|
+
end
|
124
|
+
puts "Store #{store[0]} dumped."
|
125
|
+
stores << data
|
126
|
+
end
|
127
|
+
stores
|
128
|
+
end
|
129
|
+
|
130
|
+
def fetch_store(store)
|
131
|
+
open(store[1][:txt]).read
|
132
|
+
rescue => e
|
133
|
+
puts "#{store[0]} offline: #{e}.".capitalize
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
|
137
|
+
def parse(txt, del)
|
138
|
+
products = []
|
139
|
+
txt.each_line do |l|
|
140
|
+
sid, *info = l.split(del)
|
141
|
+
next if info.length < 2
|
142
|
+
price = info.delete_at(-1).strip.to_i
|
143
|
+
next if price.nil? || price.zero?
|
144
|
+
products << { :sid => sid.strip, :name => info.join("").strip.gsub(/\.{2,}/, ""), :price => price }
|
145
|
+
end
|
146
|
+
products
|
147
|
+
end
|
148
|
+
|
149
|
+
# Get terminal size to fit the table nicely.
|
150
|
+
# From highliner.
|
151
|
+
def terminal_size
|
152
|
+
`stty size`.split.map { |x| x.to_i }.reverse
|
153
|
+
end
|
154
|
+
|
155
|
+
def red(txt); "\e[31m#{txt}\e[0m"; end
|
156
|
+
def green(txt); "\e[32m#{txt}\e[0m"; end
|
157
|
+
def yellow(txt); "\e[33m#{txt}\e[0m"; end
|
158
|
+
def bold(txt); "\e[2m#{txt}\e[0m"; end
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Pyradise
|
2
|
+
module I18n
|
3
|
+
I = {
|
4
|
+
:en_us =>
|
5
|
+
{
|
6
|
+
:search => "Searching for",
|
7
|
+
:order => "Ordered by",
|
8
|
+
:delete => "About to delete all the records. Sure? [y/N] ",
|
9
|
+
:created => "products created"
|
10
|
+
},
|
11
|
+
:pt_br =>
|
12
|
+
{
|
13
|
+
:search => "Procurando por",
|
14
|
+
:order => "Ordenado por",
|
15
|
+
:delete => "Deletar todo o banco? [y/N] ",
|
16
|
+
:created => "produtos criados"
|
17
|
+
}
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
}
|
22
|
+
|
23
|
+
def t(text)
|
24
|
+
I[Options[:lang].to_sym][text.to_sym]
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/pyradise/product.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
module Pyradise
|
2
|
+
class Product < Sequel::Model
|
2
3
|
|
3
4
|
def before_save
|
4
5
|
phist = { Time.now.to_i => price }
|
@@ -13,4 +14,5 @@ class Product < Sequel::Model
|
|
13
14
|
self.update(:prices => Marshal.dump(prices ? prices.merge({Time.now.to_i => np}) : np))
|
14
15
|
end
|
15
16
|
|
17
|
+
end
|
16
18
|
end
|
data/lib/pyradise.rb
CHANGED
@@ -1,145 +1,34 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'open-uri'
|
3
2
|
require 'sequel'
|
4
3
|
|
5
|
-
#TODO: def init
|
6
|
-
HOME = ENV['HOME'] + "/.pyradise"
|
7
|
-
unless File.exists? HOME
|
8
|
-
FileUtils.mkdir_p HOME
|
9
|
-
conf = open(HOME + "/conf.yml", "wb")
|
10
|
-
conf.write(":rate: 2.0\n:tax: 1.3\n")
|
11
|
-
conf.close
|
12
|
-
end
|
13
|
-
|
14
|
-
DB = Sequel.connect("sqlite://#{HOME}/py.sqlite3")
|
15
|
-
require 'pyradise/product'
|
16
|
-
# require 'pyradise/stat'
|
17
|
-
|
18
|
-
unless DB.table_exists? :products
|
19
|
-
require 'pyradise/migrate'
|
20
|
-
CreatePyradise.apply DB, :up
|
21
|
-
end
|
22
|
-
|
23
4
|
module Pyradise
|
24
5
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def run! comm, opts
|
32
|
-
if respond_to? comm[0]
|
33
|
-
send(*comm)
|
34
|
-
else
|
35
|
-
puts "Can't do that..."
|
36
|
-
exit
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def fetch
|
41
|
-
create from_stores
|
42
|
-
end
|
43
|
-
|
44
|
-
def list(*query)
|
45
|
-
t = Time.now
|
46
|
-
w = terminal_size[0]
|
47
|
-
s = w - 35
|
48
|
-
puts "Searching #{'"' + query[0] + '"' if query[0]}... Order by: #{query[1] || 'name'}"
|
49
|
-
puts "_" * w
|
50
|
-
prods = Product.filter(:name.like("%#{query[0]}%")).order(query[1] ? query[1].to_sym : :name)
|
51
|
-
prods.each_with_index do |prod, i|
|
52
|
-
name = prod.name.length > s ? prod.name[0..s] + ".." : prod.name
|
53
|
-
out = sprintf("%-6s | %-5s | %-#{w-38}s %-3d | R$ %d", prod.store, prod.sid, name, prod.price, prod.price * RATE * TAX)
|
54
|
-
puts i % 2 == 0 ? bold(out) : out
|
55
|
-
end
|
56
|
-
puts "_" * w
|
57
|
-
puts green("Total: #{prods.all.length} (#{Time.now - t}s)")
|
58
|
-
end
|
59
|
-
alias :search :list
|
60
|
-
|
61
|
-
def info(sid=nil)
|
62
|
-
if !sid
|
63
|
-
puts red("Use: pyradise view <ID>")
|
64
|
-
elsif !prod = Product.filter(:sid => sid.to_i).first
|
65
|
-
puts yellow("Product not found.")
|
66
|
-
else
|
67
|
-
w = terminal_size[0] - 20
|
68
|
-
prices = prod.prices
|
69
|
-
max = prices.values.max.to_f
|
70
|
-
prices.keys.sort.each do |k|
|
71
|
-
rel = "=" * (prices[k] / max * w)
|
72
|
-
puts "#{Time.at(k).strftime('%M/%d')} #{rel}| #{prices[k]}"
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
alias :show :info
|
77
|
-
|
78
|
-
def clear
|
79
|
-
Product.dataset.delete
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
|
-
def create txts
|
85
|
-
products = []
|
86
|
-
for txt in txts
|
87
|
-
print "Parsing #{txt[:store]}..."
|
88
|
-
c = Product.all.length
|
89
|
-
parse(txt[:txt], txt[:delimiter]).each do |t|
|
90
|
-
next if t[:price] == 0
|
91
|
-
create_product(t, txt[:store].to_s)
|
92
|
-
end
|
93
|
-
puts "#{Product.all.length - c} products created."
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def create_product(t, store)
|
98
|
-
if prod = Product.filter(:name => t[:name], :store => store).first
|
99
|
-
prod.new_price!(t[:price]) if t[:price] != prod.price
|
100
|
-
else
|
101
|
-
Product.create(t.merge(:store => store))
|
102
|
-
end
|
103
|
-
rescue => e
|
104
|
-
puts "SQLITE Err => #{e}, #{t.inspect} - #{store} #{prod}"
|
105
|
-
end
|
6
|
+
HOME = ENV['HOME'] + "/.pyradise"
|
7
|
+
#TODO: def init
|
8
|
+
DB = Sequel.connect("sqlite://#{HOME}/py.sqlite3")
|
9
|
+
def locale
|
10
|
+
`locale | grep LANG`.scan(/LANG=(.*)\./)[0][0].downcase rescue "en_us"
|
11
|
+
end
|
106
12
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
dump.write(data[:txt])
|
116
|
-
dump.close
|
117
|
-
puts "Store #{store[0]} dumped... "
|
118
|
-
stores << data
|
119
|
-
end
|
120
|
-
stores
|
121
|
-
end
|
13
|
+
unless File.exists? HOME
|
14
|
+
FileUtils.mkdir_p HOME
|
15
|
+
conf = open(HOME + "/conf.yml", "wb")
|
16
|
+
conf.write(":rate: 1.8 # dollar conversion rate\n")
|
17
|
+
conf.write(":tax: 1.3 # transport tax\n")
|
18
|
+
conf.write(":lang: #{locale} # program language\n")
|
19
|
+
conf.close
|
20
|
+
end
|
122
21
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
products << { :sid => sid.strip, :name => info.join("").strip.gsub(/\.{2,}/, ""), :price => price }
|
131
|
-
end
|
132
|
-
products
|
133
|
-
end
|
22
|
+
# require 'pyradise/stat'
|
23
|
+
unless DB.table_exists? :products
|
24
|
+
require 'pyradise/migrate'
|
25
|
+
CreatePyradise.apply DB, :up
|
26
|
+
end
|
27
|
+
Defaults = {:rate => 1, :tax => 1.3, :order => :price, :lang => :en_us}
|
28
|
+
Options = Defaults.merge(YAML.load(File.new(HOME + "/conf.yml")))
|
134
29
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
end
|
30
|
+
require 'pyradise/product'
|
31
|
+
require 'pyradise/i18n'
|
32
|
+
require 'pyradise/cli'
|
139
33
|
|
140
|
-
def red(txt); "\e[31m#{txt}\e[0m"; end
|
141
|
-
def green(txt); "\e[32m#{txt}\e[0m"; end
|
142
|
-
def yellow(txt); "\e[33m#{txt}\e[0m"; end
|
143
|
-
def bold(txt); "\e[2m#{txt}\e[0m"; end
|
144
|
-
end
|
145
34
|
end
|
data/pyradise.gemspec
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{pyradise}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Marcos Piccinini"]
|
12
|
-
s.date = %q{2009-
|
12
|
+
s.date = %q{2009-12-28}
|
13
13
|
s.default_executable = %q{pyradise}
|
14
14
|
s.email = %q{x@nofxx.com}
|
15
15
|
s.executables = ["pyradise"]
|
@@ -26,6 +26,8 @@ Gem::Specification.new do |s|
|
|
26
26
|
"VERSION",
|
27
27
|
"bin/pyradise",
|
28
28
|
"lib/pyradise.rb",
|
29
|
+
"lib/pyradise/cli.rb",
|
30
|
+
"lib/pyradise/i18n.rb",
|
29
31
|
"lib/pyradise/migrate.rb",
|
30
32
|
"lib/pyradise/product.rb",
|
31
33
|
"lib/stores.yml",
|
@@ -59,3 +61,4 @@ Gem::Specification.new do |s|
|
|
59
61
|
s.add_dependency(%q<sequel>, [">= 0"])
|
60
62
|
end
|
61
63
|
end
|
64
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pyradise
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marcos Piccinini
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-12-28 00:00:00 -02:00
|
13
13
|
default_executable: pyradise
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -40,6 +40,8 @@ files:
|
|
40
40
|
- VERSION
|
41
41
|
- bin/pyradise
|
42
42
|
- lib/pyradise.rb
|
43
|
+
- lib/pyradise/cli.rb
|
44
|
+
- lib/pyradise/i18n.rb
|
43
45
|
- lib/pyradise/migrate.rb
|
44
46
|
- lib/pyradise/product.rb
|
45
47
|
- lib/stores.yml
|