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
|