notu 0.1.6 → 0.2.0
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 +4 -4
- data/README.mdown +12 -8
- data/VERSION +1 -1
- data/lib/notu.rb +1 -0
- data/lib/notu/listing.rb +30 -0
- data/lib/notu/loved_tracks.rb +7 -20
- data/lib/notu/most_played_tracks.rb +24 -15
- data/spec/cassettes/Notu_HtmlDocument/_get/follows_redirects.yml +23 -32
- data/spec/cassettes/Notu_HtmlDocument/_get/raise_a_NetworkError_on_404.yml +9 -16
- data/spec/cassettes/Notu_HtmlDocument/_get/raise_a_ParseError_if_not_a_valid_document.yml +22 -20
- data/spec/cassettes/Notu_HtmlDocument/_get/returns_document_parsed.yml +21 -30
- data/spec/cassettes/Notu_HttpDownload/_get/accepts_HTTPS_URL.yml +21 -30
- data/spec/cassettes/Notu_HttpDownload/_get/follow_redirects.yml +23 -32
- data/spec/cassettes/Notu_HttpDownload/_get/raise_a_NetworkError_if_too_many_redirects.yml +3 -3
- data/spec/cassettes/Notu_HttpDownload/_get/raise_a_NetworkError_on_404.yml +9 -16
- data/spec/cassettes/Notu_HttpDownload/_get/retrives_document_from_given_URL.yml +21 -30
- data/spec/cassettes/Notu_LovedTracks/_each/returns_nil.yml +3906 -2531
- data/spec/cassettes/Notu_LovedTracks/_each/returns_some_tracks.yml +7805 -5039
- data/spec/cassettes/Notu_LovedTracks/_page_urls/is_correct.yml +7800 -5013
- data/spec/cassettes/Notu_LovedTracks/_pages_count/is_correct.yml +3900 -2507
- data/spec/cassettes/Notu_MostPlayedTracks/_each/returns_nil.yml +2597 -6489
- data/spec/cassettes/Notu_MostPlayedTracks/_each/returns_some_tracks.yml +9435 -13402
- data/spec/cassettes/Notu_MostPlayedTracks/_page_urls/is_correct.yml +9613 -0
- data/spec/cassettes/Notu_MostPlayedTracks/_pages_count/is_correct.yml +5085 -0
- data/spec/notu/library_spec.rb +2 -2
- data/spec/notu/loved_tracks_spec.rb +20 -4
- data/spec/notu/most_played_tracks_spec.rb +44 -7
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83c849e6aa154adb3210c2665d5ef2ffc64ca013
|
4
|
+
data.tar.gz: 69173c99d6eec9cdb3fd52a671c7c64d80553e86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6700b41e5b70ec40928596227c2bded73092ac80620826d0029c09a041b531142366e7a3ced25751b9bf20d894f6835c3c219b84742e8567f20ee817ab4f57f3
|
7
|
+
data.tar.gz: 6b82941fd297bb5b1255338cd5d642bbc7c91dfe2a6dd9ad827d18d542d75dd812a9f1203096f64e950bde924685c3f320da2d33532212c3a4083566955f856d
|
data/.gitignore
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
.DS_Store
|
2
|
-
|
3
|
-
|
4
|
-
Gemfile.lock
|
5
|
-
pkg
|
2
|
+
/.bundle/
|
3
|
+
/.ruby-version
|
4
|
+
/Gemfile.lock
|
5
|
+
/pkg/
|
data/README.mdown
CHANGED
@@ -6,21 +6,25 @@ API to get Last.fm most played and loved tracks.
|
|
6
6
|
|
7
7
|
Just add this into your `Gemfile`:
|
8
8
|
|
9
|
-
|
9
|
+
```ruby
|
10
|
+
gem 'notu'
|
11
|
+
```
|
10
12
|
|
11
13
|
Then, just run `bundle install`.
|
12
14
|
|
13
15
|
## Example
|
14
16
|
|
15
|
-
|
17
|
+
```ruby
|
18
|
+
library = Notu::Library.new(username: 'johndoe')
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
library.loved_tracks.each do |track|
|
21
|
+
puts track.artist
|
22
|
+
end
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
library.most_played_tracks(period: 'last_month').each do |track|
|
25
|
+
puts "#{track.artist}: #{track.plays_count}"
|
26
|
+
end
|
27
|
+
```
|
24
28
|
|
25
29
|
## Executing test suite
|
26
30
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/notu.rb
CHANGED
@@ -14,6 +14,7 @@ require "#{lib_path}/parse_error"
|
|
14
14
|
require "#{lib_path}/html_document"
|
15
15
|
require "#{lib_path}/http_download"
|
16
16
|
require "#{lib_path}/library"
|
17
|
+
require "#{lib_path}/listing"
|
17
18
|
require "#{lib_path}/loved_tracks"
|
18
19
|
require "#{lib_path}/most_played_tracks"
|
19
20
|
require "#{lib_path}/track"
|
data/lib/notu/listing.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Notu
|
2
|
+
|
3
|
+
module Listing
|
4
|
+
|
5
|
+
attr_reader :library
|
6
|
+
|
7
|
+
def initialize(library)
|
8
|
+
raise ArgumentError.new("#{self.class}#library must be a library, #{library.inspect} given") unless library.is_a?(Library)
|
9
|
+
@library = library
|
10
|
+
end
|
11
|
+
|
12
|
+
def page_urls
|
13
|
+
(1..pages_count).map do |index|
|
14
|
+
library.url(path: path, query: params.merge('page' => index))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def pages_count
|
19
|
+
document = HtmlDocument.get(library.url(path: path, query: params))
|
20
|
+
[1, (document/'ul.pagination li.pages').text.split(/\s+/).map { |text| text.to_i }].flatten.compact.max
|
21
|
+
end
|
22
|
+
|
23
|
+
def params
|
24
|
+
# to be overriden
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/lib/notu/loved_tracks.rb
CHANGED
@@ -3,36 +3,23 @@ module Notu
|
|
3
3
|
class LovedTracks
|
4
4
|
|
5
5
|
include Enumerable
|
6
|
-
|
7
|
-
attr_reader :library
|
8
|
-
|
9
|
-
def initialize(library)
|
10
|
-
raise ArgumentError.new("#{self.class}#library must be a library, #{library.inspect} given") unless library.is_a?(Library)
|
11
|
-
@library = library
|
12
|
-
end
|
6
|
+
include Listing
|
13
7
|
|
14
8
|
def each(&block)
|
15
9
|
return unless block_given?
|
16
10
|
page_urls.each do |url|
|
17
11
|
document = HtmlDocument.get(url)
|
18
|
-
(document/'#
|
19
|
-
|
12
|
+
(document/'#user-loved-tracks-section tbody tr').each do |element|
|
13
|
+
artist = (element/'td.chartlist-name .link-block-target').first.text
|
14
|
+
title = (element/'td.chartlist-name .chartlist-artists').first.text
|
15
|
+
yield(Track.new(artist: artist, title: title))
|
20
16
|
end
|
21
17
|
end
|
22
18
|
nil
|
23
19
|
end
|
24
20
|
|
25
|
-
|
26
|
-
|
27
|
-
def page_urls
|
28
|
-
(1..pages_count).map do |index|
|
29
|
-
library.url(path: 'library/loved', query: { 'sortBy' => 'date', 'sortOrder' => 'desc', 'page' => index })
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def pages_count
|
34
|
-
document = HtmlDocument.get(library.url(path: 'library/loved'))
|
35
|
-
[1, (document/'div.whittle-pagination a').map { |link| link.text.to_i }].flatten.max
|
21
|
+
def path
|
22
|
+
'loved'
|
36
23
|
end
|
37
24
|
|
38
25
|
end
|
@@ -3,37 +3,46 @@ module Notu
|
|
3
3
|
class MostPlayedTracks
|
4
4
|
|
5
5
|
include Enumerable
|
6
|
+
include Listing
|
6
7
|
|
7
8
|
PERIODS = {
|
8
|
-
'
|
9
|
-
'
|
10
|
-
'
|
11
|
-
'
|
12
|
-
'
|
13
|
-
'overall' => 'overall',
|
9
|
+
'7 days' => 'LAST_7_DAYS',
|
10
|
+
'30 days' => 'LAST_30_DAYS',
|
11
|
+
'90 days' => 'LAST_90_DAYS',
|
12
|
+
'365 days' => 'LAST_365_DAYS',
|
13
|
+
'Overall' => '',
|
14
14
|
}
|
15
15
|
|
16
|
-
attr_reader :
|
16
|
+
attr_reader :period
|
17
17
|
|
18
18
|
def initialize(library, options = {})
|
19
|
-
|
20
|
-
@library = library
|
19
|
+
super(library)
|
21
20
|
options = options.stringify_keys.reverse_merge('period' => PERIODS.keys.first)
|
22
21
|
self.period = options['period']
|
23
22
|
end
|
24
23
|
|
25
24
|
def each(&block)
|
26
25
|
return unless block_given?
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
page_urls.each do |url|
|
27
|
+
document = HtmlDocument.get(url)
|
28
|
+
(document/'table.chartlist tbody tr').each do |element|
|
29
|
+
artist = (element/'td.chartlist-name .link-block-target').first.text
|
30
|
+
title = (element/'td.chartlist-name .chartlist-artists').first.text
|
31
|
+
plays_count = (element/'td.chartlist-countbar .countbar-bar-value').text.gsub(/[^\d]/, '')
|
32
|
+
yield(Track.new(artist: artist, plays_count: plays_count, title: title))
|
33
|
+
end
|
33
34
|
end
|
34
35
|
nil
|
35
36
|
end
|
36
37
|
|
38
|
+
def params
|
39
|
+
{ 'date_preset' => PERIODS[period] }
|
40
|
+
end
|
41
|
+
|
42
|
+
def path
|
43
|
+
'library/tracks'
|
44
|
+
end
|
45
|
+
|
37
46
|
private
|
38
47
|
|
39
48
|
def period=(value)
|
@@ -19,7 +19,7 @@ http_interactions:
|
|
19
19
|
message: Moved Permanently
|
20
20
|
headers:
|
21
21
|
Date:
|
22
|
-
-
|
22
|
+
- Wed, 19 Aug 2015 01:03:26 GMT
|
23
23
|
Server:
|
24
24
|
- Apache/2.2.16 (Debian)
|
25
25
|
Location:
|
@@ -43,7 +43,7 @@ http_interactions:
|
|
43
43
|
<address>Apache/2.2.16 (Debian) Server at www.alweb.org Port 80</address>
|
44
44
|
</body></html>
|
45
45
|
http_version:
|
46
|
-
recorded_at:
|
46
|
+
recorded_at: Wed, 19 Aug 2015 01:03:26 GMT
|
47
47
|
- request:
|
48
48
|
method: get
|
49
49
|
uri: http://alweb.org/
|
@@ -63,7 +63,7 @@ http_interactions:
|
|
63
63
|
message: OK
|
64
64
|
headers:
|
65
65
|
Date:
|
66
|
-
-
|
66
|
+
- Wed, 19 Aug 2015 01:03:27 GMT
|
67
67
|
Server:
|
68
68
|
- Apache/2.2.16 (Debian)
|
69
69
|
X-Frame-Options:
|
@@ -73,21 +73,21 @@ http_interactions:
|
|
73
73
|
X-Content-Type-Options:
|
74
74
|
- nosniff
|
75
75
|
Etag:
|
76
|
-
- '"
|
76
|
+
- '"17883192c98ca0216d45df6579fc74e5"'
|
77
77
|
Cache-Control:
|
78
78
|
- public
|
79
79
|
X-Request-Id:
|
80
|
-
-
|
80
|
+
- 7755283b-c2d8-4431-94ad-881df833cf2b
|
81
81
|
X-Runtime:
|
82
|
-
- '0.
|
82
|
+
- '0.006922'
|
83
83
|
X-Powered-By:
|
84
|
-
- Phusion Passenger 4.0.
|
84
|
+
- Phusion Passenger 4.0.53
|
85
85
|
Status:
|
86
86
|
- 200 OK
|
87
87
|
Vary:
|
88
88
|
- Accept-Encoding
|
89
89
|
Content-Length:
|
90
|
-
- '
|
90
|
+
- '787'
|
91
91
|
Content-Type:
|
92
92
|
- text/html; charset=utf-8
|
93
93
|
body:
|
@@ -110,8 +110,8 @@ http_interactions:
|
|
110
110
|
<meta name="DC.creator" content="Alexis Toulotte" />
|
111
111
|
<meta name="DC.description" content="Page personnelle d'Alexis Toulotte." />
|
112
112
|
<meta name="google-site-verification" content="1dodRvC0Zp-iAZilnTeT5PaY4bPKHwSzs9994-3g304" />
|
113
|
-
<script src="/assets/application-
|
114
|
-
<link
|
113
|
+
<script src="/assets/application-eae74a30b96601b52819de1d8f7920d7.js"></script>
|
114
|
+
<link rel="stylesheet" media="screen" href="/assets/application-ccd3cc87d5f63a3e28a2f20ec2ea3a97.css" />
|
115
115
|
<link href="/avatar?size=128" rel="icon" type="image/png" />
|
116
116
|
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Dancing Script" />
|
117
117
|
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Raleway" />
|
@@ -123,13 +123,13 @@ http_interactions:
|
|
123
123
|
|
124
124
|
<ul class="services">
|
125
125
|
<li class="github">
|
126
|
-
<a href="
|
126
|
+
<a href="/github"><span>GitHub</span></a>
|
127
127
|
</li>
|
128
128
|
<li class="rubygems">
|
129
|
-
<a href="
|
129
|
+
<a href="/rubygems"><span>RubyGems</span></a>
|
130
130
|
</li>
|
131
131
|
<li class="cv">
|
132
|
-
<a href="
|
132
|
+
<a href="/cv"><span>CV</span></a>
|
133
133
|
</li>
|
134
134
|
<li class="email">
|
135
135
|
<a href="#"><span>Email</span></a>
|
@@ -141,31 +141,22 @@ http_interactions:
|
|
141
141
|
<a href="skype:alexistoulotte"><span>Skype</span></a>
|
142
142
|
</li>
|
143
143
|
<li class="twitter">
|
144
|
-
<a href="
|
144
|
+
<a href="/twitter"><span>Twitter</span></a>
|
145
145
|
</li>
|
146
146
|
<li class="facebook">
|
147
|
-
<a href="
|
147
|
+
<a href="/facebook"><span>Facebook</span></a>
|
148
148
|
</li>
|
149
|
-
<li class="
|
150
|
-
<a href="
|
149
|
+
<li class="linkedin">
|
150
|
+
<a href="/linkedin"><span>LinkedIn</span></a>
|
151
151
|
</li>
|
152
152
|
<li class="google-plus">
|
153
|
-
<a href="
|
154
|
-
</li>
|
155
|
-
<li class="youtube">
|
156
|
-
<a href="https://www.youtube.com/alexistoulotte"><span>YouTube</span></a>
|
153
|
+
<a href="/google_plus"><span>Google+</span></a>
|
157
154
|
</li>
|
158
155
|
<li class="mixcloud">
|
159
|
-
<a href="
|
160
|
-
</li>
|
161
|
-
<li class="last-fm">
|
162
|
-
<a href="http://www.lastfm.fr/user/alexistoulotte"><span>Last.fm</span></a>
|
163
|
-
</li>
|
164
|
-
<li class="sound-cloud">
|
165
|
-
<a href="https://soundcloud.com/alexistoulotte"><span>SoundCloud</span></a>
|
156
|
+
<a href="/mixcloud"><span>Mixcloud</span></a>
|
166
157
|
</li>
|
167
|
-
<li class="
|
168
|
-
<a href="
|
158
|
+
<li class="lastfm">
|
159
|
+
<a href="/lastfm"><span>Last.fm</span></a>
|
169
160
|
</li>
|
170
161
|
</ul>
|
171
162
|
|
@@ -174,5 +165,5 @@ http_interactions:
|
|
174
165
|
|
175
166
|
</html>
|
176
167
|
http_version:
|
177
|
-
recorded_at:
|
178
|
-
recorded_with: VCR 2.9.
|
168
|
+
recorded_at: Wed, 19 Aug 2015 01:03:27 GMT
|
169
|
+
recorded_with: VCR 2.9.3
|
@@ -19,29 +19,29 @@ http_interactions:
|
|
19
19
|
message: Not Found
|
20
20
|
headers:
|
21
21
|
Date:
|
22
|
-
-
|
22
|
+
- Wed, 19 Aug 2015 01:03:25 GMT
|
23
23
|
Server:
|
24
24
|
- Apache/2.2.16 (Debian)
|
25
25
|
X-Request-Id:
|
26
|
-
-
|
26
|
+
- cd80c4e1-b689-4add-a080-552bc5fb6ca2
|
27
27
|
X-Runtime:
|
28
|
-
- '0.
|
28
|
+
- '0.003299'
|
29
29
|
X-Powered-By:
|
30
|
-
- Phusion Passenger 4.0.
|
30
|
+
- Phusion Passenger 4.0.53
|
31
31
|
Status:
|
32
32
|
- 404 Not Found
|
33
33
|
Vary:
|
34
34
|
- Accept-Encoding
|
35
35
|
Content-Length:
|
36
|
-
- '
|
36
|
+
- '309'
|
37
37
|
Content-Type:
|
38
38
|
- text/html; charset=utf-8
|
39
39
|
body:
|
40
40
|
encoding: UTF-8
|
41
41
|
string: |
|
42
|
-
<!DOCTYPE html
|
42
|
+
<!DOCTYPE html>
|
43
43
|
|
44
|
-
<html
|
44
|
+
<html>
|
45
45
|
|
46
46
|
<head>
|
47
47
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
@@ -63,18 +63,11 @@ http_interactions:
|
|
63
63
|
<p>
|
64
64
|
You may have mistyped the address or the page may have moved.
|
65
65
|
</p>
|
66
|
-
|
67
|
-
<p class="footer">
|
68
|
-
Powered by
|
69
|
-
<a href="http://httpd.apache.org" title="The Apache HTTP Server Project">Apache</a>
|
70
|
-
and
|
71
|
-
<a href="http://debian.org" title="The Universal Operating System">Debian</a>.
|
72
|
-
</p>
|
73
66
|
</div>
|
74
67
|
|
75
68
|
</body>
|
76
69
|
|
77
70
|
</html>
|
78
71
|
http_version:
|
79
|
-
recorded_at:
|
80
|
-
recorded_with: VCR 2.9.
|
72
|
+
recorded_at: Wed, 19 Aug 2015 01:03:25 GMT
|
73
|
+
recorded_with: VCR 2.9.3
|
@@ -19,7 +19,7 @@ http_interactions:
|
|
19
19
|
message: Found
|
20
20
|
headers:
|
21
21
|
Date:
|
22
|
-
-
|
22
|
+
- Wed, 19 Aug 2015 01:03:28 GMT
|
23
23
|
Server:
|
24
24
|
- Apache/2.2.16 (Debian)
|
25
25
|
X-Frame-Options:
|
@@ -29,15 +29,15 @@ http_interactions:
|
|
29
29
|
X-Content-Type-Options:
|
30
30
|
- nosniff
|
31
31
|
Etag:
|
32
|
-
- '"
|
32
|
+
- '"fcc9c11bad7747937dfc96cd94e35bca"'
|
33
33
|
Cache-Control:
|
34
34
|
- public
|
35
35
|
X-Request-Id:
|
36
|
-
-
|
36
|
+
- 8b9c16a1-d4b1-4634-8b62-2781713921f7
|
37
37
|
X-Runtime:
|
38
|
-
- '0.
|
38
|
+
- '0.003552'
|
39
39
|
X-Powered-By:
|
40
|
-
- Phusion Passenger 4.0.
|
40
|
+
- Phusion Passenger 4.0.53
|
41
41
|
Location:
|
42
42
|
- http://www.gravatar.com/avatar/c090d5749bd9877588ac8563e0817042
|
43
43
|
Status:
|
@@ -52,7 +52,7 @@ http_interactions:
|
|
52
52
|
encoding: UTF-8
|
53
53
|
string: <html><body>You are being <a href="http://www.gravatar.com/avatar/c090d5749bd9877588ac8563e0817042">redirected</a>.</body></html>
|
54
54
|
http_version:
|
55
|
-
recorded_at:
|
55
|
+
recorded_at: Wed, 19 Aug 2015 01:03:28 GMT
|
56
56
|
- request:
|
57
57
|
method: get
|
58
58
|
uri: http://www.gravatar.com/avatar/c090d5749bd9877588ac8563e0817042
|
@@ -71,32 +71,34 @@ http_interactions:
|
|
71
71
|
code: 200
|
72
72
|
message: OK
|
73
73
|
headers:
|
74
|
+
Date:
|
75
|
+
- Wed, 19 Aug 2015 00:59:59 GMT
|
76
|
+
Last-Modified:
|
77
|
+
- Thu, 24 Jan 2013 00:31:59 GMT
|
78
|
+
Connection:
|
79
|
+
- Keep-Alive
|
80
|
+
Cache-Control:
|
81
|
+
- max-age=300
|
82
|
+
Age:
|
83
|
+
- '210'
|
74
84
|
Accept-Ranges:
|
75
85
|
- bytes
|
76
86
|
Access-Control-Allow-Origin:
|
77
87
|
- "*"
|
78
|
-
Cache-Control:
|
79
|
-
- max-age=300
|
80
88
|
Content-Disposition:
|
81
89
|
- inline; filename="c090d5749bd9877588ac8563e0817042.png"
|
82
90
|
Content-Type:
|
83
91
|
- image/png
|
84
|
-
Date:
|
85
|
-
- Thu, 28 Aug 2014 22:42:23 GMT
|
86
92
|
Expires:
|
87
|
-
-
|
88
|
-
|
89
|
-
-
|
93
|
+
- Wed, 19 Aug 2015 01:04:59 GMT
|
94
|
+
Link:
|
95
|
+
- <http://www.gravatar.com/avatar/c090d5749bd9877588ac8563e0817042>; rel="canonical"
|
90
96
|
Server:
|
91
97
|
- ECS (syd/EBA1)
|
92
98
|
Source-Age:
|
93
99
|
- '0'
|
94
|
-
Via:
|
95
|
-
- 1.1 varnish
|
96
|
-
X-Cache:
|
97
|
-
- HIT
|
98
100
|
X-Varnish:
|
99
|
-
- '
|
101
|
+
- '429679271'
|
100
102
|
Content-Length:
|
101
103
|
- '11789'
|
102
104
|
body:
|
@@ -365,5 +367,5 @@ http_interactions:
|
|
365
367
|
0nq4U7qFDsaylSsypzHmjP5Aa8QrNs33AowZ7kMMtcj/Kwwzi4jsmgCKY16w
|
366
368
|
HkbsMQmjmC6gSeNGLyXZEkoxlIaZ8f8Dl9VUi97Go/gAAAAASUVORK5CYII=
|
367
369
|
http_version:
|
368
|
-
recorded_at:
|
369
|
-
recorded_with: VCR 2.9.
|
370
|
+
recorded_at: Wed, 19 Aug 2015 01:03:29 GMT
|
371
|
+
recorded_with: VCR 2.9.3
|