Skip to content

Commit aa9213c

Browse files
committed
Update CI-Workflow-Update, Requirements und MkDocs-Sync
This pull request updates the CI workflow configuration, refreshes the `requirements.txt` file with potentially new or updated dependencies, and synchronizes the `mkdocs.yml` configuration files across different language versions (likely `de` and `en`) to ensure consistency in the documentation build process. - Add "Validate internal links in Markdown" runner - Add "Validate image links in Markdown" runner - Remove obsolet Md-files and structure problems - Image replaced by default theme color (default is the theme “Light”, not “Dark-Mode”) - Fix Options in de docs - Synchronization of the installation process German - English - Fixing links... links... and more links... :-()
1 parent e2dd14c commit aa9213c

27 files changed

Lines changed: 366 additions & 90 deletions

File tree

.github/scripts/validate_images.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import re
2+
import os
3+
import sys
4+
import requests
5+
6+
# Define the directories and their language labels
7+
DOCS_DIRS = {
8+
'de': os.path.join('docs', 'de'),
9+
'en': os.path.join('docs', 'en'),
10+
}
11+
12+
# Timeouts for external image check (in seconds)
13+
HTTP_TIMEOUT = 5
14+
15+
def find_markdown_files(base_dirs):
16+
md_files = []
17+
for lang, base_dir in base_dirs.items():
18+
if not os.path.exists(base_dir):
19+
print(f"⚠️ Warning: Directory '{base_dir}' [{lang}] does not exist. Skipping.")
20+
continue
21+
for root, dirs, files in os.walk(base_dir):
22+
for file in files:
23+
if file.endswith(".md"):
24+
md_files.append((lang, os.path.join(root, file)))
25+
return md_files
26+
27+
def extract_image_paths_with_line_numbers(md_file):
28+
image_paths_with_lines = []
29+
pattern = re.compile(r'!\[[^\]]*\]\(([^)]+)\)')
30+
31+
with open(md_file, 'r', encoding='utf-8') as f:
32+
for line_num, line in enumerate(f, 1):
33+
image_paths = pattern.findall(line)
34+
for path in image_paths:
35+
image_paths_with_lines.append((line_num, path))
36+
37+
return image_paths_with_lines
38+
39+
def validate_image_paths(md_files, check_external=True):
40+
missing_images = []
41+
broken_links = []
42+
43+
for lang, md_file in md_files:
44+
image_paths_with_lines = extract_image_paths_with_line_numbers(md_file)
45+
for line_num, path in image_paths_with_lines:
46+
path = path.strip()
47+
if path.startswith("http://") or path.startswith("https://"):
48+
if check_external:
49+
try:
50+
resp = requests.head(path, timeout=HTTP_TIMEOUT, allow_redirects=True)
51+
if resp.status_code >= 400:
52+
broken_links.append((lang, md_file, path, resp.status_code, line_num))
53+
except requests.RequestException as e:
54+
broken_links.append((lang, md_file, path, str(e), line_num))
55+
continue # Skip local check for external links
56+
57+
# Check local image paths
58+
abs_path = os.path.normpath(os.path.join(os.path.dirname(md_file), path))
59+
if not os.path.exists(abs_path):
60+
missing_images.append((lang, md_file, path, line_num))
61+
62+
return missing_images, broken_links
63+
64+
if __name__ == "__main__":
65+
print("🔍 Scanning markdown files in: " + ", ".join([f"{lang} ({dir})" for lang, dir in DOCS_DIRS.items()]))
66+
67+
md_files = find_markdown_files(DOCS_DIRS)
68+
if not md_files:
69+
print("⚠️ No Markdown files found in specified directories.")
70+
sys.exit(0)
71+
72+
# Validate images (local and optionally external)
73+
missing_images, broken_links = validate_image_paths(md_files, check_external=True)
74+
75+
has_errors = False
76+
77+
if missing_images:
78+
print("\n❌ Missing local images found:")
79+
for lang, md_file, img_path, line_num in missing_images:
80+
print(f" [{lang}] In file '{md_file}' at line {line_num}: Image '{img_path}' is missing")
81+
has_errors = True
82+
83+
if broken_links:
84+
print("\n❌ Broken external image links found:")
85+
for lang, md_file, url, error, line_num in broken_links:
86+
print(f" [{lang}] In file '{md_file}' at line {line_num}: External image URL '{url}' failed ({error})")
87+
has_errors = True
88+
89+
if has_errors:
90+
sys.exit(1)
91+
else:
92+
print("✅ All referenced images (local & external) exist and are valid.")
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import re
2+
import os
3+
import sys
4+
import requests
5+
import time
6+
7+
# Define the directories and their language labels
8+
DOCS_DIRS = {
9+
'de': os.path.join('docs', 'de'),
10+
'en': os.path.join('docs', 'en'),
11+
}
12+
13+
# Timeouts for external link check (in seconds)
14+
HTTP_TIMEOUT = 5
15+
16+
# Retry settings
17+
MAX_RETRIES = 3
18+
RETRY_DELAY = 5 # seconds
19+
20+
# Custom headers to simulate a normal web browser request
21+
HEADERS = {
22+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
23+
}
24+
25+
def find_markdown_files(base_dirs):
26+
md_files = []
27+
for lang, base_dir in base_dirs.items():
28+
if not os.path.exists(base_dir):
29+
print(f"⚠️ Warning: Directory '{base_dir}' [{lang}] does not exist. Skipping.")
30+
continue
31+
for root, dirs, files in os.walk(base_dir):
32+
for file in files:
33+
if file.endswith(".md"):
34+
md_files.append((lang, os.path.join(root, file)))
35+
return md_files
36+
37+
def extract_links_with_line_numbers(md_file):
38+
links_with_lines = []
39+
pattern = re.compile(r'\[([^\]]+)\]\(([^)]+)\)')
40+
41+
with open(md_file, 'r', encoding='utf-8') as f:
42+
for line_num, line in enumerate(f, 1):
43+
links = pattern.findall(line)
44+
for link_text, path in links:
45+
links_with_lines.append((line_num, link_text, path))
46+
47+
return links_with_lines
48+
49+
def validate_external_link(path):
50+
for attempt in range(MAX_RETRIES):
51+
try:
52+
resp = requests.head(path, timeout=HTTP_TIMEOUT, allow_redirects=True, headers=HEADERS)
53+
54+
if resp.status_code == 429:
55+
print(f"⚠️ Rate-limited. Retrying ({attempt + 1}/{MAX_RETRIES})...")
56+
time.sleep(RETRY_DELAY) # Wait before retrying
57+
continue
58+
59+
if resp.status_code >= 400:
60+
return (resp.status_code, None)
61+
return (resp.status_code, resp.url) # Return the URL after redirection, if any
62+
63+
except requests.exceptions.RequestException as e:
64+
if isinstance(e, requests.exceptions.Timeout):
65+
print(f"⚠️ Timeout occurred while accessing {path}. Retrying ({attempt + 1}/{MAX_RETRIES})...")
66+
else:
67+
print(f"⚠️ Error occurred while accessing {path}: {str(e)}. Retrying ({attempt + 1}/{MAX_RETRIES})...")
68+
time.sleep(RETRY_DELAY) # Wait before retrying
69+
return ('timeout', None)
70+
71+
def validate_internal_links(md_files):
72+
missing_links = []
73+
broken_external_links = []
74+
75+
for lang, md_file in md_files:
76+
links_with_lines = extract_links_with_line_numbers(md_file)
77+
for line_num, link_text, path in links_with_lines:
78+
if path.startswith("http://") or path.startswith("https://"):
79+
# Check external links
80+
error_code, url_after_redirect = validate_external_link(path)
81+
if error_code in [403, 404, 406]:
82+
broken_external_links.append((lang, md_file, path, error_code, line_num))
83+
elif error_code == 'timeout':
84+
broken_external_links.append((lang, md_file, path, "timeout", line_num))
85+
elif error_code == 429:
86+
broken_external_links.append((lang, md_file, path, "Rate-limited", line_num))
87+
else:
88+
# Check internal links
89+
abs_path = os.path.normpath(os.path.join(os.path.dirname(md_file), path))
90+
if not os.path.exists(abs_path):
91+
missing_links.append((lang, md_file, path, line_num))
92+
93+
return missing_links, broken_external_links
94+
95+
if __name__ == "__main__":
96+
print("🔍 Scanning markdown files for internal and external links in: " + ", ".join([f"{lang} ({dir})" for lang, dir in DOCS_DIRS.items()]))
97+
98+
md_files = find_markdown_files(DOCS_DIRS)
99+
if not md_files:
100+
print("⚠️ No Markdown files found in specified directories.")
101+
sys.exit(0)
102+
103+
# Validate internal and external links
104+
missing_links, broken_external_links = validate_internal_links(md_files)
105+
106+
has_errors = False
107+
108+
if missing_links:
109+
print("\n❌ Missing internal links found:")
110+
for lang, md_file, link_path, line_num in missing_links:
111+
print(f" [{lang}] In file '{md_file}' at line {line_num}: Internal link '{link_path}' is missing")
112+
has_errors = True
113+
114+
if broken_external_links:
115+
print("\n❌ Broken external links found:")
116+
for lang, md_file, url, error, line_num in broken_external_links:
117+
print(f" [{lang}] In file '{md_file}' at line {line_num}: External link '{url}' failed ({error})")
118+
has_errors = True
119+
120+
if has_errors:
121+
sys.exit(1)
122+
else:
123+
print("✅ All internal and external links are valid.")

