Spaces:
Sleeping
Sleeping
File size: 7,767 Bytes
fc906cf 6873fcf d7a80a2 6873fcf fc906cf d7a80a2 6873fcf fc906cf aba5c8f fc906cf 1e0ae88 fc906cf b9997e0 afd6fd6 fc906cf 9067804 fc906cf 1e0ae88 fc906cf bdd3513 fc906cf c8afaed fc906cf c8afaed fc906cf 384e013 1b407a4 867d2c5 7ea1b99 867d2c5 7ea1b99 fc906cf 384e013 fc906cf 384e013 1855cca 384e013 7ea1b99 867d2c5 7ea1b99 867d2c5 7ea1b99 fc906cf 1e0ae88 fc906cf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | """
Command-line interface for citation verification.
"""
import sys
from pathlib import Path
import click
from colorama import init, Fore, Style
from .parser import parse_bibtex_file, format_entry_summary
from .verifier import CitationVerifier
def validate_bibtex_file(ctx, param, value):
"""Validate that the BibTeX file exists."""
if not Path(value).exists():
raise click.BadParameter(
f"File not found: {value}\n"
f"Tip: Specify a BibTeX file or ensure 'references.bib' exists in the current directory"
)
return value
@click.command()
@click.option('--bibtex-file', default='references.bib', type=click.Path(),
callback=validate_bibtex_file,
help='BibTeX file to verify (default: references.bib)')
@click.option('--timeout', default=10, help='Request timeout in seconds')
@click.option('--max-retries', default=3, help='Maximum retries for 429 rate limit errors')
@click.option('--verbose', '-v', is_flag=True, help='Show detailed output')
@click.option('--summary-only', '-s', is_flag=True, help='Show only summary')
@click.option('--crossref-mailto', default='', help='Email for CrossRef polite pool (improves rate limits)')
def main(bibtex_file, timeout, max_retries, verbose, summary_only, crossref_mailto):
"""
Verify citations in a BibTeX file.
This tool checks if citations are valid by:
1. Verifying the paper can be found online with a quick search
2. Validating provided URLs are correct and accessible
3. Checking title and author metadata
4. Identifying version information (arXiv, published, etc.)
Example:
verify-citations --bibtex-file references.bib
verify-citations --bibtex-file sample.bib --verbose
verify-citations --max-retries 5
verify-citations (uses default references.bib)
"""
# Initialize colorama for cross-platform colored output
init(autoreset=True)
click.echo(f"{Fore.CYAN}=== Citation Verification Tool ==={Style.RESET_ALL}\n")
click.echo(f"Processing: {bibtex_file}\n")
try:
# Parse BibTeX file
entries = parse_bibtex_file(bibtex_file)
click.echo(f"Found {len(entries)} citation(s) to verify\n")
if not entries:
click.echo(f"{Fore.YELLOW}No citations found in file{Style.RESET_ALL}")
return
# Verify each citation
verifier = CitationVerifier(timeout=timeout, max_retries=max_retries, crossref_mailto=crossref_mailto)
results = []
for i, entry in enumerate(entries, 1):
if not summary_only:
click.echo(f"{Fore.CYAN}[{i}/{len(entries)}] Verifying:{Style.RESET_ALL}")
click.echo(f" {format_entry_summary(entry)}")
result = verifier.verify_citation(entry)
results.append(result)
if not summary_only:
_print_result(result, verbose)
click.echo() # Blank line between entries
# Print summary
_print_summary(results)
except Exception as e:
click.echo(f"{Fore.RED}Error: {str(e)}{Style.RESET_ALL}", err=True)
if verbose:
import traceback
traceback.print_exc()
sys.exit(1)
def _print_result(result: dict, verbose: bool):
"""Print verification result for a single citation."""
status = result['status']
# Status indicator
if status == 'verified':
status_msg = f"{Fore.GREEN}✓ VERIFIED{Style.RESET_ALL}"
elif status == 'issues_found':
status_msg = f"{Fore.RED}✗ ISSUES FOUND{Style.RESET_ALL}"
else:
status_msg = f"{Fore.YELLOW}⚠ INCOMPLETE{Style.RESET_ALL}"
click.echo(f" Status: {status_msg}")
# Print verbose logs if in verbose mode
if verbose and result.get('verbose_logs'):
for log in result['verbose_logs']:
click.echo(f" {Fore.CYAN}{log}{Style.RESET_ALL}")
# Print messages
if verbose or result['status'] != 'verified':
for msg in result['messages']:
# Color code messages
if msg.startswith('✓'):
colored_msg = f"{Fore.GREEN}{msg}{Style.RESET_ALL}"
elif msg.startswith('✗'):
# Red for critical errors (URL invalid, paper not found)
colored_msg = f"{Fore.RED}{msg}{Style.RESET_ALL}"
elif msg.startswith('⚠'):
# Yellow for warnings (metadata mismatches)
colored_msg = f"{Fore.YELLOW}{msg}{Style.RESET_ALL}"
elif msg.startswith('ℹ'):
colored_msg = f"{Fore.CYAN}{msg}{Style.RESET_ALL}"
else:
colored_msg = msg
click.echo(f" {colored_msg}")
def _print_summary(results: list):
"""Print summary of all verification results."""
click.echo(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}")
click.echo(f"{Fore.CYAN}SUMMARY{Style.RESET_ALL}\n")
verified = sum(1 for r in results if r['status'] == 'verified')
issues = sum(1 for r in results if r['status'] == 'issues_found')
incomplete = sum(1 for r in results if r['status'] == 'incomplete')
# Count citations with actual 403 errors (where has_403 flag is True)
citations_with_403 = [r for r in results if r.get('has_403', False)]
# Count citations where metadata could not be verified automatically
# This matches the count of "Could not verify metadata automatically" messages
citations_no_metadata_verification = [
r for r in results
if r.get('metadata_not_verified', False)
]
total = len(results)
click.echo(f"Total citations: {total}")
click.echo(f"{Fore.GREEN}Verified: {verified}{Style.RESET_ALL}")
click.echo(f"{Fore.RED}Issues found: {issues}{Style.RESET_ALL}")
click.echo(f"{Fore.YELLOW}Incomplete: {incomplete}{Style.RESET_ALL}")
# Show citations with issues (excluding those that only have 403 errors)
if issues > 0:
click.echo(f"\n{Fore.YELLOW}Citations with issues:{Style.RESET_ALL}")
for result in results:
if result['status'] == 'issues_found':
click.echo(f"- {result['id']}: {result['title']}")
# Show citations with 403 errors separately
if citations_with_403:
click.echo(f"\n{Fore.YELLOW}Citations where you should manually check the links due to a 403 error:{Style.RESET_ALL}")
for result in citations_with_403:
click.echo(f"- {result['id']}: {result['title']}")
# Show citations where authors could not be verified
if citations_no_metadata_verification:
click.echo(f"\n{Fore.YELLOW}Citations where author list could not be verified:{Style.RESET_ALL}")
click.echo(f"Count: {len(citations_no_metadata_verification)}\n")
for result in citations_no_metadata_verification:
click.echo(f"- {result['id']}: {result['title']}")
# DOI verification via CrossRef
doi_checked = [r for r in results if r['checks'].get('doi_correct') is not None]
if doi_checked:
doi_ok = [r for r in doi_checked if r['checks']['doi_correct'] is True]
doi_bad = [r for r in doi_checked if r['checks']['doi_correct'] is False]
click.echo(f"\n{Fore.CYAN}DOI verification via CrossRef:{Style.RESET_ALL}")
click.echo(f" Checked: {len(doi_checked)}, Verified: {len(doi_ok)}, Issues: {len(doi_bad)}")
if doi_bad:
click.echo(f"\n{Fore.YELLOW}Citations with DOI issues:{Style.RESET_ALL}")
for result in doi_bad:
click.echo(f"- {result['id']}: {result['title']}")
if __name__ == '__main__':
main()
|