restfulie 0.5.0 → 0.6.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.
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
5
5
  require 'spec/rake/spectask'
6
6
 
7
7
  GEM = "restfulie"
8
- GEM_VERSION = "0.5.0"
8
+ GEM_VERSION = "0.6.0"
9
9
  SUMMARY = "Hypermedia aware resource based library in ruby (client side) and ruby on rails (server side)."
10
10
  AUTHOR = "Guilherme Silveira, Caue Guerra"
11
11
  EMAIL = "guilherme.silveira@caelum.com.br"
@@ -18,7 +18,7 @@ spec = Gem::Specification.new do |s|
18
18
  s.summary = SUMMARY
19
19
  s.require_paths = ['lib']
20
20
  s.files = FileList['lib/**/*.rb', '[A-Z]*'].to_a
21
- # s.add_dependency("ratom", [">= 0.6.3"])
21
+ s.add_dependency("ratom", [">= 0.6.3"])
22
22
  # s.add_dependency("jeokkarak", [">= 1.0.3"])
23
23
 
24
24
  # s.add_dependency(%q<rubigen>, [">= 1.3.4"])
@@ -1,3 +1,20 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
1
18
  module Restfulie
2
19
 
3
20
  # TODO break media type registering for DECODING and ENCODING appart, so we can have two files
@@ -23,7 +40,7 @@ module Restfulie
23
40
  # retrieves the nth element from an atom feed
24
41
  def [](position)
25
42
 
26
- hash = entry[position].content.hash
43
+ hash = entry[position].content.internal_hash
27
44
  hash = hash.dup
28
45
  hash.delete("type")
29
46
  result = Restfulie::MediaType::DefaultMediaTypeDecoder.from_hash(hash)
@@ -36,7 +53,7 @@ module Restfulie
36
53
  private
37
54
 
38
55
  def add_links_to(result, entry)
39
- links = entry.link.hash
56
+ links = entry.link.internal_hash
40
57
  links = [links] if links.kind_of? Hash
41
58
  self_definition = self_from(links)
42
59
  links << {:rel => "destroy", :method => "delete", :href => self_definition["href"]} unless self_definition.nil?
@@ -1,3 +1,20 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
1
18
  class Hash
2
19
  def to_object(body)
3
20
  if keys.length>1
@@ -15,46 +32,34 @@ module Restfulie
15
32
 
16
33
  # will execute some action in a specific URI
17
34
  def self.at(uri)
18
- Client::RequestExecution.new(nil).at uri
35
+ Client::RequestExecution.new(nil, nil).at uri
19
36
  end
20
-
37
+
21
38
  module Client
22
- module Base
39
+ module Config
40
+ BASIC_MAPPING = { :delete => Net::HTTP::Delete, :put => Net::HTTP::Put, :get => Net::HTTP::Get, :post => Net::HTTP::Post}
41
+ DEFAULTS = { :destroy => Net::HTTP::Delete, :delete => Net::HTTP::Delete, :cancel => Net::HTTP::Delete,
42
+ :refresh => Net::HTTP::Get, :reload => Net::HTTP::Get, :show => Net::HTTP::Get, :latest => Net::HTTP::Get, :self => Net::HTTP::Get}
23
43
 
24
- SELF_RETRIEVAL = [:latest, :refresh, :reload]
25
-
26
- # translates a response to an object
27
- def from_response(res, invoking_object)
28
-
29
- return invoking_object if res.code=="304"
30
-
31
- raise UnsupportedContentType.new("unsupported content type '#{res.content_type}' '#{res.code}'") unless res.content_type=="application/xml"
32
-
33
- # TODO this method should use the RequestExecution process to parse the content type and body
34
- # TODO add default html parser: do nothin
35
-
36
- body = res.body
37
- return {} if body.empty?
38
-
39
- hash = Hash.from_xml body
40
- hash.to_object(body)
41
-
44
+ def self.self_retrieval
45
+ [:latest, :refresh, :reload, :self]
46
+ end
47
+
48
+ def self.requisition_method_for(overriden_option,name)
49
+ return BASIC_MAPPING[overriden_option.to_sym] if overriden_option
50
+ DEFAULTS[name.to_sym] || Net::HTTP::Post
42
51
  end
