rotor_machine 1.0.9 → 1.0.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53a47ea917f9a4bc9c40c87c09a9c768d5481ab93eb185132453b982efcfd4c8
4
- data.tar.gz: 6d21d443a973ac550b2e3e191b5394dd5f5ea1a94ea765c2753a787fda1ea3dd
3
+ metadata.gz: 33525b02fa9c5e61d56bb3067a51ae60ce50fd412d1836786fa30e940c9e9950
4
+ data.tar.gz: 98e386fcbf3d6bca1a1a69c9e362f14239358c899082f826fc11abec0a1275a1
5
5
  SHA512:
6
- metadata.gz: 4527027053efa3f730f3704538bc26f84211206e0a68abab1180b0dd97ae1b6586b75ba550da59f721f35cdf014c3dc9fa7f186d753b7f87098ee4440a6e5579
7
- data.tar.gz: 280fb74f5667d05b3d4142d89b80b065bcc54e7a23c31260c9c4ea2499a5ae8bdcfb1d15e1f75066398eb11ab64829965946b9899b874e3d50ed615402e7406d
6
+ metadata.gz: 82cc734ccd263bd2c337852db5cd7ab89bef8f684da390689940875714187c81fc5de77d357fa4484368eae4a7c4819ba40d12e98aff239c310cf9e61297b4e0
7
+ data.tar.gz: d0f5dcecf48866ff9f0d903ae7c849f7b5cb0dd9dbb6cf18c832cee1db99aea8a7caa225b95cc561ddd897440493b59573a07db24fdfed94e3d3344b0dae5a05
data/Guardfile ADDED
@@ -0,0 +1,40 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # RSpec files
32
+ rspec = dsl.rspec
33
+ watch(rspec.spec_helper) { rspec.spec_dir }
34
+ watch(rspec.spec_support) { rspec.spec_dir }
35
+ watch(rspec.spec_files)
36
+
37
+ # Ruby files
38
+ ruby = dsl.ruby
39
+ dsl.watch_spec_files_for(ruby.lib_files)
40
+ end
@@ -0,0 +1,195 @@
1
+ module RotorMachine
2
+ ##
3
+ # The {RotorMachine::Factory} provides Factory-pattern helpers to build
4
+ # various parts of the {RotorMachine} - rotors, reflectors, plugboards,
5
+ # and fully-configured machines.
6
+ module Factory
7
+ extend self
8
+
9
+ ##
10
+ # Build a new {Rotor} and return it.
11
+ #
12
+ # The options hash for this method can accept the following named
13
+ # arguments:
14
+ #
15
+ # `:rotor_kind` - The type of rotor to create. Should be a symbol matching
16
+ # a rotor type constant in the {RotorMachine::Rotor} class,
17
+ # or a 26-character string giving the letter sequence for
18
+ # the rotor. Defaults to `:ROTOR_1` if not specified.
19
+ #
20
+ # `:initial_position` - The initial position of the rotor (0-based
21
+ # numeric position, or a letter on the rotor.) Defaults
22
+ # to 0 if not specified.
23
+ #
24
+ # `:step_size` - How many positions the rotor should advance with each
25
+ # step. Defaults to 1 if not specified.
26
+ #
27
+ # @param options [Hash] The options hash containing the options for the
28
+ # rotor.
29
+ # @return The newly-built rotor.
30
+ def build_rotor(options={})
31
+ rotor_kind = options.fetch(:rotor_kind, :ROTOR_I)
32
+ initial_position = options.fetch(:initial_position, 0)
33
+ step_size = options.fetch(:step_size, 1)
34
+
35
+ rotor_alphabet = nil
36
+
37
+ if (rotor_kind.class.name == "Symbol")
38
+ raise ArgumentError, "Invalid rotor kind (symbol #{rotor_kind} not found)" unless RotorMachine::Rotor.constants.include?(rotor_kind)
39
+ rotor_alphabet = RotorMachine::Rotor.const_get(rotor_kind)
40
+ elsif (rotor_kind.class.name == "String")
41
+ raise ArgumentError, "Invalid rotor kind (invalid length)" unless rotor_kind.length == 26
42
+ rotor_alphabet = rotor_kind.upcase
43
+ else
44
+ raise ArgumentError, "Invalid rotor kind (invalid type #{rotor_kind.class.name})"
45
+ end
46
+
47
+ if initial_position.is_a? Numeric
48
+ raise ArgumentError, "Invalid position (#{initial_position} out of range)" unless ((0..25).include?(initial_position))
49
+ elsif initial_position.is_a? String
50
+ unless RotorMachine::Rotor::ALPHABET.include?(initial_position)
51
+ raise ArgumentError, "Invalid position (invalid letter '#{initial_position}')"
52
+ end
53
+ else
54
+ raise ArgumentError, "Invalid position (invalid type #{initial_position.class.name})"
55
+ end
56
+
57
+ if step_size.is_a? Numeric
58
+ raise ArgumentError, "Invalid step size (#{step_size} out of range)" unless ((1..25).include?(step_size))
59
+ else
60
+ raise ArgumentError, "Invalid step size (invalid type #{step_size.class.name})"
61
+ end
62
+
63
+ return RotorMachine::Rotor.new(rotor_alphabet, initial_position, step_size)
64
+ end
65
+
66
+ ##
67
+ # Build a new {Reflector} and return it.
68
+ #
69
+ # The options hash for this method can accept the following named
70
+ # arguments:
71
+ #
72
+ # `:reflector_kind` - The type of reflector to create. Should be a symbol matching
73
+ # a reflector type constant in the {RotorMachine::Reflector} class,
74
+ # or a 26-character string giving the letter sequence for
75
+ # the reflector. Defaults to `:REFLECTOR_A` if not specified.
76
+ #
77
+ # `:initial_position` - The initial position of the reflector (0-based
78
+ # numeric position, or a letter on the rotor.) Defaults
79
+ # to 0 if not specified.
80
+ #
81
+ # @param options [Hash] The options hash containing the options for the
82
+ # reflector.
83
+ # @return The newly-built reflector.
84
+ def build_reflector(options={})
85
+ reflector_kind = options.fetch(:reflector_kind, :REFLECTOR_A)
86
+ initial_position = options.fetch(:initial_position, 0)
87
+
88
+ reflector_alphabet = nil
89
+
90
+ if (reflector_kind.class.name == "Symbol")
91
+ unless RotorMachine::Reflector.constants.include?(reflector_kind)
92
+ raise ArgumentError, "Invalid reflector kind (symbol #{reflector_kind} not found)"
93
+ end
94
+ reflector_alphabet = RotorMachine::Reflector.const_get(reflector_kind)
95
+ elsif (reflector_kind.class.name == "String")
96
+ raise ArgumentError, "Invalid reflector kind (invalid length)" unless reflector_kind.length == 26
97
+ reflector_alphabet = reflector_kind.upcase
98
+ else
99
+ raise ArgumentError, "Invalid reflector kind (invalid type #{reflector_kind.class.name})"
100
+ end
101
+
102
+ if initial_position.is_a? Numeric
103
+ raise ArgumentError, "Invalid position (#{initial_position} out of range)" unless ((0..25).include?(initial_position))
104
+ elsif initial_position.is_a? String
105
+ unless RotorMachine::Reflector::ALPHABET.include?(initial_position)
106
+ raise ArgumentError, "Invalid position (invalid letter '#{initial_position}')"
107
+ end
108
+ initial_position = reflector_alphabet.index(initial_position)
109
+ else
110
+ raise ArgumentError, "Invalid position (invalid type #{initial_position.class.name})"
111
+ end
112
+
113
+ return RotorMachine::Reflector.new(reflector_alphabet, initial_position)
114
+ end
115
+
116
+ ##
117
+ # Build a new {Plugboard} object and return it.
118
+ #
119
+ # @return The newly built plugboard.
120
+ def build_plugboard(options={})
121
+ return RotorMachine::Plugboard.new()
122
+ end
123
+
124
+ ##
125
+ # Build a set of {Rotor}s and return them.
126
+ #
127
+ # @param rotors [Array] A list of rotor types. Each type should be either
128
+ # a symbol corresponding to a constant in the {Rotor} class, or a 26
129
+ # character string providing the sequence of letters for the rotor.
130
+ # @param initial_positions [String] The starting letter to set each rotor
131
+ # to.
132
+ # @return [Array] An array of {Rotor} objects.
133
+ def build_rotor_set(rotors=[], initial_positions=nil)
134
+ ra = rotors.each.collect { |r| build_rotor(rotor_kind: r) }
135
+ unless initial_positions.nil?
136
+ ra.each_with_index do |r, i|
137
+ unless initial_positions[i].nil?
138
+ r.position = initial_positions[i]
139
+ end
140
+ end
141
+ end
142
+ return ra
143
+ end
144
+
145
+ ##
146
+ # Build a {Machine} and return it.
147
+ #
148
+ # The options hash can provide the following options:
149
+ #
150
+ # - `:rotors` - An array of {Rotor} objects. This can be constructed
151
+ # manually, through multiple calls to {#build_rotor}, or through a single
152
+ # call to {#build_rotor_set}.
153
+ # - `:reflector` - A {Reflector object.
154
+ # - `:connections` - A {Hash} of connections to make on the new {Machine}'s
155
+ # {Plugboard}.
156
+ #
157
+ # @param options The options for the newly built {Machine}.
158
+ # @return The newly constructed {Machine}.
159
+ def build_machine(options={})
160
+ rotors = options.fetch(:rotors, [])
161
+ reflector = options.fetch(:reflector, nil)
162
+ connections = options.fetch(:connections, {})
163
+
164
+ m = RotorMachine::Machine.new()
165
+ rotors.each { |r| m.rotors << r }
166
+ m.reflector = reflector
167
+ m.plugboard = RotorMachine::Plugboard.new()
168
+ unless connections.empty?
169
+ connections.each { |from, to| m.plugboard.connect(from, to) }
170
+ end
171
+
172
+ return m
173
+ end
174
+
175
+ ##
176
+ # {make_rotor} is an alias for {build_rotor}
177
+ alias :make_rotor :build_rotor
178
+
179
+ ##
180
+ # {make_reflector} is an alias for {build_reflector}
181
+ alias :make_reflector :build_reflector
182
+
183
+ ##
184
+ # {make_plugboard} is an alias for {build_plugboard}
185
+ alias :make_plugboard :build_plugboard
186
+
187
+ ##
188
+ # {make_machine} is an alias for {build_machine}
189
+ alias :make_machine :build_machine
190
+
191
+ ##
192
+ # {make_rotor_set} is an alias for {build_rotor_set}
193
+ alias :make_rotor_set :build_rotor_set
194
+ end
195
+ end
@@ -88,12 +88,10 @@ module RotorMachine
88
88
  # - Reflector A
