inform-runtime 1.0.4
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/LICENSE +623 -0
- data/README.md +185 -0
- data/Rakefile +65 -0
- data/config/database.yml +37 -0
- data/exe/inform.rb +6 -0
- data/game/config.yml +5 -0
- data/game/example.inf +76 -0
- data/game/example.rb +90 -0
- data/game/forms/example_form.rb +2 -0
- data/game/grammar/game_grammar.inf.rb +11 -0
- data/game/languages/english.rb +2 -0
- data/game/models/example_model.rb +2 -0
- data/game/modules/example_module.rb +9 -0
- data/game/rules/example_state.rb +2 -0
- data/game/scripts/example_script.rb +2 -0
- data/game/topics/example_topic.rb +2 -0
- data/game/verbs/game_verbs.rb +15 -0
- data/game/verbs/metaverbs.rb +2028 -0
- data/lib/runtime/articles.rb +138 -0
- data/lib/runtime/builtins.rb +359 -0
- data/lib/runtime/color.rb +145 -0
- data/lib/runtime/command.rb +470 -0
- data/lib/runtime/config.rb +48 -0
- data/lib/runtime/context.rb +78 -0
- data/lib/runtime/daemon.rb +266 -0
- data/lib/runtime/database.rb +500 -0
- data/lib/runtime/events.rb +771 -0
- data/lib/runtime/experimental/handler_dsl.rb +175 -0
- data/lib/runtime/game.rb +74 -0
- data/lib/runtime/game_loader.rb +132 -0
- data/lib/runtime/grammar_parser.rb +553 -0
- data/lib/runtime/helpers.rb +177 -0
- data/lib/runtime/history.rb +45 -0
- data/lib/runtime/inflector.rb +195 -0
- data/lib/runtime/io.rb +174 -0
- data/lib/runtime/kernel.rb +450 -0
- data/lib/runtime/library.rb +59 -0
- data/lib/runtime/library_loader.rb +135 -0
- data/lib/runtime/link.rb +158 -0
- data/lib/runtime/logging.rb +197 -0
- data/lib/runtime/mixins.rb +570 -0
- data/lib/runtime/module.rb +202 -0
- data/lib/runtime/object.rb +761 -0
- data/lib/runtime/options.rb +104 -0
- data/lib/runtime/persistence.rb +292 -0
- data/lib/runtime/plurals.rb +60 -0
- data/lib/runtime/prototype.rb +307 -0
- data/lib/runtime/publication.rb +92 -0
- data/lib/runtime/runtime.rb +321 -0
- data/lib/runtime/session.rb +202 -0
- data/lib/runtime/stdlib.rb +604 -0
- data/lib/runtime/subscription.rb +47 -0
- data/lib/runtime/tag.rb +287 -0
- data/lib/runtime/tree.rb +204 -0
- data/lib/runtime/version.rb +24 -0
- data/lib/runtime/world_tree.rb +69 -0
- data/lib/runtime.rb +35 -0
- metadata +199 -0
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: false
|
|
3
|
+
|
|
4
|
+
# Copyright Nels Nelson 2008-2023 but freely usable (see license)
|
|
5
|
+
#
|
|
6
|
+
# This file is part of the Inform Runtime.
|
|
7
|
+
#
|
|
8
|
+
# The Inform Runtime is free software: you can redistribute it and/or
|
|
9
|
+
# modify it under the terms of the GNU General Public License as published
|
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# The Inform Runtime is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
JoinedTemplate = '%<str>s%<other>s'.freeze
|
|
23
|
+
ObjectIdentityTemplate = '#<%<klass>s:%<name>s:0x%<hex>x:%<id>d>'.freeze
|
|
24
|
+
ObjectIdentityAttributes = %i[name].freeze
|
|
25
|
+
AttributeTemplate = '@%<key>s=%<value>s'.freeze
|
|
26
|
+
|
|
27
|
+
# These are mixins to support Inform standard library built-ins and some
|
|
28
|
+
# so-called least-surprise idiomatic methods.
|
|
29
|
+
class Object
|
|
30
|
+
def +(other)
|
|
31
|
+
format(JoinedTemplate, str: self.to_s, other: other.to_s)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def identity
|
|
35
|
+
identification = { klass: self.class }
|
|
36
|
+
identifier = (self.respond_to?(:object?) && self.object? ? self.id : self.object_id) || 0
|
|
37
|
+
identification[:name] = (self.respond_to?(:name) ? self.name : nil) || 'unnamed'
|
|
38
|
+
identification[:hex] = identifier << 1
|
|
39
|
+
identification[:id] = identifier
|
|
40
|
+
format(ObjectIdentityTemplate, identification)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def number?
|
|
44
|
+
self.to_s.match?(/\A[+-]?\d+?(\.\d+)?\Z/)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def positive?
|
|
48
|
+
# TODO: This might be as simple as:
|
|
49
|
+
# self.to_i.positive?
|
|
50
|
+
self.to_s.match?(/\A+?\d+?(\.\d+)?\Z/)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def negative?
|
|
54
|
+
# TODO: This might be as simple as:
|
|
55
|
+
# self.to_i.negative?
|
|
56
|
+
self.to_s.match?(/\A-?\d+?(\.\d+)?\Z/)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def blank?
|
|
60
|
+
respond_to?(:empty?) ? empty? : self.nil?
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The NilClass class
|
|
65
|
+
class NilClass
|
|
66
|
+
def +(other)
|
|
67
|
+
case other
|
|
68
|
+
when String then format(JoinedTemplate, str: self.to_s, other: other.to_s)
|
|
69
|
+
when Integer then 0 + other
|
|
70
|
+
when Enumerable then [] + other
|
|
71
|
+
else format(JoinedTemplate, str: self.to_s, other: other.to_s)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def -(other)
|
|
76
|
+
case other
|
|
77
|
+
when String then self.to_s.delete other
|
|
78
|
+
when Integer then 0 - other
|
|
79
|
+
when Enumerable then [] - other
|
|
80
|
+
else self.to_s.delete other
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def >(other, default = false)
|
|
85
|
+
other < 0
|
|
86
|
+
# return default unless other.respond_to? __method__
|
|
87
|
+
# 0.send(__method__, other)
|
|
88
|
+
rescue StandardError => _e
|
|
89
|
+
default
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def >=(other, default = false)
|
|
93
|
+
other <= 0
|
|
94
|
+
# return default unless other.respond_to? __method__
|
|
95
|
+
# 0.send(__method__, other)
|
|
96
|
+
rescue StandardError => _e
|
|
97
|
+
default
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def <(other, default = false)
|
|
101
|
+
other > 0
|
|
102
|
+
# return default unless other.respond_to? __method__
|
|
103
|
+
# 0.send(__method__, other)
|
|
104
|
+
rescue StandardError => _e
|
|
105
|
+
default
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def <=(other, default = false)
|
|
109
|
+
other >= 0
|
|
110
|
+
# return default unless other.respond_to? __method__
|
|
111
|
+
# 0.send(__method__, other)
|
|
112
|
+
rescue StandardError => _e
|
|
113
|
+
default
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# def & (other)
|
|
117
|
+
# 0 & other
|
|
118
|
+
# # return self unless other.respond_to? __method__
|
|
119
|
+
# # 0.send(__method__, other) rescue self
|
|
120
|
+
# end
|
|
121
|
+
|
|
122
|
+
# def | (other)
|
|
123
|
+
# 0 | other
|
|
124
|
+
# # return self unless other.respond_to? __method__
|
|
125
|
+
# # 0.send(__method__, other) rescue self
|
|
126
|
+
# end
|
|
127
|
+
|
|
128
|
+
# Incompatible with RubyUnits::Unit#to_base
|
|
129
|
+
# def *(other)
|
|
130
|
+
# 0 * other
|
|
131
|
+
# return self unless other.respond_to? __method__
|
|
132
|
+
# 0.send(__method__, other) rescue self
|
|
133
|
+
# end
|
|
134
|
+
|
|
135
|
+
def [](_key)
|
|
136
|
+
self
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def []=(_key, _value)
|
|
140
|
+
self
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def to_ary
|
|
144
|
+
[]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def to_i
|
|
148
|
+
0
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def to_f
|
|
152
|
+
0.0
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def to_str
|
|
156
|
+
self.to_s
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def to_sym
|
|
160
|
+
self
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
alias intern to_sym
|
|
164
|
+
def empty?
|
|
165
|
+
true
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
include Enumerable
|
|
169
|
+
|
|
170
|
+
def each(*_args, &_block)
|
|
171
|
+
return # Don't do anything, I'm nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def join(s)
|
|
175
|
+
[].join(s)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def refresh
|
|
179
|
+
self
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
# class NilClass
|
|
183
|
+
|
|
184
|
+
# The TrueClass class
|
|
185
|
+
class TrueClass
|
|
186
|
+
def +(other)
|
|
187
|
+
format(JoinedTemplate, str: self.to_s, other: other.to_s)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def >(other)
|
|
191
|
+
other < 1
|
|
192
|
+
rescue StandardError => _e
|
|
193
|
+
false
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def <(other)
|
|
197
|
+
other > 1
|
|
198
|
+
rescue StandardError => _e
|
|
199
|
+
false
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def to_i
|
|
203
|
+
1
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def to_f
|
|
207
|
+
1.0
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# The FalseClass class
|
|
212
|
+
class FalseClass
|
|
213
|
+
def +(other)
|
|
214
|
+
format(JoinedTemplate, str: self.to_s, other: other.to_s)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def >(other)
|
|
218
|
+
other < 0
|
|
219
|
+
rescue StandardError => _e
|
|
220
|
+
false
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def <(other)
|
|
224
|
+
other > 0
|
|
225
|
+
rescue StandardError => _e
|
|
226
|
+
false
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Don't do this, it will almost definitely break a lot of things.
|
|
230
|
+
# def ==(other)
|
|
231
|
+
# other.number? && other.to_i == 0
|
|
232
|
+
# rescue StandardError => _e
|
|
233
|
+
# false
|
|
234
|
+
# end
|
|
235
|
+
|
|
236
|
+
def to_i
|
|
237
|
+
0
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def to_f
|
|
241
|
+
0.0
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# The Numeric class
|
|
246
|
+
class Numeric
|
|
247
|
+
def commas(delimiter = ',')
|
|
248
|
+
parts = self.to_s.split('.')
|
|
249
|
+
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, format(JoinedTemplate, str: '\1', other: delimiter))
|
|
250
|
+
parts.join('.')
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# The Integer class
|
|
255
|
+
class Integer
|
|
256
|
+
alias equals_operator ==
|
|
257
|
+
def ==(other)
|
|
258
|
+
case other
|
|
259
|
+
when NilClass, FalseClass then self == 0
|
|
260
|
+
when TrueClass then self == 1
|
|
261
|
+
else equals_operator(other)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
alias lessthan_operator <
|
|
266
|
+
def <(other)
|
|
267
|
+
case other
|
|
268
|
+
when NilClass, FalseClass then self < 0
|
|
269
|
+
when TrueClass then self < 1
|
|
270
|
+
else lessthan_operator(other)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
alias lessthanorequal_operator <=
|
|
275
|
+
def <=(other)
|
|
276
|
+
case other
|
|
277
|
+
when NilClass, FalseClass then self <= 0
|
|
278
|
+
when TrueClass then self <= 1
|
|
279
|
+
else lessthanorequal_operator(other)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
alias greaterthan_operator >
|
|
284
|
+
def >(other)
|
|
285
|
+
case other
|
|
286
|
+
when NilClass, FalseClass then self > 0
|
|
287
|
+
when TrueClass then self > 1
|
|
288
|
+
else greaterthan_operator(other)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
alias greaterthanorequal_operator >=
|
|
293
|
+
def >=(other)
|
|
294
|
+
case other
|
|
295
|
+
when NilClass, FalseClass then self >= 0
|
|
296
|
+
when TrueClass then self >= 1
|
|
297
|
+
else greaterthanorequal_operator(other)
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
alias plus_operator +
|
|
302
|
+
def +(other)
|
|
303
|
+
case other
|
|
304
|
+
when String then format(JoinedTemplate, str: self.to_s, other: other.to_s)
|
|
305
|
+
when NilClass, FalseClass then self + 0
|
|
306
|
+
when TrueClass then self + 1
|
|
307
|
+
else plus_operator(other)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def number?
|
|
312
|
+
true
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Some math constants
|
|
316
|
+
N_BYTES = [42].pack('i').size unless defined? N_BYTES
|
|
317
|
+
N_BITS = N_BYTES * 8 unless defined? N_BITS
|
|
318
|
+
MAX = (2**(N_BITS - 2)) - 1 unless defined? MAX
|
|
319
|
+
MIN = -MAX - 1 unless defined? MIN
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# The String class
|
|
323
|
+
class String
|
|
324
|
+
def +(other)
|
|
325
|
+
format(JoinedTemplate, str: self, other: other)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Replace the second of three capture groups with the given block.
|
|
329
|
+
def midsub(regexp, &_block)
|
|
330
|
+
self.gsub(regexp) { '\1' + yield('\2') + '\3' }
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def sentence_case
|
|
334
|
+
self.capitalize
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def poststrip
|
|
338
|
+
self.gsub(/\s+$/, '')
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def poststrip!
|
|
342
|
+
self.gsub!(/\s+$/, '')
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def prestrip
|
|
346
|
+
self.gsub(/^\s+/, '')
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def prestrip!
|
|
350
|
+
self.gsub!(/^\s+/, '')
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def to_n
|
|
354
|
+
/\./.match?(self) ? self.to_f : self.to_i
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
DefaultUnderscoreOptions = {
|
|
358
|
+
downcase: true
|
|
359
|
+
}.freeze
|
|
360
|
+
|
|
361
|
+
def underscore(options = {})
|
|
362
|
+
options = DefaultUnderscoreOptions.merge(options)
|
|
363
|
+
return snake_case if options[:downcase]
|
|
364
|
+
self
|
|
365
|
+
.gsub(/::/, '/')
|
|
366
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
|
367
|
+
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
|
368
|
+
.tr(' -', '_')
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def snake_case
|
|
372
|
+
self
|
|
373
|
+
.gsub(/::/, '/')
|
|
374
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
|
375
|
+
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
|
376
|
+
.tr(' -', '_')
|
|
377
|
+
.downcase
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def titleize
|
|
381
|
+
self.split(/[\W_]/).map(&:capitalize).join(' ')
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def camelize(_s = nil)
|
|
385
|
+
self.downcase.split(/[^a-z0-9]/).inject('') do |str1, str2|
|
|
386
|
+
str2.length > 1 ? [str1, str2[0].chr.upcase, str2[1..]].join : str1.to_s
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def dequote
|
|
391
|
+
self.gsub(/(\A['"]|['"]\Z)/, '')
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def save(file)
|
|
395
|
+
File.write(file, self)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
HumanUnits = { b: 'bytes', kb: 'kilobytes', mb: 'megabytes', gb: 'gigabytes', tb: 'terabytes' }.freeze
|
|
399
|
+
HumanUnitsRegex = /(#{HumanUnits.keys.join('|')})/i.freeze
|
|
400
|
+
|
|
401
|
+
def to_human
|
|
402
|
+
scalar, unit = self.split
|
|
403
|
+
unit.sub!(HumanUnitsRegex) { |s| HumanUnits[s.downcase.to_sym] }
|
|
404
|
+
[scalar.to_f.commas, unit].join(' ')
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
# class String
|
|
408
|
+
|
|
409
|
+
# The Symbol class
|
|
410
|
+
class Symbol
|
|
411
|
+
def +(other)
|
|
412
|
+
format(JoinedTemplate, str: self.to_s, other: other.to_s)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def to_n
|
|
416
|
+
to_s.to_n
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def underscore(options = {})
|
|
420
|
+
to_s.underscore(options)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def snake_case
|
|
424
|
+
to_s.snake_case
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def titleize
|
|
428
|
+
to_s.titleize
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def camelize(_s = nil)
|
|
432
|
+
to_s.camelize
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def dequote
|
|
436
|
+
to_s.dequote
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def save(file)
|
|
440
|
+
to_s.save file
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
CommaSpaceString = ', '.freeze
|
|
445
|
+
ListCommaTemplate = '%<str>s, %<other>s %<last>s'.freeze
|
|
446
|
+
ListSansCommaTemplate = '%<str>s, %<other>s %<last>s'.freeze
|
|
447
|
+
SentenceTemplate = '%<str>s %<other>s %<last>s'.freeze
|
|
448
|
+
|
|
449
|
+
# The Array class
|
|
450
|
+
class Array
|
|
451
|
+
alias plus_operator +
|
|
452
|
+
def +(other)
|
|
453
|
+
case other
|
|
454
|
+
when String then self.to_s + other
|
|
455
|
+
else plus_operator(other)
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def sum(&block)
|
|
460
|
+
return inject(0, &block) if block_given?
|
|
461
|
+
inject(:+)
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def average
|
|
465
|
+
return self.first if self.length == 1
|
|
466
|
+
(self.sum / self.length.to_f)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def having(method)
|
|
470
|
+
select { |a| a.respond_to? method.to_sym }
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def identities
|
|
474
|
+
map { |o| "#{o} [#{o.identity}]" }
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def longest_length
|
|
478
|
+
inject(0) do |max, x|
|
|
479
|
+
[x.to_s.length, max].max
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def pad(min = 1, max = longest_length + min)
|
|
484
|
+
each do |x|
|
|
485
|
+
yield(format(JoinedTemplate, str: x.to_s, other: ' ' * [max - x.to_s.length, min].max), x)
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def to_sentence(conjunction = 'and')
|
|
490
|
+
case length
|
|
491
|
+
when 0 then ''
|
|
492
|
+
when 1 then self.first.dup.to_s
|
|
493
|
+
when 2
|
|
494
|
+
format(SentenceTemplate, str: self[0], other: conjunction, last: self[1])
|
|
495
|
+
else
|
|
496
|
+
format(SentenceTemplate, str: self[0...].join(CommaSpaceString), other: conjunction, last: self[-1])
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
alias ruby_to_s to_s
|
|
501
|
+
def to_s(separator = CommaSpaceString)
|
|
502
|
+
# This will make it possible to print Lists nicely, but not screw up
|
|
503
|
+
# the behavior of the List.
|
|
504
|
+
map(&:to_s).join(separator)
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
alias random sample
|
|
508
|
+
end
|
|
509
|
+
# class Array
|
|
510
|
+
|
|
511
|
+
# The EmptyImmutableEnumerable class
|
|
512
|
+
class EmptyImmutableEnumerable
|
|
513
|
+
def initialize(klass)
|
|
514
|
+
@subject = klass.new.freeze
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def respond_to_missing?(method)
|
|
518
|
+
@subject.respond_to?(method)
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def method_missing(method, *args, &block)
|
|
522
|
+
return @subject.send(method, *args, &block) if @subject.respond_to?(method)
|
|
523
|
+
super
|
|
524
|
+
rescue StandardError => _e
|
|
525
|
+
args.first
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# The Array class
|
|
530
|
+
class Array
|
|
531
|
+
Empty = EmptyImmutableEnumerable.new(Array)
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
# The Hash class
|
|
535
|
+
class Hash
|
|
536
|
+
Empty = EmptyImmutableEnumerable.new(Hash)
|
|
537
|
+
|
|
538
|
+
def sort
|
|
539
|
+
keys.sort.each { |k| self[k] = self.delete(k) }
|
|
540
|
+
self
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
MethodBooleanPattern = %r{\?$}.freeze
|
|
544
|
+
MethodWriterPattern = %r{=$}.freeze
|
|
545
|
+
|
|
546
|
+
# TODO: Test
|
|
547
|
+
# def method_missing(method, *args, &block)
|
|
548
|
+
# m = method.to_s
|
|
549
|
+
# return false if MethodBooleanPattern.match?(m)
|
|
550
|
+
# mname = MethodWriterPattern.match?(m) ? m.chop : m
|
|
551
|
+
# property = mname.to_sym
|
|
552
|
+
# if MethodWriterPattern.match?(m)
|
|
553
|
+
# self[mname] = *args
|
|
554
|
+
# elsif self.key?(mname)
|
|
555
|
+
# self.fetch(mname, Hash::Empty)
|
|
556
|
+
# elsif self.key?(property)
|
|
557
|
+
# self.fetch(property, Hash::Empty)
|
|
558
|
+
# else
|
|
559
|
+
# super
|
|
560
|
+
# end
|
|
561
|
+
# end
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# The Set class
|
|
565
|
+
class Set
|
|
566
|
+
alias ruby_to_s to_s
|
|
567
|
+
def to_s(separator = CommaSpaceString)
|
|
568
|
+
to_a.to_s(separator)
|
|
569
|
+
end
|
|
570
|
+
end
|