ruby-nmap 0.9.3 → 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.
Files changed (107) hide show
  1. checksums.yaml +5 -5
  2. data/.document +1 -0
  3. data/.editorconfig +11 -0
  4. data/.github/workflows/ruby.yml +31 -0
  5. data/ChangeLog.md +122 -67
  6. data/Gemfile +11 -5
  7. data/LICENSE.txt +1 -1
  8. data/README.md +88 -50
  9. data/Rakefile +8 -3
  10. data/UPGRADING.md +47 -0
  11. data/gemspec.yml +6 -6
  12. data/lib/nmap/command.rb +765 -0
  13. data/lib/nmap/version.rb +1 -1
  14. data/lib/nmap/xml/address.rb +38 -0
  15. data/lib/nmap/xml/cpe/url.rb +80 -0
  16. data/lib/nmap/xml/cpe.rb +47 -0
  17. data/lib/nmap/xml/hop.rb +22 -0
  18. data/lib/nmap/xml/host.rb +546 -0
  19. data/lib/nmap/xml/host_script.rb +26 -0
  20. data/lib/nmap/xml/hostname.rb +44 -0
  21. data/lib/nmap/xml/ip_id_sequence.rb +26 -0
  22. data/lib/nmap/xml/os.rb +131 -0
  23. data/lib/nmap/xml/os_class.rb +86 -0
  24. data/lib/nmap/xml/os_match.rb +22 -0
  25. data/lib/nmap/xml/port.rb +114 -0
  26. data/lib/nmap/xml/postscript.rb +26 -0
  27. data/lib/nmap/xml/prescript.rb +26 -0
  28. data/lib/nmap/xml/run_stat.rb +22 -0
  29. data/lib/nmap/xml/scan.rb +38 -0
  30. data/lib/nmap/xml/scan_task.rb +55 -0
  31. data/lib/nmap/xml/scanner.rb +22 -0
  32. data/lib/nmap/xml/script.rb +110 -0
  33. data/lib/nmap/xml/scripts.rb +33 -0
  34. data/lib/nmap/xml/sequence.rb +52 -0
  35. data/lib/nmap/xml/service.rb +172 -0
  36. data/lib/nmap/xml/status.rb +22 -0
  37. data/lib/nmap/xml/tcp_sequence.rb +48 -0
  38. data/lib/nmap/xml/tcp_ts_sequence.rb +26 -0
  39. data/lib/nmap/xml/traceroute.rb +73 -0
  40. data/lib/nmap/xml/uptime.rb +22 -0
  41. data/lib/nmap/xml.rb +46 -44
  42. data/ruby-nmap.gemspec +38 -83
  43. data/spec/command_spec.rb +726 -0
  44. data/spec/fixtures/down_host_scan.xml +16 -0
  45. data/spec/{local_scan.xml → fixtures/local_scan.xml} +1 -1
  46. data/spec/{scan.xml → fixtures/scan.xml} +1 -1
  47. data/spec/spec_helper.rb +2 -2
  48. data/spec/{address_spec.rb → xml/address_spec.rb} +2 -2
  49. data/spec/{cpe → xml/cpe}/url_spec.rb +1 -1
  50. data/spec/{cpe_examples.rb → xml/cpe_examples.rb} +1 -1
  51. data/spec/{hop_spec.rb → xml/hop_spec.rb} +2 -2
  52. data/spec/{host_script_spec.rb → xml/host_script_spec.rb} +2 -2
  53. data/spec/{host_spec.rb → xml/host_spec.rb} +12 -8
  54. data/spec/{hostname_spec.rb → xml/hostname_spec.rb} +2 -2
  55. data/spec/{ip_id_sequence_spec.rb → xml/ip_id_sequence_spec.rb} +3 -3
  56. data/spec/{os_class_spec.rb → xml/os_class_spec.rb} +3 -3
  57. data/spec/{os_match_spec.rb → xml/os_match_spec.rb} +2 -2
  58. data/spec/{os_spec.rb → xml/os_spec.rb} +3 -3
  59. data/spec/{port_spec.rb → xml/port_spec.rb} +10 -5
  60. data/spec/{postscript_spec.rb → xml/postscript_spec.rb} +2 -2
  61. data/spec/{prescript_spec.rb → xml/prescript_spec.rb} +2 -2
  62. data/spec/{run_stat_spec.rb → xml/run_stat_spec.rb} +2 -2
  63. data/spec/{scan_spec.rb → xml/scan_spec.rb} +2 -2
  64. data/spec/{scan_task_spec.rb → xml/scan_task_spec.rb} +6 -6
  65. data/spec/{scanner_spec.rb → xml/scanner_spec.rb} +3 -3
  66. data/spec/xml/script_spec.rb +137 -0
  67. data/spec/xml/scripts_examples.rb +19 -0
  68. data/spec/{sequence_examples.rb → xml/sequence_examples.rb} +1 -0
  69. data/spec/{service_spec.rb → xml/service_spec.rb} +31 -5
  70. data/spec/{status_spec.rb → xml/status_spec.rb} +4 -3
  71. data/spec/{tcp_sequence_spec.rb → xml/tcp_sequence_spec.rb} +3 -3
  72. data/spec/{tcp_ts_sequence_spec.rb → xml/tcp_ts_sequence_spec.rb} +3 -3
  73. data/spec/{traceroute_spec.rb → xml/traceroute_spec.rb} +3 -3
  74. data/spec/{uptime_spec.rb → xml/uptime_spec.rb} +2 -2
  75. data/spec/xml_spec.rb +93 -45
  76. metadata +78 -99
  77. data/.travis.yml +0 -14
  78. data/lib/nmap/address.rb +0 -34
  79. data/lib/nmap/cpe/url.rb +0 -78
  80. data/lib/nmap/cpe.rb +0 -45
  81. data/lib/nmap/hop.rb +0 -20
  82. data/lib/nmap/host.rb +0 -586
  83. data/lib/nmap/host_script.rb +0 -18
  84. data/lib/nmap/hostname.rb +0 -42
  85. data/lib/nmap/ip_id_sequence.rb +0 -24
  86. data/lib/nmap/os.rb +0 -127
  87. data/lib/nmap/os_class.rb +0 -82
  88. data/lib/nmap/os_match.rb +0 -18
  89. data/lib/nmap/port.rb +0 -99
  90. data/lib/nmap/postscript.rb +0 -16
  91. data/lib/nmap/prescript.rb +0 -16
  92. data/lib/nmap/program.rb +0 -102
  93. data/lib/nmap/run_stat.rb +0 -20
  94. data/lib/nmap/scan.rb +0 -34
  95. data/lib/nmap/scan_task.rb +0 -50
  96. data/lib/nmap/scanner.rb +0 -18
  97. data/lib/nmap/scripts.rb +0 -71
  98. data/lib/nmap/sequence.rb +0 -50
  99. data/lib/nmap/service.rb +0 -170
  100. data/lib/nmap/status.rb +0 -18
  101. data/lib/nmap/task.rb +0 -381
  102. data/lib/nmap/tcp_sequence.rb +0 -46
  103. data/lib/nmap/tcp_ts_sequence.rb +0 -22
  104. data/lib/nmap/traceroute.rb +0 -71
  105. data/lib/nmap/uptime.rb +0 -20
  106. data/spec/scripts_examples.rb +0 -35
  107. data/spec/task_spec.rb +0 -150
