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.
- checksums.yaml +4 -4
- data/LICENSE +201 -0
- data/README.md +94 -306
- data/lib/sashite/snn/constants.rb +25 -0
- data/lib/sashite/snn/errors/argument/messages.rb +28 -0
- data/lib/sashite/snn/errors/argument.rb +21 -0
- data/lib/sashite/snn/errors.rb +11 -0
- data/lib/sashite/snn/parser.rb +145 -0
- data/lib/sashite/snn/style_name.rb +100 -0
- data/lib/sashite/snn.rb +39 -76
- data/lib/sashite-snn.rb +1 -1
- metadata +17 -20
- data/LICENSE.md +0 -22
- data/lib/sashite/snn/name.rb +0 -170
|
@@ -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/
|
|
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
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
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
|
-
#
|
|
15
|
+
# @example Parsing
|
|
16
|
+
# snn = Sashite::Snn.parse("Chess")
|
|
17
|
+
# snn.name # => "Chess"
|
|
13
18
|
#
|
|
14
|
-
#
|
|
15
|
-
# "
|
|
16
|
-
# "chess"
|
|
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
|
-
#
|
|
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
|
-
#
|
|
25
|
+
# Parses an SNN string into a StyleName.
|
|
37
26
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
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
|
-
# @
|
|
42
|
-
#
|
|
31
|
+
# @example
|
|
32
|
+
# snn = Sashite::Snn.parse("Chess")
|
|
33
|
+
# snn.name # => "Chess"
|
|
43
34
|
#
|
|
44
|
-
# @example
|
|
45
|
-
# Sashite::Snn.
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
# @
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
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:
|
|
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
|
-
|
|
14
|
-
|
|
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
|
|
20
|
+
- LICENSE
|
|
31
21
|
- README.md
|
|
32
22
|
- lib/sashite-snn.rb
|
|
33
23
|
- lib/sashite/snn.rb
|
|
34
|
-
- lib/sashite/snn/
|
|
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
|
-
-
|
|
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:
|
|
56
|
+
rubygems_version: 4.0.3
|
|
60
57
|
specification_version: 4
|
|
61
|
-
summary: SNN (Style Name Notation)
|
|
62
|
-
|
|
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.
|
data/lib/sashite/snn/name.rb
DELETED
|
@@ -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
|