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,138 @@
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 Articles module
24
+ # Support standard Inform methods for dynamically applying appropriate
25
+ # articles to nouns.
26
+ module Articles
27
+ LowercaseTheSpaceString = 'the '.freeze
28
+ LowercaseSomeSpaceString = 'some '.freeze
29
+
30
+ # TODO: Add support for on-the-fly indefinite/definite mode
31
+ # differentiation based on the inclusion of the noun (x1) in
32
+ # the pronouns table.
33
+ # rubocop: disable Metrics/AbcSize
34
+ # rubocop: disable Metrics/CyclomaticComplexity
35
+ def the(obj)
36
+ return Inform::English::NOTHING__TX if obj.nil?
37
+ return PrefaceByArticle(obj, 0) if obj.is_a?(String)
38
+ return obj.to_s unless obj.respond_to?(:has?)
39
+ return obj.to_s if obj.has?(:proper)
40
+ return obj.article + ' ' + obj.to_s if obj.string?(:article)
41
+ return your(obj) if obj.respond_to?(:owner) && obj.owner == player
42
+ # PrefaceByArticle(obj, 1)
43
+ LowercaseTheSpaceString + obj.to_s
44
+ end
45
+ # rubocop: enable Metrics/AbcSize
46
+ # rubocop: enable Metrics/CyclomaticComplexity
47
+
48
+ def The(obj)
49
+ return obj.to_s unless obj.respond_to?(:has?)
50
+ return obj.to_s if obj.has?(:proper)
51
+ the(obj).capitalize
52
+ end
53
+
54
+ def defart(obj)
55
+ return theirself(obj) if obj == player
56
+ return hisorher(player, obj) if player.descendants.include?(obj)
57
+ the(obj)
58
+ end
59
+
60
+ def indefart(obj)
61
+ return theirself(obj) if obj == player
62
+ return hisorher(player, obj) if player.descendants.include?(obj)
63
+ a(obj)
64
+ end
65
+
66
+ LowercaseASpaceString = 'a '.freeze
67
+
68
+ # rubocop: disable Metrics/AbcSize
69
+ def a(obj)
70
+ return Inform::English::NOTHING__TX if obj.nil?
71
+ return PrefaceByArticle(obj, 1) if obj.is_a?(String)
72
+ return obj.to_s unless obj.respond_to?(:has?)
73
+ return obj.to_s if obj.has?(:proper)
74
+ return obj.article + ' ' + obj.to_s if obj.string?(:article)
75
+ return LowercaseSomeSpaceString + obj.to_s if obj.has?(:pluralname)
76
+ # PrefaceByArticle(obj, 1)
77
+ LowercaseASpaceString + obj.to_s
78
+ end
79
+ # rubocop: enable Metrics/AbcSize
80
+
81
+ def A(obj)
82
+ a(obj)&.sentence_case || obj.to_s
83
+ end
84
+
85
+ def isorare(obj); IsorAre(obj); end
86
+
87
+ def itorthem(obj); ItorThem(obj); end
88
+
89
+ def thatorthose(obj); ThatorThose(obj); end
90
+
91
+ def Cthatorthose(obj); CThatorThose(obj); end
92
+
93
+ def cthatorthose(obj); CThatorThose(obj); end
94
+
95
+ def Ctheyreorthats(obj); CTheyreorThats(obj); end
96
+
97
+ def ctheyreorthats(obj); CTheyreorThats(obj); end
98
+ end
99
+ # module Articles
100
+
101
+ # Re-open the Inform::Parser module
102
+ module Parser
103
+ include Inform::Articles
104
+
105
+ VowelsOrHiPattern = %r{^([aeiou]|hi).+}.freeze
106
+
107
+ # TODO: Figure out why this isn't being called by #a(obj) above.
108
+ # rubocop: disable Metrics/AbcSize
109
+ # rubocop: disable Metrics/CyclomaticComplexity
110
+ # rubocop: disable Metrics/MethodLength
111
+ # rubocop: disable Metrics/PerceivedComplexity
112
+ def PrefaceByArticle(o, _acode, pluralise = false, capitalise = false)
113
+ log.info "wtf1 #{self.class}##{__method__}" # TODO: Remove
114
+ if (obj_articles = o.&:articles) && obj_articles.respond_to?(:[])
115
+ return obj_articles[acode] unless obj_articles[acode].nil?
116
+ return if pluralise
117
+ end
118
+ return if pluralise
119
+ if VowelsOrHiPattern.match?(o.to_s)
120
+ return "an #{o}".sentence_case if capitalise
121
+ "an #{o}"
122
+ else
123
+ return "a #{o}".sentence_case if capitalise
124
+ "a #{o}"
125
+ end
126
+ end
127
+ # rubocop: enable Metrics/AbcSize
128
+ # rubocop: enable Metrics/CyclomaticComplexity
129
+ # rubocop: enable Metrics/MethodLength
130
+ # rubocop: enable Metrics/PerceivedComplexity
131
+ end
132
+ # module Parser
133
+
134
+ module Verbs
135
+ include Inform::Articles
136
+ end
137
+ end
138
+ # module Inform
@@ -0,0 +1,359 @@
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 'set'
22
+
23
+ require 'method_source' # Require the method_source gem
24
+
25
+ require_relative 'articles'
26
+ require_relative 'color'
27
+ require_relative 'plurals'
28
+
29
+ # These are built-ins which may be used throughout the Inform namespace.
30
+ # TODO: Consider moving these into a module included in the Kernel module.
31
+ module Inform
32
+ include Inform::Articles
33
+ include Inform::Color
34
+ include Inform::Plurals
35
+
36
+ alias random rand
37
+
38
+ def dictionary
39
+ Inform::Dictionary
40
+ end
41
+
42
+ def compass
43
+ InformLibrary::Compass
44
+ end
45
+
46
+ def nothing
47
+ nil
48
+ end
49
+
50
+ def content; respond_to?(:descendants) ? self.descendants : Array::Empty; end
51
+ alias contents content
52
+
53
+ # rubocop: disable Metrics/AbcSize
54
+ # rubocop: disable Metrics/CyclomaticComplexity
55
+ # rubocop: disable Metrics/MethodLength
56
+ # rubocop: disable Metrics/PerceivedComplexity
57
+ def with(context = nil, &block)
58
+ return self if block.nil?
59
+ existing_methods = self.singleton_methods(false)
60
+
61
+ if context.nil?
62
+ self.instance_eval(&block)
63
+ else
64
+ self.instance_exec(context, &block)
65
+ end
66
+
67
+ # Only persist methods being defined just now on *this* instance
68
+ added = self.singleton_methods(false) - existing_methods
69
+ added.select! do |m|
70
+ self.singleton_class.instance_method(m).owner == self.singleton_class
71
+ end
72
+
73
+ if added.any?
74
+ persisted = self.properties.fetch(:instance_behavior, '')
75
+ added.each do |method_name|
76
+ source = method_source(method_name)
77
+ next if source.nil?
78
+ # Only support def ... end blocks
79
+ next unless resembles_method?(source)
80
+ persisted << source unless persisted.include?(source)
81
+ end
82
+ unless persisted.empty?
83
+ self.will_change_column(:properties) if self.respond_to?(:will_change_column)
84
+ self.properties[:instance_behavior] = persisted
85
+ end
86
+ end
87
+ self.save_changes if self.respond_to?(:save_changes)
88
+ self
89
+ end
90
+ # rubocop: enable Metrics/AbcSize
91
+ # rubocop: enable Metrics/CyclomaticComplexity
92
+ # rubocop: enable Metrics/MethodLength
93
+ # rubocop: enable Metrics/PerceivedComplexity
94
+
95
+ MethodDefinitionPattern = /\s*def\s+/.freeze
96
+ MethodTerminationPattern = /\s*end$/.freeze
97
+
98
+ def resembles_method?(source)
99
+ MethodDefinitionPattern.match?(source) && MethodTerminationPattern.match?(source)
100
+ end
101
+
102
+ def method_source(method_name)
103
+ self.singleton_class.instance_method(method_name).source
104
+ rescue StandardError => _e
105
+ nil
106
+ end
107
+
108
+ def apply_instance_behavior!
109
+ source = self.properties[:instance_behavior]
110
+ return if source.nil? || source.empty?
111
+ return unless source.include?('def ')
112
+ self.instance_eval(source, "(instance_behavior for #{self.identity})", 1)
113
+ rescue NameError => e
114
+ log.warn "[InstanceBehavior] #{e.class}: #{e.message}"
115
+ end
116
+
117
+ def workflags
118
+ Thread.current[:workflags] ||= Set.new
119
+ end
120
+
121
+ def initial
122
+ # TODO: What is this supposed to return?
123
+ end
124
+
125
+ def ofclass(klass)
126
+ self.is_a?(klass)
127
+ end
128
+ alias ofclass? ofclass
129
+
130
+ def provides?(prop)
131
+ symbolized_prop = prop.to_sym
132
+ return symbolized_prop if self.respond_to?(symbolized_prop)
133
+ downcased_prop = symbolized_prop.downcase
134
+ return downcased_prop if self.respond_to?(downcased_prop)
135
+ false
136
+ end
137
+
138
+ def react_after?(action_routine = action)
139
+ return false if action_routine.nil?
140
+ self.apply_modules if self.respond_to?(:apply_modules)
141
+ self.provides?('react_after_' + action_routine)
142
+ rescue StandardError => e
143
+ log.error "Error checking for after reaction: #{e.message}", e
144
+ false
145
+ end
146
+
147
+ def react_before?(action_routine = action)
148
+ return false if action_routine.nil?
149
+ self.apply_modules if self.respond_to?(:apply_modules)
150
+ self.provides?('react_before_' + action_routine)
151
+ rescue StandardError => e
152
+ log.error "Error checking for before reaction: #{e.message}", e
153
+ false
154
+ end
155
+
156
+ def life?(action_routine = action)
157
+ return false if action_routine.nil?
158
+ self.apply_modules if self.respond_to?(:apply_modules)
159
+ self.provides?('life_' + action_routine)
160
+ rescue StandardError => e
161
+ log.error "Error checking for life reaction: #{e.message}", e
162
+ false
163
+ end
164
+
165
+ def objectloop(*_args, &block)
166
+ if self.is_a?(InformLibrary)
167
+ Inform::Object.all.each { |o| block.call(o) }
168
+ elsif self.respond_to?(:descendants)
169
+ self.descendants.each { |o| block.call(o) }
170
+ end
171
+ end
172
+
173
+ def getobject(id)
174
+ o = Inform::Object[id]
175
+ o.refresh
176
+ rescue StandardError
177
+ raise NoSuchObject
178
+ end
179
+
180
+ def findobject(*args, &block)
181
+ return Inform::Object.dataset.order(:name, :id).grep(:name, *args).to_a if args.first.is_a?(Regexp)
182
+ Inform::Object.dataset.grep(&block).to_a
183
+ end
184
+
185
+ def deadflag; @deadflag; end
186
+
187
+ def deadflag=(flag); @deadflag = flag; end
188
+
189
+ def the_time; @the_time.nil? ? 0 : @the_time; end
190
+
191
+ def the_time=(the_time); @the_time = the_time; end
192
+
193
+ def score; @score.nil? ? 0 : @score; end
194
+
195
+ def score=(score); @score = score; end
196
+
197
+ def things_score; @things_score.nil? ? 0 : @things_score; end
198
+
199
+ def things_score=(things_score); @things_score = things_score; end
200
+
201
+ def visibility_ceiling=(visibility_ceiling)
202
+ e = Thread.current[:event]
203
+ @visibility_ceiling = visibility_ceiling
204
+ e.visibility_ceiling = visibility_ceiling if e
205
+ end
206
+
207
+ def topic
208
+ consult_words
209
+ end
210
+ alias text topic
211
+
212
+ def number(x = nil)
213
+ return special_number1 if x.nil?
214
+ LanguageNumber(x)
215
+ ''
216
+ end
217
+
218
+ def special
219
+ @special_word
220
+ end
221
+
222
+ def num_words
223
+ @words.nil? || @words.empty? ? 0 : @words.length
224
+ end
225
+
226
+ def current_word
227
+ @words[@wn] || ''
228
+ end
229
+
230
+ def remaining_words
231
+ @words[@wn..]
232
+ end
233
+
234
+ def language_pronouns
235
+ # TODO: Cleanup upon disconnect
236
+ key = self.respond_to?(:inflib) ? self.inflib : self.identity
237
+ LanguagePronouns[key] ||= Marshal.load(Marshal.dump(LanguagePronouns[:default]))
238
+ end
239
+
240
+ def language_descriptors
241
+ # TODO: Cleanup upon disconnect
242
+ key = self.respond_to?(:inflib) ? self.inflib : self.identity
243
+ LanguageDescriptors[key] ||= Marshal.load(Marshal.dump(LanguageDescriptors[:default]))
244
+ end
245
+
246
+ def light_adjusted(obj = @player)
247
+ # TODO: Consider performing the following in another thread or an event
248
+ obj.root.descendants.each do |o|
249
+ o.inflib.send(:AdjustLight, true) if o.has?(:creature) && !o.inflib.nil?
250
+ end
251
+ end
252
+
253
+ def go(obj)
254
+ invoke :Go, obj
255
+ end
256
+
257
+ def say(*s)
258
+ invoke :Say, s.flatten.sample
259
+ end
260
+
261
+ def emote(*s)
262
+ return consult_words if s.empty?
263
+ invoke :Emote, s.flatten.sample
264
+ end
265
+
266
+ def drop(obj)
267
+ invoke :Drop, obj
268
+ end
269
+
270
+ def search(obj)
271
+ invoke :Search, obj
272
+ end
273
+
274
+ def disrobe(obj)
275
+ invoke :Disrobe, obj
276
+ end
277
+
278
+ def quit
279
+ Inform::Runtime.instance.quit
280
+ publish :disconnect
281
+ end
282
+
283
+ # ----------------------------------------------------------------------------
284
+ # (From the Inform Designer's Manual)
285
+ # §26 Describing objects and rooms
286
+ # ----------------------------------------------------------------------------
287
+ # To print the name of such an object, Inform does the following:
288
+ #
289
+ # (1) If the short_name is a string, it's printed and that's all.
290
+ # (2) If it is a routine, then it is called. If it returns true, that's all.
291
+ # (3) The text given in the header of the object definition is printed.
292
+ # ----------------------------------------------------------------------------
293
+ def to_s
294
+ textual_name = self.short_name if self.respond_to?(:short_name)
295
+ return textual_name if textual_name.is_a?(String) # (1)
296
+ return if textual_name == true # (2)
297
+ if self.respond_to?(:name)
298
+ return self.name.join(' ') if self.name.respond_to?(:join)
299
+ return self.name.to_s # (3)
300
+ end
301
+ self.identity
302
+ end
303
+ alias to_str to_s
304
+
305
+ def classification
306
+ type = object_type.to_s
307
+ clazz = self.class.to_s
308
+ return type if type == clazz
309
+ "#{type} < #{clazz}"
310
+ end
311
+
312
+ def randomly(n = 3)
313
+ rand * n
314
+ end
315
+
316
+ def pause(n)
317
+ sleep(n)
318
+ :inform
319
+ end
320
+
321
+ def library_method?(method)
322
+ Inform::Library.methods_index.include?(method) && respond_to?(method)
323
+ end
324
+
325
+ VERB = 1
326
+ PLURAL = 4
327
+ PREPOSITION = 8
328
+ NOUN = 128
329
+ def dict_par1(w, par = 0)
330
+ # +128 if given a noun, +8 if a preposition, +4 if plural, +1 if a verb
331
+ par += NOUN if Inform::Dictionary.include?(w.to_sym)
332
+ par += PREPOSITION if Inform::English::Prepositions.include?(w)
333
+ par += PLURAL if plural?(w)
334
+ par += VERB unless Inform::Grammar::Verbs.lookup(w).nil?
335
+ par
336
+ end
337
+
338
+ protected
339
+
340
+ CommaSpacePattern = %r{[,\s]+}.freeze
341
+ NonAlphabeticPattern = %r{[^a-z]}i.freeze
342
+
343
+ # rubocop: disable Metrics/AbcSize
344
+ # rubocop: disable Metrics/CyclomaticComplexity
345
+ # rubocop: disable Metrics/PerceivedComplexity
346
+ def index_words(obj = self)
347
+ return unless obj.respond_to?(:name)
348
+ k = obj.name
349
+ return if k.nil? || (k.respond_to?(:empty?) && k.empty?)
350
+ a = k.split(CommaSpacePattern).grep_v(NonAlphabeticPattern) if k.respond_to?(:split)
351
+ return unless k.respond_to?(:gsub!)
352
+ a = k.gsub!(NonAlphabeticPattern, '') if a.nil? || a.empty?
353
+ Inform::Dictionary.merge(a.map(&:downcase).map(&:intern))
354
+ end
355
+ # rubocop: enable Metrics/AbcSize
356
+ # rubocop: enable Metrics/CyclomaticComplexity
357
+ # rubocop: enable Metrics/PerceivedComplexity
358
+ end
359
+ # module Inform
@@ -0,0 +1,145 @@
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 Color module
24
+ module Color
25
+ StyleCodes = {
26
+ plain: [
27
+ "\e[0m", "\e[0m"],
28
+ bold: [
29
+ "\e[1m", "\e[22m"],
30
+ italic: [
31
+ "\e[3m", "\e[23m"],
32
+ underline: [
33
+ "\e[4m", "\e[24m"],
34
+ blink: [
35
+ "\e[5m", "\e[25m"],
36
+ flash: [
37
+ "\e[6m", "\e[26m"],
38
+ inverse: [
39
+ "\e[7m", "\e[27m"],
40
+ hidden: [
41
+ "\e[8m", "\e[28m"],
42
+ strikethrough: [
43
+ "\e[9m", "\e[29m"]
44
+ }.freeze
45
+
46
+ ColorCodes = {
47
+ default: [
48
+ "\e[0m", "\e[0m"],
49
+ black: [
50
+ "\e[30m", "\e[39m"],
51
+ black_on_white: [
52
+ "\e[30m", "\e[39m"],
53
+ red: [
54
+ "\e[31m", "\e[39m"],
55
+ green: [
56
+ "\e[32m", "\e[39m"],
57
+ yellow: [
58
+ "\e[33m", "\e[39m"],
59
+ blue: [
60
+ "\e[34m", "\e[39m"],
61
+ purple: [
62
+ "\e[35m", "\e[39m"],
63
+ magenta: [
64
+ "\e[35m", "\e[39m"],
65
+ cyan: [
66
+ "\e[36m", "\e[39m"],
67
+ white: [
68
+ "\e[37m", "\e[39m"]
69
+ }.freeze
70
+
71
+ BackgroundColorCodes = {
72
+ default: [
73
+ "\e[0m", "\e[0m"],
74
+ black: [
75
+ "\e[40m", "\e[49m"],
76
+ red: [
77
+ "\e[41m", "\e[49m"],
78
+ green: [
79
+ "\e[42m", "\e[49m"],
80
+ yellow: [
81
+ "\e[43m", "\e[49m"],
82
+ blue: [
83
+ "\e[44m", "\e[49m"],
84
+ purple: [
85
+ "\e[45m", "\e[49m"],
86
+ magenta: [
87
+ "\e[45m", "\e[49m"],
88
+ cyan: [
89
+ "\e[46m", "\e[49m"],
90
+ white: [
91
+ "\e[47m", "\e[49m"]
92
+ }.freeze
93
+
94
+ # Text style and color for terminals
95
+ def style(style, text = nil)
96
+ code = (StyleCodes[style] || StyleCodes[:plain]).first
97
+ return code + text + (StyleCodes[style] || StyleCodes[:plain]).last unless text.nil?
98
+ print code
99
+ end
100
+ def un_style(style)
101
+ print((StyleCodes[style] || StyleCodes[:plain]).last)
102
+ end
103
+ alias unstyle un_style
104
+ def color(color, text = nil)
105
+ code = (ColorCodes[color] || ColorCodes[:default]).first
106
+ return code + text + (ColorCodes[color] || ColorCodes[:default]).last unless text.nil?
107
+ print code
108
+ end
109
+ def un_color(color)
110
+ print((ColorCodes[color] || ColorCodes[:default]).last)
111
+ end
112
+ alias uncolor un_color
113
+ def background_color(color, text = nil)
114
+ code = (BackgroundColorCodes[color] || BackgroundColorCodes[:default]).first
115
+ return code + text + (BackgroundColorCodes[color] || BackgroundColorCodes[:default]).last unless text.nil?
116
+ print code
117
+ end
118
+ alias bgcolor background_color
119
+ def un_background_color(color)
120
+ print((BackgroundColorCodes[color] || BackgroundColorCodes[:default]).last)
121
+ end
122
+ alias unbgcolor un_background_color
123
+
124
+ NearestColor = {
125
+ red: %i[
126
+ crimson brick salmon coral],
127
+ yellow: %i[
128
+ golden orange],
129
+ green: %i[
130
+ seagreen teal forestgreen],
131
+ blue: %i[
132
+ aqua cyan skyblue turqoise],
133
+ magenta: %i[
134
+ lavender fuchsia indigo orchid plum purple violet]
135
+ }.freeze
136
+
137
+ def nearest_color(color)
138
+ colors = NearestColor.select { |_, v| v.include?(color) }
139
+ return colors.first.first unless colors.empty?
140
+ :white
141
+ end
142
+ end
143
+ # module Color
144
+ end
145
+ # module Inform