mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2026-10-27 01:16:06 +03:00
Merge branch 'develop' into develop
This commit is contained in:
44 files changed
+277
-178
No files matched your search
@@ -59,7 +59,7 @@ jobs:
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
# based on.
|
||||
- name: Checkout head commits
|
||||
# https://github.com/actions/checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
#with:
|
||||
#ref: ${{github.head.sha}}
|
||||
#repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Python
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
OWNER: 'EDCD'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: '${{ env.CHECKOUT_BRANCH }}'
|
||||
submodules: true
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
name: Linux environment build steps
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
- name: Create .gitversion
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
shell: powershell
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
@@ -158,7 +158,7 @@ jobs:
|
||||
shell: powershell
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
|
||||
+6
-6
@@ -193,7 +193,7 @@ class Logger:
|
||||
self.logger_channel_rotating.setFormatter(self.logger_formatter)
|
||||
self.logger.addHandler(self.logger_channel_rotating)
|
||||
|
||||
def get_logger(self) -> 'LoggerMixin':
|
||||
def get_logger(self) -> LoggerMixin:
|
||||
"""
|
||||
Obtain the self.logger of the class instance.
|
||||
|
||||
@@ -232,7 +232,7 @@ class Logger:
|
||||
logger.trace("Not changing log level because it's TRACE") # type: ignore
|
||||
|
||||
|
||||
def get_plugin_logger(plugin_name: str, loglevel: int = _default_loglevel) -> 'LoggerMixin':
|
||||
def get_plugin_logger(plugin_name: str, loglevel: int = _default_loglevel) -> LoggerMixin:
|
||||
"""
|
||||
Return a logger suitable for a plugin.
|
||||
|
||||
@@ -352,7 +352,7 @@ class EDMCContextFilter(logging.Filter):
|
||||
try:
|
||||
args, _, _, value_dict = inspect.getargvalues(frame)
|
||||
if len(args) and args[0] in ('self', 'cls'):
|
||||
frame_class: 'object' = value_dict[args[0]]
|
||||
frame_class: object = value_dict[args[0]]
|
||||
|
||||
if frame_class:
|
||||
# See https://en.wikipedia.org/wiki/Name_mangling#Python for how name mangling works.
|
||||
@@ -450,7 +450,7 @@ class EDMCContextFilter(logging.Filter):
|
||||
# Go up through stack frames until we find the first with a
|
||||
# type(f_locals.self) of logging.Logger. This should be the start
|
||||
# of the frames internal to logging.
|
||||
frame: 'FrameType' = getframe(0)
|
||||
frame: FrameType = getframe(0)
|
||||
while frame:
|
||||
if isinstance(frame.f_locals.get('self'), logging.Logger):
|
||||
frame = cast('FrameType', frame.f_back) # Want to start on the next frame below
|
||||
@@ -518,7 +518,7 @@ class EDMCContextFilter(logging.Filter):
|
||||
return module_name
|
||||
|
||||
|
||||
def get_main_logger(sublogger_name: str = '') -> 'LoggerMixin':
|
||||
def get_main_logger(sublogger_name: str = '') -> LoggerMixin:
|
||||
"""Return the correct logger for how the program is being run."""
|
||||
if not os.getenv("EDMC_NO_UI"):
|
||||
# GUI app being run
|
||||
@@ -537,4 +537,4 @@ base_logger_name = appcmdname if os.getenv('EDMC_NO_UI') else appname
|
||||
edmclogger = Logger(base_logger_name, loglevel=loglevel)
|
||||
for h in config_logger.handlers[:]:
|
||||
config_logger.removeHandler(h)
|
||||
logger: 'LoggerMixin' = edmclogger.get_logger()
|
||||
logger: LoggerMixin = edmclogger.get_logger()
|
||||
@@ -54,7 +54,7 @@ if __name__ == '__main__':
|
||||
log_file_path.mkdir(exist_ok=True)
|
||||
log_file_path /= f'{appname}.log'
|
||||
|
||||
sys.stdout = sys.stderr = open(log_file_path, mode='wt', buffering=1) # Do NOT use WITH here.
|
||||
sys.stdout = sys.stderr = open(log_file_path, mode='w', buffering=1) # Do NOT use WITH here.
|
||||
# TODO: Test: Make *sure* this redirect is working, else py2exe is going to cause an exit popup
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ if __name__ == '__main__': # noqa: C901
|
||||
|
||||
if args.capi_use_debug_access_token:
|
||||
import config as conf_module
|
||||
with open(conf_module.config.app_dir_path / 'access_token.txt', 'r') as at:
|
||||
with open(conf_module.config.app_dir_path / 'access_token.txt') as at:
|
||||
conf_module.capi_debug_access_token = at.readline().strip()
|
||||
|
||||
level_to_set: int | None = None
|
||||
@@ -476,7 +476,7 @@ class AppWindow:
|
||||
if sys.platform == 'win32' and not bool(config.get_int('no_systray')):
|
||||
from simplesystray import SysTrayIcon
|
||||
|
||||
def open_window(systray: 'SysTrayIcon', *args) -> None:
|
||||
def open_window(systray: SysTrayIcon, *args) -> None:
|
||||
self.w.deiconify()
|
||||
|
||||
logfile_loc = pathlib.Path(config.app_dir_path / 'logs')
|
||||
@@ -612,7 +612,7 @@ class AppWindow:
|
||||
self.status.grid(columnspan=2, sticky=tk.EW)
|
||||
|
||||
for child in frame.winfo_children():
|
||||
child.grid_configure(padx=self.PADX, pady=(
|
||||
child.grid_configure(padx=self.PADX, pady=( # type: ignore
|
||||
sys.platform != 'win32' or isinstance(child, tk.Frame)) and 2 or 0)
|
||||
|
||||
self.menubar = tk.Menu()
|
||||
@@ -953,7 +953,7 @@ class AppWindow:
|
||||
|
||||
self.cooldown()
|
||||
|
||||
def export_market_data(self, data: 'CAPIData') -> bool: # noqa: CCR001
|
||||
def export_market_data(self, data: CAPIData) -> bool: # noqa: CCR001
|
||||
"""
|
||||
Export CAPI market data.
|
||||
|
||||
@@ -1872,7 +1872,7 @@ class AppWindow:
|
||||
h.write(str(companion.session.capi_raw_data).encode(encoding='utf-8'))
|
||||
|
||||
if sys.platform == 'win32':
|
||||
def exit_tray(self, systray: 'SysTrayIcon') -> None:
|
||||
def exit_tray(self, systray: SysTrayIcon) -> None:
|
||||
"""Tray icon is shutting down."""
|
||||
exit_thread = threading.Thread(
|
||||
target=self.onexit,
|
||||
|
||||
@@ -839,3 +839,9 @@
|
||||
|
||||
/* edastro_core.py: Settings>EDAstro - Label on checkbox for 'send data'; */
|
||||
"Send data to EDAstro" = "Send data to EDAstro";
|
||||
|
||||
/* common_coreutils.py: Colonisation ship name, with title; In files: common_coreutils.py:81:81 */
|
||||
"System Colonisation Ship {NAME}" = "System Colonisation Ship {NAME}";
|
||||
|
||||
/* common_coreutils.py: Unidentified colonisation ship; In files: common_coreutils.py:84:84 */
|
||||
"System Colonisation Ship" = "System Colonisation Ship";
|
||||
+13
@@ -163,6 +163,19 @@ circular imports.
|
||||
For an example of how this is done, look at the code in `plugins/common_coreutils.py`
|
||||
and the usage of these functions in other core plugins.
|
||||
|
||||
Plugins may import additional code modules from `load.py` in the usual way. Note
|
||||
that using an import statement like `import my_module` or `from my_module import
|
||||
foo` will only import one module with that name **across all plugins**. This can
|
||||
be used to create a shared library used by several plugins if used carefully,
|
||||
but may create a conflict if another plugin also has a module of the same name
|
||||
containing different code. For example if two plugins both contain a
|
||||
`widgets.py` file and both do `import widgets`, only one of the two modules will
|
||||
actually be imported.
|
||||
|
||||
To avoid this issue, use a relative import statement such as `import .widgets`
|
||||
or `from .widgets import foo` to ensure that the module is loaded from the
|
||||
current plugin, even if the name conflicts with another plugin's code.
|
||||
|
||||
---
|
||||
|
||||
## Logging
|
||||
|
||||
+39
-45
@@ -1,9 +1,10 @@
|
||||
"""Export various CSV formats."""
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
import csv
|
||||
from pathlib import Path
|
||||
|
||||
from config import config
|
||||
from edmc_data import commodity_bracketmap as bracketmap
|
||||
|
||||
@@ -12,7 +13,7 @@ from edmc_data import commodity_bracketmap as bracketmap
|
||||
(COMMODITY_DEFAULT, COMMODITY_CSV) = range(2)
|
||||
|
||||
|
||||
def export(data, kind=COMMODITY_DEFAULT, filename=None) -> None:
|
||||
def export(data, kind=COMMODITY_DEFAULT, filename=None) -> None: # noqa: CCR001
|
||||
"""
|
||||
Export commodity data from the given CAPI data.
|
||||
|
||||
@@ -21,55 +22,48 @@ def export(data, kind=COMMODITY_DEFAULT, filename=None) -> None:
|
||||
:param filename: Filename to write to, or None for a standard format name.
|
||||
:return:
|
||||
"""
|
||||
querytime = config.get_int('querytime', default=int(time.time()))
|
||||
querytime = config.get_int("querytime", default=int(time.time()))
|
||||
|
||||
if not filename:
|
||||
filename_system = data['lastSystem']['name'].strip()
|
||||
filename_starport = data['lastStarport']['name'].strip()
|
||||
filename_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime))
|
||||
filename_kind = 'csv'
|
||||
filename = f'{filename_system}.{filename_starport}.{filename_time}.{filename_kind}'
|
||||
filename = Path(config.get_str('outdir')) / filename
|
||||
|
||||
if kind == COMMODITY_CSV:
|
||||
sep = ';' # BUG: for fixing later after cleanup
|
||||
header = sep.join(('System', 'Station', 'Commodity', 'Sell', 'Buy', 'Demand', '', 'Supply', '', 'Date', '\n'))
|
||||
rowheader = sep.join((data['lastSystem']['name'], data['lastStarport']['name']))
|
||||
|
||||
else:
|
||||
sep = ','
|
||||
header = sep.join(
|
||||
('System', 'Station', 'Commodity', 'Sell', 'Buy', 'Demand', '', 'Supply', '', 'Average', 'FDevID', 'Date\n')
|
||||
sysname = data["lastSystem"]["name"].strip()
|
||||
station = data["lastStarport"]["name"].strip()
|
||||
timestamp = time.strftime("%Y-%m-%dT%H.%M.%S", time.localtime(querytime))
|
||||
ext = "csv" if kind == COMMODITY_CSV else "scsv"
|
||||
filename = (
|
||||
Path(config.get_str("outdir")) / f"{sysname}.{station}.{timestamp}.{ext}"
|
||||
)
|
||||
|
||||
rowheader = sep.join((data['lastSystem']['name'], data['lastStarport']['name']))
|
||||
system = data["lastSystem"]["name"]
|
||||
station = data["lastStarport"]["name"]
|
||||
|
||||
with open(filename, 'wt') as h: # codecs can't automatically handle line endings, so encode manually where required
|
||||
h.write(header)
|
||||
if kind == COMMODITY_CSV:
|
||||
header = ["System", "Station", "Commodity", "Sell", "Buy", "Demand",
|
||||
"", "Supply", "", "Date"]
|
||||
else:
|
||||
header = ["System", "Station", "Commodity", "Sell", "Buy", "Demand",
|
||||
"", "Supply", "", "Average", "FDevID", "Date"]
|
||||
|
||||
for commodity in data['lastStarport']['commodities']:
|
||||
line = sep.join((
|
||||
rowheader,
|
||||
commodity['name'],
|
||||
commodity['sellPrice'] and str(int(commodity['sellPrice'])) or '',
|
||||
commodity['buyPrice'] and str(int(commodity['buyPrice'])) or '',
|
||||
str(int(commodity['demand'])) if commodity['demandBracket'] else '',
|
||||
bracketmap[commodity['demandBracket']],
|
||||
str(int(commodity['stock'])) if commodity['stockBracket'] else '',
|
||||
bracketmap[commodity['stockBracket']]
|
||||
))
|
||||
with open(filename, "w", newline="", encoding="utf-8") as output_file:
|
||||
writer = csv.writer(output_file, delimiter=";" if kind == COMMODITY_DEFAULT else ",")
|
||||
|
||||
writer.writerow(header)
|
||||
|
||||
for commodity in data["lastStarport"]["commodities"]:
|
||||
row = [
|
||||
system,
|
||||
station,
|
||||
commodity["name"],
|
||||
int(commodity["sellPrice"]) if commodity.get("sellPrice") is not None else "",
|
||||
int(commodity["buyPrice"]) if commodity.get("buyPrice") is not None else "",
|
||||
int(commodity["demand"]) if commodity.get("demandBracket") is not None else "",
|
||||
bracketmap.get(commodity.get("demandBracket"), ""),
|
||||
int(commodity["stock"]) if commodity.get("stockBracket") is not None else "",
|
||||
bracketmap.get(commodity.get("stockBracket"), ""),
|
||||
]
|
||||
|
||||
if kind == COMMODITY_DEFAULT:
|
||||
line = sep.join(
|
||||
(
|
||||
line,
|
||||
str(int(commodity['meanPrice'])),
|
||||
str(commodity['id']),
|
||||
data['timestamp'] + '\n'
|
||||
)
|
||||
)
|
||||
mean = int(commodity["meanPrice"]) if commodity.get("meanPrice") is not None else ""
|
||||
row.extend([mean, commodity["id"]])
|
||||
|
||||
else:
|
||||
line = sep.join((line, data['timestamp'] + '\n'))
|
||||
|
||||
h.write(line)
|
||||
row.append(data["timestamp"])
|
||||
writer.writerow(row)
|
||||
+13
-14
@@ -29,23 +29,22 @@ def ensure_on_screen(self, parent: tk.Tk):
|
||||
:param self: The calling class instance of tk.TopLevel
|
||||
:param parent: The parent window
|
||||
"""
|
||||
if sys.platform != 'win32':
|
||||
return
|
||||
try:
|
||||
# Get monitor info for the monitor containing the parent window
|
||||
monitor = win32api.MonitorFromWindow(parent.winfo_id(), win32con.MONITOR_DEFAULTTONEAREST)
|
||||
monitor_info = win32api.GetMonitorInfo(monitor)
|
||||
work_area = monitor_info['Work'] # Gets the working area (excludes taskbar)
|
||||
if sys.platform == 'win32':
|
||||
try:
|
||||
# Get monitor info for the monitor containing the parent window
|
||||
monitor = win32api.MonitorFromWindow(parent.winfo_id(), win32con.MONITOR_DEFAULTTONEAREST)
|
||||
monitor_info = win32api.GetMonitorInfo(monitor)
|
||||
work_area = monitor_info['Work'] # Gets the working area (excludes taskbar)
|
||||
|
||||
# Calculate optimal position
|
||||
x = max(work_area[0], min(parent.winfo_rootx(), work_area[2] - self.winfo_width()))
|
||||
y = max(work_area[1], min(parent.winfo_rooty(), work_area[3] - self.winfo_height()))
|
||||
# Calculate optimal position
|
||||
x = max(work_area[0], min(parent.winfo_rootx(), work_area[2] - self.winfo_width()))
|
||||
y = max(work_area[1], min(parent.winfo_rooty(), work_area[3] - self.winfo_height()))
|
||||
|
||||
# Update window position
|
||||
self.geometry(f"+{x}+{y}")
|
||||
# Update window position
|
||||
self.geometry(f"+{x}+{y}")
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to ensure window is on screen: {e}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to ensure window is on screen: {e}")
|
||||
|
||||
|
||||
def log_locale(prefix: str) -> None:
|
||||
|
||||
+5
-4
@@ -29,7 +29,8 @@ import webbrowser
|
||||
from email.utils import parsedate
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING, Any, Mapping, TypeVar
|
||||
from typing import TYPE_CHECKING, Any, TypeVar
|
||||
from collections.abc import Mapping
|
||||
import requests
|
||||
import config as conf_module
|
||||
import killswitch
|
||||
@@ -69,7 +70,7 @@ class CAPIData(UserDict):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: str | dict[str, Any] | 'CAPIData' | None = None,
|
||||
data: str | dict[str, Any] | CAPIData | None = None,
|
||||
source_host: str | None = None,
|
||||
source_endpoint: str | None = None,
|
||||
request_cmdr: str | None = None
|
||||
@@ -1204,7 +1205,7 @@ def fixup(data: CAPIData) -> CAPIData: # noqa: C901, CCR001 # Can't be usefully
|
||||
if not (config.app_dir_path / 'FDevIDs' / f).is_file():
|
||||
logger.warning(f'FDevID file {f} not found! Generating output without these commodity name rewrites.')
|
||||
continue
|
||||
with open(config.app_dir_path / 'FDevIDs' / f, 'r') as csvfile:
|
||||
with open(config.app_dir_path / 'FDevIDs' / f) as csvfile:
|
||||
reader = csv.DictReader(csvfile)
|
||||
|
||||
for row in reader:
|
||||
@@ -1325,7 +1326,7 @@ def index_possibly_sparse_list(data: Mapping[str, V] | list[V], key: int) -> V:
|
||||
if isinstance(data, list):
|
||||
return data[key]
|
||||
|
||||
if isinstance(data, (dict, dict)):
|
||||
if isinstance(data, dict):
|
||||
return data[str(key)]
|
||||
|
||||
raise ValueError(f'Unexpected data type {type(data)}')
|
||||
|
||||
+3
-2
@@ -43,7 +43,8 @@ import sys
|
||||
import tomllib
|
||||
import tomli_w
|
||||
from time import gmtime
|
||||
from typing import Any, Callable, Type, TypeVar
|
||||
from typing import Any, TypeVar
|
||||
from collections.abc import Callable
|
||||
import semantic_version
|
||||
from constants import GITVERSION_FILE, applongname, appname
|
||||
|
||||
@@ -474,7 +475,7 @@ class Config:
|
||||
@staticmethod
|
||||
def _suppress_call(
|
||||
func: Callable[..., _T],
|
||||
exceptions: Type[BaseException] | list[Type[BaseException]] = Exception,
|
||||
exceptions: type[BaseException] | list[type[BaseException]] = Exception,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> _T | None:
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import sys
|
||||
from config import config_logger
|
||||
|
||||
if sys.platform != "linux":
|
||||
raise EnvironmentError("This file is for Linux only.")
|
||||
raise OSError("This file is for Linux only.")
|
||||
|
||||
|
||||
import pathlib
|
||||
@@ -137,7 +137,7 @@ class LinuxConfigMinimal:
|
||||
|
||||
def close(self) -> None:
|
||||
"""Release resources (stub)."""
|
||||
self.config = None
|
||||
del self.config
|
||||
|
||||
|
||||
def linux_helper(config: Config) -> Config:
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
|
||||
from . import Config
|
||||
|
||||
if sys.platform != "win32":
|
||||
raise EnvironmentError("This file is for Windows only.")
|
||||
raise OSError("This file is for Windows only.")
|
||||
|
||||
REG_RESERVED_ALWAYS_ZERO = 0
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ if __name__ == "__main__": # noqa: C901
|
||||
# Symbolic name from in-game name
|
||||
reverse_ship_map = {v: k for k, v in list(ship_name_map.items())}
|
||||
|
||||
bulkheads = list(outfitting.armour_map.keys())
|
||||
bulkheads = list(outfitting.armour_map)
|
||||
|
||||
ships = {}
|
||||
modules = {}
|
||||
|
||||
+2
-1
@@ -7,7 +7,8 @@ import pathlib
|
||||
import threading
|
||||
import zlib
|
||||
from http import server
|
||||
from typing import Any, Callable, Literal
|
||||
from typing import Any, Literal
|
||||
from collections.abc import Callable
|
||||
from urllib.parse import parse_qs
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
|
||||
+1
-1
@@ -605,7 +605,7 @@ edmc_suit_symbol_localised = {
|
||||
# This is sourced from what the game is passed by the launcher, caveat emptor. It was mentioned that / is also
|
||||
# an option
|
||||
# This is only run once when this file is imported by something, no runtime cost or repeated expansions will occur
|
||||
__keys = list(edmc_suit_symbol_localised.keys())
|
||||
__keys = list(edmc_suit_symbol_localised)
|
||||
for lang in __keys:
|
||||
new_lang = lang.replace('\\', r'\\')
|
||||
new_lang_2 = lang.replace('\\', '/')
|
||||
|
||||
+2
-2
@@ -189,7 +189,7 @@ def export(data, filename=None) -> None: # noqa: C901, CCR001
|
||||
raise
|
||||
|
||||
if filename:
|
||||
with open(filename, 'wt') as h:
|
||||
with open(filename, 'w') as h:
|
||||
h.write(string)
|
||||
return
|
||||
|
||||
@@ -206,5 +206,5 @@ def export(data, filename=None) -> None: # noqa: C901, CCR001
|
||||
timestamp = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime))
|
||||
filename = pathlib.Path(config.get_str('outdir')) / f'{ship}.{timestamp}.txt'
|
||||
|
||||
with open(filename, 'wt') as h:
|
||||
with open(filename, 'w') as h:
|
||||
h.write(string)
|
||||
+1
-1
@@ -6,7 +6,7 @@ from EDMCLogging import get_main_logger
|
||||
from hotkey import AbstractHotkeyMgr
|
||||
|
||||
if sys.platform != 'linux':
|
||||
raise EnvironmentError("This file is for Linux only.")
|
||||
raise OSError("This file is for Linux only.")
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ from EDMCLogging import get_main_logger
|
||||
from hotkey import AbstractHotkeyMgr
|
||||
|
||||
if sys.platform != 'win32':
|
||||
raise EnvironmentError("This file is for Windows only.")
|
||||
raise OSError("This file is for Windows only.")
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import tkinter as tk
|
||||
from enum import Enum
|
||||
from os import getpid as os_getpid
|
||||
from tkinter import ttk
|
||||
from typing import Callable
|
||||
from collections.abc import Callable
|
||||
from l10n import translations as tr
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
|
||||
+3
-2
@@ -11,9 +11,10 @@ import json
|
||||
import threading
|
||||
from copy import deepcopy
|
||||
from typing import (
|
||||
TYPE_CHECKING, Any, Callable, Mapping, MutableMapping, MutableSequence, NamedTuple, Sequence,
|
||||
TYPE_CHECKING, Any, NamedTuple,
|
||||
TypedDict, TypeVar, cast, Union
|
||||
)
|
||||
from collections.abc import Callable, Mapping, MutableMapping, MutableSequence, Sequence
|
||||
import requests
|
||||
import semantic_version
|
||||
from semantic_version.base import Version
|
||||
@@ -217,7 +218,7 @@ class KillSwitchSet:
|
||||
def __init__(self, kill_switches: list[KillSwitches]) -> None:
|
||||
self.kill_switches = kill_switches
|
||||
|
||||
def get_disabled(self, id: str, *, version: Union[Version, str] = _current_version) -> DisabledResult:
|
||||
def get_disabled(self, id: str, *, version: Version | str = _current_version) -> DisabledResult:
|
||||
"""
|
||||
Return whether the given feature ID is disabled by a killswitch for the given version.
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ import re
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
from os import listdir, sep
|
||||
from typing import Iterable, TextIO, cast
|
||||
from typing import TextIO, cast
|
||||
from collections.abc import Iterable
|
||||
import pathlib
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
|
||||
+3
-3
@@ -33,7 +33,7 @@ def export(data: companion.CAPIData, requested_filename: str | None = None) -> N
|
||||
) # pretty print
|
||||
|
||||
if requested_filename is not None and requested_filename:
|
||||
with open(requested_filename, 'wt') as h:
|
||||
with open(requested_filename, 'w') as h:
|
||||
h.write(string)
|
||||
return
|
||||
if not requested_filename:
|
||||
@@ -45,7 +45,7 @@ def export(data: companion.CAPIData, requested_filename: str | None = None) -> N
|
||||
regexp = re.compile(re.escape(ship) + r'\.\d\d\d\d-\d\d-\d\dT\d\d\.\d\d\.\d\d\.txt')
|
||||
oldfiles = sorted([x for x in listdir(config.get_str('outdir')) if regexp.match(x)])
|
||||
if oldfiles:
|
||||
with open(Path(config.get_str('outdir')) / Path(oldfiles[-1]), 'rU') as h:
|
||||
with open(Path(config.get_str('outdir')) / Path(oldfiles[-1])) as h:
|
||||
if h.read() == string:
|
||||
return # same as last time - don't write
|
||||
|
||||
@@ -57,5 +57,5 @@ def export(data: companion.CAPIData, requested_filename: str | None = None) -> N
|
||||
ship_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(query_time))
|
||||
file_path = output_directory / f"{ship}.{ship_time}.txt"
|
||||
|
||||
with open(file_path, 'wt') as h:
|
||||
with open(file_path, 'w') as h:
|
||||
h.write(string)
|
||||
+16
-7
@@ -18,7 +18,8 @@ from collections import defaultdict
|
||||
from os import SEEK_END, SEEK_SET, listdir
|
||||
from os.path import basename, expanduser, getctime, isdir, join
|
||||
from time import gmtime, localtime, mktime, sleep, strftime, strptime, time
|
||||
from typing import TYPE_CHECKING, Any, BinaryIO, MutableMapping
|
||||
from typing import TYPE_CHECKING, Any, BinaryIO
|
||||
from collections.abc import MutableMapping
|
||||
import psutil
|
||||
import semantic_version
|
||||
import util_ships
|
||||
@@ -347,7 +348,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
"""
|
||||
return bool(self.thread and self.thread.is_alive())
|
||||
|
||||
def on_created(self, event: 'FileSystemEvent') -> None:
|
||||
def on_created(self, event: FileSystemEvent) -> None:
|
||||
"""Watchdog callback when, e.g. client (re)started."""
|
||||
if not event.is_directory and self._RE_LOGFILE.search(str(basename(event.src_path))):
|
||||
|
||||
@@ -811,6 +812,10 @@ class EDLogs(FileSystemEventHandler):
|
||||
self.state['OnFoot'] = False
|
||||
self.state['Taxi'] = entry['Taxi']
|
||||
|
||||
if entry['Multicrew']:
|
||||
# Player has boarded another player's ship, but has not selected a role yet
|
||||
self.state['Role'] = 'Idle'
|
||||
|
||||
# We can't now have anything in the BackPack, it's all in the
|
||||
# ShipLocker.
|
||||
self.backpack_set_empty()
|
||||
@@ -846,6 +851,10 @@ class EDLogs(FileSystemEventHandler):
|
||||
self.state['Taxi'] = False
|
||||
self.state['Dropship'] = False
|
||||
|
||||
# Since the player is now on foot, it's no longer possible to be multicrewing, so no
|
||||
# need to actually check the multicrew parameter.
|
||||
self.state['Role'] = None
|
||||
|
||||
elif event_type == 'dropshipdeploy':
|
||||
# We're definitely on-foot now
|
||||
self.state['OnFoot'] = True
|
||||
@@ -2267,7 +2276,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
string = json.dumps(self.ship(False), ensure_ascii=False, indent=2, separators=(',', ': ')) # pretty print
|
||||
if filename:
|
||||
try:
|
||||
with open(filename, 'wt', encoding='utf-8') as h:
|
||||
with open(filename, 'w', encoding='utf-8') as h:
|
||||
h.write(string)
|
||||
|
||||
except UnicodeError:
|
||||
@@ -2276,7 +2285,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
)
|
||||
|
||||
try:
|
||||
with open(filename, 'wt') as h:
|
||||
with open(filename, 'w') as h:
|
||||
h.write(string)
|
||||
|
||||
except OSError:
|
||||
@@ -2291,7 +2300,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
ship = util_ships.ship_file_name(self.state['ShipName'], self.state['ShipType'])
|
||||
regexp = re.compile(re.escape(ship) + r'\.\d{4}-\d\d-\d\dT\d\d\.\d\d\.\d\d\.txt')
|
||||
oldfiles = sorted((x for x in listdir(config.get_str('outdir')) if regexp.match(x)))
|
||||
oldfiles = sorted(x for x in listdir(config.get_str('outdir')) if regexp.match(x))
|
||||
if oldfiles:
|
||||
try:
|
||||
with open(join(config.get_str('outdir'), oldfiles[-1]), encoding='utf-8') as h:
|
||||
@@ -2323,13 +2332,13 @@ class EDLogs(FileSystemEventHandler):
|
||||
filename = join(config.get_str('outdir'), f'{ship}.{ts}.txt')
|
||||
|
||||
try:
|
||||
with open(filename, 'wt', encoding='utf-8') as h:
|
||||
with open(filename, 'w', encoding='utf-8') as h:
|
||||
h.write(string)
|
||||
|
||||
except UnicodeError:
|
||||
logger.exception("UnicodeError writing ship loadout to new filename with utf-8 encoding, trying without...")
|
||||
try:
|
||||
with open(filename, 'wt') as h:
|
||||
with open(filename, 'w') as h:
|
||||
h.write(string)
|
||||
|
||||
except OSError:
|
||||
|
||||
+16
-4
@@ -14,6 +14,7 @@ import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
from PIL import ImageGrab
|
||||
from l10n import translations as tr
|
||||
from collections.abc import Callable
|
||||
|
||||
if sys.platform == 'win32':
|
||||
PAGEFG = 'SystemWindowText'
|
||||
@@ -50,10 +51,18 @@ class Label(ttk.Label):
|
||||
"""Custom ttk.Label class to fix some display issues."""
|
||||
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
kw['foreground'] = kw.pop('foreground', PAGEFG if sys.platform == 'win32'
|
||||
else ttk.Style().lookup('TLabel', 'foreground'))
|
||||
kw['background'] = kw.pop('background', PAGEBG if sys.platform == 'win32'
|
||||
else ttk.Style().lookup('TLabel', 'background'))
|
||||
if sys.platform == 'win32':
|
||||
fg_color = PAGEFG
|
||||
else:
|
||||
fg_color = ttk.Style().lookup('TLabel', 'foreground')
|
||||
|
||||
if sys.platform == 'win32':
|
||||
bg_color = PAGEBG
|
||||
else:
|
||||
bg_color = ttk.Style().lookup('TLabel', 'background')
|
||||
|
||||
kw['foreground'] = kw.pop('foreground', fg_color)
|
||||
kw['background'] = kw.pop('background', bg_color)
|
||||
super().__init__(master, **kw)
|
||||
|
||||
|
||||
@@ -171,6 +180,7 @@ class ScrollableNotebook(Notebook):
|
||||
self,
|
||||
master: ttk.Frame | None = None,
|
||||
tabmenu: bool = False,
|
||||
on_tab_change: Callable | None = None,
|
||||
*args,
|
||||
**kwargs
|
||||
) -> None:
|
||||
@@ -191,6 +201,8 @@ class ScrollableNotebook(Notebook):
|
||||
self.notebookTab: ttk.Notebook = ttk.Notebook(self, **kwargs)
|
||||
self.notebookTab.place(x=0, y=0)
|
||||
self.notebookTab.bind("<<NotebookTabChanged>>", self._tab_changer)
|
||||
if on_tab_change:
|
||||
self.notebookTab.bind("<<NotebookTabChanged>>", on_tab_change, add='+')
|
||||
|
||||
# Sliding frame and controls
|
||||
slide_frame: ttk.Frame = ttk.Frame(self)
|
||||
|
||||
+1
-1
@@ -296,7 +296,7 @@ def export(data, filename) -> None:
|
||||
header = 'System,Station,Category,Name,Mount,Guidance,Ship,Class,Rating,FDevID,Date\n'
|
||||
rowheader = f'{data["lastSystem"]["name"]},{data["lastStarport"]["name"]}'
|
||||
|
||||
with open(filename, 'wt') as h:
|
||||
with open(filename, 'w') as h:
|
||||
h.write(header)
|
||||
for v in data["lastStarport"].get("modules", {}).values():
|
||||
try:
|
||||
|
||||
@@ -18,7 +18,8 @@ import tkinter as tk
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from tkinter import ttk
|
||||
from typing import Any, Mapping, MutableMapping
|
||||
from typing import Any
|
||||
from collections.abc import Mapping, MutableMapping
|
||||
|
||||
import companion
|
||||
import myNotebook as nb # noqa: N813
|
||||
@@ -48,13 +49,20 @@ last_error = LastError()
|
||||
class Plugin:
|
||||
"""An EDMC plugin."""
|
||||
|
||||
def __init__(self, name: str, loadfile: Path | None, plugin_logger: logging.Logger | None): # noqa: CCR001
|
||||
def __init__( # noqa: CCR001
|
||||
self,
|
||||
name: str,
|
||||
loadfile: Path | None,
|
||||
plugin_logger: logging.Logger | None,
|
||||
internal: bool = False
|
||||
):
|
||||
"""
|
||||
Load a single plugin.
|
||||
|
||||
:param name: Base name of the file being loaded from.
|
||||
:param loadfile: Full path/filename of the plugin.
|
||||
:param plugin_logger: The logging instance for this plugin to use.
|
||||
:param internal: True to use internal plugin loader logic. Defaults to False.
|
||||
:raises Exception: Typically, ImportError or OSError
|
||||
"""
|
||||
self.name: str = name # Display name.
|
||||
@@ -65,14 +73,19 @@ class Plugin:
|
||||
if loadfile:
|
||||
logger.info(f'loading plugin "{name.replace(".", "_")}" from "{loadfile}"')
|
||||
try:
|
||||
filename = 'plugin_'
|
||||
filename += name.encode(encoding='ascii', errors='replace').decode('utf-8').replace('.', '_')
|
||||
spec = importlib.util.spec_from_file_location(filename, loadfile)
|
||||
# Replaces older load_module() code. Includes a safety check that the module name is set.
|
||||
if spec is not None and spec.loader is not None:
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module.__name__] = module
|
||||
spec.loader.exec_module(module)
|
||||
if internal:
|
||||
filename = 'plugin_'
|
||||
filename += name.encode(encoding='ascii', errors='replace').decode('utf-8').replace('.', '_')
|
||||
spec = importlib.util.spec_from_file_location(filename, loadfile)
|
||||
# Replaces older load_module() code. Includes a safety check that the module name is set.
|
||||
if spec is not None and spec.loader is not None:
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module.__name__] = module
|
||||
spec.loader.exec_module(module)
|
||||
else:
|
||||
module = importlib.import_module('.load', name)
|
||||
|
||||
if module:
|
||||
if getattr(module, 'plugin_start3', None):
|
||||
newname = module.plugin_start3(Path(loadfile).resolve().parent)
|
||||
self.name = str(newname) if newname else self.name
|
||||
@@ -170,7 +183,7 @@ def _load_internal_plugins():
|
||||
try:
|
||||
plugin_name = name[:-3]
|
||||
plugin_path = config.internal_plugin_dir_path / name
|
||||
plugin = Plugin(plugin_name, plugin_path, logger)
|
||||
plugin = Plugin(plugin_name, plugin_path, logger, internal=True)
|
||||
plugin.folder = None
|
||||
internal.append(plugin)
|
||||
except Exception:
|
||||
|
||||
@@ -22,13 +22,15 @@ referenced in this file (or only in any other core plugin), and if so...
|
||||
# pylint: disable=import-error
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping, cast
|
||||
from typing import Any, cast
|
||||
from collections.abc import Mapping
|
||||
import tkinter as tk
|
||||
import base64
|
||||
import gzip
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import myNotebook as nb # noqa: N813
|
||||
from EDMCLogging import get_main_logger
|
||||
from companion import CAPIData
|
||||
@@ -47,6 +49,49 @@ SEPY = 10 # seperator line spacing
|
||||
STATION_UNDOCKED = '×' # "Station" name to display when not docked = U+00D7
|
||||
|
||||
|
||||
# Known colonisation ship names match one of the following:
|
||||
# "$EXT_PANEL_ColonisationShip;"
|
||||
# "$EXT_PANEL_ColonisationShip; <ship name>" with "<ship name>" being eg; "Okawara Horizons"
|
||||
# "$EXT_PANEL_ColonisationShip:#index=<number>;"
|
||||
# "$EXT_PANEL_ColonisationShip:#index=<number>; <ship name>"
|
||||
_RE_COLONISATION_SHIP = re.compile(r'\$EXT_PANEL_ColonisationShip(?::#index=(\d+))?;(?: (.+))?')
|
||||
|
||||
|
||||
def localised_name(value: str | None) -> str | None:
|
||||
"""
|
||||
Handle localisation of strings in places like station names.
|
||||
|
||||
This is currently an issue for system colonisation ships that have a station name along the
|
||||
lines of "$EXT_PANEL_ColonisationShip; Okawara Horizons" when written to the journal, but would
|
||||
be better displayed as "System Colonisation Ship Okawara Horizona" (or other appropriately
|
||||
localised equivalent).
|
||||
|
||||
If no (known) localisable strings are present, the input will be returned unmodified.
|
||||
|
||||
:param value: The non-localised string.
|
||||
:return: The localised string, or None if the input was None.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
colonisation_ship_match = _RE_COLONISATION_SHIP.match(value)
|
||||
if colonisation_ship_match:
|
||||
ship_index = colonisation_ship_match.group(1)
|
||||
ship_name = colonisation_ship_match.group(2)
|
||||
# Use the ship's index as its name if no name was provided.
|
||||
ship_id = ship_name or ship_index
|
||||
|
||||
if ship_id:
|
||||
# LANG: Colonisation ship name, with title
|
||||
value = tr.tl("System Colonisation Ship {NAME}").format(NAME=ship_id)
|
||||
|
||||
else:
|
||||
# LANG: Unidentified colonisation ship
|
||||
value = tr.tl("System Colonisation Ship")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def plugin_start3(plugin_dir: str) -> str:
|
||||
"""
|
||||
Start the plugin.
|
||||
@@ -121,7 +166,7 @@ def station_link_common(data: CAPIData, this):
|
||||
:param this: The module global from the calling module.
|
||||
"""
|
||||
if data['commander']['docked'] or this.on_foot and this.station_name:
|
||||
this.station_link['text'] = this.station_name
|
||||
this.station_link['text'] = localised_name(this.station_name)
|
||||
elif data['lastStarport']['name'] and data['lastStarport']['name'] != "":
|
||||
this.station_link['text'] = STATION_UNDOCKED
|
||||
else:
|
||||
@@ -163,12 +208,14 @@ def station_name_setter_common(this):
|
||||
:param this: The module global from the calling module.
|
||||
"""
|
||||
to_set: str = cast(str, this.station_name)
|
||||
|
||||
if not to_set:
|
||||
if this.system_population is not None and this.system_population > 0:
|
||||
to_set = STATION_UNDOCKED
|
||||
else:
|
||||
to_set = ''
|
||||
|
||||
to_set = str(localised_name(to_set))
|
||||
this.station_link['text'] = to_set
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -22,7 +22,8 @@ referenced in this file (or only in any other core plugin), and if so...
|
||||
# pylint: disable=import-error
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping
|
||||
from typing import Any
|
||||
from collections.abc import Mapping
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import myNotebook as nb # noqa: N813 # its not my fault.
|
||||
|
||||
+3
-2
@@ -33,7 +33,8 @@ from tkinter import ttk
|
||||
from platform import system
|
||||
from textwrap import dedent
|
||||
from threading import Lock
|
||||
from typing import Any, Iterator, Mapping, MutableMapping
|
||||
from typing import Any
|
||||
from collections.abc import Iterator, Mapping, MutableMapping
|
||||
import requests
|
||||
import companion
|
||||
import edmc_data
|
||||
@@ -152,7 +153,7 @@ class EDDNSender:
|
||||
r"unable to validate.',\)]$"
|
||||
)
|
||||
|
||||
def __init__(self, eddn: 'EDDN', eddn_endpoint: str) -> None:
|
||||
def __init__(self, eddn: EDDN, eddn_endpoint: str) -> None:
|
||||
"""
|
||||
Prepare the system for processing messages.
|
||||
|
||||
|
||||
+4
-3
@@ -29,7 +29,8 @@ from queue import Queue
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
from tkinter import ttk
|
||||
from typing import Any, Literal, Mapping, MutableMapping, cast, Sequence
|
||||
from typing import Any, Literal, cast
|
||||
from collections.abc import Mapping, MutableMapping, Sequence
|
||||
import requests
|
||||
import killswitch
|
||||
import monitor
|
||||
@@ -43,7 +44,7 @@ from ttkHyperlinkLabel import HyperlinkLabel
|
||||
from l10n import translations as tr
|
||||
from plugins.common_coreutils import (api_keys_label_common, PADX, PADY, BUTTONX, SEPY, BOXY, STATION_UNDOCKED,
|
||||
show_pwd_var_common, station_link_common, this_format_common,
|
||||
cmdr_data_initial_common)
|
||||
cmdr_data_initial_common, localised_name)
|
||||
|
||||
|
||||
# TODO:
|
||||
@@ -543,7 +544,7 @@ entry: {entry!r}'''
|
||||
to_set = ''
|
||||
|
||||
if this.station_link:
|
||||
this.station_link['text'] = to_set
|
||||
this.station_link['text'] = localised_name(to_set)
|
||||
this.station_link['url'] = station_url(str(this.system_name), str(this.station_name))
|
||||
this.station_link.update_idletasks()
|
||||
|
||||
|
||||
+2
-1
@@ -20,7 +20,8 @@ referenced in this file (or only in any other core plugin), and if so...
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping
|
||||
from typing import Any
|
||||
from collections.abc import Mapping
|
||||
from plugins.common_coreutils import shipyard_url_common # pylint: disable=E0401
|
||||
|
||||
|
||||
|
||||
+4
-13
@@ -31,7 +31,8 @@ from datetime import datetime, timedelta, timezone
|
||||
from operator import itemgetter
|
||||
from threading import Lock, Thread
|
||||
from tkinter import ttk
|
||||
from typing import Any, Callable, Deque, Mapping, NamedTuple, Sequence, cast, Union
|
||||
from typing import Any, Deque, NamedTuple, cast, Union
|
||||
from collections.abc import Callable, Mapping, Sequence
|
||||
import requests
|
||||
import edmc_data
|
||||
import killswitch
|
||||
@@ -105,7 +106,6 @@ class This:
|
||||
self.materials: list[dict[str, Any]] | None = None
|
||||
self.last_credits: int = 0 # Send credit update soon after Startup / new game
|
||||
self.storedmodules: list[dict[str, Any]] | None = None
|
||||
self.embarked: bool = False # Boarded Someone Else's Ship? Should always start as False.
|
||||
self.loadout: dict[str, Any] | None = None
|
||||
self.fleet: list[dict[str, Any]] | None = None
|
||||
self.shipswap: bool = False # just swapped ship
|
||||
@@ -533,17 +533,8 @@ def journal_entry( # noqa: C901, CCR001
|
||||
power_data = {'powerName': entry["Power"], 'rankValue': entry["Rank"]}
|
||||
new_add_event('setCommanderRankPower', entry['timestamp'], power_data)
|
||||
|
||||
# Handle Embark/Disembark Events
|
||||
if event_name == 'Embark':
|
||||
this.embarked = True
|
||||
this.multicrew = True
|
||||
|
||||
if event_name == 'Disembark':
|
||||
this.embarked = False
|
||||
this.multicrew = False
|
||||
|
||||
# Ship change
|
||||
if event_name == 'Loadout' and this.shipswap:
|
||||
elif event_name == 'Loadout' and this.shipswap:
|
||||
this.loadout = make_loadout(state)
|
||||
new_add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout)
|
||||
this.shipswap = False
|
||||
@@ -831,7 +822,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
for ship in this.fleet:
|
||||
new_add_event('setCommanderShip', entry['timestamp'], ship)
|
||||
# Loadout
|
||||
if event_name == 'Loadout' and not this.embarked: # Ignore Loadout Events if Embarked
|
||||
if event_name == 'Loadout':
|
||||
loadout = make_loadout(state)
|
||||
if this.loadout != loadout:
|
||||
this.loadout = loadout
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""EDMC preferences library."""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -14,7 +13,8 @@ from os import system
|
||||
from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812
|
||||
from tkinter import ttk
|
||||
from types import TracebackType
|
||||
from typing import Any, Callable, Optional, Type
|
||||
from typing import Any
|
||||
from collections.abc import Callable
|
||||
import myNotebook as nb # noqa: N813
|
||||
import plug
|
||||
from config import appversion_nobuild, config
|
||||
@@ -170,8 +170,8 @@ class AutoInc(contextlib.AbstractContextManager):
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Optional[TracebackType]
|
||||
) -> Optional[bool]:
|
||||
exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
|
||||
) -> bool | None:
|
||||
"""Do nothing."""
|
||||
return None
|
||||
|
||||
@@ -241,7 +241,7 @@ if sys.platform == 'win32':
|
||||
class PreferencesDialog(tk.Toplevel):
|
||||
"""The EDMC preferences dialog."""
|
||||
|
||||
def __init__(self, parent: tk.Tk, callback: Optional[Callable]):
|
||||
def __init__(self, parent: tk.Tk, callback: Callable | None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.parent = parent
|
||||
@@ -265,7 +265,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
self.cmdr: str | bool | None = False # Note if Cmdr changes in the Journal
|
||||
self.is_beta: bool = False # Note if Beta status changes in the Journal
|
||||
self.cmdrchanged_alarm: Optional[str] = None # This stores an ID that can be used to cancel a scheduled call
|
||||
self.cmdrchanged_alarm: str | None = None # This stores an ID that can be used to cancel a scheduled call
|
||||
|
||||
# Set up the main frame
|
||||
frame = ttk.Frame(self)
|
||||
@@ -276,8 +276,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
frame.rowconfigure(0, weight=1)
|
||||
frame.rowconfigure(1, weight=0)
|
||||
|
||||
notebook: nb.ScrollableNotebook = nb.ScrollableNotebook(frame, tabmenu=True)
|
||||
notebook.bind('<<NotebookTabChanged>>', self.tabchanged) # Recompute on tab change
|
||||
notebook: nb.ScrollableNotebook = nb.ScrollableNotebook(frame, tabmenu=True, on_tab_change=self.tabchanged)
|
||||
|
||||
self.PADX = 10
|
||||
self.BUTTONX = 12 # indent Checkbuttons and Radiobuttons
|
||||
@@ -310,6 +309,11 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.cmdrchanged()
|
||||
self.themevarchanged()
|
||||
|
||||
# Init hotkey attributes to avoid linter errors
|
||||
self.hotkey_text: ttk.Entry
|
||||
self.hotkey_only_btn: nb.Checkbutton
|
||||
self.hotkey_play_btn: nb.Checkbutton
|
||||
|
||||
# disable hotkey for the duration
|
||||
hotkeymgr.unregister()
|
||||
|
||||
@@ -1132,7 +1136,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
"""Handle preferences active tab changing."""
|
||||
self.outvarchanged()
|
||||
|
||||
def outvarchanged(self, event: Optional[tk.Event] = None) -> None:
|
||||
def outvarchanged(self, event: tk.Event | None = None) -> None:
|
||||
"""Handle Output tab variable changes."""
|
||||
self.displaypath(self.outdir, self.outdir_entry)
|
||||
self.displaypath(self.logdir, self.logdir_entry)
|
||||
@@ -1236,14 +1240,14 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.theme_button_0['state'] = state
|
||||
self.theme_button_1['state'] = state
|
||||
|
||||
def hotkeystart(self, event: 'tk.Event[Any]') -> None:
|
||||
def hotkeystart(self, event: tk.Event[Any]) -> None:
|
||||
"""Start listening for hotkeys."""
|
||||
event.widget.bind('<KeyPress>', self.hotkeylisten)
|
||||
event.widget.bind('<KeyRelease>', self.hotkeylisten)
|
||||
event.widget.delete(0, tk.END)
|
||||
hotkeymgr.acquire_start()
|
||||
|
||||
def hotkeyend(self, event: 'tk.Event[Any]') -> None:
|
||||
def hotkeyend(self, event: tk.Event[Any]) -> None:
|
||||
"""Stop listening for hotkeys."""
|
||||
event.widget.unbind('<KeyPress>')
|
||||
event.widget.unbind('<KeyRelease>')
|
||||
@@ -1254,7 +1258,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
# LANG: No hotkey/shortcut set
|
||||
hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else tr.tl('None'))
|
||||
|
||||
def hotkeylisten(self, event: 'tk.Event[Any]') -> str:
|
||||
def hotkeylisten(self, event: tk.Event[Any]) -> str:
|
||||
"""
|
||||
Hotkey handler.
|
||||
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ import os
|
||||
import sys
|
||||
import threading
|
||||
from urllib import parse
|
||||
from typing import TYPE_CHECKING, Type
|
||||
from typing import TYPE_CHECKING
|
||||
from config import config
|
||||
from constants import appname, protocolhandler_redirect
|
||||
from EDMCLogging import get_main_logger
|
||||
@@ -61,7 +61,7 @@ if config.auth_force_edmc_protocol or ( # noqa: C901
|
||||
):
|
||||
# This could be false if you use auth_force_edmc_protocol, but then you get to keep the pieces
|
||||
if sys.platform != "win32":
|
||||
raise EnvironmentError("This code is for Windows only.")
|
||||
raise OSError("This code is for Windows only.")
|
||||
# spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL
|
||||
from ctypes import ( # type: ignore
|
||||
windll,
|
||||
@@ -434,7 +434,7 @@ else: # Linux / Run from source
|
||||
)
|
||||
|
||||
|
||||
def get_handler_impl() -> Type[GenericProtocolHandler]:
|
||||
def get_handler_impl() -> type[GenericProtocolHandler]:
|
||||
"""
|
||||
Get the appropriate GenericProtocolHandler for the current system and config.
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ flake8-json==24.4.0
|
||||
flake8-noqa==1.4.0
|
||||
flake8-use-fstring==1.4
|
||||
|
||||
mypy==1.18.2
|
||||
mypy==1.19.0
|
||||
pep8-naming==0.15.1
|
||||
safety==3.7.0
|
||||
types-requests==2.32.4.20250913
|
||||
@@ -23,7 +23,7 @@ types-setuptools==80.9.0.20250822
|
||||
autopep8==2.3.2
|
||||
|
||||
# Git pre-commit checking
|
||||
pre-commit==4.4.0
|
||||
pre-commit==4.5.0
|
||||
|
||||
# HTML changelogs
|
||||
mistune==3.1.4
|
||||
@@ -33,9 +33,9 @@ mistune==3.1.4
|
||||
py2exe==0.14.0.0; sys_platform == 'win32'
|
||||
|
||||
# Testing
|
||||
pytest==9.0.0
|
||||
pytest==9.0.1
|
||||
pytest-cov==7.0.0 # Pytest code coverage support
|
||||
coverage[toml]==7.11.2 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
|
||||
coverage[toml]==7.12.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
|
||||
coverage-conditional-plugin==0.9.0
|
||||
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ class FileLocation:
|
||||
line_end_col: int | None
|
||||
|
||||
@staticmethod
|
||||
def from_call(path: pathlib.Path, c: ast.Call) -> "FileLocation":
|
||||
def from_call(path: pathlib.Path, c: ast.Call) -> FileLocation:
|
||||
"""
|
||||
Create a FileLocation from a Call and Path.
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ import json
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Any, AnyStr, Callable, NamedTuple, Sequence, cast
|
||||
from typing import Any, AnyStr, NamedTuple, cast
|
||||
from collections.abc import Callable, Sequence
|
||||
import companion
|
||||
import EDMCLogging
|
||||
import myNotebook as nb # noqa: N813
|
||||
|
||||
@@ -30,18 +30,18 @@ def export(data: CAPIData) -> None:
|
||||
# required
|
||||
with open(data_path / data_filename, 'wb') as h:
|
||||
# Format described here: https://github.com/eyeonus/Trade-Dangerous/wiki/Price-Data
|
||||
h.write('#! trade.py import -\n'.encode('utf-8'))
|
||||
h.write(b'#! trade.py import -\n')
|
||||
this_platform = system()
|
||||
cmdr_name = data['commander']['name'].strip()
|
||||
h.write(
|
||||
f'# Created by {applongname} {appversion()} on {this_platform} for Cmdr {cmdr_name}.\n'.encode('utf-8')
|
||||
f'# Created by {applongname} {appversion()} on {this_platform} for Cmdr {cmdr_name}.\n'.encode()
|
||||
)
|
||||
h.write(
|
||||
'#\n# <item name> <sellCR> <buyCR> <demand> <stock> <timestamp>\n\n'.encode('utf-8')
|
||||
b'#\n# <item name> <sellCR> <buyCR> <demand> <stock> <timestamp>\n\n'
|
||||
)
|
||||
system_name = data['lastSystem']['name'].strip()
|
||||
starport_name = data['lastStarport']['name'].strip()
|
||||
h.write(f'@ {system_name}/{starport_name}\n'.encode('utf-8'))
|
||||
h.write(f'@ {system_name}/{starport_name}\n'.encode())
|
||||
|
||||
# sort commodities by category
|
||||
by_category = defaultdict(list)
|
||||
@@ -50,7 +50,7 @@ def export(data: CAPIData) -> None:
|
||||
|
||||
timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.strptime(data['timestamp'], '%Y-%m-%dT%H:%M:%SZ'))
|
||||
for category in sorted(by_category):
|
||||
h.write(f' + {format(category)}\n'.encode('utf-8'))
|
||||
h.write(f' + {format(category)}\n'.encode())
|
||||
# corrections to commodity names can change the sort order
|
||||
for commodity in sorted(by_category[category], key=itemgetter('name')):
|
||||
h.write(
|
||||
@@ -61,5 +61,5 @@ def export(data: CAPIData) -> None:
|
||||
f"{demandbracketmap[commodity['demandBracket']]:1}"
|
||||
f" {int(commodity['stock']) if commodity['stockBracket'] else '':8}"
|
||||
f"{stockbracketmap[commodity['stockBracket']]:1}"
|
||||
f" {timestamp}\n".encode('utf-8')
|
||||
f" {timestamp}\n".encode()
|
||||
)
|
||||
@@ -5,7 +5,7 @@ import multiprocessing as mp
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from typing import Generator
|
||||
from collections.abc import Generator
|
||||
import pytest
|
||||
from pytest import MonkeyPatch, TempdirFactory, TempPathFactory
|
||||
from config import config
|
||||
|
||||
@@ -15,7 +15,7 @@ import sys
|
||||
import tkinter as tk
|
||||
from tkinter import font as tk_font
|
||||
from tkinter import ttk
|
||||
from typing import Callable
|
||||
from collections.abc import Callable
|
||||
from l10n import translations as tr
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
@@ -208,7 +208,7 @@ class _Theme:
|
||||
|
||||
if isinstance(widget, (tk.Frame, ttk.Frame)):
|
||||
for child in widget.winfo_children():
|
||||
self.register(child)
|
||||
self.register(child) # type: ignore
|
||||
|
||||
def register_alternate(self, pair: tuple, gridopts: dict) -> None:
|
||||
self.widgets_pair.append((pair, gridopts))
|
||||
@@ -313,7 +313,7 @@ class _Theme:
|
||||
self._update_widget(widget)
|
||||
if isinstance(widget, (tk.Frame, ttk.Frame)):
|
||||
for child in widget.winfo_children():
|
||||
self._update_widget(child)
|
||||
self._update_widget(child) # type: ignore
|
||||
|
||||
# Apply current theme to a single widget
|
||||
def _update_widget(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, C901
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@ Licensed under the GNU General Public License.
|
||||
See LICENSE file.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from edmc_data import ship_name_map
|
||||
|
||||
@@ -18,7 +19,7 @@ def ship_file_name(ship_name: str, ship_type: str) -> str:
|
||||
name = Path(name).with_suffix("").name
|
||||
|
||||
# Check if the name is a reserved filename
|
||||
if os.path.isreserved(name):
|
||||
if sys.platform == 'win32' and os.path.isreserved(name):
|
||||
name += "_"
|
||||
|
||||
return name.translate(
|
||||
|
||||
Reference in new issue
Block a user