rotor_machine 1.0.14 → 1.1.1
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 +4 -4
- data/Rakefile +10 -0
- data/bin/resolve_coverage.pl +171 -0
- data/lib/rotor_machine.rb +2 -2
- data/lib/rotor_machine/factory.rb +67 -18
- data/lib/rotor_machine/machine.rb +199 -27
- data/lib/rotor_machine/plugboard.rb +16 -2
- data/lib/rotor_machine/reflector.rb +16 -0
- data/lib/rotor_machine/rotor.rb +19 -1
- data/lib/rotor_machine/string_extensions.rb +10 -0
- data/lib/rotor_machine/version.rb +1 -1
- data/rotor_machine.gemspec +7 -7
- metadata +53 -51
- checksums.yaml.gz.sig +0 -2
- data.tar.gz.sig +0 -2
- data/certs/tammycravit.pem +0 -25
- metadata.gz.sig +0 -0
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b9b724da9efeeb1cb707e1b455a0cca97b9a911ce0b8bf11824e786c044135d6
         | 
| 4 | 
            +
              data.tar.gz: 844f9bc228874dc922369d00a3e2ae6caec69c14c63491961e21fb5b32988873
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 3da1a7af7b05149bb3ba3bbb2e514e548ea8f6b7727a1b35858a9c381372f01c2cec692e95d20cd0a435b7a21d5cfa1e1f3ec659806aba9862a7c2b7ddfa6900
         | 
| 7 | 
            +
              data.tar.gz: 04a238d1393e0b9ca931f6af190e1813ee9251b299c829ba1df7928a1a94fc2479149576aaef3505fac2ce02d5b1ba7afa347ed3400a2fc500137e15fdac5f76
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -5,3 +5,13 @@ require "tcravit_ruby_lib/rake_tasks" | |
| 5 5 | 
             
            RSpec::Core::RakeTask.new(:spec)
         | 
| 6 6 |  | 
| 7 7 | 
             
            task :default => :spec
         | 
| 8 | 
            +
            require 'rake'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            namespace :coverage do
         | 
| 11 | 
            +
              desc "Generate a contextual report from the SimpleCov output"
         | 
| 12 | 
            +
              task :resolve do
         | 
| 13 | 
            +
                project_root = File.expand_path(File.join(File.dirname(__FILE__)))
         | 
| 14 | 
            +
                system("#{project_root}/bin/resolve_coverage.pl #{project_root}/coverage/coverage.txt")
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| @@ -0,0 +1,171 @@ | |
| 1 | 
            +
            #!/usr/bin/perl -w
         | 
| 2 | 
            +
            ############################################################################
         | 
| 3 | 
            +
            # resolve_coverage.pl: Parse the output of a Ruby simplecov_erb text file,
         | 
| 4 | 
            +
            #                      and generate a report with code context for missed
         | 
| 5 | 
            +
            #                      lines.
         | 
| 6 | 
            +
            #
         | 
| 7 | 
            +
            # Version 1.00, 2018-06-15, tammycravit@me.com
         | 
| 8 | 
            +
            ############################################################################
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            use File::Spec;
         | 
| 11 | 
            +
            use File::Basename;
         | 
| 12 | 
            +
            use File::Slurp;
         | 
| 13 | 
            +
            use Term::ANSIColor qw(:constants);
         | 
| 14 | 
            +
            use Cwd;
         | 
| 15 | 
            +
            use Getopt::Long;
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ####################
         | 
| 18 | 
            +
            # CONFIGURATION CONSTANTS
         | 
| 19 | 
            +
            ####################
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            $FILE_PREFIX              = "";
         | 
| 22 | 
            +
            $FILE_SUFFIX              = "";
         | 
| 23 | 
            +
            $EXAMPLE_PREFIX           = "";
         | 
| 24 | 
            +
            $EXAMPLE_SUFFIX           = "\n";
         | 
| 25 | 
            +
            $CONTEXT_SIZE             = 2;
         | 
| 26 | 
            +
            $CONTEXT_UNCOVERED_MARKER = "->";
         | 
| 27 | 
            +
            $CONTEXT_UNCOVERED_COLOR  = RED;
         | 
| 28 | 
            +
            $FILENAME_COLOR           = BLUE;
         | 
| 29 | 
            +
            $BARE_OUTPUT              = undef;
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            $cov_files = 0;
         | 
| 32 | 
            +
            $cov_lines = 0;
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            ####################
         | 
| 35 | 
            +
            # Script begins here
         | 
| 36 | 
            +
            ####################
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            sub generate_file_context
         | 
