Files
powershell-scripts/docs/convert_to_html.py
cproudlock 7d3519f613 Add comprehensive documentation and update deployment paths
Documentation:
- Add ShopDB-API.md with full API reference (all GET/POST endpoints)
- Add detailed docs for Update-ShopfloorPCs-Remote, Invoke-RemoteMaintenance, Update-PC-CompleteAsset
- Add DATA_COLLECTION_PARITY.md comparing local vs remote data collection
- Add HTML versions of all documentation with styled code blocks
- Document software deployment mechanism and how to add new apps
- Document deprecated scripts (Invoke-RemoteAssetCollection, Install-KioskApp)

Script Updates:
- Update deployment source paths to network share (tsgwp00525.wjs.geaerospace.net)
  - InstallDashboard: \\...\scripts\Dashboard\GEAerospaceDashboardSetup.exe
  - InstallLobbyDisplay: \\...\scripts\LobbyDisplay\GEAerospaceLobbyDisplaySetup.exe
  - UpdateEMxAuthToken: \\...\scripts\eMx\eMxInfo.txt
  - DeployUDCWebServerConfig: \\...\scripts\UDC\udc_webserver_settings.json
- Update machine network detection to include 100.0.0.* for CMM cases
- Rename PC Type #9 from "Part Marker" to "Inspection"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 11:45:00 -05:00

