Return the correct extractor function for a file, or None if unsupported.
(path: Path)
| 16079 | |
| 16080 | |
| 16081 | def _get_extractor(path: Path) -> Any | None: |
| 16082 | """Return the correct extractor function for a file, or None if unsupported.""" |
| 16083 | if path.name.lower().endswith(".blade.php"): |
| 16084 | return extract_blade |
| 16085 | # MCP config files (.mcp.json, claude_desktop_config.json, ...) are routed |
| 16086 | # by filename before generic .json dispatch so they get MCP-aware nodes |
| 16087 | # (servers, commands, packages, env vars) instead of opaque JSON keys. |
| 16088 | if is_mcp_config_path(path): |
| 16089 | return extract_mcp_config |
| 16090 | # Package manifests (apm.yml, pyproject.toml, go.mod, pom.xml) → a canonical |
| 16091 | # package node + depends_on edges, by filename before generic suffix dispatch |
| 16092 | # (#1377). apm.yml would otherwise be a .yml document handled by the LLM. |
| 16093 | if is_package_manifest_path(path): |
| 16094 | return extract_package_manifest |
| 16095 | # `.h` is C/C++/ObjC-ambiguous; route Objective-C headers to extract_objc |
| 16096 | # (the suffix map sends `.h` to extract_c, which can't read @interface etc.). |
| 16097 | # ObjC sniffing has priority over the C++ sniff: an Objective-C++ header can |
| 16098 | # contain both `@interface` and inline C++ (`::`), and it must parse as ObjC. |
| 16099 | suffix = path.suffix |
| 16100 | if suffix not in _DISPATCH and suffix.lower() in _DISPATCH: |
| 16101 | suffix = suffix.lower() |
| 16102 | if suffix == ".h": |
| 16103 | if _is_objc_header(path): |
| 16104 | return extract_objc |
| 16105 | # A C++ class header routed to extract_c loses the class entirely (the C |
| 16106 | # grammar has no class_specifier). Reroute to extract_cpp (#1547). |
| 16107 | if _is_cpp_header(path): |
| 16108 | return extract_cpp |
| 16109 | # Extensionless files: resolve by shebang, mirroring detect.classify_file. |
| 16110 | # Without this, detect labels e.g. `#!/usr/bin/env bash` CLIs as code but |
| 16111 | # extraction returns no extractor and the file silently contributes nothing. |
| 16112 | if not suffix: |
| 16113 | from graphify.detect import _shebang_interpreter |
| 16114 | interp = _shebang_interpreter(path) |
| 16115 | if interp is not None: |
| 16116 | return _SHEBANG_DISPATCH.get(interp) |
| 16117 | return _DISPATCH.get(suffix) |
| 16118 | |
| 16119 | |
| 16120 | def _safe_extract_with_xaml_root(extractor, path: Path, root: Path) -> dict: |