feen 5.0.0.beta2 → 5.0.0.beta3
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 +107 -86
- data/lib/feen/dumper/games_turn.rb +40 -65
- data/lib/feen/dumper/piece_placement.rb +102 -89
- data/lib/feen/dumper/pieces_in_hand/errors.rb +12 -0
- data/lib/feen/dumper/pieces_in_hand/no_pieces.rb +10 -0
- data/lib/feen/dumper/pieces_in_hand.rb +57 -41
- data/lib/feen/dumper.rb +63 -32
- data/lib/feen/parser/games_turn/errors.rb +14 -0
- data/lib/feen/parser/games_turn/valid_games_turn_pattern.rb +24 -0
- data/lib/feen/parser/games_turn.rb +32 -110
- data/lib/feen/parser/piece_placement.rb +490 -77
- data/lib/feen/parser/pieces_in_hand/errors.rb +14 -0
- data/lib/feen/parser/pieces_in_hand/no_pieces.rb +10 -0
- data/lib/feen/parser/pieces_in_hand/valid_format_pattern.rb +15 -0
- data/lib/feen/parser/pieces_in_hand.rb +53 -44
- data/lib/feen/parser.rb +67 -30
- data/lib/feen.rb +42 -76
- metadata +9 -6
- data/lib/feen/converter/from_fen.rb +0 -170
- data/lib/feen/converter/to_fen.rb +0 -153
- data/lib/feen/converter.rb +0 -16
- data/lib/feen/sanitizer.rb +0 -119
@@ -1,135 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative File.join("games_turn", "valid_games_turn_pattern")
|
4
|
+
require_relative File.join("games_turn", "errors")
|
5
|
+
|
3
6
|
module Feen
|
4
7
|
module Parser
|
5
8
|
# Handles parsing of the games turn section of a FEEN string
|
6
9
|
module GamesTurn
|
7
|
-
ERRORS = {
|
8
|
-
invalid_type: "Games turn must be a string, got %s",
|
9
|
-
empty_string: "Games turn string cannot be empty",
|
10
|
-
separator: "Games turn must contain exactly one '/' separator",
|
11
|
-
mixed_casing: "%s game has mixed case: %s",
|
12
|
-
casing_requirement: "One game must use uppercase letters and the other lowercase letters",
|
13
|
-
invalid_chars: "Invalid characters in %s game identifier: %s",
|
14
|
-
empty_identifier: "%s game identifier cannot be empty"
|
15
|
-
}.freeze
|
16
|
-
|
17
10
|
# Parses the games turn section of a FEEN string
|
18
11
|
#
|
19
12
|
# @param games_turn_str [String] FEEN games turn string
|
20
|
-
# @return [
|
13
|
+
# @return [Array<String>] Array containing [active_player, inactive_player]
|
21
14
|
# @raise [ArgumentError] If the input string is invalid
|
22
|
-
def self.parse(games_turn_str)
|
23
|
-
validate_games_turn_string(games_turn_str)
|
24
|
-
|
25
|
-
# Split by the forward slash
|
26
|
-
parts = games_turn_str.split("/")
|
27
|
-
active_player = parts[0]
|
28
|
-
inactive_player = parts[1]
|
29
|
-
|
30
|
-
# Determine casing
|
31
|
-
active_uppercase = contains_uppercase?(active_player)
|
32
|
-
|
33
|
-
# Build result hash
|
34
|
-
{
|
35
|
-
active_player: active_player,
|
36
|
-
inactive_player: inactive_player,
|
37
|
-
uppercase_game: active_uppercase ? active_player : inactive_player,
|
38
|
-
lowercase_game: active_uppercase ? inactive_player : active_player,
|
39
|
-
active_player_casing: active_uppercase ? :uppercase : :lowercase
|
40
|
-
}
|
41
|
-
end
|
42
|
-
|
43
|
-
# Validates the games turn string for syntax and semantics
|
44
15
|
#
|
45
|
-
# @
|
46
|
-
#
|
47
|
-
#
|
48
|
-
def self.validate_games_turn_string(str)
|
49
|
-
validate_basic_structure(str)
|
50
|
-
|
51
|
-
parts = str.split("/")
|
52
|
-
validate_game_identifiers(parts)
|
53
|
-
validate_casing_requirements(parts)
|
54
|
-
end
|
55
|
-
|
56
|
-
# Validates the basic structure of the games turn string
|
16
|
+
# @example Valid games turn string with uppercase first
|
17
|
+
# GamesTurn.parse("CHESS/shogi")
|
18
|
+
# # => ["CHESS", "shogi"]
|
57
19
|
#
|
58
|
-
# @
|
59
|
-
#
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
raise ArgumentError, ERRORS[:empty_string] if str.empty?
|
20
|
+
# @example Valid games turn string with lowercase first
|
21
|
+
# GamesTurn.parse("chess/SHOGI")
|
22
|
+
# # => ["chess", "SHOGI"]
|
23
|
+
def self.parse(games_turn_str)
|
24
|
+
validate_input_type(games_turn_str)
|
65
25
|
|
66
|
-
|
67
|
-
|
26
|
+
match = ValidGamesTurnPattern.match(games_turn_str)
|
27
|
+
raise ::ArgumentError, Errors[:invalid_format] unless match
|
68
28
|
|
69
|
-
|
29
|
+
extract_game_identifiers(**match.named_captures.transform_keys(&:to_sym))
|
70
30
|
end
|
71
31
|
|
72
|
-
# Validates the
|
32
|
+
# Validates that the input is a non-empty string
|
73
33
|
#
|
74
|
-
# @param
|
75
|
-
# @raise [ArgumentError] If
|
34
|
+
# @param str [String] Input string to validate
|
35
|
+
# @raise [ArgumentError] If input is not a string or is empty
|
76
36
|
# @return [void]
|
77
|
-
private_class_method def self.
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
raise ArgumentError, format(ERRORS[:empty_identifier], position.capitalize) if game_id.nil? || game_id.empty?
|
82
|
-
|
83
|
-
unless game_id.match?(/\A[a-zA-Z]+\z/)
|
84
|
-
invalid_chars = game_id.scan(/[^a-zA-Z]/).uniq.join(", ")
|
85
|
-
raise ArgumentError, format(ERRORS[:invalid_chars], position, invalid_chars)
|
86
|
-
end
|
87
|
-
end
|
37
|
+
private_class_method def self.validate_input_type(str)
|
38
|
+
raise ::ArgumentError, format(Errors[:invalid_type], str.class) unless str.is_a?(::String)
|
39
|
+
raise ::ArgumentError, Errors[:empty_string] if str.empty?
|
88
40
|
end
|
89
41
|
|
90
|
-
#
|
42
|
+
# Extracts game identifiers from regexp match captures
|
91
43
|
#
|
92
|
-
# @param
|
93
|
-
# @
|
94
|
-
# @
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
if active_uppercase && contains_lowercase?(parts[0])
|
103
|
-
raise ArgumentError, format(ERRORS[:mixed_casing], "Active", parts[0])
|
104
|
-
end
|
105
|
-
|
106
|
-
if inactive_uppercase && contains_lowercase?(parts[1])
|
107
|
-
raise ArgumentError, format(ERRORS[:mixed_casing], "Inactive", parts[1])
|
108
|
-
end
|
109
|
-
|
110
|
-
if !active_uppercase && contains_uppercase?(parts[0])
|
111
|
-
raise ArgumentError, format(ERRORS[:mixed_casing], "Active", parts[0])
|
44
|
+
# @param uppercase_first [String, nil] Uppercase identifier if it comes first
|
45
|
+
# @param lowercase_second [String, nil] Lowercase identifier if it comes second
|
46
|
+
# @param lowercase_first [String, nil] Lowercase identifier if it comes first
|
47
|
+
# @param uppercase_second [String, nil] Uppercase identifier if it comes second
|
48
|
+
# @return [Array<String>] Array containing [active_player, inactive_player]
|
49
|
+
private_class_method def self.extract_game_identifiers(uppercase_first: nil, lowercase_second: nil, lowercase_first: nil, uppercase_second: nil)
|
50
|
+
if uppercase_first
|
51
|
+
[uppercase_first, lowercase_second]
|
52
|
+
else
|
53
|
+
[lowercase_first, uppercase_second]
|
112
54
|
end
|
113
|
-
|
114
|
-
return unless !inactive_uppercase && contains_uppercase?(parts[1])
|
115
|
-
|
116
|
-
raise ArgumentError, format(ERRORS[:mixed_casing], "Inactive", parts[1])
|
117
|
-
end
|
118
|
-
|
119
|
-
# Checks if a string contains any uppercase letters
|
120
|
-
#
|
121
|
-
# @param str [String] String to check
|
122
|
-
# @return [Boolean] True if the string contains uppercase letters
|
123
|
-
def self.contains_uppercase?(str)
|
124
|
-
str.match?(/[A-Z]/)
|
125
|
-
end
|
126
|
-
|
127
|
-
# Checks if a string contains any lowercase letters
|
128
|
-
#
|
129
|
-
# @param str [String] String to check
|
130
|
-
# @return [Boolean] True if the string contains lowercase letters
|
131
|
-
def self.contains_lowercase?(str)
|
132
|
-
str.match?(/[a-z]/)
|
133
55
|
end
|
134
56
|
end
|
135
57
|
end
|