pgn2 1.4.0 → 1.5.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/CHANGELOG.md +35 -1
- data/TODO.md +38 -31
- data/lib/pgn/board.rb +39 -10
- data/lib/pgn/lexer.rb +9 -6
- data/lib/pgn/move_calculator.rb +33 -19
- data/lib/pgn/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: da98f302d6785b75581d53fbe6dab4b7ccad9f5514f7ad278be12a540633d572
|
|
4
|
+
data.tar.gz: 9b22f32a2b44bd06cfd8d29c82f9b4af8c1c4687ed9650df33c4d5fdc76dd49e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c99f7034728254221189f9af053bc6c57938d24c96d83d8dacce7ad78cf2bc8b23de3b28a497c9b750b12f63dd17a9ca6ed65b293c5f8f0188d4f79b188e5d2
|
|
7
|
+
data.tar.gz: 437fbf1d534aa15578bef06c4ca5020116f6b2385d81c886104f69bb8db0124d7e220b3d566a22f8df4e93180929519891ba71279db21e0e2f541f1b6f0f8892
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.5.0 (2026-08-13)
|
|
4
|
+
|
|
5
|
+
### Summary
|
|
6
|
+
|
|
7
|
+
Clarity refactor on the 0x88 hot path, plus a small parser perf win: no
|
|
8
|
+
public API changes, no behavior change; serialized PGN/FEN output stays
|
|
9
|
+
byte-identical; all 201 specs green.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **`PGN::Board`**: promoted the on-board bitmask test and square-name
|
|
13
|
+
lookup to public `on_board?(idx)` / `square_name(idx)` methods,
|
|
14
|
+
replacing private duplicates of the same logic in `MoveCalculator`.
|
|
15
|
+
Added a named `Board.from_cells(cells)` factory for building a board
|
|
16
|
+
directly from a 128-cell 0x88 array; `#dup` (called every move) now
|
|
17
|
+
routes through it instead of reaching around the constructor via
|
|
18
|
+
`Board.allocate` + `instance_variable_set` directly. Performance is
|
|
19
|
+
identical — same allocate + one ivar set — just a documented factory
|
|
20
|
+
instead of a reflective one-off.
|
|
21
|
+
- **`PGN::MoveCalculator`**: calls `board.on_board?`/`board.square_name`
|
|
22
|
+
instead of its own private copies; named the castling-table square
|
|
23
|
+
literals (`C1`..`G8`) instead of raw 0x88 integers in `CASTLING`, for
|
|
24
|
+
readability. `#board`/`#move` are now `attr_reader` instead of
|
|
25
|
+
`attr_accessor` — there was no external writer, and the `@dest_idx`
|
|
26
|
+
memo already silently relied on both never changing after
|
|
27
|
+
`#initialize`; dropping the setters enforces that invariant in the
|
|
28
|
+
type instead of by convention.
|
|
29
|
+
- **`PGN::Lexer`**: dropped the per-token `@line` counter (one
|
|
30
|
+
`str.count("\n")` via `advance_line` on every `next_token`/
|
|
31
|
+
`next_token_pair` call) in favor of a lazy `line_at(off)`, computed
|
|
32
|
+
only when a line number is actually needed (error messages, the spec
|
|
33
|
+
`tokens` helper) — the parser itself never reads it. Removes a
|
|
34
|
+
`str.count("\n")` call from the parse hot path (~6% of parse CPU for
|
|
35
|
+
a value that was never read).
|
|
36
|
+
|
|
37
|
+
## 1.4.0 (2026-08-13)
|
|
4
38
|
|
|
5
39
|
### Summary
|
|
6
40
|
|
data/TODO.md
CHANGED
|
@@ -17,31 +17,45 @@
|
|
|
17
17
|
`#tokens` spec helper. Parse allocations −42% (603537 → 347037 / 500 games).
|
|
18
18
|
- Speed up replay via a board-representation rewrite ("Approach B"): done.
|
|
19
19
|
(b) ✓ (done in 1.3.0) Rewrote `Board` internals to the classic 0x88
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
representation (128-cell array indexed by `rank*16+file`) and rewrote
|
|
21
|
+
`MoveCalculator` to work entirely in single-integer square indices via
|
|
22
|
+
`Board#at_index`/`#apply!`, so the replay hot path no longer allocates
|
|
23
|
+
`[file,rank]` coordinate arrays or square-name strings. Off-board is a
|
|
24
|
+
single bitmask (`(idx & 0x88).zero?`, ~1.6x faster than a 0..7 bounds
|
|
25
|
+
check) and ray stepping is a single integer add. Algorithm unchanged, so
|
|
26
|
+
output is byte-identical. Measured (immortal game): replay 798→535 µs/i
|
|
27
|
+
(+49% throughput), allocations 1571→976 objects (−38%) / 92440→62064 bytes
|
|
28
|
+
(−33%); parse+replay +21% throughput. 182 specs green, 0 new rubocop
|
|
29
|
+
offenses vs main. The public string/coord API is preserved (additive).
|
|
30
|
+
(c) One related idea was left alone during cleanup rather than "fixed",
|
|
31
|
+
since fixing it would cost more than it's worth right now: `Board#squares`
|
|
32
|
+
rebuilds the full 8x8 array from `@cells` on every call (9 allocations,
|
|
33
|
+
64 reads); it's off the replay hot path by design, but `FEN#to_s`
|
|
34
|
+
round-trips through it on every position-to-FEN call, so FEN generation
|
|
35
|
+
pays that cost repeatedly. Memoizing would mean invalidating the cache
|
|
36
|
+
from `update`/`apply!`, i.e. adding a write to the actual hot path to
|
|
37
|
+
speed up a path that isn't hot -- the wrong trade; if FEN generation
|
|
38
|
+
becomes hot, have it read `@cells` directly instead. Also considered
|
|
39
|
+
and not attempted: column-granularity copy-on-write in `dup` (the pre-0x88
|
|
40
|
+
Board only duplicated touched file-columns on write); the flat 0x88 array
|
|
41
|
+
trades that away for simplicity and the +49% throughput measured above,
|
|
42
|
+
and reintroducing it would need its own A/B before it's worth the
|
|
43
|
+
complexity.
|
|
30
44
|
(a) ✗ (attempted, rejected) A piece-location index (piece → 0x88 indices)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
maintained in `update`/`apply!` and used for O(1) slider/leaper/king
|
|
46
|
+
origin lookups. Implemented on top of (b), all 182 specs green, but it
|
|
47
|
+
**regressed**: replay 526→727 µs/i (+38% slower), allocations 976→1591
|
|
48
|
+
objects (+63%). Root cause: `Board#dup` (called every move) must clone
|
|
49
|
+
the index (`transform_values(&:dup)` ≈ 12 piece arrays) — Board#dup went
|
|
50
|
+
91→676 objects — and every move pays per-update index maintenance
|
|
51
|
+
(`<<`/`delete`) that pawns (the most common move type, whose origins are
|
|
52
|
+
geometry-fixed and can't use the index) pay for no benefit. The index
|
|
53
|
+
helps sliders/leapers (minority of moves) but the dup + maintenance cost
|
|
54
|
+
is paid by every move. Conclusion: a global piece index is a loss for
|
|
55
|
+
replay (where only ONE given move is validated, so ray-scanning from the
|
|
56
|
+
destination is already cheap); it pays in move-_generation_ libraries
|
|
57
|
+
(chess.js/python-chess) that enumerate ALL legal moves. Not worth a COW
|
|
58
|
+
variant either (maintenance + pawns). Reverted; (b) alone is the winner.
|
|
45
59
|
- Replace the right-recursive `tag_section`/`variation_list` rules in
|
|
46
60
|
`pgn_parser.y` with ordinary left-recursion plus one explicit `.reverse`
|
|
47
61
|
at the point each list is consumed, so the legacy whittle-order
|
|
@@ -53,10 +67,3 @@
|
|
|
53
67
|
to reuse as-is. The brace check is a bandaid for `clean_text` not fully
|
|
54
68
|
normalizing multi-line/nested comments in one pass; fixing that at the
|
|
55
69
|
source would let `moves=` reuse unconditionally.
|
|
56
|
-
- `MoveCalculator#destination_coords` memoizes into `@dest_coords` based on
|
|
57
|
-
`board`/`move`/`origin` never changing after `#initialize` — true today,
|
|
58
|
-
but only by convention, since `board`, `move`, `origin` are all public
|
|
59
|
-
`attr_accessor`s with no cache invalidation tied to their setters. If a
|
|
60
|
-
future caller ever mutates and reuses a `MoveCalculator` instance, this
|
|
61
|
-
memo goes stale silently. Either drop the public setters or invalidate
|
|
62
|
-
`@dest_coords` when they're used.
|
data/lib/pgn/board.rb
CHANGED
|
@@ -27,10 +27,10 @@ module PGN
|
|
|
27
27
|
].freeze
|
|
28
28
|
|
|
29
29
|
FILE_TO_INDEX = ('a'..'h').each_with_index.to_h
|
|
30
|
-
INDEX_TO_FILE = FILE_TO_INDEX.
|
|
30
|
+
INDEX_TO_FILE = FILE_TO_INDEX.invert
|
|
31
31
|
|
|
32
32
|
RANK_TO_INDEX = ('1'..'8').each_with_index.to_h
|
|
33
|
-
INDEX_TO_RANK = RANK_TO_INDEX.
|
|
33
|
+
INDEX_TO_RANK = RANK_TO_INDEX.invert
|
|
34
34
|
|
|
35
35
|
# algebraic to unicode piece lookup
|
|
36
36
|
#
|
|
@@ -102,7 +102,6 @@ module PGN
|
|
|
102
102
|
@cells[(r * 16) + f] = squares[f][r]
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
|
-
@cells
|
|
106
105
|
end
|
|
107
106
|
|
|
108
107
|
# @overload at(str)
|
|
@@ -119,9 +118,9 @@ module PGN
|
|
|
119
118
|
# board.at("e4") #=> "P"
|
|
120
119
|
#
|
|
121
120
|
def at(arg0, arg1 = nil)
|
|
122
|
-
return
|
|
121
|
+
return at_index(index_for(arg0, arg1)) unless arg1.nil?
|
|
123
122
|
|
|
124
|
-
|
|
123
|
+
at_index(index_of(arg0))
|
|
125
124
|
end
|
|
126
125
|
|
|
127
126
|
# @param changes [Hash<String, <String, nil>>] changes to make to the board
|
|
@@ -141,8 +140,7 @@ module PGN
|
|
|
141
140
|
# board.update("e4", "P")
|
|
142
141
|
#
|
|
143
142
|
def update(square, piece)
|
|
144
|
-
|
|
145
|
-
self
|
|
143
|
+
update_index(index_of(square), piece)
|
|
146
144
|
end
|
|
147
145
|
|
|
148
146
|
# @param position [String] the square in algebraic notation
|
|
@@ -173,13 +171,25 @@ module PGN
|
|
|
173
171
|
end.join("\n")
|
|
174
172
|
end
|
|
175
173
|
|
|
174
|
+
# Build a {Board} directly from a 0x88 cell array, bypassing the 8x8
|
|
175
|
+
# -> 0x88 conversion in {#initialize}. Used by {#dup} (which runs every
|
|
176
|
+
# move) to skip the per-square rebuild; the cell array is already in the
|
|
177
|
+
# canonical 128-cell layout.
|
|
178
|
+
#
|
|
179
|
+
# @param cells [Array<String, nil>] a 128-cell 0x88 array
|
|
180
|
+
# @return [PGN::Board]
|
|
181
|
+
#
|
|
182
|
+
def self.from_cells(cells)
|
|
183
|
+
board = allocate
|
|
184
|
+
board.instance_variable_set(:@cells, cells)
|
|
185
|
+
board
|
|
186
|
+
end
|
|
187
|
+
|
|
176
188
|
# @return [PGN::Board] a copy of self. Copies the 128-cell 0x88 array;
|
|
177
189
|
# mutations to the copy do not affect the original.
|
|
178
190
|
#
|
|
179
191
|
def dup
|
|
180
|
-
|
|
181
|
-
copy.instance_variable_set(:@cells, @cells.dup)
|
|
182
|
-
copy
|
|
192
|
+
self.class.from_cells(@cells.dup)
|
|
183
193
|
end
|
|
184
194
|
|
|
185
195
|
# -- 0x88 hot-path API (integer indices) ---------------------------------
|
|
@@ -235,6 +245,25 @@ module PGN
|
|
|
235
245
|
self
|
|
236
246
|
end
|
|
237
247
|
|
|
248
|
+
# Whether a 0x88 index is on the board (see the class doc for the
|
|
249
|
+
# bitmask this tests).
|
|
250
|
+
#
|
|
251
|
+
# @param idx [Integer] a 0x88 square index
|
|
252
|
+
# @return [Boolean]
|
|
253
|
+
#
|
|
254
|
+
def on_board?(idx)
|
|
255
|
+
(idx & 0x88).zero? # rubocop:disable Style/BitwisePredicate
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# The algebraic square name of a 0x88 index.
|
|
259
|
+
#
|
|
260
|
+
# @param idx [Integer] a 0x88 square index
|
|
261
|
+
# @return [String] e.g. "e4"
|
|
262
|
+
#
|
|
263
|
+
def square_name(idx)
|
|
264
|
+
INDEX_TO_FILE[idx & 0x0F] + INDEX_TO_RANK[idx >> 4]
|
|
265
|
+
end
|
|
266
|
+
|
|
238
267
|
private
|
|
239
268
|
|
|
240
269
|
def file_of(square)
|
data/lib/pgn/lexer.rb
CHANGED
|
@@ -161,7 +161,6 @@ module PGN
|
|
|
161
161
|
def initialize(input)
|
|
162
162
|
@input = input
|
|
163
163
|
@ss = StringScanner.new(input)
|
|
164
|
-
@line = 1
|
|
165
164
|
@game_starts = []
|
|
166
165
|
@between_games = true # at start we are "between" games
|
|
167
166
|
end
|
|
@@ -182,7 +181,8 @@ module PGN
|
|
|
182
181
|
type, value = next_token_pair
|
|
183
182
|
return nil unless type
|
|
184
183
|
|
|
185
|
-
Token.new(type: type, value: value, offset: @last_offset,
|
|
184
|
+
Token.new(type: type, value: value, offset: @last_offset,
|
|
185
|
+
line: line_at(@last_offset))
|
|
186
186
|
end
|
|
187
187
|
|
|
188
188
|
# Fast path for the parser: returns [type, value] for the next
|
|
@@ -203,7 +203,6 @@ module PGN
|
|
|
203
203
|
end
|
|
204
204
|
|
|
205
205
|
value = scan_one
|
|
206
|
-
advance_line(value)
|
|
207
206
|
next if @scan_discarded
|
|
208
207
|
|
|
209
208
|
note_token(@scan_type, off)
|
|
@@ -231,7 +230,7 @@ module PGN
|
|
|
231
230
|
return m
|
|
232
231
|
end
|
|
233
232
|
raise UnconsumedInputError,
|
|
234
|
-
"Unmatched input #{@input.byteslice(@ss.pos..).inspect} on line #{@
|
|
233
|
+
"Unmatched input #{@input.byteslice(@ss.pos..).inspect} on line #{line_at(@ss.pos)}"
|
|
235
234
|
end
|
|
236
235
|
|
|
237
236
|
# Track per-game content-start offsets for verbatim pgn slicing.
|
|
@@ -246,8 +245,12 @@ module PGN
|
|
|
246
245
|
end
|
|
247
246
|
end
|
|
248
247
|
|
|
249
|
-
|
|
250
|
-
|
|
248
|
+
# Line number at a byte offset, computed lazily only for error messages
|
|
249
|
+
# and the spec `tokens` helper. Keeping a running `@line` on the parse hot
|
|
250
|
+
# path (one `str.count("\n")` per token) was ~6% of parse CPU for a value
|
|
251
|
+
# the parser never reads.
|
|
252
|
+
def line_at(off)
|
|
253
|
+
1 + @input.byteslice(0, off).count("\n")
|
|
251
254
|
end
|
|
252
255
|
end
|
|
253
256
|
|
data/lib/pgn/move_calculator.rb
CHANGED
|
@@ -30,7 +30,7 @@ module PGN
|
|
|
30
30
|
# 0x88 single-step offsets for knight and king.
|
|
31
31
|
#
|
|
32
32
|
STEP = {
|
|
33
|
-
'k' => [
|
|
33
|
+
'k' => SLIDE['q'],
|
|
34
34
|
'n' => [33, 31, -31, -33, 18, 14, -14, -18]
|
|
35
35
|
}.freeze
|
|
36
36
|
|
|
@@ -42,15 +42,6 @@ module PGN
|
|
|
42
42
|
'p' => { capture: [15, 17], normal: [16], double: [32] }
|
|
43
43
|
}.freeze
|
|
44
44
|
|
|
45
|
-
# The squares to update for each castling move, keyed by 0x88 index.
|
|
46
|
-
#
|
|
47
|
-
CASTLING = {
|
|
48
|
-
'Q' => { 0 => nil, 2 => 'K', 3 => 'R', 4 => nil },
|
|
49
|
-
'K' => { 4 => nil, 5 => 'R', 6 => 'K', 7 => nil },
|
|
50
|
-
'q' => { 112 => nil, 114 => 'k', 115 => 'r', 116 => nil },
|
|
51
|
-
'k' => { 116 => nil, 117 => 'r', 118 => 'k', 119 => nil }
|
|
52
|
-
}.freeze
|
|
53
|
-
|
|
54
45
|
# Corner-square 0x88 indices, used for castling-restriction bookkeeping
|
|
55
46
|
# (a rook leaving or being captured on a corner drops the matching right).
|
|
56
47
|
#
|
|
@@ -59,6 +50,29 @@ module PGN
|
|
|
59
50
|
A8 = 112
|
|
60
51
|
H8 = 119
|
|
61
52
|
|
|
53
|
+
# King/rook landing squares for castling, named for readability in
|
|
54
|
+
# {CASTLING} below.
|
|
55
|
+
#
|
|
56
|
+
C1 = 2
|
|
57
|
+
D1 = 3
|
|
58
|
+
E1 = 4
|
|
59
|
+
F1 = 5
|
|
60
|
+
G1 = 6
|
|
61
|
+
C8 = 114
|
|
62
|
+
D8 = 115
|
|
63
|
+
E8 = 116
|
|
64
|
+
F8 = 117
|
|
65
|
+
G8 = 118
|
|
66
|
+
|
|
67
|
+
# The squares to update for each castling move, keyed by 0x88 index.
|
|
68
|
+
#
|
|
69
|
+
CASTLING = {
|
|
70
|
+
'Q' => { A1 => nil, C1 => 'K', D1 => 'R', E1 => nil },
|
|
71
|
+
'K' => { E1 => nil, F1 => 'R', G1 => 'K', H1 => nil },
|
|
72
|
+
'q' => { A8 => nil, C8 => 'k', D8 => 'r', E8 => nil },
|
|
73
|
+
'k' => { E8 => nil, F8 => 'r', G8 => 'k', H8 => nil }
|
|
74
|
+
}.freeze
|
|
75
|
+
|
|
62
76
|
# rook-origin (0x88 index) -> castling restriction it drops.
|
|
63
77
|
#
|
|
64
78
|
ROOK_RESTRICTIONS = { A1 => 'Q', H1 => 'K', A8 => 'q', H8 => 'k' }.freeze
|
|
@@ -69,14 +83,14 @@ module PGN
|
|
|
69
83
|
WHITE_CASTLE = %w[K Q].freeze
|
|
70
84
|
BLACK_CASTLE = %w[k q].freeze
|
|
71
85
|
|
|
72
|
-
|
|
86
|
+
attr_reader :board, :move
|
|
73
87
|
|
|
74
88
|
# @param board [PGN::Board] the current board
|
|
75
89
|
# @param move [PGN::Move] the current move
|
|
76
90
|
#
|
|
77
91
|
def initialize(board, move)
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
@board = board
|
|
93
|
+
@move = move
|
|
80
94
|
@origin_idx = compute_origin
|
|
81
95
|
end
|
|
82
96
|
|
|
@@ -87,7 +101,7 @@ module PGN
|
|
|
87
101
|
def origin
|
|
88
102
|
return nil if @origin_idx.nil?
|
|
89
103
|
|
|
90
|
-
board.
|
|
104
|
+
board.square_name(@origin_idx)
|
|
91
105
|
end
|
|
92
106
|
|
|
93
107
|
# @return [PGN::Board] the board after the move is made
|
|
@@ -215,7 +229,7 @@ module PGN
|
|
|
215
229
|
possibilities = []
|
|
216
230
|
offsets.each do |off|
|
|
217
231
|
target = dest + off
|
|
218
|
-
next unless (target
|
|
232
|
+
next unless board.on_board?(target)
|
|
219
233
|
|
|
220
234
|
possibilities << target if board.at_index(target) == move.piece
|
|
221
235
|
end
|
|
@@ -250,7 +264,7 @@ module PGN
|
|
|
250
264
|
return possibilities unless move.disambiguation
|
|
251
265
|
|
|
252
266
|
possibilities.select do |idx|
|
|
253
|
-
board.
|
|
267
|
+
board.square_name(idx).match(move.disambiguation)
|
|
254
268
|
end
|
|
255
269
|
end
|
|
256
270
|
|
|
@@ -290,7 +304,7 @@ module PGN
|
|
|
290
304
|
#
|
|
291
305
|
def first_piece(idx, off)
|
|
292
306
|
idx += off
|
|
293
|
-
while (idx
|
|
307
|
+
while board.on_board?(idx)
|
|
294
308
|
square = board.at_index(idx)
|
|
295
309
|
return idx if square
|
|
296
310
|
|
|
@@ -315,7 +329,7 @@ module PGN
|
|
|
315
329
|
return nil if move.castle
|
|
316
330
|
return nil unless move.capture && board.at_index(dest_idx).nil?
|
|
317
331
|
|
|
318
|
-
(
|
|
332
|
+
board.index_for(dest_idx & 0x0F, origin_rank)
|
|
319
333
|
end
|
|
320
334
|
|
|
321
335
|
def king_position
|
|
@@ -323,7 +337,7 @@ module PGN
|
|
|
323
337
|
|
|
324
338
|
0.upto(7) do |rank|
|
|
325
339
|
0.upto(7) do |file|
|
|
326
|
-
idx = (rank
|
|
340
|
+
idx = board.index_for(file, rank)
|
|
327
341
|
return idx if board.at_index(idx) == king
|
|
328
342
|
end
|
|
329
343
|
end
|
data/lib/pgn/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pgn2
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stacey Touset
|
|
8
8
|
- Murilo Vasconcelos
|
|
9
|
+
autorequire:
|
|
9
10
|
bindir: bin
|
|
10
11
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
+
date: 2026-08-13 00:00:00.000000000 Z
|
|
12
13
|
dependencies:
|
|
13
14
|
- !ruby/object:Gem::Dependency
|
|
14
15
|
name: bundler
|
|
@@ -209,6 +210,7 @@ metadata:
|
|
|
209
210
|
source_code_uri: https://github.com/muriloime/pgn
|
|
210
211
|
bug_tracker_uri: https://github.com/muriloime/pgn/issues
|
|
211
212
|
changelog_uri: https://github.com/muriloime/pgn/blob/main/CHANGELOG.md
|
|
213
|
+
post_install_message:
|
|
212
214
|
rdoc_options: []
|
|
213
215
|
require_paths:
|
|
214
216
|
- lib
|
|
@@ -223,7 +225,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
223
225
|
- !ruby/object:Gem::Version
|
|
224
226
|
version: '0'
|
|
225
227
|
requirements: []
|
|
226
|
-
rubygems_version:
|
|
228
|
+
rubygems_version: 3.5.22
|
|
229
|
+
signing_key:
|
|
227
230
|
specification_version: 4
|
|
228
231
|
summary: A PGN parser for Ruby
|
|
229
232
|
test_files:
|