Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transition of the Configuration to a TOML format #17

Merged
merged 5 commits into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions pycritty/api/config.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import Dict, Callable, Any
from collections.abc import Mapping
from pycritty import resources, PycrittyError
from pycritty.io import log, yaml_io
from typing import Dict, Callable, Any

from pycritty import resources, PycrittyError
from pycritty.io import log, toml_io, yaml_io
from pathlib import Path

class ConfigError(PycrittyError):
def __init__(self, message='Error applying configuration'):
Expand All @@ -19,12 +20,12 @@ class Config:
"""

def __init__(self):
self.config = yaml_io.read(resources.config_file.get_or_create())
self.config = toml_io.read(resources.config_file.get_or_create())
if self.config is None:
self.config = {}

def apply(self):
yaml_io.write(self.config, resources.config_file)
toml_io.write(self.config, resources.config_file)

def set(self, **kwargs):
"""Set multiple changes at once
Expand Down Expand Up @@ -57,14 +58,21 @@ def set(self, **kwargs):

def change_theme(self, theme: str):
theme_file = resources.get_theme(theme)
theme_type = Path(theme_file.path).suffix
theme = None

if not theme_file.exists():
raise PycrittyError(f'Theme "{theme}" not found')

theme_yaml = yaml_io.read(theme_file)
if theme_yaml is None:
if theme_type != ".toml":
theme = yaml_io.read(theme_file)
if 'colors' not in theme:
raise ConfigError(f'{theme_file} does not contain color config')
else:
theme = toml_io.read(theme_file)

if theme is None:
raise ConfigError(f'File {theme_file} is empty')
if 'colors' not in theme_yaml:
raise ConfigError(f'{theme_file} does not contain color config')

expected_colors = [
'black',
Expand All @@ -84,14 +92,14 @@ def change_theme(self, theme: str):
}

for k in expected_props:
if k not in theme_yaml['colors']:
if k not in theme['colors']:
log.warn(f'Missing "colors:{k}" for theme "{theme}"')
continue
for v in expected_props[k]:
if v not in theme_yaml['colors'][k]:
if v not in theme['colors'][k]:
log.warn(f'Missing "colors:{k}:{v}" for theme "{theme}"')

self.config['colors'] = theme_yaml['colors']
self.config['colors'] = theme['colors']
log.ok(f'Theme {theme} applied')

def change_font(self, font: str):
Expand Down
8 changes: 4 additions & 4 deletions pycritty/api/load.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from pycritty import PycrittyError
from pycritty.io import log, yaml_io
from pycritty.io import log, toml_io
from pycritty.resources import saves_dir, config_file, ConfigFile


def load_config(name: str):
file_to_load = ConfigFile(saves_dir.get_or_create(), name, ConfigFile.YAML)
file_to_load = ConfigFile(saves_dir.get_or_create(), name, ConfigFile.FILES)
if not file_to_load.exists():
raise PycrittyError(f'Config "{name}" not found')

conf = yaml_io.read(file_to_load)
conf = toml_io.read(file_to_load)
if conf is None or len(conf) < 1:
log.warn(f'"{file_to_load}" has no content')
else:
yaml_io.write(conf, config_file)
toml_io.write(conf, config_file)

log.ok(f'Config "{name}" applied')
2 changes: 1 addition & 1 deletion pycritty/api/rm.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
def remove(configs: List[str], from_themes=False, force=False):
config_parent = themes_dir if from_themes else saves_dir
for conf in configs:
file = ConfigFile(config_parent.path, conf, ConfigFile.YAML)
file = ConfigFile(config_parent.path, conf, ConfigFile.FILES)
if not file.exists():
log.warn(f'{conf} ->', log.Color.BOLD, file, log.Color.YELLOW, 'not found')
continue
Expand Down
8 changes: 4 additions & 4 deletions pycritty/api/save.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Union
from pathlib import Path
from pycritty import PycrittyError
from pycritty.io import log, yaml_io
from pycritty.io import log, toml_io
from pycritty.resources import config_file, saves_dir, themes_dir
from pycritty.resources.resource import ConfigFile

