sashite-snn 3.1.0 → 4.0.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.
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constants"
4
+ require_relative "errors"
5
+
6
+ module Sashite
7
+ module Snn
8
+ # Secure parser for SNN (Style Name Notation) strings.
9
+ #
10
+ # Designed for untrusted input: validates bounds first, parses character
11
+ # by character, and enforces strict constraints at every step.
12
+ #
13
+ # @example
14
+ # Parser.parse("Chess") # => "Chess"
15
+ # Parser.parse("Chess960") # => "Chess960"
16
+ # Parser.parse("chess") # => raises Errors::Argument
17
+ #
18
+ # @see https://sashite.dev/specs/snn/1.0.0/
19
+ module Parser
20
+ # Byte ranges for validation
21
+ UPPERCASE_MIN = 0x41 # A
22
+ UPPERCASE_MAX = 0x5A # Z
23
+ LOWERCASE_MIN = 0x61 # a
24
+ LOWERCASE_MAX = 0x7A # z
25
+ DIGIT_MIN = 0x30 # 0
26
+ DIGIT_MAX = 0x39 # 9
27
+
28
+ class << self
29
+ # Parses an SNN string, validating its format.
30
+ #
31
+ # @param input [String] The SNN string to parse
32
+ # @return [String] The validated SNN string
33
+ # @raise [Errors::Argument] If the input is invalid
34
+ #
35
+ # @example
36
+ # Parser.parse("Chess") # => "Chess"
37
+ # Parser.parse("Chess960") # => "Chess960"
38
+ # Parser.parse("") # => raises Errors::Argument
39
+ def parse(input)
40
+ validate_input_type!(input)
41
+ validate_not_empty!(input)
42
+ validate_length!(input)
43
+ validate_format!(input)
44
+
45
+ input
46
+ end
47
+
48
+ # Reports whether the input is a valid SNN string.
49
+ #
50
+ # @param input [Object] The input to validate
51
+ # @return [Boolean] true if valid, false otherwise
52
+ #
53
+ # @example
54
+ # Parser.valid?("Chess") # => true
55
+ # Parser.valid?("chess") # => false
56
+ # Parser.valid?(nil) # => false
57
+ def valid?(input)
58
+ parse(input)
59
+ true
60
+ rescue Errors::Argument
61
+ false
62
+ end
63
+
64
+ private
65
+
66
+ # Validates input is a String.
67
+ def validate_input_type!(input)
68
+ return if ::String === input
69
+
70
+ raise Errors::Argument, Errors::Argument::Messages::INVALID_FORMAT
71
+ end
72
+
73
+ # Validates input is not empty.
74
+ def validate_not_empty!(input)
75
+ return unless input.empty?
76
+
77
+ raise Errors::Argument, Errors::Argument::Messages::EMPTY_INPUT
78
+ end
79
+
80
+ # Validates input does not exceed maximum length.
81
+ def validate_length!(input)
82
+ return if input.bytesize <= Constants::MAX_STRING_LENGTH
83
+
84
+ raise Errors::Argument, Errors::Argument::Messages::INPUT_TOO_LONG
85
+ end
86
+
87
+ # Validates the SNN format using byte-level parsing.
88
+ #
89
+ # Format: uppercase letter, then letters, then optional digits at end.
90
+ def validate_format!(input)
91
+ bytes = input.bytes
92
+ index = 0
93
+
94
+ # State 1: First byte must be uppercase letter
95
+ raise_invalid_format! unless uppercase?(bytes[index])
96
+ index += 1
97
+
98
+ # State 2: Parse letters (transition to digits allowed)
99
+ while index < bytes.length
100
+ byte = bytes[index]
101
+
102
+ if letter?(byte)
103
+ index += 1
104
+ elsif digit?(byte)
105
+ # Transition to digit state
106
+ index += 1
107
+ break
108
+ else
109
+ raise_invalid_format!
110
+ end
111
+ end
112
+
113
+ # State 3: Parse remaining digits only (no return to letters)
114
+ while index < bytes.length
115
+ byte = bytes[index]
116
+ raise_invalid_format! unless digit?(byte)
117
+ index += 1
118
+ end
119
+ end
120
+
121
+ # Character class predicates
122
+
123
+ def uppercase?(byte)
124
+ byte >= UPPERCASE_MIN && byte <= UPPERCASE_MAX
125
+ end
126
+
127
+ def lowercase?(byte)
128
+ byte >= LOWERCASE_MIN && byte <= LOWERCASE_MAX
129
+ end
130
+
131
+ def letter?(byte)
132
+ uppercase?(byte) || lowercase?(byte)
133
+ end
134
+
135
+ def digit?(byte)
136
+ byte >= DIGIT_MIN && byte <= DIGIT_MAX
137
+ end
138
+
139
+ def raise_invalid_format!
140
+ raise Errors::Argument, Errors::Argument::Messages::INVALID_FORMAT
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constants"
4
+ require_relative "errors"
5
+ require_relative "parser"
6
+
7
+ module Sashite
8
+ module Snn
9
+ # Represents a validated SNN (Style Name Notation) style name.
10
+ #
11
+ # A StyleName encodes a single attribute:
12
+ # - name: the validated SNN string (PascalCase with optional numeric suffix)
13
+ #
14
+ # Instances are immutable (frozen after creation).
15
+ #
16
+ # @example Creating style names
17
+ # snn = StyleName.new("Chess")
18
+ # snn = StyleName.new("Chess960")
19
+ #
20
+ # @example String conversion
21
+ # StyleName.new("Chess").to_s # => "Chess"
22
+ # StyleName.new("Chess960").to_s # => "Chess960"
23
+ #
24
+ # @see https://sashite.dev/specs/snn/1.0.0/
25
+ class StyleName
26
+ # Maximum length of a valid SNN string.
27
+ MAX_STRING_LENGTH = Constants::MAX_STRING_LENGTH
28
+
29
+ # @return [String] The validated SNN style name
30
+ attr_reader :name
31
+
32
+ # Creates a new StyleName instance.
33
+ #
34
+ # @param name [String] The SNN style name
35
+ # @return [StyleName] A new frozen StyleName instance
36
+ # @raise [Errors::Argument] If the name is invalid
37
+ #
38
+ # @example
39
+ # StyleName.new("Chess")
40
+ # StyleName.new("Chess960")
41
+ def initialize(name)
42
+ @name = Parser.parse(name)
43
+
44
+ freeze
45
+ end
46
+
47
+ # ========================================================================
48
+ # String Conversion
49
+ # ========================================================================
50
+
51
+ # Returns the SNN string representation.
52
+ #
53
+ # @return [String] The style name
54
+ #
55
+ # @example
56
+ # StyleName.new("Chess").to_s # => "Chess"
57
+ def to_s
58
+ name
59
+ end
60
+
61
+ # ========================================================================
62
+ # Equality
63
+ # ========================================================================
64
+
65
+ # Checks equality with another StyleName.
66
+ #
67
+ # @param other [Object] The object to compare
68
+ # @return [Boolean] true if equal
69
+ #
70
+ # @example
71
+ # snn1 = StyleName.new("Chess")
72
+ # snn2 = StyleName.new("Chess")
73
+ # snn1 == snn2 # => true
74
+ def ==(other)
75
+ return false unless self.class === other
76
+
77
+ name == other.name
78
+ end
79
+
80
+ alias eql? ==
81
+
82
+ # Returns a hash code for the StyleName.
83
+ #
84
+ # @return [Integer] Hash code
85
+ def hash
86
+ name.hash
87
+ end
88
+
89
+ # Returns an inspect string for the StyleName.
90
+ #
91
+ # @return [String] Inspect representation
92
+ #
93
+ # @example
94
+ # StyleName.new("Chess").inspect # => "#<Sashite::Snn::StyleName Chess>"
95
+ def inspect
96
+ "#<#{self.class} #{self}>"
97
+ end
98
+ end
99
+ end
100
+ end
data/lib/sashite/snn.rb CHANGED
@@ -1,93 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "snn/name"
3
+ require_relative "snn/constants"
4
+ require_relative "snn/errors"
5
+ require_relative "snn/parser"
6
+ require_relative "snn/style_name"
4
7
 