@@ -0,0 +1,546 @@
1
+ require 'nmap/xml/status'
2
+ require 'nmap/xml/address'
3
+ require 'nmap/xml/hostname'
4
+ require 'nmap/xml/os'
5
+ require 'nmap/xml/port'
6
+ require 'nmap/xml/ip_id_sequence'
7
+ require 'nmap/xml/tcp_sequence'
8
+ require 'nmap/xml/tcp_ts_sequence'
9
+ require 'nmap/xml/uptime'
10
+ require 'nmap/xml/traceroute'
11
+ require 'nmap/xml/host_script'
12
+
13
+ require 'nokogiri'
14
+ require 'time'
15
+
16
+ module Nmap
17
+ class XML
18
+ #
19
+ # Wraps a `host` XML element.
20
+ #
21
+ # @since 1.0.0
22
+ #
23
+ class Host
24
+
25
+ include Enumerable
26
+
27
+ #
28
+ # Creates a new Host object.
29
+ #
30
+ # @param [Nokogiri::XML::Node] node
31
+ # The XML node that contains the host information.
32
+ #
33
+ def initialize(node)
34
+ @node = node
35
+ end
36
+
37
+ #
38
+ # The time the host was first scanned.
39
+ #
40
+ # @return [Time]
41
+ # The time the host was first scanned.
42
+ #
43
+ # @since 0.1.2
44
+ #
45
+ def start_time
46
+ @start_time ||= Time.at(@node['starttime'].to_i)
47
+ end
48
+
49
+ #
50
+ # The time the host was last scanned.
51
+ #
52
+ # @return [Time]
53
+ # The time the host was last scanned.
54
+ #
55
+ # @since 0.1.2
56
+ #
57
+ def end_time
58
+ @end_time ||= Time.at(@node['endtime'].to_i)
59
+ end
60
+
61
+ #
62
+ # Parses the status of the host.
63
+ #
64
+ # @return [Status]
65
+ # The status of the host.
66
+ #
67
+ def status
68
+ unless @status
69
+ status = @node.at_xpath('status')
70
+
71
+ @status = Status.new(
72
+ status['state'].to_sym,
73
+ status['reason'],
74
+ status['reason_ttl'].to_i
75
+ )
76
+ end
77
+
78
+ return @status
79
+ end
80
+
81
+ #
82
+ # Parses each address of the host.
83
+ #
84
+ # @yield [addr]
85
+ # Each parsed address will be pass to a given block.
86
+ #
87
+ # @yieldparam [Address] addr
88
+ # A address of the host.
89
+ #
90
+ # @return [Host, Enumerator]
91
+ # The host.
92
+ # If no block was given, an enumerator will be returned.
93
+ #
94
+ def each_address
95
+ return enum_for(__method__) unless block_given?
96
+
97
+ @node.xpath("address[@addr]").each do |addr|
98
+ address = Address.new(
99
+ addr['addrtype'].to_sym,
100
+ addr['addr'],
101
+ addr['vendor']
102
+ )
103
+
104
+ yield address
105
+ end
106
+
107
+ return self
108
+ end
109
+
110
+ #
111
+ # Parses the addresses of the host.
112
+ #
113
+ # @return [Array<Host>]
114
+ # The addresses of the host.
115
+ #
116
+ def addresses
117
+ each_address.to_a
118
+ end
119
+
120
+ #
121
+ # Parses the MAC address of the host.
122
+ #
123
+ # @return [String]
124
+ # The MAC address of the host.
125
+ #
126
+ def mac
127
+ @mac ||= if (addr = @node.at_xpath("address[@addrtype='mac']"))
128
+ addr['addr']
129
+ end
130
+ end
131
+
132
+ #
133
+ # Parses the MAC vendor of the host.
134
+ #
135
+ # @return [String]
136
+ # The Mac Vendor of the host.
137
+ #
138
+ # @since 0.8.0
139
+ #
140
+ def vendor
141
+ @vendor ||= if (vendor = @node.at_xpath("address/@vendor"))
142
+ vendor.inner_text
143
+ end
144
+ end
145
+
146
+ #
147
+ # Parses the IPv4 address of the host.
148
+ #
149
+ # @return [String]
150
+ # The IPv4 address of the host.
151
+ #
152
+ def ipv4
153
+ @ipv4 ||= if (addr = @node.at_xpath("address[@addrtype='ipv4']"))
154
+ addr['addr']
155
+ end
156
+ end
157
+
158
+ #
159
+ # Parses the IPv6 address of the host.
160
+ #
161
+ # @return [String]
162
+ # The IPv6 address of the host.
163
+ #
164
+ def ipv6
165
+ @ipv6 ||= if (addr = @node.at_xpath("address[@addrtype='ipv6']"))
166
+ addr['addr']
167
+ end
168
+ end
169
+
170
+ #
171
+ # The IP address of the host.
172
+ #
173
+ # @return [String]
174
+ # The IPv4 or IPv6 address of the host.
175
+ #
176
+ def ip
177
+ ipv6 || ipv4
178
+ end
179
+
180
+ #
181
+ # The address of the host.
182
+ #
183
+ # @return [String]
184
+ # The IP or MAC address of the host.
185
+ #
186
+ def address
187
+ ip || mac
188
+ end
189
+
190
+ #
191
+ # Parses the hostnames of the host.
192
+ #
193
+ # @yield [host]
194
+ # Each parsed hostname will be passed to the given block.
195
+ #
196
+ # @yieldparam [Hostname] host
197
+ # A hostname of the host.
198
+ #
199
+ # @return [Host, Enumerator]
200
+ # The host.
201
+ # If no block was given, an enumerator will be returned.
202
+ #
203
+ def each_hostname
204
+ return enum_for(__method__) unless block_given?
205
+
206
+ @node.xpath("hostnames/hostname[@name]").each do |host|
207
+ yield Hostname.new(host['type'],host['name'])
208
+ end
209
+
210
+ return self
211
+ end
212
+
213
+ #
214
+ # Parses the hostnames of the host.
215
+ #
216
+ # @return [Array<Hostname>]
217
+ # The hostnames of the host.
218
+ #
219
+ def hostnames
220
+ each_hostname.to_a
221
+ end
222
+
223
+ #
224
+ # The primary hostname of the host.
225
+ #
226
+ # @return [Hostname, nil]
227
+ #
228
+ # @since 0.8.0
229
+ #
230
+ def hostname
231
+ each_hostname.first
232
+ end
233
+
234
+ #
235
+ # Parses the OS guessing information of the host.
236
+ #
237
+ # @yield [os]
238
+ # If a block is given, it will be passed the OS guessing information.
239
+ #
240
+ # @yieldparam [OS] os
241
+ # The OS guessing information.
242
+ #
243
+ # @return [OS]
244
+ # The OS guessing information.
245
+ #
246
+ def os
247
+ @os ||= if (os = @node.at_xpath('os'))
248
+ OS.new(os)
249
+ end
250
+
251
+ yield @os if (@os && block_given?)
252
+ return @os
253
+ end
254
+
255
+ #
256
+ # Parses the Uptime analysis of the host.
257
+ #
258
+ # @yield [uptime]
259
+ # If a block is given, it will be passed the resulting object
260
+ #
261
+ # @yieldparam [Uptime]
262
+ # Uptime value.
263
+ #
264
+ # @return [Uptime]
265
+ # The parsed object.
266
+ #
267
+ # @since 0.7.0
268
+ #
269
+ def uptime
270
+ @uptime ||= if (uptime = @node.at_xpath('uptime'))
271
+ Uptime.new(
272
+ uptime['seconds'].to_i,
273
+ Time.parse(uptime['lastboot'])
274
+ )
275
+ end
276
+
277
+ yield @uptime if (@uptime && block_given?)
278
+ return @uptime
279
+ end
280
+
281
+ #
282
+ # Parses the TCP Sequence number analysis of the host.
283
+ #
284
+ # @yield [sequence]
285
+ # If a block is given, it will be passed the resulting object
286
+ #
287
+ # @yieldparam [TcpSequence] sequence
288
+ # TCP Sequence number analysis.
289
+ #
290
+ # @return [TcpSequence]
291
+ # The parsed object.
292
+ #
293
+ def tcp_sequence
294
+ @tcp_sequence ||= if (seq = @node.at_xpath('tcpsequence'))
295
+ TcpSequence.new(seq)
296
+ end
297
+
298
+ yield @tcp_sequence if (@tcp_sequence && block_given?)
299
+ return @tcp_sequence
300
+ end
301
+
302
+ #
303
+ # Parses the IPID sequence number analysis of the host.
304
+ #
305
+ # @yield [ipidsequence]
306
+ # If a block is given, it will be passed the resulting object
307
+ #
308
+ # @yieldparam [IpIdSequence] ipidsequence
309
+ # IPID Sequence number analysis.
310
+ #
311
+ # @return [IpIdSequence]
312
+ # The parsed object.
313
+ #
314
+ def ip_id_sequence
315
+ @ip_id_sequence ||= if (seq = @node.at_xpath('ipidsequence'))
316
+ IpIdSequence.new(seq)
317
+ end
318
+
319
+ yield @ip_id_sequence if (@ip_id_sequence && block_given?)
320
+ return @ip_id_sequence
321
+ end
322
+
323
+ #
324
+ # Parses the TCP Timestamp sequence number analysis of the host.
325
+ #
326
+ # @yield [tcptssequence]
327
+ # If a block is given, it will be passed the resulting object
328
+ #
329
+ # @yieldparam [TcpTsSequence] tcptssequence
330
+ # TCP Timestamp Sequence number analysis.
331
+ #
332
+ # @return [TcpTsSequence]
333
+ # The parsed object.
334
+ #
335
+ def tcp_ts_sequence
336
+ @tcp_ts_sequence ||= if (seq = @node.at_xpath('tcptssequence'))
337
+ TcpTsSequence.new(seq)
338
+ end
339
+
340
+ yield @tcp_ts_sequence if (@tcp_ts_sequence && block_given?)
341
+ return @tcp_ts_sequence
342
+ end
343
+
344
+ #
345
+ # Parses the scanned ports of the host.
346
+ #
347
+ # @yield [port]
348
+ # Each scanned port of the host.
349
+ #
350
+ # @yieldparam [Port] port
351
+ # A scanned port of the host.
352
+ #
353
+ # @return [Host, Enumerator]
354
+ # The host.
355
+ # If no block was given, an enumerator will be returned.
356
+ #
357
+ def each_port
358
+ return enum_for(__method__) unless block_given?
359
+
360
+ @node.xpath("ports/port").each do |port|
361
+ yield Port.new(port)
362
+ end
363
+
364
+ return self
365
+ end
366
+
367
+ #
368
+ # Parses the scanned ports of the host.
369
+ #
370
+ # @return [Array<Port>]
371
+ # The scanned ports of the host.
372
+ #
373
+ def ports
374
+ each_port.to_a
375
+ end
376
+
377
+ #
378
+ # Parses the open ports of the host.
379
+ #
380
+ # @yield [port]
381
+ # Each open port of the host.
382
+ #
383
+ # @yieldparam [Port] port
384
+ # An open scanned port of the host.
385
+ #
386
+ # @return [Host, Enumerator]
387
+ # The host.
388
+ # If no block was given, an enumerator will be returned.
389
+ #
390
+ def each_open_port
391
+ return enum_for(__method__) unless block_given?
392
+
393
+ @node.xpath("ports/port[state/@state='open']").each do |port|
394
+ yield Port.new(port)
395
+ end
396
+
397
+ return self
398
+ end
399
+
400
+ #
401
+ # Parses the open ports of the host.
402
+ #
403
+ # @return [Array<Port>]
404
+ # The open ports of the host.
405
+ #
406
+ def open_ports
407
+ each_open_port.to_a
408
+ end
409
+
410
+ #
411
+ # Parses the TCP ports of the host.
412
+ #
413
+ # @yield [port]
414
+ # Each TCP port of the host.
415
+ #
416
+ # @yieldparam [Port] port
417
+ # An TCP scanned port of the host.
418
+ #
419
+ # @return [Host, Enumerator]
420
+ # The host.
421
+ # If no block was given, an enumerator will be returned.
422
+ #
423
+ def each_tcp_port
424
+ return enum_for(__method__) unless block_given?
425
+
426
+ @node.xpath("ports/port[@protocol='tcp']").each do |port|
427
+ yield Port.new(port)
428
+ end
429
+
430
+ return self
431
+ end
432
+
433
+ #
434
+ # Parses the TCP ports of the host.
435
+ #
436
+ # @return [Array<Port>]
437
+ # The TCP ports of the host.
438
+ #
439
+ def tcp_ports
440
+ each_tcp_port.to_a
441
+ end
442
+
443
+ #
444
+ # Parses the UDP ports of the host.
445
+ #
446
+ # @yield [port]
447
+ # Each UDP port of the host.
448
+ #
449
+ # @yieldparam [Port] port
450
+ # An UDP scanned port of the host.
451
+ #
452
+ # @return [Host, Enumerator]
453
+ # The host.
454
+ # If no block was given, an enumerator will be returned.
455
+ #
456
+ def each_udp_port
457
+ return enum_for(__method__) unless block_given?
458
+
459
+ @node.xpath("ports/port[@protocol='udp']").each do |port|
460
+ yield Port.new(port)
461
+ end
462
+
463
+ return self
464
+ end
465
+
466
+ #
467
+ # Parses the UDP ports of the host.
468
+ #
469
+ # @return [Array<Port>]
470
+ # The UDP ports of the host.
471
+ #
472
+ def udp_ports
473
+ each_udp_port.to_a
474
+ end
475
+
476
+ #
477
+ # Parses the open ports of the host.
478
+ #
479
+ # @see each_open_port
480
+ #
481
+ def each(&block)
482
+ each_open_port(&block)
483
+ end
484
+
485
+ #
486
+ # The NSE scripts ran against the host.
487
+ #
488
+ # @return [HostScript, nil]
489
+ # Contains the host script output and data.
490
+ #
491
+ # @since 0.9.0
492
+ #
493
+ def host_script
494
+ @host_script ||= if (hostscript = @node.at_xpath('hostscript'))
495
+ HostScript.new(hostscript)
496
+ end
497
+ end
498
+
499
+ #
500
+ # Parses the traceroute information, if present.
501
+ #
502
+ # @yield [traceroute]
503
+ # If a block is given, it will be passed the traceroute information.
504
+ #
505
+ # @yieldparam [Traceroute] traceroute
506
+ # The traceroute information.
507
+ #
508
+ # @return [Traceroute]
509
+ # The traceroute information.
510
+ #
511
+ # @since 0.7.0
512
+ #
513
+ def traceroute
514
+ @traceroute ||= if (trace = @node.at_xpath('trace'))
515
+ Traceroute.new(trace)
516
+ end
517
+
518
+ yield @traceroute if (@traceroute && block_given?)
519
+ return @traceroute
520
+ end
521
+
522
+ #
523
+ # Converts the host to a String.
524
+ #
525
+ # @return [String]
526
+ # The hostname or address of the host.
527
+ #
528
+ # @see address
529
+ #
530
+ def to_s
531
+ (hostname || address).to_s
532
+ end
533
+
534
+ #
535
+ # Inspects the host.
536
+ #
537
+ # @return [String]
538
+ # The inspected host.
539
+ #
540
+ def inspect
541
+ "#<#{self.class}: #{self}>"
542
+ end
543
+
544
+ end
545
+ end
546
+ end
@@ -0,0 +1,26 @@
1
+ require 'nmap/xml/scripts'
2
+
3
+ module Nmap
4
+ class XML
5
+ #
6
+ # Represents the `hostscript` element.
7
+ #
8
+ # @since 1.0.0
9
+ #
10
+ class HostScript
11
+
12
+ include Scripts
13
+
14
+ #
15
+ # Initializes the HostScript object.
16
+ #
17
+ # @param [Nokogiri::XML::Node] node
18
+ # The XML node that contains the host information.
19
+ #
20
+ def initialize(node)
21
+ @node = node
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ module Nmap
2
+ class XML
3
+ #
4
+ # Represents a hostname.
5
+ #
6
+ # @since 1.0.0
7
+ #
8
+ class Hostname < Struct.new(:type, :name)
9
+
10
+ #
11
+ # Determines if the hostname was specified by the user.
12
+ #
13
+ # @return [Boolean]
14
+ #
15
+ # @since 0.8.0
16
+ #
17
+ def user?
18
+ self.type == 'user'
19
+ end
20
+
21
+ #
22
+ # Determines if the hostname is a DNS `PTR`.
23
+ #
24
+ # @return [Boolean]
25
+ #
26
+ # @since 0.8.0
27
+ #
28
+ def ptr?
29
+ self.type == 'PTR'
30
+ end
31
+
32
+ #
33
+ # Converts the hostname to a String.
34
+ #
35
+ # @return [String]
36
+ # The name of the host.
37
+ #
38
+ def to_s
39
+ self.name.to_s
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,26 @@
1
+ require 'nmap/xml/sequence'
2
+
3
+ module Nmap
4
+ class XML
5
+ #
6
+ # Represents an IP ID.
7
+ #
8
+ # @since 1.0.0
9
+ #
10
+ class IpIdSequence < Sequence
11
+
12
+ #
13
+ # Converts the IpidSequence class to a String.
14
+ #
15
+ # @return [String]
16
+ # The String form of the object.
17
+ #
18
+ # @since 0.5.0
19
+ #
20
+ def to_s
21
+ "description=#{description.inspect} values=#{values.inspect}"
22
+ end
23
+
24
+ end
25
+ end
26
+ end