weibo_2 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 simsicon
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # WeiboOAuth2
2
+
3
+ WAIT, let me have a beer first.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/example/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+
3
+ gem 'sinatra'
4
+ gem "haml"
5
+ gem "sass"
6
+ gem 'weibo_2', :path => '../'
data/example/config.ru ADDED
@@ -0,0 +1,2 @@
1
+ require 'example'
2
+ run Sinatra::Application
@@ -0,0 +1,48 @@
1
+ require 'weibo_2'
2
+
3
+ %w(rubygems bundler).each { |dependency| require dependency }
4
+ Bundler.setup
5
+ %w(sinatra haml sass).each { |dependency| require dependency }
6
+ enable :sessions
7
+
8
+ client = WeiboOAuth2::Client.new('1317413087', '79c39d8c66a93bd15f16ed0e999532f9')
9
+
10
+ get '/' do
11
+ if session[:access_token] && !client.authorized?
12
+ client.get_token_from_hash({:access_token => session[:access_token], :expires_at => session[:expires_at]})
13
+ end
14
+ if session[:uid]
15
+ @user = client.users.show_by_uid(session[:uid])
16
+ end
17
+ haml :index
18
+ end
19
+
20
+ get '/connect' do
21
+
22
+ url = client.authorize_url(:redirect_uri => 'http://127.0.0.1:4567/callback')
23
+ redirect url
24
+ end
25
+
26
+ get '/callback' do
27
+ access_token = client.auth_code.get_token(params[:code].to_s, :redirect_uri => 'http://127.0.0.1:4567/callback')
28
+ session[:uid] = access_token.params["uid"]
29
+ session[:access_token] = access_token.token
30
+ session[:expires_at] = access_token.expires_at
31
+ puts access_token.params
32
+ puts access_token.inspect
33
+ puts session[:uid]
34
+ @user = client.users.show_by_uid(session[:uid].to_i)
35
+ redirect '/'
36
+ end
37
+
38
+ get '/logout' do
39
+ session[:uid] = nil
40
+ session[:access_token] = nil
41
+ session[:expires_at] = nil
42
+ redirect '/'
43
+ end
44
+
45
+ get '/screen.css' do
46
+ content_type 'text/css'
47
+ sass :screen
48
+ end
@@ -0,0 +1,7 @@
1
+ -if @user
2
+ %p
3
+ %h5
4
+ hello! you are
5
+ =@user.screen_name
6
+
7
+
@@ -0,0 +1,10 @@
1
+ !!!
2
+ %html(xml:lang='en' lang='en' xmlns='http://www.w3.org/1999/xhtml')
3
+ %head
4
+ %meta(content='text/html;charset=UTF-8' http-equiv='content-type')
5
+ %title="weibo oauth2 api example"
6
+ %link{:href => "/screen.css", :rel =>"stylesheet", :type => "text/css", :media => "screen" }
7
+ %body
8
+ %h2="weibo oauth2 api example"
9
+ %a{:href => (session[:uid] ? "/logout" : "/connect")}=(session[:uid] ? "logout" : "connect")
10
+ = yield
File without changes
@@ -0,0 +1,31 @@
1
+ module WeiboOAuth2
2
+ module Api
3
+ module V2
4
+ class Account < Base
5
+
6
+ #read interfaces
7
+ def get_privacy(opt={})
8
+ hashie get("account/get_privacy.json", :params => opt)
9
+ end
10
+
11
+ def profile_school_list(opt={})
12
+ hashie get("account/profile/school_list.json", :params => opt)
13
+ end
14
+
15
+ def rate_limit_status(opt={})
16
+ hashie get("account/rate_limit_status.json", :params => opt)
17
+ end
18
+
19
+ def get_uid(opt={})
20
+ hashie get("account/get_uid.json", :params => opt)
21
+ end
22
+
23
+ #write interfaces
24
+ def end_session(opt={})
25
+ hashie get("account/end_session.json", :params => opt)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,64 @@
1
+ require 'hashie'
2
+ require 'json'
3
+ require 'net/http/post/multipart'
4
+
5
+ module WeiboOAuth2
6
+ module Api
7
+ module V2
8
+ class Base
9
+ extend Forwardable
10
+
11
+ def_delegators :@access_token, :get, :post, :put, :delete
12
+
13
+ @@API_VERSION = 2
14
+
15
+ def initialize(access_token)
16
+ @access_token = access_token
17
+ end
18
+
19
+ def hashie(response)
20
+ json_body = JSON.parse(response.body)
21
+ if json_body.is_a? Array
22
+ Array.new(json_body.count){|i| Hashie::Mash.new(json_body[i])}
23
+ else
24
+ Hashie::Mash.new json_body
25
+ end
26
+ end
27
+
28
+ protected
29
+ def self.mime_type(file)
30
+ case
31
+ when file =~ /\.jpg/ then 'image/jpg'
32
+ when file =~ /\.gif$/ then 'image/gif'
33
+ when file =~ /\.png$/ then 'image/png'
34
+ else 'application/octet-stream'
35
+ end
36
+ end
37
+
38
+ CRLF = "\r\n"
39
+ def self.build_multipart_bodies(parts)
40
+ boundary = Time.now.to_i.to_s(16)
41
+ body = ""
42
+ parts.each do |key, value|
43
+ esc_key = CGI.escape(key.to_s)
44
+ body << "--#{boundary}#{CRLF}"
45
+ if value.respond_to?(:read)
46
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"; filename=\"#{File.basename(value.path)}\"#{CRLF}"
47
+ body << "Content-Type: #{mime_type(value.path)}#{CRLF*2}"
48
+ body << value.read
49
+ else
50
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"#{CRLF*2}#{value}"
51
+ end
52
+ body << CRLF
53
+ end
54
+ body << "--#{boundary}--#{CRLF*2}"
55
+ {
56
+ :body => body,
57
+ :headers => {"Content-Type" => "multipart/form-data; boundary=#{boundary}"}
58
+ }
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,51 @@
1
+ module WeiboOAuth2
2
+ module Api
3
+ module V2
4
+ class Comments < Base
5
+
6
+ #read interfaces
7
+ def show(id, opt={})
8
+ hashie get("comments/show.json", :params => {:id => id}.merge(opt))
9
+ end
10
+
11
+ def by_me(opt={})
12
+ hashie get("comments/by_me.json", :params => opt)
13
+ end
14
+
15
+ def to_me(opt={})
16
+ hashie get("comments/to_me.json", :params => opt)
17
+ end
18
+
19
+ def timeline(opt={})
20
+ hashie get("comments/timeline.json", :params => opt)
21
+ end
22
+
23
+ def mentions(opt={})
24
+ hashie get("comments/mentions.json", :params => opt)
25
+ end
26
+
27
+ def show_batch(cids, opt={})
28
+ hashie get("comments/show_batch.json", :params => {:cids => cids}.merge(opt))
29
+ end
30
+
31
+ #write interfaces
32
+ def create(comment, id, opt={})
33
+ hashie post("comments/create.json", :params => {:comment => comment, :id => id}.merge(opt))
34
+ end
35
+
36
+ def destroy(cid, opt={})
37
+ hashie post("comments/destroy.json", :params => {:cid => cid}.merge(opt))
38
+ end
39
+
40
+ def destroy_batch(cids, opt={})
41
+ hashie post("comments/destroy_batch.json", :params => {:cids => cids}.merge(opt))
42
+ end
43
+
44
+ def reply(cid, id, comment, opt={})
45
+ hashie post("comments/reply.json", :params => {:cid => cid, :id => id, :comment => comment}.merge(opt))
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,28 @@
1
+ module WeiboOAuth2
2
+ module Api
3
+ module V2
4
+ class Common < Base
5
+ #read interfaces
6
+ def code_to_location(codes, opt={})
7
+ hashie get("common/code_to_location", :params => {:codes => codes}.merge(opt))
8
+ end
9
+
10
+ def get_city(province, opt={})
11
+ hashie get("common/send", :params => {:province => province}.merge(opt))
12
+ end
13
+
14
+ def get_province(country, opt={})
15
+ hashie get("common/send", :params => {:country => country}.merge(opt))
16
+ end
17
+
18
+ def get_country(opt={})
19
+ hashie get("common/send", :params => {:uids => uids, :tpl_id => tpl_id}.merge(opt))
20
+ end
21
+
22
+ def get_timezone(opt={})
23
+ hashie get("common/send", :params => {:uids => uids, :tpl_id => tpl_id}.merge(opt))
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,59 @@
1
+ module WeiboOAuth2
2
+ module Api
3
+ module V2
4
+ class Favorites < Base
5
+
6
+ #read interfaces
7
+ def favorites(opt={})
8
+ hashie get("favorites.json", :params => opt)
9
+ end
10
+
11
+ def ids(opt={})
12
+ hashie get("favorites/ids.json", :params => opt)
13
+ end
14
+
15
+ def show(id, opt={})
16
+ hashie get("favorites/show.json", :params => {:id => id}.merge(opt))
17
+ end
18
+
19
+ def by_tags(tid, opt={})
20
+ hashie get("favorites/by_tags.json", :params => {:tid => tid}.merge(opt))
21
+ end
22
+
23
+ def tags(opt={})
24
+ hashie get("favorites/tags.json", :params => opt)
25
+ end
26
+
27
+ def by_tags_ids(tid, opt={})
28
+ hashie get("favorites/by_tags/ids.json", :params => {:tid => tid}.merge(opt))
29
+ end
30
+
31
+ #write interfaces
32
+ def create(id, opt={})
33
+ hashie post("favorites/create.json", :params => {:id => id}.merge(opt))
34
+ end
35
+
36
+ def destroy(id, opt={})
37
+ hashie post("favorites/destroy.json", :params => {:id => id}.merge(opt))
38
+ end
39
+
40
+ def destroy_batch(ids, opt={})
41
+ hashie post("favorites/destroy_batch.json", :params => {:ids => ids}.merge(opt))
42
+ end
43
+
44
+ def tags_update(id, tags, opt={})
45
+ hashie post("favorites/tags/update.json", :params => {:id => id, :tags => CGI::escape(tags)}.merge(opt))
46
+ end
47
+
48
+ def tags_update_batch(tid, tag, opt={})
49
+ hashie post("favorites/tags/update_batch.json", :params => {:tid => tid, :tag => CGI::escape(tag)}.merge(opt))
50
+ end
51
+
52
+ def tags_destroy_batch(tid, opt={})
53
+ hashie post("favorites/tags/destroy_batch.json", :params => {:tid => tid}.merge(opt))
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,64 @@
1
+ module WeiboOAuth2
2
+ module Api
3
+ module V2
4
+ class Friendships < Base
5
+
6
+ #read interfaces
7
+ def friends(opt={})
8
+ hashie get("friendships/friends.json", :params => opt)
9
+ end
10
+
11
+ def friends_in_common(uid, opt={})
12
+ hashie get("friendships/friends/in_common.json", :params => {:uid => uid}.merge(opt))
13
+ end
14
+
15
+ def friends_bilateral(uid, opt={})
16
+ hashie get("friendships/friends/bilateral.json", :params => {:uid => uid}.merge(opt))
17
+ end
18
+
19
+ def friends_bilateral_ids(uid, opt={})
20
+ hashie get("friendships/friends/bilateral/ids.json", :params => {:uid => uid}.merge(opt))
21
+ end
22
+
23
+ def friends_ids(opt={})
24
+ hashie get("friendships/friends/ids.json", :params => opt)
25
+ end
26
+
27
+ def followers(opt={})
28
+ hashie get("friendships/followers.json", :params => opt)
29
+ end
30
+
31
+ def followers_ids(opt={})
32
+ hashie get("friendships/followers/ids.json", :params => opt)
33
+ end
34
+
35
+ def followers_active(uid, opt={})
36
+ hashie get("friendships/followers/active.json", :params => {:uid => uid}.merge(opt))
37
+ end
38
+
39
+ def friends_chain_followers(uid, opt={})
40
+ hashie get("friendships/friends_chain/followers.json", :params => {:uid => uid}.merge(opt))
41
+ end
42
+
43
+ def show(opt={})
44
+ hashie get("friendships/show.json", :params => opt)
45
+ end
46
+
47
+
48
+ #write interfaces
49
+ def create(opt={})
50
+ hashie post("friendships/create.json", :params => opt)
51
+ end
52
+
53
+ def destroy(opt={})
54
+ hashie post("friendships/destroy.json", :params => opt)
55
+ end
56
+
57
+ def remark_update(uid, remark, opt={})
58
+ hashie post("friendships/remark/update.json", :params => {:uid => uid, :remark => remark}.merge(opt))
59
+ end
60
+
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,83 @@
1
+ module WeiboOAuth2
2
+ module Api
3
+ module V2
4
+ class Location < Base
5
+
6
+ #read interfaces
7
+ def base_get_map_image(opt={})
8
+ hashie get("location/base/get_map_image.json", :params => opt)
9
+ end
10
+
11
+ def geo_ip_to_geo(ip, opt={})
12
+ hashie get("location/geo/ip_to_geo.json", :params => {:ip => ip}.merge(opt))
13
+ end
14
+
15
+ def geo_address_to_geo(address, opt={})
16
+ hashie get("location/geo/address_to_geo.json", :params => {:address => address}.merge(opt))
17
+ end
18
+
19
+ def geo_geo_to_address(coordinate, opt={})
20
+ hashie get("location/geo/geo_to_address.json", :params => {:coordinate => coordinate}.merge(opt))
21
+ end
22
+
23
+ def geo_gps_to_offset(coordinate, opt={})
24
+ hashie get("location/geo/gps_to_offset.json", :params => {:coordinate => coordinate}.merge(opt))
25
+ end
26
+
27
+ def geo_is_domestic(coordinates, opt={})
28
+ hashie get("location/geo/is_domestic.json", :params => {:coordinate => coordinate}.merge(opt))
29
+ end
30
+
31
+ def pois_show_batch(srcids, opt={})
32
+ hashie get("location/pois/show_batch.json", :params => {:srcids => srcids}.merge(opt))
33
+ end
34
+
35
+ def pois_search_by_location(opt={})
36
+ hashie get("location/pois/search/by_location.json", :params => opt)
37
+ end
38
+
39
+ def pois_search_by_geo(opt={})
40
+ hashie get("location/pois/search/by_geo.json", :params => opt)
41
+ end
42
+
43
+ def pois_search_by_area(coordinates, opt={})
44
+ hashie get("location/pois/search/by_area.json", :params => {:coordinate => coordinate}.merge(opt))
45
+ end
46
+
47
+ #to implement
48
+ def mobile_get_location(opt={})
49
+ #hashie get("location/mobile/get_location.json", :params => opt)
50
+ nil
51
+ end
52
+
53
+ def line_drive_route(opt={})
54
+ hashie get("location/line/drive_route.json", :params => opt)
55
+ end
56
+
57
+ def line_bus_route(opt={})
58
+ hashie get("location/line/bus_route.json", :params => opt)
59
+ end
60
+
61
+ def line_bus_line(q, opt={})
62
+ hashie get("location/line/bus_line.json", :params => {:q => q}.merge(opt))
63
+ end
64
+
65
+ def line_bus_station(q, opt={})
66
+ hashie get("location/line/bus_station.json", :params => {:q => q}.merge(opt))
67
+ end
68
+
69
+ #write interfaces
70
+ def pois_add(srcid, name, address, city_name, category, longitude, latitude, opt={})
71
+ hashie post("location/pois/add.json", :params => {:srcid => srcid,
72
+ :name => name,
73
+ :address => address,
74
+ :city_name => city_name,
75
+ :category => category,
76
+ :longitude => longitude,
77
+ :latitude => latitude}.merge(opt))
78
+ end
79
+
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,12 @@
1
+ module WeiboOAuth2
2
+ module Api
3
+ module V2
4
+ class Notification < Base
5
+ #read interfaces
6
+ def send(uids, tpl_id, opt={})
7
+ hashie get("notification/send", :params => {:uids => uids, :tpl_id => tpl_id}.merge(opt))
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,123 @@
1
+ module WeiboOAuth2
2
+ module Api
3
+ module V2
4
+ class Place < Base
5
+ #read interfaces
6
+ def public_timeline(opt={})
7
+ hashie get("place/public_timeline.json", :params => opt)
8
+ end
9
+
10
+ def friends_timeline(opt={})
11
+ hashie get("place/friends_timeline.json", :params => opt)
12
+ end
13
+
14
+ def user_timeline(uid, opt={})
15
+ hashie get("place/user_timeline.json", :params => {:uid => uid}.merge(opt))
16
+ end
17
+
18
+ def poi_timeline(poiid, opt={})
19
+ hashie get("place/poi_timeline.json", :params => {:poiid => poiid}.merge(opt))
20
+ end
21
+
22
+ def nearby_timeline(lat, long, opt={})
23
+ hashie get("place/nearby_timeline.json", :params => {:lat => lat, :long => long}.merge(opt))
24
+ end
25
+
26
+ def statuses_show(id, opt={})
27
+ hashie get("place/statuses/show.json", :params => {:id => id}.merge(opt))
28
+ end
29
+
30
+ def users_show(uid, opt={})
31
+ hashie get("place/users/show.json", :params => {:uid => uid}.merge(opt))
32
+ end
33
+
34
+ def users_checkins(uid, opt={})
35
+ hashie get("place/users/checkins.json", :params => {:uid => uid}.merge(opt))
36
+ end
37
+
38
+ def users_photos(uid, opt={})
39
+ hashie get("place/users/photos.json", :params => {:uid => uid}.merge(opt))
40
+ end
41
+
42
+ def users_tips(uid, opt={})
43
+ hashie get("place/users/tips.json", :params => {:uid => uid}.merge(opt))
44
+ end
45
+
46
+ def users_todos(uid, opt={})
47
+ hashie get("place/users/todos.json", :params => {:uid => uid}.merge(opt))
48
+ end
49
+
50
+ def pois_show(poiid, opt={})
51
+ hashie get("place/pois/show.json", :params => {:poiid => poiid}.merge(opt))
52
+ end
53
+
54
+ def pois_users(poiid, opt={})
55
+ hashie get("place/pois/users.json", :params => {:poiid => poiid}.merge(opt))
56
+ end
57
+
58
+ def pois_tips(poiid, opt={})
59
+ hashie get("place/pois/tips.json", :params => {:poiid => poiid}.merge(opt))
60
+ end
61
+
62
+ def pois_photos(poiid, opt={})
63
+ hashie get("place/pois/photos.json", :params => {:poiid => poiid}.merge(opt))
64
+ end
65
+
66
+ def pois_search(keyword, opt={})
67
+ hashie get("place/pois/search.json", :params => {:keyword => keyword}.merge(opt))
68
+ end
69
+
70
+ def pois_category(opt={})
71
+ hashie get("place/pois/category.json", :params => opt)
72
+ end
73
+
74
+ def nearby_pois(lat, long, opt={})
75
+ hashie get("place/nearby/pois.json", :params => {:lat => lat, :long => long}.merge(opt))
76
+ end
77
+
78
+ def nearby_users(lat, long, opt={})
79
+ hashie get("place/nearby/users.json", :params => {:lat => lat, :long => long}.merge(opt))
80
+ end
81
+
82
+ def nearby_photos(lat, long, opt={})
83
+ hashie get("place/nearby/photos.json", :params => {:lat => lat, :long => long}.merge(opt))
84
+ end
85
+
86
+ def nearby_users_list(lat, long, opt={})
87
+ hashie get("place/nearby_users/list.json", :params => {:lat => lat, :long => long}.merge(opt))
88
+ end
89
+
90
+
91
+ #write interfaces
92
+ def pois_create(title, lat, long, city, opt={})
93
+ hashie post("place/pois/create.json", :params => {:title => title, :lat => lat, :long => long, :city => city}.merge(opt))
94
+ end
95
+
96
+ def pois_add_checkin(poiid, status, opt={})
97
+ hashie post("place/pois/add_checkin.json", :params => {:poiid => poiid, :status => status}.merge(opt))
98
+ end
99
+
100
+ def pois_add_photo(poiid, status, pic, opt={})
101
+ multipart = Base.build_multipart_bodies({"poiid" => poiid, "status" => status, "pic" => pic}.merge(opt))
102
+ hashie post("place/pois/add_photo.json", :headers => multipart[:headers], :body => multipart[:body])
103
+ end
104
+
105
+ def pois_add_tip(poiid, status, opt={})
106
+ hashie post("place/pois/add_tip.json", :params => {:poiid => poiid, :status => status}.merge(opt))
107
+ end
108
+
109
+ def pois_add_todo(poiid, status, opt={})
110
+ hashie post("place/pois/create.json", :params => {:poiid => poiid, :status => status}.merge(opt))
111
+ end
112
+
113
+ def nearby_users_create(lat, long, opt={})
114
+ hashie post("place/nearby_users/create.json", :params => {:lat => lat, :long => long}.merge(opt))
115
+ end
116
+
117
+ def nearby_users_destroy(opt={})
118
+ hashie post("place/nearby_users/destroy.json", :params => opt)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,14 @@
1
+ module WeiboOAuth2
2
+ module Api
3
+ module V2
4
+ class Register < Base
5
+
6
+ #read interfaces
7
+ def verify_nickname(nickname, opt={})
8
+ hashie get("register/verify_nickname.json", :params => {:nickname => CGI::escape(nickname)}.merge(opt))
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end