aboutsummaryrefslogtreecommitdiffstats
path: root/Makefile
diff options
context:
space:
mode:
Diffstat (limited to 'Makefile')
-rw-r--r--Makefile253
1 files changed, 253 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..de17745
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,253 @@
+# yt-local Makefile
+# Automated tasks for development, translations, and maintenance
+
+.PHONY: help install dev clean test i18n-extract i18n-init i18n-update \
+ i18n-compile i18n-stats i18n-clean setup-dev lint format backup \
+ restore distclean info check-deps run test-cov i18n-workflow \
+ ensure-venv
+
+# Variables
+SYSTEM_PYTHON := python3
+LANG_CODE ?= es
+VENV_DIR := venv
+PROJECT_NAME := yt-local
+
+# Use venv python/pip when the venv exists, system otherwise
+VENV_PYTHON := $(VENV_DIR)/bin/python
+VENV_PIP := $(VENV_DIR)/bin/pip
+PYTHON = $(if $(wildcard $(VENV_PYTHON)),$(VENV_PYTHON),$(SYSTEM_PYTHON))
+PIP = $(if $(wildcard $(VENV_PIP)),$(VENV_PIP),$(SYSTEM_PYTHON) -m pip)
+
+# Patterns for release artefacts (generate_release.py)
+RELEASE_DIR := yt-local
+RELEASE_GLOBS := yt-local-*.zip python-dist-*.zip
+RELEASE_DOWNLOADS := get-pip.py vc15_*.7z
+PYTHON_DIST_DIR := python
+
+# Validate LANG_CODE: only allow letters and underscore, 2-5 chars (e.g. es, pt_BR)
+# Guards against shell injection via make i18n-init LANG_CODE="$(malicious)"
+LANG_CODE_SAFE := $(shell echo '$(LANG_CODE)' | grep -xE '[a-zA-Z_]{2,5}' || echo '')
+
+## Help -----------------------------------------------------------------------
+
+help: ## Show this help message
+ @echo "$(PROJECT_NAME) - Available tasks:"
+ @echo ""
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
+ awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}'
+ @echo ""
+ @echo "Examples:"
+ @echo " make install # Install dependencies"
+ @echo " make dev # Run development server"
+ @echo " make i18n-extract # Extract strings for translation"
+ @echo " make i18n-init LANG_CODE=fr # Initialize French"
+ @echo " make lint # Check code style"
+
+## Venv bootstrap (internal) --------------------------------------------------
+
+ensure-venv:
+ @if [ ! -f "$(VENV_PYTHON)" ]; then \
+ echo "[INFO] Creating virtual environment in $(VENV_DIR)..."; \
+ $(SYSTEM_PYTHON) -m venv $(VENV_DIR); \
+ if [ -f requirements-dev.txt ]; then \
+ echo "[INFO] Installing dependencies (dev)..."; \
+ $(VENV_DIR)/bin/pip install -r requirements-dev.txt; \
+ else \
+ echo "[INFO] Installing dependencies..."; \
+ $(VENV_DIR)/bin/pip install -r requirements.txt; \
+ fi; \
+ echo "[SUCCESS] Virtual environment ready"; \
+ fi
+
+## Installation and Setup -----------------------------------------------------
+
+install: ensure-venv ## Install/update project dependencies
+ @echo "[INFO] Installing dependencies..."
+ $(PIP) install -r requirements.txt
+ @echo "[SUCCESS] Dependencies installed"
+
+setup-dev: ensure-venv ## Install dev dependencies (tests, linting)
+ @echo "[INFO] Installing dev dependencies..."
+ $(PIP) install -r requirements-dev.txt
+ @echo "[SUCCESS] Dev dependencies installed"
+
+## Development ----------------------------------------------------------------
+
+dev: ensure-venv ## Run development server
+ @echo "[INFO] Starting development server..."
+ @echo "[INFO] Server available at: http://localhost:9010"
+ $(PYTHON) server.py
+
+run: dev ## Alias for dev
+
+## Testing --------------------------------------------------------------------
+
+test: ensure-venv ## Run tests
+ @echo "[INFO] Running tests..."
+ @if [ -d "tests" ]; then \
+ $(PYTHON) -m pytest -v; \
+ else \
+ echo "[WARN] No tests directory found"; \
+ fi
+
+test-cov: ensure-venv ## Run tests with coverage
+ @echo "[INFO] Running tests with coverage..."
+ @$(PYTHON) -c "import pytest_cov" 2>/dev/null && \
+ $(PYTHON) -m pytest -v --cov=youtube --cov-report=html || \
+ echo "[WARN] pytest-cov not installed. Run: make setup-dev"
+
+## Internationalization (i18n) ------------------------------------------------
+
+i18n-extract: ensure-venv ## Extract strings for translation
+ @echo "[INFO] Extracting strings for translation..."
+ $(PYTHON) manage_translations.py extract
+ @echo "[SUCCESS] Strings extracted to translations/messages.pot"
+
+i18n-init: ensure-venv ## Initialize new language (use LANG_CODE=xx)
+ @if [ -z "$(LANG_CODE_SAFE)" ]; then \
+ echo "[ERROR] Invalid LANG_CODE='$(LANG_CODE)'. Use 2-5 letters (e.g. es, pt_BR)."; \
+ exit 1; \
+ fi
+ @echo "[INFO] Initializing language: $(LANG_CODE_SAFE)"
+ $(PYTHON) manage_translations.py init $(LANG_CODE_SAFE)
+ @echo "[SUCCESS] Language $(LANG_CODE_SAFE) initialized"
+ @echo "[INFO] Edit: translations/$(LANG_CODE_SAFE)/LC_MESSAGES/messages.po"
+
+i18n-update: ensure-venv ## Update existing translations
+ @echo "[INFO] Updating existing translations..."
+ $(PYTHON) manage_translations.py update
+ @echo "[SUCCESS] Translations updated"
+
+i18n-compile: ensure-venv ## Compile translations to binary .mo files
+ @echo "[INFO] Compiling translations..."
+ $(PYTHON) manage_translations.py compile
+ @echo "[SUCCESS] Translations compiled"
+
+i18n-stats: ## Show translation statistics
+ @echo "[INFO] Translation statistics:"
+ @echo ""
+ @for lang_dir in translations/*/; do \
+ if [ -d "$$lang_dir" ] && [ "$$lang_dir" != "translations/*/" ]; then \
+ lang=$$(basename "$$lang_dir"); \
+ po_file="$$lang_dir/LC_MESSAGES/messages.po"; \
+ if [ -f "$$po_file" ]; then \
+ total=$$(grep -c "^msgid " "$$po_file" 2>/dev/null || echo "0"); \
+ translated=$$(grep -c '^msgstr "[^"]' "$$po_file" 2>/dev/null || echo "0"); \
+ fuzzy=$$(grep -c "^#, fuzzy" "$$po_file" 2>/dev/null || echo "0"); \
+ if [ "$$total" -gt 0 ]; then \
+ percent=$$((translated * 100 / total)); \
+ echo " [STAT] $$lang: $$translated/$$total ($$percent%) - Fuzzy: $$fuzzy"; \
+ else \
+ echo " [STAT] $$lang: No translations yet"; \
+ fi; \
+ fi; \
+ fi; \
+ done
+ @echo ""
+
+i18n-clean: ## Clean compiled translation files (.mo only)
+ @echo "[INFO] Cleaning compiled .mo files..."
+ find translations/ -name "*.mo" -delete 2>/dev/null || true
+ @echo "[SUCCESS] .mo files removed"
+
+i18n-workflow: i18n-extract i18n-update i18n-compile i18n-stats ## Complete workflow: extract → update → compile
+ @echo "[SUCCESS] Translation workflow completed"
+
+## Code Quality ---------------------------------------------------------------
+
+lint: ensure-venv ## Check code with flake8
+ @echo "[INFO] Checking code style..."
+ @$(PYTHON) -c "import flake8" 2>/dev/null && \
+ $(PYTHON) -m flake8 youtube/ --max-line-length=120 --ignore=E501,W503,E402 \
+ --exclude=youtube/ytdlp_service.py,youtube/ytdlp_integration.py,youtube/ytdlp_proxy.py && \
+ echo "[SUCCESS] Code style check passed" || \
+ echo "[WARN] flake8 not installed. Run: make setup-dev"
+
+format: ensure-venv ## Format code with black (if available)
+ @echo "[INFO] Formatting code..."
+ @$(PYTHON) -c "import black" 2>/dev/null && \
+ $(PYTHON) -m black youtube/ --line-length=120 --exclude='ytdlp_.*\.py' && \
+ echo "[SUCCESS] Code formatted" || \
+ echo "[WARN] black not installed. Run: make setup-dev"
+
+check-deps: ensure-venv ## Check installed dependencies
+ @echo "[INFO] Checking dependencies..."
+ @$(PYTHON) -c "import flask_babel; print('[OK] Flask-Babel:', flask_babel.__version__)" 2>/dev/null || echo "[ERROR] Flask-Babel not installed"
+ @$(PYTHON) -c "import flask; print('[OK] Flask:', flask.__version__)" 2>/dev/null || echo "[ERROR] Flask not installed"
+ @$(PYTHON) -c "import yt_dlp; print('[OK] yt-dlp:', yt_dlp.__version__)" 2>/dev/null || echo "[ERROR] yt-dlp not installed"
+
+## Maintenance ----------------------------------------------------------------
+
+backup: ## Create translations backup
+ @echo "[INFO] Creating translations backup..."
+ @timestamp=$$(date +%Y%m%d_%H%M%S); \
+ tar -czf "translations_backup_$$timestamp.tar.gz" translations/ 2>/dev/null || echo "[WARN] No translations to backup"; \
+ if [ -f "translations_backup_$$timestamp.tar.gz" ]; then \
+ echo "[SUCCESS] Backup created: translations_backup_$$timestamp.tar.gz"; \
+ fi
+
+restore: ## Restore translations from latest backup
+ @echo "[INFO] Restoring translations from backup..."
+ @latest_backup=$$(find . -maxdepth 1 -name 'translations_backup_*.tar.gz' -print0 | \
+ xargs -0 ls -t 2>/dev/null | head -1); \
+ if [ -n "$$latest_backup" ]; then \
+ tar -xzf "$$latest_backup"; \
+ echo "[SUCCESS] Restored from: $$latest_backup"; \
+ else \
+ echo "[ERROR] No backup files found"; \
+ fi
+
+## Cleanup --------------------------------------------------------------------
+
+clean: ## Clean temporary files, caches, and release artefacts
+ @echo "[INFO] Cleaning temporary files..."
+ # Python byte-code and caches
+ find . -type f -name "*.pyc" -delete
+ find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
+ # Compiled translation files (project-wide)
+ find . -type f -name "*.mo" -delete
+ # Pytest cache and coverage data
+ find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true
+ find . -type d -name "htmlcov" -exec rm -rf {} + 2>/dev/null || true
+ find . -type f -name ".coverage" -delete
+ # Misc temporary / backup artefacts
+ find . -type f \( -name "*.tmp" -o -name "*.bak" -o -name "*.log" \) -delete
+ find . -type d -name "*.cache" -exec rm -rf {} + 2>/dev/null || true
+ # Checksum files
+ find . -type f \( -name "*.sha512sum" -o -name "*.sha256sum" -o -name "*.sha1sum" \
+ -o -name "*.md5sum" -o -name "*.b2sum" \) -delete
+ # Release artefacts (generate_release.py)
+ rm -rf $(RELEASE_DIR)/ $(PYTHON_DIST_DIR)/
+ rm -f $(RELEASE_GLOBS) $(RELEASE_DOWNLOADS)
+ @echo "[SUCCESS] Temporary files removed"
+
+distclean: clean ## Clean everything including venv
+ @echo "[INFO] Cleaning everything..."
+ rm -rf $(VENV_DIR)
+ @echo "[SUCCESS] Complete cleanup done"
+
+## Project Information --------------------------------------------------------
+
+info: ensure-venv ## Show project information
+ @echo "[INFO] $(PROJECT_NAME) - Project information:"
+ @echo ""
+ @echo " [INFO] Directory: $$(pwd)"
+ @echo " [INFO] Python: $$($(PYTHON) --version 2>&1)"
+ @echo " [INFO] Pip: $$($(PIP) --version 2>&1 | cut -d' ' -f1-2)"
+ @echo ""
+ @echo " [INFO] Configured languages:"
+ @for lang_dir in translations/*/; do \
+ if [ -d "$$lang_dir" ] && [ "$$lang_dir" != "translations/*/" ]; then \
+ echo " - $$(basename $$lang_dir)"; \
+ fi; \
+ done
+ @echo ""
+ @echo " [INFO] Main files:"
+ @echo " - babel.cfg (i18n configuration)"
+ @echo " - manage_translations.py (i18n CLI)"
+ @echo " - youtube/i18n_strings.py (centralized strings)"
+ @echo " - youtube/ytdlp_service.py (yt-dlp integration)"
+ @echo ""
+
+# Default target
+.DEFAULT_GOAL := help