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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +623 -0
  3. data/README.md +185 -0
  4. data/Rakefile +65 -0
  5. data/config/database.yml +37 -0
  6. data/exe/inform.rb +6 -0
  7. data/game/config.yml +5 -0
  8. data/game/example.inf +76 -0
  9. data/game/example.rb +90 -0
  10. data/game/forms/example_form.rb +2 -0
  11. data/game/grammar/game_grammar.inf.rb +11 -0
  12. data/game/languages/english.rb +2 -0
  13. data/game/models/example_model.rb +2 -0
  14. data/game/modules/example_module.rb +9 -0
  15. data/game/rules/example_state.rb +2 -0
  16. data/game/scripts/example_script.rb +2 -0
  17. data/game/topics/example_topic.rb +2 -0
  18. data/game/verbs/game_verbs.rb +15 -0
  19. data/game/verbs/metaverbs.rb +2028 -0
  20. data/lib/runtime/articles.rb +138 -0
  21. data/lib/runtime/builtins.rb +359 -0
  22. data/lib/runtime/color.rb +145 -0
  23. data/lib/runtime/command.rb +470 -0
  24. data/lib/runtime/config.rb +48 -0
  25. data/lib/runtime/context.rb +78 -0
  26. data/lib/runtime/daemon.rb +266 -0
  27. data/lib/runtime/database.rb +500 -0
  28. data/lib/runtime/events.rb +771 -0
  29. data/lib/runtime/experimental/handler_dsl.rb +175 -0
  30. data/lib/runtime/game.rb +74 -0
  31. data/lib/runtime/game_loader.rb +132 -0
  32. data/lib/runtime/grammar_parser.rb +553 -0
  33. data/lib/runtime/helpers.rb +177 -0
  34. data/lib/runtime/history.rb +45 -0
  35. data/lib/runtime/inflector.rb +195 -0
  36. data/lib/runtime/io.rb +174 -0
  37. data/lib/runtime/kernel.rb +450 -0
  38. data/lib/runtime/library.rb +59 -0
  39. data/lib/runtime/library_loader.rb +135 -0
  40. data/lib/runtime/link.rb +158 -0
  41. data/lib/runtime/logging.rb +197 -0
  42. data/lib/runtime/mixins.rb +570 -0
  43. data/lib/runtime/module.rb +202 -0
  44. data/lib/runtime/object.rb +761 -0
  45. data/lib/runtime/options.rb +104 -0
  46. data/lib/runtime/persistence.rb +292 -0
  47. data/lib/runtime/plurals.rb +60 -0
  48. data/lib/runtime/prototype.rb +307 -0
  49. data/lib/runtime/publication.rb +92 -0
  50. data/lib/runtime/runtime.rb +321 -0
  51. data/lib/runtime/session.rb +202 -0
  52. data/lib/runtime/stdlib.rb +604 -0
  53. data/lib/runtime/subscription.rb +47 -0
  54. data/lib/runtime/tag.rb +287 -0
  55. data/lib/runtime/tree.rb +204 -0
  56. data/lib/runtime/version.rb +24 -0
  57. data/lib/runtime/world_tree.rb +69 -0
  58. data/lib/runtime.rb +35 -0
  59. metadata +199 -0
