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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a0011750815867641124fd10b42a9c29157cf738
4
- data.tar.gz: cf3a8fd8a6212bd050b38d188b62c32d0804cf43
3
+ metadata.gz: 4c0db8c61b2a8ee5ea477222bae0025d6ddf665c
4
+ data.tar.gz: 1f59093e307a69f02c11c7988824bfd90299afb3
5
5
  SHA512:
6
- metadata.gz: bd2df6ed0f8f557529d6826bee5525df56b7c2a379db0b4485630c6e33d17c759ca56ba9943cb395ac6363a1509f18c3d1b748ef61fe91dcab4a9be0ea8d16f4
7
- data.tar.gz: 668f87a2ecc661ab59c7888eb3d7d628d0cf4677f38fa942141883341bd246d1e82ec968673bd868c164d0dd3cf7155267a49f31c8ce0691a92e691c2446b78b
6
+ metadata.gz: cad7b3a73d0d6d48c4e41bed36bc5d05170fb15954fafc9a5f98d6588867950c40f4240b978f785566097983d1fe162f5fda23aec93dd403b897beb342c2e5fe
7
+ data.tar.gz: 06c5c8baabc115059afdb01fbae2f870a78d01c86d9569c6ae58afd446c4be3921ed83ce5a5c9232c269852aca8af37f6db876f30dfc7dfbd56cd5d316ec5bfa
data/.gitignore CHANGED
@@ -18,3 +18,4 @@ tmp
18
18
  .DS_Store
19
19
  *sublime*
20
20
  .idea/
21
+ .floo*
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.beta1"
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
@@ -1,3 +1,3 @@
1
1
  module Goods
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
data/lib/goods/xml.rb CHANGED
@@ -9,7 +9,9 @@ module Goods
9
9
  end
10
10
 
11
11
  def categories
12
- @categories ||= extract_categories
12
+ @categories ||= begin
13
+ Util::CategoriesGraph.new(extract_categories).topsorted
14
+ end
13
15
  end
14
16
 
15
17
  def currencies
data/lib/goods.rb CHANGED
@@ -2,6 +2,7 @@ require 'open-uri'
2
2
  require "goods/version"
3
3
  require "goods/xml/validator"
4
4
  require "goods/xml"
5
+ require "goods/util"
5
6
  require "goods/element"
6
7
  require "goods/container"
7
8
  require "goods/category"
@@ -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
@@ -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
- context "category format" do
34
- let(:root_category) { categories[0] }
35
- let(:child_category) { categories[1] }
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[:id]).to eq("1")
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]).to eq("Оргтехника")
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]).to eq("1")
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
- Goods.from_io(valid_document, "url", "UTF-8")
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
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-02-27 00:00:00.000000000 Z
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.beta1
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.beta1
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