From 7b33c12b72f26ae5d1ac2eb849e14fd331a69981 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Tue, 14 Jan 2025 23:18:02 -0500 Subject: [PATCH] Temp --- circfirm/backend/config.py | 9 ++- circfirm/cli/__init__.py | 6 +- circfirm/cli/config.py | 29 +++++--- circfirm/cli/path.py | 35 ++++++++++ circfirm/plugins/__init__.py | 19 ++++-- circfirm/templates/setttings.schema.yaml | 10 +++ docs/commands/path.rst | 44 ++++++++++++ docs/index.rst | 7 +- docs/plugins/create.rst | 25 +++++++ docs/plugins/distribute.rst | 0 docs/plugins/example.rst | 6 ++ docs/plugins/intro.rst | 85 ++++++++++++++++++++++++ 12 files changed, 252 insertions(+), 23 deletions(-) create mode 100644 circfirm/cli/path.py create mode 100644 circfirm/templates/setttings.schema.yaml create mode 100644 docs/commands/path.rst create mode 100644 docs/plugins/create.rst create mode 100644 docs/plugins/distribute.rst create mode 100644 docs/plugins/example.rst create mode 100644 docs/plugins/intro.rst diff --git a/circfirm/backend/config.py b/circfirm/backend/config.py index ecfaa05..60018e4 100644 --- a/circfirm/backend/config.py +++ b/circfirm/backend/config.py @@ -7,7 +7,7 @@ Author(s): Alec Delaney """ -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Union, Tuple import yaml from typing_extensions import TypeAlias @@ -21,10 +21,13 @@ _VALID_FALSE_OPTIONS = ("n", "no", "false", "0") -def get_config_settings(settings_filepath: str) -> Any: +def get_config_settings(settings_filepath: str) -> Tuple[Any, Any]: """Get the contents of a configuration settings file.""" with open(settings_filepath, encoding="utf-8") as yamlfile: - return yaml.safe_load(yamlfile) + settings = yaml.safe_load(yamlfile) + with open(settings_filepath, encoding="utf-8") as yamlfile: + types = yaml.safe_load(yamlfile) + return settings, types def is_node_scalar(value: _YAML_NODE_T) -> bool: diff --git a/circfirm/cli/__init__.py b/circfirm/cli/__init__.py index 28e25c6..50ba2f8 100644 --- a/circfirm/cli/__init__.py +++ b/circfirm/cli/__init__.py @@ -36,7 +36,7 @@ def cli() -> None: def _maybe_output(msg: str, setting_path: Iterable[str], invert: bool = False) -> None: """Output text based on the configurable settings.""" - settings = get_settings() + settings, _ = get_settings() for path in setting_path: settings = settings[path] settings = not settings if invert else settings @@ -166,7 +166,7 @@ def announce_and_await( raise err -def get_settings() -> Dict[str, Any]: +def get_settings() -> Tuple[Dict[str, Any], Dict[str, Any]]: """Get the contents of the settings file.""" return circfirm.backend.config.get_config_settings(circfirm.SETTINGS_FILE) @@ -256,7 +256,7 @@ def load_cmd_from_module( ) # Load downloaded plugins -settings = get_settings() +settings, _ = get_settings() downloaded_modules: List[str] = settings["plugins"]["downloaded"] for downloaded_module in downloaded_modules: try: diff --git a/circfirm/cli/config.py b/circfirm/cli/config.py index 717d2a2..37d9096 100644 --- a/circfirm/cli/config.py +++ b/circfirm/cli/config.py @@ -8,6 +8,8 @@ Author(s): Alec Delaney """ +# TODO: Convert to using schema file for settings + import os from typing import Any, Dict, List, Tuple @@ -21,7 +23,7 @@ import circfirm.startup -def _get_config_settings(plugin: str = "") -> Dict[str, Any]: +def _get_config_settings(plugin: str = "") -> Tuple[Dict[str, Any], Dict[str, Any]]: try: return ( circfirm.plugins.get_settings(plugin) @@ -48,6 +50,7 @@ def cli(): """View and update the configuration settings for the circfirm CLI.""" +# TODO: Modify for schema update @cli.command(name="view") @click.argument("setting", default="all") @click.option( @@ -56,7 +59,7 @@ def cli(): def config_view(setting: str, plugin: 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 @@ -80,6 +83,7 @@ def config_view(setting: str, plugin: str) -> None: click.echo(value) +# TODO: Modify for schema update @cli.command(name="edit") @click.argument("setting") @click.argument("value") @@ -93,7 +97,7 @@ def config_edit( ) -> None: """Update a config setting.""" # Get the settings, use another reference to parse - orig_settings = _get_config_settings(plugin) + orig_settings, _ = _get_config_settings(plugin) target_setting = orig_settings config_args = setting.split(".") @@ -127,6 +131,7 @@ def config_edit( yaml.safe_dump(orig_settings, yamlfile) +# TODO: Modify for schema update @cli.command(name="add") @click.argument("setting") @click.argument("value") @@ -136,22 +141,24 @@ def config_edit( def config_add(setting: str, value: str, plugin: str) -> None: """Add a value to a list.""" # Get the settings, use another reference to parse - orig_settings = _get_config_settings(plugin) + orig_settings, types_settings = _get_config_settings(plugin) target_setting = orig_settings + target_type = types_settings config_args = setting.split(".") # Attempt to parse for the specified config setting and add it try: - for extra_arg in config_args[:-1]: + for extra_arg in config_args[:-1]: # TODO: Explore the use of helper functions here, since reference is basically a pointer target_setting = target_setting[extra_arg] + target_type = target_type[extra_arg] editing_list: List = target_setting[config_args[-1]] - target_type = type(editing_list) - if target_type in (dict, str, int, float, bool): + # TODO: Convert target_type to actual type using new helper function + if type(editing_list) in (dict, str, int, float, bool): # TODO: Should be isinstance raise click.ClickException("Cannot add items to this setting") target_list: List = target_setting[config_args[-1]] try: - element_type = type(target_list[0]) + element_type = type(target_list[0]) # TODO: Should use type from schema except IndexError: element_type = str if element_type == dict: @@ -166,6 +173,7 @@ def config_add(setting: str, value: str, plugin: str) -> None: yaml.safe_dump(orig_settings, yamlfile) +# TODO: Modify for schema update @cli.command(name="remove") @click.argument("setting") @click.argument("value") @@ -175,7 +183,7 @@ def config_add(setting: str, value: str, plugin: str) -> None: def config_remove(setting: str, value: str, plugin: str) -> None: """Remove a value from a list.""" # Get the settings, use another reference to parse - orig_settings = _get_config_settings(plugin) + orig_settings, _ = _get_config_settings(plugin) target_setting = orig_settings config_args = setting.split(".") @@ -207,13 +215,14 @@ def config_remove(setting: str, value: str, plugin: str) -> None: yaml.safe_dump(orig_settings, yamlfile) +# TODO: Modify for schema update @cli.command(name="editor") @click.option( "-p", "--plugin", default="", help="Configure a plugin instead of circfirm" ) 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) diff --git a/circfirm/cli/path.py b/circfirm/cli/path.py new file mode 100644 index 0000000..5386b8b --- /dev/null +++ b/circfirm/cli/path.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2024 Alec Delaney, for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +"""CLI functionality for the paths subcommand. + +Author(s): Alec Delaney +""" + +import click + +import circfirm + + +@click.group() +def cli() -> None: + """See filepaths for files and folders used by circfirm.""" + + +@cli.command(name="config") +def path_config() -> None: + """Get the configuration settings filepath.""" + click.echo(circfirm.SETTINGS_FILE) + + +@cli.command(name="local-plugins") +def path_local_plugins() -> None: + """Get the local plugins folder filepath.""" + click.echo(circfirm.LOCAL_PLUGINS) + + +@cli.command(name="archive") +def path_archive() -> None: + """Get the firmware archive folder filepath.""" + click.echo(circfirm.UF2_ARCHIVE) diff --git a/circfirm/plugins/__init__.py b/circfirm/plugins/__init__.py index a4ebad8..b1e0381 100644 --- a/circfirm/plugins/__init__.py +++ b/circfirm/plugins/__init__.py @@ -9,7 +9,7 @@ import os import shutil -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, Tuple import yaml @@ -39,9 +39,14 @@ def ensure_plugin_settings(name: str, settings_path: str) -> None: plugin_settings_folder = os.path.join(circfirm.PLUGIN_SETTINGS, name) if not os.path.exists(plugin_settings_folder): os.mkdir(plugin_settings_folder) - template_args = settings_path, os.path.join(plugin_settings_folder, "settings.yaml") - circfirm.startup.specify_template(*template_args) - circfirm.startup.ensure_template(*template_args) + + settings_template_args = settings_path, os.path.join(plugin_settings_folder, "settings.yaml") + circfirm.startup.specify_template(*settings_template_args) + circfirm.startup.ensure_template(*settings_template_args) + + types_template_args = settings_path, os.path.join(plugin_settings_folder, "settings.schema.yaml") + circfirm.startup.specify_template(*types_template_args) + circfirm.startup.ensure_template(*types_template_args) def _get_settings_file(name: str, extension: str) -> Optional[str]: @@ -52,9 +57,11 @@ def _get_settings_file(name: str, extension: str) -> Optional[str]: return yaml.safe_load(setfile) -def get_settings(name: str) -> Dict[str, Any]: +def get_settings(name: str) -> Tuple[Dict[str, Any], Dict[str, Any]]: """Get the contents of the settings file.""" - return _get_settings_file(name, "yaml") + settings = _get_settings_file(name, "yaml") + types = _get_settings_file(name, "schema.yaml") + return settings, types def get_plugin_settings_path(name: str) -> str: diff --git a/circfirm/templates/setttings.schema.yaml b/circfirm/templates/setttings.schema.yaml new file mode 100644 index 0000000..b325432 --- /dev/null +++ b/circfirm/templates/setttings.schema.yaml @@ -0,0 +1,10 @@ +token: + github: str +output: + supporting: + silence: bool + warning: + silence: bool +editor: str +plugins: + downloaded: [str] diff --git a/docs/commands/path.rst b/docs/commands/path.rst new file mode 100644 index 0000000..af7a362 --- /dev/null +++ b/docs/commands/path.rst @@ -0,0 +1,44 @@ +.. + SPDX-FileCopyrightText: 2024 Alec Delaney, for Adafruit Industries + SPDX-License-Identifier: MIT + +Viewing Important Filespaths +============================ + +You can see important filepaths used by ``circfirm`` using ``circfirm path``. + +See ``circfirm path --help`` and ``circfirm path [command] --help`` for more information on commands. + +Configuration Settings +---------------------- + +You can get the filepath of ``circfirm``'s configuration settings using ``circfirm path config``. + +.. note:: + + This is identical to the response given by ``circfirm config path``. + +.. code-block:: shell + + # Get the configuration settings filepath + circfirm path config + +UF2 Archive +----------- + +You can get the filepath of the UF2 archive folder using ``circfirm path archive``. + +.. code-block:: shell + + # Get the UF2 archive folder filepath + circifrm path archive + +Local Plugins Folder +-------------------- + +You can get the filepath of the local plugins folder using ``circfirm path local-plugins``. + +.. code-block:: shell + + # Get the local plugins folder filepath + circfirm path local-plugins diff --git a/docs/index.rst b/docs/index.rst index c959e3a..ea8bee9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,7 @@ commands/cache commands/query commands/config + commands/path .. toctree:: :maxdepth: 2 @@ -36,9 +37,13 @@ :caption: Plugins :hidden: + plugins/intro + plugins/create + plugins/example + .. toctree:: :maxdepth: 2 - :caption: Examples + :caption: Example Scripts :hidden: examples/update_many diff --git a/docs/plugins/create.rst b/docs/plugins/create.rst new file mode 100644 index 0000000..b6e814a --- /dev/null +++ b/docs/plugins/create.rst @@ -0,0 +1,25 @@ +.. + SPDX-FileCopyrightText: 2024 Alec Delaney, for Adafruit Industries + SPDX-License-Identifier: MIT + +Creating Plugins +================ + +Creating a plugin is idnetical to creating other `click `_-based +command line tools. When ``circfirm`` is called from the command line, these command line tools +are loaded and added as commands. + +Local Plugins +------------- + +The easiest way to get started with creating plugins is to create a local plugin. Local plugins +reside in a designated folder next to where + +Downloadable Plugins +-------------------- + +Adding a Configuration File +--------------------------- + +Important Notes +--------------- diff --git a/docs/plugins/distribute.rst b/docs/plugins/distribute.rst new file mode 100644 index 0000000..e69de29 diff --git a/docs/plugins/example.rst b/docs/plugins/example.rst new file mode 100644 index 0000000..92a2ff7 --- /dev/null +++ b/docs/plugins/example.rst @@ -0,0 +1,6 @@ +.. + SPDX-FileCopyrightText: 2024 Alec Delaney, for Adafruit Industries + SPDX-License-Identifier: MIT + +Example Plugin +============== diff --git a/docs/plugins/intro.rst b/docs/plugins/intro.rst new file mode 100644 index 0000000..9ad9223 --- /dev/null +++ b/docs/plugins/intro.rst @@ -0,0 +1,85 @@ +.. + SPDX-FileCopyrightText: 2024 Alec Delaney, for Adafruit Industries + SPDX-License-Identifier: MIT + +Introduction to Plugins +======================= + +``circfirm`` can be extended through the use of its plugin system. Plugins allow you +to extend the CLI's capabilities beyond typical use cases and provided additional +functionalities you may want to use. + +Below are general guidelines for downloading and configuring plugins, but see the +plugin's documentation for additional details. + +Installing Plugins +------------------ + +Installing a plugin depends on how ``circfirm`` itself was installed. + +pipx +^^^^ + +If you installed ``circfirm`` via ``pipx``, you will need to "inject" the plugin +into the same virtual environment as ``circfirm``. This can typically be +achieved used the ``inject`` command: + +.. code-block:: shell + + # Inject circfirm-plugin-name for circfirm + pipx inject circfirm circfirm-plugin-name + +pip +^^^ + +If you installed ``circfirm`` via ``pip``, you can simply using the ``install`` +command to install the plugin into the same environment as ``circfirm``: + +.. code-block:: shell + + # Install circfirm-plugin-name + pip install circfirm-plugin-name + +Activating Plugins +------------------ + +Activating a downloaded plugin can be done by adding an entry to the ``plugins.downloaded`` +confirguration for ``circfirm``: + +.. code-block:: shell + + # Activate plugin_name + circfirm config add plugins.downloaded plugin_name + +Note that the plugin name may not exactly match the download name of the plugin. The name +added to ``plugins.downloaded`` should be a valid Python module import name - in fact, this +is exactly what is happening behind the scenes! Check with the plugin provider for exact +details on what the plugin name is (as opposed to the download name). + +.. note:: + + This step is only needed if you downloaded the plugin. Locally created plugins do not + require activation. + +Changing Configuration Settings +------------------------------- + +Some plugins provide configuration files that can customize behavior similar to +``circfirm``. You can view, edit, and otherwise interact with these settings by +using the ``--plugin`` option with the ``config`` command and it's various +sub-commands: + +.. code-block:: shell + + # View the settings for plugin_name + circfirm config view --plugin plugin_name + + # Edit a setting for plugin_name + circfirm config edit --plugin plugin_name setting value + + # Reset the settings for plugin_name + circfirm config reset --plugin plugin_name + +Note while, conventionally, a plugin will have a single configuration file, sharing the same +name as the plugin, this may not be the case - it may use a different name or provide more +than one configuration file. Please check with the plugin provider for additional details.