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'' # Otherwise, treat as emoji return f'{icon}' 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''' {icon_html} {data['text']} ''' 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_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()