pgn-clean-cg 0.0.0rc2__tar.gz
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.
- pgn_clean_cg-0.0.0rc2/CONTRIBUTING.rst +132 -0
- pgn_clean_cg-0.0.0rc2/LICENSE +18 -0
- pgn_clean_cg-0.0.0rc2/MANIFEST.in +2 -0
- pgn_clean_cg-0.0.0rc2/PKG-INFO +200 -0
- pgn_clean_cg-0.0.0rc2/README.rst +56 -0
- pgn_clean_cg-0.0.0rc2/pgn_clean_cg/__init__.py +4 -0
- pgn_clean_cg-0.0.0rc2/pgn_clean_cg/__main__.py +9 -0
- pgn_clean_cg-0.0.0rc2/pgn_clean_cg/cli.py +100 -0
- pgn_clean_cg-0.0.0rc2/pgn_clean_cg/pgnutils.py +125 -0
- pgn_clean_cg-0.0.0rc2/pgn_clean_cg.egg-info/PKG-INFO +200 -0
- pgn_clean_cg-0.0.0rc2/pgn_clean_cg.egg-info/SOURCES.txt +24 -0
- pgn_clean_cg-0.0.0rc2/pgn_clean_cg.egg-info/dependency_links.txt +1 -0
- pgn_clean_cg-0.0.0rc2/pgn_clean_cg.egg-info/entry_points.txt +2 -0
- pgn_clean_cg-0.0.0rc2/pgn_clean_cg.egg-info/requires.txt +1 -0
- pgn_clean_cg-0.0.0rc2/pgn_clean_cg.egg-info/top_level.txt +1 -0
- pgn_clean_cg-0.0.0rc2/pyproject.toml +43 -0
- pgn_clean_cg-0.0.0rc2/setup.cfg +4 -0
- pgn_clean_cg-0.0.0rc2/tests/expected.pgn +23 -0
- pgn_clean_cg-0.0.0rc2/tests/expected.pgn.license +3 -0
- pgn_clean_cg-0.0.0rc2/tests/source.pgn +23 -0
- pgn_clean_cg-0.0.0rc2/tests/source.pgn.license +2 -0
- pgn_clean_cg-0.0.0rc2/tests/source_with_clk.pgn +25 -0
- pgn_clean_cg-0.0.0rc2/tests/source_with_clk.pgn.license +2 -0
- pgn_clean_cg-0.0.0rc2/tests/source_with_computer_eval.pgn +26 -0
- pgn_clean_cg-0.0.0rc2/tests/source_with_computer_eval.pgn.license +3 -0
- pgn_clean_cg-0.0.0rc2/tests/test_pgnutils.py +81 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
.. SPDX-FileCopyrightText: 2026 Espen Sandøy Hustad <espen@ehustad.com>
|
|
2
|
+
.. SPDX-License-Identifier: CC-BY-SA-4.0
|
|
3
|
+
|
|
4
|
+
Contributing to pgn-clean-cg
|
|
5
|
+
============================
|
|
6
|
+
|
|
7
|
+
First of all, thank you for considering contributing to pgn-clean-cg.
|
|
8
|
+
|
|
9
|
+
Before you open a pull request, please make sure that the code is correctly
|
|
10
|
+
formatted, and you have not introduced any new linter errors.
|
|
11
|
+
|
|
12
|
+
By submitting a pull request you certify that:
|
|
13
|
+
|
|
14
|
+
* You are the author of the contribution, or have the legal right to submit it.
|
|
15
|
+
* You either hold the copyright to the changes, or have the explicit legal
|
|
16
|
+
authorization to contribute them under this projects license.
|
|
17
|
+
* You understand the code.
|
|
18
|
+
* You accept full responsibility for it.
|
|
19
|
+
|
|
20
|
+
Requirements
|
|
21
|
+
------------
|
|
22
|
+
|
|
23
|
+
In order to checkout the code and initialize the development environment you will need:
|
|
24
|
+
|
|
25
|
+
* Git
|
|
26
|
+
* Python 3
|
|
27
|
+
* Pip
|
|
28
|
+
* Automake
|
|
29
|
+
|
|
30
|
+
These packages can be installed using your favourite package manager on Linux.
|
|
31
|
+
|
|
32
|
+
Getting the code
|
|
33
|
+
----------------
|
|
34
|
+
|
|
35
|
+
This project is using Git, and is hosted on `Codeberg`_.
|
|
36
|
+
|
|
37
|
+
.. code-block::
|
|
38
|
+
|
|
39
|
+
git clone ssh://git@codeberg.org/ehustad/pgn-cg-clean.git
|
|
40
|
+
cd pgn-clean-cg
|
|
41
|
+
|
|
42
|
+
Initializing the development environment
|
|
43
|
+
----------------------------------------
|
|
44
|
+
|
|
45
|
+
The development environment can be initialized by using the make target "init"
|
|
46
|
+
from the top level source directory. This will create a virtual environment using
|
|
47
|
+
the Python venv module in .venv, install development tools, and install
|
|
48
|
+
pgn-cg-clean in `editable mode`_.
|
|
49
|
+
|
|
50
|
+
.. code-block::
|
|
51
|
+
|
|
52
|
+
make init
|
|
53
|
+
|
|
54
|
+
The make targets "env", "install-dev-tools", and "install" can be used if you
|
|
55
|
+
want to do this in stages. Note that "install" depends on "install-dev-tools"
|
|
56
|
+
and both "install" and "install-dev-tools" depends on "env".
|
|
57
|
+
|
|
58
|
+
To initialize the virtual environment, run the make target "env" from the top level
|
|
59
|
+
source directory:
|
|
60
|
+
|
|
61
|
+
.. code-block::
|
|
62
|
+
|
|
63
|
+
make env
|
|
64
|
+
|
|
65
|
+
To install development tools, such as black, mypy, pylint, run the make target
|
|
66
|
+
"install-dev-tools". Note that this requires the virtual environment in .venv
|
|
67
|
+
to exist, as all packages will be installed to that virtual environment:
|
|
68
|
+
|
|
69
|
+
.. code-block::
|
|
70
|
+
|
|
71
|
+
make install-dev-tools
|
|
72
|
+
|
|
73
|
+
To install pgn-cg-clean in editable mode, run the make target "install" from the
|
|
74
|
+
top level source directory:
|
|
75
|
+
|
|
76
|
+
.. code-block::
|
|
77
|
+
|
|
78
|
+
make install
|
|
79
|
+
|
|
80
|
+
Formatting code
|
|
81
|
+
---------------
|
|
82
|
+
|
|
83
|
+
The code can formatted by using the make target "format" from the top level
|
|
84
|
+
source directory:
|
|
85
|
+
|
|
86
|
+
.. code-block::
|
|
87
|
+
|
|
88
|
+
make format
|
|
89
|
+
|
|
90
|
+
Running linters
|
|
91
|
+
---------------
|
|
92
|
+
|
|
93
|
+
The linters can be run using make the make target "lint" from the top level
|
|
94
|
+
source directory:
|
|
95
|
+
|
|
96
|
+
.. code-block::
|
|
97
|
+
|
|
98
|
+
make lint
|
|
99
|
+
|
|
100
|
+
Running unit tests
|
|
101
|
+
------------------
|
|
102
|
+
|
|
103
|
+
The unit tests can be run using the make target "test" from the top level
|
|
104
|
+
source directory:
|
|
105
|
+
|
|
106
|
+
.. code-block::
|
|
107
|
+
|
|
108
|
+
make test
|
|
109
|
+
|
|
110
|
+
Other make targets
|
|
111
|
+
------------------
|
|
112
|
+
|
|
113
|
+
Please run the make target "help" from the top level source directory, or see
|
|
114
|
+
Makefile.
|
|
115
|
+
|
|
116
|
+
.. code-block::
|
|
117
|
+
|
|
118
|
+
make help
|
|
119
|
+
|
|
120
|
+
Creating a pull request
|
|
121
|
+
-----------------------
|
|
122
|
+
|
|
123
|
+
Please see the `Codeberg documentation`_.
|
|
124
|
+
|
|
125
|
+
Thanks
|
|
126
|
+
------
|
|
127
|
+
|
|
128
|
+
Parts of this was based on the AI policy of the pip project.
|
|
129
|
+
|
|
130
|
+
.. _Codeberg: https://codeberg.org/ehustad/pgn-cg-clean/
|
|
131
|
+
.. _Codeberg documentation: https://docs.codeberg.org/collaborating/pull-requests-and-git-flow/
|
|
132
|
+
.. _editable mode: https://setuptools.pypa.io/en/latest/userguide/development_mode.html
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) <year> <copyright holders>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
+
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
9
|
+
following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
12
|
+
portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
15
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
16
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
18
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pgn-clean-cg
|
|
3
|
+
Version: 0.0.0rc2
|
|
4
|
+
Summary: Clean comments and Lichess computer analysis from pgn files
|
|
5
|
+
Author-email: Espen Sandøy Hustad <espen@ehustad.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Description-Content-Type: text/x-rst
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: chess
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
.. SPDX-FileCopyrightText: 2026 Espen Sandøy Hustad <espen@ehustad.com>
|
|
13
|
+
.. SPDX-License-Identifier: CC-BY-SA-4.0
|
|
14
|
+
|
|
15
|
+
pgn-clean-cg - A command line utility for removing Lichess computer analysis from pgn files
|
|
16
|
+
===========================================================================================
|
|
17
|
+
|
|
18
|
+
Are you tired of the computer spoiling all the fun before you even start to analyse your
|
|
19
|
+
most recent chess game? Look no further, pgn-clean-cg is a command line utility for cleaning
|
|
20
|
+
up pgn (Portable Game Notation) files.
|
|
21
|
+
|
|
22
|
+
* Remove per move computer evaluation.
|
|
23
|
+
* Remove per move comments, excluding clock times.
|
|
24
|
+
* Remove variations, excluding the main variation.
|
|
25
|
+
* Remove NAGs (Numeric Annotation Glyphs) such as ?, ??, !!, etc.
|
|
26
|
+
|
|
27
|
+
Usage
|
|
28
|
+
-----
|
|
29
|
+
|
|
30
|
+
Run the command "pgn-clean-cg" on one or more .pgn (Portable Game Notation) and specify destination .pgn file.
|
|
31
|
+
Example:
|
|
32
|
+
|
|
33
|
+
.. code-block::
|
|
34
|
+
|
|
35
|
+
pgn-clean-cg input1.pgn input2.pgn out.pgn
|
|
36
|
+
|
|
37
|
+
More verbose output can be added using the "-v" flag
|
|
38
|
+
|
|
39
|
+
.. code-block::
|
|
40
|
+
|
|
41
|
+
pgn-clean-cg -v input1.pgn input2.pgn out.pgn
|
|
42
|
+
|
|
43
|
+
The builtin help can be accessed using the "-h" flag
|
|
44
|
+
|
|
45
|
+
.. code-block::
|
|
46
|
+
|
|
47
|
+
pgn-clean-cg -h
|
|
48
|
+
|
|
49
|
+
Installation
|
|
50
|
+
------------
|
|
51
|
+
|
|
52
|
+
The following command install pgn-clean-cg from the `Python Package Index`_. You will need
|
|
53
|
+
a working installation of Python3 and pip.
|
|
54
|
+
|
|
55
|
+
.. code-block::
|
|
56
|
+
|
|
57
|
+
pip install --upgrade pgn-clean-cg
|
|
58
|
+
|
|
59
|
+
Feedback and Contributing
|
|
60
|
+
-------------------------
|
|
61
|
+
Need help, or found a bug? Feel free to open an `issue`_ over on `Codeberg`_.
|
|
62
|
+
|
|
63
|
+
If you want to get involved, please read `CONTRIBUTING.rst`_.
|
|
64
|
+
|
|
65
|
+
.. _CONTRIBUTING.rst: https://codeberg.org/ehustad/pgn-cg-clean/src/branch/main/CONTRIBUTING.rst
|
|
66
|
+
.. _issue: https://codeberg.org/ehustad/pgn-cg-clean/issues
|
|
67
|
+
.. _Python Package Index: https://pypi.org/project/pgn-cg-clean/
|
|
68
|
+
|
|
69
|
+
.. SPDX-FileCopyrightText: 2026 Espen Sandøy Hustad <espen@ehustad.com>
|
|
70
|
+
.. SPDX-License-Identifier: CC-BY-SA-4.0
|
|
71
|
+
|
|
72
|
+
Contributing to pgn-clean-cg
|
|
73
|
+
============================
|
|
74
|
+
|
|
75
|
+
First of all, thank you for considering contributing to pgn-clean-cg.
|
|
76
|
+
|
|
77
|
+
Before you open a pull request, please make sure that the code is correctly
|
|
78
|
+
formatted, and you have not introduced any new linter errors.
|
|
79
|
+
|
|
80
|
+
By submitting a pull request you certify that:
|
|
81
|
+
|
|
82
|
+
* You are the author of the contribution, or have the legal right to submit it.
|
|
83
|
+
* You either hold the copyright to the changes, or have the explicit legal
|
|
84
|
+
authorization to contribute them under this projects license.
|
|
85
|
+
* You understand the code.
|
|
86
|
+
* You accept full responsibility for it.
|
|
87
|
+
|
|
88
|
+
Requirements
|
|
89
|
+
------------
|
|
90
|
+
|
|
91
|
+
In order to checkout the code and initialize the development environment you will need:
|
|
92
|
+
|
|
93
|
+
* Git
|
|
94
|
+
* Python 3
|
|
95
|
+
* Pip
|
|
96
|
+
* Automake
|
|
97
|
+
|
|
98
|
+
These packages can be installed using your favourite package manager on Linux.
|
|
99
|
+
|
|
100
|
+
Getting the code
|
|
101
|
+
----------------
|
|
102
|
+
|
|
103
|
+
This project is using Git, and is hosted on `Codeberg`_.
|
|
104
|
+
|
|
105
|
+
.. code-block::
|
|
106
|
+
|
|
107
|
+
git clone ssh://git@codeberg.org/ehustad/pgn-cg-clean.git
|
|
108
|
+
cd pgn-clean-cg
|
|
109
|
+
|
|
110
|
+
Initializing the development environment
|
|
111
|
+
----------------------------------------
|
|
112
|
+
|
|
113
|
+
The development environment can be initialized by using the make target "init"
|
|
114
|
+
from the top level source directory. This will create a virtual environment using
|
|
115
|
+
the Python venv module in .venv, install development tools, and install
|
|
116
|
+
pgn-cg-clean in `editable mode`_.
|
|
117
|
+
|
|
118
|
+
.. code-block::
|
|
119
|
+
|
|
120
|
+
make init
|
|
121
|
+
|
|
122
|
+
The make targets "env", "install-dev-tools", and "install" can be used if you
|
|
123
|
+
want to do this in stages. Note that "install" depends on "install-dev-tools"
|
|
124
|
+
and both "install" and "install-dev-tools" depends on "env".
|
|
125
|
+
|
|
126
|
+
To initialize the virtual environment, run the make target "env" from the top level
|
|
127
|
+
source directory:
|
|
128
|
+
|
|
129
|
+
.. code-block::
|
|
130
|
+
|
|
131
|
+
make env
|
|
132
|
+
|
|
133
|
+
To install development tools, such as black, mypy, pylint, run the make target
|
|
134
|
+
"install-dev-tools". Note that this requires the virtual environment in .venv
|
|
135
|
+
to exist, as all packages will be installed to that virtual environment:
|
|
136
|
+
|
|
137
|
+
.. code-block::
|
|
138
|
+
|
|
139
|
+
make install-dev-tools
|
|
140
|
+
|
|
141
|
+
To install pgn-cg-clean in editable mode, run the make target "install" from the
|
|
142
|
+
top level source directory:
|
|
143
|
+
|
|
144
|
+
.. code-block::
|
|
145
|
+
|
|
146
|
+
make install
|
|
147
|
+
|
|
148
|
+
Formatting code
|
|
149
|
+
---------------
|
|
150
|
+
|
|
151
|
+
The code can formatted by using the make target "format" from the top level
|
|
152
|
+
source directory:
|
|
153
|
+
|
|
154
|
+
.. code-block::
|
|
155
|
+
|
|
156
|
+
make format
|
|
157
|
+
|
|
158
|
+
Running linters
|
|
159
|
+
---------------
|
|
160
|
+
|
|
161
|
+
The linters can be run using make the make target "lint" from the top level
|
|
162
|
+
source directory:
|
|
163
|
+
|
|
164
|
+
.. code-block::
|
|
165
|
+
|
|
166
|
+
make lint
|
|
167
|
+
|
|
168
|
+
Running unit tests
|
|
169
|
+
------------------
|
|
170
|
+
|
|
171
|
+
The unit tests can be run using the make target "test" from the top level
|
|
172
|
+
source directory:
|
|
173
|
+
|
|
174
|
+
.. code-block::
|
|
175
|
+
|
|
176
|
+
make test
|
|
177
|
+
|
|
178
|
+
Other make targets
|
|
179
|
+
------------------
|
|
180
|
+
|
|
181
|
+
Please run the make target "help" from the top level source directory, or see
|
|
182
|
+
Makefile.
|
|
183
|
+
|
|
184
|
+
.. code-block::
|
|
185
|
+
|
|
186
|
+
make help
|
|
187
|
+
|
|
188
|
+
Creating a pull request
|
|
189
|
+
-----------------------
|
|
190
|
+
|
|
191
|
+
Please see the `Codeberg documentation`_.
|
|
192
|
+
|
|
193
|
+
Thanks
|
|
194
|
+
------
|
|
195
|
+
|
|
196
|
+
Parts of this was based on the AI policy of the pip project.
|
|
197
|
+
|
|
198
|
+
.. _Codeberg: https://codeberg.org/ehustad/pgn-cg-clean/
|
|
199
|
+
.. _Codeberg documentation: https://docs.codeberg.org/collaborating/pull-requests-and-git-flow/
|
|
200
|
+
.. _editable mode: https://setuptools.pypa.io/en/latest/userguide/development_mode.html
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
.. SPDX-FileCopyrightText: 2026 Espen Sandøy Hustad <espen@ehustad.com>
|
|
2
|
+
.. SPDX-License-Identifier: CC-BY-SA-4.0
|
|
3
|
+
|
|
4
|
+
pgn-clean-cg - A command line utility for removing Lichess computer analysis from pgn files
|
|
5
|
+
===========================================================================================
|
|
6
|
+
|
|
7
|
+
Are you tired of the computer spoiling all the fun before you even start to analyse your
|
|
8
|
+
most recent chess game? Look no further, pgn-clean-cg is a command line utility for cleaning
|
|
9
|
+
up pgn (Portable Game Notation) files.
|
|
10
|
+
|
|
11
|
+
* Remove per move computer evaluation.
|
|
12
|
+
* Remove per move comments, excluding clock times.
|
|
13
|
+
* Remove variations, excluding the main variation.
|
|
14
|
+
* Remove NAGs (Numeric Annotation Glyphs) such as ?, ??, !!, etc.
|
|
15
|
+
|
|
16
|
+
Usage
|
|
17
|
+
-----
|
|
18
|
+
|
|
19
|
+
Run the command "pgn-clean-cg" on one or more .pgn (Portable Game Notation) and specify destination .pgn file.
|
|
20
|
+
Example:
|
|
21
|
+
|
|
22
|
+
.. code-block::
|
|
23
|
+
|
|
24
|
+
pgn-clean-cg input1.pgn input2.pgn out.pgn
|
|
25
|
+
|
|
26
|
+
More verbose output can be added using the "-v" flag
|
|
27
|
+
|
|
28
|
+
.. code-block::
|
|
29
|
+
|
|
30
|
+
pgn-clean-cg -v input1.pgn input2.pgn out.pgn
|
|
31
|
+
|
|
32
|
+
The builtin help can be accessed using the "-h" flag
|
|
33
|
+
|
|
34
|
+
.. code-block::
|
|
35
|
+
|
|
36
|
+
pgn-clean-cg -h
|
|
37
|
+
|
|
38
|
+
Installation
|
|
39
|
+
------------
|
|
40
|
+
|
|
41
|
+
The following command install pgn-clean-cg from the `Python Package Index`_. You will need
|
|
42
|
+
a working installation of Python3 and pip.
|
|
43
|
+
|
|
44
|
+
.. code-block::
|
|
45
|
+
|
|
46
|
+
pip install --upgrade pgn-clean-cg
|
|
47
|
+
|
|
48
|
+
Feedback and Contributing
|
|
49
|
+
-------------------------
|
|
50
|
+
Need help, or found a bug? Feel free to open an `issue`_ over on `Codeberg`_.
|
|
51
|
+
|
|
52
|
+
If you want to get involved, please read `CONTRIBUTING.rst`_.
|
|
53
|
+
|
|
54
|
+
.. _CONTRIBUTING.rst: https://codeberg.org/ehustad/pgn-cg-clean/src/branch/main/CONTRIBUTING.rst
|
|
55
|
+
.. _issue: https://codeberg.org/ehustad/pgn-cg-clean/issues
|
|
56
|
+
.. _Python Package Index: https://pypi.org/project/pgn-cg-clean/
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Espen Sandøy Hustad <espen@ehustad.com>
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from . import pgnutils
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VerboseVisitor(pgnutils.BaseVisitor):
|
|
13
|
+
def begin_game(self, gameinfo: pgnutils.GameInfo):
|
|
14
|
+
print(
|
|
15
|
+
f"\tCleaning game: {gameinfo.date} - {gameinfo.players} - {gameinfo.result}"
|
|
16
|
+
f" in {gameinfo.opening}"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def inputfile(filename: str) -> Path:
|
|
21
|
+
p = Path(filename)
|
|
22
|
+
|
|
23
|
+
if not p.is_file():
|
|
24
|
+
raise argparse.ArgumentTypeError(f"The file '{p}' does not exist.")
|
|
25
|
+
|
|
26
|
+
return p
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def outputfile(filename: str) -> Path:
|
|
30
|
+
p = Path(filename)
|
|
31
|
+
|
|
32
|
+
if p.is_dir():
|
|
33
|
+
raise argparse.ArgumentTypeError(f"'{p}' is a directory.")
|
|
34
|
+
return p
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main() -> int:
|
|
38
|
+
parser = argparse.ArgumentParser(
|
|
39
|
+
prog="pgn-clean-cg",
|
|
40
|
+
description="Clean pgn files for comments, variations, and Lichess computer analysis.",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"-v", "--verbose", action="store_true", help="Increase verbosity"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"input",
|
|
49
|
+
type=inputfile,
|
|
50
|
+
metavar="INPUT_FILES",
|
|
51
|
+
nargs="+",
|
|
52
|
+
help="One or more input .pgn file(s)",
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"output", type=outputfile, metavar="OUTPUT_FILE", help="Output .pgn file"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
args = parser.parse_args()
|
|
59
|
+
|
|
60
|
+
if args.output.exists():
|
|
61
|
+
overwrite = str(
|
|
62
|
+
input(
|
|
63
|
+
f"The file {args.output} already exists. Do you want to overwrite (y/N)?"
|
|
64
|
+
)
|
|
65
|
+
).lower()
|
|
66
|
+
if overwrite != "y":
|
|
67
|
+
return 1
|
|
68
|
+
|
|
69
|
+
fixed_games = []
|
|
70
|
+
cg_cleaner = pgnutils.CGCleaner()
|
|
71
|
+
|
|
72
|
+
if args.verbose:
|
|
73
|
+
cg_cleaner.register_visitor(VerboseVisitor())
|
|
74
|
+
|
|
75
|
+
for file in args.input:
|
|
76
|
+
try:
|
|
77
|
+
with file.open() as f:
|
|
78
|
+
print(f"Cleaning file: '{file}'")
|
|
79
|
+
fixed_games.extend(cg_cleaner.clean(f))
|
|
80
|
+
except (OSError, PermissionError) as e:
|
|
81
|
+
skip = str(
|
|
82
|
+
input(f"Failed to open file: {e}. Do you want to skip file (Y/n)?")
|
|
83
|
+
).lower()
|
|
84
|
+
if skip != "y":
|
|
85
|
+
return 1
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
with args.output.open("w") as f:
|
|
89
|
+
for game in fixed_games:
|
|
90
|
+
print(game, file=f, end="\n\n")
|
|
91
|
+
|
|
92
|
+
except (OSError, PermissionError) as e:
|
|
93
|
+
print(f"Aborting: Failed to open file for writing: {e}.", file=sys.stderr)
|
|
94
|
+
return 1
|
|
95
|
+
|
|
96
|
+
return 0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
sys.exit(main())
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Espen Sandøy Hustad <espen@ehustad.com>
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import List, TextIO
|
|
6
|
+
|
|
7
|
+
import chess.pgn
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _extract_item_from_list(l: chess.pgn.Headers, item: str, default_value: str) -> str:
|
|
11
|
+
try:
|
|
12
|
+
return l[item]
|
|
13
|
+
except KeyError:
|
|
14
|
+
return default_value
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CGCleaner:
|
|
18
|
+
def clean(self, pgn: TextIO) -> List[chess.pgn.Game]:
|
|
19
|
+
ret = []
|
|
20
|
+
|
|
21
|
+
while True:
|
|
22
|
+
game = chess.pgn.read_game(pgn)
|
|
23
|
+
if game is None:
|
|
24
|
+
break
|
|
25
|
+
|
|
26
|
+
ret.append(self._clean_game(game))
|
|
27
|
+
|
|
28
|
+
return ret
|
|
29
|
+
|
|
30
|
+
def register_visitor(self, visitor: BaseVisitor):
|
|
31
|
+
self.visitor = visitor
|
|
32
|
+
|
|
33
|
+
def _clean_game(self, game: chess.pgn.Game) -> chess.pgn.Game:
|
|
34
|
+
gameinfo = self._build_gameinfo(game)
|
|
35
|
+
self.visitor.begin_game(gameinfo)
|
|
36
|
+
|
|
37
|
+
for move in game.mainline():
|
|
38
|
+
move.nags = set()
|
|
39
|
+
clock = move.clock()
|
|
40
|
+
move.comment = ""
|
|
41
|
+
move.set_clock(clock)
|
|
42
|
+
self._clean_variations(move)
|
|
43
|
+
|
|
44
|
+
self.visitor.end_game(gameinfo)
|
|
45
|
+
|
|
46
|
+
return game
|
|
47
|
+
|
|
48
|
+
def _build_gameinfo(self, game: chess.pgn.Game) -> GameInfo:
|
|
49
|
+
white = Player(
|
|
50
|
+
_extract_item_from_list(game.headers, "White", "N.N"),
|
|
51
|
+
_extract_item_from_list(game.headers, "WhiteElo", ""),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
black = Player(
|
|
55
|
+
_extract_item_from_list(game.headers, "Black", "N.N"),
|
|
56
|
+
_extract_item_from_list(game.headers, "BlackElo", ""),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
opening = Opening(
|
|
60
|
+
_extract_item_from_list(game.headers, "ECO", ""),
|
|
61
|
+
_extract_item_from_list(game.headers, "Opening", "Unknown opening"),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return GameInfo(
|
|
65
|
+
Players(white, black),
|
|
66
|
+
_extract_item_from_list(game.headers, "Date", "unknown date"),
|
|
67
|
+
_extract_item_from_list(game.headers, "Result", "unknown result"),
|
|
68
|
+
opening,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def _clean_variations(self, move: chess.pgn.ChildNode):
|
|
72
|
+
if move.variations:
|
|
73
|
+
move.variations = move.variations[:1]
|
|
74
|
+
|
|
75
|
+
def __init__(self):
|
|
76
|
+
self.visitor = BaseVisitor()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class Player:
|
|
81
|
+
name: str
|
|
82
|
+
elo: str
|
|
83
|
+
|
|
84
|
+
def __repr__(self):
|
|
85
|
+
if self.elo:
|
|
86
|
+
return f"{self.name} ({self.elo})"
|
|
87
|
+
|
|
88
|
+
return self.name
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class Players:
|
|
93
|
+
white: Player
|
|
94
|
+
black: Player
|
|
95
|
+
|
|
96
|
+
def __repr__(self):
|
|
97
|
+
return f"{self.white} vs {self.black}"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class Opening:
|
|
102
|
+
eco: str
|
|
103
|
+
name: str
|
|
104
|
+
|
|
105
|
+
def __repr__(self):
|
|
106
|
+
if self.eco:
|
|
107
|
+
return f"{self.eco}:{self.name}"
|
|
108
|
+
|
|
109
|
+
return self.name
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class GameInfo:
|
|
114
|
+
players: Players
|
|
115
|
+
date: str
|
|
116
|
+
result: str
|
|
117
|
+
opening: Opening
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class BaseVisitor:
|
|
121
|
+
def begin_game(self, gameinfo: GameInfo):
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
def end_game(self, gameinfo: GameInfo):
|
|
125
|
+
pass
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pgn-clean-cg
|
|
3
|
+
Version: 0.0.0rc2
|
|
4
|
+
Summary: Clean comments and Lichess computer analysis from pgn files
|
|
5
|
+
Author-email: Espen Sandøy Hustad <espen@ehustad.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Description-Content-Type: text/x-rst
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: chess
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
.. SPDX-FileCopyrightText: 2026 Espen Sandøy Hustad <espen@ehustad.com>
|
|
13
|
+
.. SPDX-License-Identifier: CC-BY-SA-4.0
|
|
14
|
+
|
|
15
|
+
pgn-clean-cg - A command line utility for removing Lichess computer analysis from pgn files
|
|
16
|
+
===========================================================================================
|
|
17
|
+
|
|
18
|
+
Are you tired of the computer spoiling all the fun before you even start to analyse your
|
|
19
|
+
most recent chess game? Look no further, pgn-clean-cg is a command line utility for cleaning
|
|
20
|
+
up pgn (Portable Game Notation) files.
|
|
21
|
+
|
|
22
|
+
* Remove per move computer evaluation.
|
|
23
|
+
* Remove per move comments, excluding clock times.
|
|
24
|
+
* Remove variations, excluding the main variation.
|
|
25
|
+
* Remove NAGs (Numeric Annotation Glyphs) such as ?, ??, !!, etc.
|
|
26
|
+
|
|
27
|
+
Usage
|
|
28
|
+
-----
|
|
29
|
+
|
|
30
|
+
Run the command "pgn-clean-cg" on one or more .pgn (Portable Game Notation) and specify destination .pgn file.
|
|
31
|
+
Example:
|
|
32
|
+
|
|
33
|
+
.. code-block::
|
|
34
|
+
|
|
35
|
+
pgn-clean-cg input1.pgn input2.pgn out.pgn
|
|
36
|
+
|
|
37
|
+
More verbose output can be added using the "-v" flag
|
|
38
|
+
|
|
39
|
+
.. code-block::
|
|
40
|
+
|
|
41
|
+
pgn-clean-cg -v input1.pgn input2.pgn out.pgn
|
|
42
|
+
|
|
43
|
+
The builtin help can be accessed using the "-h" flag
|
|
44
|
+
|
|
45
|
+
.. code-block::
|
|
46
|
+
|
|
47
|
+
pgn-clean-cg -h
|
|
48
|
+
|
|
49
|
+
Installation
|
|
50
|
+
------------
|
|
51
|
+
|
|
52
|
+
The following command install pgn-clean-cg from the `Python Package Index`_. You will need
|
|
53
|
+
a working installation of Python3 and pip.
|
|
54
|
+
|
|
55
|
+
.. code-block::
|
|
56
|
+
|
|
57
|
+
pip install --upgrade pgn-clean-cg
|
|
58
|
+
|
|
59
|
+
Feedback and Contributing
|
|
60
|
+
-------------------------
|
|
61
|
+
Need help, or found a bug? Feel free to open an `issue`_ over on `Codeberg`_.
|
|
62
|
+
|
|
63
|
+
If you want to get involved, please read `CONTRIBUTING.rst`_.
|
|
64
|
+
|
|
65
|
+
.. _CONTRIBUTING.rst: https://codeberg.org/ehustad/pgn-cg-clean/src/branch/main/CONTRIBUTING.rst
|
|
66
|
+
.. _issue: https://codeberg.org/ehustad/pgn-cg-clean/issues
|
|
67
|
+
.. _Python Package Index: https://pypi.org/project/pgn-cg-clean/
|
|
68
|
+
|
|
69
|
+
.. SPDX-FileCopyrightText: 2026 Espen Sandøy Hustad <espen@ehustad.com>
|
|
70
|
+
.. SPDX-License-Identifier: CC-BY-SA-4.0
|
|
71
|
+
|
|
72
|
+
Contributing to pgn-clean-cg
|
|
73
|
+
============================
|
|
74
|
+
|
|
75
|
+
First of all, thank you for considering contributing to pgn-clean-cg.
|
|
76
|
+
|
|
77
|
+
Before you open a pull request, please make sure that the code is correctly
|
|
78
|
+
formatted, and you have not introduced any new linter errors.
|
|
79
|
+
|
|
80
|
+
By submitting a pull request you certify that:
|
|
81
|
+
|
|
82
|
+
* You are the author of the contribution, or have the legal right to submit it.
|
|
83
|
+
* You either hold the copyright to the changes, or have the explicit legal
|
|
84
|
+
authorization to contribute them under this projects license.
|
|
85
|
+
* You understand the code.
|
|
86
|
+
* You accept full responsibility for it.
|
|
87
|
+
|
|
88
|
+
Requirements
|
|
89
|
+
------------
|
|
90
|
+
|
|
91
|
+
In order to checkout the code and initialize the development environment you will need:
|
|
92
|
+
|
|
93
|
+
* Git
|
|
94
|
+
* Python 3
|
|
95
|
+
* Pip
|
|
96
|
+
* Automake
|
|
97
|
+
|
|
98
|
+
These packages can be installed using your favourite package manager on Linux.
|
|
99
|
+
|
|
100
|
+
Getting the code
|
|
101
|
+
----------------
|
|
102
|
+
|
|
103
|
+
This project is using Git, and is hosted on `Codeberg`_.
|
|
104
|
+
|
|
105
|
+
.. code-block::
|
|
106
|
+
|
|
107
|
+
git clone ssh://git@codeberg.org/ehustad/pgn-cg-clean.git
|
|
108
|
+
cd pgn-clean-cg
|
|
109
|
+
|
|
110
|
+
Initializing the development environment
|
|
111
|
+
----------------------------------------
|
|
112
|
+
|
|
113
|
+
The development environment can be initialized by using the make target "init"
|
|
114
|
+
from the top level source directory. This will create a virtual environment using
|
|
115
|
+
the Python venv module in .venv, install development tools, and install
|
|
116
|
+
pgn-cg-clean in `editable mode`_.
|
|
117
|
+
|
|
118
|
+
.. code-block::
|
|
119
|
+
|
|
120
|
+
make init
|
|
121
|
+
|
|
122
|
+
The make targets "env", "install-dev-tools", and "install" can be used if you
|
|
123
|
+
want to do this in stages. Note that "install" depends on "install-dev-tools"
|
|
124
|
+
and both "install" and "install-dev-tools" depends on "env".
|
|
125
|
+
|
|
126
|
+
To initialize the virtual environment, run the make target "env" from the top level
|
|
127
|
+
source directory:
|
|
128
|
+
|
|
129
|
+
.. code-block::
|
|
130
|
+
|
|
131
|
+
make env
|
|
132
|
+
|
|
133
|
+
To install development tools, such as black, mypy, pylint, run the make target
|
|
134
|
+
"install-dev-tools". Note that this requires the virtual environment in .venv
|
|
135
|
+
to exist, as all packages will be installed to that virtual environment:
|
|
136
|
+
|
|
137
|
+
.. code-block::
|
|
138
|
+
|
|
139
|
+
make install-dev-tools
|
|
140
|
+
|
|
141
|
+
To install pgn-cg-clean in editable mode, run the make target "install" from the
|
|
142
|
+
top level source directory:
|
|
143
|
+
|
|
144
|
+
.. code-block::
|
|
145
|
+
|
|
146
|
+
make install
|
|
147
|
+
|
|
148
|
+
Formatting code
|
|
149
|
+
---------------
|
|
150
|
+
|
|
151
|
+
The code can formatted by using the make target "format" from the top level
|
|
152
|
+
source directory:
|
|
153
|
+
|
|
154
|
+
.. code-block::
|
|
155
|
+
|
|
156
|
+
make format
|
|
157
|
+
|
|
158
|
+
Running linters
|
|
159
|
+
---------------
|
|
160
|
+
|
|
161
|
+
The linters can be run using make the make target "lint" from the top level
|
|
162
|
+
source directory:
|
|
163
|
+
|
|
164
|
+
.. code-block::
|
|
165
|
+
|
|
166
|
+
make lint
|
|
167
|
+
|
|
168
|
+
Running unit tests
|
|
169
|
+
------------------
|
|
170
|
+
|
|
171
|
+
The unit tests can be run using the make target "test" from the top level
|
|
172
|
+
source directory:
|
|
173
|
+
|
|
174
|
+
.. code-block::
|
|
175
|
+
|
|
176
|
+
make test
|
|
177
|
+
|
|
178
|
+
Other make targets
|
|
179
|
+
------------------
|
|
180
|
+
|
|
181
|
+
Please run the make target "help" from the top level source directory, or see
|
|
182
|
+
Makefile.
|
|
183
|
+
|
|
184
|
+
.. code-block::
|
|
185
|
+
|
|
186
|
+
make help
|
|
187
|
+
|
|
188
|
+
Creating a pull request
|
|
189
|
+
-----------------------
|
|
190
|
+
|
|
191
|
+
Please see the `Codeberg documentation`_.
|
|
192
|
+
|
|
193
|
+
Thanks
|
|
194
|
+
------
|
|
195
|
+
|
|
196
|
+
Parts of this was based on the AI policy of the pip project.
|
|
197
|
+
|
|
198
|
+
.. _Codeberg: https://codeberg.org/ehustad/pgn-cg-clean/
|
|
199
|
+
.. _Codeberg documentation: https://docs.codeberg.org/collaborating/pull-requests-and-git-flow/
|
|
200
|
+
.. _editable mode: https://setuptools.pypa.io/en/latest/userguide/development_mode.html
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
CONTRIBUTING.rst
|
|
2
|
+
LICENSE
|
|
3
|
+
MANIFEST.in
|
|
4
|
+
README.rst
|
|
5
|
+
pyproject.toml
|
|
6
|
+
pgn_clean_cg/__init__.py
|
|
7
|
+
pgn_clean_cg/__main__.py
|
|
8
|
+
pgn_clean_cg/cli.py
|
|
9
|
+
pgn_clean_cg/pgnutils.py
|
|
10
|
+
pgn_clean_cg.egg-info/PKG-INFO
|
|
11
|
+
pgn_clean_cg.egg-info/SOURCES.txt
|
|
12
|
+
pgn_clean_cg.egg-info/dependency_links.txt
|
|
13
|
+
pgn_clean_cg.egg-info/entry_points.txt
|
|
14
|
+
pgn_clean_cg.egg-info/requires.txt
|
|
15
|
+
pgn_clean_cg.egg-info/top_level.txt
|
|
16
|
+
tests/expected.pgn
|
|
17
|
+
tests/expected.pgn.license
|
|
18
|
+
tests/source.pgn
|
|
19
|
+
tests/source.pgn.license
|
|
20
|
+
tests/source_with_clk.pgn
|
|
21
|
+
tests/source_with_clk.pgn.license
|
|
22
|
+
tests/source_with_computer_eval.pgn
|
|
23
|
+
tests/source_with_computer_eval.pgn.license
|
|
24
|
+
tests/test_pgnutils.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
chess
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pgn_clean_cg
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: none
|
|
2
|
+
# SPDX-License-Identifier: CC0-1.0
|
|
3
|
+
|
|
4
|
+
[build-system]
|
|
5
|
+
requires = ["setuptools"]
|
|
6
|
+
build-backend = "setuptools.build_meta"
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "pgn-clean-cg"
|
|
10
|
+
dynamic = [
|
|
11
|
+
"version",
|
|
12
|
+
"readme",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
authors = [
|
|
16
|
+
{name = "Espen Sandøy Hustad", email = "espen@ehustad.com"},
|
|
17
|
+
]
|
|
18
|
+
description = "Clean comments and Lichess computer analysis from pgn files"
|
|
19
|
+
license = "MIT"
|
|
20
|
+
|
|
21
|
+
dependencies = [
|
|
22
|
+
"chess",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[dependency-groups]
|
|
26
|
+
dev = [
|
|
27
|
+
"black",
|
|
28
|
+
"mypy",
|
|
29
|
+
"pylint",
|
|
30
|
+
"reuse",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
pgn-clean-cg = "pgn_clean_cg.cli:main"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools]
|
|
37
|
+
packages = [
|
|
38
|
+
"pgn_clean_cg"
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.dynamic]
|
|
42
|
+
version = {attr = "pgn_clean_cg.__version__"}
|
|
43
|
+
readme = {file = ["README.rst", "CONTRIBUTING.rst"] }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[Event "rated bullet game"]
|
|
2
|
+
[Site "https://lichess.org/q3p2A91e"]
|
|
3
|
+
[Date "2026.07.13"]
|
|
4
|
+
[Round "-"]
|
|
5
|
+
[White "tsunami10"]
|
|
6
|
+
[Black "Secret_Magnus"]
|
|
7
|
+
[Result "0-1"]
|
|
8
|
+
[GameId "q3p2A91e"]
|
|
9
|
+
[UTCDate "2026.07.13"]
|
|
10
|
+
[UTCTime "13:34:00"]
|
|
11
|
+
[WhiteElo "2964"]
|
|
12
|
+
[BlackElo "3149"]
|
|
13
|
+
[WhiteRatingDiff "-3"]
|
|
14
|
+
[BlackRatingDiff "+3"]
|
|
15
|
+
[BlackTitle "IM"]
|
|
16
|
+
[Variant "Standard"]
|
|
17
|
+
[TimeControl "60+0"]
|
|
18
|
+
[ECO "B08"]
|
|
19
|
+
[Opening "Pirc Defense: Classical Variation"]
|
|
20
|
+
[Termination "Normal"]
|
|
21
|
+
[Annotator "lichess.org"]
|
|
22
|
+
|
|
23
|
+
1. Nf3 d6 2. d4 Nf6 3. Nc3 g6 4. e4 Bg7 5. e5 dxe5 6. dxe5 Qxd1+ 7. Nxd1 Ng4 8. Bf4 Nc6 9. Bb5 Bd7 10. Bxc6 Bxc6 11. e6 fxe6 12. Ne3 Nxe3 13. fxe3 Bxb2 14. Rb1 Bc3+ 15. Ke2 O-O-O 16. Ng1 Bxg2
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[Event "rated bullet game"]
|
|
2
|
+
[Site "https://lichess.org/q3p2A91e"]
|
|
3
|
+
[Date "2026.07.13"]
|
|
4
|
+
[Round "-"]
|
|
5
|
+
[White "tsunami10"]
|
|
6
|
+
[Black "Secret_Magnus"]
|
|
7
|
+
[Result "0-1"]
|
|
8
|
+
[GameId "q3p2A91e"]
|
|
9
|
+
[UTCDate "2026.07.13"]
|
|
10
|
+
[UTCTime "13:34:00"]
|
|
11
|
+
[WhiteElo "2964"]
|
|
12
|
+
[BlackElo "3149"]
|
|
13
|
+
[WhiteRatingDiff "-3"]
|
|
14
|
+
[BlackRatingDiff "+3"]
|
|
15
|
+
[BlackTitle "IM"]
|
|
16
|
+
[Variant "Standard"]
|
|
17
|
+
[TimeControl "60+0"]
|
|
18
|
+
[ECO "B08"]
|
|
19
|
+
[Opening "Pirc Defense: Classical Variation"]
|
|
20
|
+
[Termination "Normal"]
|
|
21
|
+
[Annotator "lichess.org"]
|
|
22
|
+
|
|
23
|
+
1. Nf3 d6 2. d4 Nf6 3. Nc3 g6 4. e4 Bg7 5. e5 dxe5 6. dxe5 Qxd1+ 7. Nxd1 Ng4 8. Bf4 Nc6 9. Bb5 Bd7 (9... O-O 10. O-O (10. b3 h5) 10... Nd4) 10. Bxc6 Bxc6 11. e6 fxe6 12. Ne3 Nxe3 13. fxe3 Bxb2 (13... h5 14. Nd4 Bd7 (14... Rd8)) 14. Rb1 Bc3+ 15. Ke2 O-O-O 16. Ng1 Bxg2
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[Event "Round 1: Perreard, Nicolas - Dlugosz, Jan"]
|
|
2
|
+
[Site "https://lichess.org/broadcast/59th-biel-international-chess-festival-2026-open-free-style-960-fsc/round-1/hxv9N3yC/uPir5Hui"]
|
|
3
|
+
[Date "2026.07.13"]
|
|
4
|
+
[Round "1.7"]
|
|
5
|
+
[White "Perreard, Nicolas"]
|
|
6
|
+
[Black "Dlugosz, Jan"]
|
|
7
|
+
[Result "1-0"]
|
|
8
|
+
[WhiteElo "2041"]
|
|
9
|
+
[WhiteFideId "1330861"]
|
|
10
|
+
[BlackElo "1600"]
|
|
11
|
+
[BlackFideId "545027633"]
|
|
12
|
+
[Variant "Chess960"]
|
|
13
|
+
[ECO "?"]
|
|
14
|
+
[Opening "?"]
|
|
15
|
+
[FEN "bbqrnnkr/pppppppp/8/8/8/8/PPPPPPPP/BBQRNNKR w KQkq - 0 1"]
|
|
16
|
+
[SetUp "1"]
|
|
17
|
+
[UTCDate "2026.07.13"]
|
|
18
|
+
[UTCTime "07:57:24"]
|
|
19
|
+
[BroadcastName "59th Biel International Chess Festival 2026 | Open | Free Style 960 (FSC)"]
|
|
20
|
+
[BroadcastURL "https://lichess.org/broadcast/59th-biel-international-chess-festival-2026-open-free-style-960-fsc/round-1/hxv9N3yC"]
|
|
21
|
+
[GameURL "https://lichess.org/broadcast/59th-biel-international-chess-festival-2026-open-free-style-960-fsc/round-1/hxv9N3yC/uPir5Hui"]
|
|
22
|
+
|
|
23
|
+
1. d4 { [%eval 0.24] [%clk 0:58:15] } 1... d5 { [%eval 0.19] [%clk 0:59:12] } 2. b3 { [%eval 0.23] [%clk 0:57:33] } 2... Nf6?! { [%eval 0.87] } { Inaccuracy. c5 was best. } { [%clk 0:58:38] } 3. c4 { [%eval 0.74] [%clk 0:56:51] } 3... c6 { [%eval 0.9] [%clk 0:58:27] } 4. Nf3 { [%eval 0.77] [%clk 0:51:29] } 4... N8d7?! { [%eval 1.43] } { Inaccuracy. b6 was best. } { [%clk 0:56:42] } 5. Ne3 { [%eval 1.06] [%clk 0:50:22] } 5... e6 { [%eval 0.95] [%clk 0:56:29] } 6. h4 { [%eval 0.99] [%clk 0:44:11] } 6... h5 { [%eval 0.9] [%clk 0:54:47] } 7. Qa3 { [%eval 0.58] [%clk 0:42:58] } 7... g6 { [%eval 1.03] [%clk 0:52:36] } 8. Ng5 { [%eval 0.71] [%clk 0:41:42] } 8... Nh7 { [%eval 1.15] [%clk 0:51:16] } 9. Nxe6?! { [%eval 0.05] } { Inaccuracy. Qc1 was best. } { [%clk 0:36:21] } 9... fxe6 { [%eval 0.14] [%clk 0:51:23] } 10. Bxg6? { [%eval -1.27] } { Mistake. cxd5 was best. } { [%clk 0:36:48] } 10... Nhf8?? { [%eval 2.53] } { Blunder. Ndf6 was best. } { [%clk 0:50:11] } 11. Bd3?? { [%eval -0.83] } { Blunder. Qe7 was best. } { [%clk 0:36:54] } 11... Nf6 { [%eval -0.75] [%clk 0:49:51] } 12. Rh3 { [%eval -1.09] [%clk 0:30:45] } 12... Kf7? { [%eval 0.04] } { Mistake. Qc7 was best. } { [%clk 0:49:06] } 13. Rf3 { [%eval 0.07] [%clk 0:30:37] } 13... Bd6? { [%eval 1.2] } { Mistake. Ng6 was best. } { [%clk 0:48:42] } 14. Qxa7?! { [%eval 0.22] } { Inaccuracy. Qc1 was best. } { [%clk 0:30:36] } 14... Ke7 { [%eval 0.76] [%clk 0:48:19] } 15. c5 { [%eval 0.74] [%clk 0:29:15] } 15... Bb8 { [%eval 1.14] [%clk 0:48:38] } 16. Qa3 { [%eval 1.11] [%clk 0:29:28] } 16... N8d7 { [%eval 1.09] [%clk 0:47:58] } 17. O-O-O?! { [%eval 0.0] } { Inaccuracy. g3 was best. } { [%clk 0:29:42] } 17... Bc7 { [%eval 0.0] [%clk 0:48:07] } 18. b4?! { [%eval -0.8] } { Inaccuracy. Kb1 was best. } { [%clk 0:27:36] } 1-0
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[Event "Round 1: Brixius, Dirk - Cherniaev, Alexander"]
|
|
2
|
+
[Site "https://lichess.org/broadcast/59th-biel-international-chess-festival-2026-open-free-style-960-fsc/round-1/hxv9N3yC/Yc5uWTEm"]
|
|
3
|
+
[Date "2026.07.13"]
|
|
4
|
+
[Round "1.2"]
|
|
5
|
+
[White "Brixius, Dirk"]
|
|
6
|
+
[Black "Cherniaev, Alexander"]
|
|
7
|
+
[Result "0-1"]
|
|
8
|
+
[WhiteElo "1821"]
|
|
9
|
+
[WhiteFideId "12909734"]
|
|
10
|
+
[BlackElo "2275"]
|
|
11
|
+
[BlackTitle "GM"]
|
|
12
|
+
[BlackFideId "4117301"]
|
|
13
|
+
[Variant "Chess960"]
|
|
14
|
+
[ECO "?"]
|
|
15
|
+
[Opening "?"]
|
|
16
|
+
[FEN "bbqrnnkr/pppppppp/8/8/8/8/PPPPPPPP/BBQRNNKR w KQkq - 0 1"]
|
|
17
|
+
[SetUp "1"]
|
|
18
|
+
[UTCDate "2026.07.13"]
|
|
19
|
+
[UTCTime "07:57:24"]
|
|
20
|
+
[BroadcastName "59th Biel International Chess Festival 2026 | Open | Free Style 960 (FSC)"]
|
|
21
|
+
[BroadcastURL "https://lichess.org/broadcast/59th-biel-international-chess-festival-2026-open-free-style-960-fsc/round-1/hxv9N3yC"]
|
|
22
|
+
[GameURL "https://lichess.org/broadcast/59th-biel-international-chess-festival-2026-open-free-style-960-fsc/round-1/hxv9N3yC/Yc5uWTEm"]
|
|
23
|
+
|
|
24
|
+
1. c4 { [%eval 0.35] [%clk 1:00:30] } 1... c5 { [%eval 0.15] [%clk 0:59:43] } 2. b3 { [%eval 0.11] [%clk 0:59:02] } 2... b6 { [%eval 0.23] [%clk 1:00:06] } 3. d4 { [%eval -0.06] [%clk 0:57:43] } 3... cxd4 { [%eval 0.21] [%clk 1:00:19] } 4. Bxd4 { [%eval 0.15] [%clk 0:57:21] } 4... Ne6 { [%eval 0.13] [%clk 1:00:31] } 5. e3?! { [%eval -0.68] } { Inaccuracy. Bb2 was best. } { [%clk 0:56:54] } 5... Nxd4 { [%eval -0.71] [%clk 1:00:44] } 6. exd4 { [%eval -0.71] [%clk 0:56:41] } 6... e6 { [%eval -0.69] [%clk 1:00:29] } 7. Qe3 { [%eval -0.99] [%clk 0:54:52] } 7... Nf6 { [%eval -1.04] [%clk 1:00:36] } 8. Nf3 { [%eval -0.96] [%clk 0:52:12] } 8... Qc7 { [%eval -0.73] [%clk 0:54:12] } 9. Ng3 { [%eval -1.03] [%clk 0:49:29] } 9... Qf4 { [%eval -0.75] [%clk 0:47:00] } 10. Ne2 { [%eval -0.87] [%clk 0:45:11] } 10... Qxe3 { [%eval -0.79] [%clk 0:42:56] } 11. fxe3 { [%eval -0.84] [%clk 0:45:38] } 11... O-O { [%eval -0.79] [%clk 0:43:12] } 12. Nc3 { [%eval -0.78] [%clk 0:42:47] } 12... Rc8 { [%eval -0.76] [%clk 0:32:31] } 13. e4 { [%eval -0.72] [%clk 0:40:55] } 13... d5 { [%eval -0.86] [%clk 0:25:26] } 14. e5 { [%eval -1.06] [%clk 0:37:20] } 14... Nd7?! { [%eval -0.33] } { Inaccuracy. Nh5 was best. } { [%clk 0:24:52] } 15. Bd3 { [%eval -0.22] [%clk 0:35:28] } 15... f6 { [%eval -0.4] [%clk 0:23:40] } 16. cxd5?! { [%eval -1.4] } { Inaccuracy. Nxd5 was best. } { [%clk 0:34:23] } 16... exd5? { [%eval 0.03] } { Mistake. Rxc3 was best. } { [%clk 0:24:05] } 17. e6?? { [%eval -1.69] } { Blunder. Bf5 was best. } { [%clk 0:34:20] } 17... Rxc3 { [%eval -1.75] [%clk 0:24:23] } 18. exd7 { [%eval -1.77] [%clk 0:34:35] } 18... Bc6 { [%eval -1.8] [%clk 0:17:37] } 19. Kf2 { [%eval -1.59] [%clk 0:34:00] } 19... Bxd7 { [%eval -1.66] [%clk 0:17:52] } 20. Rhe1 { [%eval -1.79] [%clk 0:32:27] } 20... Bd6 { [%eval -1.82] [%clk 0:18:03] } 21. g3 { [%eval -2.39] [%clk 0:27:46] } 21... Rfc8 { [%eval -2.33] [%clk 0:16:52] } 22. Kg2 { [%eval -2.4] [%clk 0:27:49] } 22... Bb4 { [%eval -2.39] [%clk 0:15:38] } 23. Re2 { [%eval -2.36] [%clk 0:25:34] } 23... Rc1 { [%eval -2.05] [%clk 0:15:39] } 24. Rxc1 { [%eval -1.96] [%clk 0:24:44] } 24... Rxc1 { [%eval -2.09] [%clk 0:16:07] } 25. Rc2 { [%eval -2.1] [%clk 0:20:02] } 25... Rd1? { [%eval -0.71] } { Mistake. Ra1 was best. } { [%clk 0:13:38] } 26. Ba6?? { [%eval -3.31] } { Blunder. Rc7 was best. } { [%clk 0:19:52] } 26... Bf5 { [%eval -3.26] [%clk 0:13:50] } 27. Rc7 { [%eval -3.19] [%clk 0:17:51] } 27... Be4 { [%eval -3.21] [%clk 0:14:15] } 28. Kf2 { [%eval -3.5] [%clk 0:17:54] } 28... Ra1 { [%eval -3.36] [%clk 0:14:38] } 29. a4 { [%eval -3.5] [%clk 0:17:14] } 29... Ra2+ { [%eval -3.16] [%clk 0:13:33] } 30. Ke3 { [%eval -3.65] [%clk 0:17:25] } 30... Rb2 { [%eval -3.94] [%clk 0:13:49] } 31. Rc8+ { [%eval -4.09] [%clk 0:15:04] } 31... Kf7 { [%eval -3.68] [%clk 0:13:54] } 32. Bc4?! { [%eval -4.65] } { Inaccuracy. Be2 was best. } { [%clk 0:13:35] } 32... Bxf3 { [%eval -4.54] [%clk 0:14:09] } 33. Kxf3 { [%eval -4.7] [%clk 0:13:55] } 33... dxc4 { [%eval -4.74] [%clk 0:14:31] } 34. bxc4 { [%eval -4.58] [%clk 0:14:13] } 34... Bc3 { [%eval -4.34] [%clk 0:10:54] } 35. Rc7+ { [%eval -4.48] [%clk 0:13:21] } 35... Ke6 { [%eval -4.09] [%clk 0:10:12] } 36. d5+ { [%eval -4.32] [%clk 0:13:21] } 36... Kd6 { [%eval -4.31] [%clk 0:10:36] } 37. Rxg7 { [%eval -4.22] [%clk 0:13:23] } 37... Rxh2 { [%eval -4.08] [%clk 0:10:07] } 38. Rxa7 { [%eval -4.1] [%clk 0:13:28] } 38... h5 { [%eval -3.83] [%clk 0:08:19] } 39. Ke4 { [%eval -4.65] [%clk 0:08:19] } 39... h4 { [%eval -4.52] [%clk 0:05:23] } 40. gxh4 { [%eval -4.82] [%clk 0:07:43] } 40... Rxh4+ { [%eval -4.89] [%clk 0:05:47] } 41. Kd3 { [%eval -5.42] [%clk 0:07:45] } 41... Be5 { [%eval -5.18] [%clk 0:05:54] } 42. a5 { [%eval -5.36] [%clk 0:07:30] } 42... Rd4+ { [%eval -5.27] [%clk 0:06:09] } 43. Ke3 { [%eval -5.63] [%clk 0:06:58] } 43... Rxc4 { [%eval -5.33] [%clk 0:06:32] } 44. axb6 { [%eval -5.02] [%clk 0:05:03] } 44... Kxd5 { [%eval -5.21] [%clk 0:06:34] } 45. Re7 { [%eval -6.13] [%clk 0:04:00] } 45... Rc3+ { [%eval -6.59] [%clk 0:06:47] } 46. Kd2 { [%eval -6.3] [%clk 0:04:08] } 46... Rb3 { [%eval -6.28] [%clk 0:07:11] } 47. b7 { [%eval -6.07] [%clk 0:04:22] } 47... Kc6 { [%eval -5.96] [%clk 0:07:32] } 0-1
|
|
25
|
+
|
|
26
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-FileCopyrightText: 2026 Espen Sandøy Hustad <espen@ehustad.com>
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List
|
|
6
|
+
import unittest
|
|
7
|
+
|
|
8
|
+
import chess.pgn
|
|
9
|
+
|
|
10
|
+
from pgn_clean_cg import pgnutils
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _get_path(filename: str) -> Path:
|
|
14
|
+
return Path(__file__).parent.joinpath(filename)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestPGNUtils(unittest.TestCase):
|
|
18
|
+
|
|
19
|
+
def test_remove_comment(self):
|
|
20
|
+
games = self._load_cleaned_games("source_with_computer_eval.pgn")
|
|
21
|
+
for game in games:
|
|
22
|
+
for move in game.mainline():
|
|
23
|
+
move.set_clock(None)
|
|
24
|
+
self.assertFalse(move.comment)
|
|
25
|
+
|
|
26
|
+
def test_remove_eval(self):
|
|
27
|
+
games = self._load_cleaned_games("source_with_computer_eval.pgn")
|
|
28
|
+
for game in games:
|
|
29
|
+
for move in game.mainline():
|
|
30
|
+
self.assertTrue(move.eval() is None)
|
|
31
|
+
|
|
32
|
+
def test_remove_nags(self):
|
|
33
|
+
games = self._load_cleaned_games("source_with_computer_eval.pgn")
|
|
34
|
+
for game in games:
|
|
35
|
+
for move in game.mainline():
|
|
36
|
+
self.assertFalse(move.nags)
|
|
37
|
+
|
|
38
|
+
def test_remove_variations(self):
|
|
39
|
+
actual = self._load_cleaned_games("source.pgn")
|
|
40
|
+
expected = self._load_raw_games("expected.pgn")
|
|
41
|
+
|
|
42
|
+
self.assertEqual(len(actual), len(expected))
|
|
43
|
+
|
|
44
|
+
for i in range(len(actual)):
|
|
45
|
+
self.assertEqual(str(actual[i]), str(expected[i]))
|
|
46
|
+
|
|
47
|
+
def test_retain_clock(self):
|
|
48
|
+
games = self._load_cleaned_games("source_with_clk.pgn")
|
|
49
|
+
for game in games:
|
|
50
|
+
for move in game.mainline():
|
|
51
|
+
self.assertTrue(move.clock())
|
|
52
|
+
|
|
53
|
+
def _load_raw_games(self, name: str) -> List[chess.pgn.Game]:
|
|
54
|
+
ret = []
|
|
55
|
+
|
|
56
|
+
with _get_path(name).open() as f:
|
|
57
|
+
while True:
|
|
58
|
+
game = chess.pgn.read_game(f)
|
|
59
|
+
if game is None:
|
|
60
|
+
break
|
|
61
|
+
ret.append(game)
|
|
62
|
+
|
|
63
|
+
self.assertTrue(ret)
|
|
64
|
+
return ret
|
|
65
|
+
|
|
66
|
+
def _load_cleaned_games(self, name: str) -> List[chess.pgn.Game]:
|
|
67
|
+
ret = []
|
|
68
|
+
|
|
69
|
+
with _get_path(name).open() as f:
|
|
70
|
+
ret = self.cgcleaner.clean(f)
|
|
71
|
+
|
|
72
|
+
self.assertTrue(ret)
|
|
73
|
+
return ret
|
|
74
|
+
|
|
75
|
+
def setUp(self):
|
|
76
|
+
self.cgcleaner = pgnutils.CGCleaner()
|
|
77
|
+
self.maxDiff = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
unittest.main()
|