rotor_machine 1.0.9 → 1.0.10

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