configurable 0.7.0 → 1.0.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/Help/Command Line.rdoc +141 -0
- data/Help/Config Syntax.rdoc +229 -0
- data/Help/Config Types.rdoc +143 -0
- data/{History → History.rdoc} +9 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +144 -0
- data/lib/configurable.rb +7 -270
- data/lib/configurable/class_methods.rb +344 -367
- data/lib/configurable/config_classes.rb +3 -0
- data/lib/configurable/config_classes/list_config.rb +26 -0
- data/lib/configurable/config_classes/nest_config.rb +50 -0
- data/lib/configurable/config_classes/scalar_config.rb +91 -0
- data/lib/configurable/config_hash.rb +87 -112
- data/lib/configurable/config_types.rb +6 -0
- data/lib/configurable/config_types/boolean_type.rb +22 -0
- data/lib/configurable/config_types/float_type.rb +11 -0
- data/lib/configurable/config_types/integer_type.rb +11 -0
- data/lib/configurable/config_types/nest_type.rb +39 -0
- data/lib/configurable/config_types/object_type.rb +58 -0
- data/lib/configurable/config_types/string_type.rb +15 -0
- data/lib/configurable/conversions.rb +91 -0
- data/lib/configurable/module_methods.rb +0 -1
- data/lib/configurable/version.rb +1 -5
- metadata +73 -30
- data/README +0 -207
- data/lib/cdoc.rb +0 -413
- data/lib/cdoc/cdoc_html_generator.rb +0 -38
- data/lib/cdoc/cdoc_html_template.rb +0 -42
- data/lib/config_parser.rb +0 -563
- data/lib/config_parser/option.rb +0 -108
- data/lib/config_parser/switch.rb +0 -44
- data/lib/config_parser/utils.rb +0 -177
- data/lib/configurable/config.rb +0 -97
- data/lib/configurable/indifferent_access.rb +0 -35
- data/lib/configurable/nest_config.rb +0 -78
- data/lib/configurable/ordered_hash_patch.rb +0 -85
- data/lib/configurable/utils.rb +0 -186
- data/lib/configurable/validation.rb +0 -768
data/lib/cdoc.rb
DELETED
@@ -1,413 +0,0 @@
|
|
1
|
-
# RDoc creates a namespace conflict with IRB within 'rdoc/parsers/parse_rb'
|
2
|
-
# In that file, RubyToken and RubyLex get defined in the Object namespace,
|
3
|
-
# which will conflict with prior definitions from, for instance, IRB.
|
4
|
-
#
|
5
|
-
# This code redefines the RDoc RubyToken and RubyLex within the RDoc
|
6
|
-
# namespace. RDoc is not affected because all includes and uses of
|
7
|
-
# RubyToken and RubyLex are set when RDoc is loaded. The single exception
|
8
|
-
# I know of are several calls to class methods of RubyLex (ex RubyLex.debug?).
|
9
|
-
# These calls will be routed to the existing RubyLex.
|
10
|
-
#
|
11
|
-
# Uses of the existing RubyToken and RubyLex (as by irb) should be
|
12
|
-
# unaffected as the constants are reset after RDoc loads.
|
13
|
-
#
|
14
|
-
if Object.const_defined?(:RubyToken) || Object.const_defined?(:RubyLex)
|
15
|
-
class Object # :nodoc:
|
16
|
-
old_ruby_token = const_defined?(:RubyToken) ? remove_const(:RubyToken) : nil
|
17
|
-
old_ruby_lex = const_defined?(:RubyLex) ? remove_const(:RubyLex) : nil
|
18
|
-
|
19
|
-
require 'rdoc/rdoc'
|
20
|
-
|
21
|
-
# if by chance rdoc has ALREADY been loaded then requiring
|
22
|
-
# rdoc will not reset RubyToken and RubyLex... in this case
|
23
|
-
# the old constants are what you want.
|
24
|
-
new_ruby_token = const_defined?(:RubyToken) ? remove_const(:RubyToken) : old_ruby_token
|
25
|
-
new_ruby_lex = const_defined?(:RubyLex) ? remove_const(:RubyLex) : old_ruby_lex
|
26
|
-
|
27
|
-
RDoc.const_set(:RubyToken, new_ruby_token)
|
28
|
-
RDoc.const_set(:RubyLex, new_ruby_lex)
|
29
|
-
|
30
|
-
const_set(:RubyToken, old_ruby_token) unless old_ruby_token == nil
|
31
|
-
const_set(:RubyLex, old_ruby_lex) unless old_ruby_lex == nil
|
32
|
-
end
|
33
|
-
else
|
34
|
-
require 'rdoc/rdoc'
|
35
|
-
|
36
|
-
if Object.const_defined?(:RubyToken) && !RDoc.const_defined?(:RubyToken)
|
37
|
-
class Object # :nodoc:
|
38
|
-
RDoc.const_set(:RubyToken, remove_const(:RubyToken))
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
if Object.const_defined?(:RubyLex) && !RDoc.const_defined?(:RubyLex)
|
43
|
-
class Object # :nodoc:
|
44
|
-
RDoc.const_set(:RubyLex, remove_const(:RubyLex))
|
45
|
-
RDoc::RubyLex.const_set(:RubyLex, RDoc::RubyLex)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
unless Object.const_defined?(:TokenStream)
|
51
|
-
TokenStream = RDoc::TokenStream
|
52
|
-
Options = RDoc::Options
|
53
|
-
end
|
54
|
-
|
55
|
-
# CDoc hooks into and extends RDoc to make Configurable documentation available
|
56
|
-
# as attributes. CDoc provides an extension to the standard RDoc HTMLGenerator
|
57
|
-
# and template.
|
58
|
-
#
|
59
|
-
# === Usage
|
60
|
-
# To generate task documentation with configuration information, CDoc must be
|
61
|
-
# loaded and the appropriate flags passed to rdoc . Essentially what you want
|
62
|
-
# is:
|
63
|
-
#
|
64
|
-
# % rdoc --fmt cdoc --template cdoc/cdoc_html_template [file_names....]
|
65
|
-
#
|
66
|
-
# Unfortunately, there is no way to load or require a file into the rdoc
|
67
|
-
# utility directly; the above code causes an 'Invalid output formatter' error.
|
68
|
-
# However, CDoc is easy to utilize from a Rake::RDocTask:
|
69
|
-
#
|
70
|
-
# require 'rake'
|
71
|
-
# require 'rake/rdoctask'
|
72
|
-
#
|
73
|
-
# desc 'Generate documentation.'
|
74
|
-
# Rake::RDocTask.new(:rdoc) do |rdoc|
|
75
|
-
# require 'cdoc'
|
76
|
-
# rdoc.template = 'cdoc/cdoc_html_template'
|
77
|
-
# rdoc.options << '--fmt' << 'cdoc'
|
78
|
-
#
|
79
|
-
# # specify whatever else you need
|
80
|
-
# # rdoc.rdoc_files.include(...)
|
81
|
-
# end
|
82
|
-
#
|
83
|
-
# Now execute the rake task like:
|
84
|
-
#
|
85
|
-
# % rake rdoc
|
86
|
-
#
|
87
|
-
# === Implementation
|
88
|
-
#
|
89
|
-
# RDoc is a beast to utilize in a non-standard way. One way to make RDoc parse
|
90
|
-
# unexpected flags like 'config' or 'config_attr' is to use the '--accessor'
|
91
|
-
# option (see 'rdoc --help' or the RDoc documentation for more details).
|
92
|
-
#
|
93
|
-
# CDoc hooks into the '--accessor' parsing process to pull out configuration
|
94
|
-
# attributes and format them into their own Configuration section on an RDoc
|
95
|
-
# html page. When 'cdoc' is specified as an rdoc option, CDoc in effect sets
|
96
|
-
# accessor flags for all the standard Task configuration methods, and then
|
97
|
-
# extends the RDoc::RubyParser handle these specially.
|
98
|
-
#
|
99
|
-
# If cdoc is not specified as the rdoc format, CDoc does not affect the RDoc
|
100
|
-
# output. Similarly, the configuration attributes will not appear in the
|
101
|
-
# output unless you specify a template that utilizes them.
|
102
|
-
#
|
103
|
-
# ==== Namespace conflicts
|
104
|
-
#
|
105
|
-
# RDoc creates a namespace conflict with other libraries that define RubyToken
|
106
|
-
# and RubyLex in the Object namespace (the prime example being IRB). CDoc checks
|
107
|
-
# for such a conflict and redfines the RDoc versions of RubyToken and RubyLex
|
108
|
-
# within the RDoc namespace. Essentially:
|
109
|
-
#
|
110
|
-
# original constant redefined constant
|
111
|
-
# RubyToken RDoc::RubyToken
|
112
|
-
# RubyLex RDoc::RubyLex
|
113
|
-
#
|
114
|
-
# The redefinition should not affect the existing (non RDoc) RubyToken and
|
115
|
-
# RubyLex constants, but if you directly use the RDoc versions after loading
|
116
|
-
# CDoc, you should be aware that they must be accessed through the new
|
117
|
-
# constants. Unfortunatley the trick is not seamless. The RDoc RubyLex makes
|
118
|
-
# a few calls to the RubyLex class method 'debug?'... these will be issued to
|
119
|
-
# the existing (non RDoc) RubyLex method and not the redefined
|
120
|
-
# RDoc::RubyLex.debug?
|
121
|
-
#
|
122
|
-
# In addition, because of the RubyLex calls, the RDoc::RubyLex cannot be fully
|
123
|
-
# hidden when CDoc is loaded before the conflicting RubyLex; you cannot load
|
124
|
-
# CDoc before loading IRB without raising warnings.
|
125
|
-
#
|
126
|
-
# Luckily all these troubles can be avoided very easily by not loading CDoc or
|
127
|
-
# RDoc when you're in irb. On the plus side, going against what I just said,
|
128
|
-
# you can now access/use RDoc within irb by requiring <tt>'cdoc'</tt>.
|
129
|
-
#
|
130
|
-
#--
|
131
|
-
# Note that tap-0.10.0 heavily refactored CDoc functionality out of the old CDoc
|
132
|
-
# and into Lazydoc, and changed the declaration syntax for configurations. These
|
133
|
-
# changes also affected the implementation of CDoc. Mostly the changes are hacks
|
134
|
-
# to get the old system to work in the new system... as hacky as the old CDoc was,
|
135
|
-
# now this CDoc is hacky AND may have cruft. Until it breaks completely, I leave
|
136
|
-
# it as is... ugly and hard to fathom.
|
137
|
-
#
|
138
|
-
module CDoc
|
139
|
-
|
140
|
-
# Encasulates information about the configuration. Designed to be utilized
|
141
|
-
# by the CDocHTMLGenerator as similarly as possible to standard attributes.
|
142
|
-
class ConfigAttr < RDoc::Attr # :nodoc:
|
143
|
-
# Contains the actual declaration for the config attribute. ex: "c [:key, 'value'] # comment"
|
144
|
-
attr_accessor :config_declaration, :default
|
145
|
-
|
146
|
-
def initialize(*args)
|
147
|
-
@comment = nil # suppress a warning in Ruby 1.9
|
148
|
-
super
|
149
|
-
end
|
150
|
-
|
151
|
-
alias original_comment comment
|
152
|
-
|
153
|
-
def desc
|
154
|
-
case text.to_s
|
155
|
-
when /^#--(.*)/ then $1.strip
|
156
|
-
when /^#(.*)/ then $1.strip
|
157
|
-
else
|
158
|
-
nil
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
# The description for the config. Comment is formed from the standard
|
163
|
-
# attribute comment and the text following the attribute, which is slightly
|
164
|
-
# different than normal:
|
165
|
-
#
|
166
|
-
# # standard comment
|
167
|
-
# attr_accessor :attribute
|
168
|
-
#
|
169
|
-
# # standard comment
|
170
|
-
# config_accessor :config # ...added to standard comment
|
171
|
-
#
|
172
|
-
# c [:key, 'value'] # hence you can comment inline like this.
|
173
|
-
#
|
174
|
-
# The comments for each of these will be:
|
175
|
-
# attribute:: standard comment
|
176
|
-
# config:: standard comment ...added to standard comment
|
177
|
-
# key:: hence you can comment inline like this.
|
178
|
-
#
|
179
|
-
def comment(add_default=true)
|
180
|
-
# this would include the trailing comment...
|
181
|
-
# text_comment = text.to_s.sub(/^#--.*/m, '')
|
182
|
-
#original_comment.to_s + text_comment + (default && add_default ? " (#{default})" : "")
|
183
|
-
comment = original_comment.to_s.strip
|
184
|
-
comment = desc.to_s if comment.empty?
|
185
|
-
comment + (default && add_default ? " (<tt>#{default}</tt>)" : "")
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
module CodeObjectAccess # :nodoc:
|
190
|
-
def comment_sections(section_regexp=//, normalize_comments=false)
|
191
|
-
res = {}
|
192
|
-
|
193
|
-
section = nil
|
194
|
-
lines = []
|
195
|
-
comment_lines = comment.split(/\r?\n/)
|
196
|
-
comment_lines << nil
|
197
|
-
comment_lines.each do |line|
|
198
|
-
case line
|
199
|
-
when nil, /^\s*#\s*=+(.*)/
|
200
|
-
next_section = (line == nil ? nil : $1.to_s.strip)
|
201
|
-
|
202
|
-
if section =~ section_regexp
|
203
|
-
lines << "" unless normalize_comments
|
204
|
-
res[section] = lines.join("\n") unless section == nil
|
205
|
-
end
|
206
|
-
|
207
|
-
section = next_section
|
208
|
-
lines = []
|
209
|
-
else
|
210
|
-
if normalize_comments
|
211
|
-
line =~ /^\s*#\s?(.*)/
|
212
|
-
line = $1.to_s
|
213
|
-
end
|
214
|
-
|
215
|
-
lines << line
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
res
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
module ClassModuleAccess # :nodoc:
|
224
|
-
def find_class_or_module_named(name)
|
225
|
-
return self if full_name == name
|
226
|
-
(@classes.values + @modules.values).each do |c|
|
227
|
-
res = c.find_class_or_module_named(name)
|
228
|
-
return res if res
|
229
|
-
end
|
230
|
-
nil
|
231
|
-
end
|
232
|
-
|
233
|
-
def configurations
|
234
|
-
@attributes.select do |attribute|
|
235
|
-
attribute.kind_of?(CDoc::ConfigAttr)
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
def find_configuration_named(name)
|
240
|
-
@attributes.each do |attribute|
|
241
|
-
next unless attribute.kind_of?(CDoc::ConfigAttr)
|
242
|
-
return attribute if attribute.name == name
|
243
|
-
end
|
244
|
-
nil
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
# Overrides the new method automatically extend the new object with
|
249
|
-
# ConfigParser. Intended to be used like:
|
250
|
-
# RDoc::RubyParser.extend InitializeConfigParser
|
251
|
-
module InitializeConfigParser # :nodoc:
|
252
|
-
def new(*args)
|
253
|
-
parser = super
|
254
|
-
parser.extend ConfigParser
|
255
|
-
#parser.config_mode = 'config_accessor'
|
256
|
-
parser
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
# Provides methods extending an RDoc::RubyParser such that the parser will produce
|
261
|
-
# CDoc::ConfigAttr instances in the place of RDoc::Attr instances during attribute
|
262
|
-
# parsing.
|
263
|
-
module ConfigParser # :nodoc:
|
264
|
-
include RDoc::RubyToken
|
265
|
-
include TokenStream
|
266
|
-
|
267
|
-
CONFIG_ACCESSORS = ['config', 'config_attr']
|
268
|
-
|
269
|
-
# Gets tokens until the next TkNL
|
270
|
-
def get_tk_to_nl
|
271
|
-
tokens = []
|
272
|
-
while !(tk = get_tk).kind_of?(TkNL)
|
273
|
-
tokens.push tk
|
274
|
-
end
|
275
|
-
unget_tk(tk)
|
276
|
-
tokens
|
277
|
-
end
|
278
|
-
|
279
|
-
# Works like the original parse_attr_accessor, except that the arg
|
280
|
-
# name is parsed from the config syntax and added attribute will
|
281
|
-
# be a CDoc::ConfigAttr. For example:
|
282
|
-
#
|
283
|
-
# class ConfigClass
|
284
|
-
# include Configurable
|
285
|
-
# config :key, 'value' # comment
|
286
|
-
# end
|
287
|
-
#
|
288
|
-
# produces an attribute named :key in the current config_rw mode.
|
289
|
-
#
|
290
|
-
# (see 'rdoc/parsers/parse_rb' line 2509)
|
291
|
-
def parse_config(context, single, tk, comment)
|
292
|
-
tks = get_tk_to_nl
|
293
|
-
|
294
|
-
key_tk = nil
|
295
|
-
value_tk = nil
|
296
|
-
|
297
|
-
tks.each do |token|
|
298
|
-
next if token.kind_of?(TkSPACE)
|
299
|
-
|
300
|
-
if key_tk == nil
|
301
|
-
case token
|
302
|
-
when TkSYMBOL then key_tk = token
|
303
|
-
when TkLPAREN then next
|
304
|
-
else break
|
305
|
-
end
|
306
|
-
else
|
307
|
-
case token
|
308
|
-
when TkCOMMA then value_tk = token
|
309
|
-
else
|
310
|
-
value_tk = token if value_tk.kind_of?(TkCOMMA)
|
311
|
-
break
|
312
|
-
end
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
text = ""
|
317
|
-
if tks.last.kind_of?(TkCOMMENT)
|
318
|
-
text = tks.last.text.chomp("\n").chomp("\r")
|
319
|
-
unget_tk(tks.last)
|
320
|
-
|
321
|
-
# If nodoc is given, don't document
|
322
|
-
|
323
|
-
tmp = RDoc::CodeObject.new
|
324
|
-
read_documentation_modifiers(tmp, RDoc::ATTR_MODIFIERS)
|
325
|
-
text = nil unless tmp.document_self
|
326
|
-
end
|
327
|
-
|
328
|
-
tks.reverse_each {|token| unget_tk(token) }
|
329
|
-
return if key_tk == nil || text == nil
|
330
|
-
|
331
|
-
arg = key_tk.text[1..-1]
|
332
|
-
default = nil
|
333
|
-
if value_tk
|
334
|
-
if text =~ /(.*):no_default:(.*)/
|
335
|
-
text = $1 + $2
|
336
|
-
else
|
337
|
-
default = value_tk.text
|
338
|
-
end
|
339
|
-
end
|
340
|
-
att = CDoc::ConfigAttr.new(text, arg, "RW", comment)
|
341
|
-
att.config_declaration = get_tkread
|
342
|
-
att.default = default
|
343
|
-
|
344
|
-
context.add_attribute(att)
|
345
|
-
end
|
346
|
-
|
347
|
-
# Overrides the standard parse_attr_accessor method to hook in parsing
|
348
|
-
# of the config accessors. If the input token is not named as one of the
|
349
|
-
# CONFIG_ACCESSORS, it will be processed normally.
|
350
|
-
def parse_attr_accessor(context, single, tk, comment)
|
351
|
-
case tk.name
|
352
|
-
when 'config', 'config_attr'
|
353
|
-
parse_config(context, single, tk, comment)
|
354
|
-
else
|
355
|
-
super
|
356
|
-
end
|
357
|
-
end
|
358
|
-
end
|
359
|
-
end
|
360
|
-
|
361
|
-
# Register the CDoc generator (in case you want to actually use it).
|
362
|
-
# method echos RDoc generator registration (see 'rdoc/rdoc' line 76)
|
363
|
-
Generator = Struct.new(:file_name, :class_name, :key)
|
364
|
-
RDoc::RDoc::GENERATORS['cdoc'] = Generator.new(
|
365
|
-
"cdoc/cdoc_html_generator.rb",
|
366
|
-
"CDocHTMLGenerator".intern,
|
367
|
-
"cdoc")
|
368
|
-
|
369
|
-
# Add the extended accessors to context classes.
|
370
|
-
module RDoc # :nodoc:
|
371
|
-
class CodeObject # :nodoc:
|
372
|
-
include CDoc::CodeObjectAccess
|
373
|
-
end
|
374
|
-
|
375
|
-
class ClassModule # :nodoc:
|
376
|
-
include CDoc::ClassModuleAccess
|
377
|
-
end
|
378
|
-
end
|
379
|
-
|
380
|
-
# Override methods in Options to in effect incorporate the accessor
|
381
|
-
# flags for CDoc parsing. (see 'rdoc/options') Raise an error if an
|
382
|
-
# accessor flag has already been specified.
|
383
|
-
class Options # :nodoc:
|
384
|
-
alias cdoc_original_parse parse
|
385
|
-
|
386
|
-
def parse(argv, generators)
|
387
|
-
cdoc_original_parse(argv, generators)
|
388
|
-
return unless @generator_name == 'cdoc'
|
389
|
-
|
390
|
-
accessors = CDoc::ConfigParser::CONFIG_ACCESSORS
|
391
|
-
|
392
|
-
# check the config_accessor_flags for accessor conflicts
|
393
|
-
extra_accessor_flags.each_pair do |accessor, flag|
|
394
|
-
if accessors.include?(accessor)
|
395
|
-
raise OptionList.error("cdoc format already handles the accessor '#{accessor}'")
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
|
-
# extra_accessors will be nil if no extra accessors were
|
400
|
-
# specifed, otherwise it'll be a regexp like /^(...)$/
|
401
|
-
# the string subset assumes
|
402
|
-
# regexp.to_s # => /(?-mix:^(...)$)/
|
403
|
-
@extra_accessors ||= /^()$/
|
404
|
-
current_accessors_str = @extra_accessors.to_s[9..-4]
|
405
|
-
|
406
|
-
# echos the Regexp production code in rdoc/options.rb
|
407
|
-
# (see the parse method, line 501)
|
408
|
-
re = '^(' + current_accessors_str + accessors.map{|a| Regexp.quote(a)}.join('|') + ')$'
|
409
|
-
@extra_accessors = Regexp.new(re)
|
410
|
-
|
411
|
-
RDoc::RubyParser.extend CDoc::InitializeConfigParser
|
412
|
-
end
|
413
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
require 'rdoc/generators/html_generator'
|
2
|
-
|
3
|
-
# Defines a specialized generator so it can be called for using a --fmt option.
|
4
|
-
class CDocHTMLGenerator < Generators::HTMLGenerator # :nodoc:
|
5
|
-
end
|
6
|
-
|
7
|
-
module Generators # :nodoc:
|
8
|
-
const_set(:RubyToken, RDoc::RubyToken)
|
9
|
-
|
10
|
-
class HtmlClass < ContextUser # :nodoc:
|
11
|
-
alias cdoc_original_value_hash value_hash
|
12
|
-
|
13
|
-
def value_hash
|
14
|
-
# split attributes into configurations and regular attributes
|
15
|
-
configurations, attributes = @context.attributes.partition do |attribute|
|
16
|
-
attribute.kind_of?(CDoc::ConfigAttr)
|
17
|
-
end
|
18
|
-
|
19
|
-
# set the context attributes to JUST the regular
|
20
|
-
# attributes and process as usual.
|
21
|
-
@context.attributes.clear.concat attributes
|
22
|
-
values = cdoc_original_value_hash
|
23
|
-
|
24
|
-
# set the context attributes to the configurations
|
25
|
-
# and echo the regular processing to produce a list
|
26
|
-
# of configurations
|
27
|
-
@context.attributes.clear.concat configurations
|
28
|
-
@context.sections.each_with_index do |section, i|
|
29
|
-
secdata = values["sections"][i]
|
30
|
-
|
31
|
-
al = build_attribute_list(section)
|
32
|
-
secdata["configurations"] = al unless al.empty?
|
33
|
-
end
|
34
|
-
|
35
|
-
values
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|