inform-runtime 1.0.4 → 1.2.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.
- checksums.yaml +4 -4
- data/README.md +10 -12
- data/Rakefile +26 -16
- data/lib/{runtime → story_teller}/articles.rb +14 -10
- data/lib/{runtime → story_teller}/builtins.rb +50 -22
- data/lib/{runtime → story_teller}/color.rb +8 -8
- data/lib/{runtime → story_teller}/command.rb +26 -28
- data/lib/{runtime → story_teller}/context.rb +23 -24
- data/lib/story_teller/core.rb +38 -0
- data/lib/{runtime → story_teller}/daemon.rb +35 -36
- data/lib/story_teller/engine.rb +151 -0
- data/lib/story_teller/ephemeral_adapter.rb +42 -0
- data/lib/{runtime → story_teller}/events.rb +8 -9
- data/lib/{runtime → story_teller}/experimental/handler_dsl.rb +7 -18
- data/lib/story_teller/experimental/reverse_engineer_class.rb +37 -0
- data/lib/{runtime → story_teller}/grammar_parser.rb +24 -40
- data/lib/{runtime → story_teller}/helpers.rb +21 -7
- data/lib/{runtime → story_teller}/history.rb +5 -5
- data/lib/{runtime → story_teller}/inflector.rb +4 -5
- data/lib/story_teller/inform/base.rb +160 -0
- data/lib/{runtime → story_teller/inform/ephemeral}/link.rb +27 -45
- data/lib/{runtime → story_teller/inform/ephemeral}/module.rb +17 -58
- data/lib/story_teller/inform/ephemeral/object.rb +329 -0
- data/lib/{runtime → story_teller/inform/ephemeral}/tag.rb +54 -81
- data/lib/story_teller/inform/models.rb +25 -0
- data/lib/{runtime → story_teller}/io.rb +10 -10
- data/lib/{runtime → story_teller}/kernel.rb +21 -31
- data/lib/story_teller/library/bootstrap.rb +66 -0
- data/lib/story_teller/library/declarations.rb +53 -0
- data/lib/story_teller/library/directives.rb +91 -0
- data/lib/story_teller/library/loader.rb +104 -0
- data/lib/story_teller/library/location.rb +73 -0
- data/lib/{runtime → story_teller}/library.rb +27 -12
- data/lib/{runtime → story_teller}/logging.rb +47 -24
- data/lib/{runtime → story_teller}/mixins.rb +6 -6
- data/lib/story_teller/model_adapter.rb +132 -0
- data/lib/{runtime → story_teller}/plurals.rb +11 -11
- data/lib/{runtime → story_teller}/prototype.rb +11 -10
- data/lib/{runtime → story_teller}/publication.rb +9 -9
- data/lib/{runtime → story_teller}/session.rb +6 -8
- data/lib/{runtime → story_teller}/stdlib.rb +13 -11
- data/lib/{runtime → story_teller}/subscription.rb +8 -8
- data/lib/{runtime → story_teller}/tree.rb +6 -6
- data/lib/{runtime → story_teller}/version.rb +16 -6
- data/lib/story_teller/world_tree.rb +54 -0
- data/lib/story_teller.rb +26 -0
- metadata +59 -99
- data/config/database.yml +0 -37
- data/exe/inform.rb +0 -6
- data/game/config.yml +0 -5
- data/game/example.inf +0 -76
- data/game/example.rb +0 -90
- data/game/forms/example_form.rb +0 -2
- data/game/grammar/game_grammar.inf.rb +0 -11
- data/game/languages/english.rb +0 -2
- data/game/models/example_model.rb +0 -2
- data/game/modules/example_module.rb +0 -9
- data/game/rules/example_state.rb +0 -2
- data/game/scripts/example_script.rb +0 -2
- data/game/topics/example_topic.rb +0 -2
- data/game/verbs/game_verbs.rb +0 -15
- data/game/verbs/metaverbs.rb +0 -2028
- data/lib/runtime/config.rb +0 -48
- data/lib/runtime/database.rb +0 -500
- data/lib/runtime/game.rb +0 -74
- data/lib/runtime/game_loader.rb +0 -132
- data/lib/runtime/library_loader.rb +0 -135
- data/lib/runtime/object.rb +0 -761
- data/lib/runtime/options.rb +0 -104
- data/lib/runtime/persistence.rb +0 -292
- data/lib/runtime/runtime.rb +0 -321
- data/lib/runtime/world_tree.rb +0 -69
- data/lib/runtime.rb +0 -35
data/lib/runtime/options.rb
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
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
|
-
require 'logger'
|
|
22
|
-
require 'optparse'
|
|
23
|
-
|
|
24
|
-
require_relative 'version'
|
|
25
|
-
|
|
26
|
-
# The Inform module
|
|
27
|
-
module Inform
|
|
28
|
-
# The Options module
|
|
29
|
-
module Options
|
|
30
|
-
# The ArgumentsParser
|
|
31
|
-
class ArgumentsParser
|
|
32
|
-
attr_reader :parser, :options
|
|
33
|
-
|
|
34
|
-
def initialize(args, option_parser = OptionParser.new, **params)
|
|
35
|
-
@parser = option_parser
|
|
36
|
-
@options = ::Inform::Runtime.default_config.dup
|
|
37
|
-
flags.each { |method_name| self.method(method_name).call }
|
|
38
|
-
@parser.parse!(args, **params)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def flags
|
|
42
|
-
@flags ||= %i[banner admin log_level help version]
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def banner
|
|
46
|
-
@parser.banner = "Usage: #{File.basename($PROGRAM_NAME)} [game_path] [options]"
|
|
47
|
-
@parser.separator ''
|
|
48
|
-
@parser.separator 'Arguments:'
|
|
49
|
-
@parser.separator ' game_path Path to game file or directory'
|
|
50
|
-
@parser.separator ''
|
|
51
|
-
@parser.separator 'Options:'
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def admin
|
|
55
|
-
@parser.on_tail('--admin', 'Set player character as admin; default: false') do
|
|
56
|
-
@options[:admin] = true
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def log_level
|
|
61
|
-
@parser.on_tail('-v', '--verbose', 'Increase verbosity') do
|
|
62
|
-
@options[:log_level] ||= Logger::INFO
|
|
63
|
-
@options[:log_level] -= 1
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def help
|
|
68
|
-
@parser.on_tail('-?', '--help', 'Show this message') do
|
|
69
|
-
puts @parser
|
|
70
|
-
exit
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def version
|
|
75
|
-
@parser.on_tail('--version', 'Show version') do
|
|
76
|
-
puts "#{$PROGRAM_NAME} version #{Inform::VERSION}"
|
|
77
|
-
exit
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
# class ArgumentsParser
|
|
82
|
-
|
|
83
|
-
def demand(options, arg, positional = false)
|
|
84
|
-
return options[arg] unless options[arg].nil?
|
|
85
|
-
required_arg = positional ? "<#{arg}>" : "--#{arg.to_s.gsub(/_/, '-')}"
|
|
86
|
-
raise UserError, "Required argument: #{required_arg}"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def parse_arguments(args = ARGV, _file_path = ARGF)
|
|
90
|
-
arguments_parser = ArgumentsParser.new(args)
|
|
91
|
-
demand(arguments_parser.options, :game_path)
|
|
92
|
-
arguments_parser.options
|
|
93
|
-
rescue OptionParser::InvalidArgument, OptionParser::InvalidOption,
|
|
94
|
-
OptionParser::MissingArgument, OptionParser::NeedlessArgument => e
|
|
95
|
-
puts e.message
|
|
96
|
-
puts arguments_parser.parser
|
|
97
|
-
exit
|
|
98
|
-
rescue OptionParser::AmbiguousOption => e
|
|
99
|
-
abort e.message
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
# module Options
|
|
103
|
-
end
|
|
104
|
-
# module Inform
|
data/lib/runtime/persistence.rb
DELETED
|
@@ -1,292 +0,0 @@
|
|
|
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
|
-
require 'etc'
|
|
22
|
-
require 'logger'
|
|
23
|
-
require 'uri'
|
|
24
|
-
require 'yaml'
|
|
25
|
-
|
|
26
|
-
require 'sequel'
|
|
27
|
-
|
|
28
|
-
require_relative 'database'
|
|
29
|
-
|
|
30
|
-
Sequel.extension :migration
|
|
31
|
-
Sequel.extension :connection_validator
|
|
32
|
-
|
|
33
|
-
# Re-open the Inform module to define a helper method
|
|
34
|
-
module Inform
|
|
35
|
-
def self.initialize_persistence_layer
|
|
36
|
-
log.debug "Inform.initialize_persistence_layer"
|
|
37
|
-
Inform::Persistence.init
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Simplistic in-memory cache
|
|
42
|
-
class EphemeralCache < Hash
|
|
43
|
-
def get(key)
|
|
44
|
-
self[key]
|
|
45
|
-
end
|
|
46
|
-
def set(key, value, _ttl)
|
|
47
|
-
self[key] = value
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# The Persistence class
|
|
52
|
-
class Persistence
|
|
53
|
-
unless defined?(Persistence::GlobalCache)
|
|
54
|
-
GlobalCache = defined?(Java) ? java.util.concurrent.ConcurrentHashMap.new : EphemeralCache.new
|
|
55
|
-
end
|
|
56
|
-
Plugins = {
|
|
57
|
-
after_initialize: [],
|
|
58
|
-
caching: [Persistence::GlobalCache],
|
|
59
|
-
json_serializer: [],
|
|
60
|
-
xml_serializer: []
|
|
61
|
-
# tactical_eager_loading: [],
|
|
62
|
-
# eager_each: []
|
|
63
|
-
}.freeze
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# The SequelLoggers module
|
|
67
|
-
module SequelLoggers
|
|
68
|
-
DatabaseLogging = Struct.new(:memo).new({})
|
|
69
|
-
|
|
70
|
-
def add_logger(database, logger)
|
|
71
|
-
database.loggers << logger unless database.loggers.include?(logger)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def delete_logger(database, logger)
|
|
75
|
-
database.loggers.delete(logger) if database.loggers.include?(logger)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def set_log_level(database, level)
|
|
79
|
-
DatabaseLogging.memo[:preserved_log_levels][database] = database.sql_log_level
|
|
80
|
-
database.sql_log_level = level unless level.nil?
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def enable_query_logging
|
|
84
|
-
DatabaseLogging.memo[:logger] ||= Logger.new($stdout)
|
|
85
|
-
DatabaseLogging.memo[:preserved_log_levels] ||= {}
|
|
86
|
-
logger = DatabaseLogging.memo[:logger]
|
|
87
|
-
Sequel::DATABASES.each do |database|
|
|
88
|
-
add_logger(database, logger)
|
|
89
|
-
set_log_level(database, :debug)
|
|
90
|
-
logger.debug "Enabled query logging for #{database}"
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def disable_query_logging
|
|
95
|
-
logger = DatabaseLogging.memo[:logger]
|
|
96
|
-
Sequel::DATABASES.each do |database|
|
|
97
|
-
logger.debug "Disabling query logging for #{database}"
|
|
98
|
-
delete_logger(database, logger)
|
|
99
|
-
set_log_level(database, DatabaseLogging.memo[:preserved_log_levels][database])
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
# module SequelLoggers
|
|
104
|
-
|
|
105
|
-
# The SequelPlugins module
|
|
106
|
-
module SequelPlugins
|
|
107
|
-
def enable_plugins
|
|
108
|
-
Persistence::Plugins.each { |plugin, parameters| enable_plugin(plugin, parameters) }
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def enable_plugin(plugin, parameters = [])
|
|
112
|
-
return Sequel::Model.plugin(plugin) if parameters.empty?
|
|
113
|
-
Sequel::Model.plugin(plugin, *parameters)
|
|
114
|
-
rescue LoadError => e
|
|
115
|
-
log.error e.message
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
# module SequelPlugins
|
|
119
|
-
|
|
120
|
-
# The Inform module
|
|
121
|
-
module Inform
|
|
122
|
-
# The Persistence class
|
|
123
|
-
class Persistence
|
|
124
|
-
include DatabaseConnectionHelpers
|
|
125
|
-
include SequelLoggers
|
|
126
|
-
include SequelPlugins
|
|
127
|
-
|
|
128
|
-
attr_reader :config, :environment
|
|
129
|
-
|
|
130
|
-
def self.init(env = nil)
|
|
131
|
-
Inform::Persistence.instance(env)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
@instance_mutex = Mutex.new
|
|
135
|
-
|
|
136
|
-
def self.instance(*args)
|
|
137
|
-
return @instance unless @instance.nil?
|
|
138
|
-
@instance_mutex.synchronize do
|
|
139
|
-
@instance ||= new(*args)
|
|
140
|
-
end
|
|
141
|
-
@instance
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
private_class_method :new
|
|
145
|
-
|
|
146
|
-
def initialize(env = nil)
|
|
147
|
-
@environment = env || Inform::Game.environment
|
|
148
|
-
log.debug "Initializing persistence layer for environment: #{@environment}"
|
|
149
|
-
caller[0..4].each { |t| log.debug t }
|
|
150
|
-
@config = database_config.fetch(@environment.to_s, {})
|
|
151
|
-
establish_database_connection
|
|
152
|
-
enable_plugins
|
|
153
|
-
enable_query_logging if ENV['ENABLE_SQL_LOGGING']
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def establish_database_connection
|
|
157
|
-
connect
|
|
158
|
-
rescue Sequel::DatabaseConnectionError
|
|
159
|
-
log.warn "Database requires initialization"
|
|
160
|
-
init_database
|
|
161
|
-
retry if ConnectionAttempts.memo < 2
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def database_config
|
|
165
|
-
@database_config ||= YAML.load_file(database_config_file_path)
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def database_config_file_path
|
|
169
|
-
@database_config_file_path ||= File.expand_path(
|
|
170
|
-
File.join(Inform::Runtime.project_dir_path, 'config', 'database.yml')
|
|
171
|
-
)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def init_database
|
|
175
|
-
@config = database_config.fetch('default')
|
|
176
|
-
connect
|
|
177
|
-
@database = Inform::Database.instance(Inform::Game.config[:database_name])
|
|
178
|
-
@database.bootstrap.up
|
|
179
|
-
@config = database_config.fetch(environment.to_s)
|
|
180
|
-
connect
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
# class Persistence
|
|
184
|
-
end
|
|
185
|
-
# module Inform
|
|
186
|
-
|
|
187
|
-
# The Inform module
|
|
188
|
-
module Inform
|
|
189
|
-
# The ImplicitMigration module
|
|
190
|
-
module ImplicitMigration
|
|
191
|
-
NoDatabasePattern = %r{No database associated with Sequel::Model}.freeze
|
|
192
|
-
MigrationSetupTemplate = '%<model>sSetup'.freeze
|
|
193
|
-
ModuleNamespaceDelimiterPattern = /::/.freeze
|
|
194
|
-
|
|
195
|
-
def before_inherited(subclass)
|
|
196
|
-
return if self != Sequel::Model
|
|
197
|
-
descendants << subclass
|
|
198
|
-
log.debug "#{subclass} << #{self} [#{descendants}]"
|
|
199
|
-
maybe_migrate(subclass)
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def after_inherited(_subclass)
|
|
203
|
-
examine_schema
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# rubocop: disable Metrics/AbcSize
|
|
207
|
-
# rubocop: disable Metrics/MethodLength
|
|
208
|
-
def examine_schema
|
|
209
|
-
descendants.each do |model_class|
|
|
210
|
-
table_name = model_class.table_name
|
|
211
|
-
indexes = self.db.indexes(table_name)
|
|
212
|
-
columns = model_class.columns
|
|
213
|
-
associations = model_class.associations
|
|
214
|
-
|
|
215
|
-
log.debug "Table: #{table_name}"
|
|
216
|
-
log.debug "Columns: #{columns.join(', ')}"
|
|
217
|
-
log.debug "Indexes: #{indexes}"
|
|
218
|
-
|
|
219
|
-
associations.each do |assoc_name, assoc_data|
|
|
220
|
-
log.debug "Association: #{assoc_name} (#{assoc_data[:type]}) to #{assoc_data[:class_name]}"
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
log.debug "==========="
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
# rubocop: enable Metrics/AbcSize
|
|
227
|
-
# rubocop: enable Metrics/MethodLength
|
|
228
|
-
|
|
229
|
-
def maybe_migrate(subclass)
|
|
230
|
-
migration_class = migration(subclass)
|
|
231
|
-
log.debug "Found migration class: #{migration_class}"
|
|
232
|
-
migration_class&.up
|
|
233
|
-
rescue Sequel::Error => e
|
|
234
|
-
if NoDatabasePattern.match?(e.message)
|
|
235
|
-
Inform::Persistence.instance.connect
|
|
236
|
-
retry
|
|
237
|
-
end
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
def migration(model)
|
|
241
|
-
names = format(MigrationSetupTemplate, model: model).split(ModuleNamespaceDelimiterPattern)
|
|
242
|
-
names.inject(Object) do |mod, class_name|
|
|
243
|
-
mod.const_get(class_name)
|
|
244
|
-
rescue StandardError => e
|
|
245
|
-
log.warn "Error getting reference to migration model class: #{e.message}"
|
|
246
|
-
next
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
# module ImplicitMigration
|
|
251
|
-
end
|
|
252
|
-
# module Inform
|
|
253
|
-
|
|
254
|
-
# The Inform module
|
|
255
|
-
module Inform
|
|
256
|
-
# The InheritanceListener module
|
|
257
|
-
module InheritanceListener
|
|
258
|
-
# The ClassMethods module
|
|
259
|
-
module ClassMethods
|
|
260
|
-
include Inform::ImplicitMigration
|
|
261
|
-
|
|
262
|
-
def inherited(subclass)
|
|
263
|
-
before_inherited(subclass)
|
|
264
|
-
super
|
|
265
|
-
after_inherited(subclass)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
# Returns the list of Model descendants.
|
|
269
|
-
def descendants
|
|
270
|
-
@descendants ||= []
|
|
271
|
-
end
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
def self.included(base)
|
|
275
|
-
base.extend(ClassMethods)
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
# module InheritanceListener
|
|
279
|
-
end
|
|
280
|
-
# module Inform
|
|
281
|
-
|
|
282
|
-
# The Sequel module
|
|
283
|
-
module Sequel
|
|
284
|
-
# The Sequel::Model class
|
|
285
|
-
class Model
|
|
286
|
-
include Inform::InheritanceListener
|
|
287
|
-
|
|
288
|
-
def self.implicit_table_name
|
|
289
|
-
underscore(demodulize(name)).to_sym
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
end
|
data/lib/runtime/runtime.rb
DELETED
|
@@ -1,321 +0,0 @@
|
|
|
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
|
-
require_relative 'config'
|
|
22
|
-
require_relative 'game'
|
|
23
|
-
require_relative 'session'
|
|
24
|
-
require_relative 'io'
|
|
25
|
-
require_relative 'context'
|
|
26
|
-
require_relative 'library'
|
|
27
|
-
require_relative 'options'
|
|
28
|
-
require_relative 'publication'
|
|
29
|
-
|
|
30
|
-
# The Inform module
|
|
31
|
-
module Inform
|
|
32
|
-
# The RuntimeConstants module
|
|
33
|
-
module RuntimeConstants
|
|
34
|
-
UndefinedMainPattern = %r{undefined method 'Main' for an instance of Object}.freeze
|
|
35
|
-
Registry = Class.new(Hash)
|
|
36
|
-
LibraryRegistry = Registry.new
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# The RuntimeLibrary module
|
|
40
|
-
module RuntimeLibrary
|
|
41
|
-
def play
|
|
42
|
-
log.debug "Using runtime-managed InformLibrary play method override"
|
|
43
|
-
inform_library = Inform::Runtime.libraries[Inform::Runtime]
|
|
44
|
-
inform_library.play
|
|
45
|
-
inform_library
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# The RuntimeClassMethods module
|
|
50
|
-
module RuntimeClassMethods
|
|
51
|
-
def libraries
|
|
52
|
-
Inform::Runtime::LibraryRegistry
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def default_config
|
|
56
|
-
@default_config ||= Inform::Config::DEFAULTS
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def default_game_dir_path
|
|
60
|
-
File.join(project_dir_path, Inform::Runtime.default_config[:game_dir_name])
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def support_dir_path
|
|
64
|
-
Inform::SUPPORT_DIR_PATH
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def inform_dir_path
|
|
68
|
-
Inform::INFORM_DIR_PATH
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def lib_dir_path
|
|
72
|
-
Inform::LIB_DIR_PATH
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def project_dir_path
|
|
76
|
-
Inform::PROJECT_DIR_PATH
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def database_saves_dir_path
|
|
80
|
-
File.join(Inform::Runtime.project_dir_path, '.' + Inform::Game.config[:database_name] + '_saves')
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def default_environment
|
|
84
|
-
Inform::Runtime.default_config[:environment]
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def invocation_properties
|
|
88
|
-
return Inform::Runtime.instance.invocation_properties unless Inform::Runtime.instance.nil?
|
|
89
|
-
Inform::Runtime.default_config.fetch(:properties, '').split.map(&:to_sym)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def invocation_context
|
|
93
|
-
return Inform::Runtime.instance.invocation_context unless Inform::Runtime.instance.nil?
|
|
94
|
-
Struct.new(*Inform::Runtime.invocation_properties)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def language_name
|
|
98
|
-
Inform::Runtime.default_config[:language]
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def main_gem_spec_executable
|
|
102
|
-
require 'rubygems'
|
|
103
|
-
current_working_dir = File.expand_path(__dir__)
|
|
104
|
-
gem_spec = Gem.loaded_specs.values.find do |s|
|
|
105
|
-
current_working_dir.start_with?(s.full_gem_path)
|
|
106
|
-
end
|
|
107
|
-
(gem_spec&.executables || []).first
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def player?(obj)
|
|
111
|
-
return false unless defined?(Inform::IO::Session) && Inform::IO::Session.respond_to?(:players)
|
|
112
|
-
Inform::IO::Session.players.include?(obj)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def init(options = Inform::Runtime.default_config)
|
|
116
|
-
Inform::Runtime.instance(options)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def instance(options = Inform::Runtime.default_config)
|
|
120
|
-
return @instance unless @instance.nil?
|
|
121
|
-
@instance_mutex.synchronize do
|
|
122
|
-
@instance ||= new(options)
|
|
123
|
-
end
|
|
124
|
-
@instance
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
# module RuntimeClassMethods
|
|
128
|
-
|
|
129
|
-
# The RuntimeInstanceMethods module
|
|
130
|
-
module RuntimeInstanceMethods
|
|
131
|
-
include RuntimeConstants
|
|
132
|
-
|
|
133
|
-
attr_accessor :main_object
|
|
134
|
-
|
|
135
|
-
def config_file_path
|
|
136
|
-
@config_file_path ||= File.join(game_path, @options[:game_config_file_name])
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def game_dir_name
|
|
140
|
-
@game_dir_name ||= @options[:game_dir_name]
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def game_path
|
|
144
|
-
@game_path ||= File.expand_path(@options[:game_path])
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def game_components
|
|
148
|
-
@game_components ||= @options.fetch(:game_components, '').split.map(&:to_sym)
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def grammar_module_path
|
|
152
|
-
@grammar_module_path ||= File.join(game_path, @options[:game_grammar_module_name])
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def invocation_properties
|
|
156
|
-
@invocation_properties ||= @options.fetch(:properties, '').split.map(&:to_sym)
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def invocation_context
|
|
160
|
-
@invocation_context ||= Struct.new(*invocation_properties)
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def invoke_main_method
|
|
164
|
-
::Object.new.send(:Main)
|
|
165
|
-
rescue NameError => e
|
|
166
|
-
if UndefinedMainPattern.match?(e.message)
|
|
167
|
-
log.error "Main() definition is missing"
|
|
168
|
-
else
|
|
169
|
-
log.error e.message
|
|
170
|
-
e.backtrace.each { |t| log.error t } # TODO: Remove
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# TODO: Ensuring the location of the player object requires
|
|
175
|
-
# that this method be invoked before the move @player, @location
|
|
176
|
-
# operation in the InformLibrary#play method. This means that
|
|
177
|
-
# ensuring the location must take place either in the game-
|
|
178
|
-
# defined Initialise() method, or else it can happen in a
|
|
179
|
-
# provided LibraryExtension method, but in that case it would
|
|
180
|
-
# be at risk of being overridden by a game-defined Initialise()
|
|
181
|
-
# method.
|
|
182
|
-
# TODO: Try to figure out what the best approach here is. I
|
|
183
|
-
# think that if a multi-player game is to supply an entrypoint
|
|
184
|
-
# module, it will have to handle the location ensurance itself.
|
|
185
|
-
def ensure_location
|
|
186
|
-
return if inform_library.nil?
|
|
187
|
-
return if inform_library.location
|
|
188
|
-
player_obj = inform_library.player
|
|
189
|
-
location = player_obj.location
|
|
190
|
-
location ||= player_obj.spawn_point if player_obj.respond_to?(:spawn_point)
|
|
191
|
-
inform_library.location = location
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def inform_library(key = Inform::Runtime)
|
|
195
|
-
Inform::Runtime.libraries[key]
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def manage_inform_library(key = Inform::Runtime)
|
|
199
|
-
Inform::Runtime.libraries[key] ||= begin
|
|
200
|
-
inform_library = InformLibrary.new
|
|
201
|
-
if self.respond_to?(:specialized_player)
|
|
202
|
-
inform_library.selfobj = specialized_player
|
|
203
|
-
end
|
|
204
|
-
inform_library.subscribe(inform_library.selfobj)
|
|
205
|
-
inform_library
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
def apply_preferences
|
|
210
|
-
return if inform_library.nil?
|
|
211
|
-
player_obj = inform_library.player
|
|
212
|
-
preferences = player_obj.&:preferences
|
|
213
|
-
return if preferences.nil? || !preferences.respond_to?(:properties)
|
|
214
|
-
preferences.properties.each_pair do |key, value|
|
|
215
|
-
variable = :"@#{key}"
|
|
216
|
-
Parser.initial_state.delete(variable)
|
|
217
|
-
inform_library.instance_variable_set(variable, value)
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def manage_privileges
|
|
222
|
-
Inform::Object.include(Inform::Privileges)
|
|
223
|
-
return unless @options[:admin]
|
|
224
|
-
give inform_library.player, :admin
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
def read_eval_print_loop
|
|
228
|
-
loop do
|
|
229
|
-
prompt
|
|
230
|
-
inform_library.send(inform_library.inform(read))
|
|
231
|
-
end
|
|
232
|
-
rescue Interrupt => e
|
|
233
|
-
$stdout.puts "\n#{e.class.name}"
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def play_game
|
|
237
|
-
log.debug "#{self}#play_game"
|
|
238
|
-
invoke_main_method
|
|
239
|
-
# ensure_location
|
|
240
|
-
apply_preferences
|
|
241
|
-
manage_privileges
|
|
242
|
-
read_eval_print_loop
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
def quit
|
|
246
|
-
$stdout.print "[Hit enter to exit.]"
|
|
247
|
-
$stdin.getc
|
|
248
|
-
# Curses.getch
|
|
249
|
-
# Curses.close_screen
|
|
250
|
-
exit
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def to_s
|
|
254
|
-
"#<#{self.class.name}:#{object_id}>"
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def inspect
|
|
258
|
-
to_s
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
# module RuntimeInstanceMethods
|
|
262
|
-
|
|
263
|
-
# The Inform::Privileges module to implement privilege
|
|
264
|
-
module Privileges
|
|
265
|
-
def admin?
|
|
266
|
-
self.has?(:admin)
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
def builder?
|
|
270
|
-
admin? || self.has?(:builder)
|
|
271
|
-
end
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
# The Runtime class loads a game and plays it in a REPL session.
|
|
275
|
-
class Runtime
|
|
276
|
-
include Inform::RuntimeConstants
|
|
277
|
-
include Inform::RuntimeInstanceMethods
|
|
278
|
-
include Inform::Game::Loader
|
|
279
|
-
include Inform::Library::Loader
|
|
280
|
-
include Inform::IO
|
|
281
|
-
include Inform::Publisher
|
|
282
|
-
|
|
283
|
-
class << self
|
|
284
|
-
include Inform::RuntimeClassMethods
|
|
285
|
-
include Inform::Library::ClassMethods
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
@default_config = nil
|
|
289
|
-
@instance = nil
|
|
290
|
-
@instance_mutex = Mutex.new
|
|
291
|
-
|
|
292
|
-
private_class_method :new
|
|
293
|
-
|
|
294
|
-
def initialize(options = Inform::Runtime.default_config)
|
|
295
|
-
@options = options
|
|
296
|
-
Inform::Game.init(@options)
|
|
297
|
-
Inform::Context.set(invocation_context)
|
|
298
|
-
Inform.initialize_persistence_layer
|
|
299
|
-
end
|
|
300
|
-
end
|
|
301
|
-
# class Runtime
|
|
302
|
-
|
|
303
|
-
# The App module
|
|
304
|
-
module App
|
|
305
|
-
include Inform::Options
|
|
306
|
-
|
|
307
|
-
def main(args = parse_arguments)
|
|
308
|
-
Logging.log_level = args[:log_level]
|
|
309
|
-
runtime = Inform::Runtime.init(args)
|
|
310
|
-
runtime.load_game
|
|
311
|
-
runtime.play_game
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
end
|
|
315
|
-
# module Inform
|
|
316
|
-
|
|
317
|
-
if defined?(ZCODE_ONLY)
|
|
318
|
-
# Simulate compiling with Z-code only compiler
|
|
319
|
-
WORDSIZE = (2**((0.size * 8) - 2) - 1)
|
|
320
|
-
end
|
|
321
|
-
# defined?(ZCODE_ONLY)
|