89
89
  # - An empty plugboard with no connections
90
90
  def self.default_machine
91
- machine = self.empty_machine
92
- machine.rotors << RotorMachine::Rotor.new(RotorMachine::Rotor::ROTOR_I, "A", 1)
93
- machine.rotors << RotorMachine::Rotor.new(RotorMachine::Rotor::ROTOR_II, "A", 1)
94
- machine.rotors << RotorMachine::Rotor.new(RotorMachine::Rotor::ROTOR_III, "A", 1)
95
- machine.reflector = RotorMachine::Reflector.new(RotorMachine::Reflector::REFLECTOR_A)
96
- machine
91
+ RotorMachine::Factory.build_machine(
92
+ rotors: RotorMachine::Factory::build_rotor_set([:ROTOR_I, :ROTOR_II, :ROTOR_III], "AAA"),
93
+ reflector: RotorMachine::Factory::build_reflector(reflector_kind: :REFLECTOR_A)
94
+ )
97
95
  end
98
96
 
99
97
  ##
@@ -107,11 +105,7 @@ module RotorMachine
107
105
  # A RotorMachine in this state will raise an {ArgumentError} until you
108
106
  # outfit it with at least one rotor and a reflector.
109
107
  def self.empty_machine
110
- machine = RotorMachine::Machine.new()
111
- machine.rotors = []
112
- machine.reflector = nil
113
- machine.plugboard = RotorMachine::Plugboard.new()
114
- machine
108
+ RotorMachine::Factory.build_machine()
115
109
  end
