goods 0.0.4 → 0.0.5
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/goods.gemspec +1 -1
- data/lib/goods/util.rb +120 -0
- data/lib/goods/version.rb +1 -1
- data/lib/goods/xml.rb +3 -1
- data/lib/goods.rb +1 -0
- data/spec/goods/util_spec.rb +114 -0
- data/spec/goods/xml_spec.rb +19 -8
- data/spec/goods_spec.rb +7 -2
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c0db8c61b2a8ee5ea477222bae0025d6ddf665c
|
4
|
+
data.tar.gz: 1f59093e307a69f02c11c7988824bfd90299afb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cad7b3a73d0d6d48c4e41bed36bc5d05170fb15954fafc9a5f98d6588867950c40f4240b978f785566097983d1fe162f5fda23aec93dd403b897beb342c2e5fe
|
7
|
+
data.tar.gz: 06c5c8baabc115059afdb01fbae2f870a78d01c86d9569c6ae58afd446c4be3921ed83ce5a5c9232c269852aca8af37f6db876f30dfc7dfbd56cd5d316ec5bfa
|
data/.gitignore
CHANGED
data/goods.gemspec
CHANGED
@@ -24,7 +24,7 @@ DESC
|
|
24
24
|
|
25
25
|
spec.add_development_dependency "bundler", "~> 1.3"
|
26
26
|
spec.add_development_dependency "rake"
|
27
|
-
spec.add_development_dependency "rspec", "~> 3.0.0.
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.0.0.beta2"
|
28
28
|
|
29
29
|
spec.add_runtime_dependency "libxml-ruby"
|
30
30
|
spec.add_runtime_dependency "nokogiri"
|
data/lib/goods/util.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module Goods
|
2
|
+
module Util
|
3
|
+
class CategoriesGraph
|
4
|
+
attr_reader :vertex_count, :edge_count
|
5
|
+
|
6
|
+
def initialize(categories=[])
|
7
|
+
reset
|
8
|
+
add_categories(categories)
|
9
|
+
end
|
10
|
+
|
11
|
+
def adjacent_categories?(cid1, cid2)
|
12
|
+
vertexes = [
|
13
|
+
vertex_for_category(cid1),
|
14
|
+
vertex_for_category(cid2)
|
15
|
+
]
|
16
|
+
adjacent?(*vertexes)
|
17
|
+
end
|
18
|
+
|
19
|
+
def topsorted
|
20
|
+
sorted = []
|
21
|
+
on_processed = ->(vertex_num) { sorted.unshift(vertex_num) }
|
22
|
+
dfs(nil, on_processed)
|
23
|
+
sorted.map { |v| category_for_vertex(v) }
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Make empty graph
|
29
|
+
def reset
|
30
|
+
@vertex_count = 0
|
31
|
+
@edge_count = 0
|
32
|
+
@adjacency_list = []
|
33
|
+
@vertex_to_category = []
|
34
|
+
@category_to_vertex = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_categories(categories)
|
38
|
+
categories.each { |c| init_category(c) }
|
39
|
+
categories.each { |c| link_category(c) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def init_category(category)
|
43
|
+
return if vertex_for_category(category[:id])
|
44
|
+
|
45
|
+
add_vertex do |vertex_num|
|
46
|
+
set_mapping vertex_num, category
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def link_category(category)
|
51
|
+
if category[:parent_id]
|
52
|
+
edge = [
|
53
|
+
vertex_for_category(category[:parent_id]),
|
54
|
+
vertex_for_category(category[:id])
|
55
|
+
]
|
56
|
+
|
57
|
+
add_edge(*edge)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_vertex
|
62
|
+
@adjacency_list.push([])
|
63
|
+
yield @vertex_count if block_given?
|
64
|
+
@vertex_count += 1
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_edge(v_start, v_end)
|
68
|
+
if v_start && v_end
|
69
|
+
@adjacency_list[v_start].push(v_end)
|
70
|
+
@edge_count += 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def adjacent?(v1, v2)
|
75
|
+
@adjacency_list[v1].include?(v2)
|
76
|
+
end
|
77
|
+
|
78
|
+
def adjacent_for(vertex)
|
79
|
+
@adjacency_list[vertex]
|
80
|
+
end
|
81
|
+
|
82
|
+
def vertex_for_category(category_id)
|
83
|
+
@category_to_vertex[category_id]
|
84
|
+
end
|
85
|
+
|
86
|
+
def category_for_vertex(vertex_num)
|
87
|
+
@vertex_to_category[vertex_num]
|
88
|
+
end
|
89
|
+
|
90
|
+
def set_mapping(vertex_num, category)
|
91
|
+
@vertex_to_category[vertex_num] = category
|
92
|
+
@category_to_vertex[category[:id]] = vertex_num
|
93
|
+
end
|
94
|
+
|
95
|
+
def dfs(on_discovered, on_processed)
|
96
|
+
states = vertex_count.times.map { :undiscovered }
|
97
|
+
|
98
|
+
vertex_count.times.each do |vertex|
|
99
|
+
if states[vertex] == :undiscovered
|
100
|
+
dfs_internal(vertex, states, on_discovered, on_processed)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def dfs_internal(vertex, states, on_discovered, on_processed)
|
106
|
+
states[vertex] = :discovered
|
107
|
+
on_discovered.call(vertex) if on_discovered
|
108
|
+
|
109
|
+
adjacent_for(vertex).each do |adjacent|
|
110
|
+
if states[adjacent] == :undiscovered
|
111
|
+
dfs_internal(adjacent, states, on_discovered, on_processed)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
states[vertex] = :processed
|
116
|
+
on_processed.call(vertex) if on_processed
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/goods/version.rb
CHANGED
data/lib/goods/xml.rb
CHANGED
data/lib/goods.rb
CHANGED
@@ -0,0 +1,114 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Goods::Util::CategoriesGraph do
|
4
|
+
let(:klass) { Goods::Util::CategoriesGraph }
|
5
|
+
|
6
|
+
describe "#initialize" do
|
7
|
+
it "should create empty graph if nothing passed" do
|
8
|
+
graph = klass.new
|
9
|
+
expect(graph.vertex_count).to eql(0)
|
10
|
+
expect(graph.edge_count).to eql(0)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should create graph with vertice for each category" do
|
14
|
+
graph = klass.new([
|
15
|
+
{ id: 1, name: "First" },
|
16
|
+
{ id: 2, name: "Second" }
|
17
|
+
])
|
18
|
+
expect(graph.vertex_count).to eql(2)
|
19
|
+
expect(graph.edge_count).to eql(0)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should create edge between parent and child category" do
|
23
|
+
normal_categories = [
|
24
|
+
{ id: 1, name: "First" },
|
25
|
+
{ id: 2, name: "Second", parent_id: 1 }
|
26
|
+
]
|
27
|
+
reversed_categories = normal_categories.reverse
|
28
|
+
|
29
|
+
[normal_categories, reversed_categories].each do |categories|
|
30
|
+
graph = klass.new(categories)
|
31
|
+
expect(graph.vertex_count).to eql(2)
|
32
|
+
expect(graph.edge_count).to eql(1)
|
33
|
+
expect(graph.adjacent_categories?(1, 2)).to eql(true)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should not duplicate categories" do
|
38
|
+
graph = klass.new([
|
39
|
+
{ id: 1, name: "Name" },
|
40
|
+
{ id: 1, name: "Name" }
|
41
|
+
])
|
42
|
+
|
43
|
+
expect(graph.vertex_count).to eql(1)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#topsorted" do
|
48
|
+
|
49
|
+
context "with single strongly connected component" do
|
50
|
+
let(:simple_description) {
|
51
|
+
[
|
52
|
+
{ id: 1, name: "First" },
|
53
|
+
{ id: 2, name: "Second", parent_id: 1 }
|
54
|
+
]
|
55
|
+
}
|
56
|
+
|
57
|
+
it "if already topsorted should return the same" do
|
58
|
+
graph = klass.new(simple_description)
|
59
|
+
topsorted = graph.topsorted
|
60
|
+
expect(topsorted).to eql(simple_description)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "if not topsorted should topsort" do
|
64
|
+
graph = klass.new(simple_description.reverse)
|
65
|
+
topsorted = graph.topsorted
|
66
|
+
expect(topsorted).to eql(simple_description)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "with multiple strongly connected component" do
|
71
|
+
let(:complex_description) {
|
72
|
+
[
|
73
|
+
{id: 1, name: 'C0L0N0', level: 0, component: 1},
|
74
|
+
{id: 2, name: 'C0L1N0', parent_id: 1, level: 1, component: 1},
|
75
|
+
{id: 3, name: 'C0L1N1', parent_id: 1, level: 1, component: 1},
|
76
|
+
{id: 4, name: 'C0L1N2', parent_id: 1, level: 1, component: 1},
|
77
|
+
{id: 5, name: 'C0L2N0', parent_id: 2, level: 2, component: 1},
|
78
|
+
{id: 6, name: 'C0L3N0', parent_id: 5, level: 2, component: 1},
|
79
|
+
|
80
|
+
{id: 7, name: 'C1L0N0', level: 0, component: 2},
|
81
|
+
{id: 8, name: 'C1L1N0', parent_id: 7, level: 1, component: 2},
|
82
|
+
{id: 9, name: 'C1L1N1', parent_id: 7, level: 1, component: 2},
|
83
|
+
]
|
84
|
+
}
|
85
|
+
|
86
|
+
it "if not topsorted should topsort" do
|
87
|
+
graph = klass.new(complex_description.reverse)
|
88
|
+
topsorted = graph.topsorted
|
89
|
+
topsorted.each_with_index do |cat, idx|
|
90
|
+
if cat[:parent_id]
|
91
|
+
expect(
|
92
|
+
topsorted[0, idx].find { |el| el[:id] == cat[:parent_id]}
|
93
|
+
).not_to be_nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "with graph with cycles" do
|
100
|
+
let(:cyclic) {
|
101
|
+
[
|
102
|
+
{id: 1, name: "Kili", parent_id: 3},
|
103
|
+
{id: 2, name: "Fili", parent_id: 1},
|
104
|
+
{id: 3, name: "Dili", parent_id: 2}
|
105
|
+
]
|
106
|
+
}
|
107
|
+
|
108
|
+
it "should not fail" do
|
109
|
+
graph = klass.new(cyclic)
|
110
|
+
expect(graph.topsorted).not_to be_nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/spec/goods/xml_spec.rb
CHANGED
@@ -25,21 +25,32 @@ describe Goods::XML do
|
|
25
25
|
|
26
26
|
describe "#categories" do
|
27
27
|
let(:categories) { simple_catalog.categories }
|
28
|
+
let(:root_category) {
|
29
|
+
categories.find { |el| el[:parent_id] == nil }
|
30
|
+
}
|
31
|
+
let(:child_category) {
|
32
|
+
categories.find { |el| el[:parent_id] == root_category[:id]}
|
33
|
+
}
|
28
34
|
|
29
35
|
it "should extract all categories" do
|
30
36
|
expect(categories.count).to eq(SIMPLE_CATALOG_CATEGORIES_COUNT)
|
31
37
|
end
|
32
38
|
|
33
|
-
|
34
|
-
|
35
|
-
|
39
|
+
it "should have root category" do
|
40
|
+
expect(root_category).not_to be_nil
|
41
|
+
end
|
36
42
|
|
43
|
+
it "should have child category" do
|
44
|
+
expect(child_category).not_to be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
context "category format" do
|
37
48
|
it "should have an id" do
|
38
|
-
expect(root_category
|
49
|
+
expect(root_category).to have_key(:id)
|
39
50
|
end
|
40
51
|
|
41
52
|
it "should have a name" do
|
42
|
-
expect(root_category[:name]).
|
53
|
+
expect(root_category[:name]).not_to be_empty
|
43
54
|
end
|
44
55
|
|
45
56
|
it "should have nil parent_id for root category" do
|
@@ -47,7 +58,7 @@ describe Goods::XML do
|
|
47
58
|
end
|
48
59
|
|
49
60
|
it "should have non-nil parent_id for child_category" do
|
50
|
-
expect(child_category[:parent_id]).
|
61
|
+
expect(child_category[:parent_id]).not_to be_nil
|
51
62
|
end
|
52
63
|
end
|
53
64
|
|
@@ -177,8 +188,8 @@ describe Goods::XML do
|
|
177
188
|
end
|
178
189
|
end
|
179
190
|
|
180
|
-
describe "#generation_date" do
|
181
|
-
it "should correctly get yml_catalog date" do
|
191
|
+
describe "#generation_date" do
|
192
|
+
it "should correctly get yml_catalog date" do
|
182
193
|
expect(simple_catalog.generation_date).to eq(SIMPLE_CATALOG_GENERATION_TIME)
|
183
194
|
end
|
184
195
|
end
|
data/spec/goods_spec.rb
CHANGED
@@ -11,8 +11,13 @@ describe Goods do
|
|
11
11
|
|
12
12
|
it "should return catalog if valid xml io is passed" do
|
13
13
|
expect(Goods::Catalog).to receive(:new).
|
14
|
-
with({io: valid_document, url: "url", encoding: "UTF-8"})
|
15
|
-
|
14
|
+
with({io: valid_document, url: "url", encoding: "UTF-8"}).and_call_original
|
15
|
+
|
16
|
+
catalog = Goods.from_io(valid_document, "url", "UTF-8")
|
17
|
+
|
18
|
+
expect(catalog.categories.size).to be > 0
|
19
|
+
expect(catalog.currencies.size).to be > 0
|
20
|
+
expect(catalog.offers.size).to be > 0
|
16
21
|
end
|
17
22
|
|
18
23
|
it "should raise error if invalid xml io is passed" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: goods
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Artem Pyanykh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - ~>
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 3.0.0.
|
47
|
+
version: 3.0.0.beta2
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 3.0.0.
|
54
|
+
version: 3.0.0.beta2
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: libxml-ruby
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +108,7 @@ files:
|
|
108
108
|
- lib/goods/element.rb
|
109
109
|
- lib/goods/offer.rb
|
110
110
|
- lib/goods/offers_list.rb
|
111
|
+
- lib/goods/util.rb
|
111
112
|
- lib/goods/version.rb
|
112
113
|
- lib/goods/xml.rb
|
113
114
|
- lib/goods/xml/validator.rb
|
@@ -121,6 +122,7 @@ files:
|
|
121
122
|
- spec/goods/currency_spec.rb
|
122
123
|
- spec/goods/offer_spec.rb
|
123
124
|
- spec/goods/offers_list_spec.rb
|
125
|
+
- spec/goods/util_spec.rb
|
124
126
|
- spec/goods/xml/validator_spec.rb
|
125
127
|
- spec/goods/xml_spec.rb
|
126
128
|
- spec/goods_spec.rb
|
@@ -161,6 +163,7 @@ test_files:
|
|
161
163
|
- spec/goods/currency_spec.rb
|
162
164
|
- spec/goods/offer_spec.rb
|
163
165
|
- spec/goods/offers_list_spec.rb
|
166
|
+
- spec/goods/util_spec.rb
|
164
167
|
- spec/goods/xml/validator_spec.rb
|
165
168
|
- spec/goods/xml_spec.rb
|
166
169
|
- spec/goods_spec.rb
|