diff --git a/.github/workflows/daily_README.yaml b/.github/workflows/daily_README.yaml index e23d1ce2a..605bf6e7b 100644 --- a/.github/workflows/daily_README.yaml +++ b/.github/workflows/daily_README.yaml @@ -1,131 +1,243 @@ -#!/usr/bin/env bash +# yamllint disable rule:line-length +--- +name: Generate README +on: + schedule: + - cron: 0 17 * * * + workflow_dispatch: null -set -euo pipefail +jobs: + README_updater: + if: github.repository_owner == 'alexbelgium' + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v5 -# Ensure yq is installed (mikefarah/yq v4+) -if ! command -v yq &>/dev/null; then - echo "yq is required (https://github.com/mikefarah/yq), please install it in your CI or local environment" - exit 1 -fi + - name: Install jq + yq (v4) + run: | + set -euo pipefail + if ! command -v jq >/dev/null 2>&1; then + sudo apt-get update -y + sudo apt-get install -y jq + fi + if ! command -v yq >/dev/null 2>&1; then + sudo wget -qO /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/download/v4.44.3/yq_linux_amd64" + sudo chmod +x /usr/local/bin/yq + fi -echo "Starting" + - name: Create README file + shell: bash + run: | + set -euo pipefail -cp .templates/.README.md README2.md -ADDONSLINE="$(sed -n '/%%ADDONS_LIST%%/=' README2.md)" -sed -i "/%%ADDONS_LIST%%/d" README2.md + echo "Starting" -# Step 1: Rename folders by name field in config -for f in $(find . -maxdepth 1 -type d ! -name '.' | sort -r); do - CONFIG="" - [ -f "$f/config.json" ] && CONFIG="$f/config.json" - [ -z "$CONFIG" ] && [ -f "$f/config.yaml" ] && CONFIG="$f/config.yaml" - [ -z "$CONFIG" ] && continue + # --------------------------- + # Helper functions (JSON/YAML) + # --------------------------- + get_config() { + # Echos "|" where type is json|yaml, or "|" if none found + local dir="$1" + if [ -f "$dir/config.json" ]; then + echo "$dir/config.json|json" + elif [ -f "$dir/config.yaml" ]; then + echo "$dir/config.yaml|yaml" + elif [ -f "$dir/config.yml" ]; then + echo "$dir/config.yml|yaml" + else + echo "|" + fi + } - if [[ "$CONFIG" == *.json ]]; then - NAME=$(jq -r '.name // empty' "$CONFIG") - else - NAME=$(yq -r '.name // empty' "$CONFIG") - fi + get_raw() { + # Print raw scalar or list items (newline-separated) + # $1 = jq/yq expr, $2 = type (json|yaml), $3 = path + local expr="$1" type="$2" file="$3" + if [ "$type" = "json" ]; then + jq -r "$expr" "$file" 2>/dev/null || true + else + yq -r "$expr" "$file" 2>/dev/null || true + fi + } - if [[ -n "$NAME" && "$f" != "./$NAME" ]]; then - echo "${f#./}" > "$f/oldname" - mv "$f" "./$NAME" - fi -done + get_jsonc() { + # Print compact JSON form of a subnode (for substring checks) + # $1 = expr, $2 = type, $3 = path + local expr="$1" type="$2" file="$3" + if [ "$type" = "json" ]; then + jq -c "$expr" "$file" 2>/dev/null || true + else + yq -o=json -c "$expr" "$file" 2>/dev/null || true + fi + } -# Step 2: Populate README with addons list -find . -maxdepth 1 -type d ! -name '.' | sort -r | while read -r f; do - CONFIG="" - [ -f "$f/config.json" ] && CONFIG="$f/config.json" - [ -z "$CONFIG" ] && [ -f "$f/config.yaml" ] && CONFIG="$f/config.yaml" - [ -z "$CONFIG" ] && continue + # --------------------------- + # Prepare template + # --------------------------- + cp .templates/.README.md README2.md + ADDONSLINE="$(sed -n '/%%ADDONS_LIST%%/=' README2.md)" + # Keep the placeholder line so INSERT index stays stable + # sed -i '/%%ADDONS_LIST%%/d' README2.md - # Helper functions - get_field() { - local field="$1" - if [[ "$CONFIG" == *.json ]]; then - jq -r "$field // empty" "$CONFIG" - else - yq -r "$field // empty" "$CONFIG" - fi - } + # --------------------------- + # Sort folders by addon name (supports config.json & config.yaml/.yml) + # --------------------------- + for f in $( find -- * -maxdepth 0 -type d | sort -r ); do + IFS='|' read -r CONFIG TYPE <<<"$(get_config "$f")" + if [ -n "$CONFIG" ]; then + NAME="$(get_raw '.name' "$TYPE" "$CONFIG")" + if [ -n "${NAME:-}" ] && [ "$f" != "$NAME" ]; then + echo "$f" > "$f/oldname" + mv "$f" "$NAME" + fi + fi + done - get_array() { - local field="$1" - if [[ "$CONFIG" == *.json ]]; then - jq -r "$field // empty | @sh" "$CONFIG" | tr -d "'" - else - yq -r "$field // empty | @sh" "$CONFIG" | tr -d "'" - fi - } + # --------------------------- + # Populate template + # --------------------------- + find -- * -maxdepth 0 -type d | sort -r | while read -r f; do + IFS='|' read -r CONFIG TYPE <<<"$(get_config "$f")" + if [ -n "$CONFIG" ]; then + echo "Project $f" - # Info extraction - if [ -f "$f/oldname" ]; then FOLDERNAME="$(cat "$f/oldname")"; else FOLDERNAME="${f#./}"; fi - NAME="$(get_field '.name')" - DESCRIPTION="$(get_field '.description')" + # Get variables + if [ -f "$f/oldname" ]; then FOLDERNAME="$(cat "$f/oldname")"; else FOLDERNAME="$f"; fi + NAME="$(get_raw '.name' "$TYPE" "$CONFIG")" + DESCRIPTION="$(get_raw '.description' "$TYPE" "$CONFIG")" - # Icon - ICON_RAW="$(get_field '.panel_icon')" - if [[ "$ICON_RAW" != "null" && -n "$ICON_RAW" ]]; then - ICON="${ICON_RAW#*:}" - ICON="![image](https://api.iconify.design/mdi/$ICON.svg)" - else - ICON="" - fi + # Icon + PANEL_ICON_RAW="$(get_raw '.panel_icon' "$TYPE" "$CONFIG")" + if [ -n "$PANEL_ICON_RAW" ] && [ "$PANEL_ICON_RAW" != "null" ]; then + ICON="${PANEL_ICON_RAW#*:}" + ICON="![image](https://api.iconify.design/mdi/$ICON.svg)" + else + ICON="" + fi - # Insert new line for this addon - sed -i "$ADDONSLINE"'{G;}' README2.md + # Derived / checks + SCHEMA_JSON="$(get_jsonc '.schema' "$TYPE" "$CONFIG")" + SERVICES_LIST="$(get_raw '.services[]' "$TYPE" "$CONFIG")" + ARCH_LIST="$(get_raw '.arch[]' "$TYPE" "$CONFIG")" + FULL_ACCESS="$(get_raw '.full_access' "$TYPE" "$CONFIG")" + INGRESS="$(get_raw '.ingress' "$TYPE" "$CONFIG")" - # Badges - [[ "$(get_field '.schema')" == *"localdisks"* ]] && sed -i "$ADDONSLINE"'a ![localdisks][localdisks-badge]' README2.md - [[ "$(get_field '.schema')" == *"networkdisks"* ]] && sed -i "$ADDONSLINE"'a ![smb][smb-badge]' README2.md - [[ "$(get_field '.full_access')" == "true" ]] && sed -i "$ADDONSLINE"'a ![full_access][full_access-badge]' README2.md + # Write infos + echo "Writing infos" + sed -i "$ADDONSLINE"'{G;}' README2.md - SERVICES="$(get_array '.services[]')" - [[ "$SERVICES" == *"mqtt"* ]] && sed -i "$ADDONSLINE"'a ![mqtt][mqtt-badge]' README2.md - [[ "$SERVICES" == *"mysql"* ]] && sed -i "$ADDONSLINE"'a ![MariaDB][mariadb-badge]' README2.md + if [[ "$SCHEMA_JSON" == *"localdisks"* ]]; then sed -i "$ADDONSLINE"'a ![localdisks][localdisks-badge]' README2.md; fi + if [[ "$SCHEMA_JSON" == *"networkdisks"* ]]; then sed -i "$ADDONSLINE"'a ![smb][smb-badge]' README2.md; fi + if [[ "$FULL_ACCESS" == "true" ]]; then sed -i "$ADDONSLINE"'a ![full_access][full_access-badge]' README2.md; fi + if [[ "$SERVICES_LIST" == *"mqtt"* ]]; then sed -i "$ADDONSLINE"'a ![mqtt][mqtt-badge]' README2.md; fi + if [[ "$SERVICES_LIST" == *"mysql"* ]]; then sed -i "$ADDONSLINE"'a ![MariaDB][mariadb-badge]' README2.md; fi + if [[ "$INGRESS" == "true" ]]; then sed -i "$ADDONSLINE"'a ![ingress][ingress-badge]' README2.md; fi - [[ "$(get_field '.ingress')" == "true" ]] && sed -i "$ADDONSLINE"'a ![ingress][ingress-badge]' README2.md + if [[ "$ARCH_LIST" == *"armv7"* ]]; then + sed -i "$ADDONSLINE"'a ![armv7][armv7-badge]' README2.md + else + sed -i "$ADDONSLINE"'a ![armv7no][armv7no-badge]' README2.md + fi || true - ARCHS="$(get_array '.arch[]')" - [[ "$ARCHS" == *"armv7"* ]] && sed -i "$ADDONSLINE"'a ![armv7][armv7-badge]' README2.md || sed -i "$ADDONSLINE"'a ![armv7no][armv7no-badge]' README2.md - [[ "$ARCHS" == *"amd64"* ]] && sed -i "$ADDONSLINE"'a ![amd64][amd64-badge]' README2.md || sed -i "$ADDONSLINE"'a ![amd64no][amd64no-badge]' README2.md - [[ "$ARCHS" == *"aarch64"* ]] && sed -i "$ADDONSLINE"'a ![aarch64][aarch64-badge]' README2.md || sed -i "$ADDONSLINE"'a ![aarch64no][aarch64no-badge]' README2.md + if [[ "$ARCH_LIST" == *"amd64"* ]]; then + sed -i "$ADDONSLINE"'a ![amd64][amd64-badge]' README2.md + else + sed -i "$ADDONSLINE"'a ![amd64no][amd64no-badge]' README2.md + fi || true - [ -f "$f/updater.json" ] && sed -i "$ADDONSLINE"'a ![Update](https://img.shields.io/badge/dynamic/json?label=Updated&query=%24.last_update&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2F'"$FOLDERNAME"'%2Fupdater.json)' README2.md + if [[ "$ARCH_LIST" == *"aarch64"* ]]; then + sed -i "$ADDONSLINE"'a ![aarch64][aarch64-badge]' README2.md + else + sed -i "$ADDONSLINE"'a ![aarch64no][aarch64no-badge]' README2.md + fi || true - if [[ "$CONFIG" == *.json ]]; then - sed -i "$ADDONSLINE"'a   ![Version](https://img.shields.io/badge/dynamic/json?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2F'"$FOLDERNAME"'%2Fconfig.json)' README2.md - else - sed -i "$ADDONSLINE"'a   ![Version](https://img.shields.io/badge/version-yaml-blue)' README2.md - fi + if [[ -f "$f/updater.json" ]]; then + sed -i "$ADDONSLINE"'a ![Update](https://img.shields.io/badge/dynamic/json?label=Updated&query=%24.last_update&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2F'"$FOLDERNAME"'%2Fupdater.json)' README2.md + fi - sed -i "$ADDONSLINE"'a ✓ '"$ICON"' ['"$NAME"']('"$FOLDERNAME"'/) : '"$DESCRIPTION\\n" README2.md -done + # Version badge: JSON vs YAML endpoint + if [ "$TYPE" = "json" ]; then + sed -i "$ADDONSLINE"'a   ![Version](https://img.shields.io/badge/dynamic/json?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2F'"$FOLDERNAME"'%2Fconfig.json)' README2.md || true + else + sed -i "$ADDONSLINE"'a   ![Version](https://img.shields.io/badge/dynamic/yaml?label=Version&query=%24.version&url=https%3A%2F%2Fraw.githubusercontent.com%2Falexbelgium%2Fhassio-addons%2Fmaster%2F'"$FOLDERNAME"'%2Fconfig.yaml)' README2.md || true + fi -# Step 3: Restore folder names if changed -echo "Restore structure..." -find . -maxdepth 1 -type d ! -name '.' | sort -r | while read -r f; do - [ -f "$f/oldname" ] && NAME="$(cat "$f/oldname")" && rm "$f/oldname" && mv "$f" "./$NAME" -done -echo "... done" + sed -i "$ADDONSLINE"'a ✓ '"$ICON"' ['"$NAME"']('"$FOLDERNAME"'/) : '"$DESCRIPTION\\n" README2.md + fi + done -# Step 4: Stats (count both config.json and config.yaml) -echo "Global stats..." -STATS_DOWNLOADS="$(awk 'NR==2{print $1}' Stats)" -TOTAL_ADDONS=$(find . -type f \( -name "config.json" -o -name "config.yaml" \) | wc -l) -sed -i "s|%%STATS_DOWNLOADS%%|$STATS_DOWNLOADS|g" README2.md -sed -i "s|%%STATS_ADDONS%%|$TOTAL_ADDONS|g" README2.md -STATS_ONE="$(awk 'NR==3{print $(NF)}' Stats)" -STATS_TWO="$(awk 'NR==4{print $(NF)}' Stats)" -STATS_THREE="$(awk 'NR==5{print $(NF)}' Stats)" -sed -i "s|%%STATS_ONE%%|${STATS_ONE^}|g" README2.md -sed -i "s|%%STATS_TWO%%|${STATS_TWO^}|g" README2.md -sed -i "s|%%STATS_THREE%%|${STATS_THREE^}|g" README2.md -echo "... done" + # --------------------------- + # Restore folders name + # --------------------------- + echo "Restore structure..." + find -- * -maxdepth 0 -type d | sort -r | while read -r f; do + if [ -f "$f/oldname" ]; then + NAME="$(cat "$f/oldname")" + rm "$f/oldname" + mv "$f" "$NAME" + fi + done + echo "... done" -# Add any additional stats or breakdowns here as in your original + # --------------------------- + # Global stats + # --------------------------- + echo "Global stats..." + # shellcheck disable=SC2002 + STATS_DOWNLOADS="$(awk 'NR==2{print $1}' Stats)" + sed -i "s|%%STATS_DOWNLOADS%%|$STATS_DOWNLOADS|g" README2.md && \ + # Count addons having either config.json or config.yaml/.yml (unique folders) + sed -i "s|%%STATS_ADDONS%%|$(find . -type f \( -name 'config.json' -o -name 'config.yaml' -o -name 'config.yml' \) -printf '%h\n' | sort -u | wc -l)|g" README2.md && \ + STATS_ONE="$(awk 'NR==3{print $(NF)}' Stats)" && \ + STATS_TWO="$(awk 'NR==4{print $(NF)}' Stats)" && \ + STATS_THREE="$(awk 'NR==5{print $(NF)}' Stats)" + echo "Best addon is $STATS_ONE" + sed -i "s|%%STATS_ONE%%|${STATS_ONE^}|g" README2.md + sed -i "s|%%STATS_TWO%%|${STATS_TWO^}|g" README2.md + sed -i "s|%%STATS_THREE%%|${STATS_THREE^}|g" README2.md + echo "... done" -# Final: Replace template if changed -mv README2.md README.md -echo "... done" + # --------------------------- + # Breakdown per arch + # --------------------------- + echo "Breakdown per arch..." + STATS_ARMV7="$(awk '{SUM+=$3}END{print SUM}' Stats2)" + STATS_AMD64="$(awk '{SUM+=$4}END{print SUM}' Stats2)" + STATS_AARCH64="$(awk '{SUM+=$5}END{print SUM}' Stats2)" + STATS_DOWNLOADS="$(( STATS_ARMV7 + STATS_AMD64 + STATS_AARCH64 ))" + STATS_ARMV7="$(awk -v t1="$STATS_ARMV7" -v t4="$STATS_DOWNLOADS" 'BEGIN{printf "%.0f", (t4==0?0:t1/t4*100)}')" + STATS_AMD64="$(awk -v t2="$STATS_AMD64" -v t4="$STATS_DOWNLOADS" 'BEGIN{printf "%.0f", (t4==0?0:t2/t4*100)}')" + STATS_AARCH64="$(awk -v t3="$STATS_AARCH64" -v t4="$STATS_DOWNLOADS" 'BEGIN{printf "%.0f", (t4==0?0:t3/t4*100)}')" + sed -i "s|%%STATS_ARMV7%%|armv7: ${STATS_ARMV7}%|g" README2.md + sed -i "s|%%STATS_AMD64%%|amd64: ${STATS_AMD64}%|g" README2.md + sed -i "s|%%STATS_AARCH64%%|aarch64: ${STATS_AARCH64}%|g" README2.md + echo "... done" + + for var in "$STATS_ONE" "$STATS_TWO" "$STATS_THREE"; do + i=0 + j=0 + k=0 + # shellcheck disable=SC2013 + for i in $(sed -n "/$var/p" Stats); do + k="$((k+1))" + if [ "$k" -eq 3 ]; then break; fi + if [ "$i" -eq "$i" ] 2>/dev/null && [ "$i" -gt "$j" ]; then j="$i"; fi + done + sed -i "s|${var^}|${var^} (${j}x)|g" README2.md + echo "$STATS_ONE has $j downloads" + done + echo "... done" + + # --------------------------- + # Replace template if change + # --------------------------- + echo "Replace template..." + mv README2.md README.md + echo "... done" + + - name: Commit if needed + uses: EndBug/add-and-commit@v9 + with: + message: "GitHub bot : README updated" + default_author: github_actions