Class: RotorMachine::Shell
- Inherits:
-
Object
- Object
- RotorMachine::Shell
- Defined in:
- lib/rotor_machine/shell.rb
Overview
Provide an interactive REPL for manipulating a Session to create and interact with a rotor machine.
Usage
shell = RotorMachine::Shell.new()
shell.repl()
Constant Summary collapse
- COMMANDS =
Shell command map. Each command in this list corresponds to a method in the class. The key is the name of the command, and the value is an array listing [description, arguments, aliases].
{ rotor: ["Add a rotor to the machine", "<kind> [position] [step_size]", "add_rotor"], reflector: ["Set the machine's reflector", "<kind> [position]", nil], connect: ["Connect two letters on the plugboard", "<from> <to>", "plug"], disconnect: ["Disconnnect a letter (and its inverse) on the plugboard", "<letter>", "unplug"], default_machine: ["Set the machine to its default configuration", nil, nil], empty_machine: ["Set the machine to its empty configuration", nil, nil], last_result: ["Print the result of the last encipherment operation", nil, nil], configuration: ["Print out the machine's current state", nil, "current_state,config"], set_positions: ["Set the rotor positions", "<positions>", "set_rotors"], clear_rotors: ["Clear the current rotor set", nil, nil], clear_plugboard: ["Clear the current plugboard configuration", nil, nil], help: ["Display help", "[command]", nil], version: ["Display the version of the rotor_machine library", nil, nil], about_prompt: ["Information about the shell prompt", nil, nil], about_rotors: ["List the names of available rotors", nil, nil], about_reflectors: ["List the names of available reflectors", nil, nil], }.freeze
- EXTERNAL_COMMANDS =
{ encipher: ["Encode a message", "[message]", "cipher,encode"], quit: ["Quit the application", nil, "exit"], }.freeze
Class Method Summary collapse
-
.repl(commands = nil) ⇒ Object
Helper for instantiating a new REPL.
Instance Method Summary collapse
-
#about_prompt(arglist) ⇒ Object
Display the about help for the REPL prompt.
- #about_reflectors(arglist) ⇒ Object
- #about_rotors(arglist) ⇒ Object
-
#arity(cmd) ⇒ Object
Return the “arity” (in this case, the number of mandatory arguments) for a command or its alias.
-
#banner ⇒ Object
Display the startup banner for the REPL application.
-
#clear_plugboard(args) ⇒ String
Wrapper around RotorMachine::Session#clear_plugboard.
-
#clear_rotors(args) ⇒ String
Wrapper around RotorMachine::Session#clear_rotors.
-
#configuration(args) ⇒ String
(also: #current_state, #config)
Print out the current configuration of the machine.
-
#connect(arglist) ⇒ String
Wrapper around RotorMachine::Session#connect.
-
#default_machine(args) ⇒ String
Wrapper around RotorMachine::Session#default_machine.
-
#disconnect(arglist) ⇒ String
Wrapper around RotorMachine::Session#disconnect.
-
#empty_machine(args) ⇒ String
Wrapper around RotorMachine::Session#empty_machine.
-
#encipher(arglist) ⇒ String
Wrapper around RotorMachine::Session#encipher.
-
#help(args) ⇒ Object
Print command help.
- #initialize ⇒ Shell constructor
-
#is_internal_verb?(cmd) ⇒ Boolean
Check if
cmdis included on the list of internal command verbs or is an alias for an internal verb. -
#last_result(args) ⇒ String
Wrapper around RotorMachine::Session#last_result.
-
#process_command_input(input) ⇒ Symbol
Process a single command from the REPL and display its output.
-
#readline_prompt ⇒ Object
Build the Readline prompt for the rotor machine.
-
#reflector(arglist) ⇒ String
Wrapper around RotorMachine::Session#reflector.
-
#repl(commands = nil) ⇒ Object
Provide an interactive REPL for manipulating the Rotor Machine.
-
#rotor(arglist) ⇒ String
Wrapper around RotorMachine::Session#rotor.
-
#rotor_state ⇒ Object
Return the selected letters on each of the rotors in the machine.
-
#set_positions(arglist) ⇒ String
(also: #set_rotors)
Wrapper around RotorMachine::Session#set_positions.
- #the_machine ⇒ Object
- #the_session ⇒ Object
-
#verbs ⇒ Object
Return the combined list of command verbs and their arguments/usage.
-
#version(args) ⇒ Object
Print the version number of the rotor_machine.
Constructor Details
#initialize ⇒ Shell
Initialize a new RotorMachine::Shell instance, and the interior RotorMachine::Session object which the shell manages.
52 53 54 55 |
# File 'lib/rotor_machine/shell.rb', line 52 def initialize() @session = RotorMachine::Session.new({}) @session.default_machine end |
Class Method Details
.repl(commands = nil) ⇒ Object
Helper for instantiating a new REPL.
in sequence. This is mainly intended for RSpec testing. If no commands are passed in, the interactive REPL loop (with Readline) will be launched instead.
456 457 458 |
# File 'lib/rotor_machine/shell.rb', line 456 def self.repl(commands=nil) RotorMachine::Shell.new().repl(commands) end |
Instance Method Details
#about_prompt(arglist) ⇒ Object
Display the about help for the REPL prompt. If you redefine the #readline_prompt method, you should also redefine this to reflect the new prompt.
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/rotor_machine/shell.rb', line 307 def about_prompt(arglist) #:nocov: puts "" puts "The prompt for the shell is in the following format:" puts "" puts " [XXX] <YYY> ZZZ>" puts "" puts "The components of the prompt are as follows:" puts "" puts " XXX - the number of rotors mounted to the machine" puts " YYY - the number of connections on the plugboard" puts " ZZZ - the current positions of the rotors" "" #:nocov: end |
#about_reflectors(arglist) ⇒ Object
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/rotor_machine/shell.rb', line 323 def about_reflectors(arglist) #:nocov: puts "" puts "The following reflectors are available with this machine:" puts "" RotorMachine::Reflector.constants.each { |r| puts " #{r.to_s.colorize(color: :light_blue)}" } puts "" puts "To set the reflector for the machine, use a command like this:" puts "" puts " Specify reflector: #{'reflector REFLECTOR_A'.colorize(color: :light_blue)}" puts " Specify reflector/pos: #{'reflector REFLECTOR_A 13'.colorize(color: :light_blue)}" puts "" puts "The REPL does not currently support custom reflectors." puts "" "" #:nocov: end |
#about_rotors(arglist) ⇒ Object
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/rotor_machine/shell.rb', line 341 def about_rotors(arglist) #:nocov: puts "" puts "The following rotors are available with this machine:" puts "" RotorMachine::Rotor.constants.each { |r| puts " #{r.to_s.colorize(color: :light_blue)}" } puts "" puts "To add a rotor to the machine, use a command like this:" puts "" puts " Specify rotor : #{'rotor ROTOR_I'.colorize(color: :light_blue)}" puts " Specify rotor/pos : #{'rotor ROTOR_I 13'.colorize(color: :light_blue)}" puts " #{'rotor ROTOR_I Q'.colorize(color: :light_blue)}" puts " Specify rotor/pos/step: #{'rotor ROTOR_I 13 2'.colorize(color: :light_blue)}" puts "" puts "The REPL does not currently support custom rotors." "" #:nocov: end |
#arity(cmd) ⇒ Object
Return the “arity” (in this case, the number of mandatory arguments) for a command or its alias.
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/rotor_machine/shell.rb', line 262 def arity(cmd) if COMMANDS.keys.include?(cmd) return COMMANDS[cmd][1].split(' ').select { |x| x.start_with?("<") }.count else COMMANDS.each do |k, v| unless v[2].nil? v[2].split(',').each do |a| if a.to_sym == cmd.to_sym return v[1].split(' ').select { |x| x.start_with?("<") }.count end end end end end return 0 end |
#banner ⇒ Object
Display the startup banner for the REPL application.
362 363 364 365 366 367 368 369 370 371 |
# File 'lib/rotor_machine/shell.rb', line 362 def puts "******************************************************************************".colorize(color: :white, background: :magenta) puts "* rotor_machine: Simple simulation of the German WWII Enigma Machine *".colorize(color: :white, background: :magenta) puts "* By Tammy Cravit <[email protected]> *".colorize(color: :white, background: :magenta) puts "* http://github.com/tammycravit/rotor_machine *".colorize(color: :white, background: :magenta) puts "******************************************************************************".colorize(color: :white, background: :magenta) puts "" puts "rotor_machine version #{RotorMachine::VERSION}. Type 'help' for help. <Tab> to complete commands.".colorize(color: :magenta) puts "" end |
#clear_plugboard(args) ⇒ String
Wrapper around RotorMachine::Session#clear_plugboard. Arglist is ignored.
201 202 203 204 |
# File 'lib/rotor_machine/shell.rb', line 201 def clear_plugboard(args) @session.clear_plugboard "Removed all connections from the plugboard" end |
#clear_rotors(args) ⇒ String
Wrapper around RotorMachine::Session#clear_rotors. Arglist is ignored.
192 193 194 195 |
# File 'lib/rotor_machine/shell.rb', line 192 def clear_rotors(args) @session.clear_rotors "Removed all rotors from the machine" end |
#configuration(args) ⇒ String Also known as: current_state, config
Print out the current configuration of the machine. Arglist is ignored.
170 171 172 |
# File 'lib/rotor_machine/shell.rb', line 170 def configuration(args) @session.the_machine.to_s end |
#connect(arglist) ⇒ String
Wrapper around RotorMachine::Session#connect. Expects from and to letters in the arglist.
112 113 114 115 116 117 118 |
# File 'lib/rotor_machine/shell.rb', line 112 def connect(arglist) from = arglist[0] to = arglist[1] @session.connect(from.upcase, to.upcase) "Connected #{from.upcase} to #{to.upcase} on plugboard" end |
#default_machine(args) ⇒ String
Wrapper around RotorMachine::Session#default_machine. Arglist is ignored.
144 145 146 147 |
# File 'lib/rotor_machine/shell.rb', line 144 def default_machine(args) @session.default_machine "Reset machine to default configuration" end |
#disconnect(arglist) ⇒ String
Wrapper around RotorMachine::Session#disconnect. Expects the letter to disconnect in the arglist.
125 126 127 128 129 |
# File 'lib/rotor_machine/shell.rb', line 125 def disconnect(arglist) letter = arglist[0] @session.disconnect(letter.upcase) "Disconnected #{letter} and its inverse on plugboard" end |
#empty_machine(args) ⇒ String
Wrapper around RotorMachine::Session#empty_machine. Arglist is ignored.
153 154 155 156 |
# File 'lib/rotor_machine/shell.rb', line 153 def empty_machine(args) @session.empty_machine "Reset machine to empty configuration" end |
#encipher(arglist) ⇒ String
Wrapper around RotorMachine::Session#encipher. Expects the text to encipher in the arglist.
136 137 138 |
# File 'lib/rotor_machine/shell.rb', line 136 def encipher(arglist) @session.encipher(arglist) end |
#help(args) ⇒ Object
Print command help. If an argument is specified in the first position of the arglist, help about that specific command is printed. If no argument is supplied, a list of commands is printed instead.
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/rotor_machine/shell.rb', line 210 def help(args) if args[0].nil? || args[0].empty? <<~HEREDOC #{verbs.keys.sort.collect { |k| "#{k}#{' ' *(20-k.length)} #{verbs[k][0]}" }.join("\n")} HEREDOC else cmd_info = verbs[args[0].to_sym] <<~HEREDOC #{args[0]}: #{cmd_info[0]} Usage : #{args[0]} #{cmd_info[1]} Aliases: #{cmd_info[2] || "none"} HEREDOC end end |
#is_internal_verb?(cmd) ⇒ Boolean
Check if cmd is included on the list of internal command verbs or is an alias for an internal verb.
247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/rotor_machine/shell.rb', line 247 def is_internal_verb?(cmd) aliases = [] COMMANDS.each do |k, v| unless v[2].nil? v[2].split(',').each { |a| aliases << a.to_sym } end end COMMANDS.keys.include?(cmd) || aliases.include?(cmd) end |
#last_result(args) ⇒ String
Wrapper around RotorMachine::Session#last_result. Arglist is ignored.
162 163 164 |
# File 'lib/rotor_machine/shell.rb', line 162 def last_result(args) @session.last_result end |
#process_command_input(input) ⇒ Symbol
Process a single command from the REPL and display its output.
This method is called for all commands except for “quit” and “exit”, which are processed by #repl directly.
depending on whether the command was recognized and executed.
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/rotor_machine/shell.rb', line 392 def process_command_input(input) begin unless input.empty? toks = input.tokenize cmd = toks.shift.downcase.strip if ['cipher', 'encipher', 'encode'].include?(cmd) = toks.join(' ') puts self.encipher().colorize(color: :white).bold elsif self.is_internal_verb?(cmd.to_sym) begin if toks.length >= arity(cmd.to_sym) if cmd == "last_result" puts self.send(cmd.to_sym, toks).colorize(color: :white).bold else puts self.send(cmd.to_sym, toks).colorize(color: :green) end else puts "Command #{cmd} requires at least #{arity(cmd.to_sym)} arguments".colorize(color: :red) end end else puts "Unknown command: #{cmd}".colorize(color: :light_red).bold end end rescue StandardError => e puts "Rescued exception: #{e}".colorize(color: :red) end end |
#readline_prompt ⇒ Object
Build the Readline prompt for the rotor machine. By default, displays the following pieces of information:
- Count of rotors mounted to the machine
- Count of connections on the plugboard
- Current selected letters on the rotors
If you redefine this method, you should also redefine the #about_prompt method to describe the new prompt correctly.
295 296 297 298 299 300 301 302 |
# File 'lib/rotor_machine/shell.rb', line 295 def readline_prompt [ "[#{@session.the_machine.rotors.count}]".colorize(color: :light_blue), "<#{@session.the_machine.plugboard.connections.count}>".colorize(color: :light_blue), "#{rotor_state}".colorize(color: :light_blue), "> ".colorize(color: :white), ].join(" ") end |
#reflector(arglist) ⇒ String
Wrapper around RotorMachine::Session#reflector. Expects reflector kind and position in the arglist.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/rotor_machine/shell.rb', line 92 def reflector(arglist) kind = arglist[0].to_sym if arglist[1].nil? || (arglist[1].is_a?(String) && arglist[1].empty?) position = 0 elsif arglist[1].is_a?(String) && arglist[1].is_number? position = arglist[1].to_i else position = arglist[1] end @session.reflector(kind, position) "Set reflector of kind #{kind}" end |
#repl(commands = nil) ⇒ Object
Provide an interactive REPL for manipulating the Rotor Machine. Essentially this REPL is an interactive wrapper around the RotorMachine::Session object, with tab completion and command history provided by the Readline library.
in sequence. This is mainly intended for RSpec testing. If no commands are passed in, the interactive REPL loop (with Readline) will be launched instead.
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 |
# File 'lib/rotor_machine/shell.rb', line 430 def repl(commands=nil) Readline.completion_append_character = " " Readline.completion_proc = proc { |s| verbs.keys.grep(/^#{Regexp.escape(s)}/) } if commands.nil? || commands.empty? #:nocov: while input = Readline.readline(readline_prompt, true).strip if ['exit', 'quit'].include?(input.downcase) return end process_command_input(input) end #:nocov: else commands.each { |cmd| process_command_input(cmd) } end end |
#rotor(arglist) ⇒ String
Wrapper around RotorMachine::Session#rotor. Expects rotor kind, position and step size in the arglist.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/rotor_machine/shell.rb', line 64 def rotor(arglist) kind = arglist[0].to_sym if arglist[1].nil? || (arglist[1].is_a?(String) && arglist[1].empty?) position = 0 elsif arglist[1].is_a?(String) && arglist[1].is_number? position = arglist[1].to_i else position = arglist[1] end if arglist[2].nil? || (arglist[2].is_a?(String) && arglist[2].empty?) step_size = 1 elsif arglist[2].is_a?(String) && arglist[2].is_number? step_size = arglist[2].to_i else step_size = 1 end @session.rotor(kind, position, step_size) "Added rotor #{@session.the_machine.rotors.count} of kind #{kind}" end |
#rotor_state ⇒ Object
Return the selected letters on each of the rotors in the machine.
240 241 242 |
# File 'lib/rotor_machine/shell.rb', line 240 def rotor_state @session.the_machine.rotors.collect { |r| r.letters[r.position] }.join("") end |
#set_positions(arglist) ⇒ String Also known as: set_rotors
Wrapper around RotorMachine::Session#set_positions. Expects a string specifying the rotor positions in arglist.
181 182 183 184 185 |
# File 'lib/rotor_machine/shell.rb', line 181 def set_positions(arglist) pos_string = arglist[0] @session.set_positions(pos_string) "Set rotors; rotor state is now #{rotor_state}" end |
#the_machine ⇒ Object
373 374 375 |
# File 'lib/rotor_machine/shell.rb', line 373 def the_machine return @session.the_machine end |
#the_session ⇒ Object
377 378 379 |
# File 'lib/rotor_machine/shell.rb', line 377 def the_session return @session end |
#verbs ⇒ Object
Return the combined list of command verbs and their arguments/usage.
281 282 283 |
# File 'lib/rotor_machine/shell.rb', line 281 def verbs COMMANDS.merge(EXTERNAL_COMMANDS) end |
#version(args) ⇒ Object
Print the version number of the rotor_machine.
232 233 234 |
# File 'lib/rotor_machine/shell.rb', line 232 def version(args) "rotor_machine version #{RotorMachine::VERSION}" end |