170 lines
6.1 KiB
Python
170 lines
6.1 KiB
Python
import os
|
|
import requests
|
|
import re
|
|
from bs4 import BeautifulSoup
|
|
import subprocess
|
|
import time
|
|
import logging
|
|
|
|
# Configure logging
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# === Configuration ===
|
|
REDMINE_URL = "https://redmine.maketank.net" # e.g., https://redmine.example.com
|
|
REDMINE_API_KEY = "62120b7ed6bf84023b2a1d7d5a265cf172f8d607"
|
|
PROJECT_ID = "bastelstube" # e.g., "my-project"
|
|
|
|
GITEA_WIKI_URL = "https://git.maketank.net/chaos/spielplatz.wiki.git" # Gitea wiki Git URL
|
|
GITEA_USERNAME = "do"
|
|
GITEA_TOKEN = "2d2be00637182c6d5411512f456218ee59fa1f81" # Use a personal access token
|
|
|
|
# Optional: Gitea API for creating pages (if you want to use it instead of Git)
|
|
# GITEA_API_URL = "https://gitea.example.com/api/v1"
|
|
|
|
# === Functions ===
|
|
|
|
def redmine_get_wiki_pages():
|
|
"""Get all wiki pages from Redmine project"""
|
|
# First get the list of pages
|
|
index_url = f"{REDMINE_URL}/projects/{PROJECT_ID}/wiki/index.xml"
|
|
headers = {"X-Redmine-API-Key": REDMINE_API_KEY}
|
|
response = requests.get(index_url, headers=headers)
|
|
response.raise_for_status()
|
|
|
|
soup = BeautifulSoup(response.content, "xml")
|
|
pages = []
|
|
|
|
# Get all page titles from the index
|
|
for page in soup.find_all("wiki_page"):
|
|
title = page.find("title").text
|
|
logger.info(f"Found page: {title}")
|
|
|
|
# Fetch individual page content
|
|
page_url = f"{REDMINE_URL}/projects/{PROJECT_ID}/wiki/{title}.xml"
|
|
try:
|
|
page_response = requests.get(page_url, headers=headers)
|
|
page_response.raise_for_status()
|
|
|
|
page_soup = BeautifulSoup(page_response.content, "xml")
|
|
content = page_soup.find("text").text if page_soup.find("text") else ""
|
|
|
|
pages.append({"title": title, "content": content})
|
|
logger.info(f"Fetched content for: {title}")
|
|
except Exception as e:
|
|
logger.warning(f"Failed to fetch content for page '{title}': {e}")
|
|
# Add empty content if fetch fails
|
|
pages.append({"title": title, "content": ""})
|
|
|
|
return pages
|
|
|
|
|
|
def redmine_markup_to_markdown(content):
|
|
"""Convert Redmine wiki markup (textile) to Markdown"""
|
|
# Replace Redmine-specific syntax to Markdown
|
|
|
|
# Handle child_pages macro - remove it since we're importing all pages
|
|
content = re.sub(r'\{\{child_pages(?:\([^)]*\))?\}\}', '', content)
|
|
|
|
# Convert bold (strong in textile)
|
|
content = re.sub(r'\*\*(.*?)\*\*', r'**\1**', content)
|
|
content = re.sub(r'\*(.*?)\*', r'*\1*', content)
|
|
|
|
# Convert headings
|
|
content = re.sub(r'^h1\.(.*?)$', r'# \1', content, flags=re.MULTILINE)
|
|
content = re.sub(r'^h2\.(.*?)$', r'## \1', content, flags=re.MULTILINE)
|
|
content = re.sub(r'^h3\.(.*?)$', r'### \1', content, flags=re.MULTILINE)
|
|
content = re.sub(r'^h4\.(.*?)$', r'#### \1', content, flags=re.MULTILINE)
|
|
content = re.sub(r'^h5\.(.*?)$', r'##### \1', content, flags=re.MULTILINE)
|
|
content = re.sub(r'^h6\.(.*?)$', r'###### \1', content, flags=re.MULTILINE)
|
|
|
|
# Convert lists
|
|
content = re.sub(r'^\s*\* (.*?)$', r'- \1', content, flags=re.MULTILINE)
|
|
content = re.sub(r'^\s*\# (.*?)$', r'1. \1', content, flags=re.MULTILINE)
|
|
|
|
# Convert links - handle both [[page]] and [page] syntax
|
|
content = re.sub(r'\[\[(.*?)\]\]', r'[\1](\1)', content) # Simple link
|
|
content = re.sub(r'\[(.*?)\](.*?)\]', r'[\1](\2)', content) # [text](url)
|
|
|
|
# Convert images - handle !image! and 
|
|
content = re.sub(r'!\[(.*?)\]\((.*?)\)', r'', content)
|
|
content = re.sub(r'!(.*?)!', r'', content)
|
|
|
|
# Convert code blocks
|
|
content = re.sub(r'<pre>(.*?)</pre>', r'```python\n\1\n```', content, flags=re.DOTALL)
|
|
|
|
# Convert inline code
|
|
content = re.sub(r'\{\{(.*?)\}\}', r'`\1`', content)
|
|
|
|
# Remove HTML tags (if any)
|
|
content = re.sub(r'<[^>]+>', '', content)
|
|
|
|
# Handle textile-specific elements like blockquotes
|
|
content = re.sub(r'^bq\.(.*?)$', r'> \1', content, flags=re.MULTILINE)
|
|
|
|
return content
|
|
|
|
|
|
def clone_or_init_wiki_repo(repo_url, local_dir):
|
|
"""Clone or initialize the Gitea wiki repo"""
|
|
if not os.path.exists(local_dir):
|
|
logger.info(f"Cloning Gitea wiki repository: {repo_url}")
|
|
subprocess.run(["git", "clone", repo_url, local_dir], check=True)
|
|
else:
|
|
logger.info(f"Using existing local wiki repo: {local_dir}")
|
|
os.chdir(local_dir)
|
|
subprocess.run(["git", "pull"], check=True)
|
|
return local_dir
|
|
|
|
|
|
def push_to_gitea_wiki(local_dir, pages):
|
|
"""Add pages as Markdown files and push to Gitea"""
|
|
# Remove all existing files in the repo (except .git folder)
|
|
for item in os.listdir(local_dir):
|
|
if item != '.git':
|
|
item_path = os.path.join(local_dir, item)
|
|
if os.path.isfile(item_path):
|
|
os.remove(item_path)
|
|
elif os.path.isdir(item_path):
|
|
import shutil
|
|
shutil.rmtree(item_path)
|
|
|
|
# Create pages as .md files
|
|
for page in pages:
|
|
filename = f"{page['title'].replace(' ', '_')}.md"
|
|
filepath = os.path.join(local_dir, filename)
|
|
|
|
# Convert content to Markdown
|
|
markdown_content = redmine_markup_to_markdown(page['content'])
|
|
|
|
# Write to file
|
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
f.write(f"# {page['title']}\n\n")
|
|
f.write(markdown_content)
|
|
|
|
logger.info(f"Saved page: {filename}")
|
|
|
|
# Add, commit, and push
|
|
os.chdir(local_dir)
|
|
subprocess.run(["git", "add", "."], check=True)
|
|
subprocess.run(["git", "commit", "-m", "ImportRedminewikipages"], check=True)
|
|
subprocess.run(["git", "push", "origin", "main"], check=True)
|
|
|
|
logger.info("✅ Wiki pages pushed to Gitea!")
|
|
|
|
|
|
def main():
|
|
# Step 1: Get wiki pages from Redmine
|
|
logger.info("Fetching Redmine wiki pages...")
|
|
pages = redmine_get_wiki_pages()
|
|
|
|
# Step 2: Prepare local directory for Gitea wiki
|
|
local_wiki_dir = "gitea_wiki_clone"
|
|
clone_or_init_wiki_repo(GITEA_WIKI_URL, local_wiki_dir)
|
|
|
|
# Step 3: Push pages to Gitea
|
|
push_to_gitea_wiki(local_wiki_dir, pages)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |