chadwickpy 0.1.0__py3-none-any.whl

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.
chadwickpy/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """Pure-Python port of the Chadwick baseball tools (GPL-2.0-or-later source; see module headers)."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("chadwickpy")
7
+ except PackageNotFoundError: # running from a source tree that is not installed
8
+ __version__ = "0+unknown"
chadwickpy/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ """``python -m chadwickpy TOOL [options] eventfile...`` runs one of the ported tools."""
2
+
3
+ import sys
4
+
5
+ from chadwickpy.tools.cli import main_umbrella
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main_umbrella())
chadwickpy/book.py ADDED
@@ -0,0 +1,128 @@
1
+ """Port of Chadwick's ``src/cwlib/book.c`` (``cw_scorebook_read``) and ``cw_file_find_first_game``.
2
+
3
+ Chadwick is Copyright (c) 2002-2023 Dr T L Turocy and the Chadwick Baseball
4
+ Bureau, licensed GPL-2.0-or-later; this module is a derivative of it and keeps
5
+ that notice.
6
+ """
7
+
8
+ from collections.abc import Callable, Iterator
9
+ from dataclasses import dataclass, field
10
+
11
+ from chadwickpy.file import CFile, StrTok
12
+ from chadwickpy.game import Game, read_game
13
+
14
+ COMMENT_BUFSIZE = 256 # ``char buf[256]`` in ``cw_scorebook_read_comments`` and the finder
15
+
16
+
17
+ def _read_comments(file: CFile) -> tuple[bool, list[str]]:
18
+ """``cw_scorebook_read_comments``: leading ``com`` lines. False if the file is empty."""
19
+ tok = StrTok()
20
+ comments: list[str] = []
21
+ while True:
22
+ buf = file.fgets(COMMENT_BUFSIZE)
23
+ if buf is None:
24
+ return False, comments
25
+ kind = tok(buf)
26
+ com = tok(None)
27
+ if kind is not None and kind == "com" and com is not None:
28
+ comments.append(com)
29
+ else:
30
+ return True, comments
31
+
32
+
33
+ def find_first_game(file: CFile) -> bool:
34
+ """``cw_file_find_first_game``: rewind, then stop at the first line that begins ``id``"""
35
+ tok = StrTok()
36
+ file.setpos(0)
37
+ while not file.eof:
38
+ filepos = file.getpos()
39
+ buf = file.fgets(COMMENT_BUFSIZE)
40
+ if buf is None:
41
+ return False
42
+ kind = tok(buf)
43
+ if kind is not None and kind == "id":
44
+ file.setpos(filepos)
45
+ return True
46
+ return False
47
+
48
+
49
+ def _strcmp(a: str | None, b: str | None) -> int:
50
+ """``strcmp``, which crashes in C on a NULL argument"""
51
+ if a is None or b is None:
52
+ raise ValueError("NULL string passed to strcmp (Chadwick would crash)")
53
+ return (a > b) - (a < b)
54
+
55
+
56
+ @dataclass
57
+ class Scorebook:
58
+ """``CWScorebook``: the leading comments of an event file and its games"""
59
+
60
+ comments: list[str] = field(default_factory=list)
61
+ games: list[Game] = field(default_factory=list)
62
+
63
+ def append_game(self, game: Game | None) -> bool:
64
+ """``cw_scorebook_append_game``: False (nothing done) for NULL"""
65
+ if game is None:
66
+ return False
67
+ self.games.append(game)
68
+ return True
69
+
70
+ def insert_game(self, game: Game | None) -> bool:
71
+ """``cw_scorebook_insert_game``: before the first game not earlier by date, then number"""
72
+ if game is None:
73
+ return False
74
+ if not self.games:
75
+ self.games.append(game)
76
+ return True
77
+ i = 0
78
+ while i < len(self.games):
79
+ g = self.games[i]
80
+ if _strcmp(g.info_lookup("date"), game.info_lookup("date")) < 0 or (
81
+ _strcmp(g.info_lookup("date"), game.info_lookup("date")) == 0
82
+ and _strcmp(g.info_lookup("number"), game.info_lookup("number")) < 0
83
+ ):
84
+ i += 1
85
+ else:
86
+ break
87
+ self.games.insert(i, game)
88
+ return True
89
+
90
+ def remove_game(self, game_id: str) -> Game | None:
91
+ """``cw_scorebook_remove_game``: the first game with this id, or ``None``"""
92
+ for i, game in enumerate(self.games):
93
+ if game.game_id == game_id:
94
+ return self.games.pop(i)
95
+ return None
96
+
97
+ def iterate(self, f: Callable[[Game], bool] | None = None) -> Iterator[Game]:
98
+ """``cw_scorebook_iterate`` / ``_iterator_next``: games for which ``f`` is true"""
99
+ for game in self.games:
100
+ if f is None or f(game):
101
+ yield game
102
+
103
+ def read(self, data: bytes) -> int:
104
+ """``cw_scorebook_read``: the number of games read, or -1 for an empty file"""
105
+ file = CFile(data)
106
+ ok, comments = _read_comments(file)
107
+ self.comments.extend(comments)
108
+ if not ok:
109
+ return -1
110
+ find_first_game(file)
111
+ count = 0
112
+ while not file.eof:
113
+ if not self.append_game(read_game(file)):
114
+ break
115
+ count += 1
116
+ return count
117
+
118
+
119
+ def scorebook_read(data: bytes) -> list[Game] | None:
120
+ """``cw_scorebook_read``: every game of an event file, or ``None`` where C returns -1.
121
+
122
+ ``None`` is the "could not open file" case of ``cwtools_process_scorebook``: an
123
+ empty file.
124
+ """
125
+ book = Scorebook()
126
+ if book.read(data) < 0:
127
+ return None
128
+ return book.games