Documentation
¶
Overview ¶
Package adapter is sofia's Tier-1, host-interpreted plugin mechanism: a plugin.yaml `adapter:` block declares project conventions (a root key, root markers, file extensions, and named layers with path globs), and the host — not a subprocess — turns those into project-aware `layers`/`grep`/`refs` commands grouped by layer. It is the generalization of the private "crm usages" pattern; a pure-adapter plugin ships no executable at all.
This package holds the typed schema (Config), its validation, the glob matcher, the root resolver, the layer classifier, and the synthesized cobra commands. It deliberately does NOT import internal/plugin (the manifest tier depends on this one; the reverse would be a cycle).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Classify ¶
Classify returns the name of the first layer whose any Match glob hits the given path, or "" when no layer claims it. The path is taken relative to the resolved project root; it is normalized to forward slashes and stripped of a leading "./" so a caller can pass either an OS path or a slash path. Layers are tried in declared order, so an adapter author controls precedence by ordering the block — and the classification is byte-stable.
func Commands ¶
Commands synthesizes the project-aware subcommands the host attaches under `sf <pluginName>` for an adapter plugin: `layers`, `grep`, and `refs`. They are real, in-process cobra commands — they resolve a project root, run the generic grep/refs scanners scoped to the adapter's extensions, and group the results by layer. Nothing here forks a subprocess; a pure-adapter plugin has no executable to fork.
func Match ¶
Match reports whether a slash-separated path matches a layer glob. It is a small segmented globber — no go.mod dependency (doublestar isn't vendored) — with exactly the one extension filepath.Match lacks: `**`, which matches zero or more whole path segments. Both pattern and path are split on "/"; within a segment, filepath.Match's metacharacters (`*`, `?`, `[…]`) apply and never cross a "/". So `src/*` matches `src/User.php` but not `src/Domain/User.php`, while `src/Domain/**` matches `src/Domain` and everything beneath it.
func ResolveRoot ¶
ResolveRoot finds the project root an adapter's commands operate on, trying, in order:
- $<RootKey> — when the config names a root key and that environment variable is set and points at an existing directory, it wins outright. This is the explicit escape hatch (e.g. CRM_ROOT) for working outside the tree, or from a checkout whose markers live elsewhere.
- a walk up from startDir to the nearest ancestor containing any RootMarker — the ordinary case, modelled on phpcode.loadComposerPSR4 and plugin.projectRoot.
It returns an error naming the markers when neither locates a root, so the synthesized command can say precisely why it couldn't run. The command layer adds the higher-precedence explicit `--root` on top of this.
Types ¶
type Config ¶
Config is the typed, validated form of a plugin's `adapter:` block. Kind names the adapter family (informational); RootKey is the environment variable that pins the project root when set; RootMarkers are the files a walk-up looks for otherwise; Ext scopes grep/refs to matching extensions; Layers classify a project-relative path into a named layer by glob.
func Parse ¶
Parse turns an adapter block's kind + spec map into a typed Config. It does not validate — call Config.Validate on the result before trusting it. The spec is re-marshalled to YAML and decoded into rawConfig so the same code path handles a freshly-parsed manifest and one that survived the plugins.json JSON cache (where map/number shapes differ).
func (Config) Validate ¶
Validate checks the config's invariants, reporting the first offending field (the per-field-error style pack.Manifest.Validate uses). RootMarkers is required and every marker must be a safe relative path. Layers are optional, but each must have a non-empty, unique name and at least one Match glob, and every glob must be a safe relative path. A stated but empty `ext:` (all entries blank) is rejected — it reads as "some extensions" but scopes to none.