116
110
 
117
111
  ##
@@ -40,14 +40,12 @@ module RotorMachine
40
40
  # @param to [String] A single-character string designating the end
41
41
  # of the connection.
42
42
  def connect(from, to)
43
- from.upcase!
44
- to.upcase!
45
- raise ArgumentError, "#{from} is already connected" if (connected?(from))
46
- raise ArgumentError, "#{to} is already connected" if (connected?(to))
47
- raise ArgumentError, "#{from} cannot be connected to itself" if (to == from)
43
+ raise ArgumentError, "#{from.upcase} is already connected" if (connected?(from.upcase))
44
+ raise ArgumentError, "#{to.upcase} is already connected" if (connected?(to.upcase))
45
+ raise ArgumentError, "#{from.upcase} cannot be connected to itself" if (to.upcase == from.upcase)
48
46
 
49
- @connections[from] = to
50
- @connections[to] = from
47
+ @connections[from.upcase] = to.upcase
48
+ @connections[to.upcase] = from.upcase
51
49
  end
52
50
 
53
51
  ##
@@ -14,6 +14,11 @@ module RotorMachine
14
14
  # be broken.
15
15
  class Reflector
16
16
 
17
+ ##
18
+ # Allow querying the numeric position (ie, the initial position) of the
19
+ # reflector.
20
+ attr_reader :position
21
+
17
22
  ##
18
23
  # The letter mapping for the German "A" reflector.
19
24
  REFLECTOR_A = "EJMZALYXVBWFCRQUONTSPIKHGD".freeze