43
52
 
44
- def requisition_method_for(overriden_option,name)
45
- basic_mapping = { :delete => Net::HTTP::Delete, :put => Net::HTTP::Put, :get => Net::HTTP::Get, :post => Net::HTTP::Post}
46
- defaults = {:destroy => Net::HTTP::Delete, :delete => Net::HTTP::Delete, :cancel => Net::HTTP::Delete,
47
- :refresh => Net::HTTP::Get, :reload => Net::HTTP::Get, :show => Net::HTTP::Get, :latest => Net::HTTP::Get, :self => Net::HTTP::Get}
53
+ end
54
+
55
+ module Base
48
56
 
49
- return basic_mapping[overriden_option.to_sym] if overriden_option
50
- defaults[name.to_sym] || Net::HTTP::Post
51
- end
52
-
53
57
  def is_self_retrieval?(name)
54
58
  name = name.to_sym if name.kind_of? String
55
- SELF_RETRIEVAL.include? name
59
+ Restfulie::Client::Config.self_retrieval.include? name
56
60
  end
57
-
61
+
58
62
  end
63
+
59
64
  end
60
65
  end
@@ -0,0 +1,103 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ # Basic cache implementation for restfulie.
19
+ #
20
+ # It uses the request headers and uri to store it in memory.
21
+ # This cache might not be optimal for long running clients, which should use a memcached based one.
22
+ # Use Restfulie.cache_provider to change the provider
23
+ class Restfulie::BasicCache
24
+
25
+ def put(url, req, response)
26
+ if Restfulie::Cache::Restrictions.may_cache?(req, response)
27
+ Restfulie.logger.debug "caching #{url} #{req} #{response}"
28
+ cache[key_for(url, req)] = response
29
+ end
30
+ response
31
+ end
32
+
33
+ def get(url, req)
34
+
35
+ response = cache[key_for(url, req)]
36
+ return nil if response.nil?
37
+
38
+ if response.has_expired_cache?
39
+ remove(key_for(url, req))
40
+ else
41
+ Restfulie.logger.debug "RETURNING cache #{url} #{req}"
42
+ response
43
+ end
44
+
45
+ end
46
+
47
+ # removes all elements from the cache
48
+ def clear
49
+ cache.clear
50
+ end
51
+
52
+ private
53
+
54
+ def remove(what)
55
+ @cache.delete(what)
56
+ nil
57
+ end
58
+
59
+ def cache
60
+ @cache ||= {}
61
+ end
62
+
63
+ def key_for(url, req)
64
+ [url, req.class]
65
+ end
66
+
67
+ end
68
+
69
+ # Fake cache that does not cache anything
70
+ # Use Restfulie.cache_provider = Restfulie::FakeCache.new
71
+ class Restfulie::FakeCache
72
+
73
+ def put(url, req, response)
74
+ response
75
+ end
76
+
77
+ def get(url, req)
78
+ end
79
+
80
+ def clear
81
+ end
82
+
83
+ end
84
+
85
+ module Restfulie::Cache
86
+ module Restrictions
87
+
88
+ class << self
89
+
90
+ # checks whether this request verb and its cache headers allow caching
91
+ def may_cache?(request,response)
92
+ may_cache_method?(request) && response.may_cache?
93
+ end
94
+
95
+ # only Post and Get requests are cacheable so far
96
+ def may_cache_method?(verb)
97
+ verb.kind_of?(Net::HTTP::Post) || verb.kind_of?(Net::HTTP::Get)
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+ end
@@ -1,3 +1,20 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
1
18
  module Restfulie
2
19
  module Client
3
20
  module Base
@@ -22,12 +39,12 @@ module Restfulie
22
39
 
23
40
  # retrieves a resource form a specific uri
24
41
  def from_web(uri, options = {})