Expand All @@ -13,17 +13,17 @@ def save_config(
override=False,
):
read_from = read_from or config_file
dest_file = ConfigFile(dest_parent.get_or_create(), name, ConfigFile.YAML)
dest_file = ConfigFile(dest_parent.get_or_create(), name, ConfigFile.FILES)
word_to_use = "Theme" if dest_parent == themes_dir else "Config"
if dest_file.exists() and not override:
raise PycrittyError(
f'{word_to_use} "{name}" already exists, use -o to override'
)

conf = yaml_io.read(read_from)
conf = toml_io.read(read_from)
if conf is None or len(conf) < 1:
log.warn(f'"{read_from}" has no content')
else:
dest_file.create()
yaml_io.write(conf, dest_file)
toml_io.write(conf, dest_file)
log.ok(f"{word_to_use} saved =>", log.Color.BLUE, dest_file)
9 changes: 9 additions & 0 deletions pycritty/io/error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pycritty import PycrittyError


class FileIOError(PycrittyError):
pass


class FileParseError(PycrittyError):
pass
43 changes: 43 additions & 0 deletions pycritty/io/toml_io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from io import BufferedRandom
from pathlib import Path
from typing import Dict, Any, Union, Callable
from urllib.error import URLError
from urllib.parse import urlparse
from urllib.request import urlopen

import toml

from pycritty.io.error import FileIOError, FileParseError
from pycritty.resources.resource import Resource


def write(y: Dict[str, Any], file: Union[Path, Resource]):
if isinstance(file, Resource):
file = file.path
try:
with open(file, 'w') as f:
toml.dump(y, f)
except IOError as e:
raise FileIOError(f'Error trying to write "{file}":\n{e}')


def read(url: Union[str, Path, Resource]) -> Dict[str, Any]:
has_protocol = False
open_function: Callable[..., BufferedRandom]

if isinstance(url, str):
has_protocol = urlparse(url).scheme != ''
if isinstance(url, Resource):
url = url.path
if not has_protocol or isinstance(url, Path):
open_function = open
else:
open_function = urlopen

try:
with open_function(url) as f:
return toml.load(f)
except (IOError, URLError) as e:
raise FileIOError(f'Error trying to access "{url}":\n{e}')
except (UnicodeDecodeError, toml.decoder.TomlDecodeError) as e:
raise FileParseError(f'Failed decoding "{url}":\n{e}')
34 changes: 5 additions & 29 deletions pycritty/io/yaml_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,9 @@
from urllib.parse import urlparse
from urllib.error import URLError
from pathlib import Path
import yaml
from pycritty.resources.resource import Resource
from pycritty import PycrittyError


class YamlIOError(PycrittyError):
pass


class YamlParseError(PycrittyError):
pass

from pycritty.io.error import FileIOError, FileParseError
import yaml

def read(url: Union[str, Path, Resource]) -> Dict[str, Any]:
"""Read YAML from a URL or from the local file system
Expand All @@ -43,28 +34,13 @@ def read(url: Union[str, Path, Resource]) -> Dict[str, Any]:
with open_function(url) as f:
return yaml.load(f, Loader=yaml.FullLoader)
except (IOError, URLError) as e:
raise YamlIOError(f'Error trying to access "{url}":\n{e}')
raise FileIOError(f'Error trying to access "{url}":\n{e}')
except (UnicodeDecodeError, yaml.reader.ReaderError) as e:
raise YamlParseError(f'Failed decoding "{url}":\n{e}')
raise FileParseError(f'Failed decoding "{url}":\n{e}')
except yaml.MarkedYAMLError as e:
raise YamlParseError(
raise FileParseError(
f"YAML error at {url}, "
f"{e.problem_mark and 'at line ' + str(e.problem_mark.line)}, "
f"{e.problem_mark and 'column ' + str(e.problem_mark.column)}:\n"
f"{e.problem} {e.context or ''}"
)


def write(y: Dict[str, Any], file: Union[Path, Resource]):
"""Write YAML to a file in the local system

