././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1687378333.1490905 notctyparser-23.6.21/0000755000175000017500000000000014444654635014305 5ustar00mbridakmbridak././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1685651975.0 notctyparser-23.6.21/LICENSE0000644000175000017500000000207014436201007015267 0ustar00mbridakmbridakMIT License Copyright (c) 2019-2022 classabbyamp, 0x5c Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1687378333.1480906 notctyparser-23.6.21/PKG-INFO0000644000175000017500000000331714444654635015406 0ustar00mbridakmbridakMetadata-Version: 2.1 Name: notctyparser Version: 23.6.21 Summary: Just ctyparser with added version check. Author-email: Michael Bridak Project-URL: Homepage, https://github.com/mbridak/ctyparser Project-URL: Bug Tracker, https://github.com/mbridak/ctyparser/issues Classifier: Programming Language :: Python :: 3 Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: MIT License Classifier: Environment :: X11 Applications :: Qt Classifier: Operating System :: POSIX :: Linux Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: Topic :: Communications :: Ham Radio Requires-Python: >=3.9 Description-Content-Type: text/markdown License-File: LICENSE # notctyparser notctyparser is a fork and extension of [ctyparser](https://github.com/miaowware/ctyparser). I've probably screwed something up. Use the original. A CTY.DAT parser for modern amateur radio programs. [![PyPI](https://img.shields.io/pypi/v/notctyparser)](https://pypi.org/project/notctyparser/) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/notctyparser) ![PyPI - License](https://img.shields.io/pypi/l/notctyparser) ## Installation `notctyparser` requires Python 3.6 at minimum. ```none pip install notctyparser ``` ## Fork I just added a stupid check for update without doing the update. ```python def check_update(self) -> bool: ``` - Changed locale to en_US.utf8 - Used xpath to parse the download link. - Chose more narrow raised exceptions. - Specified utf-8 file encoding. - Made the linter happy. ## Copyright Copyright 2019-2022 classabbyamp, 0x5c Released under the MIT License. See `LICENSE` for the full license text. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1687378200.0 notctyparser-23.6.21/README.md0000644000175000017500000000172214444654430015557 0ustar00mbridakmbridak# notctyparser notctyparser is a fork and extension of [ctyparser](https://github.com/miaowware/ctyparser). I've probably screwed something up. Use the original. A CTY.DAT parser for modern amateur radio programs. [![PyPI](https://img.shields.io/pypi/v/notctyparser)](https://pypi.org/project/notctyparser/) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/notctyparser) ![PyPI - License](https://img.shields.io/pypi/l/notctyparser) ## Installation `notctyparser` requires Python 3.6 at minimum. ```none pip install notctyparser ``` ## Fork I just added a stupid check for update without doing the update. ```python def check_update(self) -> bool: ``` - Changed locale to en_US.utf8 - Used xpath to parse the download link. - Chose more narrow raised exceptions. - Specified utf-8 file encoding. - Made the linter happy. ## Copyright Copyright 2019-2022 classabbyamp, 0x5c Released under the MIT License. See `LICENSE` for the full license text. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1687378333.1110902 notctyparser-23.6.21/docs/0000755000175000017500000000000014444654635015235 5ustar00mbridakmbridak././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1685651975.0 notctyparser-23.6.21/docs/conf.py0000644000175000017500000000445214436201007016517 0ustar00mbridakmbridak# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys import miaowware_sphinx_theme sys.path.insert(0, os.path.abspath("..")) # -- Project information ----------------------------------------------------- project = "ctyparser" copyright = "2019-2022 classabbyamp, 0x5c" author = "classabbyamp, 0x5c" # The full version, including alpha/beta/rc tags release = "2.2.0" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named "sphinx.ext.*") or your custom # ones. extensions = [ "sphinx.ext.autodoc" ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "miaowware" html_theme_path = miaowware_sphinx_theme.get_html_theme_path() html_logo = "_static/miaowware_logo.png" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ["_static"] master_doc = "index" # -- Options for autodoc ----------------------------------------------------- autodoc_default_options = { "members": None, "member-order": "bysource", "ignore-module-all": None, } autodoc_typehints = "none" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1687378333.1310904 notctyparser-23.6.21/notctyparser/0000755000175000017500000000000014444654635017042 5ustar00mbridakmbridak././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1685904514.0 notctyparser-23.6.21/notctyparser/__info__.py0000644000175000017500000000056014437156202021131 0ustar00mbridakmbridak"Package information file" # Format and usage strongly inspired by AGProjects/python-application. __project__ = "notctyparser" __summary__ = "A CTY.DAT parser for modern amateur radio programs." __webpage__ = "https://github.com/mbridak/ctyparser" __version__ = "23.6.2" __author__ = "classabbyamp, 0x5c" __email__ = "dev@kb6.ee, dev@0x5c.io" __license__ = "MIT" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1685904514.0 notctyparser-23.6.21/notctyparser/__init__.py0000644000175000017500000000037314437156202021143 0ustar00mbridakmbridak""" ctyparser --- A CTY.DAT parser for modern amateur radio programs. Copyright 2019-2022 classabbyamp, 0x5c Released under the terms of the MIT license. """ from .__info__ import __version__ # noqa: F401 from .bigcty import BigCty # noqa: F401 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1687036768.0 notctyparser-23.6.21/notctyparser/__main__.py0000644000175000017500000000115114443421540021114 0ustar00mbridakmbridak""" ctyparser commandline interface --- Copyright 2019-2022 classabbyamp, 0x5c Released under the terms of the MIT license. """ import os import pathlib import notctyparser file = pathlib.PurePath("./cty.json") try: cty = notctyparser.BigCty(file) except FileNotFoundError: cty = notctyparser.BigCty() print(f"LANG: {os.environ.get('LANG')}") print(f"LC_TIME: {os.environ.get('LC_TIME')}") print("Update:", cty.check_update()) print("Updated:", cty.update()) print("Datestamp:", cty.formatted_version) print("Version Entity:", cty.get("VERSION", "Not present, data possibly corrupted.")) cty.dump(file) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1687378127.0 notctyparser-23.6.21/notctyparser/bigcty.py0000644000175000017500000002527314444654317020703 0ustar00mbridakmbridak""" bigcty.py - part of miaowware/ctyparser --- Copyright 2019-2022 classabbyamp, 0x5c Released under the terms of the MIT license. """ import collections import copy import json import locale import os import pathlib import re import tempfile import zipfile from datetime import datetime from typing import Union import feedparser import requests from lxml import html DEFAULT_FEED = "http://www.country-files.com/category/big-cty/feed/" class BigCty(collections.abc.Mapping): """Class representing a BigCTY dataset. Can be initialised with data by passing the path to a valid ``cty.json`` file to the constructor. :param file_path: Location of the ``cty.json`` file to load. :type file_path: str or os.PathLike, optional :var version: the datestamp of the data, ``YYYYMMDD`` format. :vartype version: str """ regex_version_entry = re.compile(r"VER(\d{8})") regex_feed_date = re.compile(r"(\d{2}-\w+-\d{4})") regex_dat = re.compile( r"""=?(?P[a-zA-Z0-9/]+) (?:\((?P\d+)\))? (?:\[(?P\d+)\])? (?P <(?P[+-]?\d+(?:\.\d+)?) \/ (?P[+-]?\d+(?:.\d+)?)> )? (?:\{(?P\w+)\})? (?:~(?P[+-]?\d+(?:\.\d+)?)~)?""", re.X, ) def __init__(self, file_path: Union[str, os.PathLike, None] = None): locale.setlocale(locale.LC_ALL, "en_US.utf8") self._data: dict = {} self._version = "" if file_path is not None: self.load(file_path) def load(self, cty_file: Union[str, os.PathLike]) -> None: """Loads a ``cty.json`` file into the instance. :param cty_file: Path to the file to load. :type cty_file: str or os.PathLike :return: None """ cty_file = pathlib.Path(cty_file) with cty_file.open("r", encoding="utf-8") as file: ctyjson = json.load(file) self._version = ctyjson.pop("version", None) self._data = ctyjson def dump(self, cty_file: Union[str, os.PathLike]) -> None: """Dumps the data of the instance to a ``cty.json`` file. :param cty_file: Path to the file to dump to. :type cty_file: str or os.PathLike :return: None """ cty_file = pathlib.Path(cty_file) datadump = self._data.copy() datadump["version"] = self._version with cty_file.open("w", encoding="utf-8") as file: json.dump(datadump, file) def import_dat(self, dat_file: Union[str, os.PathLike]) -> None: """Imports CTY data from a ``CTY.DAT`` file. :param dat_file: Path to the file to import. :type dat_file: str or os.PathLike :return: None """ dat_file = pathlib.Path(dat_file) with dat_file.open("r", encoding="utf-8") as file: cty_dict = {} # get the version from the file ver_match = re.search(self.regex_version_entry, file.read()) self._version = ver_match.group(1) if ver_match is not None else "" file.seek(0) # stores the previous prefix for the next iteration last = "" while True: line = ( file.readline().rstrip("\r").strip(":") ) # remove unnecessary carriage returns and colons if not line: break # check if the line introduces new DXCC if line != "" and line[0].isalpha(): # split line into fields at delimiters segments = [x.strip() for x in line.split(":")] # check if this entity is not a DXCC if segments[7][0] == "*": segments[7] = segments[7][1:] segments[0] += " (not DXCC)" cty_dict[segments[7]] = { "entity": segments[0], "cq": int(segments[1]), "itu": int(segments[2]), "continent": segments[3], "lat": float(segments[4]), "long": float(segments[5]), "tz": -1 * float(segments[6]), "len": len(segments[7]), "primary_pfx": segments[7], "exact_match": False, } # store the current prefix for the next iteration last = segments[7] # check if the line continues a DXCC elif line != "" and line[0].isspace(): overrides = line.strip().rstrip(";").rstrip(",").split(",") for item in overrides: if item not in cty_dict: # get the already stored data from primary prefix data = copy.deepcopy(cty_dict[last]) # apply regex to extract the prefix and overrides match = re.search(self.regex_dat, item) if match is None: continue if match.group("itu"): data["itu"] = int(match.group("itu")) if match.group("cq"): data["cq"] = int(match.group("cq")) if match.group("latlong"): data["lat"] = float(match.group("lat")) data["long"] = float(match.group("long")) if match.group("continent"): data["continent"] = match.group("continent") if match.group("tz"): data["tz"] = -1 * float(match.group("tz")) if item.startswith("="): data["exact_match"] = True prefix = match.group("prefix") cty_dict[prefix] = data self._data = cty_dict def check_update(self) -> bool: """Checks if an update exists. :raises AttributeError : If there is no date in the feed. :return: ``True`` if an update is available, otherwise ``False``. :rtype: bool """ with requests.Session() as session: feed = session.get(DEFAULT_FEED) parsed_feed = feedparser.parse(feed.content) update_url = parsed_feed.entries[0]["link"] date_match = re.search(self.regex_feed_date, update_url) if date_match is None: raise AttributeError("Error parsing feed: date missing") date_str = date_match.group(1).title() update_date = datetime.strftime( datetime.strptime(date_str, "%d-%B-%Y"), "%Y%m%d" ) if self._version == update_date: return False return True def update(self) -> bool: """Upates the instance's data from the feed. :raises AttributeError: If there is no date in the feed. :raises ResourceWarning: If unable to download bigcty. :return: ``True`` if an update was done, otherwise ``False``. :rtype: bool """ with requests.Session() as session: feed = session.get(DEFAULT_FEED) parsed_feed = feedparser.parse(feed.content) update_url = parsed_feed.entries[0]["link"] date_match = re.search(self.regex_feed_date, update_url) if date_match is None: raise AttributeError("Error parsing feed: date missing") date_str = date_match.group(1).title() update_date = datetime.strftime( datetime.strptime(date_str, "%d-%B-%Y"), "%Y%m%d" ) if self._version == update_date: return False with tempfile.TemporaryDirectory() as temp: path = pathlib.PurePath(temp) page = session.get(update_url) tree = html.fromstring(page.content) urls = tree.xpath("//a[contains(@href,'zip')]/@href") if len(urls) == 0: raise ResourceWarning( f"Unable to find and download bigcty-{update_date}.zip" ) dl_url = urls[0] the_request = session.get(dl_url) if the_request.status_code == 404: dl_url = ( "http://www.country-files.com/bigcty/download/bigcty-" f"{update_date}.zip" ) the_request = session.get(dl_url) if the_request.status_code != 200: raise ResourceWarning( f"Unable to find and download bigcty-{update_date}.zip" ) with open(path / "cty.zip", "wb+") as file: file.write(the_request.content) zipfile.ZipFile(file).extract( "cty.dat", path=str(path) ) # Force cast as str because mypy self.import_dat(path / "cty.dat") return True @property def formatted_version(self) -> str: """Formatted representation of the version/date of the current BigCTY data. :getter: Returns version in ``YYYY-MM-DD`` format, or ``0000-00-00`` (if invalid date) :type: str """ try: return datetime.strptime(self._version, "%Y%m%d").strftime("%Y-%m-%d") except ValueError: return "0000-00-00" @property def version(self) -> str: """The version/date of the current BigCTY data. :getter: Returns version in ``YYYYMMDD`` format :type: str """ return self._version # --- Wrappers to implement dict-like functionality --- def __len__(self): return len(self._data) def __getitem__(self, key: str): return self._data[key] def __iter__(self): return iter(self._data) # --- Standard methods we should all implement --- # str(): Simply return what it would be for the underlaying dict def __str__(self): return str(self._data) # repr(): Class name, instance ID, and last_updated def __repr__(self): return ( f"<{type(self).__module__}.{type(self).__qualname__} object" f"at {hex(id(self))}, last_updated={self.last_updated}>" ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1687378333.1430905 notctyparser-23.6.21/notctyparser.egg-info/0000755000175000017500000000000014444654635020534 5ustar00mbridakmbridak././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1687378333.0 notctyparser-23.6.21/notctyparser.egg-info/PKG-INFO0000644000175000017500000000331714444654635021635 0ustar00mbridakmbridakMetadata-Version: 2.1 Name: notctyparser Version: 23.6.21 Summary: Just ctyparser with added version check. Author-email: Michael Bridak Project-URL: Homepage, https://github.com/mbridak/ctyparser Project-URL: Bug Tracker, https://github.com/mbridak/ctyparser/issues Classifier: Programming Language :: Python :: 3 Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: MIT License Classifier: Environment :: X11 Applications :: Qt Classifier: Operating System :: POSIX :: Linux Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: Topic :: Communications :: Ham Radio Requires-Python: >=3.9 Description-Content-Type: text/markdown License-File: LICENSE # notctyparser notctyparser is a fork and extension of [ctyparser](https://github.com/miaowware/ctyparser). I've probably screwed something up. Use the original. A CTY.DAT parser for modern amateur radio programs. [![PyPI](https://img.shields.io/pypi/v/notctyparser)](https://pypi.org/project/notctyparser/) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/notctyparser) ![PyPI - License](https://img.shields.io/pypi/l/notctyparser) ## Installation `notctyparser` requires Python 3.6 at minimum. ```none pip install notctyparser ``` ## Fork I just added a stupid check for update without doing the update. ```python def check_update(self) -> bool: ``` - Changed locale to en_US.utf8 - Used xpath to parse the download link. - Chose more narrow raised exceptions. - Specified utf-8 file encoding. - Made the linter happy. ## Copyright Copyright 2019-2022 classabbyamp, 0x5c Released under the MIT License. See `LICENSE` for the full license text. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1687378333.0 notctyparser-23.6.21/notctyparser.egg-info/SOURCES.txt0000644000175000017500000000050214444654635022415 0ustar00mbridakmbridakLICENSE README.md pyproject.toml docs/conf.py notctyparser/__info__.py notctyparser/__init__.py notctyparser/__main__.py notctyparser/bigcty.py notctyparser.egg-info/PKG-INFO notctyparser.egg-info/SOURCES.txt notctyparser.egg-info/dependency_links.txt notctyparser.egg-info/requires.txt notctyparser.egg-info/top_level.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1687378333.0 notctyparser-23.6.21/notctyparser.egg-info/dependency_links.txt0000644000175000017500000000000114444654635024602 0ustar00mbridakmbridak ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1687378333.0 notctyparser-23.6.21/notctyparser.egg-info/requires.txt0000644000175000017500000000003114444654635023126 0ustar00mbridakmbridakfeedparser requests lxml ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1687378333.0 notctyparser-23.6.21/notctyparser.egg-info/top_level.txt0000644000175000017500000000002714444654635023265 0ustar00mbridakmbridakdist docs notctyparser ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1687378185.0 notctyparser-23.6.21/pyproject.toml0000644000175000017500000000163114444654411017212 0ustar00mbridakmbridak[build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] name = "notctyparser" version = "23.6.21" description = "Just ctyparser with added version check." readme = "README.md" requires-python = ">=3.9" authors = [ { name="Michael Bridak", email="michael.bridak@gmail.com" }, ] dependencies = [ "feedparser", "requests", "lxml", ] classifiers = [ "Programming Language :: Python :: 3", "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", "Environment :: X11 Applications :: Qt", "Operating System :: POSIX :: Linux", "Intended Audience :: Developers", "Natural Language :: English", "Topic :: Communications :: Ham Radio", ] [project.urls] "Homepage" = "https://github.com/mbridak/ctyparser" "Bug Tracker" = "https://github.com/mbridak/ctyparser/issues" [tool.setuptools.packages.find] where = ["."] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1687378333.1490905 notctyparser-23.6.21/setup.cfg0000644000175000017500000000004614444654635016126 0ustar00mbridakmbridak[egg_info] tag_build = tag_date = 0