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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +623 -0
  3. data/README.md +185 -0
  4. data/Rakefile +65 -0
  5. data/config/database.yml +37 -0
  6. data/exe/inform.rb +6 -0
  7. data/game/config.yml +5 -0
  8. data/game/example.inf +76 -0
  9. data/game/example.rb +90 -0
  10. data/game/forms/example_form.rb +2 -0
  11. data/game/grammar/game_grammar.inf.rb +11 -0
  12. data/game/languages/english.rb +2 -0
  13. data/game/models/example_model.rb +2 -0
  14. data/game/modules/example_module.rb +9 -0
  15. data/game/rules/example_state.rb +2 -0
  16. data/game/scripts/example_script.rb +2 -0
  17. data/game/topics/example_topic.rb +2 -0
  18. data/game/verbs/game_verbs.rb +15 -0
  19. data/game/verbs/metaverbs.rb +2028 -0
  20. data/lib/runtime/articles.rb +138 -0
  21. data/lib/runtime/builtins.rb +359 -0
  22. data/lib/runtime/color.rb +145 -0
  23. data/lib/runtime/command.rb +470 -0
  24. data/lib/runtime/config.rb +48 -0
  25. data/lib/runtime/context.rb +78 -0
  26. data/lib/runtime/daemon.rb +266 -0
  27. data/lib/runtime/database.rb +500 -0
  28. data/lib/runtime/events.rb +771 -0
  29. data/lib/runtime/experimental/handler_dsl.rb +175 -0
  30. data/lib/runtime/game.rb +74 -0
  31. data/lib/runtime/game_loader.rb +132 -0
  32. data/lib/runtime/grammar_parser.rb +553 -0
  33. data/lib/runtime/helpers.rb +177 -0
  34. data/lib/runtime/history.rb +45 -0
  35. data/lib/runtime/inflector.rb +195 -0
  36. data/lib/runtime/io.rb +174 -0
  37. data/lib/runtime/kernel.rb +450 -0
  38. data/lib/runtime/library.rb +59 -0
  39. data/lib/runtime/library_loader.rb +135 -0
  40. data/lib/runtime/link.rb +158 -0
  41. data/lib/runtime/logging.rb +197 -0
  42. data/lib/runtime/mixins.rb +570 -0
  43. data/lib/runtime/module.rb +202 -0
  44. data/lib/runtime/object.rb +761 -0
  45. data/lib/runtime/options.rb +104 -0
  46. data/lib/runtime/persistence.rb +292 -0
  47. data/lib/runtime/plurals.rb +60 -0
  48. data/lib/runtime/prototype.rb +307 -0
  49. data/lib/runtime/publication.rb +92 -0
  50. data/lib/runtime/runtime.rb +321 -0
  51. data/lib/runtime/session.rb +202 -0
  52. data/lib/runtime/stdlib.rb +604 -0
  53. data/lib/runtime/subscription.rb +47 -0
  54. data/lib/runtime/tag.rb +287 -0
  55. data/lib/runtime/tree.rb +204 -0
  56. data/lib/runtime/version.rb +24 -0
  57. data/lib/runtime/world_tree.rb +69 -0
  58. data/lib/runtime.rb +35 -0
  59. metadata +199 -0
