Exported API¶
Every exported symbol in gitlab.com/phpboyscout/go/workspace, what it returns, and
how it behaves at the edges.
Detect — walk up from a directory you name¶
Resolves startDir with filepath.Abs, then for each directory from there upwards
calls fs.Stat(filepath.Join(dir, marker)) for every entry in markers, in order.
The first Stat that succeeds ends the walk and returns
&Workspace{Root: dir, Marker: marker}.
The climb stops when any of these happens first:
- a marker matched — returns the
Workspace,err == nil; filepath.Dir(dir) == dir, meaning the filesystem root was reached — returnsnil, ErrNotFound;- the depth budget ran out — returns
nil, ErrNotFound.
fs and markers have no defaults; both are required positional arguments. Passing a
nil marker slice is legal and always yields ErrNotFound.
Two errors are possible and they are not the same:
| Error | When | How to test |
|---|---|---|
ErrNotFound |
No marker matched anywhere the walk looked | errors.Is(err, workspace.ErrNotFound) |
"resolving start directory: …" |
filepath.Abs failed, which only happens for a relative startDir when os.Getwd fails |
errors.Is against the wrapped os error |
A returned *Workspace is never nil when err == nil, and is always nil when
err != nil.
DetectFromCWD — walk up from the process working directory¶
Calls os.Getwd() and passes the result to Detect unchanged. Everything above
applies. If os.Getwd fails it returns "getting current directory: …" wrapping the
os error.
The working directory comes from the operating system, not from fs. Handing this
function an afero.NewMemMapFs() therefore starts the walk at the real process
working directory, inside the in-memory filesystem — which is why the in-memory tests
in this repository seed cwd + "/go.mod" rather than an arbitrary path.
Workspace — what a successful detection tells you¶
type Workspace struct {
Root string // absolute path to the directory where a marker matched
Marker string // the entry from your markers slice that matched
}
Root is always absolute, because the walk begins with filepath.Abs(startDir).
Marker is the string as you supplied it, not a resolved path — for
DefaultMarkers it is one of ".gtb/manifest.yaml", "go.mod" or ".git". That is
what lets a caller tell "this is a generated project" from "this is some Go module"
without a second Stat.
The struct has no methods, and neither field is validated after the fact. Joining them
back together with filepath.Join(ws.Root, ws.Marker) reproduces the path that
matched.
DefaultMarkers — the built-in Go-shaped marker set¶
var DefaultMarkers = []string{
".gtb/manifest.yaml", // GTB-generated project
"go.mod", // Go module root
".git", // Git repository root
}
Order is significant and is precedence within a single directory: a directory
holding both .gtb/manifest.yaml and go.mod resolves as the manifest. It is not
precedence across directory levels — the nearest directory with any match wins,
whichever marker that is. The marker walk works
through why.
This is an exported var, not a constant, so it is writable by any package that
imports it. Assigning to an element (DefaultMarkers[0] = "…") changes it for every
caller in the process. Appending is safe today because len == cap == 3, so append
allocates a new backing array rather than writing into this one — but do not rely on
that; copy the slice if you intend to extend it:
DefaultMaxDepth — how far the walk climbs by default¶
The number of parent levels the walk will ascend when no WithMaxDepth option is
given. The start directory is always inspected on top of that, so a default walk
inspects up to 101 directories and issues up to 101 × len(markers) Stat calls
before giving up.
The bound is a guard, not a tuning knob: 100 parent levels is far deeper than any real project tree, so under normal use the walk ends at the filesystem root or at a marker, never at the bound.
WithMaxDepth — tighten or loosen the climb¶
depth counts parent levels only. The start directory is always checked.
| Value | Directories inspected |
|---|---|
WithMaxDepth(0) |
the start directory alone |
WithMaxDepth(1) |
the start directory and its parent |
WithMaxDepth(n) |
the start directory and n parents |
| omitted | the start directory and 100 parents |
| negative | none — see what happens when an argument is wrong |
Passing the option more than once keeps the last value; options are applied in the order given.
Running out of depth is reported as ErrNotFound, indistinguishable from reaching the
filesystem root with no match.
ErrNotFound — the one sentinel¶
Returned unwrapped, so both errors.Is(err, workspace.ErrNotFound) and err ==
workspace.ErrNotFound work; prefer errors.Is. The message text is
workspace not found: no marker file detected.
It is the only sentinel the package defines. It carries no detail about which directories were searched, how far the walk got, or which of the two stopping conditions ended it.
Option — the functional-option type¶
detectConfig is unexported, so WithMaxDepth is the only option that can exist
outside this package. Option appears in the signature of Detect and
DetectFromCWD and nowhere else; you cannot write your own.