Compare commits

...

61 Commits

Author SHA1 Message Date
renovate[bot]
f7bf19db41 Lock file maintenance (#52)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-22 06:13:56 +00:00
renovate[bot]
6be072d7fa Lock file maintenance (#51)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 04:36:45 +00:00
renovate[bot]
bb8031db97 Lock file maintenance (#50)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 06:14:34 +00:00
renovate[bot]
e3f14d0ebc Update actions/checkout digest to 8e8c483 (#49)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-02 20:42:08 +00:00
renovate[bot]
b7a03908f7 Lock file maintenance (#48)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 09:48:15 +00:00
3c884a5429 Merge pull request #46 from nikdoof/renovate/actions-checkout-6.x
Update actions/checkout action to v6
2025-11-30 00:25:30 +00:00
renovate[bot]
7ec3f6d87f Lock file maintenance (#47)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 05:31:44 +00:00
renovate[bot]
df98cdff4f Update actions/checkout action to v6 2025-11-20 16:30:33 +00:00
renovate[bot]
b10809059c Update actions/checkout digest to 93cb6ef (#45)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 10:58:20 +00:00
renovate[bot]
8093090a44 Lock file maintenance (#44)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 04:53:22 +00:00
renovate[bot]
c920ff61ab Lock file maintenance (#43)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 04:39:02 +00:00
renovate[bot]
61e2c8cb7a Lock file maintenance (#42)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 06:28:37 +00:00
renovate[bot]
03206106de Lock file maintenance (#41)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 04:47:48 +00:00
renovate[bot]
edcc88bd76 Lock file maintenance (#40)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 05:08:48 +00:00
d0d5436b39 Merge pull request #38 from nikdoof/renovate/astral-sh-setup-uv-7.x
Update astral-sh/setup-uv action to v7
2025-10-15 17:20:14 +01:00
505e246785 Merge pull request #37 from nikdoof/renovate/python-3.x
Update python Docker tag to v3.14
2025-10-15 17:18:52 +01:00
renovate[bot]
557f330516 Lock file maintenance (#39)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 06:03:06 +00:00
renovate[bot]
94ef0f8bbf Update astral-sh/setup-uv action to v7 2025-10-08 04:24:19 +00:00
renovate[bot]
a7606ec4d1 Update python Docker tag to v3.14 2025-10-08 04:24:17 +00:00
renovate[bot]
39879bfdc4 Lock file maintenance (#36)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 05:06:44 +00:00
renovate[bot]
53ad6a2933 Lock file maintenance (#35)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 06:37:49 +00:00
renovate[bot]
ae8788af96 Lock file maintenance (#34)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 04:22:18 +00:00
renovate[bot]
194dfb74fa Lock file maintenance (#33)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 05:22:51 +00:00
renovate[bot]
bbeb1a47ea Lock file maintenance (#32)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 06:38:52 +00:00
d3e91e8254 Merge pull request #31 from nikdoof/renovate/actions-setup-python-6.x
Update actions/setup-python action to v6
2025-09-04 15:35:51 +01:00
renovate[bot]
ba3957ba57 Update actions/setup-python action to v6 2025-09-04 04:58:50 +00:00
renovate[bot]
cde2263833 Lock file maintenance (#30)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 06:09:37 +00:00
renovate[bot]
28c7304119 Lock file maintenance (#29)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 06:03:17 +00:00
16f0633233 Merge pull request #28 from nikdoof/renovate/actions-checkout-5.x
Update actions/checkout action to v5
2025-08-12 22:46:40 +01:00
renovate[bot]
f90a117f1c Update actions/checkout action to v5 2025-08-11 17:08:36 +00:00
renovate[bot]
4f88cd2a0f Update actions/checkout digest to 08eba0b (#25)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 17:08:28 +00:00
renovate[bot]
b75629ca42 Lock file maintenance (#27)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 05:13:43 +00:00
fd11081c18 Merge pull request #26 from nikdoof/renovate/astral-sh-setup-uv-6.x
Update astral-sh/setup-uv action to v6
2025-08-09 16:41:17 +01:00
renovate[bot]
81057c1683 Update astral-sh/setup-uv action to v6 2025-08-09 15:36:45 +00:00
a3156dc0af Add nodelist2jrouter tool 2025-08-09 16:36:22 +01:00
15f44fdd48 Switch to uv 2025-08-09 16:36:02 +01:00
renovate[bot]
9fcebe000f Update dependency ruff to v0.12.8 (#24)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-08 00:59:40 +00:00
renovate[bot]
03365cc461 Update dependency ruff to v0.12.7 (#23)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 02:09:51 +00:00
renovate[bot]
e25f06fd5f Update dependency ruff to v0.12.5 (#22)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-24 19:43:22 +00:00
renovate[bot]
5342885b77 Update dependency ruff to v0.12.4 (#21)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-18 02:51:32 +00:00
renovate[bot]
a5f39773b6 Update dependency ruff to v0.12.3 (#20)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 20:25:30 +00:00
renovate[bot]
e36c2db8ae Update dependency ruff to v0.12.2 (#19)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 21:44:40 +00:00
renovate[bot]
95426ea928 Update dependency ruff to v0.12.1 (#18)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 06:27:47 +00:00
renovate[bot]
85b9166968 Update dependency ruff to ^0.12.0 (#17)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 00:57:41 +00:00
b11a852642 Add common Renovate config 2025-06-11 06:49:34 +01:00
f4ddb26cda Merge pull request #16 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.11.13
2025-06-11 06:23:08 +01:00
renovate[bot]
06ffb71f1e Update dependency ruff to v0.11.13 2025-06-06 21:57:32 +00:00
bf72ce18c8 Merge pull request #15 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.11.12
2025-05-30 15:49:57 +01:00
renovate[bot]
b62d0d689b Update dependency ruff to v0.11.12 2025-05-29 15:14:49 +00:00
64ee24fe5c Merge pull request #14 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.11.11
2025-05-24 15:14:53 +02:00
renovate[bot]
855bd17a6d Update dependency ruff to v0.11.11 2025-05-22 20:48:35 +00:00
af703864da Merge pull request #13 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.11.10
2025-05-17 11:46:11 +02:00
renovate[bot]
47708181a9 Update dependency ruff to v0.11.10 2025-05-15 19:36:45 +00:00
7e2a4e0517 Merge pull request #12 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.11.7
2025-04-30 10:04:27 +01:00
renovate[bot]
ae7e32a0f7 Update dependency ruff to v0.11.7 2025-04-24 19:48:25 +00:00
82bcfd2441 Merge pull request #11 from nikdoof/renovate/ruff-0.x-lockfile
Update dependency ruff to v0.11.6
2025-04-24 14:47:37 +01:00
b8aa8dc978 Add unique devices 2025-04-20 10:48:09 +01:00
22de39ff0b Fix logging 2025-04-19 18:01:16 +01:00
7844a7eb9c Add quiet mode 2025-04-19 17:53:16 +01:00
269c345f96 Parallel scraping using worker processes 2025-04-19 17:52:07 +01:00
renovate[bot]
e3e53ff0cf Update dependency ruff to v0.11.6 2025-04-17 15:47:10 +00:00
11 changed files with 298 additions and 104 deletions

25
.github/renovate.json vendored
View File

@@ -1,24 +1,7 @@
{
"extends": [
"config:recommended"
],
"packageRules": [
{
"description": "Auto merge GitHub Actions",
"matchManagers": [
"github-actions"
],
"matchDatasources": [
"github-tags"
],
"automerge": true,
"ignoreTests": true,
"automergeType": "branch",
"matchUpdateTypes": [
"minor",
"patch",
"digest"
]
}
"config:recommended",
"github>nikdoof/renovate-config:python",
"github>nikdoof/renovate-config:github"
]
}
}

View File

@@ -13,22 +13,18 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.13"]
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install Poetry
uses: snok/install-poetry@v1
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
virtualenvs-create: true
virtualenvs-in-project: true
enable-cache: true
- name: Install dependencies
run: |
poetry install --no-interaction --no-root
run: uv sync
- name: Lint with ruff
run: |
source $VENV
make lint
run: uv run ruff check

View File

@@ -10,19 +10,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Setup Python
uses: actions/setup-python@v5
- name: Install Poetry
uses: snok/install-poetry@v1
uses: actions/setup-python@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
virtualenvs-create: true
virtualenvs-in-project: true
enable-cache: true
- name: Build Release
run: poetry build
run: uv build
- name: Release
uses: softprops/action-gh-release@v2

3
.gitignore vendored
View File

@@ -154,4 +154,5 @@ cython_debug/
charts/**/charts/*
Chart.lock
.ruff_cache
*.json
*.json
nodes.txt

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.14

View File

@@ -27,7 +27,11 @@ def main():
# Number of zones
args.output.write('globaltalk_zones {0}\n'.format(len(globaltalk_data['zones'])))
# Nodes per zone
# Unique Devices
devices = collections.Counter(node.get("address", "Unknown") for node in globaltalk_data['nodes'])
args.output.write('globaltalk_unique_devices {0}\n'.format(len(devices)))
# Endpoints per zone
zone_counts = collections.Counter(node.get("zone", "Unknown") for node in globaltalk_data['nodes'])
for k, v in zone_counts.items():
args.output.write('globaltalk_zone_devices{{zone="{0}"}} {1}\n'.format(k, v))

View File

@@ -8,14 +8,15 @@ the GlobalTalk network, and create some nice visualisations of the network.
Created for MARCHintosh 2025
"""
import logging
import subprocess
import re
import json
import sys
import os
import argparse
import concurrent.futures
import json
import logging
import os
import re
import shutil
import subprocess
import sys
NPBLKUP_RESULTS = re.compile(r"^(.*):(.*)\s(\d*\.\d*:\d*)$")
@@ -83,10 +84,19 @@ def main():
help="Filename to write the resulting JSON to",
)
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
parser.add_argument("--quiet", action="store_true")
parser.add_argument(
"--workers",
type=int,
default=10,
help="The number of concurrent zone scans to run",
)
args = parser.parse_args()
if args.debug:
level = logging.DEBUG
elif args.quiet:
level = logging.ERROR
else:
level = logging.INFO
logging.basicConfig(level=level, stream=sys.stderr)
@@ -108,18 +118,28 @@ def main():
"nodes": [],
}
# Iterate the zones and scan them
for zone in zone_results["zones"]:
# Subfunction to run
def lookup_zone(zone) -> list[(str, str)]:
if args.zone and zone != args.zone:
continue
return None
logging.info("Scanning %s", zone)
node_data = nbplkup(zone)
zone_results["nodes"].extend(node_data)
return nbplkup(zone)
# Use ThreadPoolExecutor for concurrent execution
with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as executor:
futures = {
executor.submit(lookup_zone, zone): zone for zone in zone_results["zones"]
}
for future in concurrent.futures.as_completed(futures):
result = future.result()
if result:
zone_results["nodes"].extend(result)
nodes = len(zone_results["nodes"])
zones = len(zone_results["zones"])
print("{0} zones, {1} nodes".format(zones, nodes), file=sys.stderr)
logging.info("{0} zones, {1} nodes".format(zones, nodes), file=sys.stderr)
# Dump out the resulting JSON to stdout
args.output.write(json.dumps(zone_results))

146
nodelist2jrouter.py Executable file
View File

@@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
nodelist2jrouter - Convert GlobalTalk node list to jrouter YAML configuration
This script converts a text file containing DNS names and IP addresses
to a YAML file suitable for use with jrouter (AppleTalk protocol support).
"""
import argparse
import socket
import sys
import yaml
import logging
def resolve_address(address: str) -> str | None:
"""Resolve a DNS name or validate an IP address."""
address = address.strip()
if not address:
return None
try:
# Try to resolve as hostname first
result = socket.getaddrinfo(address, None, socket.AF_INET)
for res in result:
if res[0] == socket.AF_INET:
# Return the first valid IPv4 address
return res[4][0]
except socket.gaierror:
# If resolution fails, check if it's already a valid IP
try:
socket.inet_aton(address)
return address
except socket.error:
return None
def parse_input_file(input_file: str) -> list[str]:
"""Parse the input text file and extract valid IP addresses."""
peers = []
try:
with open(input_file, "r") as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line or line.startswith("#"):
continue
# Split by whitespace and take the first token
address = line.split()[0] if line.split() else ""
if address:
resolved_ip = resolve_address(address)
if resolved_ip:
peers.append(resolved_ip)
logging.debug(f"Resolved {address} -> {resolved_ip}")
else:
logging.debug(
f"Warning: Could not resolve '{address}' on line {line_num}"
)
except FileNotFoundError:
logging.error(f"Error: Input file '{input_file}' not found")
sys.exit(1)
except Exception as e:
logging.error(f"Error reading input file: {e}")
sys.exit(1)
return peers
def load_yaml_file(yaml_file: str) -> dict:
"""Load existing YAML file."""
try:
with open(yaml_file, "r") as f:
return yaml.safe_load(f) or {}
except FileNotFoundError:
logging.error(f"Error: YAML file '{yaml_file}' not found")
except yaml.YAMLError as e:
logging.error(f"Error parsing YAML file: {e}")
def write_yaml_file(data: dict, output_file: str) -> None:
"""Write data to YAML file."""
try:
with open(output_file, "w") as f:
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
logging.info(
f"Successfully wrote {len(data.get('peers', []))} peers to '{output_file}'"
)
except Exception as e:
logging.error(f"Error writing output file: {e}")
def main():
parser = argparse.ArgumentParser(
description="Convert GlobalTalk node list to jrouter YAML configuration"
)
parser.add_argument(
"input", help="Input text file containing DNS names/IP addresses"
)
parser.add_argument("-o", "--output", help="Output YAML file")
parser.add_argument(
"-m", "--merge", help="Merge with existing YAML file (replaces peers key)"
)
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
args = parser.parse_args()
if args.debug:
level = logging.DEBUG
else:
level = logging.INFO
logging.basicConfig(
level=level,
format="%(asctime)s - %(levelname)s - %(message)s",
stream=sys.stderr,
)
if args.output and args.merge:
logging.error("Cannot specify both --output and --merge")
return
# Parse input file and resolve addresses
peers = parse_input_file(args.input)
if not peers:
logging.warning("No valid peers found in input file")
# Prepare output data
if args.merge:
# Load existing YAML and replace peers key
data = load_yaml_file(args.merge)
data["peers"] = peers
write_yaml_file(data, args.merge)
elif args.output:
# Create new YAML file with just peers
data = {"peers": peers}
write_yaml_file(data, args.output)
else:
# Output to stdout
data = {"peers": peers}
yaml.dump(data, sys.stdout, default_flow_style=False, sort_keys=False)
if __name__ == "__main__":
main()

34
poetry.lock generated
View File

@@ -1,34 +0,0 @@
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
[[package]]
name = "ruff"
version = "0.11.5"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"},
{file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"},
{file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"},
{file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"},
{file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"},
{file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"},
{file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"},
{file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"},
{file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"},
{file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"},
{file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"},
{file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"},
{file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"},
{file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"},
{file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"},
{file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"},
{file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"},
{file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"},
]
[metadata]
lock-version = "2.1"
python-versions = ">=3.13"
content-hash = "a6cfc97ad86eb9a6300daf2e42b97ffd445ddcd0c1927366e648227dc55fbbbe"

View File

@@ -1,20 +1,14 @@
[project]
name = "globaltalk-scraper"
version = "0.1.0"
description = ""
authors = [
{name = "Andrew Williams",email = "andy@tensixtyone.com"}
]
license = {text = "MIT"}
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"pyyaml>=6.0.2",
]
[tool.poetry]
package-mode = false
[tool.poetry.group.dev.dependencies]
ruff = "^0.11.0"
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
[dependency-groups]
dev = [
"ruff>=0.12.8",
]

84
uv.lock generated Normal file
View File

@@ -0,0 +1,84 @@
version = 1
revision = 3
requires-python = ">=3.13"
[[package]]
name = "globaltalk-scraper"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "pyyaml" },
]
[package.dev-dependencies]
dev = [
{ name = "ruff" },
]
[package.metadata]
requires-dist = [{ name = "pyyaml", specifier = ">=6.0.2" }]
[package.metadata.requires-dev]
dev = [{ name = "ruff", specifier = ">=0.12.8" }]
[[package]]
name = "pyyaml"
version = "6.0.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
]
[[package]]
name = "ruff"
version = "0.14.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" },
{ url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" },
{ url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" },
{ url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" },
{ url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" },
{ url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" },
{ url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" },
{ url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" },
{ url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" },
{ url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" },
{ url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" },
{ url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" },
{ url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" },
{ url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" },
{ url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" },
{ url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" },
{ url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" },
{ url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" },
]