Skip to content
Snippets Groups Projects
Commit a34c58e0 authored by Meisam Farzalipour Tabriz's avatar Meisam Farzalipour Tabriz
Browse files

port badger to python

drop dependency on unmaintained packages
make json schema more portable
update tests
parent 998a998f
No related branches found
No related tags found
No related merge requests found
Pipeline #266589 passed
......@@ -34,11 +34,9 @@ build:badges:in_python_version:
matrix:
- PYTHON_VER: ['3.10', '3.11', '3.12', '3.13']
before_script:
- export PATH=${CI_PROJECT_DIR}/scripts:$PATH
- apt-get update && apt-get install -y --no-install-recommends jq
- pip install -r requirements.txt
- pip install -r scripts/badger.requirements.txt
script:
- tests/badger.test.sh
- PATH="${CI_PROJECT_DIR}/scripts:$PATH" tests/badger.test.sh
- mv badges py-${PYTHON_VER}_badges
artifacts:
paths:
......
......
......@@ -2,24 +2,23 @@
A set of bash scripts for repository badges. These scripts read input from file in json format conforming a [schema](https://gitlab.mpcdf.mpg.de/tbz/linkmedkit/-/blob/main/tests/badge.schema.json) compatible with [shields.io JSON format](https://github.com/badges/shields/blob/2c0737592b29e78f5c543a0e5ed908149dd18436/doc/json-format.md).
- `scripts/badger.sh`: generates a SVG badge.
- `scripts/badger.py`: generates a SVG badge.
- `scripts/gitlab_badge_sticker.sh`: updates GitLab repository badges.
## Quick Start
### Install Dependecies
1. Install `jq`
2. Install `python3` and a python3 package manager e.g. `pip`
3. Install python dependecies:
```shell
pip install -r requirements.txt
pip install -r scripts/badger.requirements.txt
```
### Prepare Input
Example badge file `badge.test.json`:
Example badge file `examples/badge.test.json`:
```json
{
......@@ -33,7 +32,7 @@ Example badge file `badge.test.json`:
### Generate SVG Badge
```shell
badger.sh --badge-info="badge.test.json"
badger.py --input-file="badge.test.json"
```
It will generate `test.svg` file as: ![test.svg](https://gitlab.mpcdf.mpg.de/tbz/linkmedkit/-/raw/main/examples/test.svg)
......@@ -51,37 +50,31 @@ gitlab_badge_sticker.sh
- Display help. Will show all the command line options and their default values.
```shell
badger.sh -h
badger.py -help
```
- Read `badge.dead_internal_links.json` file, create a badge file named `dead_internal_links.svg`.
```shell
badger.sh
badger.py
```
- Read the number of dead links from `badge.dead_external_links.json` file.
```shell
badger.sh --badge-info="badge.dead_external_links.json"
badger.py --input-file="badge.dead_external_links.json"
```
- Generate badge file `public/dead_external.svg`. Create the parent directories if missing.
```shell
badger.sh --badge-file="public/dead_external.svg"
badger.py --output-file="public/dead_external.svg"
```
- Run `badger.sh` in verbose mode
- Run `badger.py` in verbose mode
```shell
badger.sh --verbose
```
- Run `badger.sh` with a different python executable
```shell
PYTHON=python3.12 badger.sh
badger.py --verbose
```
### GitLab Badge Updater
......
......
{
"schemaVersion": 1,
"label": "dead%20internal%20links",
"message": 0,
"message": "0",
"color": "green"
}
pybadges~=3.0.0 # badger.sh
setuptools~=80.7.0 # needed on py3.12+
standard-imghdr~=3.13.0 # needed on py3.13+
\ No newline at end of file
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2021-2023 M. Farzalipour Tabriz, Max Planck Computing and Data Facility (MPCDF)
# SPDX-FileCopyrightText: 2025 M. Farzalipour Tabriz, Max Planck Institute for Physics (MPP)
# SPDX-License-Identifier: BSD-3-Clause
import argparse
import json
import logging
import sys
from pathlib import Path
from badgepy import badge
__version__ = "0.5.0"
class BadgerArgs(argparse.Namespace):
"""Badger parser attributes."""
input_file: str
output_file: str
verbose: int
version: bool
def cli_args() -> BadgerArgs:
"""Parse and Return cli arguments."""
parser = argparse.ArgumentParser(
description="Local SVG badge file generator from shields-compatible JSON data file input. https://shields.io/badges/endpoint-badge",
)
parser.add_argument(
"--input-file",
help="badge information file name in 'badge.[***].json' format. [***] is interpreted as the badge-name (default: 'badge.dead_internal_links.json')",
default="badge.dead_internal_links.json",
type=str,
)
parser.add_argument(
"--output-file",
help="output badge file name (default: '[badge-label].svg')",
default="",
type=str,
)
parser.add_argument(
"--verbose",
"-v",
action="count",
default=0,
help="Increase verbosity",
)
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s {__version__}",
)
return parser.parse_args(namespace=BadgerArgs())
def configure_root_logger(verbosity_level: int) -> None:
"""Configure the root logger based on the ``verbosity_level``.
:param verbosity_level: logger verbosity level (0,1)
"""
logger = logging.getLogger("root")
ch = logging.StreamHandler()
if verbosity_level > 0:
logger.setLevel(logging.DEBUG)
ch.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
ch.setLevel(logging.INFO)
formatter = logging.Formatter("%(levelname)s - %(message)s")
ch.setFormatter(formatter)
logger.addHandler(ch)
def main() -> None:
args = cli_args()
configure_root_logger(args.verbose)
logger = logging.getLogger("main")
input_file_path = Path(args.input_file)
try:
with input_file_path.open(encoding="utf-8") as json_file:
badge_json = json.load(json_file)
except Exception:
logger.exception("Could not read input file!")
sys.exit(1)
logger.debug("parsed input file: %s", str(badge_json))
if args.output_file:
output_file = args.output_file
else:
output_file = badge_json["label"] + ".svg"
logger.debug("output file: %s", output_file)
badge_svg_content = badge(
left_text=badge_json["label"].replace("%20", " "),
right_text=badge_json["message"],
right_color=badge_json["color"],
)
logger.debug("output: %s", badge_svg_content)
try:
output_file_path = Path(output_file)
output_file_path.parent.absolute().mkdir(parents=True, exist_ok=True)
with output_file_path.open(
mode="w",
encoding="utf-8",
) as output_svg_file:
output_svg_file.write(badge_svg_content)
except Exception:
logger.exception("Could not write output file!")
sys.exit(1)
logger.info("Badge was created.")
if __name__ == "__main__":
main()
badgepy>=1.2.0
\ No newline at end of file
#!/usr/bin/env bash
# Copyright 2021-2023 M. Farzalipour Tabriz, Max Planck Computing and Data Facility (MPCDF)
# Copyright 2025 M. Farzalipour Tabriz, Max Planck Institute for Physics (MPP)
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the 3-Clause BSD License. See the LICENSE file for details.
# CLI parser is generated using Argbash v2.9.0 (BSD-3-Clause) https://github.com/matejak/argbash
set -e
version="0.4.0"
script_name="Badger: SVG Badge Generator"
die() {
local _ret="${2:-1}"
test "${_PRINT_HELP:-no}" = yes && print_help >&2
echo "$1" >&2
exit "${_ret}"
}
# default options
_arg_badge_info_file="badge.dead_internal_links.json"
_arg_verbose="off"
print_help() {
printf '%s\n' "Badger: SVG badge generator for link medic"
printf 'Usage: %s [--badge-info <arg>] [--badge-file <arg>] [-v|--version] [--verbose] [-h|--help]\n' "$0"
printf '\t%s\n' "--badge-info: badge information file name in 'badge.[***].json' format. [***] is interpreted as the badge-name (default: 'badge.dead_internal_links.json')"
printf '\t%s\n' "--badge-file: output badge file name (default: '[badge-name].svg')"
printf '\t%s\n' "-v, --version: Prints version"
printf '\t%s\n' "--verbose: verbose mode"
printf '\t%s\n' "-h, --help: Prints help"
}
parse_commandline() {
while test $# -gt 0; do
_key="$1"
case "$_key" in
--badge-info)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_badge_info_file="$2"
shift
;;
--badge-info=*)
_arg_badge_info_file="${_key##--badge-info=}"
;;
--badge-file)
test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
_arg_badge_file="$2"
shift
;;
--badge-file=*)
_arg_badge_file="${_key##--badge-file=}"
;;
-v | --version)
echo "$script_name" "v$version"
exit 0
;;
--verbose)
_arg_verbose="on"
;;
-h | --help)
print_help
exit 0
;;
*)
_PRINT_HELP=yes die "FATAL ERROR: Got an unexpected argument '$1'" 1
;;
esac
shift
done
}
parse_commandline "$@"
python_exec=${PYTHON:-python3}
_requirements=('jq' "$python_exec")
[[ "$_arg_verbose" == "on" ]] && echo "Checking requirements..."
for _requirement in "${_requirements[@]}"; do
if ! [[ -x "$(command -v "$_requirement" || true)" ]]; then
echo "ERROR: $_requirement was not found!"
exit 1
else
[[ "$_arg_verbose" == "on" ]] && echo " > OK: $_requirement was found"
fi
done
_python_version=$($python_exec --version)
if [[ "$_arg_verbose" == "on" ]]; then
echo "Using python: $(command -v "$python_exec" || true)"
echo " > $_python_version"
fi
_python_module_requirements=('pybadges')
[[ "$_arg_verbose" == "on" ]] && echo "Checking required python modules..."
for _python_module_requirement in "${_python_module_requirements[@]}"; do
if ! $python_exec -c "import $_python_module_requirement" &>/dev/null; then
echo "ERROR: Python module $_python_module_requirement was not found or does not work properly!"
echo "Python output:"
$python_exec -c "import $_python_module_requirement"
exit 1
else
[[ "$_arg_verbose" == "on" ]] && echo " > OK: Python module $_python_module_requirement was found!"
fi
done
if [ ! -f "${_arg_badge_info_file}" ]; then
echo "ERROR: badge info file is missing: ${_arg_badge_info_file}"
exit 1
else
badge_name_json=${_arg_badge_info_file##*badge.}
badge_name=${badge_name_json%.json}
badge_label=$(jq -r '.label' <"$_arg_badge_info_file")
badge_value=$(jq -r '.message' <"$_arg_badge_info_file")
badge_color=$(jq -r '.color' <"$_arg_badge_info_file")
fi
if [ -z "$_arg_badge_file" ]; then
_arg_badge_file="$badge_name.svg"
fi
sanitized_badge_label=${badge_label//%20/ }
if [[ "$_arg_verbose" == "on" ]]; then
echo "Badge info file: $_arg_badge_info_file"
echo " > name: $badge_name"
echo " > label: $sanitized_badge_label"
echo " > value: $badge_value"
echo " > color: $badge_color"
echo "Generating badge: $_arg_badge_file"
fi
mkdir -p "$(dirname "${_arg_badge_file}")"
command "$python_exec" -m pybadges --left-text="$sanitized_badge_label" --right-text="${badge_value}" --right-color="${badge_color}" >"${_arg_badge_file}"
{
"schemaVersion": 1,
"label": "dead%20external%20links",
"message": 1,
"message": "1",
"color": "red"
}
\ No newline at end of file
{
"schemaVersion": 1,
"label": "dead%20internal%20links",
"message": 0,
"message": "0",
"color": "green"
}
\ No newline at end of file
{
"schemaVersion": 1,
"label": "http%20links",
"message": 1,
"message": "1",
"color": "yellow"
}
\ No newline at end of file
......@@ -9,7 +9,7 @@
"type": "string"
},
"message": {
"type": "integer"
"type": "string"
},
"color": {
"type": "string"
......
......
#!/usr/bin/env bash
set -e
badger.sh --verbose --badge-info="tests/badge.dead_internal_links.json" --badge-file="badges/dead_internal_links.svg"
echo "testing badge.dead_internal_links.json"
badger.py --verbose --input-file="tests/badge.dead_internal_links.json" --output-file="badges/dead_internal_links.svg"
grep -q "<svg " badges/dead_internal_links.svg
badger.sh --verbose --badge-info="tests/badge.dead_external_links.json" --badge-file="badges/path/to/dead_external_links.svg"
echo "testing badge.dead_internal_links.json"
badger.py --verbose --input-file="tests/badge.dead_external_links.json" --output-file="badges/path/to/dead_external_links.svg"
grep -q "<svg " badges/path/to/dead_external_links.svg
badger.sh --verbose --badge-info="tests/badge.http_links.json" --badge-file="badges/http_links.svg"
echo "testing badge.http_links.json"
badger.py --verbose --input-file="tests/badge.http_links.json" --output-file="badges/http_links.svg"
grep -q "<svg " badges/http_links.svg
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment