gares 1.1.1 → 2.0.0.pre.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,10 @@
1
+ require 'spec_helper'
2
+
1
3
  describe Gares::Sales do
2
4
 
3
5
  context 'with a valid station' do
4
6
 
5
- subject(:sales) { Gares::Sales.new('frqxb') }
7
+ subject(:sales) { Gares::Sales.new(sncf_id: 'frqxb') }
6
8
 
7
9
  it { expect(sales.has_borne?).to be(true) }
8
10
 
@@ -1,62 +1,127 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Gares::Search do
4
- context 'with multiple search results' do
5
- before(:each) do
6
- @search = Gares::Search.new('Aix')
7
- end
4
+ describe "search by station name" do
5
+ context 'with multiple search results' do
6
+ subject do
7
+ Gares::Search.new('étienne')
8
+ end
8
9
 
9
- it 'should remember the query' do
10
- expect(@search.query).to eql('Aix')
11
- end
10
+ it 'should remember the query' do
11
+ expect(subject.query).to eql('étienne')
12
+ end
12
13
 
13
- it 'should find 5 results' do
14
- expect(@search.stations.size).to eql(7)
15
- end
14
+ it 'should find 28 results' do
15
+ expect(subject.stations.size).to eql(12)
16
+ end
16
17
 
17
- it 'should return Gares::Station objects only' do
18
- expect(@search.stations).to all(be_an(Gares::Station))
19
- end
18
+ it 'should return Gares::Station objects only' do
19
+ expect(subject.stations).to all(be_an(Gares::Station))
20
+ end
21
+
22
+ it 'should not return gares with no name' do
23
+ subject.stations.each { |gare| expect(gare.name).to_not be_blank }
24
+ end
20
25
 
21
- it 'should not return gares with no name' do
22
- @search.stations.each { |gare| expect(gare.name).to_not be_blank }
26
+ it 'should return only the name of the result' do
27
+ expect(subject.stations.first.name).to eql('St-Étienne-du-Rouvray')
28
+ end
23
29
  end
24
30
 
25
- it 'should return only the name of the result' do
26
- expect(@search.stations.first.name).to eql('Aix en Provence')
31
+ describe 'with name that has utf-8 characters' do
32
+ subject { Gares::Station.search('Saone').first }
33
+
34
+ it 'should give the proper name' do
35
+ expect(subject.name).to eql('Port-sur-Saône')
36
+ end
27
37
  end
28
- end
29
38
 
30
- context 'with an exact match' do
31
- it 'should not raise an exception' do
32
- expect do
33
- @search = Gares::Search.new('Paris Austerlitz').stations
34
- end.not_to raise_error
39
+ context 'with an exact match' do
40
+ subject { Gares::Search.new('Paris Austerlitz') }
41
+
42
+ it 'should not raise an exception' do
43
+ expect do
44
+ subject.stations
45
+ end.not_to raise_error
46
+ end
47
+
48
+ it 'should return the gare sncf_id.downcase correctly' do
49
+ expect(subject.stations.first.sncf_id.downcase).to eql('frpaz')
50
+ end
35
51
  end
36
52
 