@@ -0,0 +1,761 @@
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
+ # Object
22
+
23
+ if defined?(Sequel::Migration)
24
+ # The ObjectSetup class
25
+ class ObjectSetup < Sequel::Migration
26
+ # rubocop: disable Metrics/MethodLength
27
+ def up
28
+ create_table? :object do
29
+ primary_key :id
30
+ foreign_key :parent_id, :object, on_delete: :set_null
31
+ index :name
32
+ index :created_at
33
+
34
+ String :name, text: true
35
+ String :short_name, text: true
36
+ String :description, text: true
37
+ String :object_type, null: false
38
+ String :properties, text: true, default: {}.to_yaml.strip
39
+ DateTime :created_at
40
+ DateTime :modified_at
41
+ end
42
+ end
43
+ # rubocop: enable Metrics/MethodLength
44
+
45
+ def down
46
+ drop_table?(:object, cascade: true)
47
+ end
48
+ end
49
+ # class ObjectSetup
50
+ end
51
+ # defined?(Sequel::Migration)
52
+
53
+ def Object(name, klass = Inform::Object, &block)
54
+ obj = klass.fetch_or_create_by_name(name)
55
+ obj.with(&block) if block_given?
56
+ obj.save_changes
57
+ obj
58
+ end
59
+
60
+ # The Inform module
61
+ module Inform
62
+ unless defined?(Inform::SystemObjects)
63
+ SystemObjects = if defined?(Java)
64
+ java.util.concurrent.ConcurrentHashMap.new
65
+ else
66
+ {}
67
+ end
68
+ end
69
+
70
+ # The Inform::Object class
71
+ # rubocop: disable Metrics/ClassLength
72
+ class Object < Sequel::Model
73
+ logger
74
+ plugin :defaults_setter
75
+ plugin :dirty
76
+ plugin :rcte_tree
77
+ plugin :serialization
78
+ plugin :serialization_modification_detection
79
+ plugin :single_table_inheritance, :object_type
80
+ set_primary_key :id
81
+ serialize_attributes :yaml, :properties
82
+ one_to_many :links, join_table: :link, key: :from_id
83
+ one_to_many :tagged, key: :object_id
84
+ one_to_many :modularized, key: :object_id
85
+ many_to_many :modules,
86
+ class: 'Inform::Module', join_table: :modularized, left_key: :object_id, right_key: :module_id
87
+ many_to_many :tags, class: 'Inform::Tag', join_table: :tagged, left_key: :object_id, right_key: :tag_id
88
+ one_to_many :children, key: :parent_id, class: self, order: %i[name id], eager: %i[modules tags links]
89
+
90
+ # rubocop: disable Metrics/MethodLength
91
+ # rubocop: disable Metrics/PerceivedComplexity
92
+ def initialize(*args, &block)
93
+ if args.empty?
94
+ if block_given?
95
+ super() { |obj| obj }
96
+ else
97
+ super()
98
+ end
99
+ elsif args.first.is_a?(String)
100
+ short_name = args.first
101
+ if block_given?
102
+ super(name: short_name, short_name: short_name) { |obj| obj }
103
+ else
104
+ super(name: short_name, short_name: short_name)
105
+ end
106
+ elsif block_given?
107
+ super { |obj| obj }
108
+ else
109
+ super(*args)
110
+ end
111
+ @tags_semaphore = Mutex.new
112
+ @semaphore = Mutex.new
113
+ self.with(&block) if block_given?
114
+ self.save_changes
115
+ end
116
+ # rubocop: enable Metrics/MethodLength
117
+ # rubocop: enable Metrics/PerceivedComplexity
118
+
119
+ def self.fetch_or_create_by_name(name)
120
+ db.transaction(savepoint: true) do
121
+ for_update.where(name: name).first || create(name: name, short_name: name)
122
+ end
123
+ end
124
+
125
+ # rubocop: disable Metrics/AbcSize
126
+ def short_name(*args)
127
+ return (self.short_name = args.first) unless args.empty?
128
+ textual_name = self.values[:short_name]
129
+ synonyms = self.values[:name]
130
+ if textual_name.nil? || (textual_name.respond_to?(:empty?) && textual_name.empty?)
131
+ self.values[:short_name] = synonyms
132
+ self.save_changes if self.respond_to?(:save_changes)
133
+ self.values[:short_name]
134
+ else
135
+ textual_name
136
+ end
137
+ end
138
+ # rubocop: enable Metrics/AbcSize
139
+
140
+ def name(*args)
141
+ return args.first.object_name if args.first.respond_to?(:object_name)
142
+ return self[:name] if args.empty?
143
+ self[:name] = args.length > 1 ? args.join(' ') : args.first
144
+ self.save_changes if self.respond_to?(:save_changes)
145
+ self[:name]
146
+ end
147
+
148
+ def description(*args)
149
+ return self[:description] if args.nil? || args.empty?
150
+ self[:description] = args.length > 1 ? args.join(' ') : args.first
151
+ self.save_changes if self.respond_to?(:save_changes)
152
+ self[:description]
153
+ end
154
+
155
+ def object_name
156
+ self[:display_name] || self[:short_name] || self[:name]
157
+ end
158
+
159
+ def name_words
160
+ self.values[:name].split(/[,\s]+/).join(', ')
161
+ end
162
+
163
+ def semaphore
164
+ @semaphore ||= Mutex.new
165
+ end
166
+
167
+ def tags_semaphore
168
+ @tags_semaphore ||= Mutex.new
169
+ end
170
+
171
+ alias tags_original tags
172
+ def tags
173
+ tags_semaphore.synchronize { tags_original }
174
+ end
175
+
176
+ def routines
177
+ ((self.methods - Object.instance_methods) - Inform::Object.instance_methods).sort.uniq
178
+ end
179
+
180
+ def <=>(other)
181
+ self.name <=> other.name
182
+ end
183
+
184
+ def ==(other)
185
+ other.respond_to?(:values) ? self.values[:id] == other.values[:id] : super
186
+ end
187
+
188
+ def parse(s)
189
+ return if inflib.nil?
190
+ semaphore.synchronize do
191
+ inflib.println(inflib.parse(s))
192
+ end
193
+ end
194
+
195
+ def invoke(a, *args)
196
+ return unless inflib
197
+ inflib.invoke(a, *args)
198
+ end
199
+
200
+ def _invoke(a, *args)
201
+ return unless inflib
202
+ inflib._invoke(a, *args)
203
+ end
204
+
205
+ def inflib
206
+ return Inform::Runtime.libraries[self] if self.has?(:animate) || (defined?(Character) && self.is_a?(Character))
207
+ @inflib
208
+ end
209
+
210
+ # rubocop: disable Style/TrivialAccessors
211
+ def inflib=(inflib)
212
+ @inflib = inflib
213
+ end
214
+ # rubocop: enable Style/TrivialAccessors
215
+
216
+ def println(s, isolate: false)
217
+ inflib&.println(s, isolate: isolate)
218
+ end
219
+
220
+ def print(s, isolate: false)
221
+ inflib&.print(s, isolate: isolate)
222
+ end
223
+
224
+ Visited = defined?(Java) ? java.util.concurrent.ConcurrentHashMap.new : {}
225
+ def visited
226
+ Visited[self] ||= []
227
+ end
228
+
229
+ def safe_refresh
230
+ self.refresh
231
+ rescue Sequel::NoExistingObject => e
232
+ log.warn "Object refresh failure: #{e.message}; ignoring"
233
+ end
234
+
235
+ def safe_save
236
+ self.save_changes if self.respond_to?(:save_changes)
237
+ rescue Sequel::NoExistingObject => e
238
+ log.warn "Object save failure: #{e.message}; ignoring"
239
+ end
240
+
241
+ def safe_init
242
+ self.init if self.respond_to? :init
243
+ rescue Sequel::NoExistingObject => e
244
+ log.warn "Object initialization failure: #{e.message}; ignoring"
245
+ end
246
+
247
+ def classify_as(klass)
248
+ return if klass.nil?
249
+ klass = find_class klass unless klass.is_a? Class
250
+ self.object_type = klass.name
251
+ self.safe_save
252
+ self.safe_init
253
+ Inform::Object[self.id]
254
+ end
255
+
256
+ def sys_children
257
+ SystemObjects[identity] ||= []
258
+ end
259
+
260
+ # def eql?(other)
261
+ # self == other
262
+ # end
263
+
264
+ # def ==(other)
265
+ # self === other
266
+ # end
267
+
268
+ # TODO: Implement a unit test for this
269
+ def <<(o)
270
+ return if o.nil?
271
+ return if o == self
272
+ if o.sysobj?
273
+ o.parent = self
274
+ sys_children << o
275
+ else
276
+ self.add_child o
277
+ self.save_changes if self.respond_to?(:save_changes)
278
+ self.refresh
279
+ end
280
+ end
281
+
282
+ def remove
283
+ self.parent.remove_child(self.id) if self.parent&.children&.include?(self)
284
+ rescue StandardError => e
285
+ log.error e
286
+ end
287
+
288
+ COUNT_FAIL_MESSAGE = 'Failed to count children for %<obj>s: %<message>s'.freeze
289
+
290
+ def empty?
291
+ self.class.where(parent_id: self.id).count.zero?
292
+ rescue StandardError => e
293
+ log.warn format(COUNT_FAIL_MESSAGE, obj: self.identity, message: e.message)
294
+ children_dataset.count.zero?
295
+ end
296
+
297
+ def child(o = nil)
298
+ o ? o.child : self.children.first
299
+ end
300
+
301
+ def location
302
+ linkto(:location) || self.parent
303
+ end
304
+
305
+ def list_together
306
+ linkto :list_together
307
+ end
308
+
309
+ def before_create
310
+ self.created_at ||= Time.now
311
+ super
312
+ end
313
+
314
+ def after_create
315
+ super
316
+ index_words if self.respond_to?(:index_words)
317
+ init
318
+ end
319
+
320
+ def before_destroy
321
+ self.safe_refresh if self.respond_to?(:safe_refresh)
322
+ self.children.each { |x| x.destroy unless x.has? :prized }
323
+ super
324
+ end
325
+
326
+ def after_destroy
327
+ super
328
+ end
329
+
330
+ def after_initialize
331
+ super
332
+ apply_instance_behavior! if self.respond_to?(:apply_instance_behavior!)
333
+ end
334
+
335
+ def after_refresh
336
+ super
337
+ drain_deferred_with! if self.respond_to?(:drain_deferred_with!)
338
+ apply_instance_behavior! if self.respond_to?(:apply_instance_behavior!)
339
+ end
340
+
341
+ def after_load
342
+ super
343
+ drain_deferred_with! if self.respond_to?(:drain_deferred_with!)
344
+ end
345
+
346
+ def after_save
347
+ super
348
+ self.modified_at = Time.now
349
+ end
350
+
351
+ def before_save
352
+ super
353
+ end
354
+
355
+ def before_clone(copy); end
356
+
357
+ def after_clone(copy)
358
+ copy.untag :prized
359
+ end
360
+
361
+ def init
362
+ # A designer should implement this in a subclass if necessary
363
+ end
364
+
365
+ # rubocop: disable Metrics/AbcSize
366
+ # rubocop: disable Metrics/MethodLength
367
+ def clone
368
+ return if Session.players.include?(self) # Don't clone players.
369
+ data = self.values.dup
370
+ data.delete :id
371
+ data.delete :attributes
372
+ data.delete :properties
373
+ copy = self.class.new(data)
374
+ before_clone(copy) if respond_to?(:before_clone)
375
+ copy.properties = self.properties.dup
376
+ copy.save
377
+ copy.untag(*copy.tags)
378
+ copy.tag(*self.tags)
379
+ copy.mod(*self.modules)
380
+ copy.link(:original, self)
381
+ self.children.each { |x| copy << x.clone }
382
+ after_clone(copy) if respond_to?(:after_clone)
383
+ copy
384
+ end
385
+ # rubocop: enable Metrics/AbcSize
386
+ # rubocop: enable Metrics/MethodLength
387
+
388
+ # Create a clone of this object that is a
389
+ # non-persistent Inform::System::Object instead
390
+ def sysclone(klass = Inform::System::Object)
391
+ copy = klass.new(self.short_name)
392
+ copy.name = self.name
393
+ # TODO: Support children cloning (maybe shallow only)
394
+ copy.properties = self.properties.dup
395
+ copy.properties[:source_id] = self.id
396
+ copy.tags = self.nil_safe_tags
397
+ # TODO: Support modules
398
+ after_clone(copy) if respond_to?(:after_clone)
399
+ copy
400
+ end
401
+
402
+ ExportFileNameTemplate = '%<self>s-%<id>s.%<ext>'.freeze
403
+ JsonOptions = {
404
+ include: %i[tagged modularized]
405
+ }.freeze
406
+
407
+ def export_json
408
+ return unless respond_to? :to_json
409
+ self.to_json(**JsonOptions).save(format(ExportFileNameTemplate, name: self.to_s, id: self.id, ext: :json))
410
+ end
411
+
412
+ def export_yaml
413
+ return unless self.respond_to? :to_yaml
414
+ self.to_yaml.save(format(ExportFileNameTemplate, name: self.to_s, id: self.id, ext: :yml))
415
+ end
416
+
417
+ XmlOptions = {
418
+ except: :id,
419
+ include: {
420
+ tagged: {
421
+ except: :id
422
+ },
423
+ modularized: {
424
+ except: :id
425
+ }
426
+ }
427
+ }.freeze
428
+ XmlFileNameTemplate = '%<self>s-%<id>s.json'.freeze
429
+
430
+ def export_xml
431
+ return unless self.respond_to? :to_xml
432
+ self.to_xml(**XmlOptions).save(format(ExportFileNameTemplate, name: self.to_s, id: self.id, ext: :xml))
433
+ end
434
+
435
+ def encode_with(coder)
436
+ %w[id name short_name description properties links modularized tagged].each do |v|
437
+ coder[v] = self.send(v) if self.respond_to? v
438
+ end
439
+ end
440
+
441
+ # def init_with(coder)
442
+ # %w[ id name short_name description properties ].each do |v|
443
+ # m = "#{v}=".to_sym
444
+ # self.send(m, coder[v]) if self.respond_to? m
445
+ # end
446
+ # log.debug "Importing #{self.inspect}"
447
+ # log.debug "Importing #{self.to_hash.inspect}"
448
+ # log.debug " - #{(self.methods - ::Object.instance_methods).uniq.sort}"
449
+ # self
450
+ # end
451
+ end
452
+ # rubocop: enable Metrics/ClassLength
453
+ # class Inform::Object
454
+ end
455
+ # module Inform
456
+
457
+ # The Inform module
458
+ module Inform
459
+ # The Inform::System module
460
+ module System
461
+ # The Inform::System::Object class
462
+ # rubocop: disable Metrics/ClassLength
463
+ class Object
464
+ Instances = Struct.new(:memo).new(defined?(Java) ? java.util.concurrent.CopyOnWriteArrayList.new : [])
465
+ attr_accessor :short_name, :display_name, :inflib, :properties, :links,
466
+ :modules, :tags, :created_at, :modified_at, :visited
467
+
468
+ attr_writer :description
469
+
470
+ alias values properties
471
+
472
+ # rubocop: disable Metrics/AbcSize
473
+ # rubocop: disable Metrics/MethodLength
474
+ def initialize(textual_name = nil, *args, &block)
475
+ super(*args)
476
+ @name = textual_name || self.class.name
477
+ @short_name = textual_name || format('(%<short_name>s)', short_name: self.class.name)
478
+ @display_name = textual_name
479
+ @description = nil
480
+ @adjectives = []
481
+ @properties = {}
482
+ @links = []
483
+ @modules = []
484
+ @tags = []
485
+ @children = []
486
+ @visited = []
487
+ @created_at = @modified_at = Time.now
488
+ index_words if respond_to?(:index_words)
489
+ index_obj(self)
490
+ self.with(&block) if block_given?
491
+ end
492
+ # rubocop: enable Metrics/AbcSize
493
+ # rubocop: enable Metrics/MethodLength
494
+
495
+ def index_obj(obj)
496
+ if Instances.memo.respond_to?(:add)
497
+ Instances.memo.add(obj)
498
+ else
499
+ Instances.memo << obj
500
+ end
501
+ end
502
+
503
+ def self.all
504
+ Instances.memo.dup
505
+ end
506
+
507
+ def self.clear
508
+ Instances.memo.clear
509
+ end
510
+
511
+ def [](property)
512
+ return description if property == :description
513
+ return name if property == :name
514
+ return short_name if property == :short_name
515
+ properties[property]
516
+ end
517
+
518
+ def to_hash
519
+ properties.merge(
520
+ id: id,
521
+ name: name,
522
+ short_name: short_name,
523
+ description: description
524
+ )
525
+ end
526
+
527
+ def <=>(other)
528
+ self.name <=> other.name
529
+ end
530
+
531
+ def parse(s)
532
+ return if inflib.nil?
533
+ inflib.semaphore.synchronize do
534
+ inflib.println(inflib.parse(s))
535
+ end
536
+ end
537
+
538
+ def id
539
+ self.object_id
540
+ end
541
+
542
+ def object_type
543
+ self.class
544
+ end
545
+
546
+ def name(*args)
547
+ return args.first.object_name if args.first.respond_to?(:object_name)
548
+ return @name if args.empty?
549
+ @name = ([@name] | args).flatten.uniq
550
+ index_words if respond_to?(:index_words)
551
+ @name
552
+ end
553
+
554
+ def description(*args)
555
+ return @description if args.nil? || args.empty?
556
+ @description = args.length > 1 ? args.join(' ') : args
557
+ @description
558
+ end
559
+
560
+ def object_name
561
+ @display_name || @short_name || @name
562
+ end
563
+
564
+ def name_words
565
+ @name.split(/[,\s]+/).join(', ')
566
+ end
567
+
568
+ def <<(o)
569
+ return if self == o
570
+ return unless o.object?
571
+ o.parent = self
572
+ @children << o
573
+ end
574
+
575
+ def remove
576
+ @children.clear
577
+ @parent = nil
578
+ end
579
+
580
+ def empty?(*args, &block)
581
+ self.children.empty?(*args, &block)
582
+ end
583
+
584
+ def children(o = nil, prop = :@children)
585
+ return @children if o.nil?
586
+ return o.instance_variable_get(prop).length if o.instance_variable_defined?(prop)
587
+ o.children.length
588
+ end
589
+
590
+ # TODO: Remove if possible after unit test implementation
591
+ def location
592
+ self.parent
593
+ end
594
+
595
+ def list_together
596
+ linkto :list_together
597
+ end
598
+
599
+ def ancestors
600
+ self.parent.nil? ? [] : self.parent.ancestors + [self.parent]
601
+ end
602
+
603
+ def descendants
604
+ @children + @children.map(&:descendants).flatten
605
+ end
606
+
607
+ def root
608
+ self.parent.nil? ? self : self.parent.root
609
+ end
610
+
611
+ def parent(o = nil, &block)
612
+ return @parent if o.nil?
613
+ o.safe_refresh if o.respond_to? :safe_refresh
614
+ return unless o.respond_to? :parent
615
+ obj_parent = block_given? ? parent_selector(o, &block) : o.parent
616
+ return nil if obj_parent.nil?
617
+ obj_parent.safe_refresh if obj_parent.respond_to? :safe_refresh
618
+ obj_parent
619
+ end
620
+
621
+ # rubocop: disable Style/TrivialAccessors
622
+ def parent=(o)
623
+ @parent = o
624
+ end
625
+ # rubocop: enable Style/TrivialAccessors
626
+
627
+ def _set_object(link_name, obj = nil)
628
+ link(link_name, obj).to
629
+ end
630
+
631
+ def _get_object(link_name)
632
+ find_link(link_name)&.to
633
+ end
634
+
635
+ def find_link(link_name)
636
+ links.find { |x| x.name == link_name }
637
+ end
638
+
639
+ def link(link_name, obj = nil)
640
+ link = Inform::System::Link.new(name: link_name, to: obj, from: self)
641
+ links << link
642
+ link
643
+ end
644
+
645
+ def linked?(link_name)
646
+ links.count { |x| x.name == link_name } > 0
647
+ end
648
+ alias _key? linked?
649
+
650
+ def unlink(link_name)
651
+ links.delete_at(links.index { |x| x.name == link_name })
652
+ end
653
+ alias _unset_object unlink
654
+
655
+ def linkto(link_name)
656
+ _get_object(link_name)
657
+ end
658
+
659
+ def linksfrom(link_name = nil)
660
+ if link_name.nil?
661
+ links.select { |x| x.to == self }.map(&:from)
662
+ else
663
+ links.select { |x| x.name == link_name && x.to == self }.map(&:from)
664
+ end
665
+ end
666
+
667
+ def init
668
+ self
669
+ end
670
+ alias safe_init init
671
+
672
+ def refresh
673
+ self
674
+ end
675
+ alias safe_refresh refresh
676
+
677
+ def save(*_args)
678
+ self
679
+ end
680
+ alias safe_save save
681
+
682
+ def to_s
683
+ self.name
684
+ end
685
+
686
+ def inspect
687
+ identity
688
+ end
689
+ end
690
+ # rubocop: enable Metrics/ClassLength
691
+ # class Inform::System::Object
692
+
693
+ # The Inform::System::Link class
694
+ class Link
695
+ LinkTemplate = '%<link_name>s -> %<to_name>s [%<to_identity>s]'.freeze
696
+ attr_reader :name, :to, :from
697
+
698
+ def initialize(properties)
699
+ @name = properties[:name]
700
+ @to = properties[:to]
701
+ @from = properties[:from]
702
+ end
703
+
704
+ def to_s
705
+ format(LinkTemplate, link_name: name, to_name: to, to_identity: to.identity)
706
+ end
707
+ end
708
+ end
709
+ # module Inform::System
710
+ end
711
+ # module Inform
712
+
713
+ # The Inform module
714
+ # module Inform
715
+ # # The Inform::SingletonClassMethods module
716
+ # module SingletonClassMethods
717
+ # Registry = Class.new(Hash)
718
+ # SingletonRegistry = Registry.new
719
+
720
+ # def instance(object_name, **attrs, &block)
721
+ # key = object_name.nil? ? attrs.to_s : object_name
722
+ # attrs.merge!(name: object_name) unless object_name.nil?
723
+ # SingletonRegistry[key] ||= fetch_or_create(**attrs, &block)
724
+ # end
725
+
726
+ # # !rubocop: disable Metrics/MethodLength
727
+ # def fetch_or_create(**attrs, &block)
728
+ # db.transaction do
729
+ # entity = where(**attrs).first
730
+ # if entity.nil?
731
+ # entity = create(**attrs)
732
+ # entity.refresh
733
+ # end
734
+ # unless block.nil?
735
+ # block.call(entity)
736
+ # entity.save_changes if entity.changed_columns.any?
737
+ # end
738
+ # entity
739
+ # end
740
+ # end
741
+ # # !rubocop: enable Metrics/MethodLength
742
+ # end
743
+ # end
744
+
745
+ # The Inform module
746
+ # module Inform
747
+ # # The Inform::SingletonObject class
748
+ # class SingletonObject < Inform::Object
749
+ # class << self
750
+ # include Inform::SingletonClassMethods
751
+ # end
752
+ # end
753
+ # end
754
+
755
+ # The ExtendedProperties class
756
+ class ExtendedProperties < Inform::Object
757
+ def init
758
+ has :proper
759
+ super
760
+ end
761
+ end