| 39 | 
            +
            {
         | 
| 40 | 
            +
              my ($filename, $lines_list) = @_;
         | 
| 41 | 
            +
              $filename =~ s@//@/@g;
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              my @lines = @$lines_list;
         | 
| 44 | 
            +
              @content = read_file($filename, chomp => 1);
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              if (length $FILE_PREFIX) { print $FILE_PREFIX; }
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              if ($BARE_OUTPUT)
         | 
| 49 | 
            +
              {
         | 
| 50 | 
            +
                print "*** ", $filename, " ($#content lines)", "\n";
         | 
| 51 | 
            +
              }
         | 
| 52 | 
            +
              else
         | 
| 53 | 
            +
              {
         | 
| 54 | 
            +
                print
         | 
| 55 | 
            +
                  BOLD, WHITE, "*** ", RESET
         | 
| 56 | 
            +
                  BOLD, $FILENAME_COLOR, $filename, RESET,
         | 
| 57 | 
            +
                  WHITE, " ($#content lines)", RESET,
         | 
| 58 | 
            +
                  "\n";
         | 
| 59 | 
            +
              }
         | 
| 60 | 
            +
              print "\n";
         | 
| 61 | 
            +
             | 
| 62 | 
            +
              foreach my $which_line (@lines)
         | 
| 63 | 
            +
              {
         | 
| 64 | 
            +
                $which_line--;
         | 
| 65 | 
            +
                if (length $EXAMPLE_PREFIX) { print $EXAMPLE_PREFIX; }
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                for ($i = $which_line - $CONTEXT_SIZE; $i <= $which_line + $CONTEXT_SIZE; $i++)
         | 
| 68 | 
            +
                {
         | 
| 69 | 
            +
                  if ($i <= $#content)
         | 
| 70 | 
            +
                  {
         | 
| 71 | 
            +
                    my $color = ($i == $which_line ? $CONTEXT_UNCOVERED_COLOR : WHITE);
         | 
| 72 | 
            +
                    my $number_color = CYAN;
         | 
| 73 | 
            +
                    my $reset = RESET;
         | 
| 74 | 
            +
                    if ($BARE_OUTPUT)
         | 
| 75 | 
            +
                    {
         | 
| 76 | 
            +
                      printf "%s%s%s%5.0d:%s %s%s\n",
         | 
| 77 | 
            +
                        "",
         | 
| 78 | 
            +
                        ($i == $which_line ? $CONTEXT_UNCOVERED_MARKER : (" " x length($CONTEXT_UNCOVERED_MARKER))),
         | 
| 79 | 
            +
                        "",
         | 
| 80 | 
            +
                        $i+1,
         | 
| 81 | 
            +
                        "",
         | 
| 82 | 
            +
                        $content[$i],
         | 
| 83 | 
            +
                        "";
         | 
| 84 | 
            +
                    }
         | 
| 85 | 
            +
                    else
         | 
| 86 | 
            +
                    {
         | 
| 87 | 
            +
                      printf "%s%s%s%5.0d:%s %s%s\n",
         | 
| 88 | 
            +
                        $color,
         | 
| 89 | 
            +
                        ($i == $which_line ? $CONTEXT_UNCOVERED_MARKER : (" " x length($CONTEXT_UNCOVERED_MARKER))),
         | 
| 90 | 
            +
                        $number_color,
         | 
| 91 | 
            +
                        $i+1,
         | 
| 92 | 
            +
                        $color,
         | 
| 93 | 
            +
                        $content[$i],
         | 
| 94 | 
            +
                        $reset;
         | 
| 95 | 
            +
                    }
         | 
| 96 | 
            +
                  }
         | 
| 97 | 
            +
                }
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                if (length $EXAMPLE_SUFFIX) { print $EXAMPLE_SUFFIX; }
         | 
| 100 | 
            +
                $cov_lines++;
         | 
| 101 | 
            +
              }
         | 
| 102 | 
            +
             | 
| 103 | 
            +
              if (length $FILE_SUFFIX) { print $FILE_SUFFIX; }
         | 
| 104 | 
            +
              $cov_files++;
         | 
| 105 | 
            +
            }
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            GetOptions(
         | 
| 108 | 
            +
              "file-prefix=s" => \$FILE_PREFIX,
         | 
| 109 | 
            +
              "file-suffix=s" => \$FILE_SUFFIX,
         | 
| 110 | 
            +
              "example-prefix=s" => \$EXAMPLE_PREFIX,
         | 
| 111 | 
            +
              "example-suffix=s" => \$EXAMPLE_SUFFIX,
         | 
| 112 | 
            +
              "context-size=i"   => \$CONTEXT_SIZE,
         | 
| 113 | 
            +
              "context-marker=s" => \$CONTEXT_UNCOVERED_MARKER,
         | 
| 114 | 
            +
              "bare"             => \$BARE_OUTPUT,
         | 
| 115 | 
            +
            );
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            $coverage_file = File::Spec->rel2abs($ARGV[0]);
         | 
| 118 | 
            +
            $project_root  = dirname($coverage_file);
         | 
| 119 | 
            +
            do
         | 
| 120 | 
            +
            {
         | 
| 121 | 
            +
              $project_root  = dirname($project_root);
         | 
| 122 | 
            +
            }
         | 
| 123 | 
            +
            until ((-d "$project_root/coverage") || ($project_root eq '/'));
         | 
| 124 | 
            +
            die "Could not find project root starting from $coverage_file\n" if ($project_root eq "/");
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            unless ($BARE_OUTPUT)
         | 
| 127 | 
            +
            {
         | 
| 128 | 
            +
              print "****************************************************************************\n";
         | 
| 129 | 
            +
              print "* resolve_coverage.pl: Parse a simplecov-erb coverage report and generate  *\n";
         | 
| 130 | 
            +
              print "*                      contextual code snippets for uncovered lines.       *\n";
         | 
| 131 | 
            +
              print "*                                                                          *\n";
         | 
| 132 | 
            +
              print "* Version 1.00, 2018-06-15, Tammy Cravit, tammycravit\@me.com               *\n";
         | 
| 133 | 
            +
              print "****************************************************************************\n";
         | 
| 134 | 
            +
              print "\n";
         | 
| 135 | 
            +
             | 
| 136 | 
            +
              print BOLD, MAGENTA, "==> Coverage file: ", RESET, MAGENTA, $coverage_file, "\n", RESET;
         | 
| 137 | 
            +
              print BOLD, MAGENTA, "==> Project root : ", RESET, MAGENTA, $project_root, "\n", RESET;
         | 
| 138 | 
            +
              print BOLD, MAGENTA, "==> Context lines: ", RESET, MAGENTA, $CONTEXT_SIZE, "\n", RESET;
         | 
| 139 | 
            +
              print "\n";
         | 
| 140 | 
            +
            }
         | 
| 141 | 
            +
             | 
| 142 | 
            +
             | 
| 143 | 
            +
            die "Usage: $0 coverage_file.txt\n" unless (-f $coverage_file);
         | 
| 144 | 
            +
             | 
| 145 | 
            +
            open (COVERAGE, $coverage_file) || die;
         | 
| 146 | 
            +
            while (<COVERAGE>)
         | 
| 147 | 
            +
            {
         | 
| 148 | 
            +
              chomp;
         | 
| 149 | 
            +
              if ($_ =~ m/^(\S+)\b.*?missed:\s?([0123456789,]+)/)
         | 
| 150 | 
            +
              {
         | 
| 151 | 
            +
                $filename = $1;
         | 
| 152 | 
            +
                @lines = split(/,/, $2);
         | 
| 153 | 
            +
                generate_file_context("$project_root/$filename", \@lines);
         | 
| 154 | 
            +
              }
         | 
| 155 | 
            +
            }
         | 
| 156 | 
            +
            close (COVERAGE);
         | 
| 157 | 
            +
             | 
| 158 | 
            +
            if ($BARE_OUTPUT)
         | 
| 159 | 
            +
            {
         | 
| 160 | 
            +
              print "resolve_coverage.pl processed ", $cov_lines, " examples from ", $cov_files,
         | 
| 161 | 
            +
                    " files.\n";
         | 
| 162 | 
            +
            }
         | 
| 163 | 
            +
            else
         | 
| 164 | 
            +
            {
         | 
| 165 | 
            +
              print "Done. Processed ",
         | 
| 166 | 
            +
                    CYAN, $cov_lines, RESET,
         | 
| 167 | 
            +
                    " examples from ",
         | 
| 168 | 
            +
                    CYAN, $cov_files, RESET,
         | 
| 169 | 
            +
                    " files.\n";
         | 
| 170 | 
            +
            }
         | 
| 171 | 
            +
            exit 0;
         | 
    
        data/lib/rotor_machine.rb
    CHANGED
    
    | @@ -3,7 +3,7 @@ $:.unshift File.dirname(__FILE__) | |
| 3 3 | 
             
            require "rotor_machine/version"
         | 
| 4 4 |  | 
| 5 5 | 
             
            Dir[File.join(File.dirname(__FILE__), "rotor_machine", "*.rb")].reject { |x| File.basename(x) == "version.rb" }.each do |f|
         | 
| 6 | 
            -
              require File.join("rotor_machine", File.basename(f)) | 
| 6 | 
            +
              require File.join("rotor_machine", File.basename(f))
         | 
| 7 7 | 
             
            end
         | 
| 8 8 |  | 
| 9 9 | 
             
            ##
         | 
| @@ -21,7 +21,7 @@ end | |
| 21 21 | 
             
            # Many thanks to Kevin Sylvestre, whose {https://ksylvest.com/posts/2015-01-03/the-enigma-machine-using-ruby blog post}
         | 
| 22 22 | 
             
            # helped me understand some aspects of the internal workings of the Enigma
         | 
| 23 23 | 
             
            # and how the signals flowed through the pieces of the machine.
         | 
| 24 | 
            -
            # | 
| 24 | 
            +
            #
         | 
| 25 25 | 
             
            #@author Tammy Cravit <tammycravit@me.com>
         | 
| 26 26 | 
             
            module RotorMachine
         | 
| 27 27 | 
             
            end
         | 
| @@ -6,15 +6,52 @@ module RotorMachine | |
| 6 6 | 
             
              module Factory
         | 
| 7 7 | 
             
                extend self
         | 
| 8 8 |  | 
| 9 | 
            +
                ##
         | 
| 10 | 
            +
                # Generates a default-configuration RotorMachine, with the following
         | 
| 11 | 
            +
                # state:
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # - Rotors I, II, III, each set to A and configured to advance a single
         | 
| 14 | 
            +
                #   step at a time
         | 
| 15 | 
            +
                # - Reflector A
         | 
| 16 | 
            +
                # - An empty plugboard with no connections
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # The {RotorMachine::Machine#default_machine} method calls this factory
         | 
| 19 | 
            +
                # method, and is maintained there for backward compatibility.
         | 
| 20 | 
            +
                def default_machine
         | 
| 21 | 
            +
                  m = build_machine(
         | 
| 22 | 
            +
                    rotors: [:ROTOR_I, :ROTOR_II, :ROTOR_III],
         | 
| 23 | 
            +
                    reflector: build_reflector(reflector_kind: :REFLECTOR_A)
         | 
| 24 | 
            +
                    )
         | 
| 25 | 
            +
                  m.set_rotors("AAA")
         | 
| 26 | 
            +
                  return m
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                ##
         | 
| 30 | 
            +
                # Generates an empty-configuration RotorMachine, with the following
         | 
| 31 | 
            +
                # state:
         | 
| 32 | 
            +
                #
         | 
| 33 | 
            +
                # - No rotors
         | 
| 34 | 
            +
                # - No reflector
         | 
| 35 | 
            +
                # - An empty plugboard with no connections
         | 
| 36 | 
            +
                #
         | 
| 37 | 
            +
                # A RotorMachine in this state will raise an {ArgumentError} until you
         | 
| 38 | 
            +
                # outfit it with at least one rotor and a reflector.
         | 
| 39 | 
            +
                #
         | 
| 40 | 
            +
                # The {RotorMachine::Machine#default_machine} method calls this factory
         | 
| 41 | 
            +
                # method, and is maintained there for backward compatibility.
         | 
| 42 | 
            +
                def empty_machine
         | 
| 43 | 
            +
                  return build_machine()
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 9 46 | 
             
                ##
         | 
| 10 47 | 
             
                # Build a new {Rotor} and return it.
         | 
| 11 48 | 
             
                #
         | 
| 12 | 
            -
                # The options hash for this method can accept the following named | 
| 49 | 
            +
                # The options hash for this method can accept the following named
         | 
| 13 50 | 
             
                # arguments:
         | 
| 14 51 | 
             
                #
         | 
| 15 52 | 
             
                # *:rotor_kind* - The type of rotor to create. Should be a symbol matching
         | 
| 16 53 | 
             
                # a rotor type constant in the {RotorMachine::Rotor} class,
         | 
| 17 | 
            -
                # or a 26-character string giving the letter sequence for | 
| 54 | 
            +
                # or a 26-character string giving the letter sequence for
         | 
| 18 55 | 
             
                # the rotor. Defaults to *:ROTOR_1* if not specified.
         | 
| 19 56 | 
             
                #
         | 
| 20 57 | 
             
                # *:initial_position* - The initial position of the rotor (0-based
         | 
| @@ -28,16 +65,19 @@ module RotorMachine | |
| 28 65 | 
             
                #                       rotor.
         | 
| 29 66 | 
             
                # @return The newly-built rotor.
         | 
| 30 67 | 
             
                def build_rotor(options={})
         | 
| 31 | 
            -
                  rotor_kind         = options.fetch(:rotor_kind, | 
| 68 | 
            +
                  rotor_kind         = options.fetch(:rotor_kind, nil)
         | 
| 32 69 | 
             
                  initial_position   = options.fetch(:initial_position,   0)
         | 
| 33 70 | 
             
                  step_size          = options.fetch(:step_size,          1)
         | 
| 34 71 |  | 
| 35 72 | 
             
                  rotor_alphabet = nil
         | 
| 73 | 
            +
                  if rotor_kind.nil?
         | 
| 74 | 
            +
                    raise ArgumentError, "Rotor kind not specified"
         | 
| 75 | 
            +
                  end
         | 
| 36 76 |  | 
| 37 | 
            -
                  if  | 
| 77 | 
            +
                  if rotor_kind.is_a? Symbol
         | 
| 38 78 | 
             
                    raise ArgumentError, "Invalid rotor kind (symbol #{rotor_kind} not found)" unless RotorMachine::Rotor.constants.include?(rotor_kind)
         | 
| 39 79 | 
             
                    rotor_alphabet = RotorMachine::Rotor.const_get(rotor_kind)
         | 
| 40 | 
            -
                  elsif  | 
| 80 | 
            +
                  elsif rotor_kind.is_a? String
         | 
| 41 81 | 
             
                    raise ArgumentError, "Invalid rotor kind (invalid length)" unless rotor_kind.length == 26
         | 
| 42 82 | 
             
                    rotor_alphabet = rotor_kind.upcase
         | 
| 43 83 | 
             
                  else
         | 
| @@ -66,12 +106,12 @@ module RotorMachine | |
| 66 106 | 
             
                ##
         | 
| 67 107 | 
             
                # Build a new {Reflector} and return it.
         | 
| 68 108 | 
             
                #
         | 
| 69 | 
            -
                # The options hash for this method can accept the following named | 
| 109 | 
            +
                # The options hash for this method can accept the following named
         | 
| 70 110 | 
             
                # arguments:
         | 
| 71 111 | 
             
                #
         | 
| 72 112 | 
             
                # *:reflector_kind* - The type of reflector to create. Should be a symbol matching
         | 
| 73 113 | 
             
                # a reflector type constant in the {RotorMachine::Reflector} class,
         | 
| 74 | 
            -
                # or a 26-character string giving the letter sequence for | 
| 114 | 
            +
                # or a 26-character string giving the letter sequence for
         | 
| 75 115 | 
             
                # the reflector. Defaults to *:REFLECTOR_A* if not specified.
         | 
| 76 116 | 
             
                #
         | 
| 77 117 | 
             
                # *:initial_position* - The initial position of the reflector (0-based
         | 
| @@ -82,21 +122,27 @@ module RotorMachine | |
| 82 122 | 
             
                # reflector.
         | 
| 83 123 | 
             
                # @return The newly-built reflector.
         | 
| 84 124 | 
             
                def build_reflector(options={})
         | 
| 85 | 
            -
                  reflector_kind     = options.fetch(:reflector_kind, | 
| 86 | 
            -
                  initial_position   = options.fetch(:initial_position,  | 
| 125 | 
            +
                  reflector_kind     = options.fetch(:reflector_kind, nil)
         | 
| 126 | 
            +
                  initial_position   = options.fetch(:initial_position, nil)
         | 
| 87 127 |  | 
| 88 128 | 
             
                  reflector_alphabet = nil
         | 
| 129 | 
            +
                  if reflector_kind.nil?
         | 
| 130 | 
            +
                    raise ArgumentError, "Reflector type not specified"
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
                  if initial_position.nil?
         | 
| 133 | 
            +
                    initial_position = 0
         | 
| 134 | 
            +
                  end
         | 
| 89 135 |  | 
| 90 | 
            -
                  if  | 
| 136 | 
            +
                  if reflector_kind.is_a? Symbol
         | 
| 91 137 | 
             
                    unless RotorMachine::Reflector.constants.include?(reflector_kind)
         | 
| 92 | 
            -
                      raise ArgumentError, "Invalid reflector kind (symbol #{reflector_kind} not found)" | 
| 138 | 
            +
                      raise ArgumentError, "Invalid reflector kind (symbol #{reflector_kind} not found)"
         | 
| 93 139 | 
             
                    end
         | 
| 94 140 | 
             
                    reflector_alphabet = RotorMachine::Reflector.const_get(reflector_kind)
         | 
| 95 | 
            -
                  elsif  | 
| 141 | 
            +
                  elsif reflector_kind.is_a? String
         | 
| 96 142 | 
             
                    raise ArgumentError, "Invalid reflector kind (invalid length)" unless reflector_kind.length == 26
         | 
| 97 143 | 
             
                    reflector_alphabet = reflector_kind.upcase
         | 
| 98 144 | 
             
                  else
         | 
| 99 | 
            -
                    raise ArgumentError, "Invalid reflector kind (invalid type | 
| 145 | 
            +
                    raise ArgumentError, "Invalid reflector kind (invalid type)"
         | 
| 100 146 | 
             
                  end
         | 
| 101 147 |  | 
| 102 148 | 
             
                  if initial_position.is_a? Numeric
         | 
| @@ -107,7 +153,7 @@ module RotorMachine | |
| 107 153 | 
             
                    end
         | 
| 108 154 | 
             
                    initial_position = reflector_alphabet.index(initial_position)
         | 
| 109 155 | 
             
                  else
         | 
| 110 | 
            -
                    raise ArgumentError, "Invalid position (invalid type | 
| 156 | 
            +
                    raise ArgumentError, "Invalid position (invalid type)"
         | 
| 111 157 | 
             
                  end
         | 
| 112 158 |  | 
| 113 159 | 
             
                  return RotorMachine::Reflector.new(reflector_alphabet, initial_position)
         | 
| @@ -149,19 +195,22 @@ module RotorMachine | |
| 149 195 |  | 
| 150 196 | 
             
                  m = RotorMachine::Machine.new()
         | 
| 151 197 | 
             
                  rotors.each do |r|
         | 
| 152 | 
            -
                    if r. | 
| 198 | 
            +
                    if r.is_a? RotorMachine::Rotor
         | 
| 153 199 | 
             
                      m.rotors << r
         | 
| 154 | 
            -
                    elsif r. | 
| 200 | 
            +
                    elsif r.is_a? Symbol
         | 
| 155 201 | 
             
                      m.rotors << RotorMachine::Factory.build_rotor(rotor_kind: r)
         | 
| 202 | 
            +
                    else
         | 
| 203 | 
            +
                      raise ArgumentError, "#{r} is not a rotor or a rotor kind symbol"
         | 
| 156 204 | 
             
                    end
         | 
| 157 205 | 
             
                  end
         | 
| 158 206 |  | 
| 159 207 | 
             
                  unless reflector.nil?
         | 
| 160 | 
            -
                    if reflector. | 
| 208 | 
            +
                    if reflector.is_a? Symbol
         | 
| 161 209 | 
             
                      m.reflector = RotorMachine::Factory.build_reflector(reflector_kind: reflector)
         | 
| 162 | 
            -
                    elsif reflector. | 
| 210 | 
            +
                    elsif reflector.is_a? RotorMachine::Reflector
         | 
| 163 211 | 
             
                      m.reflector = reflector
         | 
| 164 212 | 
             
                    else
         | 
| 213 | 
            +
                      raise ArgumentError, "#{reflector} is not a reflector or reflector kind symbol"
         | 
| 165 214 | 
             
                    end
         | 
| 166 215 | 
             
                  end
         | 
| 167 216 |  | 
| @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            module RotorMachine | 
| 1 | 
            +
            module RotorMachine
         | 
| 2 2 | 
             
              ##
         | 
| 3 3 | 
             
              # The {RotorMachine::Machine} class serves as the entrypoint and orchestrator
         | 
| 4 4 | 
             
              # for an Enigma machine.
         | 
| @@ -43,7 +43,7 @@ module RotorMachine | |
| 43 43 | 
             
              # On a physical Enigma machine, the electrical signal from a keypress is
         | 
| 44 44 | 
             
              # routed through the plugboard, then through each of the rotors in sequence
         | 
| 45 45 | 
             
              # from left to right. The signal then passes through the reflector (where it
         | 
| 46 | 
            -
              # is transposed again), then back through the rotors in reverse order, and | 
| 46 | 
            +
              # is transposed again), then back through the rotors in reverse order, and
         | 
| 47 47 | 
             
              # finally back through the plugboard a second time before being displayed on
         | 
| 48 48 | 
             
              # the light grid and/or printer.
         | 
| 49 49 | 
             
              #
         | 
| @@ -59,7 +59,7 @@ module RotorMachine | |
| 59 59 | 
             
              # to help break the Enigma's encryption in World War II.
         | 
| 60 60 | 
             
              #
         | 
| 61 61 | 
             
              # == Usage
         | 
| 62 | 
            -
              # | 
| 62 | 
            +
              #
         | 
| 63 63 | 
             
              # To use the RotorMachine Enigma machine, you need to perform the following
         | 
| 64 64 | 
             
              # steps:
         | 
| 65 65 | 
             
              #
         | 
| @@ -73,12 +73,24 @@ module RotorMachine | |
| 73 73 | 
             
              # method to encode/decode, and {#set_rotors} to reset the machine state.
         | 
| 74 74 | 
             
              #
         | 
| 75 75 | 
             
              # The {#default_machine} and {#empty_machine} class methods are shortcut
         | 
| 76 | 
            -
              # factory methods whcih set up, respectively, a fully configured machine | 
| 76 | 
            +
              # factory methods whcih set up, respectively, a fully configured machine
         | 
| 77 77 | 
             
              # with a default set of rotors and reflector, and an empty machine with
         | 
| 78 78 | 
             
              # no rotors or reflector.
         | 
| 79 79 | 
             
              class Machine
         | 
| 80 80 | 
             
                attr_accessor :rotors, :reflector, :plugboard
         | 
| 81 81 |  | 
| 82 | 
            +
                ##
         | 
| 83 | 
            +
                # Initialize a RotorMachine object.
         | 
| 84 | 
            +
                #
         | 
| 85 | 
            +
                # This object won't be usable until you add rotors, a reflector and a
         | 
| 86 | 
            +
                # plugboard. Using the {#default_machine} and {#empty_machine} helper class
         | 
| 87 | 
            +
                # methods is the preferred way to initialize functioning machines.
         | 
| 88 | 
            +
                def initialize()
         | 
| 89 | 
            +
                  @rotors = []
         | 
| 90 | 
            +
                  @reflector = nil
         | 
| 91 | 
            +
                  @plugboard = nil
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 82 94 | 
             
                ##
         | 
| 83 95 | 
             
                # Generates a default-configuration RotorMachine, with the following
         | 
| 84 96 | 
             
                # state:
         | 
| @@ -87,13 +99,12 @@ module RotorMachine | |
| 87 99 | 
             
                #   step at a time
         | 
| 88 100 | 
             
                # - Reflector A
         | 
| 89 101 | 
             
                # - An empty plugboard with no connections
         | 
| 102 | 
            +
                #
         | 
| 103 | 
            +
                # This method is just a proxy for the equivalently-named factory method in the
         | 
| 104 | 
            +
                # {RotorMachine::Factory} class, and is maintained here for backward
         | 
| 105 | 
            +
                # compatibility.
         | 
| 90 106 | 
             
                def self.default_machine
         | 
| 91 | 
            -
                   | 
| 92 | 
            -
                    rotors: [:ROTOR_I, :ROTOR_II, :ROTOR_III],
         | 
| 93 | 
            -
                    reflector: RotorMachine::Factory::build_reflector(reflector_kind: :REFLECTOR_A)
         | 
| 94 | 
            -
                  )
         | 
| 95 | 
            -
                  m.set_rotors("AAA")
         | 
| 96 | 
            -
                  return m
         | 
| 107 | 
            +
                  RotorMachine::Factory.default_machine
         | 
| 97 108 | 
             
                end
         | 
| 98 109 |  | 
| 99 110 | 
             
                ##
         | 
| @@ -106,20 +117,12 @@ module RotorMachine | |
| 106 117 | 
             
                #
         | 
| 107 118 | 
             
                # A RotorMachine in this state will raise an {ArgumentError} until you
         | 
| 108 119 | 
             
                # outfit it with at least one rotor and a reflector.
         | 
| 109 | 
            -
                def self.empty_machine
         | 
| 110 | 
            -
                  RotorMachine::Factory.build_machine()
         | 
| 111 | 
            -
                end
         | 
| 112 | 
            -
             | 
| 113 | 
            -
                ##
         | 
| 114 | 
            -
                # Initialize a RotorMachine object.
         | 
| 115 120 | 
             
                #
         | 
| 116 | 
            -
                # This  | 
| 117 | 
            -
                #  | 
| 118 | 
            -
                #  | 
| 119 | 
            -
                def  | 
| 120 | 
            -
                   | 
| 121 | 
            -
                  @reflector = nil
         | 
| 122 | 
            -
                  @plugboard = nil
         | 
| 121 | 
            +
                # This method is just a proxy for the equivalently-named factory method in the
         | 
| 122 | 
            +
                # {RotorMachine::Factory} class, and is maintained here for backward
         | 
| 123 | 
            +
                # compatibility.
         | 
| 124 | 
            +
                def self.empty_machine
         | 
| 125 | 
            +
                  RotorMachine::Factory.empty_machine()
         | 
| 123 126 | 
             
                end
         | 
| 124 127 |  | 
| 125 128 | 
             
                ##
         | 
| @@ -140,8 +143,8 @@ module RotorMachine | |
| 140 143 | 
             
                end
         | 
| 141 144 |  | 
| 142 145 | 
             
                ##
         | 
| 143 | 
            -
                # Coordinate the stepping of the set of rotors after a character is | 
| 144 | 
            -
                # enciphered. | 
| 146 | 
            +
                # Coordinate the stepping of the set of rotors after a character is
         | 
| 147 | 
            +
                # enciphered.
         | 
| 145 148 | 
             
                def step_rotors
         | 
| 146 149 | 
             
                  @rotors.reverse.each do |rotor|
         | 
| 147 150 | 
             
                    rotor.step
         | 
| @@ -156,7 +159,7 @@ module RotorMachine | |
| 156 159 | 
             
                # This is a helper method to avoid having to manipulate the rotor
         | 
| 157 160 | 
             
                # positions individually. Starting with the leftmost rotor, each
         | 
| 158 161 | 
             
                # character from this string is used to set the position of one
         | 
| 159 | 
            -
                # rotor. | 
| 162 | 
            +
                # rotor.
         | 
| 160 163 | 
             
                #
         | 
| 161 164 | 
             
                # If the string is longer than the number of rotors, the extra
         | 
| 162 165 | 
             
                # values (to the right) are ignored. If it's shorter, the values of
         | 
| @@ -185,7 +188,7 @@ module RotorMachine | |
| 185 188 | 
             
                end
         | 
| 186 189 |  | 
| 187 190 | 
             
                ##
         | 
| 188 | 
            -
                # Encipher a single character. | 
| 191 | 
            +
                # Encipher a single character.
         | 
| 189 192 | 
             
                #
         | 
| 190 193 | 
             
                # Used by {#encipher} to walk a single character of text through the
         | 
| 191 194 | 
             
                # signal path of all components of the machine.
         | 
| @@ -215,5 +218,174 @@ module RotorMachine | |
| 215 218 | 
             
                  end
         | 
| 216 219 | 
             
                  ec
         | 
| 217 220 | 
             
                end
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                ##
         | 
| 223 | 
            +
                # Create a Ruby hash containing a snapshot of the current machine state.
         | 
| 224 | 
            +
                #
         | 
| 225 | 
            +
                # The hash returned by this method contains enough information to capture
         | 
| 226 | 
            +
                # the current internal state of the machine. Although you can invoke it
         | 
| 227 | 
            +
                # directly if you want to, it is primarily intended to be accessed via
         | 
| 228 | 
            +
                # the {#save_machine_state_to} and {#load_machine_state_from} methods,
         | 
| 229 | 
            +
                # which save and load machine state to YAML files.
         | 
| 230 | 
            +
                #
         | 
| 231 | 
            +
                # @return [Hash] A Hash representing the internal state of the machine.
         | 
| 232 | 
            +
                def machine_state
         | 
| 233 | 
            +
                  machine_state = {}
         | 
| 234 | 
            +
                  machine_state[:serialization_version] = RotorMachine::VERSION_DATA[0]
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                  machine_state[:rotors] = []
         | 
| 237 | 
            +
                  self.rotors.each do |r|
         | 
| 238 | 
            +
                    rstate = {
         | 
| 239 | 
            +
                      kind: r.rotor_kind_name,
         | 
| 240 | 
            +
                      position: r.position,
         | 
| 241 | 
            +
                      step_size: r.step_size
         | 
| 242 | 
            +
                    }
         | 
| 243 | 
            +
                    if r.rotor_kind_name == :CUSTOM
         | 
| 244 | 
            +
                      rstate[:letters] = r.rotor_kind
         | 
| 245 | 
            +
                    end
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                    machine_state[:rotors] << rstate
         | 
| 248 | 
            +
                  end
         | 
| 249 | 
            +
                  machine_state[:reflector] = {
         | 
| 250 | 
            +
                    kind: self.reflector.reflector_kind_name,
         | 
| 251 | 
            +
                    position: self.reflector.position
         | 
| 252 | 
            +
                  }
         | 
| 253 | 
            +
                  if (self.reflector.reflector_kind_name == :CUSTOM)
         | 
| 254 | 
            +
                    machine_state[:reflector][:letters] = self.reflector.letters
         | 
| 255 | 
            +
                  end
         | 
| 256 | 
            +
             | 
| 257 | 
            +
                  machine_state[:plugboard] = {
         | 
| 258 | 
            +
                    connections: self.plugboard.connections.clone
         | 
| 259 | 
            +
                  }
         | 
| 260 | 
            +
                  return machine_state
         | 
| 261 | 
            +
                end
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                ##
         | 
| 264 | 
            +
                # Write the internal machine state to a YAML file.
         | 
| 265 | 
            +
                #
         | 
| 266 | 
            +
                # The generated YAML file can be loaded using the #{load_machine_state_from}
         | 
| 267 | 
            +
                # method to restore a saved machine state.
         | 
| 268 | 
            +
                #
         | 
| 269 | 
            +
                # @param filepath [String] The path to the YAML file to which the machine
         | 
| 270 | 
            +
                # state should be saved.
         | 
| 271 | 
            +
                # @return [Boolean] True if the save operation completed successfully, false
         | 
| 272 | 
            +
                # if an error was raised.
         | 
| 273 | 
            +
                def save_machine_state_to(filepath)
         | 
| 274 | 
            +
                  begin
         | 
| 275 | 
            +
                    File.open(filepath, "w") do |f|
         | 
| 276 | 
            +
                      f.puts machine_state.to_yaml
         | 
| 277 | 
            +
                    end
         | 
| 278 | 
            +
                    return true
         | 
| 279 | 
            +
                  rescue
         | 
| 280 | 
            +
                    return false
         | 
| 281 | 
            +
                  end
         | 
| 282 | 
            +
                end
         | 
| 283 | 
            +
             | 
| 284 | 
            +
                ##
         | 
| 285 | 
            +
                # Read the internal machine state from a YAML file.
         | 
| 286 | 
            +
                #
         | 
| 287 | 
            +
                # The YAML file can be created using the #{save_machine_state_to} method to
         | 
| 288 | 
            +
                # save the machine state of an existing {RotorMachine::Machine} object.
         | 
| 289 | 
            +
                #
         | 
| 290 | 
            +
                # The internal state is captured as is, so if you save the state from a machine
         | 
| 291 | 
            +
                # that's not validly configured (no rotors, no reflector, etc.), the
         | 
| 292 | 
            +
                # reconstituted machine will also have an invalid state.
         | 
| 293 | 
            +
                #
         | 
| 294 | 
            +
                # @param filepath [String] The path to the YAML file to which the machine
         | 
| 295 | 
            +
                # state should be saved.
         | 
| 296 | 
            +
                def load_machine_state_from(filepath)
         | 
| 297 | 
            +
                  raise ArgumentError, "File path \"#{filepath}\" not found!" unless File.exist?(filepath)
         | 
| 298 | 
            +
                  c = YAML.load(File.open(filepath))
         | 
| 299 | 
            +
                  self.set_machine_config_from(c)
         | 
| 300 | 
            +
                  return true
         | 
| 301 | 
            +
                end
         | 
| 302 | 
            +
             | 
| 303 | 
            +
                ##
         | 
| 304 | 
            +
                # Create a new {RotorMachine::Machine} from a YAML configuration file.
         | 
| 305 | 
            +
                #
         | 
| 306 | 
            +
                # This class method is a one-step shortcut for creating an empty {RotorMachine::Machine}
         | 
| 307 | 
            +
                # and then loading its machine state.
         | 
| 308 | 
            +
                #
         | 
| 309 | 
            +
                # @param config [Hash] A configuration hash for the new machine, such as a config
         | 
| 310 | 
            +
                # hash generated by {#machine_state}.
         | 
| 311 | 
            +
                # @return [RotorMachine::Machine] A new {RotorMachine::Machine} created from the
         | 
| 312 | 
            +
                # supplied config hash.
         | 
| 313 | 
            +
                def self.from_yaml(config)
         | 
| 314 | 
            +
                  unless config.keys.include?(:serialization_version)
         | 
| 315 | 
            +
                    raise ArgumentError, "Serialization Data Version Mismatch"
         | 
| 316 | 
            +
                  end
         | 
| 317 | 
            +
                  unless config[:serialization_version].is_a?(Numeric)
         | 
| 318 | 
            +
                    raise ArgumentError, "Serialization Data Version Mismatch"
         | 
| 319 | 
            +
                  end
         | 
| 320 | 
            +
                  if (config[:serialization_version] > RotorMachine::VERSION_DATA[0]) || (config[:serialization_version] < 1)
         | 
| 321 | 
            +
                    raise ArgumentError, "Serialization Data Version Mismatch"
         | 
| 322 | 
            +
                  end
         | 
| 323 | 
            +
             | 
| 324 | 
            +
                  m = self.empty_machine
         | 
| 325 | 
            +
                  m.set_machine_config_from(config)
         | 
| 326 | 
            +
                  return m
         | 
| 327 | 
            +
                end
         | 
| 328 | 
            +
             | 
| 329 | 
            +
                ##
         | 
| 330 | 
            +
                # Set the state of the machine based on values in a config hash.
         | 
| 331 | 
            +
                #
         | 
| 332 | 
            +
                # Any config hash (such as that generated by {#machine_state}) can be provided
         | 
| 333 | 
            +
                # as an argument, but this method is primarily intended to be accessed by the
         | 
| 334 | 
            +
                # {#from_yaml} and {#load_config_state_from} methods to deserialize a machine
         | 
| 335 | 
            +
                # state hash.
         | 
| 336 | 
            +
                #
         | 
| 337 | 
            +
                # @param config [Hash] The configuration hash describing the state of the
         | 
| 338 | 
            +
                # {RotorMachine::Machine}.
         | 
| 339 | 
            +
                # @return [RotorMachine::Machine] The {RotorMachine::Machine} which was just
         | 
| 340 | 
            +
                # configured.  def set_machine_config_from(config)
         | 
| 341 | 
            +
                def set_machine_config_from(config)
         | 
| 342 | 
            +
                  @rotors = []
         | 
| 343 | 
            +
                  @reflector = nil
         | 
| 344 | 
            +
                  @plugboard = RotorMachine::Plugboard.new()
         | 
| 345 | 
            +
             | 
| 346 | 
            +
                  # Create rotors
         | 
| 347 | 
            +
                  config[:rotors].each do |rs|
         | 
| 348 | 
            +
                    if rs[:kind] == :CUSTOM
         | 
| 349 | 
            +
                      r = RotorMachine::Rotor.new(rs[:letters], rs[:position], rs[:step_size])
         | 
| 350 | 
            +
                    else
         | 
| 351 | 
            +
                      letters = RotorMachine::Rotor.const_get(rs[:kind])
         | 
| 352 | 
            +
                      r = RotorMachine::Rotor.new(letters, rs[:position], rs[:step_size])
         | 
| 353 | 
            +
                    end
         | 
| 354 | 
            +
                    @rotors << r
         | 
| 355 | 
            +
                  end
         | 
| 356 | 
            +
             | 
| 357 | 
            +
                  # Create reflector
         | 
| 358 | 
            +
                  if config[:reflector][:kind] == :CUSTOM
         | 
| 359 | 
            +
                    letters = config[:reflector][:letters]
         | 
| 360 | 
            +
                  else
         | 
| 361 | 
            +
                    letters = RotorMachine::Reflector.const_get(config[:reflector][:kind])
         | 
| 362 | 
            +
                  end
         | 
| 363 | 
            +
                  @reflector = RotorMachine::Reflector.new(letters, config[:reflector][:position])
         | 
| 364 | 
            +
             | 
| 365 | 
            +
                  # Plugboard mappings
         | 
| 366 | 
            +
                  config[:plugboard][:connections].keys.each do |l|
         | 
| 367 | 
            +
                    unless @plugboard.connected?(l)
         | 
| 368 | 
            +
                      @plugboard.connect(l, config[:plugboard][:connections][l])
         | 
| 369 | 
            +
                    end
         | 
| 370 | 
            +
                  end
         | 
| 371 | 
            +
             | 
| 372 | 
            +
                  return self
         | 
| 373 | 
            +
                end
         | 
| 374 | 
            +
             | 
| 375 | 
            +
                ##
         | 
| 376 | 
            +
                # Compare another {RotorMachine::Machine} instance to this one.
         | 
| 377 | 
            +
                #
         | 
| 378 | 
            +
                # Returns true if the provided {RotorMachine::Machine} has the same
         | 
| 379 | 
            +
                # configuration as this one, and false otherwise.
         | 
| 380 | 
            +
                #
         | 
| 381 | 
            +
                # @param another_machine [RotorMachine::Machine] The Machine to compare to
         | 
| 382 | 
            +
                # this one.
         | 
| 383 | 
            +
                # @return [Boolean] True if the machines have identical configuration, false
         | 
| 384 | 
            +
                # otherwise.
         | 
| 385 | 
            +
                def ==(another_machine)
         | 
| 386 | 
            +
                  @rotors == another_machine.rotors &&
         | 
| 387 | 
            +
                    @reflector == another_machine.reflector &&
         | 
| 388 | 
            +
                    @plugboard == another_machine.plugboard
         | 
| 389 | 
            +
                end
         | 
| 218 390 | 
             
              end
         | 
| 219 391 | 
             
            end
         | 
| @@ -16,8 +16,9 @@ module RotorMachine | |
| 16 16 | 
             
              #   from B to A.
         | 
| 17 17 | 
             
              # - A letter cannot be connected to itself.
         | 
| 18 18 | 
             
              class Plugboard
         | 
| 19 | 
            +
                attr_reader :connections
         | 
| 19 20 |  | 
| 20 | 
            -
                ## | 
| 21 | 
            +
                ##
         | 
| 21 22 | 
             
                # Create a new, empty Plugboard object.
         | 
| 22 23 | 
             
                #
         | 
| 23 24 | 
             
                # By default, no letters are connected in the plugboard, and all input
         | 
| @@ -89,12 +90,25 @@ module RotorMachine | |
| 89 90 | 
             
                  @connections.keys.include?(letter.upcase)
         | 
| 90 91 | 
             
                end
         | 
| 91 92 |  | 
| 92 | 
            -
                ## | 
| 93 | 
            +
                ##
         | 
| 93 94 | 
             
                # Produce a human-readable representation of the #{Plugboard}'s state.
         | 
| 94 95 | 
             
                #
         | 
| 95 96 | 
             
                # @return [String] A description of the current state.
         | 
| 96 97 | 
             
                def to_s
         | 
| 97 98 | 
             
                  "a RotorMachine::Plugboard with connections: #{@connections.to_s}"
         | 
| 98 99 | 
             
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                ##
         | 
| 102 | 
            +
                # Compare this {RotorMachine::Plugboard} to another one.
         | 
| 103 | 
            +
                #
         | 
| 104 | 
            +
                # Returns True if the configuration of the supplied {RotorMachine::Plugboard}
         | 
| 105 | 
            +
                # matches this one, false otherwise.
         | 
| 106 | 
            +
                #
         | 
| 107 | 
            +
                # @param another_plugboard [RotorMachine::Plugboard] The Plugboard to compare to
         | 
| 108 | 
            +
                # this one.
         | 
| 109 | 
            +
                # @return [Boolean] True if the configurations match, false otherwise.
         | 
| 110 | 
            +
                def ==(another_plugboard)
         | 
| 111 | 
            +
                  @connections == another_plugboard.connections
         | 
| 112 | 
            +
                end
         | 
| 99 113 | 
             
              end
         | 
| 100 114 | 
             
            end
         | 
| @@ -58,6 +58,8 @@ module RotorMachine | |
| 58 58 | 
             
                #        Because the reflector does not rotate, this is essentially just
         | 
| 59 59 | 
             
                #        an additional permutation factor for the encipherment.
         | 
| 60 60 | 
             
                def initialize(selected_reflector, start_position = 0)
         | 
| 61 | 
            +
                  raise ArgumentError, "Initialization string contains duplicate letters" unless selected_reflector.is_uniq?
         | 
| 62 | 
            +
             | 
| 61 63 | 
             
                  @letters = selected_reflector.chars.freeze
         | 
| 62 64 | 
             
                  @alphabet = ALPHABET.chars.freeze
         | 
| 63 65 | 
             
                  @position = start_position
         | 
| @@ -139,5 +141,19 @@ module RotorMachine | |
| 139 141 | 
             
                def to_s
         | 
| 140 142 | 
             
                  "a RotorMachine::Reflector of type '#{self.reflector_kind_name.to_s}'"
         | 
| 141 143 | 
             
                end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                ##
         | 
| 146 | 
            +
                # Compare this {RotorMachine::Reflector} to another one.
         | 
| 147 | 
            +
                #
         | 
| 148 | 
            +
                # Returns True if the configuration of the supplied {RotorMachine::Reflector}
         | 
| 149 | 
            +
                # matches this one, false otherwise.
         | 
| 150 | 
            +
                #
         | 
| 151 | 
            +
                # @param another_reflector [RotorMachine::Reflector] The Reflector to compare 
         | 
| 152 | 
            +
                # to this one.
         | 
| 153 | 
            +
                # @return [Boolean] True if the configurations match, false otherwise.
         | 
| 154 | 
            +
                def ==(another_reflector)
         | 
| 155 | 
            +
                  self.letters == another_reflector.letters &&
         | 
| 156 | 
            +
                  self.position == another_reflector.position
         | 
| 157 | 
            +
                end
         | 
| 142 158 | 
             
              end
         | 
| 143 159 | 
             
            end
         | 
    
        data/lib/rotor_machine/rotor.rb
    CHANGED
    
    | @@ -29,6 +29,8 @@ module RotorMachine | |
| 29 29 | 
             
                # {Rotor} based on either a numeric position or a letter position.
         | 
| 30 30 | 
             
                attr_reader  :position
         | 
| 31 31 |  | 
| 32 | 
            +
                attr_reader :letters
         | 
| 33 | 
            +
             | 
| 32 34 | 
             
                ##
         | 
| 33 35 | 
             
                # Get or set the `step_size` - the number of positions the rotor should
         | 
| 34 36 | 
             
                # advance every time it's stepped.
         | 
| @@ -37,7 +39,7 @@ module RotorMachine | |
| 37 39 | 
             
                ##
         | 
| 38 40 | 
             
                # Provides the configuration of the German IC Enigma {Rotor}.
         | 
| 39 41 | 
             
                ROTOR_IC = "DMTWSILRUYQNKFEJCAZBPGXOHV".freeze
         | 
| 40 | 
            -
             | 
| 42 | 
            +
             | 
| 41 43 | 
             
                ##
         | 
| 42 44 | 
             
                # Provides the configuration of the German IIC Enigma {Rotor}.
         | 
| 43 45 | 
             
                ROTOR_IIC = "HQZGPJTMOBLNCIFDYAWVEUSRKX".freeze
         | 
| @@ -84,6 +86,7 @@ module RotorMachine | |
| 84 86 | 
             
                # @param step_size [Integer] The number of positions to step the rotor
         | 
| 85 87 | 
             
                #        each time it is advanced. Defaults to 1.
         | 
| 86 88 | 
             
                def initialize(rotor, start_on=0, step_size=1)
         | 
| 89 | 
            +
                  raise ArgumentError, "Initialization string contains duplicate letters" unless rotor.is_uniq?
         | 
| 87 90 | 
             
                  @letters = rotor.chars.freeze
         | 
| 88 91 | 
             
                  self.position = start_on
         | 
| 89 92 | 
             
                  @step_size = step_size
         | 
| @@ -197,5 +200,20 @@ module RotorMachine | |
| 197 200 | 
             
                def to_s
         | 
| 198 201 | 
             
                  return "a RotorMachine::Rotor of type '#{self.rotor_kind_name}', position=#{self.position} (#{self.current_letter}), step_size=#{@step_size}"
         | 
| 199 202 | 
             
                end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                ##
         | 
| 205 | 
            +
                # Compare this {RotorMachine::Rotor} to another one.
         | 
| 206 | 
            +
                #
         | 
| 207 | 
            +
                # Returns True if the configuration of the supplied {RotorMachine::Rotor} matches
         | 
| 208 | 
            +
                # this one, false otherwise.
         | 
| 209 | 
            +
                #
         | 
| 210 | 
            +
                # @param another_rotor [RotorMachine::Rotor] The Rotor to compare to this one.
         | 
| 211 | 
            +
                # @return [Boolean] True if the configurations match, false otherwise.
         | 
| 212 | 
            +
                def ==(another_rotor)
         | 
| 213 | 
            +
                  @letters == another_rotor.letters &&
         | 
| 214 | 
            +
                  position == another_rotor.position &&
         | 
| 215 | 
            +
                  step_size == another_rotor.step_size
         | 
| 216 | 
            +
                end
         | 
| 217 | 
            +
             | 
| 200 218 | 
             
              end
         | 
| 201 219 | 
             
            end
         | 
| @@ -4,6 +4,16 @@ | |
| 4 4 | 
             
            # @author Tammy Cravit <tammycravit@me.com>
         | 
| 5 5 |  | 
| 6 6 | 
             
            class String
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              ##
         | 
| 9 | 
            +
              # Detect if a string has any duplicated characters
         | 
| 10 | 
            +
              #
         | 
| 11 | 
            +
              # @return True if the string has no duplicated characters, false otherwise.
         | 
| 12 | 
            +
              def is_uniq?
         | 
| 13 | 
            +
                self.chars.uniq.length == self.chars.length
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
              alias :uniq? :is_uniq?
         | 
| 16 | 
            +
             | 
| 7 17 | 
             
              ##
         | 
| 8 18 | 
             
              # Break a string into blocks of a certain number of characters.
         | 
| 9 19 | 
             
              #
         | 
    
        data/rotor_machine.gemspec
    CHANGED
    
    | @@ -8,8 +8,6 @@ Gem::Specification.new do |spec| | |
| 8 8 | 
             
              spec.version       = RotorMachine::VERSION
         | 
| 9 9 | 
             
              spec.authors       = ['Tammy Cravit']
         | 
| 10 10 | 
             
              spec.email         = ['tammycravit@me.com']
         | 
| 11 | 
            -
              spec.cert_chain    = ['certs/tammycravit.pem']
         | 
| 12 | 
            -
              spec.signing_key   = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/
         | 
| 13 11 |  | 
| 14 12 | 
             
              spec.summary       = %q{Simple Enigma-like rotor machine in Ruby}
         | 
| 15 13 | 
             
              spec.homepage      = 'https://github.com/tammycravit/rotor_machine'
         | 
| @@ -22,15 +20,17 @@ Gem::Specification.new do |spec| | |
| 22 20 | 
             
              spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         | 
| 23 21 | 
             
              spec.require_paths = ['lib']
         | 
| 24 22 |  | 
| 25 | 
            -
              spec.add_dependency 'tcravit_ruby_lib'
         | 
| 23 | 
            +
              spec.add_dependency 'tcravit_ruby_lib', '~> 0.2'
         | 
| 26 24 |  | 
| 27 25 | 
             
              spec.add_development_dependency 'pry', '~> 0.11'
         | 
| 26 | 
            +
              spec.add_development_dependency 'pry-byebug', '~> 3.6'
         | 
| 28 27 | 
             
              spec.add_development_dependency 'bundler', '~> 1.16'
         | 
| 29 28 | 
             
              spec.add_development_dependency 'rake', '~> 10.0'
         | 
| 30 29 | 
             
              spec.add_development_dependency 'rspec', '~> 3.0'
         | 
| 31 30 |  | 
| 32 | 
            -
              spec.add_development_dependency 'guard'
         | 
| 33 | 
            -
              spec.add_development_dependency 'guard-rspec'
         | 
| 34 | 
            -
              spec.add_development_dependency 'guard-bundler'
         | 
| 35 | 
            -
              spec.add_development_dependency 'simplecov'
         | 
| 31 | 
            +
              spec.add_development_dependency 'guard', '~> 2.14'
         | 
| 32 | 
            +
              spec.add_development_dependency 'guard-rspec', '~> 4.7'
         | 
| 33 | 
            +
              spec.add_development_dependency 'guard-bundler', '~> 2.1'
         | 
| 34 | 
            +
              spec.add_development_dependency 'simplecov', '~> 0.15'
         | 
| 35 | 
            +
              spec.add_development_dependency 'simplecov-erb', '~> 0.1'
         | 
| 36 36 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,55 +1,29 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: rotor_machine
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.1.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Tammy Cravit
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 | 
            -
            cert_chain:
         | 
| 11 | 
            -
            -  | 
| 12 | 
            -
              -----BEGIN CERTIFICATE-----
         | 
| 13 | 
            -
              MIIEODCCAqCgAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBh0YW1t
         | 
| 14 | 
            -
              eWNyYXZpdC9EQz1tZS9EQz1jb20wHhcNMTgwMjE5MjMxNDQzWhcNMTkwMjE5MjMx
         | 
| 15 | 
            -
              NDQzWjAjMSEwHwYDVQQDDBh0YW1teWNyYXZpdC9EQz1tZS9EQz1jb20wggGiMA0G
         | 
| 16 | 
            -
              CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDDUddItdpGGMBoBfJZ2LWlXnfwEJWx
         | 
| 17 | 
            -
              iOc478enSEQFOLXj3nVuTUhyac6MQFH6nB8CkNZt7MSokuWdQ7H//1Ajq+jeCwUm
         | 
| 18 | 
            -
              WpjHF2BIL3WK7n8aAMH00p4gMAI4R8JnRjotmhUTIJCXtkIXoDTk1PGRzkH29q8+
         | 
| 19 | 
            -
              dByUGmkAoX+iHqNRLbgiywLlpVapRT5B1nE+K8oETb0TilCfdvOh+91dM1LX/z69
         | 
| 20 | 
            -
              uSOQOFoZSgnVNP/LTYaqDixdeEaDdslPRO1l8JSPdAzl1sn+YaeJwQbBWfbi+sGs
         | 
| 21 | 
            -
              MB53CkLGDsz5MsrPx8b0iBNM/xFSEmXE+du3vCSAZktjNR7kNuFGbSOQW4SQkld6
         | 
| 22 | 
            -
              mvw/Gi3TMmlgw2bELiXyUvEkHdkonFKMv9Rs2eq6Opw880YXl52/AcVi0dcsMZ3t
         | 
| 23 | 
            -
              qp70xuUgsDF7zNdSfIgQzBX+GsmIIgbRQKUyGuXMnqUJlPm4fPnl5i3mIyZnqKyx
         | 
| 24 | 
            -
              gg+2NNhKneabQF8wItZWdTGOVu87YFUOsosCAwEAAaN3MHUwCQYDVR0TBAIwADAL
         | 
| 25 | 
            -
              BgNVHQ8EBAMCBLAwHQYDVR0OBBYEFMSFUfz/8w+SLDZIFy54jEKUPq4IMB0GA1Ud
         | 
| 26 | 
            -
              EQQWMBSBEnRhbW15Y3Jhdml0QG1lLmNvbTAdBgNVHRIEFjAUgRJ0YW1teWNyYXZp
         | 
| 27 | 
            -
              dEBtZS5jb20wDQYJKoZIhvcNAQELBQADggGBAC/hS37ZCB/MYxt6gE9i5qvjdY5j
         | 
| 28 | 
            -
              qPiiQ7i5Yf2Gx6Jbe/wxiW1A3QcMdRvUSfIdC3XP3rYQf0AiyaQmbxhRn5e0LkYd
         | 
| 29 | 
            -
              riChjHZxLQG3CKj+7YiUijIv0mgaw/lA0pEhMxIb/xY03Jwh64cg2FZrd/5wWLh2
         | 
| 30 | 
            -
              QpyGVAktJp3rQolYO0fXbqRt40lg2+h2UWmaFvj++sFoCWdZzaopJZ3CS96IgUt+
         | 
| 31 | 
            -
              sqm+r9HvzygOChJyLAjM8OwabZ4e2yRR2ZLiRxvHBL4FGf7hg6Y0YAvwvyRJw/7b
         | 
| 32 | 
            -
              x6WTe0KO4pSZD02hl1A4gblx72eDvRwYkWO+dT1j9R+Wvrp/puwnzrLdThLwTsWQ
         | 
| 33 | 
            -
              YGgdQBodP4Wqsew3nfbNJOKkqOnry4lWJugso3w2fe0nUbrWuaC3++J9Eazm++n/
         | 
| 34 | 
            -
              F9wFDQvW5Nv6grw3Unc9miwN6NHA5kEjKzDDSXzWKSzAWbqzlMp/FxD+zP7NuibT
         | 
| 35 | 
            -
              pjZmBE7TzW3JK1L4mE7lBh9bwUC5WoyMBDPT7A==
         | 
| 36 | 
            -
              -----END CERTIFICATE-----
         | 
| 37 | 
            -
            date: 2018-02-19 00:00:00.000000000 Z
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2018-06-22 00:00:00.000000000 Z
         | 
| 38 12 | 
             
            dependencies:
         | 
| 39 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 40 14 | 
             
              name: tcravit_ruby_lib
         | 
| 41 15 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 42 16 | 
             
                requirements:
         | 
| 43 | 
            -
                - - " | 
| 17 | 
            +
                - - "~>"
         | 
| 44 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 45 | 
            -
                    version: '0'
         | 
| 19 | 
            +
                    version: '0.2'
         | 
| 46 20 | 
             
              type: :runtime
         | 
| 47 21 | 
             
              prerelease: false
         | 
| 48 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 49 23 | 
             
                requirements:
         | 
| 50 | 
            -
                - - " | 
| 24 | 
            +
                - - "~>"
         | 
| 51 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 52 | 
            -
                    version: '0'
         | 
| 26 | 
            +
                    version: '0.2'
         | 
| 53 27 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 54 28 | 
             
              name: pry
         | 
| 55 29 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -64,6 +38,20 @@ dependencies: | |
| 64 38 | 
             
                - - "~>"
         | 
| 65 39 | 
             
                  - !ruby/object:Gem::Version
         | 
| 66 40 | 
             
                    version: '0.11'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: pry-byebug
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '3.6'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '3.6'
         | 
| 67 55 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 68 56 | 
             
              name: bundler
         | 
| 69 57 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -110,58 +98,72 @@ dependencies: | |
| 110 98 | 
             
              name: guard
         | 
| 111 99 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 112 100 | 
             
                requirements:
         | 
| 113 | 
            -
                - - " | 
| 101 | 
            +
                - - "~>"
         | 
| 114 102 | 
             
                  - !ruby/object:Gem::Version
         | 
| 115 | 
            -
                    version: ' | 
| 103 | 
            +
                    version: '2.14'
         | 
| 116 104 | 
             
              type: :development
         | 
| 117 105 | 
             
              prerelease: false
         | 
| 118 106 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 119 107 | 
             
                requirements:
         | 
| 120 | 
            -
                - - " | 
| 108 | 
            +
                - - "~>"
         | 
| 121 109 | 
             
                  - !ruby/object:Gem::Version
         | 
| 122 | 
            -
                    version: ' | 
| 110 | 
            +
                    version: '2.14'
         | 
| 123 111 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 124 112 | 
             
              name: guard-rspec
         | 
| 125 113 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 126 114 | 
             
                requirements:
         | 
| 127 | 
            -
                - - " | 
| 115 | 
            +
                - - "~>"
         | 
| 128 116 | 
             
                  - !ruby/object:Gem::Version
         | 
| 129 | 
            -
                    version: ' | 
| 117 | 
            +
                    version: '4.7'
         | 
| 130 118 | 
             
              type: :development
         | 
| 131 119 | 
             
              prerelease: false
         | 
| 132 120 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 133 121 | 
             
                requirements:
         | 
| 134 | 
            -
                - - " | 
| 122 | 
            +
                - - "~>"
         | 
| 135 123 | 
             
                  - !ruby/object:Gem::Version
         | 
| 136 | 
            -
                    version: ' | 
| 124 | 
            +
                    version: '4.7'
         | 
| 137 125 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 138 126 | 
             
              name: guard-bundler
         | 
| 139 127 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 140 128 | 
             
                requirements:
         | 
| 141 | 
            -
                - - " | 
| 129 | 
            +
                - - "~>"
         | 
| 142 130 | 
             
                  - !ruby/object:Gem::Version
         | 
| 143 | 
            -
                    version: ' | 
| 131 | 
            +
                    version: '2.1'
         | 
| 144 132 | 
             
              type: :development
         | 
| 145 133 | 
             
              prerelease: false
         | 
| 146 134 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 147 135 | 
             
                requirements:
         | 
| 148 | 
            -
                - - " | 
| 136 | 
            +
                - - "~>"
         | 
| 149 137 | 
             
                  - !ruby/object:Gem::Version
         | 
| 150 | 
            -
                    version: ' | 
| 138 | 
            +
                    version: '2.1'
         | 
| 151 139 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 152 140 | 
             
              name: simplecov
         | 
| 153 141 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 154 142 | 
             
                requirements:
         | 
| 155 | 
            -
                - - " | 
| 143 | 
            +
                - - "~>"
         | 
| 144 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 145 | 
            +
                    version: '0.15'
         | 
| 146 | 
            +
              type: :development
         | 
| 147 | 
            +
              prerelease: false
         | 
| 148 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 149 | 
            +
                requirements:
         | 
| 150 | 
            +
                - - "~>"
         | 
| 151 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 152 | 
            +
                    version: '0.15'
         | 
| 153 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 154 | 
            +
              name: simplecov-erb
         | 
| 155 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 156 | 
            +
                requirements:
         | 
| 157 | 
            +
                - - "~>"
         | 
| 156 158 | 
             
                  - !ruby/object:Gem::Version
         | 
| 157 | 
            -
                    version: '0'
         | 
| 159 | 
            +
                    version: '0.1'
         | 
| 158 160 | 
             
              type: :development
         | 
| 159 161 | 
             
              prerelease: false
         | 
| 160 162 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 161 163 | 
             
                requirements:
         | 
| 162 | 
            -
                - - " | 
| 164 | 
            +
                - - "~>"
         | 
| 163 165 | 
             
                  - !ruby/object:Gem::Version
         | 
| 164 | 
            -
                    version: '0'
         | 
| 166 | 
            +
                    version: '0.1'
         | 
| 165 167 | 
             
            description: 
         | 
| 166 168 | 
             
            email:
         | 
| 167 169 | 
             
            - tammycravit@me.com
         | 
| @@ -182,8 +184,8 @@ files: | |
| 182 184 | 
             
            - README.md
         | 
| 183 185 | 
             
            - Rakefile
         | 
| 184 186 | 
             
            - bin/console
         | 
| 187 | 
            +
            - bin/resolve_coverage.pl
         | 
| 185 188 | 
             
            - bin/setup
         | 
| 186 | 
            -
            - certs/tammycravit.pem
         | 
| 187 189 | 
             
            - exe/rotor_machine
         | 
| 188 190 | 
             
            - images/Bundesarchiv_Enigma.jpg
         | 
| 189 191 | 
             
            - images/File:Enigma_wiring_kleur.png
         | 
| @@ -216,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 216 218 | 
             
                  version: '0'
         | 
| 217 219 | 
             
            requirements: []
         | 
| 218 220 | 
             
            rubyforge_project: 
         | 
| 219 | 
            -
            rubygems_version: 2.7. | 
| 221 | 
            +
            rubygems_version: 2.7.7
         | 
| 220 222 | 
             
            signing_key: 
         | 
| 221 223 | 
             
            specification_version: 4
         | 
| 222 224 | 
             
            summary: Simple Enigma-like rotor machine in Ruby
         | 
    
        checksums.yaml.gz.sig
    DELETED
    
    
    
        data.tar.gz.sig
    DELETED
    
    
    
        data/certs/tammycravit.pem
    DELETED
    
    | @@ -1,25 +0,0 @@ | |
| 1 | 
            -
            -----BEGIN CERTIFICATE-----
         | 
| 2 | 
            -
            MIIEODCCAqCgAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBh0YW1t
         | 
| 3 | 
            -
            eWNyYXZpdC9EQz1tZS9EQz1jb20wHhcNMTgwMjE5MjMxNDQzWhcNMTkwMjE5MjMx
         | 
| 4 | 
            -
            NDQzWjAjMSEwHwYDVQQDDBh0YW1teWNyYXZpdC9EQz1tZS9EQz1jb20wggGiMA0G
         | 
| 5 | 
            -
            CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDDUddItdpGGMBoBfJZ2LWlXnfwEJWx
         | 
| 6 | 
            -
            iOc478enSEQFOLXj3nVuTUhyac6MQFH6nB8CkNZt7MSokuWdQ7H//1Ajq+jeCwUm
         | 
| 7 | 
            -
            WpjHF2BIL3WK7n8aAMH00p4gMAI4R8JnRjotmhUTIJCXtkIXoDTk1PGRzkH29q8+
         | 
| 8 | 
            -
            dByUGmkAoX+iHqNRLbgiywLlpVapRT5B1nE+K8oETb0TilCfdvOh+91dM1LX/z69
         | 
| 9 | 
            -
            uSOQOFoZSgnVNP/LTYaqDixdeEaDdslPRO1l8JSPdAzl1sn+YaeJwQbBWfbi+sGs
         | 
| 10 | 
            -
            MB53CkLGDsz5MsrPx8b0iBNM/xFSEmXE+du3vCSAZktjNR7kNuFGbSOQW4SQkld6
         | 
| 11 | 
            -
            mvw/Gi3TMmlgw2bELiXyUvEkHdkonFKMv9Rs2eq6Opw880YXl52/AcVi0dcsMZ3t
         | 
| 12 | 
            -
            qp70xuUgsDF7zNdSfIgQzBX+GsmIIgbRQKUyGuXMnqUJlPm4fPnl5i3mIyZnqKyx
         | 
| 13 | 
            -
            gg+2NNhKneabQF8wItZWdTGOVu87YFUOsosCAwEAAaN3MHUwCQYDVR0TBAIwADAL
         | 
| 14 | 
            -
            BgNVHQ8EBAMCBLAwHQYDVR0OBBYEFMSFUfz/8w+SLDZIFy54jEKUPq4IMB0GA1Ud
         | 
| 15 | 
            -
            EQQWMBSBEnRhbW15Y3Jhdml0QG1lLmNvbTAdBgNVHRIEFjAUgRJ0YW1teWNyYXZp
         | 
| 16 | 
            -
            dEBtZS5jb20wDQYJKoZIhvcNAQELBQADggGBAC/hS37ZCB/MYxt6gE9i5qvjdY5j
         | 
| 17 | 
            -
            qPiiQ7i5Yf2Gx6Jbe/wxiW1A3QcMdRvUSfIdC3XP3rYQf0AiyaQmbxhRn5e0LkYd
         | 
| 18 | 
            -
            riChjHZxLQG3CKj+7YiUijIv0mgaw/lA0pEhMxIb/xY03Jwh64cg2FZrd/5wWLh2
         | 
| 19 | 
            -
            QpyGVAktJp3rQolYO0fXbqRt40lg2+h2UWmaFvj++sFoCWdZzaopJZ3CS96IgUt+
         | 
| 20 | 
            -
            sqm+r9HvzygOChJyLAjM8OwabZ4e2yRR2ZLiRxvHBL4FGf7hg6Y0YAvwvyRJw/7b
         | 
| 21 | 
            -
            x6WTe0KO4pSZD02hl1A4gblx72eDvRwYkWO+dT1j9R+Wvrp/puwnzrLdThLwTsWQ
         | 
| 22 | 
            -
            YGgdQBodP4Wqsew3nfbNJOKkqOnry4lWJugso3w2fe0nUbrWuaC3++J9Eazm++n/
         | 
| 23 | 
            -
            F9wFDQvW5Nv6grw3Unc9miwN6NHA5kEjKzDDSXzWKSzAWbqzlMp/FxD+zP7NuibT
         | 
| 24 | 
            -
            pjZmBE7TzW3JK1L4mE7lBh9bwUC5WoyMBDPT7A==
         | 
| 25 | 
            -
            -----END CERTIFICATE-----
         | 
    
        metadata.gz.sig
    DELETED
    
    | Binary file |