-
-
Notifications
You must be signed in to change notification settings - Fork 4
Add CLI script for resume text/link extraction #185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6c17ed9
6fa26f8
f3304b1
2cce58f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| #!/usr/bin/env python3 | ||
| """Command-line helper to exercise resume text and link extraction.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import json | ||
| from dataclasses import dataclass | ||
| from pathlib import Path | ||
| import sys | ||
| from typing import Any | ||
|
|
||
| from five08.document_text import extract_document_text | ||
| from five08.resume_extractor import ResumeProfileExtractor | ||
|
|
||
|
|
||
| @dataclass | ||
| class FileResult: | ||
| path: str | ||
| text_length: int | ||
| extracted_text: str | ||
| links: list[tuple[str, float]] | ||
|
|
||
|
|
||
| def _parse_args() -> argparse.Namespace: | ||
| parser = argparse.ArgumentParser( | ||
| description=( | ||
| "Extract resume text with shared helpers and print the links discovered " | ||
| "by the current resume URL extraction logic." | ||
| ) | ||
| ) | ||
| parser.add_argument( | ||
| "files", | ||
| nargs="+", | ||
| metavar="FILE", | ||
| help="One or more resume files to extract.", | ||
| ) | ||
| parser.add_argument( | ||
| "--text-max", | ||
| type=int, | ||
| default=4000, | ||
| help="Max characters of extracted text to print. Set to 0 for full text.", | ||
| ) | ||
| parser.add_argument( | ||
| "--json", | ||
| action="store_true", | ||
| help="Print machine-readable JSON output instead of pretty text.", | ||
| ) | ||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def _shorten_text(text: str, limit: int) -> str: | ||
| if limit <= 0 or len(text) <= limit: | ||
| return text | ||
| omitted = len(text) - limit | ||
| return f"{text[:limit]}\n... ({omitted} more chars omitted)" | ||
|
|
||
|
|
||
| def extract_website_link_candidates(text: str) -> list[tuple[str, float]]: | ||
| """Extract normalized website links and confidence scores from resume text.""" | ||
| return ResumeProfileExtractor._extract_website_link_candidates(text) | ||
|
|
||
|
|
||
| def _process_file(path: Path) -> FileResult: | ||
| raw = path.read_bytes() | ||
| text = extract_document_text(raw, filename=path.name) | ||
| link_candidates = extract_website_link_candidates(text) | ||
| return FileResult( | ||
| path=str(path), | ||
| text_length=len(text), | ||
| extracted_text=text, | ||
| links=link_candidates, | ||
| ) | ||
|
|
||
|
|
||
| def _print_pretty(results: list[FileResult], text_max: int) -> None: | ||
| for index, result in enumerate(results, start=1): | ||
| print(f"\n[{index}] {result.path}") | ||
| print(f" extracted_text_len={result.text_length}") | ||
| print(" extracted_text:") | ||
| print(_shorten_text(result.extracted_text, text_max)) | ||
|
|
||
| if result.links: | ||
| print(" extracted_links:") | ||
| for link, confidence in result.links: | ||
| print(f" - {link} (confidence={confidence:.3f})") | ||
| else: | ||
| print(" extracted_links: []") | ||
|
|
||
|
|
||
| def _print_json(results: list[FileResult], text_max: int) -> None: | ||
| payload: list[dict[str, Any]] = [] | ||
| for result in results: | ||
| payload.append( | ||
| { | ||
| "path": result.path, | ||
| "extracted_text_length": result.text_length, | ||
| "extracted_text": _shorten_text(result.extracted_text, text_max), | ||
| "extracted_links": [ | ||
| {"url": url, "confidence": confidence} | ||
| for url, confidence in result.links | ||
| ], | ||
| } | ||
| ) | ||
| print(json.dumps(payload, indent=2)) | ||
|
|
||
|
|
||
| def main() -> None: | ||
| args = _parse_args() | ||
| results: list[FileResult] = [] | ||
| exit_code = 0 | ||
|
|
||
| for file_arg in args.files: | ||
| path = Path(file_arg) | ||
| if not path.is_file(): | ||
| print(f"error: missing file: {path}", file=sys.stderr) | ||
| exit_code = 1 | ||
| continue | ||
|
Comment on lines
+115
to
+118
|
||
| try: | ||
| results.append(_process_file(path)) | ||
| except Exception as exc: # pragma: no cover - runtime diagnostic path | ||
| print(f"error: failed to process {path}: {exc}", file=sys.stderr) | ||
| exit_code = 1 | ||
|
Comment on lines
+121
to
+123
|
||
|
|
||
| if not results: | ||
| raise SystemExit(exit_code or 1) | ||
|
|
||
| if args.json: | ||
| _print_json(results, args.text_max) | ||
| else: | ||
| _print_pretty(results, args.text_max) | ||
|
|
||
| raise SystemExit(exit_code) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR description says it adds
scripts/resume_text_link_test.py, but the actual added file isscripts/resume_text_extractor.py. Please update the PR description (or rename the script) so the documentation matches what was merged.