@@ -0,0 +1,177 @@
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
+ # Additional mixin methods
22
+
23
+ # The Inform module
24
+ module Inform
25
+ # Some mixins provide exclusive Inform-ification for Ruby, withheld
26
+ # from mixins.rb because of said exclusivity
27
+ module SetHelpers
28
+ def in?(*others)
29
+ return false unless others.respond_to?(:include?)
30
+ others.flatten.include?(self)
31
+ end
32
+
33
+ def notin?(*others)
34
+ return false unless others.respond_to?(:include?)
35
+ !others.flatten.include?(self)
36
+ end
37
+ end
38
+
39
+ # Stub helpers for NilClass instances
40
+ module NilClassSetHelpers
41
+ def in?(*)
42
+ false
43
+ end
44
+ end
45
+ end
46
+
47
+ # The Inform module
48
+ module Inform
49
+ # String helper methods mostly for use by Inform::ParseNoun
50
+ # rubocop: disable Metrics/AbcSize
51
+ # rubocop: disable Metrics/CyclomaticComplexity
52
+ # rubocop: disable Metrics/MethodLength
53
+ # rubocop: disable Metrics/PerceivedComplexity
54
+ module Matchers
55
+ def matches?(obj, regexp = /^#{self}/i, delimeter = /[,\s]+/, str = self)
56
+ return false if str.nil? || (str.respond_to?(:empty?) && str.empty?)
57
+ return false unless obj.object?
58
+ k = obj.name
59
+ k = k.split(delimeter) if k.respond_to?(:split)
60
+ return str if k.respond_to?(:grep) && !k.grep(regexp).empty?
61
+ return str if k.respond_to?(:match?) && k.match?(regexp)
62
+ false
63
+ rescue StandardError => e
64
+ log.error e
65
+ false
66
+ end
67
+
68
+ def matches_exactly?(obj, regexp = /^#{self}$/i, delimeter = /[,\s]+/, str = self)
69
+ return false if str.nil? || (str.respond_to?(:empty?) && str.empty?)
70
+ return false unless obj.object?
71
+ k = obj.name
72
+ return str if k.respond_to?(:match?) && k.match?(regexp)
73
+ k = k.split(delimeter) if k.respond_to?(:split)
74
+ return str if k.respond_to?(:grep) && !k.grep(regexp).empty?
75
+ return str if k.respond_to?(:match?) && k.match?(regexp)
76
+ false
77
+ rescue StandardError => e
78
+ log.error e
79
+ false
80
+ end
81
+
82
+ def matches_any_exactly?(obj, regexp = /^#{self}$/i, delimeter = /[,\s]+/, str = self)
83
+ return false if str.nil? || (str.respond_to?(:empty?) && str.empty?)
84
+ return false unless obj.object?
85
+ k = obj.name
86
+ k = k.split(delimeter) if k.respond_to?(:split)
87
+ return str if k.respond_to?(:grep) && !k.grep(regexp).empty?
88
+ false
89
+ rescue StandardError => e
90
+ log.error e
91
+ false
92
+ end
93
+
94
+ def matches_last_exactly?(obj, regexp = /^#{self}$/i, delimeter = /[,\s]+/, str = self)
95
+ return false if str.nil? || (str.respond_to?(:empty?) && str.empty?)
96
+ return false unless obj.object?
97
+ k = obj.name
98
+ k = k.split(delimeter) if k.respond_to?(:split)
99
+ k = k.last if k.respond_to?(:last)
100
+ return str if k.respond_to?(:match?) && k.match?(regexp)
101
+ false
102
+ rescue StandardError => e
103
+ log.error e
104
+ false
105
+ end
106
+ end
107
+ # rubocop: enable Metrics/AbcSize
108
+ # rubocop: enable Metrics/CyclomaticComplexity
109
+ # rubocop: enable Metrics/MethodLength
110
+ # rubocop: enable Metrics/PerceivedComplexity
111
+ # module Matchers
112
+ end
113
+ # module Inform
114
+
115
+ # The Object class to add some Inform helper methods
116
+ class Object
117
+ include Inform::SetHelpers
118
+ include Inform::Matchers
119
+
120
+ alias exhibits? kind_of?
121
+ end
122
+
123
+ # The NilClass class to add some Inform helper methods
124
+ class NilClass
125
+ include Inform::NilClassSetHelpers
126
+ end
127
+
128
+ # The Enumerable module to add some Inform helper methods
129
+ module Enumerable
130
+ include Inform::SetHelpers
131
+
132
+ def inform(o = '')
133
+ each { |x| x.inform(o) if x.respond_to?(:inform) }
134
+ end
135
+ end
136
+
137
+ # The Inform module
138
+ module Inform
139
+ # The ObjectHelpers module functionality
140
+ module ObjectHelpers
141
+ def object?(obj = self)
142
+ obj.is_a?(Inform::Object) || obj.is_a?(Inform::System::Object)
143
+ end
144
+
145
+ def sysobj?(obj = self)
146
+ obj.is_a?(Inform::System::Object)
147
+ end
148
+ end
149
+ end
150
+
151
+ # The Inform module
152
+ module Inform
153
+ # The NilClassObjectHelpers module functionality
154
+ module NilClassObjectHelpers
155
+ def object?
156
+ false
157
+ end
158
+
159
+ def sysobj?
160
+ false
161
+ end
162
+
163
+ def list_together
164
+ self
165
+ end
166
+ end
167
+ end
168
+
169
+ # Define some built-in Inform helper methods
170
+ class Object
171
+ include Inform::ObjectHelpers
172
+ end
173
+
174
+ # Define some built-in Inform helper methods
175
+ class NilClass
176
+ include Inform::NilClassObjectHelpers
177
+ end
@@ -0,0 +1,45 @@
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
+ # The Inform module
22
+ module Inform
23
+ # The History module
24
+ module History
25
+ DefaultHistoryLimit = 10
26
+ Histories = defined?(Java) ? java.util.concurrent.ConcurrentHashMap.new : {}
27
+ HistoryCommandPattern = %r{^(!|history)$}.freeze
28
+
29
+ def history
30
+ Histories[@player.identity] ||= []
31
+ end
32
+
33
+ def history_limit
34
+ return DefaultHistoryLimit unless @player.respond_to?(:preferences)
35
+ @player.preferences.|(:history_limit, DefaultHistoryLimit).to_i
36
+ end
37
+
38
+ def record_history(s)
39
+ return if s.nil? || s.empty? || HistoryCommandPattern.match?(s)
40
+ history.delete_if { |i| i == s }
41
+ history.push(s)
42
+ history.shift if history.length > history_limit
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,195 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # Copyright (c) 2004-2014 David Heinemeier Hansson
5
+
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ # == License
26
+
27
+ # Active Support is released under the MIT license:
28
+
29
+ # * http://www.opensource.org/licenses/MIT
30
+
31
+ # The Inform module
32
+ module Inform
33
+ # The Inflector module
34
+ module Inflector
35
+ extend self # rubocop: disable Style/ModuleFunction
36
+
37
+ # The Inflections class
38
+ class Inflections
39
+ @instance = nil
40
+
41
+ def self.instance
42
+ @instance ||= new
43
+ end
44
+
45
+ attr_reader :plurals, :singulars, :uncountables, :propers
46
+
47
+ def initialize
48
+ clear_fields
49
+ end
50
+
51
+ def clear_fields(fields = %i[plurals singulars uncountables propers])
52
+ fields.each { |field| instance_variable_set(:"@#{field}", []) }
53
+ end
54
+
55
+ def plural(rule, replacement)
56
+ @uncountables.delete(rule) if rule.is_a?(String)
57
+ @uncountables.delete(replacement)
58
+ @plurals.prepend([rule, replacement])
59
+ end
60
+
61
+ def singular(rule, replacement)
62
+ @uncountables.delete(rule) if rule.is_a?(String)
63
+ @uncountables.delete(replacement)
64
+ @singulars.prepend([rule, replacement])
65
+ end
66
+
67
+ def defective(noun)
68
+ @plurals.unshift([/^#{noun}$/i, noun])
69
+ end
70
+
71
+ # rubocop: disable Metrics/AbcSize
72
+ # rubocop: disable Metrics/MethodLength
73
+ def irregular(singular, plural)
74
+ @uncountables.delete(singular)
75
+ @uncountables.delete(plural)
76
+
77
+ s0 = singular[0]
78
+ srest = singular[1..]
79
+
80
+ p0 = plural[0]
81
+ prest = plural[1..]
82
+
83
+ if s0.upcase == p0.upcase
84
+ plural(/(#{s0})#{srest}$/i, '\1' + prest)
85
+ plural(/(#{p0})#{prest}$/i, '\1' + prest)
86
+
87
+ singular(/(#{s0})#{srest}$/i, '\1' + srest)
88
+ singular(/(#{p0})#{prest}$/i, '\1' + srest)
89
+ else
90
+ plural(/#{s0.upcase}(?i)#{srest}$/, p0.upcase + prest)
91
+ plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest)
92
+ plural(/#{p0.upcase}(?i)#{prest}$/, p0.upcase + prest)
93
+ plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest)
94
+
95
+ singular(/#{s0.upcase}(?i)#{srest}$/, s0.upcase + srest)
96
+ singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest)
97
+ singular(/#{p0.upcase}(?i)#{prest}$/, s0.upcase + srest)
98
+ singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest)
99
+ end
100
+ end
101
+ # rubocop: enable Metrics/AbcSize
102
+ # rubocop: enable Metrics/MethodLength
103
+
104
+ def uncountable(*words)
105
+ (@uncountables << words).flatten!
106
+ end
107
+
108
+ def proper(rule, replacement)
109
+ @propers.unshift([rule, replacement])
110
+ end
111
+
112
+ def clear(scope = :all)
113
+ case scope
114
+ when :all
115
+ clear_fields
116
+ else
117
+ clear_fields([scope])
118
+ end
119
+ end
120
+ end
121
+ # class Inflections
122
+
123
+ def inflections
124
+ return yield Inflections.instance if block_given?
125
+ Inflections.instance
126
+ end
127
+ end
128
+ # module Inflector
129
+
130
+ # rubocop: disable Metrics/BlockLength
131
+ Inflector.inflections do |inflect|
132
+ inflect.plural(/$/, 's')
133
+ inflect.plural(/s$/i, 's')
134
+ inflect.plural(/^(ax|test)is$/i, '\1es')
135
+ inflect.plural(/(octop|vir)us$/i, '\1i')
136
+ inflect.plural(/(octop|vir)i$/i, '\1i')
137
+ inflect.plural(/(alias|status)$/i, '\1es')
138
+ inflect.plural(/(bu)s$/i, '\1ses')
139
+ inflect.plural(/(buffal|tomat)o$/i, '\1oes')
140
+ inflect.plural(/([ti])um$/i, '\1a')
141
+ inflect.plural(/([ti])a$/i, '\1a')
142
+ inflect.plural(/sis$/i, 'ses')
143
+ inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
144
+ inflect.plural(/(hive)$/i, '\1s')
145
+ inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
146
+ inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
147
+ inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
148
+ inflect.plural(/^(m|l)ouse$/i, '\1ice')
149
+ inflect.plural(/^(m|l)ice$/i, '\1ice')
150
+ inflect.plural(/^(ox)$/i, '\1en')
151
+ inflect.plural(/^(oxen)$/i, '\1')
152
+ inflect.plural(/(quiz)$/i, '\1zes')
153
+
154
+ inflect.singular(/s$/i, '')
155
+ inflect.singular(/(ss)$/i, '\1')
156
+ inflect.singular(/(n)ews$/i, '\1ews')
157
+ inflect.singular(/([ti])a$/i, '\1um')
158
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
159
+ inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
160
+ inflect.singular(/([^f])ves$/i, '\1fe')
161
+ inflect.singular(/(hive)s$/i, '\1')
162
+ inflect.singular(/(tive)s$/i, '\1')
163
+ inflect.singular(/([lr])ves$/i, '\1f')
164
+ inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
165
+ inflect.singular(/(s)eries$/i, '\1eries')
166
+ inflect.singular(/(m)ovies$/i, '\1ovie')
167
+ inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
168
+ inflect.singular(/^(m|l)ice$/i, '\1ouse')
169
+ inflect.singular(/(bus)(es)?$/i, '\1')
170
+ inflect.singular(/(o)es$/i, '\1')
171
+ inflect.singular(/(shoe)s$/i, '\1')
172
+ inflect.singular(/(cris|test)(is|es)$/i, '\1is')
173
+ inflect.singular(/^(a)x[ie]s$/i, '\1xis')
174
+ inflect.singular(/(octop|vir)(us|i)$/i, '\1us')
175
+ inflect.singular(/(alias|status)(es)?$/i, '\1')
176
+ inflect.singular(/^(ox)en/i, '\1')
177
+ inflect.singular(/(vert|ind)ices$/i, '\1ex')
178
+ inflect.singular(/(matr)ices$/i, '\1ix')
179
+ inflect.singular(/(quiz)zes$/i, '\1')
180
+ inflect.singular(/(database)s$/i, '\1')
181
+
182
+ inflect.irregular('child', 'children')
183
+ inflect.irregular('man', 'men')
184
+ inflect.irregular('move', 'moves')
185
+ inflect.irregular('glove', 'gloves')
186
+ inflect.irregular('person', 'people')
187
+ inflect.irregular('sex', 'sexes')
188
+ inflect.irregular('zombie', 'zombies')
189
+
190
+ inflect.uncountable(%w[equipment information rice money species series fish sheep jeans police])
191
+ end
192
+ # rubocop: enable Metrics/BlockLength
193
+ # Inflector.inflections do |inflect|
194
+ end
195
+ # module Inform
data/lib/runtime/io.rb ADDED
@@ -0,0 +1,174 @@
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
+ # The Inform module
22
+ module Inform
23
+ # The IO module
24
+ module IO
25
+ def session
26
+ return unless defined?(Inform::IO::Session) && Inform::IO::Session.respond_to?(:of)
27
+ @session ||= Inform::IO::Session.of(self)
28
+ end
29
+
30
+ def noop; end
31
+
32
+ def connections
33
+ subscribed_connections = explicit_subscribers.select { |a| a.is_a? Connection }
34
+ if respond_to?(:inflib) && !inflib.nil? && subscribed_connections.empty?
35
+ subscribed_connections = inflib.explicit_subscribers.select { |a| a.is_a? Connection }
36
+ end
37
+ subscribed_connections
38
+ end
39
+
40
+ def read
41
+ $stdin.gets(chomp: true)
42
+ end
43
+
44
+ def out(s)
45
+ if defined?(Curses)
46
+ Curses.addstr s
47
+ else
48
+ $stdout.write(s)
49
+ end
50
+ end
51
+
52
+ def inform(s = '')
53
+ s = invert_illeism(self, s) if respond_to?(:invert_illeism)
54
+ new_line
55
+ println s
56
+ new_line if s.is_a?(Array) && !s.empty?
57
+ prompt
58
+ flush_io
59
+ true
60
+ end
61
+ alias update inform
62
+
63
+ def flush_io
64
+ $stdout.flush
65
+ end
66
+
67
+ def tidy_output(s, columns = 76)
68
+ s.gsub!(/^\n+/, "\n")
69
+ return s if s.length < columns || columns == 0
70
+ s.gsub(/(.{#{columns}}\S+)( +|$\n?)/, "\\1\n")
71
+ end
72
+
73
+ def new_line
74
+ out "\n"
75
+ end
76
+
77
+ def spaces(n)
78
+ out(' ' * n)
79
+ end
80
+
81
+ def print(s = '')
82
+ out(s.to_s)
83
+ end
84
+
85
+ def println(s = '', isolate: false)
86
+ _isolated = (isolate == true) # TODO: Implement
87
+ return print(s) if s.is_a?(Array)
88
+ out(s.to_s + "\n")
89
+ end
90
+
91
+ def output_stream(i, s); end
92
+
93
+ def reset_prompt
94
+ @prompt = nil
95
+ end
96
+
97
+ def prompt(s = @prompt)
98
+ out(@prompt = s || "\n>")
99
+ end
100
+
101
+ # ----------------------------------------------------------------------------
102
+ # Adapted from the C code for ls. :-)
103
+ # ----------------------------------------------------------------------------
104
+
105
+ ItemPaddingString = '%<item>s%<padding>s'.freeze
106
+ MinimumLineLength = 30
107
+ MinimumCellPadding = 8
108
+ MinimumColumnPadding = 2
109
+
110
+ # rubocop: disable Metrics/AbcSize
111
+ # rubocop: disable Metrics/CyclomaticComplexity
112
+ # rubocop: disable Metrics/MethodLength
113
+ # rubocop: disable Metrics/PerceivedComplexity
114
+ def many_per_line(items)
115
+ max = 0
116
+
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 = ''
122
+
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
141
+
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
150
+
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)
158
+ end
159
+ buffer += "\e[56m"
160
+ lines << buffer.strip
161
+ buffer = ''
162
+ end
163
+
164
+ lines
165
+ end
166
+ # rubocop: enable Metrics/AbcSize
167
+ # rubocop: enable Metrics/CyclomaticComplexity
168
+ # rubocop: enable Metrics/MethodLength
169
+ # rubocop: enable Metrics/PerceivedComplexity
170
+ # def many_per_line
171
+ end
172
+ # module IO
173
+ end
174
+ # module Inform