>>> write({'example': 123}, Path().home() / 'exmaple.yaml')
"""

if isinstance(file, Resource):
file = file.path
try:
with open(file, 'w') as f:
yaml.dump(y, f)
except IOError as e:
raise YamlIOError(f'Failed writing to {file}:\n{e}')
6 changes: 3 additions & 3 deletions pycritty/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

pycritty_dir = ConfigDir(Path().home() / '.config' / 'pycritty')
alacritty_dir = ConfigDir(Path().home() / '.config' / 'alacritty')
config_file = ConfigFile(alacritty_dir.path, 'alacritty', ConfigFile.YAML)
fonts_file = ConfigFile(pycritty_dir.path, 'fonts', ConfigFile.YAML)
config_file = ConfigFile(alacritty_dir.path, 'alacritty', ConfigFile.FILES)
fonts_file = ConfigFile(pycritty_dir.path, 'fonts', ConfigFile.FILES)
themes_dir = ConfigDir(pycritty_dir.path / 'themes')
saves_dir = ConfigDir(pycritty_dir.path / 'saves')


def get_theme(theme: str) -> ConfigFile:
return ConfigFile(themes_dir.get_or_create(), theme, ConfigFile.YAML)
return ConfigFile(themes_dir.get_or_create(), theme, ConfigFile.FILES)
2 changes: 1 addition & 1 deletion pycritty/resources/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def create(self):
class ConfigFile(Resource):
"""Class used to manage config files that may have many possible extensions"""

YAML = ['yml', 'yaml']
FILES = ['toml', 'yml', 'yaml']

def __init__(self, parent: Path, name: str, extensions: List[str]):
if len(extensions) > 0:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ flake8==4.0.1
mypy==0.931
pytest==7.0.1
PyYAML==6.0
toml==0.10.2
types-setuptools==57.4.9
types-PyYAML==6.0.4
22 changes: 11 additions & 11 deletions tests/cli/test_ls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,44 @@ class TestListFonts:
def test_it_should_raise_pycritty_error_when_file_does_not_exist(self):
target_file = Mock(
spec=ConfigFile,
path=Path().cwd() / "example.yaml",
path=Path().cwd() / "example.toml",
exists=Mock(return_value=False),
)

with pytest.raises(PycrittyError):
list_fonts(target_file)

@pytest.mark.parametrize("fonts_content", [None, {}])
@patch("pycritty.api.ls.yaml_io")
@patch("pycritty.api.ls.toml_io")
def test_it_should_return_empty_fonts_when_file_has_no_content(
self, mock_yaml_io: MagicMock, fonts_content
self, mock_toml_io: MagicMock, fonts_content
):
target_file = Mock(
spec=ConfigFile,
path=Path().cwd() / "example.yaml",
path=Path().cwd() / "example.toml",
exists=Mock(return_value=True),
)
mock_yaml_io.read.return_value = fonts_content
mock_toml_io.read.return_value = fonts_content

fonts = list_fonts(target_file)

mock_yaml_io.read.assert_called_once_with(target_file)
mock_toml_io.read.assert_called_once_with(target_file)
assert list(fonts) == []

@patch("pycritty.api.ls.yaml_io")
def test_it_should_return_fonts(self, mock_yaml_io: MagicMock):
@patch("pycritty.api.ls.toml_io")
def test_it_should_return_fonts(self, mock_toml_io: MagicMock):
target_file = Mock(
spec=ConfigFile,
path=Path().cwd() / "example.yaml",
path=Path().cwd() / "example.toml",
exists=Mock(return_value=True),
)
mock_yaml_io.read.return_value = {
mock_toml_io.read.return_value = {
"fonts": {"onedark": "OneDark", "cascadia": "Cascadia"}
}

fonts = list_fonts(target_file)

mock_yaml_io.read.assert_called_once_with(target_file)
mock_toml_io.read.assert_called_once_with(target_file)
assert list(fonts) == ["onedark", "cascadia"]


Expand Down