Skip to content

Commit 4f0bae9

Browse files
committed
Add script to fix markdown links for GitHub wiki and create publish workflow
1 parent 3eece4d commit 4f0bae9

File tree

2 files changed

+376
-0
lines changed

2 files changed

+376
-0
lines changed

.github/scripts/fix_wiki_links.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generic script to fix markdown cross-references for GitHub wiki format.
4+
5+
Converts markdown links like [text](FILENAME.md) or [text](FILENAME.md#anchor)
6+
to wiki-style links like [text](Wiki-Page-Name) or [text](Wiki-Page-Name#anchor).
7+
8+
The script automatically:
9+
1. Detects all markdown links in the file
10+
2. Extracts the target filename
11+
3. Converts it to wiki page name format (Title Case with hyphens)
12+
4. Preserves anchors and link text
13+
"""
14+
15+
import re
16+
import sys
17+
from pathlib import Path
18+
19+
20+
def filename_to_wiki_name(filename: str) -> str:
21+
"""
22+
Convert a markdown filename to a GitHub wiki page name.
23+
24+
Examples:
25+
- INSTALLATION.md -> Installation
26+
- VERSION_RESOLUTION.md -> Version-Resolution
27+
- USAGE.md -> Usage
28+
- REFERENCE.md -> API-Reference (special case)
29+
30+
Rules:
31+
- Remove .md extension
32+
- Convert underscores to hyphens
33+
- Convert to Title Case
34+
- Handle special cases
35+
"""
36+
# Remove .md extension and any path components
37+
name = Path(filename).stem
38+
39+
# Special case mappings
40+
special_cases = {
41+
'REFERENCE': 'API-Reference',
42+
}
43+
44+
if name in special_cases:
45+
return special_cases[name]
46+
47+
# Convert underscores to hyphens
48+
name = name.replace('_', '-')
49+
50+
# Convert to Title Case (capitalize first letter of each word)
51+
# Split by hyphens, capitalize each part, join
52+
parts = name.split('-')
53+
title_parts = [part.capitalize() for part in parts]
54+
55+
return '-'.join(title_parts)
56+
57+
58+
def fix_markdown_links(content: str, doc_mapping: dict[str, str]) -> str:
59+
"""
60+
Fix all markdown links in content to use wiki page names.
61+
62+
Args:
63+
content: The markdown content
64+
doc_mapping: Dictionary mapping source filenames to wiki page names
65+
66+
Returns:
67+
Content with fixed links
68+
"""
69+
# Pattern to match markdown links: [text](target.md) or [text](target.md#anchor)
70+
# Also handles paths like docs/target.md or ./target.md
71+
link_pattern = re.compile(
72+
r'\[([^\]]+)\]\(([^)]+\.md)(#[^)]+)?\)',
73+
re.IGNORECASE
74+
)
75+
76+
def replace_link(match):
77+
link_text = match.group(1)
78+
target = match.group(2)
79+
anchor = match.group(3) or ''
80+
81+
# Extract just the filename from the path
82+
filename = Path(target).name
83+
84+
# Get wiki page name from mapping
85+
wiki_name = doc_mapping.get(filename.upper())
86+
87+
if wiki_name:
88+
# Return wiki-style link
89+
return f'[{link_text}]({wiki_name}{anchor})'
90+
else:
91+
# If not in mapping, try to convert generically
92+
wiki_name = filename_to_wiki_name(filename)
93+
return f'[{link_text}]({wiki_name}{anchor})'
94+
95+
return link_pattern.sub(replace_link, content)
96+
97+
98+
def main():
99+
"""Main entry point."""
100+
if len(sys.argv) < 2:
101+
print("Usage: fix_wiki_links.py <file1> [file2] ...", file=sys.stderr)
102+
sys.exit(1)
103+
104+
# Build mapping from all docs files
105+
# This assumes we're running from the repo root
106+
docs_dir = Path('docs')
107+
if not docs_dir.exists():
108+
# Try from main-repo directory
109+
docs_dir = Path('main-repo/docs')
110+
111+
doc_mapping = {}
112+
if docs_dir.exists():
113+
for md_file in docs_dir.glob('*.md'):
114+
filename = md_file.name.upper()
115+
wiki_name = filename_to_wiki_name(md_file.name)
116+
doc_mapping[filename] = wiki_name
117+
118+
# Process each file
119+
for file_path in sys.argv[1:]:
120+
file_path = Path(file_path)
121+
if not file_path.exists():
122+
print(f"Warning: File not found: {file_path}", file=sys.stderr)
123+
continue
124+
125+
# Read content
126+
content = file_path.read_text(encoding='utf-8')
127+
128+
# Fix links
129+
fixed_content = fix_markdown_links(content, doc_mapping)
130+
131+
# Write back
132+
file_path.write_text(fixed_content, encoding='utf-8')
133+
print(f"Fixed links in: {file_path}")
134+
135+
136+
if __name__ == '__main__':
137+
main()

.github/workflows/publish-wiki.yml

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
name: Publish Documentation to Wiki
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- master
8+
paths:
9+
- 'docs/**'
10+
- '.github/workflows/publish-wiki.yml'
11+
workflow_dispatch: # Enable manual trigger
12+
13+
jobs:
14+
publish-wiki:
15+
runs-on: ubuntu-latest
16+
permissions:
17+
contents: write # Required to push to wiki
18+
steps:
19+
- name: Checkout main repository
20+
uses: actions/checkout@v4
21+
with:
22+
path: main-repo
23+
24+
- name: Checkout wiki repository
25+
uses: actions/checkout@v4
26+
with:
27+
repository: alelom/python-package-folder.wiki
28+
path: wiki-repo
29+
fetch-depth: 1
30+
token: ${{ secrets.GITHUB_TOKEN }}
31+
continue-on-error: true # Continue if wiki doesn't exist yet
32+
33+
- name: Copy and fix documentation files
34+
run: |
35+
cd main-repo
36+
37+
# Ensure wiki-repo directory exists
38+
mkdir -p ../wiki-repo
39+
40+
# Function to convert filename to wiki page name
41+
# This handles the mapping from source docs to wiki pages
42+
filename_to_wiki_name() {
43+
local filename="$1"
44+
# Remove .md extension and path
45+
local name="${filename%.md}"
46+
name="${name##*/}" # Get just the filename
47+
name="${name^^}" # Convert to uppercase for matching
48+
49+
# Special case mappings
50+
case "$name" in
51+
REFERENCE)
52+
echo "API-Reference"
53+
;;
54+
*)
55+
# Convert underscores to hyphens and to Title Case
56+
echo "$name" | sed 's/_/-/g' | sed 's/\b\(.\)/\u\1/g' | sed 's/-\(.\)/-\u\1/g'
57+
;;
58+
esac
59+
}
60+
61+
# Copy all documentation files and convert to wiki format
62+
for doc_file in docs/*.md; do
63+
if [ ! -f "$doc_file" ]; then
64+
continue
65+
fi
66+
67+
# Get base filename
68+
basename=$(basename "$doc_file")
69+
wiki_name=$(filename_to_wiki_name "$basename")
70+
71+
# Copy file to wiki with new name
72+
cp "$doc_file" "../wiki-repo/${wiki_name}.md"
73+
74+
echo "Copied $basename -> ${wiki_name}.md"
75+
done
76+
77+
# Fix all cross-references using Python script
78+
# The script automatically detects and fixes all markdown links
79+
python3 << 'PYTHON_SCRIPT'
80+
import re
81+
from pathlib import Path
82+
83+
def filename_to_wiki_name(filename):
84+
"""Convert markdown filename to wiki page name."""
85+
name = Path(filename).stem.upper()
86+
special_cases = {'REFERENCE': 'API-Reference'}
87+
if name in special_cases:
88+
return special_cases[name]
89+
# Convert underscores to hyphens and to Title Case
90+
name = name.replace('_', '-')
91+
parts = name.split('-')
92+
return '-'.join(p.capitalize() for p in parts)
93+
94+
def fix_markdown_links(content, doc_mapping):
95+
"""Fix all markdown links in content."""
96+
# Pattern: [text](target.md) or [text](target.md#anchor) or [text](docs/target.md)
97+
pattern = re.compile(
98+
r'\[([^\]]+)\]\(([^)]+\.md)(#[^)]+)?\)',
99+
re.IGNORECASE
100+
)
101+
102+
def replace_link(match):
103+
link_text = match.group(1)
104+
target = match.group(2)
105+
anchor = match.group(3) or ''
106+
107+
# Extract filename from path
108+
filename = Path(target).name.upper()
109+
110+
# Get wiki name from mapping or convert generically
111+
wiki_name = doc_mapping.get(filename, filename_to_wiki_name(filename))
112+
113+
return f'[{link_text}]({wiki_name}{anchor})'
114+
115+
return pattern.sub(replace_link, content)
116+
117+
# Build mapping of all docs
118+
docs_dir = Path('docs')
119+
doc_mapping = {}
120+
for md_file in docs_dir.glob('*.md'):
121+
filename = md_file.name.upper()
122+
wiki_name = filename_to_wiki_name(md_file.name)
123+
doc_mapping[filename] = wiki_name
124+
125+
# Fix links in all wiki files
126+
wiki_dir = Path('../wiki-repo')
127+
for wiki_file in wiki_dir.glob('*.md'):
128+
content = wiki_file.read_text(encoding='utf-8')
129+
fixed_content = fix_markdown_links(content, doc_mapping)
130+
wiki_file.write_text(fixed_content, encoding='utf-8')
131+
print(f"Fixed links in: {wiki_file.name}")
132+
PYTHON_SCRIPT
133+
134+
# Create Home page dynamically with links to all documentation
135+
python3 << 'PYTHON_SCRIPT'
136+
from pathlib import Path
137+
138+
wiki_dir = Path('../wiki-repo')
139+
140+
# Get all wiki pages (excluding Home.md)
141+
wiki_pages = sorted([f.stem for f in wiki_dir.glob('*.md') if f.name != 'Home.md'])
142+
143+
# Create Home page content
144+
home_content = """# python-package-folder Documentation
145+
146+
Welcome to the python-package-folder documentation wiki!
147+
148+
## Quick Links
149+
150+
"""
151+
152+
# Add links to all documentation pages
153+
for page in wiki_pages:
154+
# Convert page name to a more readable format for the link text
155+
# e.g., "Version-Resolution" -> "Version Resolution"
156+
link_text = page.replace('-', ' ')
157+
home_content += f"- [{link_text}]({page})\n"
158+
159+
home_content += """
160+
## Overview
161+
162+
Easily build and publish any target folder in a repository, including subfolders of a monorepo.
163+
Together with [sysappend](https://pypi.org/project/sysappend/), this library makes relative imports, flexible import management, and package publishing a breeze.
164+
165+
## Features
166+
167+
- **Subfolder Build Support**: Build subfolders as separate packages with automatic detection and configuration
168+
- **Smart Import Classification**: Recursively parses all `.py` files to detect and classify imports
169+
- **Automatic Version Resolution**: Resolve versions via conventional commits (Python-native, no Node.js required)
170+
- **Package Publishing**: Publish to PyPI, TestPyPI, or Azure Artifacts
171+
172+
## Quick Start
173+
174+
```bash
175+
# Build and publish a subfolder
176+
cd src/my_subfolder
177+
python-package-folder --publish pypi
178+
```
179+
180+
For more information, see the [Usage Guide](Usage).
181+
"""
182+
183+
# Write Home.md
184+
(wiki_dir / 'Home.md').write_text(home_content, encoding='utf-8')
185+
print("Created Home.md with links to all documentation pages")
186+
PYTHON_SCRIPT
187+
188+
- name: Commit and push to wiki
189+
run: |
190+
cd wiki-repo
191+
192+
# Set up token for authentication
193+
WIKI_URL="https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/alelom/python-package-folder.wiki.git"
194+
195+
# Initialize git repo if it doesn't exist (empty wiki or first run)
196+
if [ ! -d .git ]; then
197+
echo "Initializing new wiki repository"
198+
git init
199+
git remote add origin "$WIKI_URL" || true
200+
git checkout -b master
201+
else
202+
# Configure remote if missing or update to use token
203+
if ! git remote get-url origin >/dev/null 2>&1; then
204+
git remote add origin "$WIKI_URL"
205+
else
206+
# Update remote URL to include token
207+
git remote set-url origin "$WIKI_URL"
208+
fi
209+
210+
# Fetch and checkout appropriate branch
211+
git fetch origin master 2>/dev/null || git fetch origin main 2>/dev/null || true
212+
if git show-ref --verify --quiet refs/heads/master; then
213+
git checkout master
214+
elif git show-ref --verify --quiet refs/remotes/origin/master; then
215+
git checkout -b master origin/master
216+
else
217+
git checkout -b master
218+
fi
219+
fi
220+
221+
# Configure git
222+
git config user.name "github-actions[bot]"
223+
git config user.email "github-actions[bot]@users.noreply.github.com"
224+
225+
# Stage all changes
226+
git add -A
227+
228+
# Check if there are changes to commit
229+
if git diff --cached --quiet && [ -n "$(git ls-files)" ]; then
230+
echo "No changes to commit"
231+
else
232+
# Commit changes (or create initial commit)
233+
git commit -m "Update documentation from main repository [skip ci]" || echo "No changes to commit"
234+
235+
# Push to wiki using token
236+
git push origin master || \
237+
git push origin HEAD:master || \
238+
echo "Push failed - wiki may need to be enabled in repository settings"
239+
fi

0 commit comments

Comments
 (0)