1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-24 12:40:52 +03:00

Merge branch 'stable' into releases

This commit is contained in:
David Sangrey 2024-06-09 17:40:26 -04:00
commit 6e85245594
No known key found for this signature in database
GPG Key ID: 3AEADBB0186884BC
15 changed files with 217 additions and 107 deletions

View File

@ -69,3 +69,7 @@ jobs:
- name: mypy type checks
run: |
./scripts/mypy-all.sh --platform win32
- name: translation checks
run: |
python ./scripts/find_localised_strings.py --compare-lang L10n/en.template --directory . --ignore coriolis-data

View File

@ -6,6 +6,31 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are
in the source (not distributed with the Windows installer) for the
currently used version.
---
Release 5.11.1
===
This release fixes a bug regarding FDevID files when running from Source in a non-writable location. Additionally,
Deprecation Warnings are now more visible to aid in plugin development.
**Changes and Enhancements**
* Added a check on Git Pushes to check for updated translation strings for developers
* Enabled deprecation warnings to pass to plugins and logs
* Updated Dependencies
* Replaced infi.systray with drop-in replacement simplesystray
**Bug Fixes**
* Fixed a bug that could result in the program not updating or writing FDevID files when running from source in a location where the running user can't write to
**Plugin Developers**
* nb.Entry is deprecated, and is slated for removal in 6.0 or later. Please migrate to nb.EntryMenu
* nb.ColoredButton is deprecated, and is slated for removal in 6.0 or later. Please migrate to tk.Button
* Calling internal translations with `_()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to importing `translations` and calling `translations.translate` or `translations.tl` directly
* `Translations` as the translate system singleton is deprecated, and is slated for removal in 6.0 or later. Please migrate to the `translations` singleton
* `help_open_log_folder()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to open_folder()
* `update_feed` is deprecated, and is slated for removal in 6.0 or later. Please migrate to `get_update_feed()`.
* FDevID files (`commodity.csv` and `rare_commodity.csv`) have moved their preferred location to the app dir (same location as default Plugins folder). Please migrate to use `config.app_dir_path`.
Release 5.11.0
===

View File

