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,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
|