37
- it 'should return the gare slug correctly' do
38
- @search = Gares::Search.new('Paris Austerlitz')
39
- expect(@search.stations.first.slug).to eql('frpaz')
53
+ context 'with a fuzzy match' do
54
+ subject { Gares::Search.new('CULMONT CHALINDREY') }
55
+ it 'should not raise an exception' do
56
+ expect do
57
+ subject.stations
58
+ end.not_to raise_error
59
+ end
60
+
61
+ it 'should return the gare sncf_id.downcase correctly' do
62
+ expect(subject.stations.first.sncf_id.downcase).to eql('frccy')
63
+ end
64
+
65
+ context 'with a "st" searching for "saint"' do
66
+ it 'should return the gare sncf_id.downcase correctly' do
67
+ subject = Gares::Search.new('ST ETIENNE CHATEAUCREUX')
68
+ expect(subject.stations.first.sncf_id.downcase).to eql('frhhd')
69
+ end
70
+ end
71
+
72
+ context 'with a multi-terms search' do
73
+ it 'should return the gare sncf_id.downcase correctly' do
74
+ subject = Gares::Search.new('BAR SUR AUBE')
75
+ expect(subject.stations.first.sncf_id.downcase).to eql('frapx')
76
+ end
77
+
78
+ it 'should return the gare sncf_id.downcase correctly' do
79
+ subject = Gares::Search.new('NOGENT SUR SEINE')
80
+ expect(subject.stations.first.sncf_id.downcase).to eql('frapm')
81
+ end
82
+
83
+ it 'should return the gare sncf_id.downcase correctly' do
84
+ subject = Gares::Station.search('MONTELIMAR GARE SNCF')
85
+ expect(subject.first.sncf_id.downcase).to eql('frxmk')
86
+ end
87
+
88
+ it 'should return the gare sncf_id.downcase correctly' do
89
+ subject = Gares::Station.search('MONTPELLIER SAINT-ROCH')
90
+ expect(subject.first.sncf_id.downcase).to eql('frmpl')
91
+ end
92
+
93
+ it 'should return the gare sncf_id.downcase correctly' do
94
+ subject = Gares::Station.search('CHALON SUR SAONE')
95
+ expect(subject.first.sncf_id.downcase).to eql('frxcd')
96
+ end
97
+
98
+ end
40
99
  end
41
100
  end
42
101
 
43
- context 'with a fuzzy match' do
44
- it 'should not raise an exception' do
45
- expect do
46
- @search = Gares::Search.new('CULMONT CHALINDREY').stations
47
- end.not_to raise_error
48
- end
102
+ describe "search by sncf_id" do
103
+ context 'with an exact match' do
104
+ subject { Gares::Search.new('frlpd', :sncf_id) }
49
105
 
50
- it 'should return the gare slug correctly' do
51
- @search = Gares::Search.new('CULMONT CHALINDREY')
52
- expect(@search.stations.first.slug).to eql('frccy')
53
- end
106
+ it 'should not raise an exception' do
107
+ expect do
108
+ subject.stations
109
+ end.not_to raise_error
110
+ end
54
111
 
55
- context 'with a "st" searching for "saint"' do
56
- it 'should return the gare slug correctly' do
57
- @search = Gares::Search.new('ST ETIENNE CHATEAUCREUX')
58
- expect(@search.stations.first.slug).to eql('frhhd')
112
+ it 'returns the good station' do
113
+ expect(subject.stations.first.sncf_id.downcase).to eql('frlpd')
114
+ expect(subject.stations.first.name).to eql('Lyon Part-Dieu')
59
115
  end
60
116
  end
61
117
  end
118
+
119
+ describe "search by unsupported field" do
120
+ it 'raises an exception' do
121
+ expect do
122
+ Gares::Search.new('Paris Austerlitz', :foo)
123
+ end.to raise_error
124
+ end
125
+ end
126
+
62
127
  end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ # This test uses "Lyon Part-Dieu" as a testing sample:
4
+ #
5
+ # http://www.gares-en-mouvement.com/fr/frlpd/votre-gare/
6
+ #
7
+ describe Gares::Station do
8
+
9
+ describe 'valid gare' do
10
+
11
+ subject do
12
+ # Get gare de Lyon Part-Dieu
13
+ Gares::Station.search_by_sncf_id('frlpd').first
14
+ end
15
+
16
+ it 'should find the name' do
17
+ expect(subject.name).to eql('Lyon Part-Dieu')
18
+ end
19
+
20
+ it 'should find the sncf_id' do
21
+ expect(subject.sncf_id).to eql('FRLPD')
22
+ # Still supports deprecated fields
23
+ expect(subject.slug).to eql(subject.sncf_id.downcase)
24
+ end
25
+
26
+ it 'should find the geolocation coordinates' do
27
+ expect(subject.latitude).to eql(45.760568)
28
+ expect(subject.longitude).to eql(4.859991)
29
+ # Still supports deprecated fields
30
+ expect(subject.lat).to eql(subject.latitude)
31
+ expect(subject.long).to eql(subject.longitude)
32
+ end
33
+
34
+ it 'should have opening hours' do
35
+ expect(subject.horaires.first).to eql('du lundi au dimanche de 04:50 à 00:45')
36
+ end
37
+
38
+ it 'should have a list of services' do
39
+ expect(subject.services).to be_an(Array)
40
+ expect(subject.services.first).to_not be_blank
41
+ end
42
+
43
+ it 'should have a list of sales services' do
44
+ expect(subject.sales).to be_an(Array)
45
+ expect(subject.sales.first).to_not be_blank
46
+ end
47
+
48
+ context 'Station of Agde' do
49
+ subject do
50
+ # Get gare de Agde
51
+ Gares::Station.search_by_sncf_id('frxag').first
52
+ end
53
+
54
+ describe 'a gare without wifi nor defibrillator' do
55
+ it { expect(subject.wifi?).to be(false) }
56
+ it { expect(subject.defibrillator?).to be(false) }
57
+ end
58
+
59
+ describe 'a gare with no sales services' do
60
+ it { expect(subject.has_borne?).to be(false) }
61
+ end
62
+ end
63
+ end
64
+ end
@@ -3,6 +3,27 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Gares::Train do
6
+
7
+ subject do
8
+ Gares::Train.new(train_number, date)
9
+ end
10
+
11
+ describe 'no results' do
12
+
13
+ let (:train_number) { 12345 }
14
+ let (:date) { Time.parse("2015-04-25") }
15
+
16
+ before do
17
+ # See tasks/fixtures.rake to change dataset
18
+ fake_response_for_train(train_number)
19
+ end
20
+
21
+ it "raises an error for any method called on it" do
22
+ expect { subject.departure }.to raise_error
23
+ end
24
+
25
+ end
26
+
6
27
  describe 'a delayed train' do
7
28
 
8
29
  let (:train_number) { 17709 }
@@ -24,8 +45,8 @@ describe Gares::Train do
24
45
  it "has a departure station" do
25
46
  expect(subject.departure.departure_date).to eq(Time.parse('2015-04-25 09:18:00'))
26
47
  expect(subject.departure.real_departure_date).to eq(Time.parse('2015-04-25 09:28:00'))
27
- expect(subject.departure.station.name).to eq('Lyon Part Dieu')
28
- expect(subject.departure.station.lat).to eql(45.760281)
48
+ expect(subject.departure.station.name).to eq('Lyon Part-Dieu')
49
+ expect(subject.departure.station.latitude).to eql(45.760568)
29
50
  expect(subject.departure.platform).to eq('--')
30
51
 
31
52
  expect(subject.departure.delayed?).to be(true)
@@ -35,17 +56,17 @@ describe Gares::Train do
35
56
  expect(subject.stops.size).to eq(12)
36
57
  expect(subject.stops.first.station.name).to eq('Vienne')
37
58
 
38
- expect(subject.stops[2].station.name).to eq('Tain l\'Hermitage Tournon')
59
+ expect(subject.stops[2].station.name).to eq('Tain')
39
60
  expect(subject.stops[2].platform).to eq('B')
40
61
  expect(subject.stops[2].minutes_of_delay).to eq(10)
41
62
 
42
- expect(subject.stops.last.station.name).to eq('Vitrolles Aéroport Marseille Provence')
63
+ expect(subject.stops.last.station.name).to eq('Vitrolles Aéroport Marseille-Provence TER')
43
64
  end
44
65
 
45
66
  it "has a arrival station" do
46
67
  expect(subject.arrival.arrival_date).to eq(Time.parse('2015-04-25 12:50:00'))
47
68
  expect(subject.arrival.real_arrival_date).to eq(Time.parse('2015-04-25 13:00:00'))
