prismic.io 1.0.0.preview.1

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.
@@ -0,0 +1,5 @@
1
+ module Prismic
2
+
3
+ VERSION = "1.0.0.preview.1"
4
+
5
+ end
data/prismic.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'prismic/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "prismic.io"
8
+ spec.version = Prismic::VERSION
9
+ spec.authors = ["Étienne Vallette d'Osia", "Samy Dindane"]
10
+ spec.email = ["evo@zenexity.com"]
11
+ spec.description = %q{The standard Prismic.io's API library.}
12
+ spec.summary = %q{Prismic.io development kit}
13
+ spec.homepage = "http://prismic.io"
14
+ spec.license = "Apache-2"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "yajl-ruby", "~> 1.1"
23
+ spec.add_development_dependency "rspec", "~> 2.14"
24
+ spec.add_development_dependency "nokogiri", "~> 1.6"
25
+ spec.add_development_dependency "simplecov", "~> 0.7"
26
+ end
@@ -0,0 +1,372 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'WebLink' do
4
+ describe 'as_html' do
5
+ before do
6
+ @web_link = Prismic::Fragments::WebLink.new('my_url')
7
+ end
8
+
9
+ it "returns an <a> HTML element" do
10
+ Nokogiri::XML(@web_link.as_html).child.name.should == 'a'
11
+ end
12
+
13
+ it "returns a HTML element with an href attribute" do
14
+ Nokogiri::XML(@web_link.as_html).child.has_attribute?('href').should be_true
15
+ end
16
+
17
+ it "returns a HTML element with an href attribute pointing to the url" do
18
+ Nokogiri::XML(@web_link.as_html).child.attribute('href').value.should == 'my_url'
19
+ end
20
+
21
+ it "returns a HTML element whose content is the link" do
22
+ Nokogiri::XML(@web_link.as_html).child.content.should == 'my_url'
23
+ end
24
+ end
25
+ end
26
+
27
+ describe 'MediaLink' do
28
+ describe 'as_html' do
29
+ before do
30
+ @media_link = Prismic::Fragments::MediaLink.new('my_url')
31
+ end
32
+
33
+ it "returns an <a> HTML element" do
34
+ Nokogiri::XML(@media_link.as_html).child.name.should == 'a'
35
+ end
36
+
37
+ it "returns a HTML element with an href attribute" do
38
+ Nokogiri::XML(@media_link.as_html).child.has_attribute?('href').should be_true
39
+ end
40
+
41
+ it "returns a HTML element with an href attribute pointing to the url" do
42
+ Nokogiri::XML(@media_link.as_html).child.attribute('href').value.should == 'my_url'
43
+ end
44
+
45
+ it "returns a HTML element whose content is the link" do
46
+ Nokogiri::XML(@media_link.as_html).child.content.should == 'my_url'
47
+ end
48
+ end
49
+ end
50
+
51
+ describe 'Text' do
52
+ describe 'as_html' do
53
+ before do
54
+ @text = Prismic::Fragments::Text.new('my_value')
55
+ end
56
+
57
+ it "returns a <span> HTML element" do
58
+ Nokogiri::XML(@text.as_html).child.name.should == 'span'
59
+ end
60
+
61
+ it "returns a HTML element with the 'text' class" do
62
+ Nokogiri::XML(@text.as_html).child.attribute('class').value.split.should include 'text'
63
+ end
64
+
65
+ it "returns a HTML element whose content is the value" do
66
+ Nokogiri::XML(@text.as_html).child.content.should == 'my_value'
67
+ end
68
+ end
69
+ end
70
+
71
+ describe 'Select' do
72
+ describe 'as_html' do
73
+ before do
74
+ @select = Prismic::Fragments::Select.new('my_value')
75
+ end
76
+
77
+ it "returns a <span> HTML element" do
78
+ Nokogiri::XML(@select.as_html).child.name.should == 'span'
79
+ end
80
+
81
+ it "returns a HTML element with the 'text' class" do
82
+ Nokogiri::XML(@select.as_html).child.attribute('class').value.split.should include 'text'
83
+ end
84
+
85
+ it "returns a HTML element whose content is the value" do
86
+ Nokogiri::XML(@select.as_html).child.content.should == 'my_value'
87
+ end
88
+ end
89
+ end
90
+
91
+ describe 'Date' do
92
+ before do
93
+ @date = Prismic::Fragments::Date.new(DateTime.new(2013, 8, 7, 11, 13, 7, '+2'))
94
+ end
95
+
96
+ describe 'as_html' do
97
+ it "returns a <time> HTML element" do
98
+ Nokogiri::XML(@date.as_html).child.name.should == 'time'
99
+ end
100
+
101
+ it "returns a HTML element whose content is the date in the ISO8601 format" do
102
+ Nokogiri::XML(@date.as_html).child.content.should == '2013-08-07T11:13:07.000+02:00'
103
+ end
104
+ end
105
+ end
106
+
107
+ describe 'Number' do
108
+ before do
109
+ @number = Prismic::Fragments::Number.new(10.2)
110
+ end
111
+
112
+ describe 'as_int' do
113
+ it "returns an Integer" do
114
+ @number.as_int.should be_kind_of Integer
115
+ end
116
+
117
+ it "returns the integer representation of the number" do
118
+ @number.as_int.should == 10
119
+ end
120
+ end
121
+
122
+ describe 'as_html' do
123
+ it "returns a <span> HTML element" do
124
+ Nokogiri::XML(@number.as_html).child.name.should == 'span'
125
+ end
126
+
127
+ it "returns a HTML element with the class 'number'" do
128
+ Nokogiri::XML(@number.as_html).child.attribute('class').value.split.should include 'number'
129
+ end
130
+
131
+ it "returns a HTML element whose content is the value" do
132
+ Nokogiri::XML(@number.as_html).child.content.should == 10.2.to_s
133
+ end
134
+ end
135
+ end
136
+
137
+ describe 'Color' do
138
+ before do
139
+ @hex_value = '00FF99'
140
+ @color = Prismic::Fragments::Color.new(@hex_value)
141
+ end
142
+
143
+ describe 'asRGB' do
144
+ it "returns a hash" do
145
+ @color.asRGB.should be_kind_of Hash
146
+ end
147
+
148
+ it "returns a hash of 3 elements" do
149
+ @color.asRGB.size.should == 3
150
+ end
151
+
152
+ it "returns the correct red value" do
153
+ @color.asRGB['red'].should == 0
154
+ end
155
+
156
+ it "returns the correct green value" do
157
+ @color.asRGB['green'].should == 255
158
+ end
159
+
160
+ it "returns the correct blue value" do
161
+ @color.asRGB['blue'].should == 153
162
+ end
163
+ end
164
+
165
+ describe 'self.asRGB' do
166
+ before do
167
+ @color = Prismic::Fragments::Color
168
+ end
169
+
170
+ it "returns a hash" do
171
+ @color.asRGB(@hex_value).should be_kind_of Hash
172
+ end
173
+
174
+ it "returns a hash of 3 elements" do
175
+ @color.asRGB(@hex_value).size.should == 3
176
+ end
177
+
178
+ it "returns the correct red value" do
179
+ @color.asRGB(@hex_value)['red'].should == 0
180
+ end
181
+
182
+ it "returns the correct green value" do
183
+ @color.asRGB(@hex_value)['green'].should == 255
184
+ end
185
+
186
+ it "returns the correct blue value" do
187
+ @color.asRGB(@hex_value)['blue'].should == 153
188
+ end
189
+ end
190
+
191
+ describe 'as_html' do
192
+ it "returns a <span> HTML element" do
193
+ Nokogiri::XML(@color.as_html).child.name.should == 'span'
194
+ end
195
+
196
+ it "returns a HTML element with the class 'color'" do
197
+ Nokogiri::XML(@color.as_html).child.attribute('class').value.split.should include 'color'
198
+ end
199
+
200
+ it "returns a HTML element whose content is the value" do
201
+ Nokogiri::XML(@color.as_html).child.content.should == "##@hex_value"
202
+ end
203
+ end
204
+
205
+ describe 'self.valid?' do
206
+ it "returns true if the color is valid" do
207
+ Prismic::Fragments::Color.valid?(@hex_value).should be_true
208
+ end
209
+
210
+ it "returns false if the color is not valid" do
211
+ Prismic::Fragments::Color.valid?("I'm a murloc").should be_false
212
+ end
213
+ end
214
+ end
215
+
216
+ describe 'Embed' do
217
+ before do
218
+ @embed = Prismic::Fragments::Embed.new(
219
+ 'MY_TYPE',
220
+ 'MY_PROVIDER',
221
+ 'my_url',
222
+ 'my_html',
223
+ 'my_oembed_json'
224
+ )
225
+ end
226
+
227
+ describe 'as_html' do
228
+ it "returns a div element" do
229
+ Nokogiri::XML(@embed.as_html).child.name.should == 'div'
230
+ end
231
+
232
+ it "returns an element with a data-oembed attribute" do
233
+ Nokogiri::XML(@embed.as_html).child.has_attribute?('data-oembed').should be_true
234
+ end
235
+
236
+ it "returns an element with a data-oembed attribute containing the url" do
237
+ Nokogiri::XML(@embed.as_html).child.attribute('data-oembed').value.should == 'my_url'
238
+ end
239
+
240
+ it "returns an element with a data-oembed-type attribute" do
241
+ Nokogiri::XML(@embed.as_html).child.has_attribute?('data-oembed-type').should be_true
242
+ end
243
+
244
+ it "returns an element with a data-oembed-type attribute containing the type in lowercase" do
245
+ Nokogiri::XML(@embed.as_html).child.attribute('data-oembed-type').value.should == 'my_type'
246
+ end
247
+
248
+ it "returns an element with a data-oembed-provider attribute" do
249
+ Nokogiri::XML(@embed.as_html).child.has_attribute?('data-oembed-provider').should be_true
250
+ end
251
+
252
+ it "returns an element with a data-oembed-provider attribute containing the provider in lowercase" do
253
+ Nokogiri::XML(@embed.as_html).child.attribute('data-oembed-provider').value.should == 'my_provider'
254
+ end
255
+
256
+ it "returns an element wrapping the `html` value" do
257
+ Nokogiri::XML(@embed.as_html).child.content.should == 'my_html'
258
+ end
259
+ end
260
+ end
261
+
262
+ describe 'Image::View' do
263
+ before do
264
+ @url = 'my_url'
265
+ @width = 10
266
+ @height = 2
267
+ @view = Prismic::Fragments::Image::View.new(@url, @width, @height)
268
+ end
269
+
270
+ describe 'ratio' do
271
+ it "returns the width/height ratio of the image" do
272
+ @view.ratio.should == @width / @height
273
+ end
274
+ end
275
+
276
+ describe 'as_html' do
277
+ it "return an <img> HTML element" do
278
+ Nokogiri::XML(@view.as_html).child.name.should == 'img'
279
+ end
280
+
281
+ it "returns an element whose `src` attribute equals the url" do
282
+ Nokogiri::XML(@view.as_html).child.attribute('src').value.should == @url
283
+ end
284
+
285
+ it "returns an element whose `width` attribute equals the width" do
286
+ Nokogiri::XML(@view.as_html).child.attribute('width').value.should == @width.to_s
287
+ end
288
+
289
+ it "returns an element whose `height` attribute equals the height" do
290
+ Nokogiri::XML(@view.as_html).child.attribute('height').value.should == @height.to_s
291
+ end
292
+ end
293
+ end
294
+
295
+ describe 'Image' do
296
+ before do
297
+ @main_view = Prismic::Fragments::Image::View.new('my_url', 10, 10)
298
+ @another_view = Prismic::Fragments::Image::View.new('my_url2', 20, 20)
299
+ @image = Prismic::Fragments::Image.new(@main_view, { 'another_view' => @another_view })
300
+ end
301
+
302
+ describe 'get_view' do
303
+ it "returns `main`'s value is asked for`" do
304
+ @image.get_view('main').should == @main_view
305
+ end
306
+
307
+ it "returns the value of the specified key" do
308
+ @image.get_view('another_view').should == @another_view
309
+ end
310
+
311
+ it "raises an error if the key does not exist" do
312
+ expect { @image.get_view('foo') }.to raise_error Prismic::Fragments::Image::ViewDoesNotExistException
313
+ end
314
+ end
315
+
316
+ describe 'as_html' do
317
+ it "returns the HTML representation of the main view" do
318
+ Nokogiri::XML(@image.as_html).child.name.should == Nokogiri::XML(@main_view.as_html).child.name
319
+ Nokogiri::XML(@image.as_html).child.attribute('src').value.should == Nokogiri::XML(@main_view.as_html).child.attribute('src').value
320
+ Nokogiri::XML(@image.as_html).child.attribute('width').value.should == Nokogiri::XML(@main_view.as_html).child.attribute('width').value
321
+ Nokogiri::XML(@image.as_html).child.attribute('height').value.should == Nokogiri::XML(@main_view.as_html).child.attribute('height').value
322
+ end
323
+ end
324
+ end
325
+
326
+ describe 'StructuredText::Image' do
327
+ before do
328
+ @view = Prismic::Fragments::Image::View.new('my_url', 10, 10)
329
+ @image = Prismic::Fragments::StructuredText::Block::Image.new(@view)
330
+ end
331
+
332
+ describe 'url' do
333
+ it "returns the view's url" do
334
+ @image.url.should == @view.url
335
+ end
336
+ end
337
+
338
+ describe 'width' do
339
+ it "returns the view's width" do
340
+ @image.width.should == @view.width
341
+ end
342
+ end
343
+
344
+ describe 'height' do
345
+ it "returns the view's height" do
346
+ @image.height.should == @view.height
347
+ end
348
+ end
349
+ end
350
+
351
+ describe 'StructuredText::Span' do
352
+ describe 'as_html'
353
+ end
354
+
355
+ describe 'DocumentLink' do
356
+ describe 'as_html'
357
+ end
358
+
359
+ describe 'Multiple' do
360
+ before do
361
+ @multiple = Prismic::Fragments::Multiple.new
362
+ end
363
+
364
+ describe 'push' do
365
+ it "adds the element to the collection" do
366
+ @multiple.push(:something)
367
+ @multiple.size.should == 1
368
+ @multiple.push(:something_else)
369
+ @multiple.size.should == 2
370
+ end
371
+ end
372
+ end
@@ -0,0 +1,287 @@
1
+ describe 'document_link_parser' do
2
+ before do
3
+ raw_json = <<json
4
+ {
5
+ "type": "Link.document",
6
+ "value": {
7
+ "document": {
8
+ "id": "UdUjvt_mqVNObPeO",
9
+ "type": "product",
10
+ "tags": ["Macaron"],
11
+ "slug": "dark-chocolate-macaron"
12
+ },
13
+ "isBroken": false
14
+ }
15
+ }
16
+ json
17
+ @json = JSON.parse(raw_json)
18
+ end
19
+
20
+ it "correctly parses DocumentLinks" do
21
+ document_link = Prismic::JsonParser.document_link_parser(@json)
22
+ document_link.id.should == "UdUjvt_mqVNObPeO"
23
+ document_link.link_type.should == "product"
24
+ document_link.tags.should == ['Macaron']
25
+ document_link.slug.should == "dark-chocolate-macaron"
26
+ document_link.broken?.should == false
27
+ end
28
+ end
29
+
30
+ describe 'text_parser' do
31
+ before do
32
+ raw_json = <<json
33
+ {
34
+ "type": "Text",
35
+ "value": "New York City, NY"
36
+ }
37
+ json
38
+ @json = JSON.parse(raw_json)
39
+ end
40
+
41
+ it "correctly parses Text objects" do
42
+ text = Prismic::JsonParser.text_parser(@json)
43
+ text.value.should == "New York City, NY"
44
+ end
45
+ end
46
+
47
+ describe 'web_link_parser' do
48
+ before do
49
+ raw_json = <<json
50
+ {
51
+ "type": "Link.web",
52
+ "value": {
53
+ "url": "http://prismic.io"
54
+ }
55
+ }
56
+ json
57
+ @json = JSON.parse(raw_json)
58
+ end
59
+
60
+ it "correctly parses WebLinks objects" do
61
+ web_link = Prismic::JsonParser.web_link_parser(@json)
62
+ web_link.url.should == "http://prismic.io"
63
+ end
64
+ end
65
+
66
+ describe 'date_parser' do
67
+ before do
68
+ raw_json = <<json
69
+ {
70
+ "type": "date",
71
+ "value": "2013-09-19"
72
+ }
73
+ json
74
+ @json = JSON.parse(raw_json)
75
+ end
76
+
77
+ it "correctly parses Date objects" do
78
+ date = Prismic::JsonParser.date_parser(@json)
79
+ date.value.should == Time.new(2013, 9, 19)
80
+ end
81
+ end
82
+
83
+ describe 'number_parser' do
84
+ before do
85
+ raw_json = <<json
86
+ {
87
+ "type": "Number",
88
+ "value": 3.55
89
+ }
90
+ json
91
+ @json = JSON.parse(raw_json)
92
+ end
93
+
94
+ it "correctly parses Number objects" do
95
+ number = Prismic::JsonParser.number_parser(@json)
96
+ number.value.should == 3.55
97
+ end
98
+ end
99
+
100
+ describe 'embed_parser' do
101
+ before do
102
+ @embed_type = "rich"
103
+ @provider = "GitHub"
104
+ @url = "https://gist.github.com"
105
+ @html = '<script src="https://gist.github.com/dohzya/6762845.js"></script>'
106
+ raw_json = <<json
107
+ {
108
+ "type": "embed",
109
+ "value": {
110
+ "oembed": {
111
+ "version": "1.0",
112
+ "type": #{@embed_type.to_json},
113
+ "provider_name": #{@provider.to_json},
114
+ "provider_url": #{@url.to_json},
115
+ "html": #{@html.to_json},
116
+ "gist": "dohzya/6762845",
117
+ "embed_url": "https://gist.github.com/dohzya/6762845",
118
+ "title": "dohzya/gist:6762845"
119
+ }
120
+ }
121
+ }
122
+ json
123
+ @json = JSON.parse(raw_json)
124
+ end
125
+
126
+ it "correctly parses Embed objects" do
127
+ embed = Prismic::JsonParser.embed_parser(@json)
128
+ embed.embed_type.should == @embed_type
129
+ embed.provider.should == @provider
130
+ embed.url.should == @url
131
+ embed.html.should == @html
132
+ end
133
+ end
134
+
135
+ describe 'image_parser' do
136
+ before do
137
+ raw_json = <<json
138
+ {
139
+ "type": "Image",
140
+ "value": {
141
+ "main": {
142
+ "url": "url1",
143
+ "dimensions": {
144
+ "width": 500,
145
+ "height": 500
146
+ }
147
+ },
148
+ "views": {
149
+ "icon": {
150
+ "url": "url2",
151
+ "dimensions": {
152
+ "width": 250,
153
+ "height": 250
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ json
160
+ @json = JSON.parse(raw_json)
161
+ end
162
+
163
+ it "correctly parses Image objects" do
164
+ image = Prismic::JsonParser.image_parser(@json)
165
+ image.main.url.should == "url1"
166
+ image.main.width.should == 500
167
+ image.main.height.should == 500
168
+ image.views[0].url.should == "url2"
169
+ image.views[0].width.should == 250
170
+ image.views[0].height.should == 250
171
+ end
172
+ end
173
+
174
+ describe 'color_parser' do
175
+ before do
176
+ raw_json = <<json
177
+ {
178
+ "type": "Color",
179
+ "value": "#ffeacd"
180
+ }
181
+ json
182
+ @json = JSON.parse(raw_json)
183
+ end
184
+
185
+ it "correctly parses Color objects" do
186
+ color = Prismic::JsonParser.color_parser(@json)
187
+ color.value.should == "ffeacd"
188
+ end
189
+ end
190
+
191
+ describe 'structured_text_parser' do
192
+ before do
193
+ raw_json_paragraph = File.read("#{File.dirname(__FILE__)}/responses_mocks/structured_text_paragraph.json")
194
+ json_paragraph = JSON.parse(raw_json_paragraph)
195
+ @structured_text_paragraph = Prismic::JsonParser.structured_text_parser(json_paragraph)
196
+
197
+ raw_json_heading = File.read("#{File.dirname(__FILE__)}/responses_mocks/structured_text_heading.json")
198
+ json_heading = JSON.parse(raw_json_heading)
199
+ @structured_text_heading = Prismic::JsonParser.structured_text_parser(json_heading)
200
+ end
201
+
202
+ describe 'headings parsing' do
203
+ it "correctly parses headings" do
204
+ @structured_text_heading.blocks[0].should be_a Prismic::Fragments::StructuredText::Block::Heading
205
+ @structured_text_heading.blocks[0].text.should == "Salted Caramel Macaron"
206
+ @structured_text_heading.blocks[0].level.should == 1
207
+ end
208
+
209
+ it "parses all the spans" do
210
+ @structured_text_heading.blocks[0].spans.size.should == 1
211
+ end
212
+ end
213
+
214
+ describe 'paragraphs parsing' do
215
+ it "correctly parses paragraphs" do
216
+ @structured_text_paragraph.blocks[0].should be_a Prismic::Fragments::StructuredText::Block::Paragraph
217
+ @structured_text_paragraph.blocks[0].text.size.should == 224
218
+ end
219
+ end
220
+
221
+ describe 'spans parsing' do
222
+ it "parses all the spans" do
223
+ @structured_text_paragraph.blocks[0].spans.size.should == 3
224
+ end
225
+
226
+ it "correctly parses the em spans" do
227
+ @structured_text_paragraph.blocks[0].spans[0].start.should == 103
228
+ @structured_text_paragraph.blocks[0].spans[0].end.should == 137
229
+ @structured_text_paragraph.blocks[0].spans[0].should be_a Prismic::Fragments::StructuredText::Span::Em
230
+ end
231
+
232
+ it "correctly parses the strong spans" do
233
+ @structured_text_paragraph.blocks[0].spans[2].should be_a Prismic::Fragments::StructuredText::Span::Strong
234
+ end
235
+
236
+ it "correctly parses the hyperlink spans" do
237
+ @structured_text_paragraph.blocks[0].spans[1].should be_a Prismic::Fragments::StructuredText::Span::Hyperlink
238
+ @structured_text_paragraph.blocks[0].spans[1].link.should be_a Prismic::Fragments::WebLink
239
+ end
240
+ end
241
+ end
242
+
243
+ describe 'document_parser' do
244
+ before do
245
+ raw_json = File.read("#{File.dirname(__FILE__)}/responses_mocks/document.json")
246
+ json = JSON.parse(raw_json)
247
+ @document = Prismic::JsonParser.document_parser(json)
248
+ end
249
+
250
+ it "correctly parses Document objects" do
251
+ @document.id.should == 'UdUkXt_mqZBObPeS'
252
+ @document.type.should == 'product'
253
+ @document.href.should == 'doc-url'
254
+ @document.tags.should == ['Macaron']
255
+ @document.slugs.should == ['vanilla-macaron']
256
+ end
257
+
258
+ it "correctly parses the document's fragments" do
259
+ @document.fragments.size.should == 11
260
+ @document.fragments['name'].should be_a Prismic::Fragments::StructuredText
261
+ end
262
+ end
263
+
264
+ describe 'multiple_parser' do
265
+ before do
266
+ raw_json = <<json
267
+ [
268
+ {
269
+ "type": "Text",
270
+ "value": "foo"
271
+ },
272
+ {
273
+ "type": "Text",
274
+ "value": "bar"
275
+ }
276
+ ]
277
+ json
278
+ @json = JSON.parse(raw_json)
279
+ end
280
+
281
+ it "correctly parses the Multiple object's elements" do
282
+ multiple = Prismic::JsonParser.multiple_parser(@json)
283
+ multiple.size.should == 2
284
+ multiple[0].class.should == Prismic::Fragments::Text
285
+ multiple[0].class.should == Prismic::Fragments::Text
286
+ end
287
+ end