raml_ruby 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/Rakefile +1 -0
- data/fixtures/include_1.raml +7 -0
- data/fixtures/schemas/canonicalSchemas.raml +3 -0
- data/fixtures/schemas/filesystem/file.json +1 -0
- data/fixtures/schemas/filesystem/files.json +1 -0
- data/fixtures/schemas/filesystem/fileupdate.json +1 -0
- data/fixtures/schemas/filesystem/relative/test.json +1 -0
- data/lib/raml.rb +104 -0
- data/lib/raml/exceptions.rb +27 -0
- data/lib/raml/mixin/bodies.rb +32 -0
- data/lib/raml/mixin/documentable.rb +32 -0
- data/lib/raml/mixin/global.rb +20 -0
- data/lib/raml/mixin/headers.rb +22 -0
- data/lib/raml/mixin/merge.rb +24 -0
- data/lib/raml/mixin/parent.rb +54 -0
- data/lib/raml/mixin/validation.rb +49 -0
- data/lib/raml/node.rb +219 -0
- data/lib/raml/node/abstract_method.rb +61 -0
- data/lib/raml/node/abstract_resource.rb +165 -0
- data/lib/raml/node/abstract_resource_circular.rb +5 -0
- data/lib/raml/node/body.rb +94 -0
- data/lib/raml/node/documentation.rb +28 -0
- data/lib/raml/node/header.rb +4 -0
- data/lib/raml/node/method.rb +106 -0
- data/lib/raml/node/parameter/abstract_parameter.rb +251 -0
- data/lib/raml/node/parameter/base_uri_parameter.rb +6 -0
- data/lib/raml/node/parameter/form_parameter.rb +6 -0
- data/lib/raml/node/parameter/query_parameter.rb +6 -0
- data/lib/raml/node/parameter/uri_parameter.rb +7 -0
- data/lib/raml/node/parametized_reference.rb +15 -0
- data/lib/raml/node/reference.rb +4 -0
- data/lib/raml/node/resource.rb +26 -0
- data/lib/raml/node/resource_type.rb +20 -0
- data/lib/raml/node/resource_type_reference.rb +5 -0
- data/lib/raml/node/response.rb +32 -0
- data/lib/raml/node/root.rb +246 -0
- data/lib/raml/node/schema.rb +41 -0
- data/lib/raml/node/schema_reference.rb +5 -0
- data/lib/raml/node/template.rb +55 -0
- data/lib/raml/node/trait.rb +18 -0
- data/lib/raml/node/trait_reference.rb +5 -0
- data/lib/raml/parser.rb +57 -0
- data/lib/raml/parser/include.rb +25 -0
- data/lib/raml/patch/hash.rb +6 -0
- data/lib/raml/patch/module.rb +12 -0
- data/lib/raml/version.rb +3 -0
- data/raml_ruby.gemspec +35 -0
- data/raml_spec_reqs.md +276 -0
- data/templates/abstract_parameter.slim +68 -0
- data/templates/body.slim +15 -0
- data/templates/collapse.slim +10 -0
- data/templates/documentation.slim +2 -0
- data/templates/method.slim +38 -0
- data/templates/resource.slim +33 -0
- data/templates/response.slim +13 -0
- data/templates/root.slim +39 -0
- data/templates/style.sass +119 -0
- data/test/apis/box-api.raml +4224 -0
- data/test/apis/instagram-api.raml +3378 -0
- data/test/apis/stripe-api.raml +12227 -0
- data/test/apis/twilio-rest-api.raml +6618 -0
- data/test/apis/twitter-rest-api.raml +34284 -0
- data/test/raml/body_spec.rb +268 -0
- data/test/raml/documentation_spec.rb +49 -0
- data/test/raml/header_spec.rb +17 -0
- data/test/raml/include_spec.rb +40 -0
- data/test/raml/method_spec.rb +701 -0
- data/test/raml/parameter/abstract_parameter_spec.rb +564 -0
- data/test/raml/parameter/form_parameter_spec.rb +17 -0
- data/test/raml/parameter/query_parameter_spec.rb +33 -0
- data/test/raml/parameter/uri_parameter_spec.rb +44 -0
- data/test/raml/parser_spec.rb +53 -0
- data/test/raml/raml_spec.rb +32 -0
- data/test/raml/resource_spec.rb +440 -0
- data/test/raml/resource_type_spec.rb +51 -0
- data/test/raml/response_spec.rb +251 -0
- data/test/raml/root_spec.rb +655 -0
- data/test/raml/schema_spec.rb +110 -0
- data/test/raml/spec_helper.rb +11 -0
- data/test/raml/template_spec.rb +98 -0
- data/test/raml/trait_spec.rb +31 -0
- metadata +337 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d5b2c3c690054638bb3cdf5dd0ea4fe0cf6e5763
|
4
|
+
data.tar.gz: 913d89301e7cb20ff0e6c73deb7b5104b93b4e47
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a2c78f5010254baab46d008805a257e78a0793486f0e339607a514d2622685c6d991550f53b463ed91b1ff03aead6ff3d5ee5077ba44880dc2c2483900b3d033
|
7
|
+
data.tar.gz: 1206b2996083539351d5ece371ee8451658c7994b7c9630ae99db5019b30bdd42933f673722ad28092dbc3be34da5aad19c5e63c681317d61747267db80d82ee
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014-2015 kgorin
|
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,71 @@
|
|
1
|
+
# RAML ruby
|
2
|
+
|
3
|
+
[](https://travis-ci.org/eliaslevy/raml_ruby)
|
4
|
+
|
5
|
+
Implementation of a RAML parser in Ruby. It uses the stdlib YAML parser
|
6
|
+
(Psych). It can also generate HTML documentation.
|
7
|
+
|
8
|
+
|
9
|
+
<!---
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
gem 'raml_ruby'
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install raml_ruby
|
23
|
+
-->
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Clone this repository:
|
28
|
+
|
29
|
+
git clone git@github.com:coub/raml_ruby.git
|
30
|
+
|
31
|
+
Require:
|
32
|
+
|
33
|
+
require 'lib/raml'
|
34
|
+
|
35
|
+
or
|
36
|
+
|
37
|
+
pry -r ./lib/raml.rb
|
38
|
+
|
39
|
+
To parse the file:
|
40
|
+
|
41
|
+
Raml.parse_file("path/to/your/file.raml")
|
42
|
+
|
43
|
+
To generate HTML documentation:
|
44
|
+
|
45
|
+
# write to file
|
46
|
+
Raml.document("/path/to/your/file.raml", "path/to/output/file.html")
|
47
|
+
|
48
|
+
# or just on screen
|
49
|
+
Raml.document("/path/to/your/file.raml")
|
50
|
+
|
51
|
+
## To Do
|
52
|
+
|
53
|
+
- Align mergin strategy of conflicting properties of resource types and traits with official Javascript and Java parsers.
|
54
|
+
- Security schemes
|
55
|
+
- Publish to Rubygems
|
56
|
+
|
57
|
+
More a more detailed analysis of the spec requirements and which ones are finishes see the [RAML requirements document](raml_spec_reqs.md).
|
58
|
+
|
59
|
+
## Contributing
|
60
|
+
|
61
|
+
1. Fork it
|
62
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
63
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
64
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
65
|
+
5. Create new Pull Request
|
66
|
+
|
67
|
+
## License
|
68
|
+
|
69
|
+
See [LICENSE](https://github.com/coub/raml_ruby/blob/master/LICENSE.txt).
|
70
|
+
|
71
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1 @@
|
|
1
|
+
file_schema
|
@@ -0,0 +1 @@
|
|
1
|
+
files_schema
|
@@ -0,0 +1 @@
|
|
1
|
+
file_update_schema
|
@@ -0,0 +1 @@
|
|
1
|
+
test_schema
|
data/lib/raml.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require_relative 'raml/version'
|
2
|
+
|
3
|
+
require_relative 'raml/patch/module'
|
4
|
+
require_relative 'raml/patch/hash'
|
5
|
+
|
6
|
+
require_relative 'raml/exceptions'
|
7
|
+
|
8
|
+
require_relative 'raml/parser'
|
9
|
+
require_relative 'raml/parser/include'
|
10
|
+
|
11
|
+
require_relative 'raml/mixin/bodies'
|
12
|
+
require_relative 'raml/mixin/documentable'
|
13
|
+
require_relative 'raml/mixin/global'
|
14
|
+
require_relative 'raml/mixin/headers'
|
15
|
+
require_relative 'raml/mixin/merge'
|
16
|
+
require_relative 'raml/mixin/parent'
|
17
|
+
require_relative 'raml/mixin/validation'
|
18
|
+
|
19
|
+
require_relative 'raml/node'
|
20
|
+
require_relative 'raml/node/reference'
|
21
|
+
require_relative 'raml/node/parametized_reference'
|
22
|
+
|
23
|
+
require_relative 'raml/node/parameter/abstract_parameter'
|
24
|
+
require_relative 'raml/node/parameter/form_parameter'
|
25
|
+
require_relative 'raml/node/parameter/query_parameter'
|
26
|
+
require_relative 'raml/node/parameter/uri_parameter'
|
27
|
+
require_relative 'raml/node/parameter/base_uri_parameter'
|
28
|
+
|
29
|
+
require_relative 'raml/node/schema'
|
30
|
+
require_relative 'raml/node/schema_reference'
|
31
|
+
|
32
|
+
require_relative 'raml/node/header'
|
33
|
+
require_relative 'raml/node/body'
|
34
|
+
require_relative 'raml/node/response'
|
35
|
+
|
36
|
+
require_relative 'raml/node/trait_reference'
|
37
|
+
require_relative 'raml/node/resource_type_reference'
|
38
|
+
|
39
|
+
require_relative 'raml/node/template'
|
40
|
+
|
41
|
+
require_relative 'raml/node/abstract_method'
|
42
|
+
require_relative 'raml/node/trait'
|
43
|
+
require_relative 'raml/node/method'
|
44
|
+
|
45
|
+
require_relative 'raml/node/abstract_resource'
|
46
|
+
require_relative 'raml/node/resource_type'
|
47
|
+
require_relative 'raml/node/resource'
|
48
|
+
require_relative 'raml/node/abstract_resource_circular'
|
49
|
+
|
50
|
+
require_relative 'raml/node/documentation'
|
51
|
+
|
52
|
+
require_relative 'raml/node/root'
|
53
|
+
|
54
|
+
module Raml
|
55
|
+
# Parses RAML from a string.
|
56
|
+
#
|
57
|
+
# @param raml [String] the string containing RAML.
|
58
|
+
# @return [Raml::Root] the RAML root node.
|
59
|
+
# @raise [RamlError] if the RAML is invalid.
|
60
|
+
def self.parse(raml)
|
61
|
+
Raml::Parser.parse raml
|
62
|
+
end
|
63
|
+
|
64
|
+
# Parses RAML from a file.
|
65
|
+
#
|
66
|
+
# @param filepath [String] the file path of the file containing RAML.
|
67
|
+
# @return [Raml::Root] the RAML root node.
|
68
|
+
# @raise [Errno::ENOENT] if the file can't be found.
|
69
|
+
# @raise [Errno::EACCES] if the file can't be read.
|
70
|
+
# @raise [RamlError] if the RAML is invalid.
|
71
|
+
def self.parse_file(filepath)
|
72
|
+
file = File.new filepath
|
73
|
+
raise UnsupportedRamlVersion unless file.readline =~ /\A#%RAML 0.8\s*\z/
|
74
|
+
|
75
|
+
path = File.dirname filepath
|
76
|
+
path = nil if path == ''
|
77
|
+
|
78
|
+
Raml::Parser.parse file.read, path
|
79
|
+
end
|
80
|
+
|
81
|
+
# Parses RAML from a file and generates API documentation in HTML. If no
|
82
|
+
# output filename argument is given, the HTML is returned as a string. If
|
83
|
+
# an output filename argument is given, the HTML is stored to a file at
|
84
|
+
# that location.
|
85
|
+
#
|
86
|
+
# @param filepath [String] the file path of the file containing RAML.
|
87
|
+
# @param out_file [String] the file path of the file to write the documentation to. Defaults to nil.
|
88
|
+
# @return [String] the HTML documentation, if out_file is nil.
|
89
|
+
# @raise [Errno::ENOENT] if the file can't be found.
|
90
|
+
# @raise [Errno::EACCES] if the files can't be read or written.
|
91
|
+
# @raise [RamlError] if the RAML is invalid.
|
92
|
+
def self.document(filepath, out_file=nil)
|
93
|
+
root = parse_file filepath
|
94
|
+
root.expand
|
95
|
+
|
96
|
+
if out_file
|
97
|
+
File.open(out_file, 'w') do |file|
|
98
|
+
file.write root.document
|
99
|
+
end
|
100
|
+
else
|
101
|
+
root.document
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Raml
|
2
|
+
class RamlError < StandardError; end
|
3
|
+
|
4
|
+
class UnsupportedRamlVersion < RamlError; end
|
5
|
+
class CantIncludeFile < RamlError; end
|
6
|
+
|
7
|
+
class RequiredPropertyMissing < RamlError; end
|
8
|
+
class InvalidProperty < RamlError; end
|
9
|
+
class UnknownProperty < RamlError; end
|
10
|
+
|
11
|
+
class InvalidParent < RamlError; end
|
12
|
+
class InvalidSchema < RamlError; end
|
13
|
+
|
14
|
+
class InvalidMethod < RamlError; end
|
15
|
+
|
16
|
+
class InvalidParameterType < RamlError; end
|
17
|
+
class InapplicableParameterAttribute < RamlError; end
|
18
|
+
class InvalidParameterAttribute < RamlError; end
|
19
|
+
|
20
|
+
class InvalidMediaType < RamlError; end
|
21
|
+
|
22
|
+
class UnknownTraitReference < RamlError; end
|
23
|
+
class UnknownResourceTypeReference < RamlError; end
|
24
|
+
class MergeError < RamlError; end
|
25
|
+
class UnknownTypeOrTraitParameter < RamlError; end
|
26
|
+
class UnknownTypeOrTraitParamFunction < RamlError; end
|
27
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Raml
|
2
|
+
module Bodies
|
3
|
+
# @!attribute [r] bodies
|
4
|
+
# @return [Hash<String, Raml::Body>] the bodies, keyed by their media type.
|
5
|
+
|
6
|
+
# XXX - need this line here to trigger Yard to generate docs for the above attribute.
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.instance_eval do
|
12
|
+
non_scalar_property :body
|
13
|
+
children_by :bodies, :media_type , Body
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_body(value)
|
18
|
+
if value.is_a? Hash and value.keys.all? {|k| k.is_a? String and k =~ /.+\/.+/ }
|
19
|
+
# If all keys looks like media types, its not a default media type body.
|
20
|
+
validate_hash 'body', value, String, Hash
|
21
|
+
value.map { |b_name, b_data| Body.new b_name, b_data, self }
|
22
|
+
|
23
|
+
else
|
24
|
+
# Its a default media type body.
|
25
|
+
validate_hash 'body', value, String
|
26
|
+
media_type = default_media_type
|
27
|
+
raise InvalidMediaType, 'Body with no media type, but default media type has not been declared.' unless media_type
|
28
|
+
Body.new media_type, value, self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'kramdown'
|
2
|
+
|
3
|
+
module Raml
|
4
|
+
module Documentable
|
5
|
+
# @!attribute [rw] display_name
|
6
|
+
# @return [String, nil] the node's display name.
|
7
|
+
|
8
|
+
# @!attribute [rw] description
|
9
|
+
# @return [String, nil] the node's description.
|
10
|
+
|
11
|
+
# @private
|
12
|
+
def html_description
|
13
|
+
Kramdown::Document.new(description, input: :GFM).to_html
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def self.included(base)
|
19
|
+
base.instance_eval do
|
20
|
+
scalar_property :display_name, :description
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_display_name
|
25
|
+
raise InvalidProperty, "displayName property mus be a string." unless display_name.is_a? String
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate_description
|
29
|
+
raise InvalidProperty, "description property mus be a string." unless description.is_a? String
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Raml
|
2
|
+
# @private
|
3
|
+
module Global
|
4
|
+
def default_media_type
|
5
|
+
@parent.default_media_type
|
6
|
+
end
|
7
|
+
|
8
|
+
def trait_declarations
|
9
|
+
@parent.trait_declarations
|
10
|
+
end
|
11
|
+
|
12
|
+
def resource_type_declarations
|
13
|
+
@parent.resource_type_declarations
|
14
|
+
end
|
15
|
+
|
16
|
+
def schema_declarations
|
17
|
+
@parent.schema_declarations
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Raml
|
2
|
+
module Headers
|
3
|
+
# @!attribute [r] headers
|
4
|
+
# @return [Hash<String, Raml::Header>] the headers, keyed by the header name.
|
5
|
+
|
6
|
+
# XXX - need this line here to trigger Yard to generate docs for the above attribute.
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.instance_eval do
|
12
|
+
non_scalar_property :headers
|
13
|
+
children_by :headers, :name, Header
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_headers(value)
|
18
|
+
validate_hash 'headers', value, String, Hash
|
19
|
+
value.map { |h_name, h_data| Header.new h_name, h_data, self }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Raml
|
2
|
+
# @private
|
3
|
+
module Merge
|
4
|
+
def merge(other)
|
5
|
+
other.scalar_properties.each do |prop|
|
6
|
+
prop_var = "@#{prop}"
|
7
|
+
prop_val = other.instance_variable_get prop_var
|
8
|
+
instance_variable_set prop_var, prop_val unless prop_val.nil?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def merge_properties(other, type)
|
13
|
+
match, no_match = other.send(type).values.partition { |param| self.send(type).has_key? param.name }
|
14
|
+
|
15
|
+
match.each { |param| self.send(type)[param.name].merge param }
|
16
|
+
|
17
|
+
# if its an optional property, and there is no match in self, don't merge it.
|
18
|
+
no_match.reject! { |node| node.optional }
|
19
|
+
no_match.map! { |node| node.clone }
|
20
|
+
no_match.each { |node| node.parent = self }
|
21
|
+
@children += no_match
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Raml
|
2
|
+
module Parent
|
3
|
+
# @!attribute [rw] children
|
4
|
+
# @return [Array<Raml::Node>] children nodes.
|
5
|
+
attr_accessor :children
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.extend ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
# @private
|
14
|
+
module ClassMethods
|
15
|
+
def child_of(name, type)
|
16
|
+
type = [ type ] unless type.is_a? Array
|
17
|
+
|
18
|
+
self.instance_eval do
|
19
|
+
define_method name do
|
20
|
+
@children.select { |child| type.include? child.class }.first
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def children_of(name, type)
|
26
|
+
type = [ type ] unless type.is_a? Array
|
27
|
+
|
28
|
+
self.instance_eval do
|
29
|
+
define_method name do
|
30
|
+
@children.select { |child| type.include? child.class }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def children_by(name, key, type, merge_parents=false)
|
36
|
+
self.instance_eval do
|
37
|
+
define_method name do
|
38
|
+
result = Hash[
|
39
|
+
@children.
|
40
|
+
select { |child| child.is_a? type }.
|
41
|
+
map { |child| [ child.send(key.to_sym), child ] }
|
42
|
+
]
|
43
|
+
|
44
|
+
if merge_parents and parent and parent.respond_to? name
|
45
|
+
result = parent.send(name).merge result
|
46
|
+
end
|
47
|
+
|
48
|
+
result
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|