25
- RequestExecution.new(self).at(uri).get(options)
42
+ RequestExecution.new(self, nil).at(uri).get(options)
26
43
  end
27
44
 
28
45
  private
29
46
  def remote_post(content)
30
- RequestExecution.new(self).at(entry_point_for.create.uri).post(content)
47
+ RequestExecution.new(self, nil).at(entry_point_for.create.uri).post(content)
31
48
  end
32
49
 
33
50
  end
@@ -0,0 +1,116 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'time'
19
+
20
+ # an extesion to http responses
21
+ module Restfulie::Client::HTTPResponse
22
+
23
+ attr_accessor :previous
24
+
25
+ # determines if this response code was successful (according to http specs: 200~299)
26
+ def is_successful?
27
+ code.to_i >= 200 && code.to_i <= 299
28
+ end
29
+
30
+ # determines if this response code was successful (according to http specs: 100~199)
31
+ def is_informational?
32
+ code.to_i >= 100 && code.to_i <= 199
33
+ end
34
+
35
+ # determines if this response code was successful (according to http specs: 300~399)
36
+ def is_redirection?
37
+ code.to_i >= 300 && code.to_i <= 399
38
+ end
39
+
40
+ # determines if this response code was successful (according to http specs: 400~499)
41
+ def is_client_error?
42
+ code.to_i >= 400 && code.to_i <= 499
43
+ end
44
+
45
+ # determines if this response code was successful (according to http specs: 500~599)
46
+ def is_server_error?
47
+ code.to_i >= 500 && code.to_i <= 599
48
+ end
49
+
50
+ def etag
51
+ self['Etag']
52
+ end
53
+
54
+ def last_modified
55
+ self['Last-Modified']
56
+ end
57
+
58
+ end
59
+
60
+ module Restfulie::Client::HTTPResponse
61
+
62
+ def cache_max_age
63
+ val = header_value_from('Cache-control', /^\s*max-age=(\d+)/)
64
+ if val
65
+ val.to_i
66
+ else
67
+ 0
68
+ end
69
+ end
70
+
71
+ def header_value_from(header, expression)
72
+ h = value_for(get_fields(header)[0], expression)
73
+ return nil if h.nil?
74
+ h.match(expression)[1]
75
+ end
76
+
77
+ def has_expired_cache?
78
+ return true if self['Date'].nil?
79
+ Time.now > Time.rfc2822(self['Date']) + cache_max_age.seconds
80
+ end
81
+
82
+ # checks if the header's max-age is available and no no-store if available.
83
+ def may_cache?
84
+ may_cache_field?(get_fields('Cache-control'))
85
+ end
86
+
87
+ def may_cache_field?(field)
88
+ return false if field.nil?
89
+
90
+ if field.kind_of? Array
91
+ field.each do |f|
92
+ return false if !may_cache_field?(f)
93
+ end
94
+ return true
95
+ end
96
+
97
+ max_age_header = value_for(field, /^max-age=(\d+)/)
98
+ return false if max_age_header.nil?
99
+ max_age = max_age_header[1]
100
+
101
+ return false if value_for(field, /^no-store/)
102
+
103
+ true
104
+ end
105
+
106
+ # extracts the header value for an specific expression, which can be located at the start or in the middle
107
+ # of the expression
108
+ def value_for(value, expression)
109
+ value.split(",").find { |obj| obj.strip =~ expression }
110
+ end
111
+
112
+ end
113
+
114
+ class Net::HTTPResponse
115
+ include Restfulie::Client::HTTPResponse
116
+ end
@@ -1,3 +1,20 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
1
18
  module Restfulie
2
19
  module Client
3
20
  module Helper
@@ -1,134 +1,158 @@
1
- module Restfulie
2
- module Client
3
- module Instance
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module Restfulie::Client::Instance
4
19
 