5
8
  module Sashite
6
- # SNN (Style Name Notation) implementation for Ruby
9
+ # SNN (Style Name Notation) implementation for Ruby.
7
10
  #
8
- # Provides a foundational naming system for identifying styles in abstract strategy board games.
9
- # SNN uses canonical, human-readable alphabetic names with case encoding to represent both
10
- # style identity and player assignment.
11
+ # SNN provides a human-readable naming system for game styles (Piece Styles)
12
+ # in abstract strategy board games. It uses PascalCase names with optional
13
+ # numeric suffixes to identify movement traditions or game variants.
11
14
  #
12
- # Format: All uppercase OR all lowercase alphabetic characters
15
+ # @example Parsing
16
+ # snn = Sashite::Snn.parse("Chess")
17
+ # snn.name # => "Chess"
13
18
  #
14
- # Examples:
15
- # "CHESS" - Chess style for first player
16
- # "chess" - Chess style for second player
17
- # "SHOGI" - Shōgi style for first player
18
- # "shogi" - Shōgi style for second player
19
- # "XIANGQI" - Xiangqi style for first player
20
- # "xiangqi" - Xiangqi style for second player
19
+ # @example Validation
20
+ # Sashite::Snn.valid?("Chess960") # => true
21
+ # Sashite::Snn.valid?("chess") # => false
21
22
  #
