inform-runtime 1.2.2 → 1.3.1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -41
  3. data/lib/story_teller/articles.rb +2 -1
  4. data/lib/story_teller/builtins.rb +63 -10
  5. data/lib/story_teller/color.rb +2 -1
  6. data/lib/story_teller/command.rb +4 -1
  7. data/lib/story_teller/context.rb +23 -27
  8. data/lib/story_teller/daemon.rb +2 -2
  9. data/lib/story_teller/engine.rb +80 -16
  10. data/lib/story_teller/events.rb +9 -2
  11. data/lib/story_teller/experimental/handler_dsl.rb +1 -16
  12. data/lib/story_teller/experimental/reverse_engineer_class.rb +1 -1
  13. data/lib/story_teller/grammar_parser.rb +60 -13
  14. data/lib/story_teller/helpers.rb +8 -18
  15. data/lib/story_teller/history.rb +2 -1
  16. data/lib/story_teller/inflector.rb +4 -2
  17. data/lib/story_teller/inform/ephemeral/link.rb +63 -31
  18. data/lib/story_teller/inform/ephemeral/module.rb +6 -5
  19. data/lib/story_teller/inform/ephemeral/object.rb +223 -236
  20. data/lib/story_teller/inform/ephemeral/tag.rb +27 -14
  21. data/lib/story_teller/io.rb +99 -49
  22. data/lib/story_teller/kernel.rb +13 -5
  23. data/lib/story_teller/library/bootstrap.rb +10 -27
  24. data/lib/story_teller/library/declarations.rb +1 -1
  25. data/lib/story_teller/library/directives.rb +1 -1
  26. data/lib/story_teller/library/loader.rb +4 -20
  27. data/lib/story_teller/library/location.rb +9 -3
  28. data/lib/story_teller/library.rb +1 -1
  29. data/lib/story_teller/logging.rb +165 -67
  30. data/lib/story_teller/mixins.rb +11 -4
  31. data/lib/story_teller/plurals.rb +2 -1
  32. data/lib/story_teller/privileges.rb +117 -0
  33. data/lib/story_teller/prototype.rb +158 -32
  34. data/lib/story_teller/publication.rb +2 -1
  35. data/lib/story_teller/session.rb +115 -25
  36. data/lib/story_teller/stdlib.rb +232 -2
  37. data/lib/story_teller/subscription.rb +2 -1
  38. data/lib/story_teller/tree.rb +2 -1
  39. data/lib/story_teller/version.rb +2 -2
  40. data/lib/story_teller/world_tree.rb +21 -23
  41. data/lib/story_teller.rb +18 -5
  42. metadata +7 -10
  43. data/lib/story_teller/core.rb +0 -38
  44. data/lib/story_teller/ephemeral_adapter.rb +0 -40
  45. data/lib/story_teller/inform/base.rb +0 -160
  46. data/lib/story_teller/model_adapter.rb +0 -132
@@ -1,7 +1,8 @@
1
+ # lib/story_teller/grammar_parser.rb
1
2
  # encoding: utf-8
2
3
  # frozen_string_literal: false
3
4
 
4
- # Copyright Nels Nelson 2008-2025 but freely usable (see license)
5
+ # Copyright Nels Nelson 2008-2026 but freely usable (see license)
5
6
  #
6
7
  # This file is part of StoryTeller.
7
8
  #
@@ -90,7 +91,12 @@ module Inform
90
91
  EmoteString = 'emote'.freeze
91
92
  Newline = "\n".freeze
92
93
 
94
+ # rubocop: disable Metrics/AbcSize
95
+ # rubocop: disable Metrics/MethodLength
93
96
  def to_s
97
+ grammar_lines = grammars.map(&:to_s)
98
+ grammar_lines[-1] = "#{grammar_lines[-1]};" if grammar_lines.any?
99
+
94
100
  VerbString + SpaceString +
95
101
  if meta?
96
102
  MetaString + SpaceString
@@ -100,8 +106,10 @@ module Inform
100
106
  ''
101
107
  end +
102
108
  map { |verb| "'#{verb}'" }.join(SpaceString) + Newline +
103
- grammars.join(Newline)
109
+ grammar_lines.join(Newline)
104
110
  end
111
+ # rubocop: enable Metrics/AbcSize
112
+ # rubocop: enable Metrics/MethodLength
105
113
  alias to_str to_s
106
114
 
107
115
  def hash
@@ -115,15 +123,22 @@ module Inform
115
123
  CustomScopePattern = %r{^(.*)=(.*)$}.freeze
