Skip to content

Commit

Permalink
Don't use settings schema file
Browse files Browse the repository at this point in the history
  • Loading branch information
tekktrik committed Mar 30, 2024
1 parent 9e56605 commit 569dae8
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 187 deletions.
4 changes: 0 additions & 4 deletions circfirm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,9 @@
# Files
_TEMPLATES_FOLDER = os.path.abspath(os.path.join(__file__, "..", "templates"))
_SETTINGS_FILE_SRC = os.path.join(_TEMPLATES_FOLDER, "settings.yaml")
_SETTINGS_SCHEMA_FILE_SRC = os.path.join(_TEMPLATES_FOLDER, "settings.schema.yaml")
SETTINGS_FILE = specify_template(
_SETTINGS_FILE_SRC, os.path.join(APP_DIR, "settings.yaml")
)
SETTINGS_SCHEMA_FILE = specify_template(
_SETTINGS_SCHEMA_FILE_SRC, os.path.join(APP_DIR, "settings.schema.yaml")
)

UF2INFO_FILE = "info_uf2.txt"
BOOTOUT_FILE = "boot_out.txt"
77 changes: 56 additions & 21 deletions circfirm/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import sys
import time
import types
import warnings
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar

import click
Expand All @@ -37,14 +36,26 @@ def cli() -> None:
"""Manage CircuitPython firmware from the command line."""


def maybe_support(msg: str) -> None:
"""Output supporting text based on the configurable settings."""
def _maybe_output(msg: str, setting_path: Iterable[str], invert: bool = False) -> None:
"""Output text based on the configurable settings."""
settings = get_settings()
do_output: bool = not settings["output"]["supporting"]["silence"]
if do_output:
for path in setting_path:
settings = settings[path]
settings = not settings if invert else settings
if settings:
click.echo(msg)


def maybe_support(msg: str) -> None:
"""Output supporting text based on the configurable settings."""
_maybe_output(msg, ("output", "supporting", "silence"), invert=True)


def maybe_warn(msg: str) -> None:
"""Output warning text based on the configurable settings."""
_maybe_output(msg, ("output", "warning", "silence"), invert=True)


def get_board_id(
circuitpy: Optional[str],
bootloader: Optional[str],
Expand Down Expand Up @@ -162,17 +173,12 @@ def get_settings() -> Dict[str, Any]:
return circfirm.config.get_config_settings(circfirm.SETTINGS_FILE)


def get_settings_schema() -> Dict[str, Any]:
"""Get the contents of the settings file."""
return circfirm.config.get_config_settings(circfirm.SETTINGS_SCHEMA_FILE)


def load_subcmd_folder(
path: str,
super_import_name: Optional[str] = None,
*,
filenames_as_commands: bool = False,
ignore_missing_cli: bool = True
ignore_missing_cli: bool = True,
) -> None:
"""Load subcommands dynamically from a folder of modules and packages."""
subcmd_modtuples = [
Expand All @@ -184,14 +190,26 @@ def load_subcmd_folder(
]

for (_, ispkg), subcmd_path in zip(subcmd_modtuples, subcmd_filepaths):
load_subcmd_file(subcmd_path, super_import_name, ispkg, filename_as_command=filenames_as_commands, ignore_missing_cli=ignore_missing_cli)
load_subcmd_file(
subcmd_path,
super_import_name,
ispkg,
filename_as_command=filenames_as_commands,
ignore_missing_cli=ignore_missing_cli,
)


def load_subcmd_file(path: str, super_import_name: Optional[str] = None, ispkg: bool = False, *, filename_as_command: bool = False, ignore_missing_cli: bool = True) -> None:
def load_subcmd_file(
path: str,
super_import_name: Optional[str] = None,
ispkg: bool = False,
*,
filename_as_command: bool = False,
ignore_missing_cli: bool = True,
) -> None:
"""Load subcommands dynamically from a file."""
modname = os.path.splitext(os.path.basename(path))[0]
import_name = (
f"{super_import_name}.{modname}" if super_import_name else modname
)
import_name = f"{super_import_name}.{modname}" if super_import_name else modname
if ispkg:
import_path = os.path.join(path, "__init__.py")
search_paths = {"submodule_search_locations": []}
Expand All @@ -208,28 +226,45 @@ def load_subcmd_file(path: str, super_import_name: Optional[str] = None, ispkg:
load_cmd_from_module(module, cmdname, ignore_missing_entry=ignore_missing_cli)


def load_cmd_from_module(module: types.ModuleType, cmdname: Optional[str] = None, *, ignore_missing_entry: bool = True):
def load_cmd_from_module(
module: types.ModuleType,
cmdname: Optional[str] = None,
*,
ignore_missing_entry: bool = True,
):
"""Load the sub-command `cli` from the module."""
try:
source_cli: click.MultiCommand = getattr(module, "cli")
if not cmdname:
cmdname = source_cli.name
cli.add_command(source_cli, cmdname)
except AttributeError:
if not ignore_missing_entry:
raise click.ClickException("Module does not define a function named `cli()`")
raise click.ClickException(
"Module does not define a function named `cli()`"
)


# Load extra commands from the rest of the circfirm.cli subpackage
cli_pkg_path = os.path.dirname(os.path.abspath(__file__))
cli_pkg_name = "circfirm.cli"
load_subcmd_folder(cli_pkg_path, super_import_name=cli_pkg_name, filenames_as_commands=True, ignore_missing_cli=False)
load_subcmd_folder(
cli_pkg_path,
super_import_name=cli_pkg_name,
filenames_as_commands=True,
ignore_missing_cli=False,
)

# Load downloaded plugins
settings = get_settings()
downloaded_modules: List[str] = settings["plugins"]["downloaded"]
for downloaded_module in downloaded_modules:
module = importlib.import_module(downloaded_module)
try:
module = importlib.import_module(downloaded_module)
except ModuleNotFoundError:
maybe_warn(f"Could not load plugin {downloaded_module}, skipping")
continue
load_cmd_from_module(module, None)

# Load local plugins
load_subcmd_folder(circfirm.LOCAL_PLUGINS, ignore_missing_cli=True)
load_subcmd_folder(circfirm.LOCAL_PLUGINS)
100 changes: 41 additions & 59 deletions circfirm/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,27 @@
import circfirm.startup


def _get_config_settings(plugin: str = "") -> Tuple[Dict[str, Any], Dict[str, Any]]:
def _get_config_settings(plugin: str = "") -> Dict[str, Any]:
try:
orig_settings = (
circfirm.plugins.get_settings(plugin)
if plugin
else circfirm.cli.get_settings()
)
target_schema = (
circfirm.plugins.get_settings_schema(plugin)
if plugin
else circfirm.cli.get_settings_schema()
)
return orig_settings, target_schema
return orig_settings
except FileNotFoundError:
raise click.ClickException(
"Unable to find the requested configuration settings file"
)


def _get_settings_filepath(plugin: str) -> Tuple[str, str]:
def _get_settings_filepath(plugin: str) -> str:
if plugin:
parent_folder = circfirm.plugins.get_plugin_settings_path(plugin)
target_settings_file = os.path.join(parent_folder, "settings.yaml")
target_schema_file = os.path.join(parent_folder, "settings.schema.yaml")
else:
target_settings_file = circfirm.SETTINGS_FILE
target_schema_file = circfirm.SETTINGS_SCHEMA_FILE
return target_settings_file, target_schema_file
return target_settings_file


@click.group()
Expand All @@ -64,7 +57,7 @@ def cli():
def config_view(plugin: str, setting: str) -> None:
"""View a config setting."""
# Get the settings, show all settings if no specific on is specified
settings, _ = _get_config_settings(plugin)
settings = _get_config_settings(plugin)
if setting == "all":
click.echo(yaml.safe_dump(settings, indent=4), nl=False)
return
Expand Down Expand Up @@ -101,7 +94,7 @@ def config_edit(
) -> None:
"""Update a config setting."""
# Get the settings, use another reference to parse
orig_settings, target_schema = _get_config_settings(plugin)
orig_settings = _get_config_settings(plugin)
target_setting = orig_settings

config_args = setting.split(".")
Expand All @@ -110,8 +103,7 @@ def config_edit(
try:
for extra_arg in config_args[:-1]:
target_setting = target_setting[extra_arg]
target_schema = target_schema[extra_arg]
target_value = target_schema[config_args[-1]]
target_value = target_setting[config_args[-1]]
target_value_type = type(target_value)
if not circfirm.config.is_node_scalar(target_value):
raise click.ClickException(
Expand Down Expand Up @@ -139,7 +131,7 @@ def config_edit(
)

# Write the settings back to the file
target_file, _ = _get_settings_filepath(plugin)
target_file = _get_settings_filepath(plugin)
with open(target_file, mode="w", encoding="utf-8") as yamlfile:
yaml.safe_dump(orig_settings, yamlfile)

Expand All @@ -153,7 +145,7 @@ def config_edit(
def config_add(plugin: str, setting: str, value: str) -> None:
"""Add a value to a list."""
# Get the settings, use another reference to parse
orig_settings, target_schema = _get_config_settings(plugin)
orig_settings = _get_config_settings(plugin)
target_setting = orig_settings

config_args = setting.split(".")
Expand All @@ -162,23 +154,18 @@ def config_add(plugin: str, setting: str, value: str) -> None:
try:
for extra_arg in config_args[:-1]:
target_setting = target_setting[extra_arg]
target_schema = target_schema[extra_arg]
editing_list: List = target_setting[config_args[-1]]
target_type = type(editing_list)
if target_type in (dict, str, int, float, bool):
raise click.ClickException("Cannot add items to this setting")
schema_list: List = target_schema[config_args[-1]]
element_type = type(schema_list[0])
target_list: List = target_setting[config_args[-1]]
try:
element_type = type(target_list[0])
except IndexError:
element_type = str
if element_type == dict:
raise click.ClickException("Cannot add items to lists of dictionaries")
if element_type == bool:
editing_list.append(circfirm.config.cast_input_to_bool(value))
elif element_type == int:
editing_list.append(circfirm.config.cast_input_to_int(value))
elif element_type == float:
editing_list.append(circfirm.config.cast_input_to_float(value))
else:
editing_list.append(value)
editing_list.append(value)
except KeyError:
raise click.ClickException(f"Setting {setting} does not exist")
except ValueError:
Expand All @@ -187,7 +174,7 @@ def config_add(plugin: str, setting: str, value: str) -> None:
)

# Write the settings back to the file
target_file, _ = _get_settings_filepath(plugin)
target_file = _get_settings_filepath(plugin)
with open(target_file, mode="w", encoding="utf-8") as yamlfile:
yaml.safe_dump(orig_settings, yamlfile)

Expand All @@ -201,7 +188,7 @@ def config_add(plugin: str, setting: str, value: str) -> None:
def config_remove(plugin: str, setting: str, value: str) -> None:
"""Remove a value from a list."""
# Get the settings, use another reference to parse
orig_settings, target_schema = _get_config_settings(plugin)
orig_settings = _get_config_settings(plugin)
target_setting = orig_settings

config_args = setting.split(".")
Expand All @@ -210,32 +197,28 @@ def config_remove(plugin: str, setting: str, value: str) -> None:
try:
for extra_arg in config_args[:-1]:
target_setting = target_setting[extra_arg]
target_schema = target_schema[extra_arg]
editing_list: List = target_setting[config_args[-1]]
target_type = type(editing_list)
if target_type in (dict, str, int, float, bool):
raise click.ClickException("Cannot remove items from this setting")
schema_list: List = target_schema[config_args[-1]]
element_type = type(schema_list[0])
target_list: List = target_setting[config_args[-1]]
try:
element_type = type(target_list[0])
except IndexError:
element_type = str
if element_type == dict:
raise click.ClickException(
"Cannot remove items from dictionaries via the command line"
"Cannot remove dictionary items from lists via the command line"
)
if element_type == bool:
editing_list.remove(circfirm.config.cast_input_to_bool(value))
elif element_type == int:
editing_list.remove(circfirm.config.cast_input_to_int(value))
elif element_type == float:
editing_list.remove(circfirm.config.cast_input_to_float(value))
else:
if value in editing_list:
editing_list.remove(value)
except KeyError:
raise click.ClickException(f"Setting {setting} does not exist")
except ValueError:
raise click.ClickException(f"{value} was not located in the given list")

# Write the settings back to the file
target_file, _ = _get_settings_filepath(plugin)
target_file = _get_settings_filepath(plugin)
with open(target_file, mode="w", encoding="utf-8") as yamlfile:
yaml.safe_dump(orig_settings, yamlfile)

Expand All @@ -246,10 +229,10 @@ def config_remove(plugin: str, setting: str, value: str) -> None:
)
def config_editor(plugin: str) -> None: # pragma: no cover
"""Edit the configuration file in an editor."""
settings, _ = _get_config_settings()
settings = _get_config_settings()
editor = settings["editor"]
editor = editor if editor else None
target_file, _ = _get_settings_filepath(plugin)
target_file = _get_settings_filepath(plugin)
try:
click.edit(filename=target_file, editor=editor)
except click.ClickException as err:
Expand All @@ -264,7 +247,7 @@ def config_editor(plugin: str) -> None: # pragma: no cover
)
def config_path(plugin: str) -> None:
"""Print the path where the configuration file is stored."""
target_file, _ = _get_settings_filepath(plugin)
target_file = _get_settings_filepath(plugin)
if os.path.exists(target_file):
click.echo(target_file)
else:
Expand All @@ -277,17 +260,16 @@ def config_path(plugin: str) -> None:
)
def config_reset(plugin: str) -> None:
"""Reset the configuration file with the provided template."""
target_settings_files = _get_settings_filepath(plugin)
for target_settings_file in target_settings_files:
os.remove(target_settings_file)
try:
src, dest = [
(src, dest)
for src, dest in circfirm.startup.TEMPLATE_LIST
if dest == target_settings_file
][0]
except IndexError:
raise click.ClickException(
"The default configuration file could not be located, reset not performed"
)
circfirm.startup.ensure_template(src, dest)
target_settings_file = _get_settings_filepath(plugin)
os.remove(target_settings_file)
try:
src, dest = [
(src, dest)
for src, dest in circfirm.startup.TEMPLATE_LIST
if dest == target_settings_file
][0]
except IndexError:
raise click.ClickException(
"The default configuration file could not be located, reset not performed"
)
circfirm.startup.ensure_template(src, dest)
Loading

0 comments on commit 569dae8

Please sign in to comment.