@@ -38,6 +43,10 @@ module RotorMachine
38
43
  # The letter mapping for the German "ETW" reflector.
39
44
  REFLECTOR_ETW = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".freeze
40
45
 
46
+ ##
47
+ # A letter mapping for a passthrough reflector; also used by the RSpec tests
48
+ ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".freeze
49
+
41
50
  ##
42
51
  # Initialize a new {Reflector}.
43
52
  #
@@ -50,10 +59,31 @@ module RotorMachine
50
59
  # an additional permutation factor for the encipherment.
51
60
  def initialize(selected_reflector, start_position = 0)
52
61
  @letters = selected_reflector.chars.freeze
53
- @alphabet = REFLECTOR_ETW.chars.freeze
62
+ @alphabet = ALPHABET.chars.freeze
54
63
  @position = start_position
55
64
  end
56
65
 
66
+ ##
67
+ # Set the position of the {Rotor}.
68
+ #
69
+ # If a numeric position is provided, an {ArgumentError} will be raised if
70
+ # the position is outside the bounds of the rotor. If an alphabetic position
71
+ # is provided, an {ArgumentError} will be raised if the supplied character
72
+ # is not a character represented on the rotor.
73
+ #
74
+ # @param pos [Numeric, String] The position of the rotor.
75
+ def position=(pos)
76
+ if pos.class.to_s == "String"
77
+ raise ArgumentError, "#{pos[0]} is not a character on the rotor" unless @letters.include?(pos[0])
78
+ @position = @letters.index(pos[0])
79
+ elsif pos.class.to_s == "Integer"
80
+ raise ArgumentError, "Position #{pos} is invalid" if (pos < 0 or pos > @letters.length)
81
+ @position = pos
82
+ else
83
+ raise ArgumentError, "Invalid argument to position= (#{pos.class.to_s})"
84
+ end
85
+ end
86
+
57
87
  ##
58
88
  # Feed a sequence of characters through the reflector, and return the
59
89
  # results.
@@ -65,11 +95,11 @@ module RotorMachine
65
95
  # @return [String] The results of passing the input string through the
66
96
  # {Reflector}.
67
97
  def reflect(input)
68
- input.upcase.chars.each.collect { |c|
69
- if @alphabet.include?(c) then
70
- @letters[(@alphabet.index(c) + @position) % @alphabet.length]
71
- else
72
- c
98
+ input.upcase.chars.each.collect { |c|
99
+ if @alphabet.include?(c) then
100
+ @letters[(@alphabet.index(c) + @position) % @alphabet.length]
101
+ else
102
+ c
73
103
  end }.join("")
74
104
  end
75
105
 
@@ -86,6 +116,14 @@ module RotorMachine
86
116
  return :CUSTOM
87
117
  end
88
118
 
119
+ ##
120
+ # Get the current letter position of the rotor.
121
+ #
122
+ # @return [String] The current letter position of the rotor.
123
+ def current_letter
124
+ @letters[self.position]
125
+ end
126
+
89
127
  ##
90
128
  # Return a human-readable representation of the {Reflector}
91
129
  #
@@ -1,4 +1,4 @@
1
1
  module RotorMachine
2
- VERSION_DATA = [1, 0, 9]
2
+ VERSION_DATA = [1, 0, 10]
3
3
  VERSION = VERSION_DATA.join(".")
4
4
  end
@@ -26,4 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency "bundler", "~> 1.16"
27
27
  spec.add_development_dependency "rake", "~> 10.0"
28
28
  spec.add_development_dependency "rspec", "~> 3.0"
29
+
30
+ spec.add_development_dependency 'guard'
31
+ spec.add_development_dependency 'guard-rspec'
29
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rotor_machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.9
4
+ version: 1.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tammy Cravit
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-09 00:00:00.000000000 Z
11
+ date: 2018-02-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tcravit_ruby_lib
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: guard-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description:
84
112
  email:
85
113
  - tammycravit@me.com
@@ -94,6 +122,7 @@ files:
94
122
  - ".travis.yml"
95
123
  - CODE_OF_CONDUCT.md
96
124
  - Gemfile
125
+ - Guardfile
97
126
  - LICENSE.txt
98
127
  - README.md
99
128
  - Rakefile
@@ -103,6 +132,7 @@ files:
103
132
  - images/Bundesarchiv_Enigma.jpg
104
133
  - images/File:Enigma_wiring_kleur.png
105
134
  - lib/rotor_machine.rb
135
+ - lib/rotor_machine/factory.rb
106
136
  - lib/rotor_machine/machine.rb
107
137
  - lib/rotor_machine/plugboard.rb
108
138
  - lib/rotor_machine/reflector.rb