5
- # list of possible states to access
6
- def _possible_states
7
- @_possible_states ||= {}
8
- end
9
-
10
- # which content-type generated this data
11
- attr_accessor :_came_from
12
-
13
- def invoke_remote_transition(name, options, block)
14
-
15
- method = self.class.requisition_method_for options[:method], name
16
-
17
- state = self._possible_states[name]
18
- url = URI.parse(state["href"] || state[:href])
19
- req = method.new(url.path)
20
- req.body = options[:data] if options[:data]
21
- add_request_headers(req, name)
22
-
23
- response = Net::HTTP.new(url.host, url.port).request(req)
20
+ # list of possible states to access
21
+ def existing_relations
22
+ @existing_relations ||= {}
23
+ end
24
24
 
25
- return block.call(response) if block
26
- return response unless method == Net::HTTP::Get
27
- self.class.from_response response, self
28
- end
29
-
30
- private
31
- def add_request_headers(req, name)
32
- req.add_field("Accept", "application/xml") if self._came_from == :xml
33
- req.add_field("If-None-Match", self.etag) if self.class.is_self_retrieval?(name) && self.respond_to?(:etag)
34
- req.add_field("If-Modified-Since", self.last_modified) if self.class.is_self_retrieval?(name) && self.respond_to?(:last_modified)
25
+ # which content-type generated this data
26
+ attr_reader :_came_from
27
+
28
+ # prepares a new request
29
+ def request
30
+ Restfulie::Client::RequestExecution.new(self.class, self)
31
+ end
32
+
33
+ # parse arguments from a transition invocation (or relation)
34
+ # it will receive either zero, one or two args, if there are two args, return them
35
+ # if there is one hash arg, its the options, add a data = nil
36
+ # if there is one arg (not a hash), its the data, add a options = {}
37
+ # if there are no args, data is nil and options = {}
38
+ def parse_args_from_transition(args)
39
+ data = nil
40
+ if args.nil? || args.size==0
41
+ options = {}
42
+ elsif args.size==1
43
+ if args[0].kind_of?(Hash)
44
+ options = args[0]
45
+ else
46
+ data = args[0]
47
+ options = {}
35
48
  end
49
+ elsif args.size==2
50
+ data = args[0]
51
+ options = args[1] || {}
52
+ end
53
+ [data, options]
54
+ end
55
+
56
+ def invoke_remote_transition(name, args, block = nil)
57
+
58
+ data, options = parse_args_from_transition(args)
59
+
60
+ method = Restfulie::Client::Config.requisition_method_for options[:method], name
61
+ state = self.existing_relations[name]
62
+
63
+ request = Restfulie::Client::RequestExecution.new(self.class, self).at(state["href"] || state[:href]).with(options[:headers])
64
+ request.do method, name, data
36
65
 
37
- public
66
+ end
38
67
 
39
-
40
- # inserts all links from this object as can_xxx and xxx methods
41
- def add_transitions(links)
42
-
43
- links.each do |t|
44
- self._possible_states[t["rel"] || t[:rel]] = t
45
- self.add_state(t)
46
- end
47
- self.extend Restfulie::Client::State
48
- end
68
+ # inserts all links from this object as can_xxx and xxx methods
69
+ def add_transitions(links)
70
+ links.each do |t|
71
+ self.existing_relations[t["rel"] || t[:rel]] = t
72
+ self.add_state(t)
73
+ end
74
+ self.extend Restfulie::Client::State
75
+ end
49
76
 
77
+ # adds the specific information for one state change or related resource
78
+ def add_state(transition)
79
+ name = transition["rel"] || transition[:rel]
80
+
81
+ # TODO: wrong, should be instance_eval
82
+ self.class.module_eval do
50
83
 
51
- def add_state(transition)
52
- name = transition["rel"] || transition[:rel]
53
-
54
- # TODO: wrong, should be instance_eval
55
- self.class.module_eval do
56
-
57
- def temp_method(options = {}, &block)
58
- self.invoke_remote_transition(Restfulie::Client::Helper.current_method, options, block)
59
- end
60
-
61
- alias_method name, :temp_method
62
- undef :temp_method
63
- end
64
- end
65
-
66
- # returns a list of extended fields for this instance.
67
- # extended fields are those unknown to this model but kept in a hash
68
- # to allow forward-compatibility.
69
- def extended_fields
70
- @hash ||= {}
71
- @hash
84
+ def temp_method(*args, &block)
85
+ self.invoke_remote_transition(Restfulie::Client::Helper.current_method, args, block)
72
86
  end