116
124
  attr_reader :source, :action, :expected_tokens
117
125
 
126
+ # rubocop: disable Metrics/MethodLength
118
127
  def initialize(grammar, source, action, reverse = nil)
119
128
  super()
120
- concat(symbolize_grammar(grammar)) unless grammar.empty?
129
+ unless grammar.empty?
130
+ symbolized_grammar = symbolize_grammar(grammar)
131
+ concat(symbolized_grammar) unless symbolized_grammar.empty?
132
+ end
121
133
  push(:end)
122
134
  @action = action
123
- @expected_tokens = grammar.map { |a| CustomScopePattern.match?(a) ? a.to_s.split('=').first.to_sym : a }
135
+ @expected_tokens = symbolized_grammar&.map do |a|
136
+ CustomScopePattern.match?(a) ? a.to_s.split('=').first.to_sym : a
137
+ end
124
138
  @reverse = reverse.nil? ? false : reverse
125
139
  @source = source
126
140
  end
141
+ # rubocop: enable Metrics/MethodLength
127
142
 
128
143
  def empty?
129
144
  length == (include?(:end) ? 1 : 0)
@@ -261,35 +276,64 @@ module Inform
261
276
  BuilderPattern = %r{builder}.freeze
262
277
  Profanity = %w[shit damn fuck sod].freeze
263
278
  ValidModes = %i[admin builder player].freeze
264
- ModePatterns = ValidModes.each_with_object({}) { |key, memo| memo[key] = Regexp.new(key.to_s) }
279
+ ModePatterns = ValidModes.each_with_object({}) do |key, memo|
280
+ memo[key] = Regexp.new(key.to_s)
281
+ end
282
+
283
+ # TODO: Remove (2026-04-30) after confirming replacement below
284
+ # works better.
285
+ # def lookup(key, actor = nil, grammar_index = self)
286
+ # return nil if key.nil? || !grammar_index.include?(key)
287
+ # verb = grammar_index[key].dup
288
+ # return verb if actor.nil?
289
+ # return nil if guarded?(verb, actor)
290
+ # filter_by_permission(verb, actor)
291
+ # verb
292
+ # end
265
293
 
266
294
  def lookup(key, actor = nil, grammar_index = self)
267
295
  return nil if key.nil? || !grammar_index.include?(key)
296
+
268
297
  verb = grammar_index[key].dup
269
298
  return verb if actor.nil?
270
- return nil if guarded?(verb, actor)
299
+
271
300
  filter_by_permission(verb, actor)
301
+ return nil if verb.grammars.empty?
302
+
272
303
  verb
273
304
  end
274
305
 
275
306
  def all(actor = nil, grammar_index = self)
276
307
  return grammar_index.map(&:keys).flatten.sort if actor.nil?
277
308
  mode = role(actor)
278
- init_all_verbs_by_mode(mode, grammar_index, actor) unless Inform::Grammar.partitions.include?(mode)
309
+ unless Inform::Grammar.partitions.include?(mode)
310
+ init_all_verbs_by_mode(mode, grammar_index, actor)
311
+ end
279
312
  Inform::Grammar.partitions[mode]
280
313
  end
281
314
 
282
315
  def by_mode(mode = nil, grammar_index = self)
283
316
  mode = validate(mode)
284
- init_verbs_filtered_by_mode(mode, grammar_index) unless Inform::Grammar.selections.include?(mode)
317
+ unless Inform::Grammar.selections.include?(mode)
318
+ init_verbs_filtered_by_mode(mode, grammar_index)
319
+ end
285
320
  Inform::Grammar.selections[mode]
286
321
  end
287
322
 
288
323
  private
289
324
 
325
+ # TODO: Remove (2026-04-30) after confirming replacement below
326
+ # works better.
327
+ # def init_all_verbs_by_mode(mode, grammar_index, actor, set = verbs_set_instance)
328
+ # verbs = grammar_index.each_with_object(set) do |(key, verb), memo|
329
+ # memo.add(key) unless Profanity.include?(key) || guarded?(verb, actor)
330
+ # end
331
+ # Inform::Grammar.partitions[mode] = verbs.to_a
332
+ # end
333
+
290
334
  def init_all_verbs_by_mode(mode, grammar_index, actor, set = verbs_set_instance)
291
335
  verbs = grammar_index.each_with_object(set) do |(key, verb), memo|
292
- memo.add(key) unless Profanity.include?(key) || guarded?(verb, actor)
336
+ memo.add(key) unless Profanity.include?(key) || !visible_to?(verb, actor)
293
337
  end
294
338
  Inform::Grammar.partitions[mode] = verbs.to_a
295
339
  end
@@ -312,13 +356,16 @@ module Inform
312
356
  end
313
357
 
314
358
  def guarded?(resource, obj)
315
- (AdminPattern.match?(resource.source) && !obj.admin?) ||
316
- (BuilderPattern.match?(resource.source) && !obj.builder?)
359
+ (AdminPattern.match?(resource.source) &&
360
+ !StoryTeller::Privileges.privileged?(obj, :admin)) ||
361
+ (BuilderPattern.match?(resource.source) &&
362
+ !StoryTeller::Privileges.privileged?(obj, :builder))
317
363
  end
318
364
 
319
365
  def role(obj)
320
- return :builder if obj.builder?
321
- return :admin if obj.admin?
366
+ return :admin if StoryTeller::Privileges.privileged?(obj, :admin)
367
+ return :builder if StoryTeller::Privileges.privileged?(obj, :builder)
368
+
322
369
  :player
323
370
  end
324
371
 
@@ -1,7 +1,8 @@
1
+ # lib/story_teller/helpers.rb
1
2
  # encoding: utf-8
2
3
  # frozen_string_literal: false
3
4
 
4
- # Copyright Nels Nelson 2008-2025 but freely usable (see license)
5
+ # Copyright Nels Nelson 2008-2026 but freely usable (see license)
5
6
  #
6
7
  # This file is part of StoryTeller.
7
8
  #
@@ -138,30 +139,19 @@ end
138
139
  module Inform
139
140
  # The ObjectHelpers module functionality
140
141
  module ObjectHelpers
142
+ # rubocop: disable Style/CaseEquality
141
143
  def object?(obj = self)
142
- Inform::Object === obj # rubocop: disable Style/CaseEquality
144
+ defined?(Inform::Ephemeral::Object) && Inform::Ephemeral::Object === obj ||
145
+ defined?(Inform::Object) && Inform::Object === obj
143
146
  end
147
+ # rubocop: enable Style/CaseEquality
144
148
 
145
- def sysobj?(obj = self)
149
+ def ephemeral?(obj = self)
146
150
  defined?(Inform::Ephemeral::Object) && obj.is_a?(Inform::Ephemeral::Object)
147
151
  end
148
152
  end
149
153
  end
150
154
 
151
- # # The Inform module
152
- # module Inform
153
- # # The ObjectHelpers module functionality
154
- # module ObjectHelpers
155
- # def object?(obj = self)
156
- # obj.is_a?(Inform::Object) || obj.is_a?(Inform::Ephemeral::Object)
157
- # end
158
-
159
- # def sysobj?(obj = self)
160
- # obj.is_a?(Inform::Ephemeral::Object)
161
- # end
162
- # end
163
- # end
164
-
165
155
  # The Inform module
166
156
  module Inform
167
157
  # The NilClassObjectHelpers module functionality
@@ -170,7 +160,7 @@ module Inform
170
160
  false
171
161
  end
172
162
 
173
- def sysobj?
163
+ def ephemeral?
174
164
  false
175
165
  end
176
166
 
@@ -1,7 +1,8 @@
1
+ # lib/story_teller/history.rb
1
2
  # encoding: utf-8
2
3
  # frozen_string_literal: false
3
4
 
4
- # Copyright Nels Nelson 2008-2025 but freely usable (see license)
5
+ # Copyright Nels Nelson 2008-2026 but freely usable (see license)
5
6
  #
6
7
  # This file is part of StoryTeller.
7
8
  #
@@ -1,7 +1,8 @@
1
+ # lib/story_teller/inflector.rb
1
2
  # encoding: utf-8
2
3
  # frozen_string_literal: false
3
4
 
4
- # Copyright (c) 2004-2014 David Heinemeier Hansson
5
+ # Copyright (c) David Heinemeier Hansson
5
6
 
6
7
  # Permission is hereby granted, free of charge, to any person obtaining
7
8
  # a copy of this software and associated documentation files (the
@@ -31,6 +32,8 @@
31
32
  # The StoryTeller module
32
33
  module StoryTeller
33
34
  # The Inflector module
35
+ # Mostly adapted from:
36
+ # https://github.com/rails/rails/blob/main/activesupport/lib/active_support/inflections.rb
34
37
  module Inflector
35
38
  # The Inflections class
36
39
  class Inflections
@@ -181,7 +184,6 @@ module StoryTeller
181
184
  inflect.irregular('child', 'children')
182
185
  inflect.irregular('man', 'men')
183
186
  inflect.irregular('move', 'moves')
184
- inflect.irregular('glove', 'gloves')
185
187
  inflect.irregular('person', 'people')
186
188
  inflect.irregular('sex', 'sexes')
187
189
  inflect.irregular('zombie', 'zombies')
@@ -2,7 +2,7 @@
2
2
  # encoding: utf-8
3
3
  # frozen_string_literal: false
4
4
 
5
- # Copyright Nels Nelson 2008-2025 but freely usable (see license)
5
+ # Copyright Nels Nelson 2008-2026 but freely usable (see license)
6
6
  #
7
7
  # This file is part of the StoryTeller.
8
8
  #
@@ -21,17 +21,18 @@
21
21
 
22
22
  # Link
23
23
 
24
- # The Inform module
24
+ # module Inform
25
25
  module Inform
26
26
  # module Ephemeral
27
27
  module Ephemeral
28
28
  # The Inform::Ephemeral::Link class
29
- class Link < Inform::Link
29
+ class Link
30
30
  LinkTemplate = '%<link_name>s -> %<to_name>s [%<to_identity>s]'.freeze
31
- attr_reader :name, :to, :from
31
+ attr_accessor :name, :to, :from, :created_at, :modified_at
32
32
 
33
33
  def initialize(properties)
34
34
  super()
35
+ before_create
35
36
  @name = properties[:name]
36
37
  @to = properties[:to]
37
38
  @from = properties[:from]
@@ -39,11 +40,13 @@ module Ephemeral
39
40
 
40
41
  def before_create
41
42
  self.created_at ||= Time.now
42
- super
43
+ end
44
+
45
+ def save
46
+ after_save
43
47
  end
44
48
 
45
49
  def after_save
46
- super
47
50
  self.modified_at = Time.now.utc
48
51
  end
49
52
 
@@ -54,14 +57,6 @@ module Ephemeral
54
57
  def <=>(other)
55
58
  self.name <=> other.name
56
59
  end
57
-
58
- def init_with(coder)
59
- LinkMethods.each do |method_name|
60
- method_symbol = format(MethodWriterTemplate, method_name: method_name).to_sym
61
- self.send(method_symbol, coder[method_name]) if self.respond_to? method_symbol
62
- end
63
- self
64
- end
65
60
  end
66
61
  # class Link
67
62
  end
@@ -71,6 +66,11 @@ end
71
66
 
72
67
  # The Inform module
73
68
  module Inform
69
+ def link_klass
70
+ Inform::Ephemeral::Link
71
+ end
72
+ module_function :link_klass
73
+
74
74
  # The Linkable module
75
75
  module Linkable
76
76
  def links
@@ -80,16 +80,29 @@ module Inform
80
80
  end
81
81
 
82
82
  # rubocop: disable Metrics/AbcSize
83
+ # rubocop: disable Metrics/CyclomaticComplexity
83
84
  # rubocop: disable Metrics/MethodLength
85
+ # rubocop: disable Metrics/PerceivedComplexity
84
86
  def link(link_name, obj = nil)
85
- link = Inform::Link.first(name: link_name.to_s, from_id: self.id)
87
+ link = if Inform.link_klass.respond_to?(:first)
88
+ Inform.link_klass.first(name: link_name.to_s, from_id: self.id)
89
+ else
90
+ self.links.find { |l| l.name == link_name.to_s }
91
+ end
86
92
  link&.to&.refresh
87
93
  return link if obj.nil?
88
94
  if link.nil?
89
- clauses = { name: link_name.to_s, from_id: self.id, to_id: obj.id }
90
- link = Inform::Link.find_or_create(**clauses) do |l|
91
- l.from = self
92
- l.to = obj
95
+ link = if Inform.link_klass.respond_to?(:find_or_create)
96
+ clauses = { name: link_name.to_s, from_id: self.id, to_id: obj.id }
97
+ Inform.link_klass.find_or_create(**clauses) do |l|
98
+ l.from = self
99
+ l.to = obj
100
+ end
101
+ else
102
+ properties = { name: link_name.to_s, from: self, to: obj }
103
+ Inform::Ephemeral::Link.new(properties).tap do |l|
104
+ self.links << l
105
+ end
93
106
  end
94
107
  else
95
108
  link.to = obj