48
- expect(subject.arrival.station.name).to eq('Marseille St Charles')
69
+ expect(subject.arrival.station.name).to eq('Marseille St-Charles')
49
70
  expect(subject.arrival.platform).to eq('--')
50
71
  end
51
72
  end
@@ -71,15 +92,15 @@ describe Gares::Train do
71
92
  it "has a departure station" do
72
93
  expect(subject.departure.departure_date).to eq(Time.parse('2015-04-25 06:42:00'))
73
94
  expect(subject.departure.real_departure_date).to be_nil
74
- expect(subject.departure.station.name).to eq('Paris Est')
95
+ expect(subject.departure.station.name).to eq('Paris-Gare-de-l’Est')
75
96
  expect(subject.departure.platform).to eq('--')
76
97
 
77
98
  expect(subject.departure.delayed?).to be(false)
78
99
  end
79
100
 
80
- it "has stops" do
101
+ it "has stops", focus: true do
81
102
  expect(subject.stops.size).to eq(7)
82
- expect(subject.stops.first.station.name).to eq('Nogent sur Seine')
103
+ expect(subject.stops.first.station.name).to eq('Nogent-sur-Seine')
83
104
 
84
105
  expect(subject.stops[2].station.name).to eq('Troyes')
85
106
 
@@ -89,8 +110,28 @@ describe Gares::Train do
89
110
  it "has a arrival station" do
90
111
  expect(subject.arrival.arrival_date).to eq(Time.parse('2015-04-25 09:45:00'))
91
112
  expect(subject.arrival.real_arrival_date).to be_nil
92
- expect(subject.arrival.station.name).to eq('Culmont - Chalindrey')
113
+ expect(subject.arrival.station.name).to eq('Culmont-Chalindrey')
93
114
  expect(subject.arrival.platform).to eq('--')
94
115
  end
95
116
  end
117
+
118
+ describe 'a multi-itinerary train' do
119
+ let (:train_number) { 6815 }
120
+ let (:date) { Time.parse("2015-04-25") }
121
+
122
+ before do
123
+ # See tasks/fixtures.rake to change dataset
124
+ fake_response_for_train(train_number)
125
+ end
126
+
127
+ subject do
128
+ Gares::Train.new(train_number, date)
129
+ end
130
+
131
+ it "selects always the first itinerary", focus: true do
132
+ expect(subject.departure.station.name).to eq("Dijon Ville")
133
+ expect(subject.stops.first.station.name).to eq("Chalon-sur-Saône")
134
+ expect(subject.arrival.station.name).to eq("Nice Ville")
135
+ end
136
+ end
96
137
  end
data/spec/spec_helper.rb CHANGED
@@ -33,7 +33,7 @@ GARES_SAMPLES = {
33
33
  'http://www.gares-en-mouvement.com/fr/frxag/votre-gare' => 'frxag',
34
34
  'http://www.gares-en-mouvement.com/fr/frlpd/votre-gare' => 'frlpd',
35
35
  'http://www.gares-en-mouvement.com/fr/frhco/votre-gare' => 'frhco',
36
- 'https://www.kimonolabs.com/api/7jys32dy?apikey=lsOO4tNm78cH9JxqWg9gAk9l4nYaou9j&kimmodify=1' => 'search'
36
+ 'https://raw.githubusercontent.com/capitainetrain/stations/master/stations.csv' => 'stations.csv'
37
37
  }
38
38
 
39
39
  # Sample fixtures for Trains
@@ -41,7 +41,12 @@ TRAINS_SAMPLES = [
41
41
  { 17709 => 'train-17709' },
42
42
  { 11641 => 'train-11641' },
43
43
  { 17495 => 'train-17495' },
44
- { 6815 => 'train-6815' }, # Multi-itinerary
44
+ { 12345 => 'train-12345' },
45
+ { 6815 => 'train-6815' }, # Multi-itinerary see MULTI_TRAINS_SAMPLES
46
+ ]
47
+
48
+ MULTI_TRAINS_SAMPLES = [
49
+ { 6815 => ['train-6815-0', 'train-6815-1'] },
45
50
  ]
