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
@@ -0,0 +1,15 @@
|
|
1
|
+
module Raml
|
2
|
+
class ParametizedReference < Reference
|
3
|
+
# @!attribute [rw] parameters
|
4
|
+
# @return [Hash<String,String>] parameters to interpolate when instantiating the resouce type or trait.
|
5
|
+
attr_accessor :parameters
|
6
|
+
|
7
|
+
# @param name [String] the resource type or trait name.
|
8
|
+
# @param parameters [Hash<String,String>] parameters to interpolate when instantiating the resouce type or trait.
|
9
|
+
# @param parent [Raml::Node] the parent node.
|
10
|
+
def initialize(name, parameters={}, parent)
|
11
|
+
super name, parent
|
12
|
+
@parameters = parameters
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Raml
|
2
|
+
class Resource < AbstractResource
|
3
|
+
inherit_class_attributes
|
4
|
+
|
5
|
+
regexp_property( /\A\//, ->(key,value) { Resource.new key, value, self } )
|
6
|
+
|
7
|
+
# @!attribute [r] resources
|
8
|
+
# @return [Hash<String, Raml::Resource>] the nested resources, keyed by the resource relative path.
|
9
|
+
|
10
|
+
children_by :resources, :name, Resource
|
11
|
+
|
12
|
+
self.doc_template = relative_path 'resource.slim'
|
13
|
+
|
14
|
+
# @private
|
15
|
+
def apply_resource_type
|
16
|
+
super
|
17
|
+
resources.values.each(&:apply_resource_type)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @private
|
21
|
+
def apply_traits
|
22
|
+
methods.values.each(&:apply_traits)
|
23
|
+
resources.values.each(&:apply_traits)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Raml
|
2
|
+
class ResourceType < Template
|
3
|
+
class Instance < AbstractResource
|
4
|
+
inherit_class_attributes
|
5
|
+
|
6
|
+
# @!attribute [rw] usage
|
7
|
+
# @return [String,nil] how the resource type should be used.
|
8
|
+
scalar_property :usage
|
9
|
+
end
|
10
|
+
|
11
|
+
# Instantiate a new resource type with the given parameters.
|
12
|
+
# @param params [Hash] the parameters to interpolate in the resource type.
|
13
|
+
# @return [Raml::ResourceType::Instance] the instantiated resouce type.
|
14
|
+
def instantiate(params)
|
15
|
+
instance = Instance.new( *interpolate(params), @parent )
|
16
|
+
instance.apply_resource_type
|
17
|
+
instance
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Raml
|
2
|
+
class Response < PropertiesNode
|
3
|
+
inherit_class_attributes
|
4
|
+
|
5
|
+
include Documentable
|
6
|
+
include Global
|
7
|
+
include Merge
|
8
|
+
include Parent
|
9
|
+
include Validation
|
10
|
+
include Bodies
|
11
|
+
include Headers
|
12
|
+
|
13
|
+
self.doc_template = relative_path 'response.slim'
|
14
|
+
|
15
|
+
def initialize(name, properties, parent)
|
16
|
+
super
|
17
|
+
@name = name.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
# @private
|
21
|
+
def merge(other)
|
22
|
+
raise MergeError, "Response status codes don't match." if name != other.name
|
23
|
+
|
24
|
+
super
|
25
|
+
|
26
|
+
merge_properties other, :headers
|
27
|
+
merge_properties other, :bodies
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'sass'
|
2
|
+
require 'uri'
|
3
|
+
require 'uri_template'
|
4
|
+
|
5
|
+
module Raml
|
6
|
+
# RAML root node. Its parent is itself.
|
7
|
+
class Root < PropertiesNode
|
8
|
+
inherit_class_attributes
|
9
|
+
|
10
|
+
include Parent
|
11
|
+
include Validation
|
12
|
+
|
13
|
+
# @!attribute [rw] title
|
14
|
+
# @return [String] API title.
|
15
|
+
|
16
|
+
# @!attribute [rw] version
|
17
|
+
# @return [String,nil] API version.
|
18
|
+
|
19
|
+
# @!attribute [rw] base_uri
|
20
|
+
# @return [String] the API base URI.
|
21
|
+
|
22
|
+
# @!attribute [rw] protocols
|
23
|
+
# @return [Array<String>, nil] the supported protocols. Nil or an array of up to two string
|
24
|
+
# elements from the set "HTTP" and "HTTPS".
|
25
|
+
|
26
|
+
# @!attribute [rw] media_type
|
27
|
+
# @return [String] the default request and response body media type.
|
28
|
+
|
29
|
+
# @!attribute [r] documents
|
30
|
+
# @return [Array<Raml::Documentation>] the top level documentation.
|
31
|
+
|
32
|
+
# @!attribute [r] base_uri_parameters
|
33
|
+
# @return [Hash<String, Raml::Parameter::BaseUriParameter>] the base URI parameters, keyed
|
34
|
+
# by the parameter name.
|
35
|
+
|
36
|
+
# @!attribute [r] schemas
|
37
|
+
# @return [Hash<String, Raml::Schema>] the schema definitions, keyed by the schema name.
|
38
|
+
|
39
|
+
# @!attribute [r] resources
|
40
|
+
# @return [Hash<String, Raml::Resource>] the nested resources, keyed by the resource relative path.
|
41
|
+
|
42
|
+
# @!attribute [r] traits
|
43
|
+
# @return [Hash<String, Raml::Trait>] the trait definitions, keyed by the trait name.
|
44
|
+
|
45
|
+
# @!attribute [r] resource_types
|
46
|
+
# @return [Hash<String, Raml::ResourceType>] the resource type definitions, keyed by the resource type name.
|
47
|
+
|
48
|
+
scalar_property :title , :version , :base_uri ,
|
49
|
+
:protocols , :media_type
|
50
|
+
|
51
|
+
non_scalar_property :base_uri_parameters, :documentation , :schemas, :secured_by,
|
52
|
+
:security_schemes , :resource_types, :traits
|
53
|
+
|
54
|
+
regexp_property( /\A\//, ->(key,value) { Resource.new key, value, self } )
|
55
|
+
|
56
|
+
children_of :documents, Documentation
|
57
|
+
|
58
|
+
children_by :base_uri_parameters, :name, Parameter::BaseUriParameter
|
59
|
+
children_by :resources , :name, Resource
|
60
|
+
children_by :schemas , :name, Schema
|
61
|
+
children_by :traits , :name, Trait
|
62
|
+
children_by :resource_types , :name, ResourceType
|
63
|
+
|
64
|
+
alias :default_media_type :media_type
|
65
|
+
alias :trait_declarations :traits
|
66
|
+
alias :resource_type_declarations :resource_types
|
67
|
+
alias :schema_declarations :schemas
|
68
|
+
|
69
|
+
self.doc_template = relative_path 'root.slim'
|
70
|
+
|
71
|
+
def initialize(root_data)
|
72
|
+
super nil, root_data, self
|
73
|
+
end
|
74
|
+
|
75
|
+
# Applies resource types and traits, and inlines schemas. It should be called
|
76
|
+
# before documentation is generated.
|
77
|
+
def expand
|
78
|
+
unless @expanded
|
79
|
+
resources.values.each(&:apply_resource_type)
|
80
|
+
resources.values.each(&:apply_traits)
|
81
|
+
inline_reference SchemaReference, schemas, @children
|
82
|
+
@expanded = true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @private
|
87
|
+
def resource_path
|
88
|
+
''
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def validate
|
94
|
+
raise RequiredPropertyMissing, 'Missing root title property.' if title.nil?
|
95
|
+
raise RequiredPropertyMissing, 'Missing root baseUri property' if base_uri.nil?
|
96
|
+
_validate_base_uri
|
97
|
+
end
|
98
|
+
|
99
|
+
def validate_title
|
100
|
+
validate_string :title, title
|
101
|
+
end
|
102
|
+
|
103
|
+
def _validate_base_uri
|
104
|
+
validate_string :base_uri, base_uri
|
105
|
+
|
106
|
+
# Check whether its a URL.
|
107
|
+
uri = parse_uri base_uri
|
108
|
+
|
109
|
+
# If the parser doesn't think its a URL or the URL is not for HTTP or HTTPS,
|
110
|
+
# try to parse it as a URL template.
|
111
|
+
if uri.nil? and not uri.kind_of? URI::HTTP
|
112
|
+
template = parse_template
|
113
|
+
|
114
|
+
# The template parser did not complain, but does it generate valid URLs?
|
115
|
+
uri = template.expand Hash[ template.variables.map {|var| [ var, 'a'] } ]
|
116
|
+
uri = parse_uri uri
|
117
|
+
raise InvalidProperty, 'baseUri property is not a URL or a URL template.' unless
|
118
|
+
uri and uri.kind_of? URI::HTTP
|
119
|
+
|
120
|
+
raise RequiredPropertyMissing, 'version property is required when baseUri template has version parameter' if
|
121
|
+
template.variables.include? 'version' and version.nil?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def validate_protocols
|
126
|
+
if protocols
|
127
|
+
validate_array :protocols, protocols, String
|
128
|
+
|
129
|
+
@protocols.map!(&:upcase)
|
130
|
+
|
131
|
+
raise InvalidProperty, 'protocols property elements must be HTTP or HTTPS' unless
|
132
|
+
protocols.all? { |p| [ 'HTTP', 'HTTPS'].include? p }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def validate_media_type
|
137
|
+
if media_type
|
138
|
+
validate_string :media_type, media_type
|
139
|
+
raise InvalidProperty, 'mediaType property is malformed' unless media_type =~ Body::MEDIA_TYPE_RE
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def parse_schemas(schemas)
|
144
|
+
validate_array :schemas, schemas, Hash
|
145
|
+
|
146
|
+
raise InvalidProperty, 'schemas property must be an array of maps with string keys' unless
|
147
|
+
schemas.all? {|s| s.keys.all? {|k| k.is_a? String }}
|
148
|
+
|
149
|
+
raise InvalidProperty, 'schemas property must be an array of maps with string values' unless
|
150
|
+
schemas.all? {|s| s.values.all? {|v| v.is_a? String }}
|
151
|
+
|
152
|
+
raise InvalidProperty, 'schemas property contains duplicate schema names' unless
|
153
|
+
schemas.map(&:keys).flatten.uniq!.nil?
|
154
|
+
|
155
|
+
schemas.reduce({}) { |memo, map | memo.merge! map }.
|
156
|
+
map { |name, data| Schema.new name, data, self }
|
157
|
+
end
|
158
|
+
|
159
|
+
def parse_base_uri_parameters(base_uri_parameters)
|
160
|
+
validate_hash :base_uri_parameters, base_uri_parameters, String, Hash
|
161
|
+
|
162
|
+
raise InvalidProperty, 'baseUriParameters property can\'t contain reserved "version" parameter' if
|
163
|
+
base_uri_parameters.include? 'version'
|
164
|
+
|
165
|
+
base_uri_parameters.map { |name, data| Parameter::BaseUriParameter.new name, data, self }
|
166
|
+
end
|
167
|
+
|
168
|
+
def parse_documentation(documentation)
|
169
|
+
validate_array :documentation, documentation
|
170
|
+
|
171
|
+
raise InvalidProperty, 'documentation property must include at least one document or not be included' if
|
172
|
+
documentation.empty?
|
173
|
+
|
174
|
+
documentation.map { |doc| doc = doc.dup; Documentation.new doc.delete("title"), doc, self }
|
175
|
+
end
|
176
|
+
|
177
|
+
def parse_secured_by(data)
|
178
|
+
# XXX ignored for now
|
179
|
+
end
|
180
|
+
|
181
|
+
def parse_security_schemes(data)
|
182
|
+
# XXX ignored for now
|
183
|
+
end
|
184
|
+
|
185
|
+
def parse_resource_types(types)
|
186
|
+
validate_array :resource_types, types, Hash
|
187
|
+
|
188
|
+
raise InvalidProperty, 'resourceTypes property must be an array of maps with string keys' unless
|
189
|
+
types.all? {|t| t.keys.all? {|k| k.is_a? String }}
|
190
|
+
|
191
|
+
raise InvalidProperty, 'resourceTypes property must be an array of maps with map values' unless
|
192
|
+
types.all? {|t| t.values.all? {|v| v.is_a? Hash }}
|
193
|
+
|
194
|
+
raise InvalidProperty, 'resourceTypes property contains duplicate type names' unless
|
195
|
+
types.map(&:keys).flatten.uniq!.nil?
|
196
|
+
|
197
|
+
types.reduce({}) { |memo, map | memo.merge! map }.
|
198
|
+
map { |name, data| ResourceType.new name, data, self }
|
199
|
+
end
|
200
|
+
|
201
|
+
def parse_traits(traits)
|
202
|
+
validate_array :traits, traits, Hash
|
203
|
+
|
204
|
+
raise InvalidProperty, 'traits property must be an array of maps with string keys' unless
|
205
|
+
traits.all? {|t| t.keys.all? {|k| k.is_a? String }}
|
206
|
+
|
207
|
+
raise InvalidProperty, 'traits property must be an array of maps with map values' unless
|
208
|
+
traits.all? {|t| t.values.all? {|v| v.is_a? Hash }}
|
209
|
+
|
210
|
+
raise InvalidProperty, 'traits property contains duplicate trait names' unless
|
211
|
+
traits.map(&:keys).flatten.uniq!.nil?
|
212
|
+
|
213
|
+
traits.reduce({}) { |memo, map | memo.merge! map }.
|
214
|
+
map { |name, data| Trait.new name, data, self }
|
215
|
+
end
|
216
|
+
|
217
|
+
def parse_uri(uri)
|
218
|
+
URI.parse uri
|
219
|
+
rescue URI::InvalidURIError
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
|
223
|
+
def parse_template
|
224
|
+
URITemplate::RFC6570.new base_uri
|
225
|
+
rescue URITemplate::RFC6570::Invalid
|
226
|
+
raise InvalidProperty, 'baseUri property is not a URL or a URL template.'
|
227
|
+
end
|
228
|
+
|
229
|
+
def inline_reference(reference_type, map, nodes)
|
230
|
+
nodes.map! do |node|
|
231
|
+
if node.is_a? reference_type
|
232
|
+
map[node.name]
|
233
|
+
else
|
234
|
+
inline_reference reference_type, map, node.children if node.respond_to? :children
|
235
|
+
node
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def style_sheet
|
241
|
+
File.open(self.class.relative_path('style.sass'), 'r') do |file|
|
242
|
+
Sass::Engine.new(file.read, syntax: :scss).render
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'json-schema'
|
2
|
+
|
3
|
+
module Raml
|
4
|
+
class Schema < ValueNode
|
5
|
+
# @return [Boolean] true if the schema appears to be an JSON Schema, false otherwise.
|
6
|
+
def json_schema?
|
7
|
+
/"\$schema":\s*"http:\/\/json-schema.org\/[^"]*"/ === @value
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Boolean] true if the schema appears to be an XML Schema, false otherwise.
|
11
|
+
def xml_schema?
|
12
|
+
/<xs:schema [^>]*xmlns:xs="http:\/\/www\.w3\.org\/2001\/XMLSchema"[^>]*>/ === @value
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns HTML documenting the node and child nodes.
|
16
|
+
# @return [String] HTML documentation.
|
17
|
+
def document
|
18
|
+
highlight @value, parent.media_type
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def validate_value
|
24
|
+
validate_json if json_schema?
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate_json
|
28
|
+
parsed_schema = JSON.parse @value
|
29
|
+
version = parsed_schema['$schema']
|
30
|
+
# json-schema gem doesn't handle this lastest version string
|
31
|
+
version = nil if version == 'http://json-schema.org/schema#'
|
32
|
+
# fix up schema versions URLs that don't end in "#""
|
33
|
+
version = "#{version}#" if version =~ /\Ahttps?:\/\/json-schema\.org\/draft-\d\d\/schema\z/
|
34
|
+
|
35
|
+
meta_schema = JSON::Validator.metaschema_for JSON::Validator.version_string_for version
|
36
|
+
JSON::Validator.validate! meta_schema, parsed_schema
|
37
|
+
rescue JSON::ParserError, JSON::Schema::SchemaError, JSON::Schema::ValidationError => e
|
38
|
+
raise InvalidSchema, "Could not parse JSON Schema: #{e}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'active_support/core_ext/string'
|
2
|
+
|
3
|
+
module Raml
|
4
|
+
class Template < ValueNode
|
5
|
+
|
6
|
+
# @private
|
7
|
+
def interpolate(params)
|
8
|
+
name = @name.clone
|
9
|
+
data = clone_data
|
10
|
+
interpolate_params name, params
|
11
|
+
interpolate_params data, params
|
12
|
+
[ name, data ]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def clone_data
|
18
|
+
# ugly but effective
|
19
|
+
Marshal.load Marshal.dump @value
|
20
|
+
end
|
21
|
+
|
22
|
+
def interpolate_params(value, params)
|
23
|
+
case value
|
24
|
+
when String
|
25
|
+
interpolate_params_string value, params
|
26
|
+
when Hash
|
27
|
+
value.map! { |key,val| [ interpolate_params(key, params), interpolate_params(val, params) ] }
|
28
|
+
when Array
|
29
|
+
value.map! { |val| interpolate_params val, params }
|
30
|
+
else
|
31
|
+
value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def interpolate_params_string(value, params)
|
36
|
+
value = value.dup if value.frozen?
|
37
|
+
|
38
|
+
value.gsub!(/(<<([^!\s>]+)(?:\s*\|\s*!(\w+))?>>)/) do |match|
|
39
|
+
param_name = $2
|
40
|
+
function = $3
|
41
|
+
|
42
|
+
param = params[param_name]
|
43
|
+
raise UnknownTypeOrTraitParameter, "#{param_name} is not a known parameter." if param.nil?
|
44
|
+
|
45
|
+
if function
|
46
|
+
raise UnknownTypeOrTraitParamFunction, function unless [ 'singularize', 'pluralize'].include? function
|
47
|
+
param = param.send function
|
48
|
+
end
|
49
|
+
|
50
|
+
param
|
51
|
+
end
|
52
|
+
value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|