@@ -98,40 +111,59 @@ module Inform
98
111
  link
99
112
  end
100
113
  # rubocop: enable Metrics/AbcSize
114
+ # rubocop: enable Metrics/CyclomaticComplexity
101
115
  # rubocop: enable Metrics/MethodLength
116
+ # rubocop: enable Metrics/PerceivedComplexity
102
117
 
103
118
  def linked?(link_name)
104
- if Inform::Link.respond_to?(:filter)
105
- Inform::Link.filter(name: link_name.to_s, from_id: self.id).count > 0
119
+ if Inform.link_klass.respond_to?(:filter)
120
+ Inform.link_klass.filter(name: link_name.to_s, from_id: self.id).count > 0
106
121
  else
107
- !self.links.find(&:link_name).nil?
122
+ self.links.any? { |link| link.name == link_name.to_s && link.from == self }
108
123
  end
109
124
  end
110
125
 
126
+ # rubocop: disable Metrics/AbcSize
111
127
  def unlink(link_name)
112
- Inform::Link.first(name: link_name.to_s, from_id: self.id).destroy&.to
128
+ if Inform.link_klass.respond_to?(:first)
129
+ Inform.link_klass.first(name: link_name.to_s, from_id: self.id).destroy&.to
130
+ else
131
+ self.links.delete { |link| link.name == link_name.to_s && link.from == self }
132
+ end
113
133
  rescue Sequel::NoExistingObject => e
114
- log.warn 'Error: ' + e.message
115
- log.warn 'No such link: ' + link_name
134
+ log.warn "Error: #{e.message}"
135
+ log.warn "No such link: #{link_name}"
116
136
  nil
117
137
  end
138
+ # rubocop: enable Metrics/AbcSize
118
139
 
119
140
  def linkto(link_name)
120
- Inform::Link.first(name: link_name.to_s, from_id: self.id)&.to&.refresh
141
+ if Inform.link_klass.respond_to?(:first)
142
+ Inform.link_klass.first(name: link_name.to_s, from_id: self.id)&.to&.refresh
143
+ else
144
+ self.links.find { |link| link.name == link_name.to_s && link.from == self }&.to
145
+ end
121
146
  end
122
147
 
148
+ # rubocop: disable Metrics/AbcSize
123
149
  def linksfrom(link_name = nil)
124
- if link_name.nil?
125
- Inform::Link.filter(to_id: self.id).map(&:from)
150
+ if Inform.link_klass.respond_to?(:filter)
151
+ if link_name.nil?
152
+ Inform.link_klass.filter(to_id: self.id)
153
+ else
154
+ Inform.link_klass.filter(name: link_name.to_s, to_id: self.id)
155
+ end
126
156
  else
127
- Inform::Link.filter(name: link_name.to_s, to_id: self.id).map(&:from)
128
- end
157
+ self.links.select { |link| link.name == link_name.to_s && link.to == self }
158
+ end.map(&:from)
129
159
  end
160
+ # rubocop: enable Metrics/AbcSize
130
161
 
131
162
  alias _key? linked?
132
163
  alias _get_object linkto
133
164
  def _set_object(link_name, obj = nil)
134
- link(link_name, obj).to
165
+ link = link(link_name, obj)
166
+ link.to
135
167
  end
136
168
  alias _unset_object unlink
137
169
  end
@@ -1,7 +1,8 @@
1
+ # lib/story_teller/inform/module.rb
1
2
  # encoding: utf-8
2
3
  # frozen_string_literal: false
3
4
 
4
- # Copyright Nels Nelson 2008-2025 but freely usable (see license)
5
+ # Copyright Nels Nelson 2008-2026 but freely usable (see license)
5
6
  #
6
7
  # This file is part of StoryTeller.
7
8
  #
@@ -20,12 +21,12 @@
20
21
 
21
22
  # Module
22
23
 
23
- # The Inform module
24
+ # module Inform
24
25
  module Inform
25
26
  # module Ephemeral
26
27
  module Ephemeral
27
- # The Inform::Module class
28
- class Module < Inform::Module
28
+ # class Module
29
+ class Module
29
30
  def to_s
30
31
  name
31
32
  end
@@ -81,7 +82,7 @@ module Inform
81
82
  # module Ephemeral
82
83
  module Ephemeral
83
84
  # The Inform::Ephemeral::Modularized class
84
- class Modularized < Inform::Modularized; end
85
+ class Modularized; end
85
86
  end
86
87
  end
87
88
  # module Inform