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