87
+
88
+ alias_method name, :temp_method
89
+ undef :temp_method
90
+ end
91
+ end
92
+
93
+ # returns a list of extended fields for this instance.
94
+ # extended fields are those unknown to this model but kept in a hash
95
+ # to allow forward-compatibility.
96
+ def extended_fields
97
+ @extended_fields ||= {}
98
+ @extended_fields
99
+ end
73
100
 
74
- def method_missing(name, *args)
75
- name = name.to_s if name.kind_of? Symbol
76
-
77
- if name[-1,1] == "="
78
- extended_fields[name.chop] = args[0]
79
- elsif name[-1,1] == "?"
80
- found = extended_fields[name.chop]
81
- return super(name,args) if found.nil?
82
- parse(found)
83
- else
84
- found = extended_fields[name]
85
- return super(name,args) if found.nil?
86
- parse(transform(found))
87
- end
101
+ def method_missing(name, *args)
102
+ name = name.to_s if name.kind_of? Symbol
103
+
104
+ if name[-1,1] == "="
105
+ extended_fields[name.chop] = args[0]
106
+ elsif name[-1,1] == "?"
107
+ found = extended_fields[name.chop]
108
+ return super(name,args) if found.nil?
109
+ parse(found)
110
+ else
111
+ found = extended_fields[name]
112
+ return super(name,args) if found.nil?
113
+ parse(transform(found))
114
+ end
88
115
 
89
- end
116
+ end
90
117
 
91
- # TODO test this guy
92
- def respond_to?(sym)
93
- extended_fields[sym.to_s].nil? ? super(sym) : true
94
- end
118
+ def respond_to?(sym)
119
+ extended_fields[sym.to_s].nil? ? super(sym) : true
120
+ end
95
121
 
96
- # redefines attribute definition allowing the invocation of method_missing
97
- # when an attribute does not exist
98
- def attributes=(values)
99
- values.each do |key, value|
100
- unless attributes.include? key
101
- method_missing("#{key}=", value)
102
- values.delete key
103
- end
104
- end
105
- super(values)
122
+ # redefines attribute definition allowing the invocation of method_missing
123
+ # when an attribute does not exist
124
+ def attributes=(values)
125
+ values.each do |key, value|
126
+ unless attributes.include? key
127
+ method_missing("#{key}=", value)
128
+ values.delete key
106
129
  end
130
+ end
131
+ super(values)
132
+ end
107
133
 
108
134
 
109
- # serializes the extended fields with the existing fields
110
- def to_xml(options={})
111
- super(options) do |xml|
112
- extended_fields.each do |key,value|
113
- xml.tag! key, value
114
- end
115
- end
135
+ # serializes the extended fields with the existing fields
136
+ def to_xml(options={})
137
+ super(options) do |xml|
138
+ extended_fields.each do |key,value|
139
+ xml.tag! key, value
116
140
  end
141
+ end
142
+ end
117
143
 
118
- private
119
-
120
- # transforms a value in a custom hash
121
- def transform(value)
122
- return CustomHash.new(value) if value.kind_of?(Hash) || value.kind_of?(Array)
123
- value
124
- end
125
-
126
- def parse(val)
127
- raise "undefined method: '#{val}'" if val.nil?
128
- val
129
- end
144
+ private
145
+
146
+ # transforms a value in a custom hash
147
+ def transform(value)
148
+ return CustomHash.new(value) if value.kind_of?(Hash) || value.kind_of?(Array)
149
+ value
150
+ end
151
+
152
+ def parse(val)
153
+ raise "undefined method: '#{val}'" if val.nil?
154
+ val
155
+ end
130
156
 
131
157
 
132
- end
133
- end
134
158
  end