@ -42,6 +42,7 @@ import logging.handlers
import os
import pathlib
import tempfile
import warnings
from contextlib import suppress
from fnmatch import fnmatch
# So that any warning about accessing a protected member is only in one place.
@ -99,6 +100,8 @@ logging.Logger.trace = lambda self, message, *args, **kwargs: self._log( # type
# MAGIC-CONT: See MAGIC tagged comment in Logger.__init__()
logging.Formatter.converter = gmtime
warnings.simplefilter('default', DeprecationWarning)
def _trace_if(self: logging.Logger, condition: str, message: str, *args, **kwargs) -> None:
if any(fnmatch(condition, p) for p in config_mod.trace_on):

View File

@ -65,6 +65,7 @@ from config import appversion, appversion_nobuild, config, copyright
from EDMCLogging import edmclogger, logger, logging
from journal_lock import JournalLock, JournalLockResult
from update import check_for_fdev_updates
if __name__ == '__main__': # noqa: C901
# Command-line arguments
@ -395,7 +396,7 @@ if TYPE_CHECKING:
from logging import TRACE # type: ignore # noqa: F401 # Needed to update mypy
if sys.platform == 'win32':
from infi.systray import SysTrayIcon
from simplesystray import SysTrayIcon
# isort: on
@ -451,7 +452,7 @@ class AppWindow:
self.prefsdialog = None
if sys.platform == 'win32':
from infi.systray import SysTrayIcon
from simplesystray import SysTrayIcon
def open_window(systray: 'SysTrayIcon') -> None:
self.w.deiconify()
@ -2323,6 +2324,7 @@ sys.path: {sys.path}'''
root.after(2, show_killswitch_poppup, root)
# Start the main event loop
try:
check_for_fdev_updates()
root.mainloop()
except KeyboardInterrupt:
logger.info("Ctrl+C Detected, Attempting Clean Shutdown")

View File

@ -208,5 +208,5 @@ def build() -> None:
if __name__ == "__main__":
check_for_fdev_updates()
check_for_fdev_updates(local=True)
build()

View File

@ -22,6 +22,7 @@ from traceback import print_exc
import companion
import outfitting
from config import config
from edmc_data import companion_category_map, ship_name_map
@ -50,7 +51,10 @@ def addcommodities(data) -> None: # noqa: CCR001
if not data['lastStarport'].get('commodities'):
return
commodityfile = pathlib.Path('FDevIDs/commodity.csv')
try:
commodityfile = pathlib.Path(config.app_dir_path / 'FDevIDs' / 'commodity.csv')
except FileNotFoundError:
commodityfile = pathlib.Path('FDevIDs/commodity.csv')
commodities = {}
# slurp existing

View File

@ -1203,10 +1203,10 @@ def fixup(data: CAPIData) -> CAPIData: # noqa: C901, CCR001 # Can't be usefully
if not commodity_map:
# Lazily populate
for f in ('commodity.csv', 'rare_commodity.csv'):
if not os.path.isfile(config.respath_path / 'FDevIDs/' / f):
if not os.path.isfile(config.app_dir_path / 'FDevIDs/' / f):
logger.warning(f'FDevID file {f} not found! Generating output without these commodity name rewrites.')
continue
with open(config.respath_path / 'FDevIDs' / f, 'r') as csvfile:
with open(config.app_dir_path / 'FDevIDs' / f, 'r') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:

View File

@ -30,7 +30,6 @@ __all__ = [
'AbstractConfig',
'config',
'get_update_feed',
'update_feed'
]
import abc
@ -41,7 +40,6 @@ import pathlib
import re
import subprocess
import sys
import traceback
import warnings
from abc import abstractmethod
from typing import Any, Callable, Type, TypeVar
@ -54,7 +52,7 @@ appcmdname = 'EDMC'
# <https://semver.org/#semantic-versioning-specification-semver>
# Major.Minor.Patch(-prerelease)(+buildmetadata)
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
_static_appversion = '5.11.0'
_static_appversion = '5.11.1'
_cached_version: semantic_version.Version | None = None
copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'
@ -329,8 +327,8 @@ class AbstractConfig(abc.ABC):
:raises OSError: On Windows, if a Registry error occurs.
:return: The data or the default.
"""
warnings.warn(DeprecationWarning('get is Deprecated. use the specific getter for your type'))
logger.debug('Attempt to use Deprecated get() method\n' + ''.join(traceback.format_stack()))
# DEPRECATED: Migrate to specific type getters. Will remove in 6.0 or later.
warnings.warn('get is Deprecated. use the specific getter for your type', DeprecationWarning, stacklevel=2)
if (a_list := self._suppress_call(self.get_list, ValueError, key, default=None)) is not None:
return a_list
@ -388,8 +386,8 @@ class AbstractConfig(abc.ABC):
See get_int for its replacement.
:raises OSError: On Windows, if a Registry error occurs.
"""
warnings.warn(DeprecationWarning('getint is Deprecated. Use get_int instead'))
logger.debug('Attempt to use Deprecated getint() method\n' + ''.join(traceback.format_stack()))
# DEPRECATED: Migrate to get_int. Will remove in 6.0 or later.
warnings.warn('getint is Deprecated. Use get_int instead', DeprecationWarning, stacklevel=2)
return self.get_int(key, default=default)
@ -446,17 +444,19 @@ class AbstractConfig(abc.ABC):
"""Close this config and release any associated resources."""
raise NotImplementedError
# DEPRECATED: Password system doesn't do anything. Will remove in 6.0 or later.
def get_password(self, account: str) -> None:
"""Legacy password retrieval."""
warnings.warn("password subsystem is no longer supported", DeprecationWarning)
warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2)
def set_password(self, account: str, password: str) -> None:
"""Legacy password setting."""
warnings.warn("password subsystem is no longer supported", DeprecationWarning)
warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2)
def delete_password(self, account: str) -> None:
"""Legacy password deletion."""
warnings.warn("password subsystem is no longer supported", DeprecationWarning)
warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2)
# End Dep Zone
def get_config(*args, **kwargs) -> AbstractConfig:
@ -489,5 +489,10 @@ def get_update_feed() -> str:
return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml'
# WARNING: update_feed is deprecated, and will be removed in 6.0 or later. Please migrate to get_update_feed()
update_feed = get_update_feed()
# DEPRECATED: Migrate to get_update_feed(). Will remove in 6.0 or later.
def __getattr__(name: str):
if name == 'update_feed':
warnings.warn('update_feed is deprecated, and will be removed in 6.0 or later. '
'Please migrate to get_update_feed()', DeprecationWarning, stacklevel=2)
return get_update_feed()
raise AttributeError(name=name)

23
l10n.py
View File

@ -86,8 +86,7 @@ class Translations:
Use when translation is not desired or not available
"""
self.translations = {None: {}}
# WARNING: '_' is Deprecated. Will be removed in 6.0 or later.
# Migrate to calling translations.translate or tr.tl directly.
# DEPRECATED: Migrate to translations.translate or tr.tl. Will remove in 6.0 or later.
builtins.__dict__['_'] = lambda x: str(x).replace(r'\"', '"').replace('{CR}', '\n')
def install(self, lang: str | None = None) -> None: # noqa: CCR001
@ -131,8 +130,7 @@ class Translations:
except Exception:
logger.exception(f'Exception occurred while parsing {lang}.strings in plugin {plugin}')
# WARNING: '_' is Deprecated. Will be removed in 6.0 or later.
# Migrate to calling translations.translate or tr.tl directly.
# DEPRECATED: Migrate to translations.translate or tr.tl. Will remove in 6.0 or later.
builtins.__dict__['_'] = self.translate
def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]:
@ -262,16 +260,19 @@ class Translations:
class _Locale:
"""Locale holds a few utility methods to convert data to and from localized versions."""
# DEPRECATED: Migrate to _Locale.string_from_number. Will remove in 6.0 or later.
def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: N802
warnings.warn(DeprecationWarning('use _Locale.string_from_number instead.'))
warnings.warn('use _Locale.string_from_number instead.', DeprecationWarning, stacklevel=2)
return self.string_from_number(number, decimals) # type: ignore
# DEPRECATED: Migrate to _Locale.number_from_string. Will remove in 6.0 or later.
def numberFromString(self, string: str) -> int | float | None: # noqa: N802
warnings.warn(DeprecationWarning('use _Locale.number_from_string instead.'))
warnings.warn('use _Locale.number_from_string instead.', DeprecationWarning, stacklevel=2)
return self.number_from_string(string)
# DEPRECATED: Migrate to _Locale.preferred_languages. Will remove in 6.0 or later.
def preferredLanguages(self) -> Iterable[str]: # noqa: N802
warnings.warn(DeprecationWarning('use _Locale.preferred_languages instead.'))
warnings.warn('use _Locale.preferred_languages instead.', DeprecationWarning, stacklevel=2)
return self.preferred_languages()
def string_from_number(self, number: float | int, decimals: int = 5) -> str:
@ -362,13 +363,13 @@ Locale = _Locale()
translations = Translations()
# WARNING: 'Translations' singleton is deprecated. Will be removed in 6.0 or later.
# Migrate to importing 'translations'.
# DEPRECATED: Migrate to `translations`. Will be removed in 6.0 or later.
# 'Translations' singleton is deprecated.
# Begin Deprecation Zone
class _Translations(Translations):
def __init__(self):
logger.warning(DeprecationWarning('Translations and _Translations() are deprecated. '
'Please use translations and Translations() instead.'))
warnings.warn('Translations and _Translations() are deprecated. '
'Please use translations and Translations() instead.', DeprecationWarning, stacklevel=2)
super().__init__()

View File

@ -11,6 +11,7 @@ from __future__ import annotations
import sys
import tkinter as tk
import warnings
from tkinter import ttk, messagebox
from PIL import ImageGrab
from l10n import translations as tr
@ -126,6 +127,7 @@ class Entry(EntryMenu):
# DEPRECATED: Migrate to EntryMenu. Will remove in 6.0 or later.
def __init__(self, master: ttk.Frame | None = None, **kw):
warnings.warn('Migrate to EntryMenu. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2)
EntryMenu.__init__(self, master, **kw)
@ -144,6 +146,7 @@ class ColoredButton(tk.Button):
# DEPRECATED: Migrate to tk.Button. Will remove in 6.0 or later.
def __init__(self, master: ttk.Frame | None = None, **kw):
warnings.warn('Migrate to tk.Button. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2)
tk.Button.__init__(self, master, **kw)

View File

@ -9,6 +9,7 @@ import subprocess
import sys
import tempfile
import tkinter as tk
import warnings
from os import system
from os.path import expanduser, expandvars, join, normpath
from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812
@ -37,13 +38,11 @@ logger = get_main_logger()
# May be imported by plugins
# DEPRECATED: Migrate to open_log_folder. Will remove in 6.0 or later.
def help_open_log_folder() -> None:
"""Open the folder logs are stored in."""
logger.warning(
DeprecationWarning("This function is deprecated, use open_log_folder instead. "
"This function will be removed in 6.0 or later")
)
warnings.warn('prefs.help_open_log_folder is deprecated, use open_log_folder instead. '
'This function will be removed in 6.0 or later', DeprecationWarning, stacklevel=2)
open_folder(pathlib.Path(tempfile.gettempdir()) / appname)

View File

@ -5,7 +5,7 @@ wheel
# We can't rely on just picking this up from either the base (not venv),
# or venv-init-time version. Specify here so that dependabot will prod us
# about new versions.
setuptools==69.2.0
setuptools==70.0.0
# Static analysis tools
flake8==7.0.0

View File

@ -1,5 +1,5 @@
requests==2.32.3
pillow==10.3.0
watchdog==4.0.0
infi.systray==0.1.12; sys_platform == 'win32'
simplesystray==0.1.0; sys_platform == 'win32'
semantic-version==2.10.0

View File

@ -1,4 +1,5 @@
"""Search all given paths recursively for localised string calls."""
from __future__ import annotations
import argparse
@ -17,20 +18,20 @@ def get_func_name(thing: ast.AST) -> str:
if isinstance(thing, ast.Attribute):
return get_func_name(thing.value)
return ''
return ""
def get_arg(call: ast.Call) -> str:
"""Extract the argument string to the translate function."""
if len(call.args) > 1:
print('??? > 1 args', call.args, file=sys.stderr)
print("??? > 1 args", call.args, file=sys.stderr)
arg = call.args[0]
if isinstance(arg, ast.Constant):
return arg.value
if isinstance(arg, ast.Name):
return f'VARIABLE! CHECK CODE! {arg.id}'
return f'Unknown! {type(arg)=} {ast.dump(arg)} ||| {ast.unparse(arg)}'
return f"VARIABLE! CHECK CODE! {arg.id}"
return f"Unknown! {type(arg)=} {ast.dump(arg)} ||| {ast.unparse(arg)}"
def find_calls_in_stmt(statement: ast.AST) -> list[ast.Call]:
@ -38,8 +39,14 @@ def find_calls_in_stmt(statement: ast.AST) -> list[ast.Call]:
out = []
for n in ast.iter_child_nodes(statement):
out.extend(find_calls_in_stmt(n))
if isinstance(statement, ast.Call) and get_func_name(statement.func) in ('tr', 'translations'):
if ast.unparse(statement).find('.tl') != -1 or ast.unparse(statement).find('translate') != -1:
if isinstance(statement, ast.Call) and get_func_name(statement.func) in (
"tr",
"translations",
):
if (
ast.unparse(statement).find(".tl") != -1
or ast.unparse(statement).find("translate") != -1
):
out.append(statement)
return out
@ -53,11 +60,13 @@ COMMENT_OWN_LINE_RE is for a comment on its own line.
The difference is necessary in order to tell if a 'above' LANG comment is for
its own line (SAME_LINE), or meant to be for this following line (OWN_LINE).
"""
COMMENT_SAME_LINE_RE = re.compile(r'^.*?(#.*)$')
COMMENT_OWN_LINE_RE = re.compile(r'^\s*?(#.*)$')
COMMENT_SAME_LINE_RE = re.compile(r"^.*?(#.*)$")
COMMENT_OWN_LINE_RE = re.compile(r"^\s*?(#.*)$")
def extract_comments(call: ast.Call, lines: list[str], file: pathlib.Path) -> str | None: # noqa: CCR001
def extract_comments( # noqa: CCR001
call: ast.Call, lines: list[str], file: pathlib.Path
) -> str | None:
"""
Extract comments from source code based on the given call.
@ -83,23 +92,23 @@ def extract_comments(call: ast.Call, lines: list[str], file: pathlib.Path) -> st
match = COMMENT_OWN_LINE_RE.match(above_line)
if match:
above_comment = match.group(1).strip()
if not above_comment.startswith('# LANG:'):
bad_comment = f'Unknown comment for {file}:{call.lineno} {above_line}'
if not above_comment.startswith("# LANG:"):
bad_comment = f"Unknown comment for {file}:{call.lineno} {above_line}"
above_comment = None
else:
above_comment = above_comment.replace('# LANG:', '').strip()
above_comment = above_comment.replace("# LANG:", "").strip()
if current_line is not None:
match = COMMENT_SAME_LINE_RE.match(current_line)
if match:
current_comment = match.group(1).strip()
if not current_comment.startswith('# LANG:'):
bad_comment = f'Unknown comment for {file}:{call.lineno} {current_line}'
if not current_comment.startswith("# LANG:"):
bad_comment = f"Unknown comment for {file}:{call.lineno} {current_line}"
current_comment = None
else:
current_comment = current_comment.replace('# LANG:', '').strip()
current_comment = current_comment.replace("# LANG:", "").strip()
if current_comment is not None:
out = current_comment
@ -109,13 +118,13 @@ def extract_comments(call: ast.Call, lines: list[str], file: pathlib.Path) -> st
print(bad_comment, file=sys.stderr)
if out is None:
print(f'No comment for {file}:{call.lineno} {current_line}', file=sys.stderr)
print(f"No comment for {file}:{call.lineno} {current_line}", file=sys.stderr)
return out
def scan_file(path: pathlib.Path) -> list[ast.Call]:
"""Scan a file for ast.Calls."""
data = path.read_text(encoding='utf-8')
data = path.read_text(encoding="utf-8")
lines = data.splitlines()
parsed = ast.parse(data)
out: list[ast.Call] = []
@ -125,13 +134,15 @@ def scan_file(path: pathlib.Path) -> list[ast.Call]:
# see if we can extract any comments
for call in out:
setattr(call, 'comment', extract_comments(call, lines, path))
setattr(call, "comment", extract_comments(call, lines, path))
out.sort(key=lambda c: c.lineno)
return out
def scan_directory(path: pathlib.Path, skip: list[pathlib.Path] | None = None) -> dict[pathlib.Path, list[ast.Call]]:
def scan_directory(
path: pathlib.Path, skip: list[pathlib.Path] | None = None
) -> dict[pathlib.Path, list[ast.Call]]:
"""
Scan a directory for expected callsites.
@ -145,7 +156,7 @@ def scan_directory(path: pathlib.Path, skip: list[pathlib.Path] | None = None) -
if any(same_path.name == thing.name for same_path in skip):
continue
if thing.is_file() and thing.suffix == '.py':
if thing.is_file() and thing.suffix == ".py":
out[thing] = scan_file(thing)
elif thing.is_dir():
out.update(scan_directory(thing, skip))
@ -163,10 +174,10 @@ def parse_template(path) -> set[str]:
"""
lang_re = re.compile(r'\s*"([^"]+)"\s*=\s*"([^"]+)"\s*;\s*$')
out = set()
with open(path, encoding='utf-8') as file:
with open(path, encoding="utf-8") as file:
for line in file:
match = lang_re.match(line.strip())
if match and match.group(1) != '!Language':
if match and match.group(1) != "!Language":
out.add(match.group(1))
return out
@ -183,14 +194,16 @@ 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.
:param path: Path to the file this FileLocation is in
:param c: Call object to extract line information from
"""
return FileLocation(path, c.lineno, c.col_offset, c.end_lineno, c.end_col_offset)
return FileLocation(
path, c.lineno, c.col_offset, c.end_lineno, c.end_col_offset
)
@dataclasses.dataclass
@ -238,95 +251,121 @@ def generate_lang_template(data: dict[pathlib.Path, list[ast.Call]]) -> str:
entries: list[LangEntry] = []
for path, calls in data.items():
for c in calls:
entries.append(LangEntry([FileLocation.from_call(path, c)], get_arg(c), [getattr(c, 'comment')]))
entries.append(
LangEntry(
[FileLocation.from_call(path, c)],
get_arg(c),
[getattr(c, "comment")],
)
)
deduped = dedupe_lang_entries(entries)
out = '''/* Language name */
out = """/* Language name */
"!Language" = "English";
'''
print(f'Done Deduping entries {len(entries)=} {len(deduped)=}', file=sys.stderr)
"""
print(f"Done Deduping entries {len(entries)=} {len(deduped)=}", file=sys.stderr)
for entry in deduped:
assert len(entry.comments) == len(entry.locations)
comment_set = set()
for comment, loc in zip(entry.comments, entry.locations):
if comment:
comment_set.add(f'{loc.path.name}: {comment};')
comment_set.add(f"{loc.path.name}: {comment};")
files = 'In files: ' + entry.files()
comment = ' '.join(comment_set).strip()
files = "In files: " + entry.files()
comment = " ".join(comment_set).strip()
header = f'{comment} {files}'.strip()
header = f"{comment} {files}".strip()
string = f'"{entry.string}"'
out += f'/* {header} */\n'
out += f'{string} = {string};\n\n'
out += f"/* {header} */\n"
out += f"{string} = {string};\n\n"
return out
if __name__ == '__main__':
def main(): # noqa: CCR001
"""Run the Translation Checker."""
parser = argparse.ArgumentParser()
parser.add_argument('--directory', help='Directory to search from', default='.')
parser.add_argument('--ignore', action='append', help='directories to ignore', default=['venv', '.venv', '.git'])
parser.add_argument("--directory", help="Directory to search from", default=".")
parser.add_argument(
"--ignore",
action="append",
help="Directories to ignore",
default=["venv", ".venv", ".git"],
)
group = parser.add_mutually_exclusive_group()
group.add_argument('--json', action='store_true', help='JSON output')
group.add_argument('--lang', help='en.template "strings" output to specified file, "-" for stdout')
group.add_argument('--compare-lang', help='en.template file to compare against')
group.add_argument("--json", action="store_true", help="JSON output")
group.add_argument(
"--lang", help='en.template "strings" output to specified file, "-" for stdout'
)
group.add_argument("--compare-lang", help="en.template file to compare against")
args = parser.parse_args()
directory = pathlib.Path(args.directory)
res = scan_directory(directory, [pathlib.Path(p) for p in args.ignore])
if args.compare_lang is not None and len(args.compare_lang) > 0:
output = []
if args.compare_lang:
seen = set()
template = parse_template(args.compare_lang)
for file, calls in res.items():
for c in calls:
arg = get_arg(c)
if arg in template:
seen.add(arg)
else:
print(f'NEW! {file}:{c.lineno}: {arg!r}')
output.append(f"NEW! {file}:{c.lineno}: {arg!r}")
for old in set(template) ^ seen:
print(f'No longer used: {old!r}')
output.append(f"No longer used: {old!r}")
elif args.json:
to_print_data = [
{
'path': str(path),
'string': get_arg(c),
'reconstructed': ast.unparse(c),
'start_line': c.lineno,
'start_offset': c.col_offset,
'end_line': c.end_lineno,
'end_offset': c.end_col_offset,
'comment': getattr(c, 'comment', None)
} for (path, calls) in res.items() for c in calls
"path": str(path),
"string": get_arg(c),
"reconstructed": ast.unparse(c),
"start_line": c.lineno,
"start_offset": c.col_offset,
"end_line": c.end_lineno,
"end_offset": c.end_col_offset,
"comment": getattr(c, "comment", None),
}
for path, calls in res.items()
for c in calls
]
print(json.dumps(to_print_data, indent=2))
output.append(json.dumps(to_print_data, indent=2))
elif args.lang:
if args.lang == '-':
print(generate_lang_template(res))
lang_template = generate_lang_template(res)
if args.lang == "-":
output.append(lang_template)
else:
with open(args.lang, mode='w+', newline='\n') as langfile:
langfile.writelines(generate_lang_template(res))
with open(args.lang, mode="w+", newline="\n", encoding="UTF-8") as langfile:
langfile.writelines(lang_template)
else:
for path, calls in res.items():
if len(calls) == 0:
if not calls:
continue
print(path)
output.append(str(path))
for c in calls:
print(
f' {c.lineno:4d}({c.col_offset:3d}):{c.end_lineno:4d}({c.end_col_offset:3d})\t', ast.unparse(c)
output.append(
f" {c.lineno:4d}({c.col_offset:3d}):{c.end_lineno:4d}({c.end_col_offset:3d})\t{ast.unparse(c)}"
)
output.append("")
print()
# Print all collected output at the end
if output:
print("\n".join(output))
sys.exit(1)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
sys.exit()

View File

@ -8,6 +8,7 @@ See LICENSE file.
from __future__ import annotations
import pathlib
import shutil
import sys
import threading
from traceback import print_exc
@ -26,23 +27,40 @@ if TYPE_CHECKING:
logger = get_main_logger()
def check_for_fdev_updates(silent: bool = False) -> None: # noqa: CCR001
def check_for_fdev_updates(silent: bool = False, local: bool = False) -> None: # noqa: CCR001
"""Check for and download FDEV ID file updates."""
if local:
pathway = config.respath_path
else:
pathway = config.app_dir_path
files_urls = [
('commodity.csv', 'https://raw.githubusercontent.com/EDCD/FDevIDs/master/commodity.csv'),
('rare_commodity.csv', 'https://raw.githubusercontent.com/EDCD/FDevIDs/master/rare_commodity.csv')
]
for file, url in files_urls:
fdevid_file = pathlib.Path(config.respath_path / 'FDevIDs' / file)
fdevid_file = pathlib.Path(pathway / 'FDevIDs' / file)
fdevid_file.parent.mkdir(parents=True, exist_ok=True)
try:
with open(fdevid_file, newline='', encoding='utf-8') as f:
local_content = f.read()
except FileNotFoundError:
local_content = None
logger.info(f'File {file} not found. Writing from bundle...')
try:
for localfile in files_urls:
filepath = pathlib.Path(f"FDevIDs/{localfile[0]}")
try:
shutil.copy(filepath, pathway / 'FDevIDs')
except shutil.SameFileError:
logger.info("Not replacing same file...")
fdevid_file = pathlib.Path(pathway / 'FDevIDs' / file)
with open(fdevid_file, newline='', encoding='utf-8') as f:
local_content = f.read()
except FileNotFoundError:
local_content = None
response = requests.get(url)
response = requests.get(url, timeout=20)
if response.status_code != 200:
if not silent:
logger.error(f'Failed to download {file}! Unable to continue.')
@ -169,10 +187,17 @@ class Updater:
self.updater.win_sparkle_check_update_with_ui()
check_for_fdev_updates()
# TEMP: Only include until 6.0
try:
check_for_fdev_updates(local=True)
except Exception as e:
logger.info("Tried to update bundle FDEV files but failed. Don't worry, "
"this likely isn't important and can be ignored unless"
f" you run into other issues. If you're curious: {e}")
def check_appcast(self) -> EDMCVersion | None:
"""
Manually (no Sparkle or WinSparkle) check the update_feed appcast file.
Manually (no Sparkle or WinSparkle) check the get_update_feed() appcast file.
Checks if any listed version is semantically greater than the current
running version.