162 lines
4.7 KiB
Python
162 lines
4.7 KiB
Python
"""CLI entry point for org-to-quartz converter."""
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from .org_parser import parse_org_file
|
|
from .markdown_writer import convert_document, write_markdown
|
|
from .image_handler import copy_images, update_image_paths
|
|
from .citations import CitationResolver
|
|
|
|
|
|
def find_org_files(input_dir: Path) -> list[Path]:
|
|
"""Find all .org files in directory (recursive)."""
|
|
return list(input_dir.rglob("*.org"))
|
|
|
|
|
|
def convert_file(
|
|
org_path: Path,
|
|
input_dir: Path,
|
|
output_dir: Path,
|
|
citation_resolver: CitationResolver | None,
|
|
verbose: bool = False,
|
|
) -> Path | None:
|
|
"""Convert a single org file to Quartz markdown.
|
|
|
|
Args:
|
|
org_path: Path to the org file
|
|
input_dir: Root input directory (for computing relative paths)
|
|
output_dir: Root output directory
|
|
citation_resolver: Optional citation resolver
|
|
verbose: Enable verbose output
|
|
|
|
Returns:
|
|
Path to created note directory, or None on error
|
|
"""
|
|
try:
|
|
# Parse org file
|
|
doc = parse_org_file(org_path)
|
|
|
|
if verbose:
|
|
print(f" Parsed: {doc.title or org_path.stem}")
|
|
|
|
# Compute relative path from input dir to preserve directory structure
|
|
relative_path = org_path.relative_to(input_dir)
|
|
|
|
# Special case: root index.org becomes content/index.md directly
|
|
if relative_path.stem == "index" and relative_path.parent == Path("."):
|
|
note_dir = output_dir
|
|
output_path = output_dir / "index.md"
|
|
else:
|
|
# Replace .org with directory containing index.md
|
|
# e.g., concepts/zettelkasten.org -> concepts/zettelkasten/index.md
|
|
note_relative_dir = relative_path.parent / relative_path.stem
|
|
note_dir = output_dir / note_relative_dir
|
|
output_path = note_dir / "index.md"
|
|
|
|
# Create output directory for this note
|
|
note_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Copy images first (before conversion, to get path mapping)
|
|
image_mapping = copy_images(doc, output_dir)
|
|
|
|
if verbose and image_mapping:
|
|
print(f" Copied {len(image_mapping)} images")
|
|
|
|
# Convert document
|
|
md_content = convert_document(doc, citation_resolver)
|
|
|
|
# Update image paths in converted content
|
|
md_content = update_image_paths(md_content, image_mapping)
|
|
|
|
# Write output
|
|
output_path.write_text(md_content, encoding="utf-8")
|
|
|
|
return note_dir
|
|
|
|
except Exception as e:
|
|
print(f" Error converting {org_path.name}: {e}", file=sys.stderr)
|
|
return None
|
|
|
|
|
|
def main() -> int:
|
|
"""Main CLI entry point."""
|
|
parser = argparse.ArgumentParser(
|
|
prog="org-to-quartz",
|
|
description="Convert org-mode notes to Quartz-compatible markdown",
|
|
)
|
|
parser.add_argument(
|
|
"input_dir",
|
|
type=Path,
|
|
help="Directory containing .org files",
|
|
)
|
|
parser.add_argument(
|
|
"output_dir",
|
|
type=Path,
|
|
help="Output directory (e.g., quartz/content)",
|
|
)
|
|
parser.add_argument(
|
|
"--bib",
|
|
type=Path,
|
|
metavar="FILE",
|
|
help="Path to .bib file for citation resolution",
|
|
)
|
|
parser.add_argument(
|
|
"-v", "--verbose",
|
|
action="store_true",
|
|
help="Verbose output",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Validate input directory
|
|
if not args.input_dir.is_dir():
|
|
print(f"Error: Input directory does not exist: {args.input_dir}", file=sys.stderr)
|
|
return 1
|
|
|
|
# Create output directory
|
|
args.output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Find org files
|
|
org_files = find_org_files(args.input_dir)
|
|
if not org_files:
|
|
print(f"No .org files found in {args.input_dir}", file=sys.stderr)
|
|
return 1
|
|
|
|
print(f"Found {len(org_files)} org files")
|
|
|
|
# Initialize citation resolver
|
|
citation_resolver = CitationResolver(args.bib)
|
|
|
|
if args.verbose:
|
|
if citation_resolver.zotero.is_available():
|
|
print("Zotero Better BibTeX: available")
|
|
else:
|
|
print("Zotero Better BibTeX: not available")
|
|
if args.bib:
|
|
print(f"BibTeX file: {args.bib}")
|
|
|
|
# Convert each file
|
|
success_count = 0
|
|
error_count = 0
|
|
|
|
for org_path in org_files:
|
|
print(f"Converting: {org_path.name}")
|
|
result = convert_file(org_path, args.input_dir, args.output_dir, citation_resolver, args.verbose)
|
|
if result:
|
|
success_count += 1
|
|
if args.verbose:
|
|
print(f" -> {result}")
|
|
else:
|
|
error_count += 1
|
|
|
|
# Summary
|
|
print(f"\nDone: {success_count} converted, {error_count} errors")
|
|
|
|
return 0 if error_count == 0 else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|