inform-runtime 1.2.3 → 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 +2 -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 +45 -10
  10. data/lib/story_teller/events.rb +1 -1
  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 -4
  15. data/lib/story_teller/history.rb +1 -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 +2 -2
  23. data/lib/story_teller/library/bootstrap.rb +2 -2
  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 +3 -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 +1 -1
  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 +89 -6
  33. data/lib/story_teller/prototype.rb +150 -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 +231 -1
  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 +6 -10
  43. data/lib/story_teller/core.rb +0 -39
  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/io.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 the StoryTeller.
7
8
  #
@@ -23,8 +24,10 @@ module StoryTeller
23
24
  # The IO module
24
25
  module IO
25
26
  def session
26
- return unless defined?(StoryTeller::IO::Session) && StoryTeller::IO::Session.respond_to?(:of)
27
- @session ||= StoryTeller::IO::Session.of(self)
27
+ return @session if defined?(@session) && !@session.nil?
28
+ return nil unless defined?(StoryTeller::IO::Session)
29
+
30
+ StoryTeller::IO::Session.of(self)
28
31
  end
29
32
 
30
33
  def noop; end
@@ -98,76 +101,123 @@ module StoryTeller
98
101
  out(@prompt = s || "\n>")
99
102
  end
100
103
 
104
+ def preference_value(key)
105
+ prefs = session.preferences
106
+
107
+ return prefs.public_send(key) if prefs.respond_to?(key)
108
+ return prefs[key] if prefs.respond_to?(:[]) && prefs.key?(key)
109
+ return prefs[key.to_s] if prefs.respond_to?(:[]) && prefs.key?(key.to_s)
110
+
111
+ nil
112
+ end
113
+
101
114
  # ----------------------------------------------------------------------------
102
115
  # Adapted from the C code for ls. :-)
103
116
  # ----------------------------------------------------------------------------
104
117
 
105
118
  ItemPaddingString = '%<item>s%<padding>s'.freeze
119
+ DefaultLineLength = 30
106
120
  MinimumLineLength = 30
107
121
  MinimumCellPadding = 8
108
122
  MinimumColumnPadding = 2
109
123
 
124
+ def preferred_line_length
125
+ width = preference_value(:word_wrap).to_i
126
+ return width if width > MinimumLineLength
127
+
128
+ DefaultLineLength
129
+ end
130
+
131
+ # def many_per_line(items)
132
+ # max = 0
133
+
134
+ # lines = []
135
+ # prefs = @player.linkto :preferences
136
+ # line_length = preferred_line_length
137
+ # buffer = ''
138
+
139
+ # items.each do |item|
140
+ # max = [item.length, max].max
141
+ # end
142
+ # max += MinimumColumnPadding
143
+ # width = (line_length - MinimumCellPadding) / max
144
+ # height = (items.length / width).ceil - 1
145
+
146
+ # return if width == 0
147
+
148
+ # columns = []
149
+ # i = 0
150
+ # loop do
151
+ # # Build a list of columns with nil padding
152
+ # column = items[i, height]
153
+ # columns << column + Array.new(height - column.length)
154
+ # i += height
155
+ # break if (i + height) >= (items.length + height)
156
+ # end
157
+
158
+ # m = []
159
+ # loop do
160
+ # m << []
161
+ # for column in columns
162
+ # m.last << column.shift
163
+ # end
164
+ # break if columns.last.empty?
165
+ # end
166
+
167
+ # m.each do |row|
168
+ # buffer = "\e[51m"
169
+ # row.each do |item|
170
+ # next unless item
171
+ # n = [max - item.length, MinimumColumnPadding].max
172
+ # padding = " " * n unless item == row.last
173
+ # buffer += format(ItemPaddingString, item: item, padding: padding)
174
+ # end
175
+ # buffer += "\e[56m"
176
+ # lines << buffer.strip
177
+ # buffer = ''
178
+ # end
179
+
180
+ # lines
181
+ # end
182
+ # # def many_per_line
183
+
110
184
  # rubocop: disable Metrics/AbcSize
111
185
  # rubocop: disable Metrics/CyclomaticComplexity
112
186
  # rubocop: disable Metrics/MethodLength
113
- # rubocop: disable Metrics/PerceivedComplexity
114
187
  def many_per_line(items)
115
- max = 0
188
+ items = items.map(&:to_s)
189
+ return [] if items.empty?
116
190
 
117
- lines = []
118
- prefs = @player.linkto :preferences
119
- line_length = prefs ? prefs.word_wrap.to_i : MinimumLineLength
120
- line_length = MinimumLineLength if line_length <= MinimumLineLength
121
- buffer = ''
191
+ line_length = preferred_line_length
192
+ max_item_length = items.map(&:length).max || 0
193
+ cell_width = max_item_length + MinimumColumnPadding
122
194
 
123
- items.each do |item|
124
- max = [item.length, max].max
125
- end
126
- max += MinimumColumnPadding
127
- width = (line_length - MinimumCellPadding) / max
128
- height = (items.length / width).ceil - 1
129
-
130
- return if width == 0
131
-
132
- columns = []
133
- i = 0
134
- loop do
135
- # Build a list of columns with nil padding
136
- column = items[i, height]
137
- columns << column + Array.new(height - column.length)
138
- i += height
139
- break if (i + height) >= (items.length + height)
140
- end
195
+ column_count = (line_length - MinimumCellPadding) / cell_width
196
+ column_count = 1 if column_count < 1
197
+ column_count = items.length if column_count > items.length
141
198
 
142
- m = []
143
- loop do
144
- m << []
145
- for column in columns
146
- m.last << column.shift
147
- end
148
- break if columns.last.empty?
149
- end
199
+ row_count = (items.length.to_f / column_count).ceil
200
+ rows = []
201
+
202
+ row_count.times do |row_index|
203
+ row = []
150
204
 
151
- m.each do |row|
152
- buffer = "\e[51m"
153
- row.each do |item|
154
- next unless item
155
- n = [max - item.length, MinimumColumnPadding].max
156
- padding = " " * n unless item == row.last
157
- buffer += format(ItemPaddingString, item: item, padding: padding)
205
+ column_count.times do |column_index|
206
+ item_index = row_index + (column_index * row_count)
207
+ item = items[item_index]
208
+ next if item.nil?
209
+
210
+ row << item.ljust(cell_width)
158
211
  end
159
- buffer += "\e[56m"
160
- lines << buffer.strip
161
- buffer = ''
212
+
213
+ rows << row.join.rstrip
162
214
  end
163
215
 
164
- lines
216
+ rows
165
217
  end
166
218
  # rubocop: enable Metrics/AbcSize
167
219
  # rubocop: enable Metrics/CyclomaticComplexity
168
220
  # rubocop: enable Metrics/MethodLength
169
- # rubocop: enable Metrics/PerceivedComplexity
170
- # def many_per_line
171
221
  end
172
222
  # module IO
173
223
  end
@@ -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 StoryTeller.
8
8
  #
@@ -346,7 +346,7 @@ remove_method :original_rand
346
346
  return 0 if obj.nil?
347
347
  obj.refresh if obj.respond_to?(:refresh)
348
348
  if defined? db
349
- return db.run('SELECT count(id) from "object" where parent_id = ' + obj.id) unless sysobj?
349
+ return db.run('SELECT count(id) from "object" where parent_id = ' + obj.id) unless ephemeral?
350
350
  end
351
351
  return obj.instance_variable_get(prop).length if obj.instance_variable_defined?(prop)
352
352
  return unless obj.respond_to?(:children)
@@ -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
  #
@@ -38,7 +38,7 @@ module StoryTeller
38
38
  inflib = InformLibrary.new
39
39
  bind_inform_library(inflib, player)
40
40
  inflib.subscribe(inflib.selfobj)
41
- ensure_location(inflib)
41
+ ensure_player_location(inflib) if StoryTeller::Engine.persist_player_location?
42
42
  inflib
43
43
  end
44
44
  end
@@ -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
  #
@@ -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
  #
@@ -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
  #
@@ -33,11 +33,11 @@ module StoryTeller
33
33
  module_function
34
34
 
35
35
  def augment_inform_library!
36
- ensure_inform_object_model!
37
36
  InformLibrary.extend(StoryTeller::Engine::Library)
38
37
  InformLibrary.class_eval do
39
38
  include Inform::Context unless include?(Inform::Context)
40
39
  include Inform::StdLib unless include?(Inform::StdLib)
40
+ include Inform::Rules unless include?(Inform::Rules)
41
41
  include Inform::History unless include?(Inform::History)
42
42
  end
43
43
  InformLibrary.prepend(StoryTeller::Library::Location)
@@ -45,21 +45,6 @@ module StoryTeller
45
45
 
46
46
  def load_library
47
47
  augment_inform_library!
48
- StoryTeller::Engine.library
49
- end
50
-
51
- def inform_object_model?
52
- object_class = StoryTeller::ModelAdapter.object_class
53
- return false if object_class.nil?
54
-
55
- object_class.method_defined?(:<<) &&
56
- object_class.method_defined?(:with)
57
- end
58
-
59
- def ensure_inform_object_model!
60
- return if inform_object_model?
61
-
62
- StoryTeller::EphemeralAdapter.register!
63
48
  end
64
49
 
65
50
  # rubocop: disable Metrics/AbcSize
@@ -85,6 +70,7 @@ module StoryTeller
85
70
  end
86
71
  # rubocop: enable Metrics/AbcSize
87
72
  # rubocop: enable Metrics/MethodLength
73
+
88
74
  def load_grammar(module_name)
89
75
  Inform::Grammar.load_by_name(module_name)
90
76
  end
@@ -98,8 +84,5 @@ module StoryTeller
98
84
  Inform::Grammar::Verbs.clear
99
85
  end
100
86
  end
101
- # module Loader
102
87
  end
103
- # module Library
104
88
  end
105
- # module StoryTeller
@@ -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
  #
@@ -26,11 +26,17 @@ module StoryTeller
26
26
  # module Location
27
27
  module Location
28
28
  def location
29
- @player&.location
29
+ return @location unless StoryTeller::Engine.persist_player_location?
30
+
31
+ @player&.location || @location
30
32
  end
31
33
 
32
34
  def location=(obj)
33
- @player.location = obj
35
+ if StoryTeller::Engine.persist_player_location?
36
+ @player.location = obj
37
+ else
38
+ @location = obj
39
+ end
34
40
  end
35
41
  end
36
42
 
@@ -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
  #
@@ -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 StoryTeller.
8
8
  #
@@ -1,7 +1,8 @@
1
+ # lib/story_teller/mixins.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
  #
@@ -456,9 +457,15 @@ class Array
456
457
  end
457
458
  end
458
459
 
459
- def sum(&block)
460
- return inject(0, &block) if block_given?
461
- inject(:+)
460
+ def sum(init = nil)
461
+ if block_given?
462
+ initial = init.nil? ? 0 : init
463
+ inject(initial) { |total, element| total + yield(element) }
464
+ elsif init.nil?
465
+ inject(:+)
466
+ else
467
+ inject(init, :+)
468
+ end
462
469
  end
463
470
 
464
471
  def average
@@ -1,7 +1,8 @@
1
+ # lib/story_teller/plurals.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 the StoryTeller.
7
8
  #
@@ -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
  #
@@ -19,16 +19,99 @@
19
19
  # You should have received a copy of the GNU General Public License
20
20
  # along with the StoryTeller. If not, see <http://www.gnu.org/licenses/>.
21
21
 
22
- # The StoryTeller module
22
+ # module StoryTeller
23
23
  module StoryTeller
24
+ # module PrivilegedIdentity
25
+ module PrivilegedIdentity
26
+ def privilege?(_privilege)
27
+ false
28
+ end
29
+
30
+ def privilege_label
31
+ to_s
32
+ end
33
+ end
34
+
24
35
  # The StoryTeller::Privileges module to implement privilege
25
36
  module Privileges
26
- def admin?
27
- self.has?(:admin)
37
+ class << self
38
+ def privileged?(subject, privilege)
39
+ StoryTeller::PrivilegeGrants.granted?(subject, privilege)
40
+ end
41
+
42
+ def subject_for(subject)
43
+ session_for(subject) || subject
44
+ end
45
+
46
+ private
47
+
48
+ def session_for(subject)
49
+ return nil if subject.nil?
50
+ return nil unless defined?(StoryTeller::IO::Session)
51
+ return subject if subject.is_a?(StoryTeller::IO::Session)
52
+ return subject.instance_variable_get(:@session) if subject.instance_variable_defined?(:@session)
53
+
54
+ StoryTeller::IO::Session.of(subject)
55
+ end
56
+ end
57
+
58
+ def privileged?(privilege, subject = self)
59
+ StoryTeller::Privileges.privileged?(subject, privilege)
28
60
  end
61
+ end
62
+
63
+ # module PrivilegeGrants
64
+ module PrivilegeGrants
65
+ @session_grants = Hash.new { |hash, key| hash[key] = Set.new }
66
+ @mode = :persistent
67
+
68
+ class << self
69
+ attr_accessor :mode
70
+
71
+ def grant_session(subject, privilege)
72
+ subject = StoryTeller::Privileges.subject_for(subject)
73
+ return nil if subject.nil?
74
+
75
+ @session_grants[identity(subject)].add(privilege.to_sym)
76
+ end
77
+
78
+ def session_granted?(subject, privilege)
79
+ subject = StoryTeller::Privileges.subject_for(subject)
80
+ return false if subject.nil?
81
+
82
+ @session_grants[identity(subject)].include?(privilege.to_sym)
83
+ end
84
+
85
+ def persistent_granted?(subject, privilege)
86
+ subject = StoryTeller::Privileges.subject_for(subject)
87
+ return false unless subject.is_a?(StoryTeller::PrivilegedIdentity)
88
+
89
+ subject.privilege?(privilege.to_sym)
90
+ end
91
+
92
+ def granted?(subject, privilege)
93
+ privilege = privilege.to_sym
94
+
95
+ session_granted?(subject, privilege) ||
96
+ (privilege == :builder && session_granted?(subject, :admin)) ||
97
+ persistent_granted_under_mode?(subject, privilege)
98
+ end
99
+
100
+ private
101
+
102
+ def persistent_granted_under_mode?(subject, privilege)
103
+ return false if mode == :session_only
104
+
105
+ persistent_granted?(subject, privilege) ||
106
+ (privilege == :builder && persistent_granted?(subject, :admin))
107
+ end
108
+
109
+ def identity(subject)
110
+ return subject.identity if subject.respond_to?(:identity)
29
111
 
30
- def builder?
31
- admin? || self.has?(:builder)
112
+ subject.object_id
113
+ end
32
114
  end
33
115
  end
34
116
  end
117
+ # module StoryTeller