diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..78ccc3d --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,45 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Upload Python Package + +on: + push: + branches: + - develop + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.9' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements-deploy.txt ]; then pip install -r requirements-deploy.txt; fi + - name: Build package + run: | + python update_version.py \ + --changelog_file changelog.md \ + --version_file nextion/version.py \ + --debug + python setup.py sdist + - name: Publish package + uses: pypa/gh-action-pypi-publish@717ba43cfbb0387f6ce311b169a825772f54d295 + with: + user: __token__ + # password: ${{ secrets.PYPI_API_TOKEN }} + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + skip_existing: true + print_hash: true + verbose: true diff --git a/changelog.md b/changelog.md index f5b4105..d0f7dab 100644 --- a/changelog.md +++ b/changelog.md @@ -11,8 +11,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### Fixed --> + ## Released +## [0.4.0] - 2022-07-24 +### Added +- `clearChannel(channel)` in [`nextion_waveform`](nextion/nextion_waveform.py) + to clear a waveform channel data +- Script to [update version file](update_version.py) based on changelog entry +- [Requirements file](requirements-deploy.txt) for GitHub CI python publish + workflow +- [GitHub CI python publish workflow](.github/workflows/python-publish.yml) + +### Fixed +- Replace undefined `__author__` in [`setup.py`](setup.py) +- Add missing shebang header to [`version`](nextion/version.py) file + ## [0.3.0] - 2022-07-22 ### Added - Function `hide()` and `show()` for all supported Nextion elements to hide @@ -51,8 +68,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Example HMI file](examples/everything.HMI) to be used for all examples -[Unreleased]: https://github.com/brainelectronics/micropython-nextion/compare/0.3.0...develop +[Unreleased]: https://github.com/brainelectronics/micropython-nextion/compare/0.4.0...develop +[0.4.0]: https://github.com/brainelectronics/micropython-nextion/tree/0.4.0 [0.3.0]: https://github.com/brainelectronics/micropython-nextion/tree/0.3.0 [0.2.0]: https://github.com/brainelectronics/micropython-nextion/tree/0.2.0 [0.1.0]: https://github.com/brainelectronics/micropython-nextion/tree/0.1.0 diff --git a/examples/waveform/main.py b/examples/waveform/main.py index bb5cc20..2d3cc7f 100644 --- a/examples/waveform/main.py +++ b/examples/waveform/main.py @@ -40,6 +40,14 @@ s0.addValue(2, channel_2_value) s0.addValue(3, channel_3_value) time.sleep(0.1) +print() + +time.sleep(1) + +# remove channel 0 waveform +print('Removing channel 0 waveform...') +s0.clearChannel(0) +print() print('Returning to REPL in 5 seconds') diff --git a/nextion/nextion_waveform.py b/nextion/nextion_waveform.py index 78a0c46..bbbb2c6 100644 --- a/nextion/nextion_waveform.py +++ b/nextion/nextion_waveform.py @@ -52,3 +52,21 @@ def addValue(self, ch: int, number: int) -> bool: cmd = "add {},{},{}".format(self.cid, ch, number) self._nh.sendCommand(cmd) return True + + def clearChannel(self, ch: int) -> bool: + """ + Clear a channel of the waveform + + :param ch: The channel to clear or 255 for all channels + :type ch: int + + :returns: True on success, false otherwise + :rtype: bool + """ + if (ch > 3) and (ch != 255): + self._logger.debug("Only channel (0-3) or all (255) can be cleared") + return False + + cmd = "cle {},{}".format(self.cid, ch) + self._nh.sendCommand(cmd) + return self._nh.recvRetCommandFinished() diff --git a/nextion/version.py b/nextion/version.py index 2c794cf..82a51d4 100644 --- a/nextion/version.py +++ b/nextion/version.py @@ -1,3 +1,6 @@ -__version_info__ = ('0', '2', '0') +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- + +__version_info__ = ('0', '4', '0') __version__ = '.'.join(__version_info__) __author__ = 'brainelectronics' diff --git a/requirements-deploy.txt b/requirements-deploy.txt new file mode 100644 index 0000000..b0d9706 --- /dev/null +++ b/requirements-deploy.txt @@ -0,0 +1,14 @@ +# List external packages here +# Avoid fixed versions +# # to upload package to PyPi or other package hosts +# twine>=4.0.1,<5 + +# to parse changelog semver +semver>=2.13.0,<3 + +# # to convert markdown to reStructuredText +# m2rr>=0.2.3,<1 +# # fix bug in m2rr dependecy definitions +# # see https://github.com/qhua948/m2rr/pull/1 +# docutils<=0.18 +# mistune<=1 diff --git a/setup.py b/setup.py index 04a2d88..6999e15 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ long_description=long_description, long_description_content_type='text/markdown', url='https://github.com/brainelectronics/micropython-nextion', - author=__author__, + author='brainelectronics', author_email='info@brainelectronics.de', classifiers=[ 'Development Status :: 4 - Beta', diff --git a/update_version.py b/update_version.py new file mode 100644 index 0000000..f4916a2 --- /dev/null +++ b/update_version.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- + +""" +Update version info in python version file with latest changelog version + +The changelog and package version shall always be aligned. This script can be +used to update the version info line "__version_info__ = ..." of a version.py +file with the latest changelog entry. +This changelog entry shall follow the semantic version pattern, see +https://semver.org/ and shall match the following pattern: + +## [x.y.z] - yyyy-mm-dd + +The line shall start with two hashtags followed by a single space. The semver +with x, y and z as non-negative integers, seperated by a dot and surrounded by +square brackets. Followed by a space, a dash, another space and the ISO8601 +formatted date. Additional timestamps after the data, seperated from the date +by a single space, are optional. +""" + +__author__ = "brainelectronics" +__copyright__ = "MIT License, Copyright (c) 2022 brainelectronics" +__credits__ = ["brainelectronics"] +__version__ = "0.1.0" +__maintainer__ = "brainelectronics" +__email__ = "git@brainelectronics.de" +__status__ = "Beta" + +import argparse +import fileinput +import logging +from pathlib import Path +import re +import semver +from sys import stdout + + +def parser_valid_file(parser: argparse.ArgumentParser, arg: str) -> str: + """ + Determine whether file exists. + :param parser: The parser + :type parser: parser object + :param arg: The file to check + :type arg: str + :raise argparse.ArgumentError: Argument is not a file + :returns: Input file path, parser error is thrown otherwise. + :rtype: str + """ + if not Path(arg).is_file(): + parser.error("The file {} does not exist!".format(arg)) + else: + return arg + + +def parse_arguments() -> argparse.Namespace: + """ + Parse CLI arguments. + :raise argparse.ArgumentError Argparse error + :return: argparse object + """ + parser = argparse.ArgumentParser(description=""" + Update version info file based on changelog entry + """, formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + # default arguments + parser.add_argument('-d', '--debug', + action='store_true', + help='Output logger messages to stderr') + + # specific arguments + parser.add_argument('--changelog_file', + dest='changelog_file', + required=True, + type=lambda x: parser_valid_file(parser, x), + help='Path to changelog file') + + parser.add_argument('--version_file', + dest='version_file', + required=True, + type=lambda x: parser_valid_file(parser, x), + help='Path to version file') + + parsed_args = parser.parse_args() + + return parsed_args + + +def parse_changelog(changelog_file: Path, logger: logging.Logger) -> str: + """ + Parse the changelog for the first matching release version line + + :param changelog_file: The path to the changelog file + :type changelog_file: Path + :param logger: Logger object + :type logger: logging.Logger + + :returns: Extracted semantic version string + :rtype: str + """ + release_version_line_regex = r"^\#\# \[\d{1,}[.]\d{1,}[.]\d{1,}\] \- \d{4}\-\d{2}-\d{2}" + # append "$" to match only ISO8601 dates without additional timestamps + release_version_line = "" + + with open(changelog_file, "r") as f: + for line in f: + match = re.search(release_version_line_regex, line) + if match: + release_version_line = match.group() + break + + logger.debug("First matching release version line: '{}'". + format(release_version_line)) + + return parse_semver_line(release_version_line, logger) + + +def parse_semver_line(release_version_line: str, + logger: logging.Logger) -> str: + """ + Parse a release version line for a semantic version + + :param release_version_line: The release version line as described + :type release_version_line: str + :param logger: Logger object + :type logger: logging.Logger + + :returns: Semantic version string + :rtype: str + """ + # release_version_line = "## [0.2.0] - 2022-05-19" + semver_regex = r"\[\d{1,}[.]\d{1,}[.]\d{1,}\]" + semver_string = "0.0.0" + + # extract semver from release version line + match = re.search(semver_regex, release_version_line) + if match: + semver_string = match.group() + # remove '[' and ']' from semver_string + semver_string = re.sub(r"[\[\]]", "", semver_string) + if not semver.VersionInfo.isvalid(semver_string): + logger.error("Parsed SemVer string is invalid, check format") + raise ValueError("Invalid SemVer string") + logger.debug("Extracted SemVer string: '{}'".format(semver_string)) + else: + logger.warning("No SemVer string found in given release version line") + + # semver_string = "0.2.0" + return semver_string + + +def create_version_info_line(semver_string: str, + logger: logging.Logger) -> str: + """ + Create the version info line used in "version.py" + + :param semver_string: The valid semver string + :type semver_string: str + :param logger: Logger object + :type logger: logging.Logger + + :returns: Complete version info line + :rtype: str + """ + # semver_string = "0.2.0" + ver = semver.VersionInfo.parse(semver_string) + + version_info = (str(ver.major), str(ver.minor), str(ver.patch)) + version_info_line = "__version_info__ = {}".format(version_info) + logger.debug("New version info line: '{}'".format(version_info_line)) + + # version_info_line = "__version_info__ = ('0', '2', '0')" + return version_info_line + + +def update_version_file(version_file: Path, + version_info_line: str, + logger: logging.Logger) -> None: + """ + Update the version file with a new version info line + + :param version_file: The path to the version file + :type version_file: Path + :param version_info_line: The version info line + :type version_info_line: str + :param logger: Logger object + :type logger: logging.Logger + """ + # standard output (print) is redirected to the original file + for line in fileinput.input(version_file, inplace=True): + if line.startswith("__version_info__ = "): + print(version_info_line, end="\n") + else: + # keep line ending + print(line, end="") + + logger.debug("Version file '{}' updated with '{}'". + format(version_file, version_info_line)) + + +def main(): + # parse CLI arguments + args = parse_arguments() + + custom_format = '[%(asctime)s] [%(levelname)-8s] [%(filename)-15s @'\ + ' %(funcName)-15s:%(lineno)4s] %(message)s' + logging.basicConfig(level=logging.INFO, + format=custom_format, + stream=stdout) + logger = logging.getLogger(__name__) + if args.debug: + logger.setLevel(logging.DEBUG) + + here = Path(__file__).parent + changelog_file = (here / args.changelog_file).resolve() + version_file = (here / args.version_file).resolve() + + logger.debug("Using changelog file '{}' to update version file '{}'". + format(changelog_file, version_file)) + + semver_string = parse_changelog(changelog_file, logger) + version_info_line = create_version_info_line(semver_string, logger) + update_version_file(version_file, version_info_line, logger) + + +if __name__ == '__main__': + main()