From 79924f158a9066d2c9157aeffe51e88761060d0d Mon Sep 17 00:00:00 2001 From: ussserrr Date: Tue, 5 May 2020 23:03:50 +0300 Subject: [PATCH] prepare release 1.30 --- CHANGELOG.md | 42 +++++++++++++++++++++++++++++++++++++++- README.md | 13 ++++++++----- TODO.md | 44 ++++-------------------------------------- stm32pio/app.py | 4 ++-- stm32pio/settings.py | 4 ++-- stm32pio_gui/README.md | 20 +++++++++++++++++-- stm32pio_gui/app.py | 24 ++++++++++++++--------- 7 files changed, 90 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ba1fcb..54a8f7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -231,4 +231,44 @@ ## ver. 1.21 (19.04.20) - Fixed: GUI. All resorce paths are now reliably treated both for QML and Python - - Changed: README installation process actualized \ No newline at end of file + - Changed: README installation process actualized + +## ver. 1.30 (05.05.20) + - New: `examples` folder (currently, only an embedding one (updated and moved from the wiki page)) + - New: `docs` folder with some useful internal descriptions (currently, only a logging schematic (with sources)) + - New: issues guide for GitHub (OS, content of the config, project tree, enable verbose, etc.) + - New: GUI. Show the app version in "About" dialog + - New: GUI. Handle a theoretical app loading error + - New: GUI. Notify a user that the "board" parameter is empty + - New: GUI. The app can be started from CLI + - New: GUI. `ProjectListItem.fromStartup` property + - New: GUI. Expose projects' `config` to QML + - New: Tests. Preserving user files and folders on regeneration + - New: Tests. 'verbose' and 'non-verbose' tests as `subTest` (also `should_log_error_...`) + - New: Inform a user that given parameters have overridden the config ones + - Fixed: GUI. `TypeError: Cannot read property 'actionRunning' of null` (deconstruction order) (on project deletion only) + - Fixed: GUI. The app now can handle as many projects as needed (use QML `DelegateModel` to store state in the `ListView` delegate) + - Fixed: #13 (new parsing algo to analyze the CubeMX output) + - Changed: improved `typing` annotations + - Changed: wrap imports into `try...except` + - Changed: new README logo, add sources (draw.io) + - Changed: GUI. Icons instead of a text for "Clean", "Open editor" + - Changed: GUI. Gray out "stage" line in all projects except current + - Changed: GUI. 2 types of logging formatters for 2 verbosity levels + - Changed: GUI. More general `goToProject` signal instead of `duplicateFound` + - Changed: GUI. Projects list is now saves to `Settings` in a separate thread using `QThreadPool` and `saveInSettings()` method + - Changed: GUI. `ProjectsList.each_project_is_duplicate_of` generator + - Changed: GUI. Optimized project' `state` handling + - Changed: GUI. Insert board ID from config, if there is one, focus on that input field by default + - Changed: logging mechanics is remade from scratch: + - add `stm32pio.app.setup_logging()`, `should_setup_logging` argument fo `stm32pio.app.setup_main()`. This also fixes annoying logging errors on testing because the loggers interfere with each other + - `stm32pio.util.ProjectLoggerAdapter()` subclass as an individual logger for every project + - add `stm32pio.util.log_current_exception()` + - get rid of `log_record_factory` substitution + - add `stm32pio.util.Verbosity` entity (enum). Acts like an additional degree of freedom for the logging setup + - rewritten `stm32pio.util.DispatchingFormatter` + - GUI. New `BuffersDispatchingHandler()` class + - and some others (see block schema) + - Changed: better parameters and configs merging + - Changed: make `platformio_ini_is_patched` a property instead of function + - Changed: improved in-code docs diff --git a/README.md b/README.md index f6acf1d..669d78c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ The [GUI version](/stm32pio_gui) is available, too. > - [Requirements](#requirements) > - [Installation](#installation) > - [Usage](#usage) +> - [GUI from CLI](#gui-from-cli) > - [Project patching](#project-patching) > - [Embedding](#embedding) > - [Example](#example) @@ -94,6 +95,12 @@ $ python app.py --help ``` to see help on available commands. Find the copy of its output on the [project wiki](https://github.com/ussserrr/stm32pio/wiki/stm32pio-help) page, also. +### GUI from CLI +You can start the [GUI version](/stm32pio_gui) using `gui` subcommand and pass some of the arguments to it: +```shell script +$ stm32pio gui -d ./sample-project -b discovery_f4 +``` + ### Project patching Note, that the patch operation (which takes the CubeMX code and PlatformIO project to the compliance) erases all the comments (lines starting with `;`) inside the `platformio.ini` file. They are not required anyway, in general, but if you need them for some reason please consider to save the information somewhere else. @@ -102,7 +109,7 @@ For those who want to modify the patch (default one is at [`settings.py`](/stm32 ### Embedding -You can also use stm32pio as an ordinary Python package and embed it in your own application. Find the minimal example at the [project wiki](https://github.com/ussserrr/stm32pio/wiki/Embedding-example) page to see some possible ways of implementing this. Basically, you need to import `stm32pio.lib` module (where the main `Stm32pio` class resides), (optionally) set up a logger and you are good to go. If you prefer higher-level API similar to the CLI version, use `main()` function in `app.py` passing the same CLI arguments to it (except the actual script name). Also, take a look at the CLI ([`app.py`](/stm32pio/app.py)) or GUI versions. +You can also use stm32pio as an ordinary Python package and embed it in your own application. Find the minimal example at the [examples](/examples) to see some possible ways of implementing this. Basically, you need to import `stm32pio.lib` module (where the main `Stm32pio` class resides), (optionally) set up a logger and you are good to go. If you prefer higher-level API similar to the CLI version, use `main()` function in `app.py` passing the same CLI arguments to it (except the actual script name). Also, take a look at the CLI ([`app.py`](/stm32pio/app.py)) or GUI versions. ## Example @@ -138,10 +145,6 @@ There are some tests in file [`test.py`](/stm32pio/tests/test.py) (based on the ```shell script stm32pio-repo/ $ python -m unittest -b -v ``` -or -```shell script -stm32pio-repo/ $ python -m stm32pio.tests.test -b -v -``` to test the app. It uses STM32F0 framework to generate and build a code from the test [`stm32pio-test-project.ioc`](/stm32pio-test-project/stm32pio-test-project.ioc) project file. Please make sure that the test project folder is clean (i.e. contains only an .ioc file) before running the test otherwise it can lead to some cases failing. Tests automatically create temporary directory (using `tempfile` Python standard module) where all actions are performed. For the specific test suite or case you can use diff --git a/TODO.md b/TODO.md index 82a515e..07ca4b3 100644 --- a/TODO.md +++ b/TODO.md @@ -1,95 +1,59 @@ # TODOs ## Business logic, general features - - [x] Issues guide for the GitHub (OS, content of the config, project tree, enable verbose) - [ ] GitHub CHANGELOG - separate New, Fixed, Changed into paragraphs - [ ] Middleware support (FreeRTOS, etc.) - [ ] Arduino framework support (needs research to check if it is possible) - [ ] Create VSCode plugin - [ ] UML diagrams (core, GUI back- and front-ends, thread flows, events, etc.) - [ ] CI is possible (Arch's AUR has the STM32CubeMX package, also there is a direct link). Deploy Docker one in Azure Pipelines, basic at Travis CI - - [x] We can hide almost all logging setup behind the scene. Think of it as of a default implementation that can be changed though. Also, can make a `setup_logging()` function ## GUI version - [ ] Obtain boards on demand (not at the startup) - - [x] Expose version to the About dialog - - [x] Handle a theoretical initialization error (when boards are receiving) - - [x] Maybe `data()` `QAbstractListModel` method can be used instead of custom `get()` (no, at least without any tweaking (`QModelIndex` vs `int`)) - [ ] Can probably detect Ctrl and Shift clicks without moving the mouse first - - [x] Notify the user that the 'board' parameter is empty - [ ] Mac: sometimes auto turned off shift highlighting after action (hide-restore helps) - [ ] Some visual flaws when the window have got resized (e.g. 'Add' button position doesn't change until the list gets focus, 'Log' area crawls onto the status bar) - - [x] Gray out "stage" line in all projects except current - [ ] Tests (research approaches and patterns) - [ ] Remade the list item to use States, too. Probably, such properties need to be implemented: ``` state: { loaded, - + visitedAfterInstantiating, - + actionRunning, lastActionStatus, visitedAfterAction, ... } ``` - - [x] Test performance with a large number of projects in the model. First test was made: - 1. Some projects occasionally change `initLoading` by itself (probably Loader unloads the content) (hence cannot click on them, busy indicator appearing) - - Note: Delegates are instantiated as needed and may be destroyed at any time. They are parented to ListView's contentItem, not to the view itself. State should never be stored in a delegate. - - Use `id()` in `setInitInfo()`. Or do not use ListView at all (replace by Repeater, for example) as it can reset our "notifications" when reloading - 2. Some projects show OK even after its deletion (only the app restart helps) - [ ] Test with different timings - [ ] Divide on multiple modules (both Python and QML) - [ ] Implement other methods for Qt abstract models - [ ] Warning on 'Clean' action (maybe the window with a checkbox "Do not ask in the future" (QSettings parameter)) - - [x] 2 types of logging formatters for 2 verbosity levels - - [x] `TypeError: Cannot read property 'actionRunning' of null` (deconstruction order) (on project deletion only) - [ ] QML logging - pass to Python' `logging` and establish a similar format. Distinguish between `console.log()`, `console.error()` and so on - [ ] Lost log box autoscroll when manually scrolling between the actions - [ ] Crash on shutdown in Win and Linux (errors such as `[QML] CRITICAL QThread: Destroyed while thread is still running Process finished with exit code 1073741845`) - - [x] Start with a folder opened if it was provided on CLI (for example, `stm32pio_gui .`) - - [ ] Linux: - - Not a monospaced font in the log area + - [ ] Linux: Not a monospaced font in the log area - [ ] Temporarily pin projects with currently running actions to the top (and stay there on scrolling). See QML Package type - - [x] Icons instead of text for "Clean", "Open editor" ## Core library - - [x] https://github.com/ussserrr/stm32pio/issues/13 - [ ] when updating the project (`generate` command), check for boards match - - [x] Remove casts to string where we can use path-like objects (related to Python version as new ones receive path-like objects arguments while old ones aren't) + - [ ] Remove casts to string where we can use path-like objects (related to a Python version as new ones receives path-like objects arguments while old ones aren't) - [ ] We look for some snippets of strings in logs and output for the testing code but we hard-code them and this is not good, probably (e.g. 'DEBUG') - [ ] Store an initial content of the folder in .ini config and ignore it on clean-up process. Allow the user to modify such list (i.e. list of exclusion) in the config file. Mb some integration with `.gitignore` - [ ] at some point check for all tools (CubeMX, ...) to be present in the system (both CLI and GUI) (global `--check` command (as `--version`), also before execution of the full cycle (no sense to start if some tool doesn't exist)) - [ ] generate code docs (help user to understand an internal mechanics, e.g. for embedding). Can be uploaded to the GitHub Wiki - [ ] colored logs, maybe (breaks zero-dependency principle) - - [x] check logging work when embed stm32pio lib in a third-party stuff (no logging setup at all) - - [x] merge subprocess pipes to one where suitable (i.e. `stdout` and `stderr`) - - [x] redirect subprocess pipes to `DEVNULL` where suitable to suppress output (tests) - [ ] maybe migrate to async/await approach in the future (return some kind of a "remote controller" to control the running action) - [ ] `__init__`' `parameters` dict argument schema (Python 3.8 feature). - - [x] See https://docs.python.org/3/howto/logging-cookbook.html#context-info to maybe remade current logging schema (current is, perhaps, a cause of the strange error while testing (in the logging thread), also it modifies a global settings (log message factory)) - - [x] Test preserving user files and folders on regeneration and mb other operations - - [x] Move special formatters inside the library. It is an implementation detail actually that we use subprocesses and so on - [ ] Mb store the last occurred exception traceback in .ini file and show on some CLI command (so we don't necessarily need to turn on the verbose mode and repeat this action). And, in general, we should show the error reason right off - - [x] 'verbose' and 'non-verbose' tests as `subTest` (also `should_log_error_...`) - [ ] the lib sometimes raising, sometimes returning the code and it is not consistent. While the reasons behind such behavior are clear, would be great to always return a result code and raise the exceptions in the outer scope, if there is need to - [ ] check board (no sense to go further on 'new' if the board in config.ini is not correct) - - [x] check if `platformio.ini` config will be successfully parsed when there are interpolation and/or empty parameters - - [x] check if `.ioc` file is a text file on project initialization. Let `_find_ioc_file()` method to use explicitly provided file (useful for GUI). Maybe let user specify it via CLI - - [x] mb add CLI command for starting the GUI version (for example, `stm32pio --gui`) - [ ] test using virtualenv - [ ] test for different `.ioc` files (i.e. F0, F1, F4 and so on) as it is not the same actually - [ ] mb allow to use an arbitrary strings (arrays of str) to specify tools commands in stm32pio.ini (shell=True or a list of args (split a string)) - [ ] cache boards for a small interval of time - - [x] use warnings.warn() (https://docs.python.org/3/howto/logging.html#logging-basic-tutorial) - [ ] count another '-v' as '-v' for PlatformIO calls (slider in GUI settings window) - - [x] move GUI-related stuff from the `util.py` - - [x] typing (`Mapping` instead of `dict` and so on) - - [ ] check imports from 3rd-party code when the stm32pio installed from PyPI - - [x] Update embedding example (and maybe move to the repo itself) - [ ] Project' name (path) can be reused so cannot be used as a unique identifier but so is id(self)? Probably it is better to use a path (human-readable) - - [x] Inform user that given parameters have overridden the config - [ ] Analyze `.ioc` file for the wrong framework/parameters diff --git a/stm32pio/app.py b/stm32pio/app.py index d43184d..325a546 100755 --- a/stm32pio/app.py +++ b/stm32pio/app.py @@ -48,8 +48,8 @@ def parse_args(args: List[str]) -> Optional[argparse.Namespace]: help="create config .ini file to check and tweak parameters before proceeding") parser_new = subparsers.add_parser('new', help="generate CubeMX code, create PlatformIO project, glue them together") - parser_gui = subparsers.add_parser('gui', help="start the graphical version of the application. " - "All arguments will be passed forward") + parser_gui = subparsers.add_parser('gui', help="start the graphical version of the application. All arguments will " + "be passed forward, see its --help for more information") parser_generate = subparsers.add_parser('generate', help="generate CubeMX code only") parser_status = subparsers.add_parser('status', help="get the description of the current project state") parser_clean = subparsers.add_parser('clean', diff --git a/stm32pio/settings.py b/stm32pio/settings.py index 23aa0c2..4e66c3c 100644 --- a/stm32pio/settings.py +++ b/stm32pio/settings.py @@ -57,5 +57,5 @@ config_file_name = 'stm32pio.ini' # Longest name (not necessarily a method so a little bit tricky...) -log_fieldwidth_function = max([len(member) for member in dir(stm32pio.lib.Stm32pio)]) + 1 -# log_fieldwidth_function = 25 + 1 +# log_fieldwidth_function = max([len(member) for member in dir(stm32pio.lib.Stm32pio)]) + 1 +log_fieldwidth_function = 25 + 1 diff --git a/stm32pio_gui/README.md b/stm32pio_gui/README.md index c388d42..8fbbd14 100644 --- a/stm32pio_gui/README.md +++ b/stm32pio_gui/README.md @@ -5,7 +5,13 @@ The cross-platform GUI version of the stm32pio. It wraps the core library functionality into the Qt5-QML skin using the PySide2 (aka "Qt for Python" project) and adding the projects management feature allowing you to store and manipulate on multiple stm32pio projects at one place. -## Installation +## Table of contents +> - [Install and run](#install-and-run) +> - [Usage](#usage) +> - [Architecture notes](#architecture-notes) + + +## Install and run The app requires PySide2 5.12+ package (Qt 5.12 respectively). It is available in all major package managers including pip, apt, brew and so on. @@ -18,6 +24,10 @@ Then it can be started as ```shell script $ stm32pio_gui ``` +or +```shell script +$ stm32pio gui +``` from anywhere. If you have already installed the latest basic CLI version, this script and sources are already on your machine so you can reinstall it using the command above or just supplement the setup installing the PySide2 manually. If you rather want to launch completely from sources, it is possible like this: @@ -29,11 +39,15 @@ or stm32pio-repo/ $ python -m stm32pio_gui ``` +Either way, you can additionally specify the project (and board ID) to open with: +```shell script +$ stm32pio_gui -d ./sample-project -b discovery_f4 +``` ## Usage -Add a folder with the `.ioc` file to begin with. You can either use an "Add" button or drag-and-drop it into the main window, in te latter case you can add multiple projects simultaneously. If the project is empty the initialization screen will be shown to help in setup: +Add a folder with the `.ioc` file to begin with. You can either use an "Add" button or drag-and-drop it into the main window, in the latter case you can also have an ability to add multiple projects simultaneously. If the project is empty the initialization screen will be shown to help in setup: ![Init](screenshots/init_screen.png) @@ -51,6 +65,8 @@ Shift-click on it to execute the series. The picked actions will be framed with Add Ctrl to the mouse click to start the editor specified in the settings after the action. It can be combined with Shift as well. **Hint:** specify a `start` as an "Editor" command to open the folder in the new Explorer window under the Windows, `open` for the Finder on the macOS. +Currently, the project config (stm32pio.ini) is not live-reloaded so any changes you do to it will not be reflected until the next start. + ## Architecture notes diff --git a/stm32pio_gui/app.py b/stm32pio_gui/app.py index 6f82818..1487985 100644 --- a/stm32pio_gui/app.py +++ b/stm32pio_gui/app.py @@ -11,7 +11,7 @@ import threading import time import weakref -from typing import List, Callable, Optional, Any, Mapping, MutableMapping +from typing import List, Callable, Optional, Any, Mapping, MutableMapping, Iterator try: from PySide2.QtCore import QUrl, Property, QAbstractListModel, QModelIndex, QObject, Qt, Slot, Signal, QThread,\ @@ -459,15 +459,19 @@ def _saveInSettings(self) -> None: settings.endGroup() module_logger.info(f"{len(projects_to_save)} projects have been saved to Settings") # total amount - def saveInSettings(self): + def saveInSettings(self) -> None: """Spawn a thread to wait for all projects and save them in background""" w = Worker(self._saveInSettings, logger=module_logger) self.workers_pool.start(w) - def duplicates(self, path: str): + def each_project_is_duplicate_of(self, path: str) -> Iterator[bool]: """ - When we add a bunch of projects (or in general case, too) recently added ones can be not instantiated yet so we - cannot extract their properties and need to check before. samefile will raise, if the path doesn't exist, though + Returns generator yielding an answer to the question "Is current project is a duplicate of one represented by a + given path?" for every project in this model, one by one. + + Logic explanation: At a given time some projects (e.g., when we add a bunch of projects, recently added ones) + can be not instantiated yet so we cannot extract their project.path property and need to check before comparing. + In this case, simply evaluate strings. Also, samefile will even raise, if the given path doesn't exist. """ for list_item in self.projects: try: @@ -476,7 +480,7 @@ def duplicates(self, path: str): except OSError: yield False - def addListItem(self, path: str, list_item_kwargs: Mapping[str, Any] = None, go_to_this: bool = False): + def addListItem(self, path: str, list_item_kwargs: Mapping[str, Any] = None, go_to_this: bool = False) -> None: """ Create and append to the list tail a new ProjectListItem instance. This doesn't save in QSettings, it's an up to the caller task (e.g. if we adding a bunch of projects, it make sense to store them once in the end). @@ -492,7 +496,8 @@ def addListItem(self, path: str, list_item_kwargs: Mapping[str, Any] = None, go_ else: list_item_kwargs = {} - duplicate_index = next((idx for idx, is_duplicated in enumerate(self.duplicates(path)) if is_duplicated), -1) + duplicate_index = next((idx for idx, is_duplicated in enumerate(self.each_project_is_duplicate_of(path)) + if is_duplicated), -1) if duplicate_index > -1: # Just added project is already in the list so abort the addition module_logger.warning(f"This project is already in the list: {path}") @@ -506,7 +511,8 @@ def addListItem(self, path: str, list_item_kwargs: Mapping[str, Any] = None, go_ self.goToProject.emit(duplicate_index) # jump to the existing one return - # Insert given path into the constructor args (do not use dict.update() as we have list value) + # Insert given path into the constructor args (do not use dict.update() as we have list value that we also want + # to "merge") if len(list_item_kwargs) == 0: list_item_kwargs = { 'project_args': [path] } elif 'project_args' not in list_item_kwargs or len(list_item_kwargs['project_args']) == 0: @@ -642,7 +648,7 @@ def parse_args(args: list) -> Optional[argparse.Namespace]: parser.add_argument('--version', action='version', version=f"stm32pio v{stm32pio.app.__version__}") parser.add_argument('-d', '--directory', dest='path', default=str(pathlib.Path.cwd()), - help="path to the project (current directory, if not given)") + help="path to the project (current directory, if not given, but any other option should be specified then)") parser.add_argument('-b', '--board', dest='board', default='', help="PlatformIO name of the board") return parser.parse_args(args) if len(args) else None