22
- # Case Encoding:
23
- # - UPPERCASE names represent the first player's style
24
- # - lowercase names represent the second player's style
25
- #
26
- # Constraints:
27
- # - Alphabetic characters only (A-Z, a-z)
28
- # - Case consistency required (all uppercase OR all lowercase)
29
- # - No digits, no special characters, no mixed case
30
- #
31
- # As a foundational primitive, SNN has no dependencies and serves as a building block
32
- # for formal style identification in the Sashité ecosystem.
33
- #
34
- # See: https://sashite.dev/specs/snn/1.0.0/
23
+ # @see https://sashite.dev/specs/snn/1.0.0/
35
24
  module Snn
36
- # Check if a string is valid SNN notation
25
+ # Parses an SNN string into a StyleName.
37
26
  #
38
- # Valid SNN strings must contain only alphabetic characters in consistent case
39
- # (either all uppercase or all lowercase).
27
+ # @param input [String] The SNN string to parse
28
+ # @return [StyleName] A new StyleName instance
29
+ # @raise [Errors::Argument] If the input is invalid
40
30
  #
41
- # @param snn_string [String] the string to validate
42
- # @return [Boolean] true if valid SNN, false otherwise
31
+ # @example
32
+ # snn = Sashite::Snn.parse("Chess")
33
+ # snn.name # => "Chess"
43
34
  #
44
- # @example Validate SNN strings
45
- # Sashite::Snn.valid?("CHESS") # => true
46
- # Sashite::Snn.valid?("shogi") # => true
47
- # Sashite::Snn.valid?("Chess") # => false (mixed case)
48
- # Sashite::Snn.valid?("CHESS960") # => false (contains digits)
49
- # Sashite::Snn.valid?("GO9X9") # => false (contains digits)
50
- def self.valid?(snn_string)
51
- Name.valid?(snn_string)
35
+ # @example With numeric suffix
36
+ # snn = Sashite::Snn.parse("Chess960")
37
+ # snn.name # => "Chess960"
38
+ def self.parse(input)
39
+ StyleName.new(input)
52
40
  end
53
41
 
