mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-12 23:37:14 +03:00
[2051] Revert Linux Changes
I can't adequately test this right now, so not touching it.
This commit is contained in:
parent
28e10b70ef
commit
cb4a26186a
129
config/linux.py
129
config/linux.py
@ -9,7 +9,6 @@ import os
|
||||
import pathlib
|
||||
import sys
|
||||
from configparser import ConfigParser
|
||||
from typing import Optional, Union, List
|
||||
from config import AbstractConfig, appname, logger
|
||||
|
||||
assert sys.platform == 'linux'
|
||||
@ -19,97 +18,100 @@ class LinuxConfig(AbstractConfig):
|
||||
"""Linux implementation of AbstractConfig."""
|
||||
|
||||
SECTION = 'config'
|
||||
|
||||
# TODO: I dislike this, would rather use a sane config file format. But here we are.
|
||||
__unescape_lut = {'\\': '\\', 'n': '\n', ';': ';', 'r': '\r', '#': '#'}
|
||||
__escape_lut = {'\\': '\\', '\n': 'n', ';': ';', '\r': 'r'}
|
||||
|
||||
def __init__(self, filename: Optional[str] = None) -> None:
|
||||
"""
|
||||
Initialize LinuxConfig instance.
|
||||
|
||||
:param filename: Optional file name to use for configuration storage.
|
||||
"""
|
||||
def __init__(self, filename: str | None = None) -> None:
|
||||
super().__init__()
|
||||
|
||||
# Initialize directory paths
|
||||
# http://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
xdg_data_home = pathlib.Path(os.getenv('XDG_DATA_HOME', default='~/.local/share')).expanduser()
|
||||
self.app_dir_path = xdg_data_home / appname
|
||||
self.app_dir_path.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
self.plugin_dir_path = self.app_dir_path / 'plugins'
|
||||
self.plugin_dir_path.mkdir(exist_ok=True)
|
||||
|
||||
self.respath_path = pathlib.Path(__file__).parent.parent
|
||||
|
||||
self.internal_plugin_dir_path = self.respath_path / 'plugins'
|
||||
self.default_journal_dir_path = None # type: ignore
|
||||
self.identifier = f'uk.org.marginal.{appname.lower()}' # TODO: Unused?
|
||||
|
||||
# Configure the filename
|
||||
config_home = pathlib.Path(os.getenv('XDG_CONFIG_HOME', default='~/.config')).expanduser()
|
||||
self.filename = pathlib.Path(filename) if filename is not None else config_home / appname / f'{appname}.ini'
|
||||
|
||||
self.filename = config_home / appname / f'{appname}.ini'
|
||||
if filename is not None:
|
||||
self.filename = pathlib.Path(filename)
|
||||
|
||||
self.filename.parent.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
# Initialize the configuration
|
||||
self.config = ConfigParser(comment_prefixes=('#',), interpolation=None)
|
||||
self.config.read(self.filename)
|
||||
self.config: ConfigParser | None = ConfigParser(comment_prefixes=('#',), interpolation=None)
|
||||
self.config.read(self.filename) # read() ignores files that dont exist
|
||||
|
||||
# Ensure that our section exists. This is here because configparser will happily create files for us, but it
|
||||
# does not magically create sections
|
||||
try:
|
||||
self.config[self.SECTION].get("this_does_not_exist")
|
||||
self.config[self.SECTION].get("this_does_not_exist", fallback=None)
|
||||
except KeyError:
|
||||
logger.info("Config section not found. Backing up existing file (if any) and re-adding a section header")
|
||||
logger.info("Config section not found. Backing up existing file (if any) and readding a section header")
|
||||
if self.filename.exists():
|
||||
backup_filename = self.filename.parent / f'{appname}.ini.backup'
|
||||
backup_filename.write_bytes(self.filename.read_bytes())
|
||||
(self.filename.parent / f'{appname}.ini.backup').write_bytes(self.filename.read_bytes())
|
||||
|
||||
self.config.add_section(self.SECTION)
|
||||
|
||||
# Set 'outdir' if not specified or invalid
|
||||
outdir = self.get_str('outdir')
|
||||
if outdir is None or not pathlib.Path(outdir).is_dir():
|
||||
if (outdir := self.get_str('outdir')) is None or not pathlib.Path(outdir).is_dir():
|
||||
self.set('outdir', self.home)
|
||||
|
||||
def __escape(self, s: str) -> str:
|
||||
"""
|
||||
Escape special characters in a string.
|
||||
Escape a string using self.__escape_lut.
|
||||
|
||||
:param s: The input string.
|
||||
:return: The escaped string.
|
||||
This does NOT support multi-character escapes.
|
||||
|
||||
:param s: str - String to be escaped.
|
||||
:return: str - The escaped string.
|
||||
"""
|
||||
escaped_chars = []
|
||||
|
||||
out = ""
|
||||
for c in s:
|
||||
escaped_chars.append(self.__escape_lut.get(c, c))
|
||||
if c not in self.__escape_lut:
|
||||
out += c
|
||||
continue
|
||||
|
||||
return ''.join(escaped_chars)
|
||||
out += '\\' + self.__escape_lut[c]
|
||||
|
||||
return out
|
||||
|
||||
def __unescape(self, s: str) -> str:
|
||||
"""
|
||||
Unescape special characters in a string.
|
||||
Unescape a string.
|
||||
|
||||
:param s: The input string.
|
||||
:return: The unescaped string.
|
||||
:param s: str - The string to unescape.
|
||||
:return: str - The unescaped string.
|
||||
"""
|
||||
unescaped_chars = []
|
||||
out: list[str] = []
|
||||
i = 0
|
||||
while i < len(s):
|
||||
current_char = s[i]
|
||||
if current_char != '\\':
|
||||
unescaped_chars.append(current_char)
|
||||
c = s[i]
|
||||
if c != '\\':
|
||||
out.append(c)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if i == len(s) - 1:
|
||||
# We have a backslash, check what its escaping
|
||||
if i == len(s)-1:
|
||||
raise ValueError('Escaped string has unescaped trailer')
|
||||
|
||||
unescaped = self.__unescape_lut.get(s[i + 1])
|
||||
unescaped = self.__unescape_lut.get(s[i+1])
|
||||
if unescaped is None:
|
||||
raise ValueError(f'Unknown escape: \\{s[i + 1]}')
|
||||
raise ValueError(f'Unknown escape: \\ {s[i+1]}')
|
||||
|
||||
unescaped_chars.append(unescaped)
|
||||
out.append(unescaped)
|
||||
i += 2
|
||||
|
||||
return "".join(unescaped_chars)
|
||||
return "".join(out)
|
||||
|
||||
def __raw_get(self, key: str) -> Optional[str]:
|
||||
def __raw_get(self, key: str) -> str | None:
|
||||
"""
|
||||
Get a raw data value from the config file.
|
||||
|
||||
@ -121,7 +123,7 @@ class LinuxConfig(AbstractConfig):
|
||||
|
||||
return self.config[self.SECTION].get(key)
|
||||
|
||||
def get_str(self, key: str, *, default: Optional[str] = None) -> str:
|
||||
def get_str(self, key: str, *, default: str | None = None) -> str:
|
||||
"""
|
||||
Return the string referred to by the given key if it exists, or the default.
|
||||
|
||||
@ -129,28 +131,29 @@ class LinuxConfig(AbstractConfig):
|
||||
"""
|
||||
data = self.__raw_get(key)
|
||||
if data is None:
|
||||
return default or ""
|
||||
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default
|
||||
|
||||
if '\n' in data:
|
||||
raise ValueError('Expected string, but got list')
|
||||
raise ValueError('asked for string, got list')
|
||||
|
||||
return self.__unescape(data)
|
||||
|
||||
def get_list(self, key: str, *, default: Optional[list] = None) -> list:
|
||||
def get_list(self, key: str, *, default: list | None = None) -> list:
|
||||
"""
|
||||
Return the list referred to by the given key if it exists, or the default.
|
||||
|
||||
Implements :meth:`AbstractConfig.get_list`.
|
||||
"""
|
||||
data = self.__raw_get(key)
|
||||
|
||||
if data is None:
|
||||
return default or []
|
||||
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default
|
||||
|
||||
split = data.split('\n')
|
||||
if split[-1] != ';':
|
||||
raise ValueError('Encoded list does not have trailer sentinel')
|
||||
|
||||
return [self.__unescape(item) for item in split[:-1]]
|
||||
return list(map(self.__unescape, split[:-1]))
|
||||
|
||||
def get_int(self, key: str, *, default: int = 0) -> int:
|
||||
"""
|
||||
@ -159,47 +162,55 @@ class LinuxConfig(AbstractConfig):
|
||||
Implements :meth:`AbstractConfig.get_int`.
|
||||
"""
|
||||
data = self.__raw_get(key)
|
||||
|
||||
if data is None:
|
||||
return default
|
||||
|
||||
try:
|
||||
return int(data)
|
||||
except ValueError as e:
|
||||
raise ValueError(f'Failed to convert {key=} to int') from e
|
||||
|
||||
def get_bool(self, key: str, *, default: Optional[bool] = None) -> bool:
|
||||
except ValueError as e:
|
||||
raise ValueError(f'requested {key=} as int cannot be converted to int') from e
|
||||
|
||||
def get_bool(self, key: str, *, default: bool | None = None) -> bool:
|
||||
"""
|
||||
Return the bool referred to by the given key if it exists, or the default.
|
||||
|
||||
Implements :meth:`AbstractConfig.get_bool`.
|
||||
"""
|
||||
if self.config is None:
|
||||
raise ValueError('Attempt to use a closed config')
|
||||
raise ValueError('attempt to use a closed config')
|
||||
|
||||
data = self.__raw_get(key)
|
||||
if data is None:
|
||||
return default or False
|
||||
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default
|
||||
|
||||
return bool(int(data))
|
||||
|
||||
def set(self, key: str, val: Union[int, str, List[str]]) -> None:
|
||||
def set(self, key: str, val: int | str | list[str]) -> None:
|
||||
"""
|
||||
Set the given key's data to the given value.
|
||||
|
||||
Implements :meth:`AbstractConfig.set`.
|
||||
"""
|
||||
if self.config is None:
|
||||
raise ValueError('Attempt to use a closed config')
|
||||
raise ValueError('attempt to use a closed config')
|
||||
|
||||
to_set: str | None = None
|
||||
if isinstance(val, bool):
|
||||
to_set = str(int(val))
|
||||
|
||||
elif isinstance(val, str):
|
||||
to_set = self.__escape(val)
|
||||
|
||||
elif isinstance(val, int):
|
||||
to_set = str(val)
|
||||
|
||||
elif isinstance(val, list):
|
||||
to_set = '\n'.join([self.__escape(s) for s in val] + [';'])
|
||||
|
||||
else:
|
||||
raise ValueError(f'Unexpected type for value {type(val).__name__}')
|
||||
raise ValueError(f'Unexpected type for value {type(val)=}')
|
||||
|
||||
self.config.set(self.SECTION, key, to_set)
|
||||
self.save()
|
||||
@ -211,7 +222,7 @@ class LinuxConfig(AbstractConfig):
|
||||
Implements :meth:`AbstractConfig.delete`.
|
||||
"""
|
||||
if self.config is None:
|
||||
raise ValueError('Attempt to delete from a closed config')
|
||||
raise ValueError('attempt to use a closed config')
|
||||
|
||||
self.config.remove_option(self.SECTION, key)
|
||||
self.save()
|
||||
@ -223,7 +234,7 @@ class LinuxConfig(AbstractConfig):
|
||||
Implements :meth:`AbstractConfig.save`.
|
||||
"""
|
||||
if self.config is None:
|
||||
raise ValueError('Attempt to save a closed config')
|
||||
raise ValueError('attempt to use a closed config')
|
||||
|
||||
with open(self.filename, 'w', encoding='utf-8') as f:
|
||||
self.config.write(f)
|
||||
@ -235,4 +246,4 @@ class LinuxConfig(AbstractConfig):
|
||||
Implements :meth:`AbstractConfig.close`.
|
||||
"""
|
||||
self.save()
|
||||
self.config = None # type: ignore
|
||||
self.config = None
|
||||
|
Loading…
x
Reference in New Issue
Block a user