CoolFace
Apppublic

Pq234/robot-learning-tutorial

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
export-latex.mjs319 linesDownload Raw Back to scripts
1#!/usr/bin/env node2import { spawn } from 'node:child_process';3import { promises as fs } from 'node:fs';4import { resolve, dirname, basename, extname } from 'node:path';5import process from 'node:process';6 7async function run(command, args = [], options = {}) {8  return new Promise((resolvePromise, reject) => {9    const child = spawn(command, args, { stdio: 'inherit', shell: false, ...options });10    child.on('error', reject);11    child.on('exit', (code) => {12      if (code === 0) resolvePromise(undefined);13      else reject(new Error(`${command} ${args.join(' ')} exited with code ${code}`));14    });15  });16}17 18function parseArgs(argv) {19  const out = {};20  for (const arg of argv.slice(2)) {21    if (!arg.startsWith('--')) continue;22    const [k, v] = arg.replace(/^--/, '').split('=');23    out[k] = v === undefined ? true : v;24  }25  return out;26}27 28function slugify(text) {29  return String(text || '')30    .normalize('NFKD')31    .replace(/\p{Diacritic}+/gu, '')32    .toLowerCase()33    .replace(/[^a-z0-9]+/g, '-')34    .replace(/^-+|-+$/g, '')35    .slice(0, 120) || 'article';36}37 38async function checkPandocInstalled() {39  try {40    await run('pandoc', ['--version'], { stdio: 'pipe' });41    return true;42  } catch {43    return false;44  }45}46 47async function readMdxFile(filePath) {48  try {49    const content = await fs.readFile(filePath, 'utf-8');50    return content;51  } catch (error) {52    console.warn(`Warning: Could not read ${filePath}:`, error.message);53    return '';54  }55}56 57function extractFrontmatter(content) {58  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/);59  if (!frontmatterMatch) return { frontmatter: {}, content };60 61  const frontmatterText = frontmatterMatch[1];62  const contentWithoutFrontmatter = content.replace(frontmatterMatch[0], '');63 64  // Simple YAML parsing for basic fields65  const frontmatter = {};66  const lines = frontmatterText.split('\n');67  let currentKey = null;68  let currentValue = '';69 70  for (const line of lines) {71    const trimmed = line.trim();72    if (trimmed.includes(':') && !trimmed.startsWith('-')) {73      if (currentKey) {74        frontmatter[currentKey] = currentValue.trim();75      }76      const [key, ...valueParts] = trimmed.split(':');77      currentKey = key.trim();78      currentValue = valueParts.join(':').trim();79    } else if (currentKey) {80      currentValue += '\n' + trimmed;81    }82  }83 84  if (currentKey) {85    frontmatter[currentKey] = currentValue.trim();86  }87 88  return { frontmatter, content: contentWithoutFrontmatter };89}90 91function cleanMdxToMarkdown(content) {92  // Remove import statements93  content = content.replace(/^import .+?;?\s*$/gm, '');94 95  // Remove JSX component calls like <ComponentName />96  content = content.replace(/<[A-Z][a-zA-Z0-9]*\s*\/>/g, '');97 98  // Convert JSX components to simpler markdown99  // Handle Sidenote components specially100  content = content.replace(/<Sidenote>([\s\S]*?)<\/Sidenote>/g, (match, innerContent) => {101    // Extract main content and aside content102    const asideMatch = innerContent.match(/<Fragment slot="aside">([\s\S]*?)<\/Fragment>/);103    const mainContent = innerContent.replace(/<Fragment slot="aside">[\s\S]*?<\/Fragment>/, '').trim();104    const asideContent = asideMatch ? asideMatch[1].trim() : '';105 106    let result = mainContent;107    if (asideContent) {108      result += `\n\n> **Note:** ${asideContent}`;109    }110    return result;111  });112 113  // Handle Note components114  content = content.replace(/<Note[^>]*>([\s\S]*?)<\/Note>/g, (match, innerContent) => {115    return `\n> **Note:** ${innerContent.trim()}\n`;116  });117 118  // Handle Wide and FullWidth components119  content = content.replace(/<(Wide|FullWidth)>([\s\S]*?)<\/\1>/g, '$2');120 121  // Handle HtmlEmbed components (convert to simple text)122  content = content.replace(/<HtmlEmbed[^>]*\/>/g, '*[Interactive content not available in LaTeX]*');123 124  // Remove remaining JSX fragments125  content = content.replace(/<Fragment[^>]*>([\s\S]*?)<\/Fragment>/g, '$1');126  content = content.replace(/<[A-Z][a-zA-Z0-9]*[^>]*>([\s\S]*?)<\/[A-Z][a-zA-Z0-9]*>/g, '$1');127 128  // Clean up className attributes129  content = content.replace(/className="[^"]*"/g, '');130 131  // Clean up extra whitespace132  content = content.replace(/\n{3,}/g, '\n\n');133 134  return content.trim();135}136 137async function processChapterImports(content, contentDir) {138  let processedContent = content;139 140  // First, extract all import statements and their corresponding component calls141  const importPattern = /import\s+(\w+)\s+from\s+["']\.\/chapters\/([^"']+)["'];?/g;142  const imports = new Map();143  let match;144 145  // Collect all imports146  while ((match = importPattern.exec(content)) !== null) {147    const [fullImport, componentName, chapterPath] = match;148    imports.set(componentName, { path: chapterPath, importStatement: fullImport });149  }150 151  // Remove all import statements152  processedContent = processedContent.replace(importPattern, '');153 154  // Process each component call155  for (const [componentName, { path: chapterPath }] of imports) {156    const componentCallPattern = new RegExp(`<${componentName}\\s*\\/>`, 'g');157 158    try {159      const chapterFile = resolve(contentDir, 'chapters', chapterPath);160      const chapterContent = await readMdxFile(chapterFile);161      const { content: chapterMarkdown } = extractFrontmatter(chapterContent);162      const cleanChapter = cleanMdxToMarkdown(chapterMarkdown);163 164      processedContent = processedContent.replace(componentCallPattern, cleanChapter);165      console.log(`✅ Processed chapter: ${chapterPath}`);166    } catch (error) {167      console.warn(`Warning: Could not process chapter ${chapterPath}:`, error.message);168      processedContent = processedContent.replace(componentCallPattern, `\n*[Chapter ${chapterPath} could not be loaded]*\n`);169    }170  }171 172  return processedContent;173}174 175function createLatexPreamble(frontmatter) {176  const title = frontmatter.title ? frontmatter.title.replace(/\n/g, ' ') : 'Untitled Article';177  const subtitle = frontmatter.subtitle || '';178  const authors = frontmatter.authors || '';179  const date = frontmatter.published || '';180 181  return `\\documentclass[11pt,a4paper]{article}182\\usepackage[utf8]{inputenc}183\\usepackage[T1]{fontenc}184\\usepackage{amsmath,amsfonts,amssymb}185\\usepackage{graphicx}186\\usepackage{hyperref}187\\usepackage{booktabs}188\\usepackage{longtable}189\\usepackage{array}190\\usepackage{multirow}191\\usepackage{wrapfig}192\\usepackage{float}193\\usepackage{colortbl}194\\usepackage{pdflscape}195\\usepackage{tabu}196\\usepackage{threeparttable}197\\usepackage{threeparttablex}198\\usepackage{ulem}199\\usepackage{makecell}200\\usepackage{xcolor}201\\usepackage{listings}202\\usepackage{fancyvrb}203\\usepackage{geometry}204\\geometry{margin=1in}205 206\\title{${title}${subtitle ? `\\\\\\large ${subtitle}` : ''}}207${authors ? `\\author{${authors}}` : ''}208${date ? `\\date{${date}}` : ''}209 210\\begin{document}211\\maketitle212\\tableofcontents213\\newpage214 215`;216}217 218async function main() {219  const cwd = process.cwd();220  const args = parseArgs(process.argv);221 222  // Check if pandoc is installed223  const hasPandoc = await checkPandocInstalled();224  if (!hasPandoc) {225    console.error('❌ Pandoc is not installed. Please install it first:');226    console.error('   macOS: brew install pandoc');227    console.error('   Ubuntu: apt-get install pandoc');228    console.error('   Windows: choco install pandoc');229    process.exit(1);230  }231 232  const contentDir = resolve(cwd, 'src/content');233  const articleFile = resolve(contentDir, 'article.mdx');234 235  // Check if article.mdx exists236  try {237    await fs.access(articleFile);238  } catch {239    console.error(`❌ Could not find article.mdx at ${articleFile}`);240    process.exit(1);241  }242 243  console.log('> Reading article content...');244  const articleContent = await readMdxFile(articleFile);245  const { frontmatter, content } = extractFrontmatter(articleContent);246 247  console.log('> Processing chapters...');248  const processedContent = await processChapterImports(content, contentDir);249 250  console.log('> Converting MDX to Markdown...');251  const markdownContent = cleanMdxToMarkdown(processedContent);252 253  // Generate output filename254  const title = frontmatter.title ? frontmatter.title.replace(/\n/g, ' ') : 'article';255  const outFileBase = args.filename ? String(args.filename).replace(/\.(tex|pdf)$/i, '') : slugify(title);256 257  // Create temporary markdown file258  const tempMdFile = resolve(cwd, 'temp-article.md');259  await fs.writeFile(tempMdFile, markdownContent);260 261 262  console.log('> Converting to LaTeX with Pandoc...');263  const outputLatex = resolve(cwd, 'dist', `${outFileBase}.tex`);264 265  // Ensure dist directory exists266  await fs.mkdir(resolve(cwd, 'dist'), { recursive: true });267 268  // Pandoc conversion arguments269  const pandocArgs = [270    tempMdFile,271    '-o', outputLatex,272    '--from=markdown',273    '--to=latex',274    '--standalone',275    '--toc',276    '--number-sections',277    '--highlight-style=tango',278    '--listings'279  ];280 281  // Add bibliography if it exists282  const bibFile = resolve(contentDir, 'bibliography.bib');283  try {284    await fs.access(bibFile);285    pandocArgs.push('--bibliography', bibFile);286    pandocArgs.push('--citeproc');287    console.log('✅ Found bibliography file, including citations');288  } catch {289    console.log('ℹ️  No bibliography file found');290  }291 292  try {293    await run('pandoc', pandocArgs);294    console.log(`✅ LaTeX generated: ${outputLatex}`);295 296    // Optionally compile to PDF if requested297    if (args.pdf) {298      console.log('> Compiling LaTeX to PDF...');299      const outputPdf = resolve(cwd, 'dist', `${outFileBase}.pdf`);300      await run('pdflatex', ['-output-directory', resolve(cwd, 'dist'), outputLatex]);301      console.log(`✅ PDF generated: ${outputPdf}`);302    }303 304  } catch (error) {305    console.error('❌ Pandoc conversion failed:', error.message);306    process.exit(1);307  } finally {308    // Clean up temporary file309    try {310      await fs.unlink(tempMdFile);311    } catch { }312  }313}314 315main().catch((err) => {316  console.error(err);317  process.exit(1);318});319