|
| 1 | +# provides |
| 2 | + |
| 3 | +A Go library for mapping package identities to the names used in source code. A Python distribution may provide a differently named module, a Go module may contain several package paths, and a Maven artifact may contain several Java packages. Project bindings retain dependency renames and aliases separately from the canonical package identity. |
| 4 | + |
| 5 | +## Installation |
| 6 | + |
| 7 | +```bash |
| 8 | +go get github.com/git-pkgs/provides |
| 9 | +``` |
| 10 | + |
| 11 | +## Model |
| 12 | + |
| 13 | +`Surface` maps a versioned PURL to its provided source names. `Binding` connects a PURL to the imported and local names used by one project. An aliased binding may also retain the package-side target name. Both retain the evidence used to produce the mapping. |
| 14 | + |
| 15 | +`ProvidedName.Name` is the exact source-visible spelling. Package-manager normalisation does not apply to it, and matching is case-sensitive. `flask` therefore does not match `Flask`. |
| 16 | + |
| 17 | +## Matching names |
| 18 | + |
| 19 | +Exact names match only themselves. Prefix names also match descendants separated by the configured boundary: |
| 20 | + |
| 21 | +```go |
| 22 | +pythonModule := provides.ProvidedName{ |
| 23 | + Language: "python", |
| 24 | + Name: "werkzeug", |
| 25 | + Kind: "module", |
| 26 | + Match: provides.MatchPrefix, |
| 27 | + Separator: ".", |
| 28 | +} |
| 29 | + |
| 30 | +pythonModule.Matches("werkzeug") // true |
| 31 | +pythonModule.Matches("werkzeug.http") // true |
| 32 | +pythonModule.Matches("werkzeugx") // false |
| 33 | +pythonModule.Matches("Werkzeug.http") // false |
| 34 | +``` |
| 35 | + |
| 36 | +Explicit export maps can use exact matching so an exported npm subpath does not imply that deeper paths are available: |
| 37 | + |
| 38 | +```go |
| 39 | +export := provides.ProvidedName{ |
| 40 | + Language: "javascript", |
| 41 | + Name: "react/jsx-runtime", |
| 42 | + Kind: "subpath", |
| 43 | + Match: provides.MatchExact, |
| 44 | +} |
| 45 | + |
| 46 | +export.Matches("react/jsx-runtime") // true |
| 47 | +export.Matches("react/jsx-runtime/private") // false |
| 48 | +``` |
| 49 | + |
| 50 | +## Merging results |
| 51 | + |
| 52 | +Resolvers can find the same mapping through manifests, installed metadata, artifacts, or curated data. `MergeSurfaceResults` and `MergeBindingResults` deduplicate mappings, combine their evidence, retain conflicting mappings, and return stable ordering. |
| 53 | + |
| 54 | +```go |
| 55 | +const purl = "pkg:pypi/pyyaml@6.0.3" |
| 56 | + |
| 57 | +manifest := provides.SurfaceResult{Surface: provides.Surface{ |
| 58 | + PURL: purl, |
| 59 | + Provides: []provides.ProvidedName{{ |
| 60 | + Language: "python", |
| 61 | + Name: "yaml", |
| 62 | + Kind: "module", |
| 63 | + Evidence: []provides.Evidence{{ |
| 64 | + Method: provides.EvidenceManifest, |
| 65 | + Source: "METADATA", |
| 66 | + }}, |
| 67 | + }}, |
| 68 | +}} |
| 69 | + |
| 70 | +result := provides.MergeSurfaceResults(purl, manifest) |
| 71 | +``` |
| 72 | + |
| 73 | +Non-fatal resolver problems are returned as `Diagnostic` values beside any successful mappings. |
| 74 | + |
| 75 | +## Curated Python surfaces |
| 76 | + |
| 77 | +The `curated` package includes local mappings for PyYAML, `brotlipy`, Brotli, Pillow, and Beautiful Soup. `ResolveProjectSurfaces` joins caller-supplied dependency PURLs to those mappings: |
| 78 | + |
| 79 | +```go |
| 80 | +packages := []provides.Package{ |
| 81 | + {PURL: "pkg:pypi/PyYAML@6.0.3"}, |
| 82 | + {PURL: "pkg:pypi/brotlipy@0.7.0"}, |
| 83 | + {PURL: "pkg:pypi/Pillow@11.0.0"}, |
| 84 | +} |
| 85 | + |
| 86 | +result, err := provides.ResolveProjectSurfaces( |
| 87 | + context.Background(), |
| 88 | + curated.Python(), |
| 89 | + packages, |
| 90 | + provides.SurfaceOptions{}, |
| 91 | +) |
| 92 | +``` |
| 93 | + |
| 94 | +This path reads no files, runs no package-manager commands, and makes no network requests. PyPI distribution names are normalised for catalog lookup, while each returned `Surface.PURL` retains the caller's spelling and version. Unknown packages are omitted without producing a diagnostic. |
| 95 | + |
| 96 | +## Resolving an import |
| 97 | + |
| 98 | +`ResolveImport` combines project-surface resolution with a reverse lookup. Every matching dependency is returned when an import is ambiguous: |
| 99 | + |
| 100 | +```go |
| 101 | +result, err := provides.ResolveImport( |
| 102 | + context.Background(), |
| 103 | + curated.Python(), |
| 104 | + provides.ImportRequest{ |
| 105 | + Language: "python", |
| 106 | + Name: "brotli", |
| 107 | + Packages: []provides.Package{ |
| 108 | + {PURL: "pkg:pypi/brotlipy@0.7.0"}, |
| 109 | + {PURL: "pkg:pypi/brotli@1.1.0"}, |
| 110 | + }, |
| 111 | + }, |
| 112 | +) |
| 113 | +``` |
| 114 | + |
| 115 | +`result.Matches` contains both PURLs and the curated evidence for the `brotli` module. Supplying only the dependency declared by a project narrows the result without applying a package-name heuristic. |
| 116 | + |
| 117 | +## Rust and Go bindings |
| 118 | + |
| 119 | +The `bindings` package parses package-manager output supplied by the caller. It does not read files, run commands, or use the network. |
| 120 | + |
| 121 | +`ParseCargoManifest` reads dependency, development-dependency, build-dependency, and target-specific tables from `Cargo.toml`. A renamed dependency keeps its canonical package in the PURL and exposes its source-visible crate name through `Binding.Imported`: |
| 122 | + |
| 123 | +```go |
| 124 | +result, err := bindings.ParseCargoManifest("Cargo.toml", cargoToml) |
| 125 | +``` |
| 126 | + |
| 127 | +Manifest bindings use versionless PURLs because Cargo manifest versions are constraints. Path and Git dependencies are omitted when the manifest alone cannot establish a registry identity. `ParseCargoMetadata` reads the resolved root package or default workspace members and returns versioned PURLs. Its `resolve.nodes[].deps[].name` value preserves Cargo renames: |
| 128 | + |
| 129 | +```go |
| 130 | +result, err := bindings.ParseCargoMetadata(cargoMetadataJSON) |
| 131 | +``` |
| 132 | + |
| 133 | +`ParseGoList` accepts the concatenated JSON stream written by `go list -deps -json`. Each non-standard dependency package becomes a binding to its module PURL. `Binding.Imported` contains the full import path and `Binding.Local` contains the declared Go package name, which may differ from the last path component: |
| 134 | + |
| 135 | +```go |
| 136 | +result, err := bindings.ParseGoList(goListJSON) |
| 137 | +``` |
| 138 | + |
| 139 | +The caller controls how those byte slices are obtained. Cargo metadata is more precise than a manifest when both are available because it contains resolved versions and exact crate names. |
| 140 | + |
| 141 | +## JavaScript and TypeScript bindings |
| 142 | + |
| 143 | +`ParseNPMManifest` reads registry dependencies and `npm:` aliases from the dependency sections in `package.json`. Manifest ranges produce versionless PURLs. Local paths, Git sources, and URL dependencies are omitted when their npm identity cannot be established. |
| 144 | + |
| 145 | +```go |
| 146 | +result, err := bindings.ParseNPMManifest("package.json", packageJSON) |
| 147 | +``` |
| 148 | + |
| 149 | +For `"my-react": "npm:react@18"`, `Binding.Imported` is `my-react`, `Binding.Target` is `react`, and the PURL is `pkg:npm/react`. |
| 150 | + |
| 151 | +`ParseNPMPackage` reads a package root and explicit `exports` subpaths. It reports JavaScript and TypeScript names separately, with exact matching for each declared export: |
| 152 | + |
| 153 | +```go |
| 154 | +result, err := bindings.ParseNPMPackage( |
| 155 | + "pkg:npm/react@19.0.0", |
| 156 | + "package.json", |
| 157 | + packageJSON, |
| 158 | +) |
| 159 | +``` |
| 160 | + |
| 161 | +Export patterns are returned as diagnostics by the manifest parser. A later artifact resolver can expand them against the files shipped by the package. |
| 162 | + |
| 163 | +`ParseDenoConfig` reads npm targets from the top-level `imports` map in `deno.json`. Keys ending in `/` become literal prefix bindings, while other keys remain exact. `Binding.Target` retains the package-side root or subpath: |
| 164 | + |
| 165 | +```go |
| 166 | +result, err := bindings.ParseDenoConfig("deno.json", denoJSON) |
| 167 | +``` |
| 168 | + |
| 169 | +## Artifact surfaces |
| 170 | + |
| 171 | +The `artifacts` package accepts an `archives.Reader` opened by the caller. It does not download artifacts or add another archive abstraction. |
| 172 | + |
| 173 | +```go |
| 174 | +reader, err := archives.OpenBytes("demo.whl", wheelBytes) |
| 175 | +if err != nil { |
| 176 | + return err |
| 177 | +} |
| 178 | +defer reader.Close() |
| 179 | + |
| 180 | +result, err := artifacts.ResolvePythonWheel( |
| 181 | + context.Background(), |
| 182 | + provides.Package{PURL: "pkg:pypi/demo@1.0.0"}, |
| 183 | + reader, |
| 184 | +) |
| 185 | +``` |
| 186 | + |
| 187 | +The package contains five artifact inspectors: |
| 188 | + |
| 189 | +- `ResolvePythonWheel` reads `Import-Name` and `Import-Namespace`, then falls back to `top_level.txt` and wheel paths when those fields are absent. |
| 190 | +- `ResolveJavaArchive` enumerates Java packages and reads explicit or automatic module names. |
| 191 | +- `ResolveNPMTarball` reads package roots and exports, expanding export patterns against the tarball file list. |
| 192 | +- `ResolveCargoCrate` reads the published library target, with `src/lib.rs` as a fallback. |
| 193 | +- `ResolveGoModule` enumerates directories containing non-test Go source files. |
| 194 | + |
| 195 | +Entry extraction failures become diagnostics when other archive evidence can still produce names. Listing failures and invalid PURLs remain errors. The caller owns and closes the archive reader. |
| 196 | + |
| 197 | +## Resolver interfaces |
| 198 | + |
| 199 | +`SurfaceResolver` resolves the names provided by one package. `BindingResolver` resolves dependency bindings for a project directory. The core package defines these interfaces without running package managers or making network requests. |
| 200 | + |
| 201 | +Further acquisition adapters are planned. The current package contains the shared types, matching rules, merge helpers, resolver interfaces, project-surface join, local binding parsers, artifact inspectors, and the built-in Python catalog used by Hyrum. |
0 commit comments