rotor_machine 1.2.0 → 1.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 547ab156c985ce0df5dad34665d7705456ff34993b84cc738c887fce7144783f
4
- data.tar.gz: 6d2211b04113a15757706928e3b572bc09bd2781036fb0317344984f774bd82e
3
+ metadata.gz: 1434f2b4192ac16aca51423849b94d8c53456fa27371d55ac97ef4df9f0d82cf
4
+ data.tar.gz: 775be6d47e8db0655ead3338289df1040cbcacdc9565e062064c9cb6a3f5e168
5
5
  SHA512:
6
- metadata.gz: d3d3e224d89bdc1531cfe50f40cf66cec543b693da2547ebf4fd4a3892fc125f5a86c60c96d0912c0161f28e2c353772a5987da99a1d2872d2ee163e49616321
7
- data.tar.gz: a85cec802ad9a032b8348f5311bd686428d8e23c183182db4dcf9827c831d272b4bb2df1d14716e3749b94928c7c9fd7920d98b18d649ffd6db947d73b17ecf4
6
+ metadata.gz: 8e34d8afdeb57b48c992be30ce4d042f95519d87537a5c9c6422beaa7f1ed0bd0aa63d54cfb03aae92604fd1600a5d2b0a6fe470bafe6f70327d635251486acf
7
+ data.tar.gz: d6cd87c934b973e50ac79907508a33507cdd827164b71f631ed33b0052ca39cbe192f4cfa52832bb4aa9c842dfe97a6a9270a3aab2b7e49dfa1e786de251db76
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  Gemfile.lock
10
+ TODO
10
11
 
11
12
  # rspec failure tracking
12
13
  .rspec_status
@@ -1 +1 @@
1
- ruby-2.5.0
1
+ ruby-2.5.3
data/README.md CHANGED
@@ -189,6 +189,22 @@ After the operations in the block are executed, the `RotorMachine.Session` metho
189
189
  will return the `RotorMachine::Session` object, which can be further reused if
190
190
  needed.
191
191
 
192
+ ## Using the `rotor_machine` REPL
193
+
194
+ The `rotor_machine` executable instantatiates an instance of the
195
+ `RotorMachine::Shell` class and then runs its `repl()` method. This creates an
196
+ interactive shell whereby you can interact with a rotor machine. The
197
+ `RotorMachine::Shell` is an interactive wrapper around `RotorMachine::Session`
198
+ with help, readline and ANSI colorization added.
199
+
200
+ Either run the `rotor_machine` executable, or run the following code to instantiate
201
+ a REPL:
202
+
203
+ `RotorMachine::Shell.new().repl`
204
+
205
+ The REPL provides interactive usage help. Type `help` at the REPL prompt for more
206
+ details.
207
+
192
208
  ## Documentation
193
209
 
194
210
  The classes in
@@ -1,3 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "rotor_machine"
3
+ require 'readline'
4
+ require 'colorize'
5
+ require 'rotor_machine'
6
+
7
+ String.disable_colorization = false
8
+
9
+ shell = RotorMachine::Shell.new()
10
+ shell.repl()
@@ -95,7 +95,11 @@ module RotorMachine
95
95
  #
96
96
  # @return [String] A description of the current state.
97
97
  def to_s
98
- "a RotorMachine::Plugboard with connections: #{@connections.to_s}"
98
+ seen = []
99
+ @connections.each { |k, v| seen << k.to_sym unless seen.include?(v.to_sym) }
100
+ conn = @connections.clone
101
+ conn.keys.each { |k| conn.delete(k.to_s) unless seen.include?(k.to_sym) }
102
+ "a RotorMachine::Plugboard with connections: #{conn.to_s}"
99
103
  end
100
104
 
101
105
  ##
@@ -76,7 +76,15 @@ module RotorMachine
76
76
  ##
77
77
  # Encipher a string.
78
78
  def encipher(the_string="")
79
- res = @machine.encipher(the_string)
79
+ if the_string.nil?
80
+ res = ""
81
+ elsif the_string.is_a?(Array)
82
+ res = @machine.encipher(the_string.join(" "))
83
+ elsif the_string.is_a?(String)
84
+ res = @machine.encipher(the_string)
85
+ else
86
+ res = @machine.encipher(the_string.to_s)
87
+ end
80
88
  @last_result = res
81
89
  res
82
90
  end
