sashite-epin 2.0.0 → 2.2.0
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/README.md +158 -539
- data/lib/sashite/epin/constants.rb +15 -0
- data/lib/sashite/epin/errors/argument/messages.rb +25 -0
- data/lib/sashite/epin/errors/argument.rb +16 -0
- data/lib/sashite/epin/errors.rb +3 -0
- data/lib/sashite/epin/identifier.rb +118 -211
- data/lib/sashite/epin/parser.rb +101 -0
- data/lib/sashite/epin.rb +54 -198
- data/lib/sashite-epin.rb +0 -11
- metadata +12 -12
- data/LICENSE.md +0 -21
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sashite
|
|
4
|
+
module Epin
|
|
5
|
+
# Constants for EPIN (Extended Piece Identifier Notation).
|
|
6
|
+
#
|
|
7
|
+
# EPIN extends PIN with a single derivation marker.
|
|
8
|
+
# PIN constants (VALID_TYPES, VALID_SIDES, VALID_STATES, etc.)
|
|
9
|
+
# are accessed through the sashite-pin dependency.
|
|
10
|
+
module Constants
|
|
11
|
+
# Derivation marker suffix.
|
|
12
|
+
DERIVATION_SUFFIX = "'"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sashite
|
|
4
|
+
module Epin
|
|
5
|
+
module Errors
|
|
6
|
+
class Argument < ::ArgumentError
|
|
7
|
+
# Centralized error messages for EPIN parsing and validation.
|
|
8
|
+
#
|
|
9
|
+
# PIN-related errors (empty input, must contain exactly one letter, etc.)
|
|
10
|
+
# are propagated from the sashite-pin dependency.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# raise Errors::Argument, Messages::INVALID_DERIVATION_MARKER
|
|
14
|
+
module Messages
|
|
15
|
+
# Parsing error
|
|
16
|
+
INVALID_DERIVATION_MARKER = "invalid derivation marker"
|
|
17
|
+
|
|
18
|
+
# Validation errors (constructor)
|
|
19
|
+
INVALID_PIN = "pin must be a Sashite::Pin::Identifier"
|
|
20
|
+
INVALID_DERIVED = "derived must be true or false"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "argument/messages"
|
|
4
|
+
|
|
5
|
+
module Sashite
|
|
6
|
+
module Epin
|
|
7
|
+
module Errors
|
|
8
|
+
# Error raised when EPIN parsing or validation fails.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# raise Argument, Argument::Messages::INVALID_DERIVATION_MARKER
|
|
12
|
+
class Argument < ::ArgumentError
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -1,293 +1,200 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
require_relative "errors"
|
|
4
5
|
|
|
5
6
|
module Sashite
|
|
6
7
|
module Epin
|
|
7
|
-
# Represents
|
|
8
|
+
# Represents a parsed EPIN (Extended Piece Identifier Notation) identifier.
|
|
8
9
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
10
|
+
# An Identifier combines a PIN component with a derivation status:
|
|
11
|
+
# - PIN: encodes abbr, side, state, and terminal status
|
|
12
|
+
# - Derived: indicates whether the piece uses native or derived style
|
|
12
13
|
#
|
|
13
|
-
#
|
|
14
|
+
# Instances are immutable (frozen after creation).
|
|
14
15
|
#
|
|
15
|
-
#
|
|
16
|
-
# - All piece attributes (name, side, state, terminal) come from the PIN component
|
|
17
|
-
# - EPIN adds only style derivation tracking (native vs derived)
|
|
18
|
-
# - Zero code duplication
|
|
19
|
-
#
|
|
20
|
-
# ## Minimal API
|
|
21
|
-
#
|
|
22
|
-
# Core methods (6 total):
|
|
23
|
-
# 1. new(pin, derived: false) - create from PIN component
|
|
24
|
-
# 2. pin - get PIN component
|
|
25
|
-
# 3. derived? - check derivation status
|
|
26
|
-
# 4. to_s - serialize
|
|
27
|
-
# 5. with_pin(new_pin) - replace PIN component
|
|
28
|
-
# 6. with_derived(boolean) - change derivation status
|
|
29
|
-
#
|
|
30
|
-
# Everything else uses the PIN component API directly.
|
|
31
|
-
#
|
|
32
|
-
# All instances are immutable - transformation methods return new instances.
|
|
33
|
-
#
|
|
34
|
-
# @example Basic usage
|
|
35
|
-
# # Create from PIN component
|
|
16
|
+
# @example Creating identifiers
|
|
36
17
|
# pin = Sashite::Pin.parse("K^")
|
|
37
|
-
# epin =
|
|
38
|
-
# epin.
|
|
39
|
-
#
|
|
40
|
-
# # Mark as derived
|
|
41
|
-
# derived = epin.mark_derived
|
|
42
|
-
# derived.to_s # => "K^'" (derived from opponent's style)
|
|
18
|
+
# epin = Identifier.new(pin)
|
|
19
|
+
# epin = Identifier.new(pin, derived: true)
|
|
43
20
|
#
|
|
44
|
-
# @example
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# epin.pin.side # => :first (Piece Side)
|
|
48
|
-
# epin.pin.state # => :enhanced (Piece State)
|
|
49
|
-
# epin.pin.terminal? # => true (Terminal Status)
|
|
50
|
-
# epin.derived? # => true (Piece Style)
|
|
21
|
+
# @example String conversion
|
|
22
|
+
# Identifier.new(pin).to_s # => "K^"
|
|
23
|
+
# Identifier.new(pin, derived: true).to_s # => "K^'"
|
|
51
24
|
#
|
|
52
|
-
# @
|
|
53
|
-
# epin = Sashite::Epin.parse("K^")
|
|
54
|
-
#
|
|
55
|
-
# # Transform PIN component
|
|
56
|
-
# epin.with_pin(epin.pin.with_type(:Q)) # => "Q^"
|
|
57
|
-
#
|
|
58
|
-
# # Transform derivation
|
|
59
|
-
# epin.mark_derived # => "K^'"
|
|
60
|
-
# epin.with_derived(true) # => "K^'"
|
|
61
|
-
#
|
|
62
|
-
# @see https://sashite.dev/specs/epin/1.0.0/ EPIN Specification v1.0.0
|
|
25
|
+
# @see https://sashite.dev/specs/epin/1.0.0/
|
|
63
26
|
class Identifier
|
|
64
|
-
#
|
|
65
|
-
# Grammar: <epin> ::= <pin> | <pin> "'"
|
|
66
|
-
EPIN_PATTERN = /\A[-+]?[A-Za-z]\^?'?\z/
|
|
67
|
-
|
|
68
|
-
# Derivation marker character
|
|
69
|
-
DERIVATION_MARKER = "'"
|
|
70
|
-
|
|
71
|
-
# Error messages
|
|
72
|
-
ERROR_INVALID_EPIN = "Invalid EPIN string: %s"
|
|
73
|
-
ERROR_INVALID_PIN = "PIN component must be a Pin::Identifier, got: %s"
|
|
74
|
-
ERROR_MULTIPLE_MARKERS = "EPIN string cannot have multiple derivation markers: %s"
|
|
75
|
-
|
|
76
|
-
# @return [Pin::Identifier] the PIN component
|
|
27
|
+
# @return [Sashite::Pin::Identifier] PIN component
|
|
77
28
|
attr_reader :pin
|
|
78
29
|
|
|
79
|
-
#
|
|
80
|
-
def derived?
|
|
81
|
-
@derived
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Create a new EPIN identifier from PIN component and derivation flag
|
|
30
|
+
# Creates a new Identifier instance.
|
|
85
31
|
#
|
|
86
|
-
# @param pin [Pin::Identifier]
|
|
87
|
-
# @param derived [Boolean]
|
|
88
|
-
# @
|
|
32
|
+
# @param pin [Sashite::Pin::Identifier] PIN component
|
|
33
|
+
# @param derived [Boolean] Derived status
|
|
34
|
+
# @return [Identifier] A new frozen Identifier instance
|
|
35
|
+
# @raise [Errors::Argument] If any attribute is invalid
|
|
89
36
|
#
|
|
90
|
-
# @example
|
|
37
|
+
# @example
|
|
91
38
|
# pin = Sashite::Pin.parse("K^")
|
|
92
|
-
#
|
|
93
|
-
#
|
|
39
|
+
# Identifier.new(pin)
|
|
40
|
+
# Identifier.new(pin, derived: true)
|
|
94
41
|
def initialize(pin, derived: false)
|
|
95
|
-
|
|
42
|
+
validate_pin!(pin)
|
|
43
|
+
validate_derived!(derived)
|
|
96
44
|
|
|
97
45
|
@pin = pin
|
|
98
|
-
@derived =
|
|
46
|
+
@derived = derived
|
|
99
47
|
|
|
100
48
|
freeze
|
|
101
49
|
end
|
|
102
50
|
|
|
103
|
-
#
|
|
51
|
+
# Returns the derived status.
|
|
104
52
|
#
|
|
105
|
-
# @
|
|
106
|
-
# @return [Identifier] new identifier instance
|
|
107
|
-
# @raise [ArgumentError] if the EPIN string is invalid
|
|
53
|
+
# @return [Boolean] true if derived style, false otherwise
|
|
108
54
|
#
|
|
109
|
-
# @example
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
string_value = String(epin_string)
|
|
115
|
-
validate_epin_string(string_value)
|
|
116
|
-
|
|
117
|
-
# Check for derivation marker
|
|
118
|
-
has_marker = string_value.end_with?(DERIVATION_MARKER)
|
|
119
|
-
|
|
120
|
-
# Extract PIN part (remove derivation marker if present)
|
|
121
|
-
pin_part = has_marker ? string_value[0...-1] : string_value
|
|
122
|
-
|
|
123
|
-
# Parse PIN component
|
|
124
|
-
pin_identifier = Pin::Identifier.parse(pin_part)
|
|
125
|
-
|
|
126
|
-
new(pin_identifier, derived: has_marker)
|
|
55
|
+
# @example
|
|
56
|
+
# Identifier.new(pin).derived? # => false
|
|
57
|
+
# Identifier.new(pin, derived: true).derived? # => true
|
|
58
|
+
def derived?
|
|
59
|
+
@derived
|
|
127
60
|
end
|
|
128
61
|
|
|
129
|
-
#
|
|
62
|
+
# Returns the native status.
|
|
130
63
|
#
|
|
131
|
-
# @
|
|
132
|
-
# @return [Boolean] true if valid EPIN, false otherwise
|
|
64
|
+
# @return [Boolean] true if native style, false otherwise
|
|
133
65
|
#
|
|
134
|
-
# @example
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# Sashite::Epin::Identifier.valid?("KK'") # => false (invalid PIN)
|
|
140
|
-
def self.valid?(epin_string)
|
|
141
|
-
return false unless epin_string.is_a?(::String)
|
|
142
|
-
return false unless epin_string.match?(EPIN_PATTERN)
|
|
143
|
-
|
|
144
|
-
# Check for multiple derivation markers
|
|
145
|
-
marker_count = epin_string.count(DERIVATION_MARKER)
|
|
146
|
-
return false if marker_count > 1
|
|
147
|
-
|
|
148
|
-
# Validate PIN part
|
|
149
|
-
has_marker = epin_string.end_with?(DERIVATION_MARKER)
|
|
150
|
-
pin_part = has_marker ? epin_string[0...-1] : epin_string
|
|
151
|
-
|
|
152
|
-
Pin::Identifier.valid?(pin_part)
|
|
66
|
+
# @example
|
|
67
|
+
# Identifier.new(pin).native? # => true
|
|
68
|
+
# Identifier.new(pin, derived: true).native? # => false
|
|
69
|
+
def native?
|
|
70
|
+
!@derived
|
|
153
71
|
end
|
|
154
72
|
|
|
155
|
-
#
|
|
73
|
+
# ========================================================================
|
|
74
|
+
# String Conversion
|
|
75
|
+
# ========================================================================
|
|
76
|
+
|
|
77
|
+
# Returns the EPIN string representation.
|
|
156
78
|
#
|
|
157
|
-
# @return [String] EPIN
|
|
79
|
+
# @return [String] The EPIN string
|
|
158
80
|
#
|
|
159
|
-
# @example
|
|
160
|
-
#
|
|
161
|
-
# derived.to_s
|
|
81
|
+
# @example
|
|
82
|
+
# Identifier.new(pin).to_s # => "K^"
|
|
83
|
+
# Identifier.new(pin, derived: true).to_s # => "K^'"
|
|
162
84
|
def to_s
|
|
163
|
-
pin.to_s
|
|
85
|
+
derived? ? "#{pin}#{Constants::DERIVATION_SUFFIX}" : pin.to_s
|
|
164
86
|
end
|
|
165
87
|
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
def suffix
|
|
170
|
-
derived? ? DERIVATION_MARKER : ""
|
|
171
|
-
end
|
|
88
|
+
# ========================================================================
|
|
89
|
+
# Transformations
|
|
90
|
+
# ========================================================================
|
|
172
91
|
|
|
173
|
-
#
|
|
92
|
+
# Returns a new Identifier with a different PIN component.
|
|
174
93
|
#
|
|
175
|
-
# @param new_pin [Pin::Identifier] new PIN component
|
|
176
|
-
# @return [Identifier] new
|
|
177
|
-
# @raise [
|
|
94
|
+
# @param new_pin [Sashite::Pin::Identifier] The new PIN component
|
|
95
|
+
# @return [Identifier] A new Identifier with the specified PIN
|
|
96
|
+
# @raise [Errors::Argument] If the PIN is invalid
|
|
178
97
|
#
|
|
179
|
-
# @example
|
|
180
|
-
# epin =
|
|
181
|
-
# new_pin =
|
|
182
|
-
# epin.with_pin(new_pin).to_s # => "Q^'"
|
|
98
|
+
# @example
|
|
99
|
+
# epin = Identifier.new(pin, derived: true)
|
|
100
|
+
# new_pin = Sashite::Pin.parse("+Q^")
|
|
101
|
+
# epin.with_pin(new_pin).to_s # => "+Q^'"
|
|
183
102
|
def with_pin(new_pin)
|
|
184
|
-
raise ::ArgumentError, format(ERROR_INVALID_PIN, new_pin.class) unless new_pin.is_a?(Pin::Identifier)
|
|
185
103
|
return self if pin == new_pin
|
|
186
104
|
|
|
187
|
-
self.class.new(new_pin, derived: derived
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
# Create a new identifier with different derivation status
|
|
191
|
-
#
|
|
192
|
-
# @param new_derived [Boolean] new derivation status
|
|
193
|
-
# @return [Identifier] new identifier with different derivation
|
|
194
|
-
#
|
|
195
|
-
# @example Change derivation status
|
|
196
|
-
# native = Sashite::Epin.parse("K^")
|
|
197
|
-
# derived = native.with_derived(true)
|
|
198
|
-
# derived.to_s # => "K^'"
|
|
199
|
-
def with_derived(new_derived)
|
|
200
|
-
new_derived_bool = !!new_derived
|
|
201
|
-
return self if derived? == new_derived_bool
|
|
202
|
-
|
|
203
|
-
self.class.new(pin, derived: new_derived_bool)
|
|
105
|
+
self.class.new(new_pin, derived: @derived)
|
|
204
106
|
end
|
|
205
107
|
|
|
206
|
-
#
|
|
108
|
+
# Returns a new Identifier marked as derived.
|
|
207
109
|
#
|
|
208
|
-
# @return [Identifier] new
|
|
110
|
+
# @return [Identifier] A new Identifier with derived: true
|
|
209
111
|
#
|
|
210
|
-
# @example
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
|
|
214
|
-
def mark_derived
|
|
112
|
+
# @example
|
|
113
|
+
# epin = Identifier.new(pin)
|
|
114
|
+
# epin.derive.to_s # => "K^'"
|
|
115
|
+
def derive
|
|
215
116
|
return self if derived?
|
|
216
117
|
|
|
217
118
|
self.class.new(pin, derived: true)
|
|
218
119
|
end
|
|
219
120
|
|
|
220
|
-
#
|
|
121
|
+
# Returns a new Identifier marked as native.
|
|
221
122
|
#
|
|
222
|
-
# @return [Identifier] new
|
|
123
|
+
# @return [Identifier] A new Identifier with derived: false
|
|
223
124
|
#
|
|
224
|
-
# @example
|
|
225
|
-
#
|
|
226
|
-
# native
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return self unless derived?
|
|
125
|
+
# @example
|
|
126
|
+
# epin = Identifier.new(pin, derived: true)
|
|
127
|
+
# epin.native.to_s # => "K^"
|
|
128
|
+
def native
|
|
129
|
+
return self if native?
|
|
230
130
|
|
|
231
131
|
self.class.new(pin, derived: false)
|
|
232
132
|
end
|
|
233
133
|
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
def native?
|
|
238
|
-
!derived?
|
|
239
|
-
end
|
|
134
|
+
# ========================================================================
|
|
135
|
+
# Comparison Queries
|
|
136
|
+
# ========================================================================
|
|
240
137
|
|
|
241
|
-
#
|
|
138
|
+
# Checks if two Identifiers have the same derived status.
|
|
242
139
|
#
|
|
243
|
-
# @param other [Identifier]
|
|
244
|
-
# @return [Boolean] true if same
|
|
140
|
+
# @param other [Identifier] The other Identifier to compare
|
|
141
|
+
# @return [Boolean] true if same derived status
|
|
245
142
|
#
|
|
246
|
-
# @example
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
def
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
derived? == other.derived?
|
|
143
|
+
# @example
|
|
144
|
+
# epin1 = Identifier.new(pin1, derived: true)
|
|
145
|
+
# epin2 = Identifier.new(pin2, derived: true)
|
|
146
|
+
# epin1.same_derived?(epin2) # => true
|
|
147
|
+
def same_derived?(other)
|
|
148
|
+
@derived == other.derived?
|
|
254
149
|
end
|
|
255
150
|
|
|
256
|
-
#
|
|
151
|
+
# ========================================================================
|
|
152
|
+
# Equality
|
|
153
|
+
# ========================================================================
|
|
154
|
+
|
|
155
|
+
# Checks equality with another Identifier.
|
|
257
156
|
#
|
|
258
|
-
# @param other [Object] object to compare
|
|
259
|
-
# @return [Boolean] true if
|
|
157
|
+
# @param other [Object] The object to compare
|
|
158
|
+
# @return [Boolean] true if equal
|
|
260
159
|
def ==(other)
|
|
261
|
-
return false unless
|
|
160
|
+
return false unless self.class === other
|
|
262
161
|
|
|
263
|
-
pin == other.pin && derived
|
|
162
|
+
pin == other.pin && @derived == other.derived?
|
|
264
163
|
end
|
|
265
164
|
|
|
266
|
-
# Alias for == to ensure Set functionality works correctly
|
|
267
165
|
alias eql? ==
|
|
268
166
|
|
|
269
|
-
#
|
|
167
|
+
# Returns a hash code for the Identifier.
|
|
270
168
|
#
|
|
271
|
-
# @return [Integer]
|
|
169
|
+
# @return [Integer] Hash code
|
|
272
170
|
def hash
|
|
273
|
-
[
|
|
171
|
+
[pin, @derived].hash
|
|
274
172
|
end
|
|
275
173
|
|
|
276
|
-
#
|
|
174
|
+
# Returns an inspect string for the Identifier.
|
|
277
175
|
#
|
|
278
|
-
# @
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
176
|
+
# @return [String] Inspect representation
|
|
177
|
+
def inspect
|
|
178
|
+
"#<#{self.class} #{self}>"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
# ========================================================================
|
|
184
|
+
# Private Validation
|
|
185
|
+
# ========================================================================
|
|
282
186
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return unless marker_count > 1
|
|
187
|
+
def validate_pin!(pin)
|
|
188
|
+
return if ::Sashite::Pin::Identifier === pin
|
|
286
189
|
|
|
287
|
-
raise ::
|
|
190
|
+
raise Errors::Argument, Errors::Argument::Messages::INVALID_PIN
|
|
288
191
|
end
|
|
289
192
|
|
|
290
|
-
|
|
193
|
+
def validate_derived!(derived)
|
|
194
|
+
return if ::TrueClass === derived || ::FalseClass === derived
|
|
195
|
+
|
|
196
|
+
raise Errors::Argument, Errors::Argument::Messages::INVALID_DERIVED
|
|
197
|
+
end
|
|
291
198
|
end
|
|
292
199
|
end
|
|
293
200
|
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
|
|
6
|
+
module Sashite
|
|
7
|
+
module Epin
|
|
8
|
+
# Parser for EPIN (Extended Piece Identifier Notation) strings.
|
|
9
|
+
#
|
|
10
|
+
# This parser extracts the derivation marker and delegates PIN parsing
|
|
11
|
+
# to the sashite-pin library.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# Parser.parse("K") # => { pin: { abbr: :K, side: :first, ... }, derived: false }
|
|
15
|
+
# Parser.parse("K^'") # => { pin: { abbr: :K, side: :first, ..., terminal: true }, derived: true }
|
|
16
|
+
#
|
|
17
|
+
# @see https://sashite.dev/specs/epin/1.0.0/
|
|
18
|
+
module Parser
|
|
19
|
+
# Parses an EPIN string into its components.
|
|
20
|
+
#
|
|
21
|
+
# @param input [String] The EPIN string to parse
|
|
22
|
+
# @return [Hash] A hash with :pin (PIN components hash) and :derived keys
|
|
23
|
+
# @raise [Errors::Argument] If the input is not a valid EPIN string
|
|
24
|
+
def self.parse(input)
|
|
25
|
+
validate_string!(input)
|
|
26
|
+
|
|
27
|
+
derived = has_derivation_marker?(input)
|
|
28
|
+
|
|
29
|
+
if derived
|
|
30
|
+
validate_derivation_marker!(input)
|
|
31
|
+
pin_string = input.chop
|
|
32
|
+
else
|
|
33
|
+
pin_string = input
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
pin_components = parse_pin_component(pin_string)
|
|
37
|
+
|
|
38
|
+
{ pin: pin_components, derived: derived }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Validates an EPIN string without raising an exception.
|
|
42
|
+
#
|
|
43
|
+
# @param input [String] The EPIN string to validate
|
|
44
|
+
# @return [Boolean] true if valid, false otherwise
|
|
45
|
+
def self.valid?(input)
|
|
46
|
+
return false unless ::String === input
|
|
47
|
+
|
|
48
|
+
parse(input)
|
|
49
|
+
true
|
|
50
|
+
rescue Errors::Argument
|
|
51
|
+
false
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class << self
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Validates that input is a String.
|
|
58
|
+
#
|
|
59
|
+
# @param input [Object] The input to validate
|
|
60
|
+
# @raise [Errors::Argument] If input is not a String
|
|
61
|
+
def validate_string!(input)
|
|
62
|
+
return if ::String === input
|
|
63
|
+
|
|
64
|
+
raise Errors::Argument, "invalid PIN component: must contain exactly one letter"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Checks if the input contains a derivation marker.
|
|
68
|
+
#
|
|
69
|
+
# @param input [String] The input to check
|
|
70
|
+
# @return [Boolean] true if contains derivation marker
|
|
71
|
+
def has_derivation_marker?(input)
|
|
72
|
+
input.include?(Constants::DERIVATION_SUFFIX)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Validates derivation marker position and uniqueness.
|
|
76
|
+
#
|
|
77
|
+
# @param input [String] The input to validate
|
|
78
|
+
# @raise [Errors::Argument] If derivation marker is invalid
|
|
79
|
+
def validate_derivation_marker!(input)
|
|
80
|
+
count = input.count(Constants::DERIVATION_SUFFIX)
|
|
81
|
+
last_char = input[-1]
|
|
82
|
+
|
|
83
|
+
return if count == 1 && last_char == Constants::DERIVATION_SUFFIX
|
|
84
|
+
|
|
85
|
+
raise Errors::Argument, Errors::Argument::Messages::INVALID_DERIVATION_MARKER
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Parses the PIN component using sashite-pin.
|
|
89
|
+
#
|
|
90
|
+
# @param pin_string [String] The PIN string to parse
|
|
91
|
+
# @return [Hash] PIN components hash
|
|
92
|
+
# @raise [Errors::Argument] If PIN parsing fails
|
|
93
|
+
def parse_pin_component(pin_string)
|
|
94
|
+
::Sashite::Pin::Parser.parse(pin_string)
|
|
95
|
+
rescue ::Sashite::Pin::Errors::Argument => e
|
|
96
|
+
raise Errors::Argument, "invalid PIN component: #{e.message}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|