add install/update subcommands, add key-val spec for templates
This commit is contained in:
parent
8a78da1a28
commit
cb1dd52833
@ -4,20 +4,56 @@ from symconf import util
|
||||
from symconf.config import ConfigManager
|
||||
|
||||
|
||||
def add_set_subparser(subparsers):
|
||||
def update_app_settings(args):
|
||||
def add_install_subparser(subparsers):
|
||||
def install_apps(args):
|
||||
cm = ConfigManager(args.config_dir)
|
||||
cm.update_apps(
|
||||
cm.install_apps(apps=args.apps)
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
'install',
|
||||
description='Run install scripts for registered applications.'
|
||||
)
|
||||
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=install_apps)
|
||||
|
||||
def add_update_subparser(subparsers):
|
||||
def update_apps(args):
|
||||
cm = ConfigManager(args.config_dir)
|
||||
cm.update_apps(apps=args.apps)
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
'update',
|
||||
description='Run update scripts for registered applications.'
|
||||
)
|
||||
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=update_apps)
|
||||
|
||||
def add_config_subparser(subparsers):
|
||||
def config_apps(args):
|
||||
cm = ConfigManager(args.config_dir)
|
||||
cm.config_apps(
|
||||
apps=args.apps,
|
||||
scheme=args.scheme,
|
||||
palette=args.palette,
|
||||
)
|
||||
|
||||
parser = subparsers.add_parser(
|
||||
'set',
|
||||
description='Generate theme files for various applications. Uses a template (in TOML ' \
|
||||
+ 'format) to map application-specific config keywords to colors (in JSON ' \
|
||||
+ 'format).'
|
||||
'config',
|
||||
description='Set config files for registered applications.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--palette',
|
||||
@ -39,45 +75,20 @@ def add_set_subparser(subparsers):
|
||||
help = 'Application target for theme. App must be present in the registry. ' \
|
||||
+ 'Use "*" to apply to all registered apps'
|
||||
)
|
||||
parser.set_defaults(func=update_app_settings)
|
||||
|
||||
def add_gen_subparser(subparsers):
|
||||
parser = subparsers.add_parser(
|
||||
'gen',
|
||||
description='Generate theme files for various applications. Uses a template (in TOML ' \
|
||||
+ 'format) to map application-specific config keywords to colors (in JSON ' \
|
||||
+ 'format).'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-a', '--app',
|
||||
required=True,
|
||||
help='Application target for theme. Supported: ["kitty"]'
|
||||
'-T', '--template-vars',
|
||||
required = False,
|
||||
nargs='+',
|
||||
action=util.KVPair,
|
||||
help='Groups to use when populating templates, in the form group=value'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--palette',
|
||||
required=True,
|
||||
help='Palette to use for template mappings. Uses local "theme/<palette>/colors.json".'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-t', '--template',
|
||||
default=None,
|
||||
help='Path to TOML template file. If omitted, app\'s default template path is used.' \
|
||||
+ 'If a directory is provided, all TOML files in the folder will be used.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
default=None,
|
||||
help='Output file path for theme. If omitted, app\'s default theme output path is used.'
|
||||
)
|
||||
parser.set_defaults(func=generate_theme_files)
|
||||
parser.set_defaults(func=config_apps)
|
||||
|
||||
|
||||
# central argparse entry point
|
||||
parser = argparse.ArgumentParser(
|
||||
'symconf',
|
||||
description='Generate theme files for various applications. Uses a template (in TOML ' \
|
||||
+ 'format) to map application-specific config keywords to colors (in JSON ' \
|
||||
+ 'format).'
|
||||
description='Manage application configuration with symlinks.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-c', '--config-dir',
|
||||
@ -88,8 +99,9 @@ parser.add_argument(
|
||||
|
||||
# add subparsers
|
||||
subparsers = parser.add_subparsers(title='subcommand actions')
|
||||
#add_gen_subparser(subparsers)
|
||||
add_set_subparser(subparsers)
|
||||
add_install_subparser(subparsers)
|
||||
add_update_subparser(subparsers)
|
||||
add_config_subparser(subparsers)
|
||||
|
||||
|
||||
def main():
|
||||
|
@ -1,6 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import stat
|
||||
import inspect
|
||||
import tomllib
|
||||
import argparse
|
||||
@ -12,8 +13,9 @@ from colorama import Fore, Back, Style
|
||||
from symconf import util
|
||||
from symconf.reader import DictReader
|
||||
|
||||
|
||||
def y(t):
|
||||
return Style.RESET_ALL + Fore.YELLOW + t + Style.RESET_ALL
|
||||
return Style.RESET_ALL + Fore.BLUE + t + Style.RESET_ALL
|
||||
|
||||
class ConfigManager:
|
||||
def __init__(
|
||||
@ -92,6 +94,30 @@ class ConfigManager:
|
||||
|
||||
return value
|
||||
|
||||
def _run_script(
|
||||
self,
|
||||
script,
|
||||
):
|
||||
script_path = Path(script)
|
||||
if script_path.stat().st_mode & stat.S_IXUSR == 0:
|
||||
print(
|
||||
f'{y("│")}' + Fore.RED + Style.DIM \
|
||||
+ f' > script "{script_path.relative_to(self.config_dir)}" missing execute permissions, skipping'
|
||||
)
|
||||
return
|
||||
|
||||
print(f'{y("│")}' + Fore.BLUE + Style.DIM + f' > running script "{script_path.relative_to(self.config_dir)}"')
|
||||
|
||||
output = subprocess.check_output(str(script_path), shell=True)
|
||||
if output:
|
||||
fmt_output = output.decode().strip().replace('\n',f'\n{y("│")} ')
|
||||
print(
|
||||
f'{y("│")}' + \
|
||||
Fore.BLUE + Style.DIM + \
|
||||
f' > captured script output "{fmt_output}"' \
|
||||
+ Style.RESET_ALL
|
||||
)
|
||||
|
||||
def app_config_map(self, app_name) -> dict[str, Path]:
|
||||
'''
|
||||
Get the config map for a provided app.
|
||||
@ -650,11 +676,6 @@ class ConfigManager:
|
||||
links_fail.append((from_path, to_path))
|
||||
continue
|
||||
|
||||
if not from_path.parent.exists():
|
||||
print(f'Target config parent directory for "{from_path}" doesn\'t exist, skipping')
|
||||
links_fail.append((from_path, to_path))
|
||||
continue
|
||||
|
||||
# if config file being symlinked exists & isn't already a symlink (i.e.,
|
||||
# previously set by this script), throw an error.
|
||||
if from_path.exists() and not from_path.is_symlink():
|
||||
@ -671,6 +692,10 @@ class ConfigManager:
|
||||
from_path.unlink(missing_ok=True)
|
||||
|
||||
#print(f'Linking [{from_path}] -> [{to_path}]')
|
||||
|
||||
# create parent directory if doesn't exist
|
||||
from_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
from_path.symlink_to(to_path)
|
||||
links_succ.append((from_path, to_path))
|
||||
|
||||
@ -685,20 +710,10 @@ class ConfigManager:
|
||||
to_p = to_p.relative_to(self.config_dir)
|
||||
print(f'{y("│")}' + Fore.RED + f' > failed to link {from_p} -> {to_p}')
|
||||
|
||||
|
||||
for script in script_list:
|
||||
print(f'{y("│")}' + Fore.BLUE + f' > running script "{script.relative_to(self.config_dir)}"')
|
||||
output = subprocess.check_output(str(script), shell=True)
|
||||
if output:
|
||||
fmt_output = output.decode().strip().replace('\n',f'\n{y("│")} ')
|
||||
print(
|
||||
f'{y("│")}' + \
|
||||
Fore.BLUE + Style.DIM + \
|
||||
f' > captured script output "{fmt_output}"' \
|
||||
+ Style.RESET_ALL
|
||||
)
|
||||
self._run_script(script)
|
||||
|
||||
def update_apps(
|
||||
def config_apps(
|
||||
self,
|
||||
apps: str | list[str] = '*',
|
||||
scheme = 'any',
|
||||
@ -723,6 +738,11 @@ class ConfigManager:
|
||||
print(' > scheme :: ' + Fore.YELLOW + f'{scheme}\n' + Style.RESET_ALL)
|
||||
|
||||
for app_name in app_list:
|
||||
app_dir = Path(self.apps_dir, app_name)
|
||||
if not app_dir.exists():
|
||||
# app has no directory, skip it
|
||||
continue
|
||||
|
||||
self.update_app_config(
|
||||
app_name,
|
||||
app_settings=self.app_registry[app_name],
|
||||
@ -731,3 +751,53 @@ class ConfigManager:
|
||||
strict=False,
|
||||
**kw_groups,
|
||||
)
|
||||
|
||||
def install_apps(
|
||||
self,
|
||||
apps: str | list[str] = '*',
|
||||
):
|
||||
if apps == '*':
|
||||
# get all registered apps
|
||||
app_list = list(self.app_registry.keys())
|
||||
else:
|
||||
# get requested apps that overlap with registry
|
||||
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('> symconf parameters: ')
|
||||
print(' > registered apps :: ' + Fore.YELLOW + f'{app_list}' + Style.RESET_ALL)
|
||||
|
||||
for app_name in app_list:
|
||||
install_script = Path(self.apps_dir, app_name, 'install.sh')
|
||||
if not install_script.exists():
|
||||
continue
|
||||
|
||||
self._run_script(install_script)
|
||||
|
||||
def update_apps(
|
||||
self,
|
||||
apps: str | list[str] = '*',
|
||||
):
|
||||
if apps == '*':
|
||||
# get all registered apps
|
||||
app_list = list(self.app_registry.keys())
|
||||
else:
|
||||
# get requested apps that overlap with registry
|
||||
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('> symconf parameters: ')
|
||||
print(' > registered apps :: ' + Fore.YELLOW + f'{app_list}' + Style.RESET_ALL)
|
||||
|
||||
for app_name in app_list:
|
||||
update_script = Path(self.apps_dir, app_name, 'update.sh')
|
||||
if not update_script.exists():
|
||||
continue
|
||||
|
||||
self._run_script(update_script)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from xdg import BaseDirectory
|
||||
|
||||
@ -19,3 +20,13 @@ def deep_update(mapping: dict, *updating_mappings: dict) -> dict:
|
||||
updated_mapping[k] = v
|
||||
return updated_mapping
|
||||
|
||||
|
||||
class KVPair(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
kv_dict = getattr(namespace, self.dest, {})
|
||||
if kv_dict is None:
|
||||
kv_dict = {}
|
||||
for value in values:
|
||||
key, val = value.split('=', 1)
|
||||
kv_dict[key] = val
|
||||
setattr(namespace, self.dest, kv_dict)
|
||||
|
Loading…
Reference in New Issue
Block a user