54
- # Parse an SNN string into a Name object
55
- #
56
- # Converts a valid SNN string into an immutable Name object. The name must follow
57
- # SNN format rules: all uppercase or all lowercase alphabetic characters only.
58
- #
59
- # @param snn_string [String] the name string
60
- # @return [Snn::Name] a parsed name object
61
- # @raise [ArgumentError] if the name is invalid
62
- #
63
- # @example Parse valid SNN names
64
- # Sashite::Snn.parse("SHOGI") # => #<Snn::Name value="SHOGI">
65
- # Sashite::Snn.parse("chess") # => #<Snn::Name value="chess">
66
- #
67
- # @example Invalid names raise errors
68
- # Sashite::Snn.parse("Chess") # => ArgumentError (mixed case)
69
- # Sashite::Snn.parse("CHESS960") # => ArgumentError (contains digits)
70
- def self.parse(snn_string)
71
- Name.parse(snn_string)
72
- end
73
-
74
- # Create a new Name instance directly
75
- #
76
- # Constructs a Name object from a string or symbol. The value must follow
77
- # SNN format rules: all uppercase or all lowercase alphabetic characters only.
78
- #
79
- # @param value [String, Symbol] style name to construct
80
- # @return [Snn::Name] new name instance
81
- # @raise [ArgumentError] if name format is invalid
82
- #
83
- # @example Create names
84
- # Sashite::Snn.name("XIANGQI") # => #<Snn::Name value="XIANGQI">
85
- # Sashite::Snn.name(:makruk) # => #<Snn::Name value="makruk">
86
- #
87
- # @example Invalid formats raise errors
88
- # Sashite::Snn.name("Chess960") # => ArgumentError
89
- def self.name(value)
90
- Name.new(value)
42
+ # Reports whether the input is a valid SNN string.
43
+ #
44
+ # @param input [Object] The input to validate
45
+ # @return [Boolean] true if valid, false otherwise
46
+ #
47
+ # @example
48
+ # Sashite::Snn.valid?("Chess") # => true
49
+ # Sashite::Snn.valid?("Chess960") # => true
50
+ # Sashite::Snn.valid?("chess") # => false
51
+ # Sashite::Snn.valid?("") # => false
52
+ def self.valid?(input)
53
+ Parser.valid?(input)
91
54
  end
92
55
  end
93
56
  end
data/lib/sashite-snn.rb CHANGED
@@ -7,7 +7,7 @@ require_relative "sashite/snn"
7
7
  # Sashité provides a collection of libraries for representing and manipulating
8
8
  # board game concepts according to the Sashité Protocol specifications.
9
9
  #
10
- # @see https://sashite.dev/protocol/ Sashité Protocol
10
+ # @see https://sashite.dev/game-protocol/ Sashité Protocol
11
11
  # @see https://sashite.dev/specs/ Sashité Specifications
12
12
  # @author Sashité
13
13
  module Sashite
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sashite-snn
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
@@ -9,38 +9,35 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: |
13
- SNN (Style Name Notation) provides a foundational, rule-agnostic naming system for identifying
14
- game styles in abstract strategy board games. This gem implements the SNN Specification v1.0.0
15
- with a modern Ruby interface featuring immutable style name objects and functional programming
16
- principles.
17
-
18
- SNN uses case-consistent alphabetic names (e.g., "CHESS", "SHOGI", "XIANGQI" for first player;
19
- "chess", "shogi", "xiangqi" for second player) to unambiguously represent both style identity
20
- and player assignment. As a foundational primitive with no dependencies, SNN serves as a building
21
- block for formal style identification across the Sashité ecosystem.
22
-
23
- Format: All uppercase OR all lowercase alphabetic characters only (no digits, no special characters).
24
- Ideal for game engines, protocols, and tools requiring clear and extensible style identifiers.
12
+ description: SNN (Style Name Notation) implementation for Ruby. Provides a rule-agnostic
13
+ format for identifying game styles in abstract strategy board games with immutable
14
+ style name objects and functional programming principles.
25
15
  email: contact@cyril.email
26
16
  executables: []
27
17
  extensions: []
28
18
  extra_rdoc_files: []
29
19
  files:
30
- - LICENSE.md
20
+ - LICENSE
31
21
  - README.md
32
22
  - lib/sashite-snn.rb
33
23
  - lib/sashite/snn.rb
34
- - lib/sashite/snn/name.rb
24
+ - lib/sashite/snn/constants.rb
25
+ - lib/sashite/snn/errors.rb
26
+ - lib/sashite/snn/errors/argument.rb
27
+ - lib/sashite/snn/errors/argument/messages.rb
28
+ - lib/sashite/snn/parser.rb
29
+ - lib/sashite/snn/style_name.rb
35
30
  homepage: https://github.com/sashite/snn.rb
36
31
  licenses:
37
- - MIT
32
+ - Apache-2.0
38
33
  metadata:
39
34
  bug_tracker_uri: https://github.com/sashite/snn.rb/issues
40
35
  documentation_uri: https://rubydoc.info/github/sashite/snn.rb/main
41
36
  homepage_uri: https://github.com/sashite/snn.rb
42
37
  source_code_uri: https://github.com/sashite/snn.rb
43
38
  specification_uri: https://sashite.dev/specs/snn/1.0.0/
39
+ wiki_uri: https://sashite.dev/specs/snn/1.0.0/examples/
40
+ funding_uri: https://github.com/sponsors/sashite
44
41
  rubygems_mfa_required: 'true'
45
42
  rdoc_options: []
46
43
  require_paths:
@@ -56,8 +53,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
53
  - !ruby/object:Gem::Version
57
54
  version: '0'
58
55
  requirements: []
59
- rubygems_version: 3.7.1
56
+ rubygems_version: 4.0.3
60
57
  specification_version: 4
61
- summary: SNN (Style Name Notation) - foundational naming system for abstract strategy
62
- game styles
58
+ summary: SNN (Style Name Notation) implementation for Ruby with immutable style name
59
+ objects
63
60
  test_files: []
data/LICENSE.md DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2025 Cyril Kato
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,170 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Sashite
4
- module Snn
5
- # Represents a style name in SNN (Style Name Notation) format.
6
- #
7
- # SNN provides a foundational naming system for abstract strategy game styles.
8
- # Each name must consist of alphabetic characters only, all in the same case
9
- # (either all uppercase or all lowercase).
10
- #
11
- # Case encoding:
12
- # - UPPERCASE names represent the first player's style
13
- # - lowercase names represent the second player's style
14
- #
15
- # Constraints:
16
- # - Alphabetic characters only (A-Z or a-z)
17
- # - Case consistency required (all uppercase OR all lowercase)
18
- # - No digits, no special characters, no mixed case
19
- #
20
- # All instances are immutable.
21
- #
22
- # @example Valid names
23
- # Sashite::Snn::Name.new("CHESS") # => #<Snn::Name value="CHESS">
24
- # Sashite::Snn::Name.new("shogi") # => #<Snn::Name value="shogi">
25
- # Sashite::Snn::Name.new("XIANGQI") # => #<Snn::Name value="XIANGQI">
26
- #
27
- # @example Invalid names
28
- # Sashite::Snn::Name.new("Chess") # => ArgumentError (mixed case)
29
- # Sashite::Snn::Name.new("CHESS960") # => ArgumentError (contains digits)
30
- # Sashite::Snn::Name.new("GO9X9") # => ArgumentError (contains digits)
31
- class Name
32
- # SNN validation pattern matching the specification
33
- # Format: All uppercase OR all lowercase alphabetic characters
34
- SNN_PATTERN = /\A([A-Z]+|[a-z]+)\z/
35
-
36
- # Error message for invalid SNN strings
37
- ERROR_INVALID_NAME = "Invalid SNN string: %s"
38
-
39
- # @return [String] the canonical style name
40
- attr_reader :value
41
-
42
- # Create a new style name instance
43
- #
44
- # The name must follow SNN format rules: all uppercase or all lowercase
45
- # alphabetic characters only. No digits, special characters, or mixed case.
46
- #
47
- # @param name [String, Symbol] the style name (e.g., "SHOGI", :chess)
48
- # @raise [ArgumentError] if the name does not match SNN pattern
49
- #
50
- # @example Create valid names
51
- # Sashite::Snn::Name.new("CHESS") # First player Chess
52
- # Sashite::Snn::Name.new("shogi") # Second player Shōgi
53
- # Sashite::Snn::Name.new(:XIANGQI) # First player Xiangqi
54
- #
55
- # @example Invalid names raise errors
56
- # Sashite::Snn::Name.new("Chess") # Mixed case
57
- # Sashite::Snn::Name.new("CHESS960") # Contains digits
58
- def initialize(name)
59
- string_value = name.to_s
60
- self.class.validate_format(string_value)
61
-
62
- @value = string_value.freeze
63
- freeze
64
- end
65
-
66
- # Parse an SNN string into a Name object
67
- #
68
- # This is an alias for the constructor, provided for consistency
69
- # with other Sashité specifications.
70
- #
71
- # @param string [String] the SNN-formatted style name
72
- # @return [Name] a new Name instance
73
- # @raise [ArgumentError] if the string is invalid
74
- #
75
- # @example Parse valid names
76
- # Sashite::Snn::Name.parse("SHOGI") # => #<Snn::Name value="SHOGI">
77
- # Sashite::Snn::Name.parse("chess") # => #<Snn::Name value="chess">
78
- def self.parse(string)
79
- new(string)
80
- end
81
-
82
- # Check whether the given string is a valid SNN name
83
- #
84
- # Valid SNN strings must:
85
- # - Contain only alphabetic characters (A-Z or a-z)
86
- # - Have consistent case (all uppercase OR all lowercase)
87
- # - Contain at least one character
88
- #
89
- # @param string [String] input string to validate
90
- # @return [Boolean] true if valid, false otherwise
91
- #
92
- # @example Valid names
93
- # Sashite::Snn::Name.valid?("CHESS") # => true
94
- # Sashite::Snn::Name.valid?("shogi") # => true
95
- # Sashite::Snn::Name.valid?("XIANGQI") # => true
96
- #
97
- # @example Invalid names
98
- # Sashite::Snn::Name.valid?("Chess") # => false (mixed case)
99
- # Sashite::Snn::Name.valid?("CHESS960") # => false (contains digits)
100
- # Sashite::Snn::Name.valid?("GO9X9") # => false (contains digits)
101
- # Sashite::Snn::Name.valid?("") # => false (empty)
102
- def self.valid?(string)
103
- string.is_a?(::String) && string.match?(SNN_PATTERN)
104
- end
105
-
106
- # Returns the string representation of the name
107
- #
108
- # @return [String] the canonical style name
109
- #
110
- # @example
111
- # name = Sashite::Snn::Name.new("SHOGI")
112
- # name.to_s # => "SHOGI"
113
- def to_s
114
- value
115
- end
116
-
117
- # Equality based on string value
118
- #
119
- # Two names are equal if they have the same string value. Case matters:
120
- # "CHESS" (first player) is not equal to "chess" (second player).
121
- #
122
- # @param other [Object] object to compare with
123
- # @return [Boolean] true if equal, false otherwise
124
- #
125
- # @example
126
- # name1 = Sashite::Snn::Name.new("CHESS")
127
- # name2 = Sashite::Snn::Name.new("CHESS")
128
- # name3 = Sashite::Snn::Name.new("chess")
129
- #
130
- # name1 == name2 # => true
131
- # name1 == name3 # => false (different case = different player)
132
- def ==(other)
133
- other.is_a?(self.class) && value == other.value
134
- end
135
-
136
- # Required for correct Set/Hash behavior
137
- alias eql? ==
138
-
139
- # Hash based on class and value
140
- #
141
- # Enables Name objects to be used as hash keys and in sets.
142
- #
143
- # @return [Integer] hash code
144
- #
145
- # @example Use as hash key
146
- # styles = {
147
- # Sashite::Snn::Name.new("CHESS") => "Western Chess",
148
- # Sashite::Snn::Name.new("SHOGI") => "Japanese Chess"
149
- # }
150
- def hash
151
- [self.class, value].hash
152
- end
153
-
154
- # Validate that the string is in proper SNN format
155
- #
156
- # @param str [String] string to validate
157
- # @raise [ArgumentError] if the string does not match SNN pattern
158
- #
159
- # @example Valid format
160
- # Sashite::Snn::Name.validate_format("CHESS") # No error
161
- # Sashite::Snn::Name.validate_format("shogi") # No error
162
- #
163
- # @example Invalid format
164
- # Sashite::Snn::Name.validate_format("Chess") # Raises ArgumentError
165
- def self.validate_format(str)
166
- raise ::ArgumentError, format(ERROR_INVALID_NAME, str.inspect) unless str.match?(SNN_PATTERN)
167
- end
168
- end
169
- end
170
- end