412 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Convert Markdown documentation to styled HTML
"""
import re
import os
import html
def convert_md_to_html(md_content, title="Documentation"):
"""Convert markdown content to styled HTML."""
# HTML template with CSS styling
html_template = '''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<style>
* {{
box-sizing: border-box;
}}
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
max-width: 900px;
margin: 0 auto;
padding: 20px 40px;
background-color: #ffffff;
color: #333;
}}
h1 {{
color: #1a5276;
border-bottom: 3px solid #1a5276;
padding-bottom: 10px;
margin-top: 0;
}}
h2 {{
color: #2874a6;
border-bottom: 2px solid #d5dbdb;
padding-bottom: 8px;
margin-top: 40px;
}}
h3 {{
color: #2e86ab;
margin-top: 30px;
}}
h4 {{
color: #5d6d7e;
margin-top: 25px;
}}
a {{
color: #2980b9;
text-decoration: none;
}}
a:hover {{
text-decoration: underline;
}}
code {{
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
background-color: #f4f4f4;
padding: 2px 6px;
border-radius: 3px;
font-size: 0.9em;
border: 1px solid #e1e1e1;
}}
pre {{
background-color: #2d2d2d;
color: #f8f8f2;
padding: 15px 20px;
border-radius: 6px;
overflow-x: auto;
font-family: 'Cascadia Mono', 'JetBrains Mono', 'Fira Code', 'Source Code Pro', 'DejaVu Sans Mono', 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 14px;
line-height: 1.4;
border: 1px solid #444;
margin: 15px 0;
-webkit-font-feature-settings: "liga" 0;
font-feature-settings: "liga" 0;
letter-spacing: 0;
}}
pre code {{
background-color: transparent;
padding: 0;
border: none;
color: inherit;
font-size: inherit;
}}
table {{
border-collapse: collapse;
width: 100%;
margin: 15px 0;
font-size: 14px;
}}
th, td {{
border: 1px solid #ddd;
padding: 10px 12px;
text-align: left;
}}
th {{
background-color: #34495e;
color: white;
font-weight: 600;
}}
tr:nth-child(even) {{
background-color: #f9f9f9;
}}
tr:hover {{
background-color: #f1f1f1;
}}
ul, ol {{
padding-left: 25px;
}}
li {{
margin-bottom: 5px;
}}
blockquote {{
border-left: 4px solid #3498db;
margin: 15px 0;
padding: 10px 20px;
background-color: #f8f9fa;
color: #555;
}}
hr {{
border: none;
border-top: 2px solid #eee;
margin: 30px 0;
}}
.toc {{
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 20px;
margin-bottom: 30px;
}}
.toc h2 {{
margin-top: 0;
border-bottom: none;
font-size: 1.2em;
}}
.toc ul {{
list-style-type: none;
padding-left: 0;
}}
.toc li {{
margin-bottom: 8px;
}}
.toc a {{
color: #2c3e50;
}}
.note {{
background-color: #fff3cd;
border-left: 4px solid #ffc107;
padding: 10px 15px;
margin: 15px 0;
}}
.warning {{
background-color: #f8d7da;
border-left: 4px solid #dc3545;
padding: 10px 15px;
margin: 15px 0;
}}
@media print {{
body {{
max-width: 100%;
padding: 20px;
}}
pre {{
white-space: pre-wrap;
word-wrap: break-word;
}}
h2 {{
page-break-before: auto;
}}
pre, table {{
page-break-inside: avoid;
}}
}}
</style>
</head>
<body>
{content}
</body>
</html>'''
lines = md_content.split('\n')
html_lines = []
i = 0
in_list = False
list_type = None
while i < len(lines):
line = lines[i]
# Empty line - close any open list
if not line.strip():
if in_list:
html_lines.append(f'</{list_type}>')
in_list = False
list_type = None
i += 1
continue
# Headers
if line.startswith('# '):
if in_list:
html_lines.append(f'</{list_type}>')
in_list = False
text = process_inline(line[2:].strip())
anchor = slugify(line[2:].strip())
html_lines.append(f'<h1 id="{anchor}">{text}</h1>')
i += 1
elif line.startswith('## '):
if in_list:
html_lines.append(f'</{list_type}>')
in_list = False
text = process_inline(line[3:].strip())
anchor = slugify(line[3:].strip())
html_lines.append(f'<h2 id="{anchor}">{text}</h2>')
i += 1
elif line.startswith('### '):
if in_list:
html_lines.append(f'</{list_type}>')
in_list = False
text = process_inline(line[4:].strip())
anchor = slugify(line[4:].strip())
html_lines.append(f'<h3 id="{anchor}">{text}</h3>')
i += 1
elif line.startswith('#### '):
if in_list:
html_lines.append(f'</{list_type}>')
in_list = False
text = process_inline(line[5:].strip())
anchor = slugify(line[5:].strip())
html_lines.append(f'<h4 id="{anchor}">{text}</h4>')
i += 1
# Horizontal rule
elif line.strip() == '---':
if in_list:
html_lines.append(f'</{list_type}>')
in_list = False
html_lines.append('<hr>')
i += 1
# Code blocks
elif line.strip().startswith('```'):
if in_list:
html_lines.append(f'</{list_type}>')
in_list = False
lang = line.strip()[3:]
code_lines = []
i += 1
while i < len(lines) and not lines[i].strip().startswith('```'):
code_lines.append(html.escape(lines[i]))
i += 1
code_content = '\n'.join(code_lines)
if lang:
html_lines.append(f'<pre><code class="language-{lang}">{code_content}</code></pre>')
else:
html_lines.append(f'<pre><code>{code_content}</code></pre>')
i += 1 # Skip closing ```
# Tables
elif '|' in line and i + 1 < len(lines) and '---' in lines[i + 1]:
if in_list:
html_lines.append(f'</{list_type}>')
in_list = False
html_lines.append('<table>')
# Header row
cells = [c.strip() for c in line.split('|')[1:-1]]
html_lines.append('<thead><tr>')
for cell in cells:
html_lines.append(f'<th>{process_inline(cell)}</th>')
html_lines.append('</tr></thead>')
i += 2 # Skip header and separator
html_lines.append('<tbody>')
while i < len(lines) and '|' in lines[i]:
cells = [c.strip() for c in lines[i].split('|')[1:-1]]
html_lines.append('<tr>')
for cell in cells:
html_lines.append(f'<td>{process_inline(cell)}</td>')
html_lines.append('</tr>')
i += 1
html_lines.append('</tbody></table>')
# Bullet lists
elif line.strip().startswith('- ') or line.strip().startswith('* '):
if not in_list or list_type != 'ul':
if in_list:
html_lines.append(f'</{list_type}>')
html_lines.append('<ul>')
in_list = True
list_type = 'ul'
text = process_inline(line.strip()[2:])
html_lines.append(f'<li>{text}</li>')
i += 1
# Numbered lists
elif re.match(r'^\d+\.\s', line.strip()):
if not in_list or list_type != 'ol':
if in_list:
html_lines.append(f'</{list_type}>')
html_lines.append('<ol>')
in_list = True
list_type = 'ol'
text = process_inline(re.sub(r'^\d+\.\s', '', line.strip()))
html_lines.append(f'<li>{text}</li>')
i += 1
# Blockquote
elif line.strip().startswith('>'):
if in_list:
html_lines.append(f'</{list_type}>')
in_list = False
text = process_inline(line.strip()[1:].strip())
html_lines.append(f'<blockquote>{text}</blockquote>')
i += 1
# Regular paragraph
else:
if in_list:
html_lines.append(f'</{list_type}>')
in_list = False
para_lines = [line.strip()]
i += 1
while i < len(lines) and lines[i].strip() and not lines[i].startswith('#') and not lines[i].startswith('```') and not lines[i].strip().startswith('- ') and not lines[i].strip().startswith('* ') and '|' not in lines[i] and not re.match(r'^\d+\.\s', lines[i].strip()) and lines[i].strip() != '---':
para_lines.append(lines[i].strip())
i += 1
text = process_inline(' '.join(para_lines))
html_lines.append(f'<p>{text}</p>')
# Close any remaining list
if in_list:
html_lines.append(f'</{list_type}>')
content = '\n'.join(html_lines)
return html_template.format(title=html.escape(title), content=content)
def process_inline(text):
"""Process inline markdown formatting."""
# Escape HTML first
# But we need to be careful not to double-escape
# Bold
text = re.sub(r'\*\*([^*]+)\*\*', r'<strong>\1</strong>', text)
# Italic
text = re.sub(r'\*([^*]+)\*', r'<em>\1</em>', text)
# Inline code (before links to avoid conflicts)
text = re.sub(r'`([^`]+)`', lambda m: f'<code>{html.escape(m.group(1))}</code>', text)
# Links
text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2">\1</a>', text)
# Checkmarks and X marks
text = text.replace('', '&#10003;')
text = text.replace('', '&#10007;')
return text
def slugify(text):
"""Convert text to URL-friendly slug."""
text = text.lower()
text = re.sub(r'[^a-z0-9\s-]', '', text)
text = re.sub(r'[\s]+', '-', text)
return text
def convert_file(md_path, html_path):
"""Convert a markdown file to HTML."""
print(f"Converting {os.path.basename(md_path)} -> {os.path.basename(html_path)}")
with open(md_path, 'r', encoding='utf-8') as f:
content = f.read()
# Extract title from first h1
title_match = re.search(r'^# (.+)$', content, re.MULTILINE)
title = title_match.group(1) if title_match else os.path.basename(md_path)
html_content = convert_md_to_html(content, title)
with open(html_path, 'w', encoding='utf-8') as f:
f.write(html_content)
def main():
docs_dir = '/home/camp/projects/powershell/docs'
md_files = [
'Update-ShopfloorPCs-Remote.md',
'Invoke-RemoteMaintenance.md',
'Update-PC-CompleteAsset.md',
'DATA_COLLECTION_PARITY.md',
'ShopDB-API.md'
]
for md_file in md_files:
md_path = os.path.join(docs_dir, md_file)
html_path = os.path.join(docs_dir, md_file.replace('.md', '.html'))
if os.path.exists(md_path):
convert_file(md_path, html_path)
else:
print(f"Warning: {md_path} not found")
print("\nConversion complete!")
print(f"HTML files saved to: {docs_dir}")
if __name__ == '__main__':
main()