@@ -0,0 +1,460 @@
1
+ require 'readline'
2
+ require 'colorize'
3
+
4
+ module RotorMachine
5
+ ##
6
+ # Provide an interactive REPL for manipulating a {RotorMachine::Session} to create
7
+ # and interact with a rotor machine.
8
+ #
9
+ # == Usage
10
+ #
11
+ # shell = RotorMachine::Shell.new()
12
+ # shell.repl()
13
+ class Shell
14
+
15
+ ##
16
+ # Shell command map. Each command in this list corresponds to a method in the
17
+ # {RotorMachine::Shell class}. The key is the name of the command, and the
18
+ # value is an array listing [description, arguments, aliases].
19
+ COMMANDS =
20
+ {
21
+ rotor: ["Add a rotor to the machine", "<kind> [position] [step_size]", "add_rotor"],
22
+ reflector: ["Set the machine's reflector", "<kind> [position]", nil],
23
+ connect: ["Connect two letters on the plugboard", "<from> <to>", "plug"],
24
+ disconnect: ["Disconnnect a letter (and its inverse) on the plugboard", "<letter>", "unplug"],
25
+ default_machine: ["Set the machine to its default configuration", nil, nil],
26
+ empty_machine: ["Set the machine to its empty configuration", nil, nil],
27
+ last_result: ["Print the result of the last encipherment operation", nil, nil],
28
+ configuration: ["Print out the machine's current state", nil, "current_state,config"],
29
+ set_positions: ["Set the rotor positions", "<positions>", "set_rotors"],
30
+ clear_rotors: ["Clear the current rotor set", nil, nil],
31
+ clear_plugboard: ["Clear the current plugboard configuration", nil, nil],
32
+ help: ["Display help", "[command]", nil],
33
+ version: ["Display the version of the rotor_machine library", nil, nil],
34
+ about_prompt: ["Information about the shell prompt", nil, nil],
35
+ about_rotors: ["List the names of available rotors", nil, nil],
36
+ about_reflectors: ["List the names of available reflectors", nil, nil],
37
+ }.freeze
38
+
39
+ ##
40
+ # Shell "external command" map. This functions the same as the {COMMANDS} list,
41
+ # except for commands that are internal to the REPL and are implemented within the
42
+ # logic of the {#repl} method.
43
+ EXTERNAL_COMMANDS =
44
+ {
45
+ encipher: ["Encode a message", "[message]", "cipher,encode"],
46
+ quit: ["Quit the application", nil, "exit"],
47
+ }.freeze
48
+
49
+ ##
50
+ # Initialize a new {RotorMachine::Shell} instance, and the interior
51
+ # {RotorMachine::Session} object which the shell manages.
52
+ def initialize()
53
+ @session = RotorMachine::Session.new({})
54
+ @session.default_machine
55
+ end
56
+
57
+ ########## shell command handlers ##########
58
+
59
+ ##
60
+ # Wrapper around {RotorMachine::Session#rotor}. Expects rotor kind, position
61
+ # and step size in the arglist.
62
+ # @param arglist [Array] Array of arguments provided from the REPL
63
+ # @return [String] A confirmation message, or an error message on failure.
64
+ def rotor(arglist)
65
+ kind = arglist[0].to_sym
66
+
67
+ if arglist[1].nil? || (arglist[1].is_a?(String) && arglist[1].empty?)
68
+ position = 0
69
+ elsif arglist[1].is_a?(String) && arglist[1].is_number?
70
+ position = arglist[1].to_i
71
+ else
72
+ position = arglist[1]
73
+ end
74
+
75
+ if arglist[2].nil? || (arglist[2].is_a?(String) && arglist[2].empty?)
76
+ step_size = 1
77
+ elsif arglist[2].is_a?(String) && arglist[2].is_number?
78
+ step_size = arglist[2].to_i
79
+ else
80
+ step_size = 1
81
+ end
82
+
83
+ @session.rotor(kind, position, step_size)
84
+ "Added rotor #{@session.the_machine.rotors.count} of kind #{kind}"
85
+ end
86
+
87
+ ##
88
+ # Wrapper around {RotorMachine::Session#reflector}. Expects reflector kind and
89
+ # position in the arglist.
90
+ # @param arglist [Array] Array of arguments provided from the REPL
91
+ # @return [String] A confirmation message, or an error message on failure.
92
+ def reflector(arglist)
93
+ kind = arglist[0].to_sym
94
+
95
+ if arglist[1].nil? || (arglist[1].is_a?(String) && arglist[1].empty?)
96
+ position = 0
97
+ elsif arglist[1].is_a?(String) && arglist[1].is_number?
98
+ position = arglist[1].to_i
99
+ else
100
+ position = arglist[1]
101
+ end
102
+
103
+ @session.reflector(kind, position)
104
+ "Set reflector of kind #{kind}"
105
+ end
106
+
107
+ ##
108
+ # Wrapper around {RotorMachine::Session#connect}. Expects from and to letters
109
+ # in the arglist.
110
+ # @param arglist [Array] Array of arguments provided from the REPL
111
+ # @return [String] A confirmation message, or an error message on failure.
112
+ def connect(arglist)
113
+ from = arglist[0]
114
+ to = arglist[1]
115
+
116
+ @session.connect(from.upcase, to.upcase)
117
+ "Connected #{from.upcase} to #{to.upcase} on plugboard"
118
+ end
119
+
120
+ ##
121
+ # Wrapper around {RotorMachine::Session#disconnect}. Expects the letter to
122
+ # disconnect in the arglist.
123
+ # @param arglist [Array] Array of arguments provided from the REPL
124
+ # @return [String] A confirmation message, or an error message on failure.
125
+ def disconnect(arglist)
126
+ letter = arglist[0]
127
+ @session.disconnect(letter.upcase)
128
+ "Disconnected #{letter} and its inverse on plugboard"
129
+ end
130
+
131
+ ##
132
+ # Wrapper around {RotorMachine::Session#encipher}. Expects the text to encipher
133
+ # in the arglist.
134
+ # @param arglist [Array] Array of arguments provided from the REPL
135
+ # @return [String] A confirmation message, or an error message on failure.
136
+ def encipher(arglist)
137
+ @session.encipher(arglist)
138
+ end
139
+
140
+ ##
141
+ # Wrapper around {RotorMachine::Session#default_machine}. Arglist is ignored.
142
+ # @param arglist [Array] Array of arguments provided from the REPL
143
+ # @return [String] A confirmation message, or an error message on failure.
144
+ def default_machine(args)
145
+ @session.default_machine
146
+ "Reset machine to default configuration"
147
+ end
148
+
149
+ ##
150
+ # Wrapper around {RotorMachine::Session#empty_machine}. Arglist is ignored.
151
+ # @param arglist [Array] Array of arguments provided from the REPL
152
+ # @return [String] A confirmation message, or an error message on failure.
153
+ def empty_machine(args)
154
+ @session.empty_machine
155
+ "Reset machine to empty configuration"
156
+ end
157
+
158
+ ##
159
+ # Wrapper around {RotorMachine::Session#last_result}. Arglist is ignored.
160
+ # @param arglist [Array] Array of arguments provided from the REPL
161
+ # @return [String] A confirmation message, or an error message on failure.
162
+ def last_result(args)
163
+ @session.last_result
164
+ end
165
+
166
+ ##
167
+ # Print out the current configuration of the machine. Arglist is ignored.
168
+ # @param arglist [Array] Array of arguments provided from the REPL
169
+ # @return [String] A confirmation message, or an error message on failure.
170
+ def configuration(args)
171
+ @session.the_machine.to_s
172
+ end
173
+ alias_method :current_state, :configuration
174
+ alias_method :config, :configuration
175
+
176
+ ##
177
+ # Wrapper around {RotorMachine::Session#set_positions}. Expects a string
178
+ # specifying the rotor positions in arglist[0].
179
+ # @param arglist [Array] Array of arguments provided from the REPL
180
+ # @return [String] A confirmation message, or an error message on failure.
181
+ def set_positions(arglist)
182
+ pos_string = arglist[0]
183
+ @session.set_positions(pos_string)
184
+ "Set rotors; rotor state is now #{rotor_state}"
185
+ end
186
+ alias_method :set_rotors, :set_positions
187
+
188
+ ##
189
+ # Wrapper around {RotorMachine::Session#clear_rotors}. Arglist is ignored.
190
+ # @param arglist [Array] Array of arguments provided from the REPL
191
+ # @return [String] A confirmation message, or an error message on failure.
192
+ def clear_rotors(args)
193
+ @session.clear_rotors
194
+ "Removed all rotors from the machine"
195
+ end
196
+
197
+ ##
198
+ # Wrapper around {RotorMachine::Session#clear_plugboard}. Arglist is ignored.
199
+ # @param arglist [Array] Array of arguments provided from the REPL
200
+ # @return [String] A confirmation message, or an error message on failure.
201
+ def clear_plugboard(args)
202
+ @session.clear_plugboard
203
+ "Removed all connections from the plugboard"
204
+ end
205
+
206
+ ##
207
+ # Print command help. If an argument is specified in the first position of the
208
+ # arglist, help about that specific command is printed. If no argument is
209
+ # supplied, a list of commands is printed instead.
210
+ def help(args)
211
+ if args[0].nil? || args[0].empty?
212
+ <<~HEREDOC
213
+
214
+ #{verbs.keys.sort.collect { |k| "#{k}#{' ' *(20-k.length)} #{verbs[k][0]}" }.join("\n")}
215
+ HEREDOC
216
+ else
217
+ cmd_info = verbs[args[0].to_sym]
218
+
219
+ <<~HEREDOC
220
+
221
+ #{args[0]}: #{cmd_info[0]}
222
+
223
+ Usage : #{args[0]} #{cmd_info[1]}
224
+ Aliases: #{cmd_info[2] || "none"}
225
+
226
+ HEREDOC
227
+ end
228
+ end
229
+
230
+ ##
231
+ # Print the version number of the rotor_machine.
232
+ def version(args)
233
+ "rotor_machine version #{RotorMachine::VERSION}"
234
+ end
235
+
236
+ ########## Internal Helper Methods ##########
237
+
238
+ ##
239
+ # Return the selected letters on each of the rotors in the machine.
240
+ def rotor_state
241
+ @session.the_machine.rotors.collect { |r| r.letters[r.position] }.join("")
242
+ end
243
+
244
+ ##
245
+ # Check if `cmd` is included on the list of internal command verbs or is an
246
+ # alias for an internal verb.
247
+ def is_internal_verb?(cmd)
248
+ aliases = []
249
+
250
+ COMMANDS.each do |k, v|
251
+ unless v[2].nil?
252
+ v[2].split(',').each { |a| aliases << a.to_sym }
253
+ end
254
+ end
255
+
256
+ COMMANDS.keys.include?(cmd) || aliases.include?(cmd)
257
+ end
258
+
259
+ ##
260
+ # Return the "arity" (in this case, the number of mandatory arguments) for a
261
+ # command or its alias.
262
+ def arity(cmd)
263
+ if COMMANDS.keys.include?(cmd)
264
+ return COMMANDS[cmd][1].split(' ').select { |x| x.start_with?("<") }.count
265
+ else
266
+ COMMANDS.each do |k, v|
267
+ unless v[2].nil?
268
+ v[2].split(',').each do |a|
269
+ if a.to_sym == cmd.to_sym
270
+ return v[1].split(' ').select { |x| x.start_with?("<") }.count
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end
276
+ return 0
277
+ end
278
+
279
+ ##
280
+ # Return the combined list of command verbs and their arguments/usage.
281
+ def verbs
282
+ COMMANDS.merge(EXTERNAL_COMMANDS)
283
+ end
284
+
285
+ ##
286
+ # Build the Readline prompt for the rotor machine. By default, displays the
287
+ # following pieces of information:
288
+ #
289
+ # - Count of rotors mounted to the machine
290
+ # - Count of connections on the plugboard
291
+ # - Current selected letters on the rotors
292
+ #
293
+ # If you redefine this method, you should also redefine the {#about_prompt}
294
+ # method to describe the new prompt correctly.
295
+ def readline_prompt
296
+ [
297
+ "[#{@session.the_machine.rotors.count}]".colorize(color: :light_blue),
298
+ "<#{@session.the_machine.plugboard.connections.count}>".colorize(color: :light_blue),
299
+ "#{rotor_state}".colorize(color: :light_blue),
300
+ "> ".colorize(color: :white),
301
+ ].join(" ")
302
+ end
303
+
304
+ ##
305
+ # Display the about help for the REPL prompt. If you redefine the {#readline_prompt}
306
+ # method, you should also redefine this to reflect the new prompt.
307
+ def about_prompt(arglist)
308
+ #:nocov:
309
+ puts ""
310
+ puts "The prompt for the shell is in the following format:"
311
+ puts ""
312
+ puts " [XXX] <YYY> ZZZ>"
313
+ puts ""
314
+ puts "The components of the prompt are as follows:"
315
+ puts ""
316
+ puts " XXX - the number of rotors mounted to the machine"
317
+ puts " YYY - the number of connections on the plugboard"
318
+ puts " ZZZ - the current positions of the rotors"
319
+ ""
320
+ #:nocov:
321
+ end
322
+
323
+ def about_reflectors(arglist)
324
+ #:nocov:
325
+ puts ""
326
+ puts "The following reflectors are available with this machine:"
327
+ puts ""
328
+ RotorMachine::Reflector.constants.each { |r| puts " #{r.to_s.colorize(color: :light_blue)}" }
329
+ puts ""
330
+ puts "To set the reflector for the machine, use a command like this:"
331
+ puts ""
332
+ puts " Specify reflector: #{'reflector REFLECTOR_A'.colorize(color: :light_blue)}"
333
+ puts " Specify reflector/pos: #{'reflector REFLECTOR_A 13'.colorize(color: :light_blue)}"
334
+ puts ""
335
+ puts "The REPL does not currently support custom reflectors."
336
+ puts ""
337
+ ""
338
+ #:nocov:
339
+ end
340
+
341
+ def about_rotors(arglist)
342
+ #:nocov:
343
+ puts ""
344
+ puts "The following rotors are available with this machine:"
345
+ puts ""
346
+ RotorMachine::Rotor.constants.each { |r| puts " #{r.to_s.colorize(color: :light_blue)}" }
347
+ puts ""
348
+ puts "To add a rotor to the machine, use a command like this:"
349
+ puts ""
350
+ puts " Specify rotor : #{'rotor ROTOR_I'.colorize(color: :light_blue)}"
351
+ puts " Specify rotor/pos : #{'rotor ROTOR_I 13'.colorize(color: :light_blue)}"
352
+ puts " #{'rotor ROTOR_I Q'.colorize(color: :light_blue)}"
353
+ puts " Specify rotor/pos/step: #{'rotor ROTOR_I 13 2'.colorize(color: :light_blue)}"
354
+ puts ""
355
+ puts "The REPL does not currently support custom rotors."
356
+ ""
357
+ #:nocov:
358
+ end
359
+
360
+ ##
361
+ # Display the startup banner for the REPL application.
362
+ def banner
363
+ puts "******************************************************************************".colorize(color: :white, background: :magenta)
364
+ puts "* rotor_machine: Simple simulation of the German WWII Enigma Machine *".colorize(color: :white, background: :magenta)
365
+ puts "* By Tammy Cravit <tammycravit@me.com> *".colorize(color: :white, background: :magenta)
366
+ puts "* http://github.com/tammycravit/rotor_machine *".colorize(color: :white, background: :magenta)
367
+ puts "******************************************************************************".colorize(color: :white, background: :magenta)
368
+ puts ""
369
+ puts "rotor_machine version #{RotorMachine::VERSION}. Type 'help' for help. <Tab> to complete commands.".colorize(color: :magenta)
370
+ puts ""
371
+ end
372
+
373
+ def the_machine
374
+ return @session.the_machine
375
+ end
376
+
377
+ def the_session
378
+ return @session
379
+ end
380
+
381
+ ########## REPL MAIN FUNCTION ##########
382
+
383
+ ##
384
+ # Process a single command from the REPL and display its output.
385
+ #
386
+ # This method is called for all commands except for "quit" and "exit", which are
387
+ # processed by {#repl} directly.
388
+ #
389
+ # @param input [String] The command line provided by the REPL (or the test).
390
+ # @return [Symbol] One of :SUCCESS, :EXCEPTION, :ARITY, :INVALID_COMMAND
391
+ # depending on whether the command was recognized and executed.
392
+ def process_command_input(input)
393
+ begin
394
+ unless input.empty?
395
+ toks = input.tokenize
396
+ cmd = toks.shift.downcase.strip
397
+
398
+ if ['cipher', 'encipher', 'encode'].include?(cmd)
399
+ message = toks.join(' ')
400
+ puts self.encipher(message).colorize(color: :white).bold
401
+ elsif self.is_internal_verb?(cmd.to_sym)
402
+ begin
403
+ if toks.length >= arity(cmd.to_sym)
404
+ if cmd == "last_result"
405
+ puts self.send(cmd.to_sym, toks).colorize(color: :white).bold
406
+ else
407
+ puts self.send(cmd.to_sym, toks).colorize(color: :green)
408
+ end
409
+ else
410
+ puts "Command #{cmd} requires at least #{arity(cmd.to_sym)} arguments".colorize(color: :red)
411
+ end
412
+ end
413
+ else
414
+ puts "Unknown command: #{cmd}".colorize(color: :light_red).bold
415
+ end
416
+ end
417
+ rescue StandardError => e
418
+ puts "Rescued exception: #{e}".colorize(color: :red)
419
+ end
420
+ end
421
+
422
+ ##
423
+ # Provide an interactive REPL for manipulating the Rotor Machine. Essentially
424
+ # this REPL is an interactive wrapper around the {RotorMachine::Session} object,
425
+ # with tab completion and command history provided by the {Readline} library.
426
+ #
427
+ # @param commands [Array] If provided, the commands passed in will be executed
428
+ # in sequence. This is mainly intended for RSpec testing. If no commands are
429
+ # passed in, the interactive REPL loop (with Readline) will be launched instead.
430
+ def repl(commands=nil)
431
+ Readline.completion_append_character = " "
432
+ Readline.completion_proc = proc { |s| verbs.keys.grep(/^#{Regexp.escape(s)}/) }
433
+
434
+ banner
435
+
436
+ if commands.nil? || commands.empty?
437
+ #:nocov:
438
+ while input = Readline.readline(readline_prompt, true).strip
439
+ if ['exit', 'quit'].include?(input.downcase)
440
+ return
441
+ end
442
+ process_command_input(input)
443
+ end
444
+ #:nocov:
445
+ else
446
+ commands.each { |cmd| process_command_input(cmd) }
447
+ end
448
+ end
449
+
450
+ ##
451
+ # Helper for instantiating a new REPL.
452
+ #
453
+ # @param commands [Array] If provided, the commands passed in will be executed
454
+ # in sequence. This is mainly intended for RSpec testing. If no commands are
455
+ # passed in, the interactive REPL loop (with Readline) will be launched instead.
456
+ def self.repl(commands=nil)
457
+ RotorMachine::Shell.new().repl(commands)
458
+ end
459
+ end
460
+ end
@@ -40,6 +40,36 @@ class String
40
40
  self.chars.reject{|s| s.match(/\s/)}.each_slice(block_size).map(&:join)
41
41
  end
42
42
  end
43
-
44
43
  alias :in_blocks :in_blocks_of
44
+
45
+ ##
46
+ # Break a string into words, including handling quoted substrings.
47
+ #
48
+ # @return An array of tokens, if the string can be tokenized.
49
+ def tokenize
50
+ self.
51
+ split(/\s(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/).
52
+ select {|s| not s.empty? }.
53
+ map {|s| s.gsub(/(^ +)|( +$)|(^["']+)|(["']+$)/,'')}
54
+ end
55
+
56
+ ##
57
+ # Test if the string is a number.
58
+ #
59
+ # @return True if the string is a number, false otherwise.
60
+ def is_number?
61
+ true if Float(self) rescue false
62
+ end
63
+
64
+
65
+ ##
66
+ # Strip ANSI color sequences from the string.
67
+ #
68
+ # @return The string with ANSI color sequences escaped.
69
+ def uncolorize
70
+ pattern = /\033\[([0-9]+);([0-9]+);([0-9]+)m(.+?)\033\[0m|([^\033]+)/m
71
+ self.scan(pattern).inject("") do |str, match|
72
+ str << (match[3] || match[4])
73
+ end
74
+ end
45
75
  end
@@ -1,4 +1,4 @@
1
1
  module RotorMachine
2
- VERSION_DATA = [1, 2, 0]
2
+ VERSION_DATA = [1, 4, 0]
3
3
  VERSION = VERSION_DATA.join(".")
4
4
  end
@@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.require_paths = ['lib']
22
22
 
23
23
  spec.add_dependency 'tcravit_ruby_lib', '~> 0.2'
24
+ spec.add_dependency 'colorize', '~> 0.8'
24
25
 
25
26
  spec.add_development_dependency 'pry', '~> 0.11'
26
27
  spec.add_development_dependency 'pry-byebug', '~> 3.6'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rotor_machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tammy Cravit
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-07-02 00:00:00.000000000 Z
11
+ date: 2018-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tcravit_ruby_lib
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: colorize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.8'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.8'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: pry
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -196,6 +210,7 @@ files:
196
210
  - lib/rotor_machine/reflector.rb
197
211
  - lib/rotor_machine/rotor.rb
198
212
  - lib/rotor_machine/session.rb
213
+ - lib/rotor_machine/shell.rb
199
214
  - lib/rotor_machine/string_extensions.rb
200
215
  - lib/rotor_machine/version.rb
201
216
  - rotor_machine.gemspec