xdfetch 0.0.1__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.
- xdfetch/__init__.py +5 -0
- xdfetch/__main__.py +3 -0
- xdfetch/adapters/__init__.py +5 -0
- xdfetch/cli.py +51 -0
- xdfetch/convert/__init__.py +5 -0
- xdfetch/outlets.tsv +1 -0
- xdfetch/registry.py +41 -0
- xdfetch-0.0.1.dist-info/METADATA +56 -0
- xdfetch-0.0.1.dist-info/RECORD +12 -0
- xdfetch-0.0.1.dist-info/WHEEL +4 -0
- xdfetch-0.0.1.dist-info/entry_points.txt +2 -0
- xdfetch-0.0.1.dist-info/licenses/LICENSE +21 -0
xdfetch/__init__.py
ADDED
xdfetch/__main__.py
ADDED
xdfetch/cli.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""xdfetch command line.
|
|
2
|
+
|
|
3
|
+
list the outlets xdfetch knows
|
|
4
|
+
get fetch one outlet's puzzle and write it as .xd (not written yet)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from . import __version__, registry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def cmd_list(args: argparse.Namespace) -> int:
|
|
16
|
+
outlets = registry.load()
|
|
17
|
+
for o in sorted(outlets.values(), key=lambda o: o.id):
|
|
18
|
+
print(f"{o.id}\t{o.days}\t{o.homepage}")
|
|
19
|
+
return 0
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def cmd_get(args: argparse.Namespace) -> int:
|
|
23
|
+
print("xdfetch get: not written yet", file=sys.stderr)
|
|
24
|
+
return 2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
28
|
+
p = argparse.ArgumentParser(
|
|
29
|
+
prog="xdfetch", description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
|
|
30
|
+
)
|
|
31
|
+
p.add_argument("--version", action="version", version=f"xdfetch {__version__}")
|
|
32
|
+
sub = p.add_subparsers(dest="command", required=True)
|
|
33
|
+
|
|
34
|
+
sp = sub.add_parser("list", help="the outlets xdfetch knows")
|
|
35
|
+
sp.set_defaults(func=cmd_list)
|
|
36
|
+
|
|
37
|
+
sp = sub.add_parser("get", help="fetch one outlet's puzzle and write it as .xd")
|
|
38
|
+
sp.add_argument("outlet")
|
|
39
|
+
sp.add_argument("--date", help="YYYY-MM-DD (default: the outlet's latest)")
|
|
40
|
+
sp.set_defaults(func=cmd_get)
|
|
41
|
+
|
|
42
|
+
return p
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main(argv: list[str] | None = None) -> int:
|
|
46
|
+
args = build_parser().parse_args(argv)
|
|
47
|
+
return args.func(args)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
raise SystemExit(main())
|
xdfetch/outlets.tsv
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
id adapter params days tz homepage notes
|
xdfetch/registry.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""The outlet registry: outlets.tsv, one row per outlet.
|
|
2
|
+
|
|
3
|
+
Columns:
|
|
4
|
+
id the outlet's id, following the xd corpus's publication ids
|
|
5
|
+
adapter which adapter fetches it (see xdfetch.adapters)
|
|
6
|
+
params the adapter's parameters, as space-separated key=value
|
|
7
|
+
days publication days: daily, mon-sat, sun, ...
|
|
8
|
+
tz the outlet's time zone, for "today"
|
|
9
|
+
homepage the outlet's own page for its puzzles
|
|
10
|
+
notes anything worth knowing
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import csv
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
OUTLETS_TSV = Path(__file__).with_name("outlets.tsv")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class Outlet:
|
|
24
|
+
id: str
|
|
25
|
+
adapter: str
|
|
26
|
+
params: str = ""
|
|
27
|
+
days: str = "daily"
|
|
28
|
+
tz: str = "America/New_York"
|
|
29
|
+
homepage: str = ""
|
|
30
|
+
notes: str = ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load(path: Path = OUTLETS_TSV) -> dict[str, Outlet]:
|
|
34
|
+
"""Read a registry file. Later rows with the same id replace earlier ones."""
|
|
35
|
+
outlets: dict[str, Outlet] = {}
|
|
36
|
+
with path.open(encoding="utf-8", newline="") as f:
|
|
37
|
+
for row in csv.DictReader(f, delimiter="\t"):
|
|
38
|
+
fields = {k: (v or "") for k, v in row.items() if k in Outlet.__dataclass_fields__}
|
|
39
|
+
outlet = Outlet(**fields)
|
|
40
|
+
outlets[outlet.id] = outlet
|
|
41
|
+
return outlets
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: xdfetch
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Fetch crossword puzzles from online outlets and convert them to .xd
|
|
5
|
+
Project-URL: Homepage, https://github.com/century-arcade/xdfetch
|
|
6
|
+
Project-URL: Repository, https://github.com/century-arcade/xdfetch
|
|
7
|
+
Project-URL: Issues, https://github.com/century-arcade/xdfetch/issues
|
|
8
|
+
Author: Alex DeJarnatt
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: crossword,downloader,puzzles,xd
|
|
12
|
+
Classifier: Development Status :: 1 - Planning
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Topic :: Games/Entertainment :: Puzzle Games
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# xdfetch
|
|
22
|
+
|
|
23
|
+
Fetches crossword puzzles from the outlets that publish them online and converts them to
|
|
24
|
+
[.xd](https://github.com/century-arcade/xdformat), the plain-text crossword format of the
|
|
25
|
+
[xd corpus](https://github.com/century-arcade/xd).
|
|
26
|
+
|
|
27
|
+
**Status: just started.** The package installs and the command runs, but it can't fetch
|
|
28
|
+
anything yet. [DESIGN.md](DESIGN.md) has the plan.
|
|
29
|
+
|
|
30
|
+
## What it will do
|
|
31
|
+
|
|
32
|
+
pip install xdfetch
|
|
33
|
+
xdfetch list # the outlets it knows
|
|
34
|
+
xdfetch get lat # today's LA Times -> lat2026-11-09.xd
|
|
35
|
+
xdfetch get nyt --date 2026-11-01 # with your own subscription cookie in NYT_S
|
|
36
|
+
xdfetch run --store ./archive --start 2026-10-01 --end 2026-10-31 # keep an archive
|
|
37
|
+
|
|
38
|
+
Three rules shape it:
|
|
39
|
+
|
|
40
|
+
1. **Fetch keeps the bytes as served.** The file the outlet sent is kept as it was, and
|
|
41
|
+
conversion reads from that.
|
|
42
|
+
2. **Convert is separate:** one converter per *format*, not per outlet, chosen by looking
|
|
43
|
+
at the content. Anything a source has that .xd can't carry is reported, never dropped
|
|
44
|
+
silently.
|
|
45
|
+
3. **Most outlets are a row, not code.** `outlets.tsv` says where each outlet's file is and
|
|
46
|
+
when it publishes; code is only needed for a new kind of host.
|
|
47
|
+
|
|
48
|
+
## Development
|
|
49
|
+
|
|
50
|
+
uv sync --group dev
|
|
51
|
+
uv run pytest
|
|
52
|
+
uv run ruff check
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
xdfetch/__init__.py,sha256=2cLAjkCeyDyPOLzXlrJp5hBHc_0ctKDR3T68nvH0uz0,200
|
|
2
|
+
xdfetch/__main__.py,sha256=k1ocEWawweo1qCJWNFAAvyxz3tcY13dzvCenHszij30,48
|
|
3
|
+
xdfetch/cli.py,sha256=rdtVqvZ5Nd41PoSvOJWzLeVxX6ZNn5w4MTMGc52GPpc,1407
|
|
4
|
+
xdfetch/outlets.tsv,sha256=hWuU-x3VI0YmGPT-ZneM6NTsJE82ilm2iSqf3bZtwIA,41
|
|
5
|
+
xdfetch/registry.py,sha256=yljLJuJEfQJ98X0v_PdeKP6yTz9ZyL4jE00pu-uLpsU,1351
|
|
6
|
+
xdfetch/adapters/__init__.py,sha256=Xvw5nWqetcO2qQ00Qr8dvROL_TlPLgcK0HgNg5EQ15s,229
|
|
7
|
+
xdfetch/convert/__init__.py,sha256=YlLc6ni2HpPR-vqRDATYOYrsDF1fat0ER65HHbLy7Yc,249
|
|
8
|
+
xdfetch-0.0.1.dist-info/METADATA,sha256=fOq4T18l-5GsOsfgFXC_dzAkNygdPaMqpikB5QqL_-0,2155
|
|
9
|
+
xdfetch-0.0.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
10
|
+
xdfetch-0.0.1.dist-info/entry_points.txt,sha256=nbVgi63O7_e4cyH4D5SZSMcmjec_0Flsl2PMKe0QQG8,45
|
|
11
|
+
xdfetch-0.0.1.dist-info/licenses/LICENSE,sha256=4XmtFFPTUKdPrypVToYNo6_d4Zd_uc2uaZV6OQ8D_ok,1081
|
|
12
|
+
xdfetch-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 century-arcade
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|