name: Documentation on: push: branches: [ main, develop ] paths: - 'docs/**' - 'README.md' - 'CONTRIBUTING.md' - 'src/**/*.py' pull_request: paths: - 'docs/**' - 'README.md' - 'CONTRIBUTING.md' workflow_dispatch: env: PYTHON_VERSION: '3.11' jobs: # Validate documentation validate-docs: name: Validate Documentation runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: token: ${{ secrets.GITEA_TOKEN }} - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install documentation tools run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install sphinx sphinx-rtd-theme sphinx-autodoc-typehints pip install markdown-link-check || true - name: Check markdown links run: | echo "🔗 Checking markdown links..." find . -name "*.md" -not -path "./.git/*" -not -path "./node_modules/*" | while read file; do echo "Checking $file" # Basic link validation (you can enhance this) grep -o 'http[s]*://[^)]*' "$file" | while read url; do if curl -s --head "$url" | head -n 1 | grep -q "200 OK"; then echo "✅ $url" else echo "❌ $url (in $file)" fi done done - name: Validate README structure run: | echo "📋 Validating README structure..." required_sections=( "# Northern Thailand Ping River Monitor" "## Features" "## Quick Start" "## Installation" "## Usage" "## API Endpoints" "## Docker" "## Contributing" "## License" ) for section in "${required_sections[@]}"; do if grep -q "$section" README.md; then echo "✅ Found: $section" else echo "❌ Missing: $section" fi done - name: Check documentation completeness run: | echo "📚 Checking documentation completeness..." # Check if all Python modules have docstrings python -c " import ast import os def check_docstrings(filepath): with open(filepath, 'r', encoding='utf-8') as f: tree = ast.parse(f.read()) missing_docstrings = [] for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)): if not ast.get_docstring(node): missing_docstrings.append(f'{node.name} in {filepath}') return missing_docstrings all_missing = [] for root, dirs, files in os.walk('src'): for file in files: if file.endswith('.py') and not file.startswith('__'): filepath = os.path.join(root, file) missing = check_docstrings(filepath) all_missing.extend(missing) if all_missing: print('⚠️ Missing docstrings:') for item in all_missing[:10]: # Show first 10 print(f' - {item}') if len(all_missing) > 10: print(f' ... and {len(all_missing) - 10} more') else: print('✅ All functions and classes have docstrings') " # Generate API documentation generate-api-docs: name: Generate API Documentation runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: token: ${{ secrets.GITEA_TOKEN }} - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - name: Generate OpenAPI spec run: | echo "📝 Generating OpenAPI specification..." python -c " import json import sys sys.path.insert(0, 'src') try: from web_api import app openapi_spec = app.openapi() with open('openapi.json', 'w') as f: json.dump(openapi_spec, f, indent=2) print('✅ OpenAPI spec generated: openapi.json') except Exception as e: print(f'❌ Failed to generate OpenAPI spec: {e}') " - name: Generate API documentation run: | echo "📖 Generating API documentation..." # Create API documentation from OpenAPI spec if [ -f openapi.json ]; then cat > api-docs.md << 'EOF' # API Documentation This document describes the REST API endpoints for the Northern Thailand Ping River Monitor. ## Base URL - Production: `https://ping-river-monitor.b4l.co.th` - Staging: `https://staging.ping-river-monitor.b4l.co.th` - Development: `http://localhost:8000` ## Authentication Currently, the API does not require authentication. This may change in future versions. ## Endpoints EOF # Extract endpoints from OpenAPI spec python -c " import json with open('openapi.json', 'r') as f: spec = json.load(f) for path, methods in spec.get('paths', {}).items(): for method, details in methods.items(): print(f'### {method.upper()} {path}') print() print(details.get('summary', 'No description available')) print() if 'parameters' in details: print('**Parameters:**') for param in details['parameters']: print(f'- `{param[\"name\"]}` ({param.get(\"in\", \"query\")}): {param.get(\"description\", \"No description\")}') print() print('---') print() " >> api-docs.md echo "✅ API documentation generated: api-docs.md" fi - name: Upload documentation artifacts uses: actions/upload-artifact@v3 with: name: documentation-${{ github.run_number }} path: | openapi.json api-docs.md # Build Sphinx documentation build-sphinx-docs: name: Build Sphinx Documentation runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: token: ${{ secrets.GITEA_TOKEN }} - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install sphinx sphinx-rtd-theme sphinx-autodoc-typehints - name: Create Sphinx configuration run: | mkdir -p docs/sphinx cat > docs/sphinx/conf.py << 'EOF' import os import sys sys.path.insert(0, os.path.abspath('../../src')) project = 'Northern Thailand Ping River Monitor' copyright = '2025, Ping River Monitor Team' author = 'Ping River Monitor Team' version = '3.1.3' release = '3.1.3' extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon', 'sphinx_autodoc_typehints', ] templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] autodoc_default_options = { 'members': True, 'member-order': 'bysource', 'special-members': '__init__', 'undoc-members': True, 'exclude-members': '__weakref__' } EOF cat > docs/sphinx/index.rst << 'EOF' Northern Thailand Ping River Monitor Documentation ================================================ .. toctree:: :maxdepth: 2 :caption: Contents: modules Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` EOF - name: Generate module documentation run: | cd docs/sphinx sphinx-apidoc -o . ../../src - name: Build documentation run: | cd docs/sphinx sphinx-build -b html . _build/html - name: Upload Sphinx documentation uses: actions/upload-artifact@v3 with: name: sphinx-docs-${{ github.run_number }} path: docs/sphinx/_build/html/ # Documentation summary docs-summary: name: Documentation Summary runs-on: ubuntu-latest needs: [validate-docs, generate-api-docs, build-sphinx-docs] if: always() steps: - name: Generate documentation summary run: | echo "# 📚 Documentation Build Summary" > docs-summary.md echo "" >> docs-summary.md echo "**Build Date:** $(date -u)" >> docs-summary.md echo "**Repository:** ${{ github.repository }}" >> docs-summary.md echo "**Commit:** ${{ github.sha }}" >> docs-summary.md echo "" >> docs-summary.md echo "## 📊 Results" >> docs-summary.md echo "" >> docs-summary.md if [ "${{ needs.validate-docs.result }}" = "success" ]; then echo "- ✅ **Documentation Validation**: Passed" >> docs-summary.md else echo "- ❌ **Documentation Validation**: Failed" >> docs-summary.md fi if [ "${{ needs.generate-api-docs.result }}" = "success" ]; then echo "- ✅ **API Documentation**: Generated" >> docs-summary.md else echo "- ❌ **API Documentation**: Failed" >> docs-summary.md fi if [ "${{ needs.build-sphinx-docs.result }}" = "success" ]; then echo "- ✅ **Sphinx Documentation**: Built" >> docs-summary.md else echo "- ❌ **Sphinx Documentation**: Failed" >> docs-summary.md fi echo "" >> docs-summary.md echo "## 🔗 Available Documentation" >> docs-summary.md echo "" >> docs-summary.md echo "- [README.md](../README.md)" >> docs-summary.md echo "- [API Documentation](../docs/)" >> docs-summary.md echo "- [Contributing Guide](../CONTRIBUTING.md)" >> docs-summary.md echo "- [Deployment Checklist](../DEPLOYMENT_CHECKLIST.md)" >> docs-summary.md cat docs-summary.md - name: Upload documentation summary uses: actions/upload-artifact@v3 with: name: docs-summary-${{ github.run_number }} path: docs-summary.md