46
51
 
47
52
  unless ENV['LIVE_TEST']
@@ -63,10 +68,18 @@ def fake_response_for_train(number)
63
68
  unless ENV['LIVE_TEST']
64
69
  begin
65
70
  response = TRAINS_SAMPLES.find { |sample| sample.keys.first == number }.values.first
71
+ multi_responses = MULTI_TRAINS_SAMPLES.find { |sample| sample.keys.first == number }
72
+ multi_responses = multi_responses.nil? ? [] : multi_responses.values.first
66
73
  sncf_result_url = 'http://www.sncf.com/en/horaires-info-trafic/train/resultats'
67
74
  FakeWeb.register_uri(:get, sncf_result_url, response: read_fixture("get-#{response}"))
68
75
  sncf_post_url = 'http://www.sncf.com/sncf/train'
69
76
  FakeWeb.register_uri(:post, sncf_post_url, response: read_fixture("post-#{response}"))
77
+ multi_responses.each_with_index do |multi_response, idx|
78
+ sncf_get_multi_url = "http://www.sncf.com/sncf/train/displayDetailTrain?idItineraire=#{idx}"
79
+ FakeWeb.register_uri(:get, sncf_get_multi_url, response: read_fixture("get-#{multi_response}"))
80
+ sncf_get_data_url = "http://www.sncf.com/en/horaires-info-trafic/train/resultats?#{idx}"
81
+ FakeWeb.register_uri(:get, sncf_get_data_url, response: read_fixture("get-#{multi_response}-data"))
82
+ end
70
83
  rescue LoadError
71
84
  puts 'Could not load FakeWeb, these tests will hit gares-en-mouvement.com'
72
85
  puts 'You can run `gem install fakeweb` to stub out the responses.'
data/tasks/fixtures.rake CHANGED
@@ -34,6 +34,24 @@ namespace :fixtures do
34
34
  File.open(File.expand_path(File.dirname(__FILE__) + "/../spec/fixtures/#{get_fixture}"), 'w') do |f|
35
35
  f.write(page)
36
36
  end
37
+
38
+ multi_train_sample = MULTI_TRAINS_SAMPLES.find { |one| one.keys.first == train_number } || {"" => []}
39
+ multi_train_sample.values.first.each_with_index do |fixture, idx|
40
+ get_fixture = "get-#{fixture}"
41
+ get_fixture_data = "#{get_fixture}-data"
42
+ page = `curl -is 'http://www.sncf.com/sncf/train/displayDetailTrain?idItineraire=#{idx}' -H 'Cookie: #{cookies.join(";")}'`
43
+
44
+ File.open(File.expand_path(File.dirname(__FILE__) + "/../spec/fixtures/#{get_fixture}"), 'w') do |f|
45
+ f.write(page)
46
+ end
47
+
48
+ page = `curl -is 'http://www.sncf.com/en/horaires-info-trafic/train/resultats?#{idx}' -H 'Cookie: #{cookies.join(";")}'`
49
+
50
+ File.open(File.expand_path(File.dirname(__FILE__) + "/../spec/fixtures/#{get_fixture_data}"), 'w') do |f|
51
+ f.write(page)
52
+ end
53
+
54
+ end
37
55
  end
38
56
  end
39
57
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gares
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 2.0.0.pre.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Bonaud
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-25 00:00:00.000000000 Z
11
+ date: 2015-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: smarter_csv
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: unidecoder
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -198,6 +212,7 @@ files:
198
212
  - gares.gemspec
199
213
  - lib/gares.rb
200
214
  - lib/gares/base.rb
215
+ - lib/gares/errors.rb
201
216
  - lib/gares/sales.rb
202
217
  - lib/gares/search.rb
203
218
  - lib/gares/services.rb
@@ -217,17 +232,24 @@ files:
217
232
  - spec/fixtures/frqxb-services-vente
218
233
  - spec/fixtures/frxag
219
234
  - spec/fixtures/get-train-11641
235
+ - spec/fixtures/get-train-12345
220
236
  - spec/fixtures/get-train-17495
221
237
  - spec/fixtures/get-train-17709
222
238
  - spec/fixtures/get-train-6815
239
+ - spec/fixtures/get-train-6815-0
240
+ - spec/fixtures/get-train-6815-0-data
241
+ - spec/fixtures/get-train-6815-1
242
+ - spec/fixtures/get-train-6815-1-data
223
243
  - spec/fixtures/post-train-11641
244
+ - spec/fixtures/post-train-12345
224
245
  - spec/fixtures/post-train-17495
225
246
  - spec/fixtures/post-train-17709
226
247
  - spec/fixtures/post-train-6815
227
248
  - spec/fixtures/search
228
- - spec/gares/gare_spec.rb
249
+ - spec/fixtures/stations.csv
229
250
  - spec/gares/sales_spec.rb
230
251
  - spec/gares/search_spec.rb
252
+ - spec/gares/station_spec.rb
231
253
  - spec/gares/string_extensions_spec.rb
232
254
  - spec/gares/train_spec.rb
233
255
  - spec/spec_helper.rb
@@ -248,9 +270,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
248
270
  version: '0'
249
271
  required_rubygems_version: !ruby/object:Gem::Requirement
250
272
  requirements:
251
- - - ">="
273
+ - - ">"
252
274
  - !ruby/object:Gem::Version
253
- version: '0'
275
+ version: 1.3.1
254
276
  requirements: []
255
277
  rubyforge_project: gares
256
278
  rubygems_version: 2.2.0
@@ -1,82 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- # This test uses "Lyon Part-Dieu" as a testing sample:
6
- #
7
- # http://www.gares-en-mouvement.com/fr/frlpd/votre-gare/
8
- #
9
- describe Gares::Station do
10
-
11
- describe 'valid gare' do
12
-
13
- before(:each) do
14
- # Get gare de Lyon Part-Dieu
15
- @gare = Gares::Station.new('frlpd')
16
- end
17
-
18
- it 'should find the name' do
19
- name = @gare.name
20
-
21
- expect(name).to eql('Lyon Part Dieu')
22
- end
23
-
24
- it 'should find the geolocation coordinates' do
25
- lat = @gare.lat
26
- long = @gare.long
27
-
28
- expect(lat).to eql(45.760281)
29
- expect(long).to eql(4.859801)
30
- end
31
-
32
- it 'should have opening hours' do
33
- horaires = @gare.horaires
34
-
35
- expect(horaires.first).to eql('du lundi au dimanche de 04:50 à 00:45')
36
- end
37
-
38
- it 'should have a list of services' do
39
- services = @gare.services
40
-
41
- expect(services).to be_an(Array)
42
- expect(services.first).to_not be_blank
43
- end
44
-
45
- it 'should have a list of sales services' do
46
- sales = @gare.sales
47
-
48
- expect(sales).to be_an(Array)
49
- expect(sales.first).to_not be_blank
50
- end
51
-
52
- context 'a gare without wifi nor defibrillator' do
53
- before(:each) do
54
- # Get gare de Agde
55
- @gare = Gares::Station.new('frxag')
56
- end
57
-
58
- it { expect(@gare.wifi?).to be(false) }
59
- it { expect(@gare.defibrillator?).to be(false) }
60
- end
61
-
62
- context 'a gare with no sales services' do
63
- before(:each) do
64
- # Get gare de Agde
65
- @gare = Gares::Station.new('frxag')
66
- end
67
-
68
- it { expect(@gare.has_borne?).to be(false) }
69
- end
70
- end
71
-
72
- describe 'with name that has utf-8 characters' do
73
- # Belleville sur Sâone
74
- before(:each) do
75
- @gare = Gares::Station.search('Saone').first
76
- end
77
-
78
- it 'should give the proper name' do
79
- expect(@gare.name).to eql('Belleville sur Sâone')
80
- end
81
- end
82
- end