diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..e69de29 diff --git a/symconf/__init__.py b/symconf/__init__.py index 8f5b154..4adc8e3 100644 --- a/symconf/__init__.py +++ b/symconf/__init__.py @@ -8,5 +8,9 @@ from symconf import config from symconf import matching from symconf import reader from symconf import template -from symconf import theme from symconf import util + +from importlib.metadata import version + + +__version__ = version('symconf') diff --git a/symconf/__main__.py b/symconf/__main__.py index c6d6a1d..a500d3b 100644 --- a/symconf/__main__.py +++ b/symconf/__main__.py @@ -1,6 +1,7 @@ import argparse +from importlib.metadata import version -from symconf import util +from symconf import util, __version__ from symconf.config import ConfigManager @@ -85,6 +86,34 @@ def add_config_subparser(subparsers): ) parser.set_defaults(func=configure_apps) +def add_generate_subparser(subparsers): + def generate_apps(args): + cm = ConfigManager(args.config_dir) + cm.generate_app_templates( + gen_dir=args.gen_dir, + apps=args.apps, + ) + + parser = subparsers.add_parser( + 'generate', + description='Generate all template config files for specified apps' + ) + parser.add_argument( + '-g', '--gen-dir', + required = True, + type = util.absolute_path, + help = 'Path to write generated template files' + ) + parser.add_argument( + '-a', '--apps', + required = False, + default = "*", + type = lambda s: s.split(',') if s != '*' else s, + help = 'Application target for theme. App must be present in the registry. ' \ + + 'Use "*" to apply to all registered apps' + ) + parser.set_defaults(func=generate_apps) + # central argparse entry point parser = argparse.ArgumentParser( @@ -97,12 +126,19 @@ parser.add_argument( type = util.absolute_path, help = 'Path to config directory' ) +parser.add_argument( + '-v', '--version', + action='version', + version=__version__, + help = 'Print symconf version' +) # add subparsers subparsers = parser.add_subparsers(title='subcommand actions') +add_config_subparser(subparsers) +add_generate_subparser(subparsers) add_install_subparser(subparsers) add_update_subparser(subparsers) -add_config_subparser(subparsers) def main(): diff --git a/symconf/config.py b/symconf/config.py index b5276e0..ab252b3 100644 --- a/symconf/config.py +++ b/symconf/config.py @@ -346,6 +346,45 @@ class ConfigManager: return template_dict, relaxed_theme_matches + def _prepare_all_templates(self) -> dict[str, dict]: + palette_map = {} + palette_group_dir = Path(self.group_dir, 'palette') + if palette_group_dir.exists(): + for palette_path in palette_group_dir.iterdir(): + palette_map[palette_path.stem] = palette_path + + palette_base = [] + if 'none' in palette_map: + palette_base.append(palette_map['none']) + + # then palette-scheme groups (require 2-combo logic) + theme_map = {} + theme_group_dir = Path(self.group_dir, 'theme') + if theme_group_dir.exists(): + for theme_toml in theme_group_dir.iterdir(): + fp = FilePart(theme_toml) + + theme_matches = self.matcher.match_paths( + theme_group_dir.iterdir(), # match files in groups/theme/ + self.matcher.prefix_order(fp.scheme, fp.style) # reg non-template order + ) + relaxed_theme_matches = self.matcher.relaxed_match(theme_matches) + + palette = fp.style.split('-')[-1] + palette_paths = [*palette_base] + if palette in palette_map: + palette_paths.append(palette_map[palette]) + + theme_dict = {} + palette_dict = TOMLTemplate.stack_toml(palette_paths) + for file_part in relaxed_theme_matches: + toml_dict = TOMLTemplate(file_part.path).fill(palette_dict) + theme_dict = util.deep_update(theme_dict, toml_dict) + + theme_map[fp.path.stem] = {'theme': theme_dict} + + return theme_map + def get_matching_configs( self, app_name, @@ -764,3 +803,55 @@ class ConfigManager: apps: str | list[str] = '*', ): self._app_action('update.sh', apps) + + def generate_app_templates( + self, + gen_dir : str | Path, + apps : str | list[str] = '*', + ): + if apps == '*': + app_list = list(self.app_registry.keys()) + else: + app_list = [a for a in apps if a in self.app_registry] + + if not app_list: + print(f'None of the apps "{apps}" are registered, exiting') + return + + print(f'> symconf parameters: ') + print(f' > registered apps :: {color_text(app_list, Fore.YELLOW)}') + print(f'> Writing templates...') + + gen_dir = util.absolute_path(gen_dir) + theme_map = self._prepare_all_templates() + + for app_name in app_list: + app_template_dir = Path(self.apps_dir, app_name, 'templates') + if not app_template_dir.exists(): + continue + + app_template_files = list(app_template_dir.iterdir()) + + print( + color_text("├─", Fore.BLUE), + f'{app_name} :: generating ({len(app_template_files)}) template files' + ) + + for template_file in app_template_files: + app_template = FileTemplate(template_file) + + for theme_stem, theme_dict in theme_map.items(): + tgt_template_dir = Path(gen_dir, app_name) + tgt_template_dir.mkdir(parents=True, exist_ok=True) + + tgt_template_path = Path( + tgt_template_dir, + f'{theme_stem}.{template_file.name}' + ) + filled_template = app_template.fill(theme_dict) + tgt_template_path.write_text(filled_template) + + print( + color_text("│", Fore.BLUE), + f'> generating "{tgt_template_path.name}"' + ) diff --git a/symconf/theme.py b/symconf/theme.py deleted file mode 100644 index 1e5a60a..0000000 --- a/symconf/theme.py +++ /dev/null @@ -1,74 +0,0 @@ -import argparse -import inspect -import json -import tomllib as toml -from pathlib import Path - - -# separation sequences to use base on app -app_sep_map = { - 'kitty': ' ', -} - -def generate_theme_files(): - basepath = get_running_path() - - # set arg conditional variables - palette_path = Path(basepath, 'themes', args.palette) - colors_path = Path(palette_path, 'colors.json') - theme_app = args.app - - template_path = None - output_path = None - - if args.template is None: - template_path = Path(palette_path, 'apps', theme_app, 'templates') - else: - template_path = Path(args.template).resolve() - - if args.output is None: - output_path = Path(palette_path, 'apps', theme_app, 'generated') - else: - output_path = Path(args.output).resolve() - - # check paths - if not colors_path.exists(): - print(f'Resolved colors path [{colors_path}] doesn\'t exist, exiting') - return - - if not template_path.exists(): - print(f'Template path [{template_path}] doesn\'t exist, exiting') - return - - if not output_path.exists() or not output_path.is_dir(): - print(f'Output path [{output_path}] doesn\'t exist or not a directory, exiting') - return - - print(f'Using palette colors [{colors_path}]') - print(f'-> with templates in [{template_path}]') - print(f'-> to output path [{output_path}]\n') - - # load external files (JSON, TOML) - colors_json = json.load(colors_path.open()) - - # get all matching TOML files - template_list = [template_path] - if template_path.is_dir(): - template_list = template_path.rglob('*.toml') - - for template_path in template_list: - template_toml = toml.load(template_path.open('rb')) - - # lookup app-specific config separator - config_sep = app_sep_map.get(theme_app, ' ') - output_lines = [] - for config_key, color_key in template_toml.items(): - color_value = colors_json - for _key in color_key.split('.'): - color_value = color_value.get(_key, {}) - output_lines.append(f'{config_key}{config_sep}{color_value}') - - output_file = Path(output_path, template_path.stem).with_suffix('.conf') - output_file.write_text('\n'.join(output_lines)) - print(f'[{len(output_lines)}] lines written to [{output_file}] for app [{theme_app}]') - diff --git a/tests/test_imports.py b/tests/test_imports.py index 355d8cb..23bc4e6 100644 --- a/tests/test_imports.py +++ b/tests/test_imports.py @@ -7,5 +7,4 @@ def test_imports(): from symconf import config from symconf import reader - from symconf import theme from symconf import util