Browse Source

ci: add discord notifier and move the deployment to a matrix (#419)

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
pull/423/head
Pier CeccoPierangioliEugenio 9 months ago committed by GitHub
parent
commit
c9fdcf27a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 57
      .github/workflows/notify-social.yml
  2. 197
      .github/workflows/release.yml
  3. 0
      tools/ci/ci_tools/__init__.py
  4. 229
      tools/ci/ci_tools/release_helper.py
  5. 85
      tools/ci/ci_tools/release_helper_test.py
  6. 0
      tools/ci/ci_tools/social/__init__.py
  7. 82
      tools/ci/ci_tools/social/notify_discord.py
  8. 68
      tools/ci/ci_tools/social/social_common.py
  9. 27
      tools/ci/pyproject.toml

57
.github/workflows/notify-social.yml

@ -0,0 +1,57 @@
name: Release Sandbox
permissions:
contents: read
on:
workflow_dispatch:
inputs:
app:
description: The changed app
type: string
default: pyTermTk
version:
description: The app version
type: string
default: v0.0.0
discord-message:
description: The release message
type: string
default: pyTermTk released
workflow_call:
inputs:
app:
description: The changed app
type: string
default: pyTermTk
version:
description: The app version
type: string
default: v0.0.0
discord-message:
description: The release message
type: string
default: pyTermTk released
jobs:
notify-discord:
# runs-on: ubuntu-latest
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Instrall deps
run: |
python -m pip install discord.py
- name: Deploy Discord message
env:
MESSAGE: ${{ inputs.discord-message }}
run: |
python tools/ci/social/notify_discord.py ${{ inputs.app }} ${{ inputs.version }}

197
.github/workflows/release.yml

@ -21,6 +21,7 @@ permissions:
env:
APP_NAME: pyTermTk
APP_TTK: libs/pyTermTk
jobs:
release-please:
@ -29,6 +30,9 @@ jobs:
outputs:
rp_out: ${{ toJson(steps.release-please.outputs) }}
matrix: ${{ steps.set-matrix.outputs.matrix }}
matrix-pypi: ${{ steps.set-matrix.outputs.matrix_pypi }}
matrix-itch: ${{ steps.set-matrix.outputs.matrix_itch }}
steps:
- uses: actions/checkout@v4
with:
@ -48,12 +52,26 @@ jobs:
OUTPUTS: ${{ toJSON(steps.release-please.outputs) }}
run: |
echo OUTPUTS: "$OUTPUTS"
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Update Version
if: ${{ steps.release-please.outputs.prs_created == 'true'}}
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT_TOKEN }}
RP_OUT: ${{ steps.release-please.outputs }}
run: |
_get_name(){
_ITEM=$1
jq -r ".packages[\"${_ITEM}\"][\"package-name\"]" .release-please-config.json
}
_get_version(){
_ITEM=$1
jq -r ".[\"${_ITEM}\"]" .release-please-manifest.json
}
echo '::group::Setup Git'
git config --global user.name 'Eugenio Parodi - Action'
git config --global user.email 'ceccopierangioliegenio@googlemail.com'
@ -65,55 +83,27 @@ jobs:
cd pyTermTk.new
echo '::group::Retrieve the Versions'
# Update version in the project
_VERSION_TTK=$(jq -r '.["libs/pyTermTk" ]' .release-please-manifest.json)
_VERSION_DPT=$(jq -r '.["apps/dumbPaintTool"]' .release-please-manifest.json)
_VERSION_T_D=$(jq -r '.["apps/ttkDesigner" ]' .release-please-manifest.json)
_VERSION_KOD=$(jq -r '.["apps/ttkode" ]' .release-please-manifest.json)
_VERSION_TLG=$(jq -r '.["apps/tlogg" ]' .release-please-manifest.json)
_NAME_TTK=$(jq -r '.packages["libs/pyTermTk" ]["package-name"]' .release-please-config.json)
_NAME_DPT=$(jq -r '.packages["apps/dumbPaintTool"]["package-name"]' .release-please-config.json)
_NAME_T_D=$(jq -r '.packages["apps/ttkDesigner" ]["package-name"]' .release-please-config.json)
_NAME_KOD=$(jq -r '.packages["apps/ttkode" ]["package-name"]' .release-please-config.json)
_NAME_TLG=$(jq -r '.packages["apps/tlogg" ]["package-name"]' .release-please-config.json)
echo "Version ${_NAME_TTK}: ${_VERSION_TTK}"
echo "Version ${_NAME_DPT}: ${_VERSION_DPT}"
echo "Version ${_NAME_T_D}: ${_VERSION_T_D}"
echo "Version ${_NAME_KOD}: ${_VERSION_KOD}"
echo "Version ${_NAME_TLG}: ${_VERSION_TLG}"
echo '::group::🍧 Print the Versions'
release-helper \
--config .release-please-config.json \
--manifest .release-please-manifest.json \
info <<< '{}'
echo '::endgroup::'
echo '::group::Update the Versions'
echo '::group::🍓 Update the Versions'
_VERSION_TTK=$(_get_name ${APP_TTK})
sed -i \
"s|__version__:str.*|__version__:str = '${_VERSION_TTK}'|" \
libs/pyTermTk/TermTk/__init__.py
if grep -q "${_NAME_DPT}: ${_VERSION_DPT}" <<< ' ${{ steps.release-please.outputs.pr }}' ; then
sed -i \
"s|__version__:str.*|__version__:str = '${_VERSION_T_D}'|" \
apps/ttkDesigner/ttkDesigner/__init__.py
sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i apps/dumbPaintTool/pyproject.toml
fi
if grep -q "${_NAME_T_D}: ${_VERSION_T_D}" <<< ' ${{ steps.release-please.outputs.pr }}' ; then
sed -i \
"s|__version__:str.*|__version__:str = '${_VERSION_DPT}'|" \
apps/dumbPaintTool/dumbPaintTool/__init__.py
sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i apps/ttkDesigner/pyproject.toml
fi
if grep -q "${_NAME_KOD}: ${_VERSION_KOD}" <<< ' ${{ steps.release-please.outputs.pr }}' ; then
sed -i \
"s|__version__:str.*|__version__:str = '${_VERSION_KOD}'|" \
apps/ttkode/ttkode/__init__.py
sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i apps/ttkode/pyproject.toml
fi
if grep -q "${_NAME_TLG}: ${_VERSION_TLG}" <<< ' ${{ steps.release-please.outputs.pr }}' ; then
sed -i \
"s|__version__:str.*|__version__:str = '${_VERSION_TLG}'|" \
apps/tlogg/tlogg/__init__.py
sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i apps/tlogg/pyproject.toml
fi
release-helper \
--config .release-please-config.json \
--manifest .release-please-manifest.json \
upgrade <<< ${RP_OUT}
cp libs/pyTermTk/CHANGELOG.md CHANGELOG.md
echo '::endgroup::'
@ -124,10 +114,35 @@ jobs:
CHANGELOG.md
find . -name pyproject.toml | xargs git add
if [[ $(git status --porcelain) ]] ; then
git commit -m "chore: updated TermTk and apps to versions to ${_VERSION_TTK}, ${_VERSION_DPT}, ${_VERSION_T_D} ${_VERSION_KOD}"
git commit -m "chore: updated TermTk and apps to versions"
git push
fi
echo '::endgroup::'
- name: Define the Matrix strategy
id: set-matrix
env:
RP_OUT: ${{ steps.release-please.outputs }}
run: |
echo "matrix<<EOF" >> $GITHUB_OUTPUT
release-helper \
--config .release-please-config.json \
--manifest .release-please-manifest.json \
matrix all <<< ${RP_OUR} >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "matrix_itch<<EOF" >> $GITHUB_OUTPUT
release-helper \
--config .release-please-config.json \
--manifest .release-please-manifest.json \
matrix itch <<< ${RP_OUR} >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "matrix_pypi<<EOF" >> $GITHUB_OUTPUT
release-helper \
--config .release-please-config.json \
--manifest .release-please-manifest.json \
matrix pypi <<< ${RP_OUR} >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
pyTermTk-deploy-artifacts:
if: ${{ fromJson(needs.release-please.outputs.rp_out)['libs/pyTermTk--release_created'] }}
@ -201,38 +216,36 @@ jobs:
- pyTermTk-deploy-artifacts
secrets: inherit
publish-pyTermTk:
if: ${{ fromJson(needs.release-please.outputs.rp_out)['libs/pyTermTk--release_created'] }}
name: Publish pyTermTk
uses: ./.github/workflows/python-publish.yml
with:
pkg_name: pyTermTk
pkg_folder: libs/pyTermTk
publish-pypi:
if: ${{ needs.release-please.outputs.matrix-pypi != '[]' }}
name: Publish pypi ${{ matrix.name }}
needs:
- release-please
secrets: inherit
publish-ttkDesigner:
if: ${{ fromJson(needs.release-please.outputs.rp_out)['apps/ttkDesigner--release_created'] }}
name: Publish ttkDesigner
strategy:
matrix:
include: ${{ fromJson(needs.release-please.outputs.matrix-pypi) }}
uses: ./.github/workflows/python-publish.yml
with:
pkg_name: ttkDesigner
pkg_folder: apps/ttkDesigner
needs:
- release-please
pkg_name: ${{ matrix.name }}
pkg_folder: ${{ matrix.path }}
secrets: inherit
publish-dumbPaintTool:
if: ${{ fromJson(needs.release-please.outputs.rp_out)['apps/dumbPaintTool--release_created'] }}
name: Publish dumbPaintTool
uses: ./.github/workflows/python-publish.yml
with:
pkg_name: dumbPaintTool
pkg_folder: apps/dumbPaintTool
needs:
- release-please
secrets: inherit
# publish-itch:
# name: Publish Itch ${{ matrix.name }}
# needs:
# - release-please
# - generate-matrix
# runs-on: self-hosted
# strategy:
# matrix:
# include: ${{ fromJson(needs.generate-matrix.outputs.matrix-itch) }}
# steps:
# - name: Build ${{ matrix.name }}
# run: |
# echo "Building ${{ matrix.name }} at path: ${{ matrix.path }}"
# uses: ./.github/workflows/itch-publish.yml
# with:
# pkg_name: dumb-paint-tool
publish-dumbPaintTool-itch:
if: ${{ fromJson(needs.release-please.outputs.rp_out)['apps/dumbPaintTool--release_created'] }}
@ -244,24 +257,30 @@ jobs:
- release-please
secrets: inherit
publish-ttkode:
if: ${{ fromJson(needs.release-please.outputs.rp_out)['apps/ttkode--release_created'] }}
name: Publish ttkode
uses: ./.github/workflows/python-publish.yml
with:
pkg_name: ttkode
pkg_folder: apps/ttkode
needs:
- release-please
secrets: inherit
publish-tlogg:
if: ${{ fromJson(needs.release-please.outputs.rp_out)['apps/tlogg--release_created'] }}
name: Publish tlogg
uses: ./.github/workflows/python-publish.yml
with:
pkg_name: tlogg
pkg_folder: apps/tlogg
notify:
if: ${{ needs.release-please.outputs.matrix != '[]' }}
name: Notify ${{ matrix.name }} to the socials
needs:
- release-please
secrets: inherit
runs-on: self-hosted
strategy:
matrix:
include: ${{ fromJson(needs.release-please.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
shell: bash
run:
pip install -e 'tools/ci[social]'
- name: Notify ${{ matrix.name }} on Discord
env:
RN: ${{ matrix.release-notes }}
MESSAGE: ${{ matrix.release-notes }}
run: |
notify-discord ${{ matrix.name }} v${{ matrix.version }}

0
tools/ci/ci_tools/__init__.py

229
tools/ci/ci_tools/release_helper.py

@ -0,0 +1,229 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import re
import sys
import glob
import json
import argparse
import fileinput
from dataclasses import dataclass
from enum import Enum
from typing import List, Dict, Union
class MatrixType(Enum):
ALL = "all"
PYPI = "pypi"
ITCH = "itch"
@dataclass
class _AppData():
name: str
path: str
version: str
pypi: bool = False
itch: bool = False
tag: str = ""
release_notes: str = ""
def to_dict(self) -> Dict[str, Union[str,bool]]:
return {
"name" : self.name,
"path" : self.path,
"version" : self.version,
"pypi" : self.pypi,
"itch" : self.itch,
"tag" : self.tag,
"release-notes" : self.release_notes
}
def _print_info(apps_data:List[_AppData]) -> None:
for _a in apps_data:
print(f"{_a.name} : {_a.version}")
# for item in $(jq -r '.[].path' <<< ${APPS_ARRAY}) ; do; do
# # Update version in the project
# _VERSION=$(_get_version ${item})
# _NAME=$(_get_name ${item})
# if grep -q "${_NAME}: ${_VERSION}" <<< ' ${{ steps.release-please.outputs.pr }}' ; then
# sed -i \
# "s|__version__:str.*|__version__:str = '${_VERSION}'|" \
# ${item}/*/__init__.py
# sed "s|'pyTermTk *>=[^']*'|'pyTermTk>=${_VERSION_TTK}'|" -i ${item}/pyproject.toml
# echo ✅ Bumped ${_NAME} to ${_VERSION}
# else
# echo 🆗 No new release found for ${_NAME}
# fi
# done
def _upgrade_files(apps_data:List[_AppData], rp_data:Dict, dry_run:bool) -> None:
_ttk = [_a for _a in apps_data if _a.name=='pyTermTk'][0]
for _a in apps_data:
print(f"{_a.name} : {_a.version}")
if f"{_a.name}: {_a.version}" not in rp_data.get('pr',''):
print(f"🆗 No new release found for ${_a.name}")
else:
print(f"✅ Bumped ${_a.name} to ${_a.version}")
print(f"sed {_a.path}/*/__init__.py <<< {_a.version}")
pattern = re.compile(r"__version__:str.*")
replacement=f"__version__:str = '{_a.version}'"
files = glob.glob(f"{_a.path}/*/__init__.py")
if dry_run:
print(files, replacement)
else:
for line in fileinput.input(files, inplace=True):
print(pattern.sub(replacement, line), end="")
pattern = re.compile(r"'pyTermTk *>=[^']*'")
replacement = f"'pyTermTk>={_ttk.version}'"
files = glob.glob(f"{_a.path}/pyproject.toml")
if dry_run:
print(files, replacement)
else:
for line in fileinput.input(files, inplace=True):
print(pattern.sub(replacement, line), end="")
def _gen_matrix(matrix_type: MatrixType, rp_data:Dict, apps_data:List[_AppData]) -> List[_AppData]:
if matrix_type == MatrixType.PYPI:
apps = [app for app in apps_data if app.pypi]
elif matrix_type == MatrixType.ITCH:
apps = [app for app in apps_data if app.itch]
elif matrix_type == MatrixType.ALL:
apps = apps_data
else:
raise ValueError(f"Invalid matrix type: {matrix_type}")
# if 'pr' not in rp_data:
# return []#
# pr = json.loads(rp_data['pr'])
# print(rp_data)
# for app in apps:
# print(f"{app.name}: [{app.path}--release_created]: ", rp_data.get(f"{app.path}--release_created",False))
apps = [app for app in apps if rp_data.get(f"{app.path}--release_created",False) in ('true',True)]
for app in apps:
app.tag = rp_data.get(f"{app.path}--tag_name",'')
app.release_notes = rp_data.get(f"{app.path}--body",'')
return apps
def main():
parser = argparse.ArgumentParser(description="Release Helper Script")
# Configuration File Argument
parser.add_argument("--config", metavar="config_file", type=argparse.FileType("r"), help="Path to the configuration file")
parser.add_argument("--manifest", metavar="config_file", type=argparse.FileType("r"), help="Path to the configuration file")
subparsers = parser.add_subparsers(title="Features", dest="feature")
# Apps Feature
info_parser = subparsers.add_parser("info", help="Print release info")
upgrade_parser = subparsers.add_parser("upgrade", help="update the app versions")
upgrade_parser.add_argument("--dry-run", action="store_true", help="Do not apply thw changes")
# Apps Feature
apps_parser = subparsers.add_parser("apps", help="Apps related operations")
apps_parser.add_argument("--list", action="store_true", help="List available apps")
apps_parser.add_argument("--build", metavar="app_name", type=str, help="Build a specific app")
# Matrix Feature
matrix_parser = subparsers.add_parser("matrix", help="Matrix related operations")
matrix_parser.add_argument("type", metavar="matrix_type", type=str, choices=[e.value for e in MatrixType], help="Specify the type of matrix to generate")
args = parser.parse_args()
# Load and parse configuration file if provided
config = {}
if args.config:
try:
config = json.load(args.config) # Parse the JSON file
# print(f"Loaded configuration: {json.dumps(config, indent=2)}")
except json.JSONDecodeError:
print(f"Error: Configuration file '{args.config.name}' is not valid JSON.")
sys.exit(1)
# Load and parse configuration file if provided
manifest = {}
if args.manifest:
try:
manifest = json.load(args.manifest) # Parse the JSON file
# print(f"Loaded manifesturation: {json.dumps(manifest, indent=2)}")
except json.JSONDecodeError:
print(f"Error: Configuration file '{args.manifest.name}' is not valid JSON.")
sys.exit(1)
input_data = {}
if not sys.stdin.isatty(): # or sys.stdin.peek(1):
try:
read = sys.stdin.read()
input_data = json.loads(read)
except json.JSONDecodeError:
print("Error: Invalid JSON input.")
sys.exit(1)
apps_data = [
_AppData(
name=_v.get('package-name',''),
path=_a,
version=manifest.get(_a,"0.0.0"),
itch=_v.get('itch',False),
pypi=_v.get('pypi',False))
for _a,_v in config.get('packages',{}).items()]
# print(apps_data)
if args.feature == "info":
_print_info(apps_data)
elif args.feature == "upgrade":
print(args)
_upgrade_files(apps_data, input_data, args.dry_run)
elif args.feature == "apps":
if args.list:
print("Available Apps:")
for app in apps_data:
print(f" - {app.name}")
elif args.build:
print(f"Building app: {args.build}")
# Implement build logic here
else:
apps_parser.print_help()
elif args.feature == "matrix":
matrix_type = MatrixType(args.type)
matrix = _gen_matrix(matrix_type, input_data, apps_data)
# print(json.dumps(
# {
# 'has_matrix': bool(matrix),
# 'matrix':[app.to_dict() for app in matrix]
# }
# , indent=2))
print(json.dumps([app.to_dict() for app in matrix], indent=2))
else:
parser.print_help()
if __name__ == "__main__":
main()

85
tools/ci/ci_tools/release_helper_test.py

@ -0,0 +1,85 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .release_helper import *
rp_pr_1 = '''{
"releases_created": "false",
"paths_released": "[]",
"prs_created": "true",
"pr": "{\"headBranchName\":\"release-please--branches--main\",\"baseBranchName\":\"main\",\"number\":397,\"title\":\"chore: release main\",\"body\":\":robot: I have created a release *beep* *boop*\\n---\\n\\n\\n<details><summary>pyTermTk: 0.43.0-a.0</summary>\\n\\n## [0.43.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/pyTermTk-v0.42.1-a.0...pyTermTk-v0.43.0-a.0) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **kodeTab:** reworked iterWidget in iterItems\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Fixes\\n\\n* **spinbox:** better check for float, empty strings and negative numbers ([4909bf6](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/4909bf6756000f9450249b28f8c8379a2160415c))\\n\\n\\n### Chores\\n\\n* **kodeTab:** reworked iterWidget in iterItems ([47f73fc](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/47f73fc03a5a049ac3e6073dcadc09018b509328))\\n* **ttk:** workaround timer disconnect in case of error ([d70b2c1](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/d70b2c1c3cf25f7ffb479bc2850b3c9a3ca0fe0c))\\n\\n\\n### Refactors\\n\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n* **TTkColor:** improved typings ([711d611](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/711d611a73be0d0a6fce37e4624b5ae30847dd9c))\\n</details>\\n\\n<details><summary>ttkode: 0.4.0-a.2</summary>\\n\\n## [0.4.0-a.2](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/ttkode-v0.3.2-a.2...ttkode-v0.4.0-a.2) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Refactors\\n\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n</details>\\n\\n<details><summary>tlogg: 0.7.0-a.0</summary>\\n\\n## [0.7.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/tlogg-v0.6.0-a.0...tlogg-v0.7.0-a.0) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Refactors\\n\\n* move the main routine outside the a folder ([#400](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/400)) ([b1bb71f](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/b1bb71fd1ecd9c41a4cb016de15f1d695ea58ba5))\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n</details>\\n\\n---\\nThis PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).\",\"files\":[],\"labels\":[\"autorelease: pending\"]}",
"prs": "[{\"headBranchName\":\"release-please--branches--main\",\"baseBranchName\":\"main\",\"number\":397,\"title\":\"chore: release main\",\"body\":\":robot: I have created a release *beep* *boop*\\n---\\n\\n\\n<details><summary>pyTermTk: 0.43.0-a.0</summary>\\n\\n## [0.43.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/pyTermTk-v0.42.1-a.0...pyTermTk-v0.43.0-a.0) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **kodeTab:** reworked iterWidget in iterItems\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Fixes\\n\\n* **spinbox:** better check for float, empty strings and negative numbers ([4909bf6](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/4909bf6756000f9450249b28f8c8379a2160415c))\\n\\n\\n### Chores\\n\\n* **kodeTab:** reworked iterWidget in iterItems ([47f73fc](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/47f73fc03a5a049ac3e6073dcadc09018b509328))\\n* **ttk:** workaround timer disconnect in case of error ([d70b2c1](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/d70b2c1c3cf25f7ffb479bc2850b3c9a3ca0fe0c))\\n\\n\\n### Refactors\\n\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n* **TTkColor:** improved typings ([711d611](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/711d611a73be0d0a6fce37e4624b5ae30847dd9c))\\n</details>\\n\\n<details><summary>ttkode: 0.4.0-a.2</summary>\\n\\n## [0.4.0-a.2](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/ttkode-v0.3.2-a.2...ttkode-v0.4.0-a.2) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Refactors\\n\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n</details>\\n\\n<details><summary>tlogg: 0.7.0-a.0</summary>\\n\\n## [0.7.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/tlogg-v0.6.0-a.0...tlogg-v0.7.0-a.0) (2025-05-28)\\n\\n\\n### ⚠ BREAKING CHANGES\\n\\n* **TabWidget:** tab request close event need to be handled inside the app\\n\\n### Refactors\\n\\n* move the main routine outside the a folder ([#400](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/400)) ([b1bb71f](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/b1bb71fd1ecd9c41a4cb016de15f1d695ea58ba5))\\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\\n</details>\\n\\n---\\nThis PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).\",\"files\":[],\"labels\":[\"autorelease: pending\"]}]"
}
'''
rp_release_1 = '''{
"releases_created": "true",
"libs/pyTermTk--release_created": "true",
"libs/pyTermTk--id": "222844982",
"libs/pyTermTk--name": "pyTermTk: v0.43.0-a.0",
"libs/pyTermTk--tag_name": "pyTermTk-v0.43.0-a.0",
"libs/pyTermTk--sha": "edce717e527f2fe93a8a0c7f17e08a6b5fecd7bd",
"libs/pyTermTk--body": "## [0.43.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/pyTermTk-v0.42.1-a.0...pyTermTk-v0.43.0-a.0) (2025-06-03)\n\n\n### ⚠ BREAKING CHANGES\n\n* **kodeTab:** reworked iterWidget in iterItems\n* **TabWidget:** tab request close event need to be handled inside the app\n\n### Fixes\n\n* **spinbox:** better check for float, empty strings and negative numbers ([4909bf6](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/4909bf6756000f9450249b28f8c8379a2160415c))\n\n\n### Chores\n\n* autogen code for scrollarea classes ([#406](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/406)) ([fef1b0e](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/fef1b0ea5bd6ddc8f3e8f93a23ea156071e77493))\n* **Input:** add support for ctrl and other key comination ([#404](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/404)) ([5c2bb92](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/5c2bb9202cd819aa573e9f0d9ea966a4d0e5c485))\n* **kodeTab:** reworked iterWidget in iterItems ([47f73fc](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/47f73fc03a5a049ac3e6073dcadc09018b509328))\n* **spinbox:** fix return type ([ddc53a0](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/ddc53a07653a6f3aa958509d7d400cc6c6264d91))\n* **spinbox:** handle left/right wheel event ([ce961a6](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/ce961a657573ee520b73fca7d4ae721a8837a1d0))\n* **ttk:** workaround timer disconnect in case of error ([d70b2c1](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/d70b2c1c3cf25f7ffb479bc2850b3c9a3ca0fe0c))\n\n\n### Refactors\n\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))\n* **TTkColor:** improved typings ([711d611](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/711d611a73be0d0a6fce37e4624b5ae30847dd9c))",
"libs/pyTermTk--html_url": "https://github.com/ceccopierangiolieugenio/pyTermTk/releases/tag/pyTermTk-v0.43.0-a.0",
"libs/pyTermTk--draft": "false",
"libs/pyTermTk--upload_url": "https://uploads.github.com/repos/ceccopierangiolieugenio/pyTermTk/releases/222844982/assets{?name,label}",
"libs/pyTermTk--path": "libs/pyTermTk",
"libs/pyTermTk--version": "0.43.0-a.0",
"libs/pyTermTk--major": "0",
"libs/pyTermTk--minor": "43",
"libs/pyTermTk--patch": "0",
"libs/pyTermTk--prNumber": "397",
"apps/ttkode--release_created": "true",
"apps/ttkode--id": "222844984",
"apps/ttkode--name": "ttkode: v0.4.0-a.2",
"apps/ttkode--tag_name": "ttkode-v0.4.0-a.2",
"apps/ttkode--sha": "edce717e527f2fe93a8a0c7f17e08a6b5fecd7bd",
"apps/ttkode--body": "## [0.4.0-a.2](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/ttkode-v0.3.2-a.2...ttkode-v0.4.0-a.2) (2025-06-03)\n\n\n### ⚠ BREAKING CHANGES\n\n* **TabWidget:** tab request close event need to be handled inside the app\n\n### Features\n\n* add save feature ([#407](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/407)) ([26ff9b2](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/26ff9b2f0a81bddadeb6849d5d560ae67406f973))\n\n\n### Refactors\n\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))",
"apps/ttkode--html_url": "https://github.com/ceccopierangiolieugenio/pyTermTk/releases/tag/ttkode-v0.4.0-a.2",
"apps/ttkode--draft": "false",
"apps/ttkode--upload_url": "https://uploads.github.com/repos/ceccopierangiolieugenio/pyTermTk/releases/222844984/assets{?name,label}",
"apps/ttkode--path": "apps/ttkode",
"apps/ttkode--version": "0.4.0-a.2",
"apps/ttkode--major": "0",
"apps/ttkode--minor": "4",
"apps/ttkode--patch": "0",
"apps/ttkode--prNumber": "397",
"apps/tlogg--release_created": "true",
"apps/tlogg--id": "222844986",
"apps/tlogg--name": "tlogg: v0.7.0-a.0",
"apps/tlogg--tag_name": "tlogg-v0.7.0-a.0",
"apps/tlogg--sha": "edce717e527f2fe93a8a0c7f17e08a6b5fecd7bd",
"apps/tlogg--body": "## [0.7.0-a.0](https://github.com/ceccopierangiolieugenio/pyTermTk/compare/tlogg-v0.6.0-a.0...tlogg-v0.7.0-a.0) (2025-06-03)\n\n\n### ⚠ BREAKING CHANGES\n\n* **TabWidget:** tab request close event need to be handled inside the app\n\n### Refactors\n\n* move the main routine outside the a folder ([#400](https://github.com/ceccopierangiolieugenio/pyTermTk/issues/400)) ([b1bb71f](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/b1bb71fd1ecd9c41a4cb016de15f1d695ea58ba5))\n* **TabWidget:** tab request close event need to be handled inside the app ([9420adf](https://github.com/ceccopierangiolieugenio/pyTermTk/commit/9420adf68e2184482cd71266f280c560ea911f45))",
"apps/tlogg--html_url": "https://github.com/ceccopierangiolieugenio/pyTermTk/releases/tag/tlogg-v0.7.0-a.0",
"apps/tlogg--draft": "false",
"apps/tlogg--upload_url": "https://uploads.github.com/repos/ceccopierangiolieugenio/pyTermTk/releases/222844986/assets{?name,label}",
"apps/tlogg--path": "apps/tlogg",
"apps/tlogg--version": "0.7.0-a.0",
"apps/tlogg--major": "0",
"apps/tlogg--minor": "7",
"apps/tlogg--patch": "0",
"apps/tlogg--prNumber": "397",
"paths_released": "[\"libs/pyTermTk\",\"apps/ttkode\",\"apps/tlogg\"]",
"prs_created": "false"
}'''

0
tools/ci/ci_tools/social/__init__.py

82
tools/ci/ci_tools/social/notify_discord.py

@ -0,0 +1,82 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os, sys
import re
import asyncio
import argparse
from typing import Dict,List,Any
import discord
current_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(current_dir)
from ci_tools.social.social_common import get_social_data, SocialData, get_env_var
async def send_discord_message(version: str, data:SocialData):
token = get_env_var("DISCORD_TOKEN")
message = get_env_var("MESSAGE")
intents = discord.Intents.default()
client = discord.Client(intents=intents)
embed = discord.Embed(
title=f"{data.name} Released!!!",
url=data.link,
# description="Here's a new feature we added.",
color=0x00ff00,
)
embed.add_field(name="Version", value=version, inline=True)
# embed.add_field(name="What's New:", value=message, inline=False)
# embed.set_image(url="https://example.com/image.png")
# embed.add_field(name="Feature", value="Auto-messaging", inline=False)
embed.set_footer(text="Bot by Pier...")
message = re.sub(r'\((https?://[^\)]+)\)', r'(<\1>)', message).replace('\\n','\n')[:2000]
# print(message)
# exit(1)
@client.event
async def on_ready():
print(f'Logged in as {client.user}')
channel = client.get_channel(data.discord_channel_id)
await channel.send(embed=embed)
await channel.send(message)
await client.close() # Optional: close after sending
await client.start(token)
def main():
parser = argparse.ArgumentParser(description="Send a Discord notification.")
parser.add_argument("app", type=str, help="The application name.")
parser.add_argument("version", type=str, help="The application version.")
args = parser.parse_args()
data = get_social_data(args.app)
if not data:
raise ValueError(f"app: {args.app} is not recognised")
asyncio.run(send_discord_message(args.version, data))
if __name__ == "__main__":
main()

68
tools/ci/ci_tools/social/social_common.py

@ -0,0 +1,68 @@
# MIT License
#
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.from dataclasses import dataclass
__all__ = ['get_social_data','SocialData','get_env_var']
import os
from dataclasses import dataclass
from typing import List
@dataclass
class SocialData():
name: str
link: str
discord_channel_id: int
_all_data:List[SocialData] = [
SocialData(
name='pytermtk',
link='https://github.com/ceccopierangiolieugenio/pyTermTk',
discord_channel_id=1379381341145268305,
),
SocialData(
name='ttkode',
link='https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/ttkode',
discord_channel_id=1379381474783924295,
),
SocialData(
name='dumbpainttool',
link='https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/dumbPaintTool',
discord_channel_id=1379381571412430931,
),
SocialData(
name='tlogg',
link='https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/apps/tlogg',
discord_channel_id=1379381593378000916,
),
]
def get_social_data(app:str) -> SocialData:
for _sd in _all_data:
if _sd.name.lower() == app.lower():
return _sd
raise ValueError(f"app: {app} is not recognised")
def get_env_var(name:str) -> str:
value = os.environ.get(name)
if value is None:
raise EnvironmentError(f"{name} environment variable is not available")
return value

27
tools/ci/pyproject.toml

@ -0,0 +1,27 @@
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "ci_tools"
version = "0.1.0"
description = "ci helpers"
authors = [
{name = "Eugenio Parodi", email = "ceccopierangiolieugenio@googlemail.com"},
]
requires-python = ">=3.9"
dependencies = [
'GitPython==3.1.44'
]
[project.optional-dependencies]
social = [
'discord.py==2.5.2'
]
[project.scripts]
release-helper = "ci_tools.release_helper:main"
notify-discord = "ci_tools.social.notify_discord:main"
[tool.setuptools]
packages = ["ci_tools"]
Loading…
Cancel
Save