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.
- checksums.yaml +4 -4
- data/README.md +33 -41
- data/lib/story_teller/articles.rb +2 -1
- data/lib/story_teller/builtins.rb +63 -10
- data/lib/story_teller/color.rb +2 -1
- data/lib/story_teller/command.rb +2 -1
- data/lib/story_teller/context.rb +23 -27
- data/lib/story_teller/daemon.rb +2 -2
- data/lib/story_teller/engine.rb +45 -10
- data/lib/story_teller/events.rb +1 -1
- data/lib/story_teller/experimental/handler_dsl.rb +1 -16
- data/lib/story_teller/experimental/reverse_engineer_class.rb +1 -1
- data/lib/story_teller/grammar_parser.rb +60 -13
- data/lib/story_teller/helpers.rb +8 -4
- data/lib/story_teller/history.rb +1 -1
- data/lib/story_teller/inflector.rb +4 -2
- data/lib/story_teller/inform/ephemeral/link.rb +63 -31
- data/lib/story_teller/inform/ephemeral/module.rb +6 -5
- data/lib/story_teller/inform/ephemeral/object.rb +223 -236
- data/lib/story_teller/inform/ephemeral/tag.rb +27 -14
- data/lib/story_teller/io.rb +99 -49
- data/lib/story_teller/kernel.rb +2 -2
- data/lib/story_teller/library/bootstrap.rb +2 -2
- data/lib/story_teller/library/declarations.rb +1 -1
- data/lib/story_teller/library/directives.rb +1 -1
- data/lib/story_teller/library/loader.rb +3 -20
- data/lib/story_teller/library/location.rb +9 -3
- data/lib/story_teller/library.rb +1 -1
- data/lib/story_teller/logging.rb +1 -1
- data/lib/story_teller/mixins.rb +11 -4
- data/lib/story_teller/plurals.rb +2 -1
- data/lib/story_teller/privileges.rb +89 -6
- data/lib/story_teller/prototype.rb +150 -32
- data/lib/story_teller/publication.rb +2 -1
- data/lib/story_teller/session.rb +115 -25
- data/lib/story_teller/stdlib.rb +231 -1
- data/lib/story_teller/subscription.rb +2 -1
- data/lib/story_teller/tree.rb +2 -1
- data/lib/story_teller/version.rb +2 -2
- data/lib/story_teller/world_tree.rb +21 -23
- data/lib/story_teller.rb +18 -5
- metadata +6 -10
- data/lib/story_teller/core.rb +0 -39
- data/lib/story_teller/ephemeral_adapter.rb +0 -40
- data/lib/story_teller/inform/base.rb +0 -160
- data/lib/story_teller/model_adapter.rb +0 -132
data/lib/story_teller/io.rb
CHANGED
|
@@ -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-
|
|
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
|
|
27
|
-
|
|
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
|
-
|
|
188
|
+
items = items.map(&:to_s)
|
|
189
|
+
return [] if items.empty?
|
|
116
190
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
next
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
buffer = ''
|
|
212
|
+
|
|
213
|
+
rows << row.join.rstrip
|
|
162
214
|
end
|
|
163
215
|
|
|
164
|
-
|
|
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
|
data/lib/story_teller/kernel.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# encoding: utf-8
|
|
3
3
|
# frozen_string_literal: false
|
|
4
4
|
|
|
5
|
-
# Copyright Nels Nelson 2008-
|
|
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
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
|
data/lib/story_teller/library.rb
CHANGED
data/lib/story_teller/logging.rb
CHANGED
data/lib/story_teller/mixins.rb
CHANGED
|
@@ -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-
|
|
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(
|
|
460
|
-
|
|
461
|
-
|
|
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
|
data/lib/story_teller/plurals.rb
CHANGED
|
@@ -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-
|
|
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-
|
|
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
|
-
#
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
112
|
+
subject.object_id
|
|
113
|
+
end
|
|
32
114
|
end
|
|
33
115
|
end
|
|
34
116
|
end
|
|
117
|
+
# module StoryTeller
|