Spaces:
Sleeping
Sleeping
File size: 6,000 Bytes
774ed1c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
import argparse
import markdown
import yaml
from weasyprint import HTML
from pathlib import Path
from typing import Dict, Tuple, Any
class ResumeConverter:
def __init__(self, input_path: Path, template_path: Path):
self.input_path = input_path
self.template_path = template_path
self.markdown_content = self._read_file(input_path)
self.template_content = self._read_file(template_path)
@staticmethod
def _read_file(filepath: Path) -> str:
"""Read content from a file."""
with open(filepath, 'r', encoding='utf-8') as f:
return f.read()
def _parse_markdown(self) -> Tuple[Dict[str, Any], str]:
"""Parse markdown content with YAML frontmatter."""
# Split content into lines
lines = self.markdown_content.splitlines()
# Get name from first line
name = lines[0].lstrip('# ').strip()
# Find YAML content
yaml_lines = []
content_lines = []
in_yaml = False
for line in lines[1:]:
if line.strip() == 'header:' or line.strip() == 'social:':
in_yaml = True
yaml_lines.append(line)
elif in_yaml:
if line and (line.startswith(' ') or line.startswith('\t')):
yaml_lines.append(line)
else:
in_yaml = False
content_lines.append(line)
else:
content_lines.append(line)
# Parse YAML
yaml_content = '\n'.join(yaml_lines)
try:
metadata = yaml.safe_load(yaml_content)
except yaml.YAMLError as e:
print(f"Error parsing YAML: {e}")
metadata = {}
metadata['name'] = name
content = '\n'.join(content_lines)
return metadata, content
def _generate_icon(self, icon: str) -> str:
"""Generate icon HTML from either Font Awesome class or emoji."""
if not icon:
return ''
# If icon starts with 'fa' or contains 'fa-', treat as Font Awesome
if icon.startswith('fa') or 'fa-' in icon:
return f'<i class="{icon}"></i>'
# Otherwise, treat as emoji
return f'<span class="emoji">{icon}</span>'
def _generate_social_links_html(self, social_data: Dict[str, Dict[str, str]]) -> str:
"""Generate HTML for social links section."""
social_items = []
for platform, data in social_data.items():
icon = data['icon']
# For Font Awesome icons, add fa-brands class to enable brand colors
if icon.startswith('fa') or 'fa-' in icon:
icon = f"fa-brands {icon}" if 'fa-brands' not in icon else icon
icon_html = self._generate_icon(icon)
item = f'''<a href="{data['url']}" class="social-link" target="_blank">
{icon_html}
<span>{data['text']}</span>
</a>'''
social_items.append(item)
return '\n'.join(social_items)
def convert_to_html(self) -> str:
"""Convert markdown to HTML using template."""
# Parse markdown and YAML
metadata, content = self._parse_markdown()
# Convert markdown content
html_content = markdown.markdown(content, extensions=['extra'])
# Generate social links section
if 'social' in metadata:
social_html = self._generate_social_links_html(metadata['social'])
else:
social_html = ''
# Replace template placeholders
html = self.template_content.replace('{{name}}', metadata['name'])
html = html.replace('{{title}}', f"{metadata['name']}'s Resume")
html = html.replace('{{content}}', html_content)
html = html.replace('<!-- SOCIAL_LINKS -->', social_html)
# Replace header information
if 'header' in metadata:
header = metadata['header']
html = html.replace('{{header_title}}', header.get('title', ''))
html = html.replace('{{header_email}}', header.get('email', ''))
html = html.replace('{{header_phone}}', header.get('phone', ''))
html = html.replace('{{header_location}}', header.get('location', ''))
return html
def save_html(self, output_path: Path, html_content: str) -> None:
"""Save HTML content to file."""
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"Created HTML file: {output_path}")
def save_pdf(self, output_path: Path, html_content: str) -> None:
"""Convert HTML to PDF and save."""
try:
HTML(string=html_content).write_pdf(output_path)
print(f"Created PDF file: {output_path}")
except Exception as e:
print(f"Error converting to PDF: {e}")
def main():
parser = argparse.ArgumentParser(description='Convert markdown resume to HTML/PDF')
parser.add_argument('input', nargs='?', default='resume.md',
help='Input markdown file (default: resume.md)')
parser.add_argument('--template', default='template.html',
help='HTML template file (default: template.html)')
parser.add_argument('--output-html', help='Output HTML file')
parser.add_argument('--output-pdf', help='Output PDF file')
args = parser.parse_args()
# Process paths
input_path = Path(args.input)
template_path = Path(args.template)
output_html = Path(args.output_html) if args.output_html else input_path.with_suffix('.html')
output_pdf = Path(args.output_pdf) if args.output_pdf else input_path.with_suffix('.pdf')
# Create converter and process files
converter = ResumeConverter(input_path, template_path)
html_content = converter.convert_to_html()
# Save output files
converter.save_html(output_html, html_content)
converter.save_pdf(output_pdf, html_content)
if __name__ == '__main__':
main()
|