From 54d4662107bd652508d73655488e7ce30c7ed7bb Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Fri, 26 May 2023 19:05:59 +0200 Subject: [PATCH] feat(tools): idf_monitor: support for loadable hint provider modules Currently hints are supported based on hints.yml only, which may be limiting for some use cases. This introduces a generic plugin approach, which allows to implement hint module that doesn't require entry in hints.yml. Such module has the full command output available and it is not limited to a single regex in hints.yml. Note that regex in hint.yml expects the output concatenated into a single line, but hint modules are getting the output unchanged. Signed-off-by: Frantisek Hrbata --- tools/idf_py_actions/tools.py | 49 ++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/tools/idf_py_actions/tools.py b/tools/idf_py_actions/tools.py index e823303390e..c90da90fa9d 100644 --- a/tools/idf_py_actions/tools.py +++ b/tools/idf_py_actions/tools.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import asyncio +import importlib import json import os import re @@ -8,6 +9,7 @@ import sys from asyncio.subprocess import Process from io import open +from pkgutil import iter_modules from types import FunctionType from typing import Any, Dict, Generator, List, Match, Optional, TextIO, Tuple, Union @@ -165,16 +167,50 @@ def debug_print_idf_version() -> None: print_warning(f'ESP-IDF {idf_version() or "version unknown"}') -def load_hints() -> Any: +def load_hints() -> Dict: """Helper function to load hints yml file""" - with open(os.path.join(os.path.dirname(__file__), 'hints.yml'), 'r') as file: - hints = yaml.safe_load(file) + hints: Dict = { + 'yml': [], + 'modules': [] + } + + current_module_dir = os.path.dirname(__file__) + with open(os.path.join(current_module_dir, 'hints.yml'), 'r') as file: + hints['yml'] = yaml.safe_load(file) + + hint_modules_dir = os.path.join(current_module_dir, 'hint_modules') + if not os.path.exists(hint_modules_dir): + return hints + + sys.path.append(hint_modules_dir) + for _, name, _ in iter_modules([hint_modules_dir]): + # Import modules for hint processing and add list of their 'generate_hint' functions into hint dict. + # If the module doesn't have the function 'generate_hint', it will raise an exception + try: + hints['modules'].append(getattr(importlib.import_module(name), 'generate_hint')) + except ModuleNotFoundError: + red_print(f'Failed to import "{name}" from "{hint_modules_dir}" as a module') + raise SystemExit(1) + except AttributeError: + red_print('Module "{}" does not have function generate_hint.'.format(name)) + raise SystemExit(1) + return hints -def generate_hints_buffer(output: str, hints: list) -> Generator: +def generate_hints_buffer(output: str, hints: Dict) -> Generator: """Helper function to process hints within a string buffer""" - for hint in hints: + # Call modules for possible hints with unchanged output. Note that + # hints in hints.yml expect new line trimmed, but modules should + # get the output unchanged. Please see tools/idf_py_actions/hints.yml + for generate_hint in hints['modules']: + module_hint = generate_hint(output) + if module_hint: + yield module_hint + + # hints expect new lines trimmed + output = ' '.join(line.strip() for line in output.splitlines() if line.strip()) + for hint in hints['yml']: variables_list = hint.get('variables') hint_list, hint_vars, re_vars = [], [], [] match: Optional[Match[str]] = None @@ -214,8 +250,7 @@ def generate_hints(*filenames: str) -> Generator: hints = load_hints() for file_name in filenames: with open(file_name, 'r') as file: - output = ' '.join(line.strip() for line in file if line.strip()) - yield from generate_hints_buffer(output, hints) + yield from generate_hints_buffer(file.read(), hints) def fit_text_in_terminal(out: str) -> str: