"""Search GitHub organization repositories for a term and return exact matches first.""" import argparse import os import re from typing import List, Dict, Any from github import Github class GitHubOrgSearcher: """Search GitHub organization for code matches.""" def __init__(self, token: str, org: str): """Initialize the searcher. Args: token: GitHub personal access token org: Organization name """ self.github = Github(token) self.org = org def search_code(self, search_term: str) -> Dict[str, List[Dict[str, Any]]]: """Search for code in the organization. Args: search_term: Term or phrase to search for (typically an email address) Returns: Dictionary with 'exact' and 'partial' match lists """ # Determine search terms based on email format search_terms = [search_term] # Always search for the full term # Parse email if it contains @ if '@' in search_term: local_part = search_term.split('@')[0] # Check if there's a dot in the local part if '.' in local_part: # Extract the last name (part after the last dot) last_name = local_part.split('.')[-1] search_terms.append(last_name) print(f"Email has dot - searching for '{search_term}' and '{last_name}'") else: # Use the whole local part (username) search_terms.append(local_part) print(f"Email without dot - searching for '{search_term}' and '{local_part}'") # Collect all results across search terms all_results = [] seen_shas = set() # Track unique files by SHA to avoid duplicates for term in search_terms: query = f"{term} org:{self.org}" print(f" Searching GitHub for: {term}") try: code_results = self.github.search_code(query) for result in code_results: # Skip if we've already seen this file if result.sha in seen_shas: continue seen_shas.add(result.sha) all_results.append(result) except Exception as e: print(f" Warning: Search for '{term}' failed: {e}") continue # Separate exact matches from partial matches exact_matches = [] partial_matches = [] # Create regex patterns for exact word match for all search terms exact_patterns = [ re.compile(rf"\b{re.escape(term)}\b", re.IGNORECASE) for term in search_terms ] for result in all_results: try: # Get the file content content = result.decoded_content.decode("utf-8") # Check if the exact term exists in the content match_data = { "repository": result.repository.full_name, "path": result.path, "url": result.html_url, "sha": result.sha, } # Check if any of the search terms match exactly if any(pattern.search(content) for pattern in exact_patterns): exact_matches.append(match_data) else: partial_matches.append(match_data) except Exception as e: # If we can't decode, skip this file print(f"Warning: Could not process {result.path}: {e}") continue return {"exact": exact_matches, "partial": partial_matches} def main(): """Run the GitHub org search.""" parser = argparse.ArgumentParser( description="Search GitHub org for a term, exact matches first" ) parser.add_argument("--search_term", help="Term or phrase to search for") parser.add_argument("--org", help="GitHub organization name", default="theorchard") parser.add_argument( "--token", help="GitHub personal access token (or set GITHUB_TOKEN env var)", default=os.environ.get("GITHUB_TOKEN"), ) args = parser.parse_args() if not args.token: print("Error: GitHub token required. Set GITHUB_TOKEN or use --token") return 1 # GitHub search searcher = GitHubOrgSearcher(args.token, args.org) print(f"Searching for '{args.search_term}' in org '{args.org}'...") results = searcher.search_code(args.search_term) print(f"\n{'='*80}") print(f"EXACT MATCHES ({len(results['exact'])} found)") print(f"{'='*80}\n") for match in results["exact"]: print(f"Repository: {match['repository']}") print(f"Path: {match['path']}") print(f"URL: {match['url']}") print() print(f"\n{'='*80}") print(f"PARTIAL MATCHES ({len(results['partial'])} found)") print(f"{'='*80}\n") for match in results["partial"]: print(f"Repository: {match['repository']}") print(f"Path: {match['path']}") print(f"URL: {match['url']}") print() total = len(results["exact"]) + len(results["partial"]) print(f"\nTotal matches: {total}") print(f"Exact: {len(results['exact'])}, Partial: {len(results['partial'])}") return 0 if __name__ == "__main__": exit(main())