rotor_machine 1.2.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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