Initial commit
This commit is contained in:
206
skills/venue-templates/scripts/customize_template.py
Executable file
206
skills/venue-templates/scripts/customize_template.py
Executable file
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Customize Template Script
|
||||
Customize LaTeX templates with author information and project details.
|
||||
|
||||
Usage:
|
||||
python customize_template.py --template nature_article.tex --output my_paper.tex
|
||||
python customize_template.py --template nature_article.tex --title "My Research" --output my_paper.tex
|
||||
python customize_template.py --interactive
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def get_skill_path():
|
||||
"""Get the path to the venue-templates skill directory."""
|
||||
script_dir = Path(__file__).parent
|
||||
skill_dir = script_dir.parent
|
||||
return skill_dir
|
||||
|
||||
def find_template(template_name):
|
||||
"""Find template file in assets directory."""
|
||||
skill_path = get_skill_path()
|
||||
assets_path = skill_path / "assets"
|
||||
|
||||
# Search in all subdirectories
|
||||
for subdir in ["journals", "posters", "grants"]:
|
||||
template_path = assets_path / subdir / template_name
|
||||
if template_path.exists():
|
||||
return template_path
|
||||
|
||||
return None
|
||||
|
||||
def customize_template(template_path, output_path, **kwargs):
|
||||
"""Customize a template with provided information."""
|
||||
|
||||
# Read template
|
||||
with open(template_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace placeholders
|
||||
replacements = {
|
||||
'title': (
|
||||
[r'Insert Your Title Here[^}]*', r'Your [^}]*Title[^}]*Here[^}]*'],
|
||||
kwargs.get('title', '')
|
||||
),
|
||||
'authors': (
|
||||
[r'First Author\\textsuperscript\{1\}, Second Author[^}]*',
|
||||
r'First Author.*Second Author.*Third Author'],
|
||||
kwargs.get('authors', '')
|
||||
),
|
||||
'affiliations': (
|
||||
[r'Department Name, Institution Name, City, State[^\\]*',
|
||||
r'Department of [^,]*, University Name[^\\]*'],
|
||||
kwargs.get('affiliations', '')
|
||||
),
|
||||
'email': (
|
||||
[r'first\.author@university\.edu',
|
||||
r'\[email protected\]'],
|
||||
kwargs.get('email', '')
|
||||
)
|
||||
}
|
||||
|
||||
# Apply replacements
|
||||
modified = False
|
||||
for key, (patterns, replacement) in replacements.items():
|
||||
if replacement:
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, content):
|
||||
content = re.sub(pattern, replacement, content, count=1)
|
||||
modified = True
|
||||
print(f"✓ Replaced {key}")
|
||||
|
||||
# Write output
|
||||
with open(output_path, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
if modified:
|
||||
print(f"\n✓ Customized template saved to: {output_path}")
|
||||
else:
|
||||
print(f"\n⚠️ Template copied to: {output_path}")
|
||||
print(" No customizations applied (no matching placeholders found or no values provided)")
|
||||
|
||||
print(f"\nNext steps:")
|
||||
print(f"1. Open {output_path} in your LaTeX editor")
|
||||
print(f"2. Replace remaining placeholders")
|
||||
print(f"3. Add your content")
|
||||
print(f"4. Compile with pdflatex or your preferred LaTeX compiler")
|
||||
|
||||
def interactive_mode():
|
||||
"""Run in interactive mode."""
|
||||
print("\n=== Template Customization (Interactive Mode) ===\n")
|
||||
|
||||
# List available templates
|
||||
skill_path = get_skill_path()
|
||||
assets_path = skill_path / "assets"
|
||||
|
||||
print("Available templates:\n")
|
||||
templates = []
|
||||
for i, subdir in enumerate(["journals", "posters", "grants"], 1):
|
||||
subdir_path = assets_path / subdir
|
||||
if subdir_path.exists():
|
||||
print(f"{subdir.upper()}:")
|
||||
for j, template_file in enumerate(sorted(subdir_path.glob("*.tex")), 1):
|
||||
templates.append(template_file)
|
||||
print(f" {len(templates)}. {template_file.name}")
|
||||
|
||||
print()
|
||||
|
||||
# Select template
|
||||
while True:
|
||||
try:
|
||||
choice = int(input(f"Select template (1-{len(templates)}): "))
|
||||
if 1 <= choice <= len(templates):
|
||||
template_path = templates[choice - 1]
|
||||
break
|
||||
else:
|
||||
print(f"Please enter a number between 1 and {len(templates)}")
|
||||
except ValueError:
|
||||
print("Please enter a valid number")
|
||||
|
||||
print(f"\nSelected: {template_path.name}\n")
|
||||
|
||||
# Get customization info
|
||||
title = input("Paper title (press Enter to skip): ").strip()
|
||||
authors = input("Authors (e.g., 'John Doe, Jane Smith') (press Enter to skip): ").strip()
|
||||
affiliations = input("Affiliations (press Enter to skip): ").strip()
|
||||
email = input("Corresponding email (press Enter to skip): ").strip()
|
||||
|
||||
# Output file
|
||||
default_output = f"my_{template_path.stem}.tex"
|
||||
output = input(f"Output filename [{default_output}]: ").strip()
|
||||
if not output:
|
||||
output = default_output
|
||||
|
||||
output_path = Path(output)
|
||||
|
||||
# Customize
|
||||
print()
|
||||
customize_template(
|
||||
template_path,
|
||||
output_path,
|
||||
title=title,
|
||||
authors=authors,
|
||||
affiliations=affiliations,
|
||||
email=email
|
||||
)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Customize LaTeX templates with author and project information",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s --interactive
|
||||
%(prog)s --template nature_article.tex --output my_paper.tex
|
||||
%(prog)s --template neurips_article.tex --title "My ML Research" --output my_neurips.tex
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument('--template', type=str, help='Template filename')
|
||||
parser.add_argument('--output', type=str, help='Output filename')
|
||||
parser.add_argument('--title', type=str, help='Paper title')
|
||||
parser.add_argument('--authors', type=str, help='Author names')
|
||||
parser.add_argument('--affiliations', type=str, help='Institutions/affiliations')
|
||||
parser.add_argument('--email', type=str, help='Corresponding author email')
|
||||
parser.add_argument('--interactive', action='store_true', help='Run in interactive mode')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Interactive mode
|
||||
if args.interactive:
|
||||
interactive_mode()
|
||||
return
|
||||
|
||||
# Command-line mode
|
||||
if not args.template or not args.output:
|
||||
print("Error: --template and --output are required (or use --interactive)")
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
# Find template
|
||||
template_path = find_template(args.template)
|
||||
if not template_path:
|
||||
print(f"Error: Template '{args.template}' not found")
|
||||
print("\nSearched in:")
|
||||
skill_path = get_skill_path()
|
||||
for subdir in ["journals", "posters", "grants"]:
|
||||
print(f" - {skill_path}/assets/{subdir}/")
|
||||
return
|
||||
|
||||
# Customize
|
||||
output_path = Path(args.output)
|
||||
customize_template(
|
||||
template_path,
|
||||
output_path,
|
||||
title=args.title,
|
||||
authors=args.authors,
|
||||
affiliations=args.affiliations,
|
||||
email=args.email
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
260
skills/venue-templates/scripts/query_template.py
Executable file
260
skills/venue-templates/scripts/query_template.py
Executable file
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Query Template Script
|
||||
Search and retrieve venue-specific templates by name, type, or keywords.
|
||||
|
||||
Usage:
|
||||
python query_template.py --venue "Nature" --type "article"
|
||||
python query_template.py --keyword "machine learning"
|
||||
python query_template.py --list-all
|
||||
python query_template.py --venue "NeurIPS" --requirements
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Template database
|
||||
TEMPLATES = {
|
||||
"journals": {
|
||||
"nature": {
|
||||
"file": "nature_article.tex",
|
||||
"full_name": "Nature",
|
||||
"description": "Top-tier multidisciplinary science journal",
|
||||
"page_limit": "~3000 words",
|
||||
"citation_style": "Superscript numbered",
|
||||
"format": "Single column"
|
||||
},
|
||||
"neurips": {
|
||||
"file": "neurips_article.tex",
|
||||
"full_name": "NeurIPS (Neural Information Processing Systems)",
|
||||
"description": "Top-tier machine learning conference",
|
||||
"page_limit": "8 pages + unlimited refs",
|
||||
"citation_style": "Numbered [1]",
|
||||
"format": "Two column",
|
||||
"anonymization": "Required (double-blind)"
|
||||
},
|
||||
"plos_one": {
|
||||
"file": "plos_one.tex",
|
||||
"full_name": "PLOS ONE",
|
||||
"description": "Open-access multidisciplinary journal",
|
||||
"page_limit": "No limit",
|
||||
"citation_style": "Vancouver [1]",
|
||||
"format": "Single column"
|
||||
}
|
||||
},
|
||||
"posters": {
|
||||
"beamerposter": {
|
||||
"file": "beamerposter_academic.tex",
|
||||
"full_name": "Beamerposter Academic",
|
||||
"description": "Classic academic conference poster using beamerposter",
|
||||
"size": "A0, customizable",
|
||||
"package": "beamerposter"
|
||||
}
|
||||
},
|
||||
"grants": {
|
||||
"nsf": {
|
||||
"file": "nsf_proposal_template.tex",
|
||||
"full_name": "NSF Standard Grant",
|
||||
"description": "National Science Foundation research proposal",
|
||||
"page_limit": "15 pages (project description)",
|
||||
"key_sections": "Project Summary, Project Description, Broader Impacts"
|
||||
},
|
||||
"nih_specific_aims": {
|
||||
"file": "nih_specific_aims.tex",
|
||||
"full_name": "NIH Specific Aims Page",
|
||||
"description": "Most critical page of NIH proposals",
|
||||
"page_limit": "1 page (strictly enforced)",
|
||||
"key_sections": "Hook, Hypothesis, 3 Aims, Payoff"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def get_skill_path():
|
||||
"""Get the path to the venue-templates skill directory."""
|
||||
# Assume script is in .claude/skills/venue-templates/scripts/
|
||||
script_dir = Path(__file__).parent
|
||||
skill_dir = script_dir.parent
|
||||
return skill_dir
|
||||
|
||||
def search_templates(venue=None, template_type=None, keyword=None):
|
||||
"""Search for templates matching criteria."""
|
||||
results = []
|
||||
|
||||
for cat_name, category in TEMPLATES.items():
|
||||
# Filter by type if specified
|
||||
if template_type and cat_name != template_type and template_type != "all":
|
||||
continue
|
||||
|
||||
for temp_id, template in category.items():
|
||||
# Filter by venue name
|
||||
if venue:
|
||||
venue_lower = venue.lower()
|
||||
if venue_lower not in temp_id and venue_lower not in template.get("full_name", "").lower():
|
||||
continue
|
||||
|
||||
# Filter by keyword
|
||||
if keyword:
|
||||
keyword_lower = keyword.lower()
|
||||
search_text = json.dumps(template).lower()
|
||||
if keyword_lower not in search_text:
|
||||
continue
|
||||
|
||||
results.append({
|
||||
"id": temp_id,
|
||||
"category": cat_name,
|
||||
"file": template["file"],
|
||||
"full_name": template.get("full_name", temp_id),
|
||||
"description": template.get("description", ""),
|
||||
"details": template
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
def list_all_templates():
|
||||
"""List all available templates."""
|
||||
print("\n=== AVAILABLE TEMPLATES ===\n")
|
||||
|
||||
for cat_name, category in TEMPLATES.items():
|
||||
print(f"\n{cat_name.upper()}:")
|
||||
for temp_id, template in category.items():
|
||||
print(f" • {template.get('full_name', temp_id)}")
|
||||
print(f" File: {template['file']}")
|
||||
if "description" in template:
|
||||
print(f" Description: {template['description']}")
|
||||
print()
|
||||
|
||||
def print_template_info(template):
|
||||
"""Print detailed information about a template."""
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Template: {template['full_name']}")
|
||||
print(f"{'='*60}")
|
||||
print(f"Category: {template['category']}")
|
||||
print(f"File: {template['file']}")
|
||||
|
||||
details = template['details']
|
||||
|
||||
print(f"\nDescription: {details.get('description', 'N/A')}")
|
||||
|
||||
if 'page_limit' in details:
|
||||
print(f"Page Limit: {details['page_limit']}")
|
||||
if 'citation_style' in details:
|
||||
print(f"Citation Style: {details['citation_style']}")
|
||||
if 'format' in details:
|
||||
print(f"Format: {details['format']}")
|
||||
if 'anonymization' in details:
|
||||
print(f"⚠️ Anonymization: {details['anonymization']}")
|
||||
if 'size' in details:
|
||||
print(f"Poster Size: {details['size']}")
|
||||
if 'package' in details:
|
||||
print(f"LaTeX Package: {details['package']}")
|
||||
if 'key_sections' in details:
|
||||
print(f"Key Sections: {details['key_sections']}")
|
||||
|
||||
# Print full path to template
|
||||
skill_path = get_skill_path()
|
||||
template_path = skill_path / "assets" / template['category'] / template['file']
|
||||
print(f"\nFull Path: {template_path}")
|
||||
|
||||
if template_path.exists():
|
||||
print("✓ Template file exists")
|
||||
else:
|
||||
print("✗ Template file not found")
|
||||
|
||||
print()
|
||||
|
||||
def print_requirements(venue):
|
||||
"""Print formatting requirements for a venue."""
|
||||
results = search_templates(venue=venue)
|
||||
|
||||
if not results:
|
||||
print(f"No templates found for venue: {venue}")
|
||||
return
|
||||
|
||||
template = results[0] # Take first match
|
||||
details = template['details']
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"FORMATTING REQUIREMENTS: {template['full_name']}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
if 'page_limit' in details:
|
||||
print(f"📄 Page Limit: {details['page_limit']}")
|
||||
if 'format' in details:
|
||||
print(f"📐 Format: {details['format']}")
|
||||
if 'citation_style' in details:
|
||||
print(f"📚 Citation Style: {details['citation_style']}")
|
||||
if 'anonymization' in details:
|
||||
print(f"🔒 Anonymization: {details['anonymization']}")
|
||||
if 'size' in details:
|
||||
print(f"📏 Size: {details['size']}")
|
||||
|
||||
print(f"\n💡 For detailed requirements, see:")
|
||||
skill_path = get_skill_path()
|
||||
|
||||
if template['category'] == "journals":
|
||||
print(f" {skill_path}/references/journals_formatting.md")
|
||||
elif template['category'] == "posters":
|
||||
print(f" {skill_path}/references/posters_guidelines.md")
|
||||
elif template['category'] == "grants":
|
||||
print(f" {skill_path}/references/grants_requirements.md")
|
||||
|
||||
print()
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Query venue-specific LaTeX templates",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s --list-all
|
||||
%(prog)s --venue "Nature" --type journals
|
||||
%(prog)s --keyword "machine learning"
|
||||
%(prog)s --venue "NeurIPS" --requirements
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument('--venue', type=str, help='Venue name (e.g., "Nature", "NeurIPS")')
|
||||
parser.add_argument('--type', type=str, choices=['journals', 'posters', 'grants', 'all'],
|
||||
help='Template type')
|
||||
parser.add_argument('--keyword', type=str, help='Search keyword')
|
||||
parser.add_argument('--list-all', action='store_true', help='List all available templates')
|
||||
parser.add_argument('--requirements', action='store_true',
|
||||
help='Show formatting requirements for venue')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# List all templates
|
||||
if args.list_all:
|
||||
list_all_templates()
|
||||
return
|
||||
|
||||
# Show requirements
|
||||
if args.requirements:
|
||||
if not args.venue:
|
||||
print("Error: --requirements requires --venue")
|
||||
parser.print_help()
|
||||
return
|
||||
print_requirements(args.venue)
|
||||
return
|
||||
|
||||
# Search for templates
|
||||
if not any([args.venue, args.type, args.keyword]):
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
results = search_templates(venue=args.venue, template_type=args.type, keyword=args.keyword)
|
||||
|
||||
if not results:
|
||||
print("No templates found matching your criteria.")
|
||||
return
|
||||
|
||||
print(f"\nFound {len(results)} template(s):\n")
|
||||
|
||||
for result in results:
|
||||
print_template_info(result)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
255
skills/venue-templates/scripts/validate_format.py
Executable file
255
skills/venue-templates/scripts/validate_format.py
Executable file
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validate Format Script
|
||||
Check if document meets venue-specific formatting requirements.
|
||||
|
||||
Usage:
|
||||
python validate_format.py --file my_paper.pdf --venue "Nature" --check-all
|
||||
python validate_format.py --file my_paper.pdf --venue "NeurIPS" --check page-count,margins
|
||||
python validate_format.py --file my_paper.pdf --venue "PLOS ONE" --report validation_report.txt
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
# Venue requirements database
|
||||
VENUE_REQUIREMENTS = {
|
||||
"nature": {
|
||||
"page_limit": 5, # Approximate for ~3000 words
|
||||
"margins": {"top": 2.5, "bottom": 2.5, "left": 2.5, "right": 2.5}, # cm
|
||||
"font_size": 12, # pt
|
||||
"font_family": "Times",
|
||||
"line_spacing": "double"
|
||||
},
|
||||
"neurips": {
|
||||
"page_limit": 8, # Excluding refs
|
||||
"margins": {"top": 2.54, "bottom": 2.54, "left": 2.54, "right": 2.54}, # cm (1 inch)
|
||||
"font_size": 10,
|
||||
"font_family": "Times",
|
||||
"format": "two-column"
|
||||
},
|
||||
"plos_one": {
|
||||
"page_limit": None, # No limit
|
||||
"margins": {"top": 2.54, "bottom": 2.54, "left": 2.54, "right": 2.54},
|
||||
"font_size": 10,
|
||||
"font_family": "Arial",
|
||||
"line_spacing": "double"
|
||||
},
|
||||
"nsf": {
|
||||
"page_limit": 15, # Project description
|
||||
"margins": {"top": 2.54, "bottom": 2.54, "left": 2.54, "right": 2.54}, # 1 inch required
|
||||
"font_size": 11, # Minimum
|
||||
"font_family": "Times Roman",
|
||||
"line_spacing": "single or double"
|
||||
},
|
||||
"nih": {
|
||||
"page_limit": 12, # Research strategy
|
||||
"margins": {"top": 1.27, "bottom": 1.27, "left": 1.27, "right": 1.27}, # 0.5 inch minimum
|
||||
"font_size": 11, # Arial 11pt minimum
|
||||
"font_family": "Arial",
|
||||
"line_spacing": "any"
|
||||
}
|
||||
}
|
||||
|
||||
def get_pdf_info(pdf_path):
|
||||
"""Extract information from PDF using pdfinfo."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['pdfinfo', str(pdf_path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
info = {}
|
||||
for line in result.stdout.split('\n'):
|
||||
if ':' in line:
|
||||
key, value = line.split(':', 1)
|
||||
info[key.strip()] = value.strip()
|
||||
|
||||
return info
|
||||
except FileNotFoundError:
|
||||
print("⚠️ pdfinfo not found. Install poppler-utils for full PDF analysis.")
|
||||
print(" macOS: brew install poppler")
|
||||
print(" Linux: sudo apt-get install poppler-utils")
|
||||
return None
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error running pdfinfo: {e}")
|
||||
return None
|
||||
|
||||
def check_page_count(pdf_path, venue_reqs):
|
||||
"""Check if page count is within limit."""
|
||||
pdf_info = get_pdf_info(pdf_path)
|
||||
|
||||
if not pdf_info:
|
||||
return {"status": "skip", "message": "Could not determine page count"}
|
||||
|
||||
pages = int(pdf_info.get('Pages', 0))
|
||||
limit = venue_reqs.get('page_limit')
|
||||
|
||||
if limit is None:
|
||||
return {"status": "pass", "message": f"No page limit. Document has {pages} pages."}
|
||||
|
||||
if pages <= limit:
|
||||
return {"status": "pass", "message": f"✓ Page count OK: {pages}/{limit} pages"}
|
||||
else:
|
||||
return {"status": "fail", "message": f"✗ Page count exceeded: {pages}/{limit} pages"}
|
||||
|
||||
def check_margins(pdf_path, venue_reqs):
|
||||
"""Check if margins meet requirements."""
|
||||
# Note: This is a simplified check. Full margin analysis requires more sophisticated tools.
|
||||
req_margins = venue_reqs.get('margins', {})
|
||||
|
||||
if not req_margins:
|
||||
return {"status": "skip", "message": "No margin requirements specified"}
|
||||
|
||||
# This is a placeholder - accurate margin checking requires parsing PDF content
|
||||
return {
|
||||
"status": "info",
|
||||
"message": f"ℹ️ Required margins: {req_margins} cm (manual verification recommended)"
|
||||
}
|
||||
|
||||
def check_fonts(pdf_path, venue_reqs):
|
||||
"""Check fonts in PDF."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['pdffonts', str(pdf_path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
fonts_found = []
|
||||
for line in result.stdout.split('\n')[2:]: # Skip header
|
||||
if line.strip():
|
||||
parts = line.split()
|
||||
if parts:
|
||||
fonts_found.append(parts[0])
|
||||
|
||||
req_font = venue_reqs.get('font_family', '')
|
||||
req_size = venue_reqs.get('font_size')
|
||||
|
||||
message = f"ℹ️ Fonts found: {', '.join(set(fonts_found))}\n"
|
||||
message += f" Required: {req_font}"
|
||||
if req_size:
|
||||
message += f" {req_size}pt minimum"
|
||||
|
||||
return {"status": "info", "message": message}
|
||||
|
||||
except FileNotFoundError:
|
||||
return {"status": "skip", "message": "pdffonts not available"}
|
||||
except subprocess.CalledProcessError:
|
||||
return {"status": "skip", "message": "Could not extract font information"}
|
||||
|
||||
def validate_document(pdf_path, venue, checks):
|
||||
"""Validate document against venue requirements."""
|
||||
|
||||
venue_key = venue.lower().replace(" ", "_")
|
||||
|
||||
if venue_key not in VENUE_REQUIREMENTS:
|
||||
print(f"❌ Unknown venue: {venue}")
|
||||
print(f"Available venues: {', '.join(VENUE_REQUIREMENTS.keys())}")
|
||||
return
|
||||
|
||||
venue_reqs = VENUE_REQUIREMENTS[venue_key]
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"VALIDATING: {pdf_path.name}")
|
||||
print(f"VENUE: {venue}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
results = {}
|
||||
|
||||
# Run requested checks
|
||||
if 'page-count' in checks or 'all' in checks:
|
||||
results['page-count'] = check_page_count(pdf_path, venue_reqs)
|
||||
|
||||
if 'margins' in checks or 'all' in checks:
|
||||
results['margins'] = check_margins(pdf_path, venue_reqs)
|
||||
|
||||
if 'fonts' in checks or 'all' in checks:
|
||||
results['fonts'] = check_fonts(pdf_path, venue_reqs)
|
||||
|
||||
# Print results
|
||||
for check_name, result in results.items():
|
||||
print(f"{check_name.upper()}:")
|
||||
print(f" {result['message']}\n")
|
||||
|
||||
# Summary
|
||||
failures = sum(1 for r in results.values() if r['status'] == 'fail')
|
||||
passes = sum(1 for r in results.values() if r['status'] == 'pass')
|
||||
|
||||
print(f"{'='*60}")
|
||||
if failures == 0:
|
||||
print(f"✓ VALIDATION PASSED ({passes} checks)")
|
||||
else:
|
||||
print(f"✗ VALIDATION FAILED ({failures} issues)")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
return results
|
||||
|
||||
def generate_report(pdf_path, venue, results, report_path):
|
||||
"""Generate validation report."""
|
||||
|
||||
with open(report_path, 'w') as f:
|
||||
f.write(f"Validation Report\n")
|
||||
f.write(f"{'='*60}\n\n")
|
||||
f.write(f"File: {pdf_path}\n")
|
||||
f.write(f"Venue: {venue}\n")
|
||||
f.write(f"Date: {Path.ctime(pdf_path)}\n\n")
|
||||
|
||||
for check_name, result in results.items():
|
||||
f.write(f"{check_name.upper()}:\n")
|
||||
f.write(f" Status: {result['status']}\n")
|
||||
f.write(f" {result['message']}\n\n")
|
||||
|
||||
failures = sum(1 for r in results.values() if r['status'] == 'fail')
|
||||
f.write(f"\nSummary: {'PASSED' if failures == 0 else 'FAILED'}\n")
|
||||
|
||||
print(f"Report saved to: {report_path}")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Validate document formatting for venue requirements",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s --file my_paper.pdf --venue "Nature" --check-all
|
||||
%(prog)s --file my_paper.pdf --venue "NeurIPS" --check page-count,fonts
|
||||
%(prog)s --file proposal.pdf --venue "NSF" --report validation.txt
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument('--file', type=str, required=True, help='PDF file to validate')
|
||||
parser.add_argument('--venue', type=str, required=True, help='Target venue')
|
||||
parser.add_argument('--check', type=str, default='all',
|
||||
help='Checks to perform: page-count, margins, fonts, all (comma-separated)')
|
||||
parser.add_argument('--check-all', action='store_true', help='Perform all checks')
|
||||
parser.add_argument('--report', type=str, help='Save report to file')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check file exists
|
||||
pdf_path = Path(args.file)
|
||||
if not pdf_path.exists():
|
||||
print(f"Error: File not found: {pdf_path}")
|
||||
return
|
||||
|
||||
# Parse checks
|
||||
if args.check_all:
|
||||
checks = ['all']
|
||||
else:
|
||||
checks = [c.strip() for c in args.check.split(',')]
|
||||
|
||||
# Validate
|
||||
results = validate_document(pdf_path, args.venue, checks)
|
||||
|
||||
# Generate report if requested
|
||||
if args.report and results:
|
||||
generate_report(pdf_path, args.venue, results, Path(args.report))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user