.github/workflows/main.yml

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,55 @@ name: CI
33
on:
44
push:
55
branches:
6-
- master
6+
- master
7+
pull_request:
8+
branches:
9+
- master
10+
types: [opened, synchronize, reopened]
711

812
jobs:
9-
build-deploy:
13+
build-and-deploy-documentation:
1014
runs-on: ubuntu-latest
1115
steps:
12-
- uses: actions/checkout@v3
16+
- uses: actions/checkout@v4
1317
with:
1418
fetch-depth: 0
19+
1520
- name: Set up Python
16-
uses: actions/setup-python@v4
21+
uses: actions/setup-python@v5
1722
with:
1823
python-version: 3.11
19-
- name: Caching dependencies
24+
25+
- name: Cache Python dependencies
2026
uses: actions/cache@v3
2127
with:
2228
key: ${{ github.ref }}
2329
path: .cache
24-
- name: Install dependencies
30+
31+
- name: Install Python dependencies
2532
run: |
2633
python -m pip install --upgrade pip
2734
pip install -r requirements.txt
28-
- name: Build English
35+
pip install requests
36+
37+
- name: Validate internal links in Markdown
38+
run: python .github/scripts/validate_internal_links.py
39+
40+
- name: Validate image links in Markdown
41+
run: python .github/scripts/validate_images.py
42+
43+
- name: Build English Documentation
2944
run: mkdocs build -f config/en/mkdocs.yml
30-
- name: Build German
45+
46+
- name: Build German Documentation
3147
run: mkdocs build -f config/de/mkdocs.yml
48+
3249
- name: Copy common root files (including legacy redirects)
3350
run: cp -R docs/root/* site
34-
- name: Deploy
51+
52+
- name: Deploy to GitHub Pages
53+
if: github.ref == 'refs/heads/master' && success()
3554
uses: peaceiris/actions-gh-pages@v3
36-
if: success()
3755
with:
3856
github_token: ${{ secrets.GITHUB_TOKEN }}
3957
publish_dir: ./site

.project

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<projectDescription>
3+
<name>portfolio-help</name>
4+
<comment></comment>
5+
<projects>
6+
</projects>
7+
<buildSpec>
8+
</buildSpec>
9+
<natures>
10+
</natures>
11+
</projectDescription>

0 commit comments

Comments
 (0)