outdir

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2026 License: BSD-3-Clause Imports: 3 Imported by: 0

README

outdir

Choose where a file that must never be committed may be written.

dir, err := outdir.Ensure(outdir.Spec{
    App: "xrdesk",
    Env: "XRDESK_CAPTURE_DIR",
    Sub: "captures",
})

Pure Go, CGO_ENABLED=0, no dependencies.

go get github.com/go-appdirs/outdir

⚠️ The import path changed in v0.2.0: this package used to live at github.com/go-fsctl/outdir, in an organisation whose every other module drives a kernel storage interface — while this one opens nothing. That repository was deleted rather than left redirecting, because a transfer redirect answers the old path with the new repository's identity, which a tool cannot see through. The old path now fails, which is the honest answer.

Why

⛔ The mistake it prevents has happened. A live test wrote a capture of a whole desktop into a public repository's testdata/, untracked — one git add -A from publication. A capture of a real display is a picture of a person at work; a camera frame is a picture of a person; a log of a signed-in account's traffic is data about a real person's account.

⛔ A .gitignore entry is the wrong fix. Ignoring is a safety net, not a barrier: git add -f, a fresh clone, or any tool that does not consult it publishes the file anyway. The barrier is to write somewhere that is not in a work tree at all — and to refuse when the chosen place is.

The directory is also durable, which a t.TempDir() is not: that is removed when the test ends, so the artefact is gone before anybody can open it. What a person needs after a failure is the picture, and a path in the log that still exists.

What it checks

  • The work-tree walk goes up to the filesystem root. testdata/ is three levels below the .git that would publish it.
  • A .git file counts as well as a directory — that is what a linked worktree and a submodule leave behind.
  • Symbolic links are resolved, from the nearest ancestor that exists: the path usually does not exist yet, and on macOS every temporary directory is reached through /var, a link to /private/var.
  • An override is checked too. The mistake this prevents is exactly the one a person makes, so taking their word for it would be the one case where the barrier is not there.
  • Choose creates nothing; Ensure creates it. A caller that decides not to write must not leave an empty directory behind.

Documentation

Overview

Package outdir chooses where a file that must never be committed may be written.

⛔ THE MISTAKE IT PREVENTS HAS HAPPENED. A live test wrote a capture of a whole desktop into a PUBLIC repository's testdata/, untracked -- one `git add -A` from publication. A capture of a real display is a picture of a person at work, and a camera frame is a picture of a person; a log of a signed-in account's traffic is data about a real person's account.

⛔ A .gitignore ENTRY IS THE WRONG FIX. Ignoring is a safety net, not a barrier: `git add -f`, a fresh clone, or any tool that does not consult it publishes the file anyway. The barrier is to write somewhere that is not in a work tree at all, and to REFUSE when the chosen place is.

The directory is also DURABLE, which the temporary one a test framework hands out is not: a t.TempDir() is removed when the test ends, so the artefact is gone before anybody can open it. What a person needs after a failure is the picture, and a path in the log that still exists.

Using it

dir, err := outdir.Choose(outdir.Spec{
    App: "xrdesk",
    Env: "XRDESK_CAPTURE_DIR",
    Sub: "captures",
})

A caller who lets a person override the place through an environment variable gets the same check applied to their choice, because the mistake this prevents is exactly the one a person makes.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Choose

func Choose(s Spec) (string, error)

Choose reports the absolute directory to write in, or refuses.

It does NOT create the directory: a caller that decides not to write should not leave an empty one behind. Call Ensure when the file is about to be written.

func Ensure

func Ensure(s Spec) (string, error)

Ensure is Choose with the directory made.

func RepoRootOf

func RepoRootOf(path string) string

RepoRootOf is the git work tree this path is inside, or "".

⛔ IT WALKS UP TO THE FILESYSTEM ROOT, and that is the whole of the check: a directory is inside a work tree when ANY ancestor holds a .git, not only when its immediate parent does. testdata/ is three levels down from the .git that would publish it.

A .git FILE counts as well as a directory. That is what a git worktree and a submodule leave behind -- a file holding "gitdir: ..." -- and a capture written into one is as committable as any other.

⛔ IT WALKS FROM THE NEAREST ANCESTOR THAT EXISTS, resolved through symbolic links. Both halves matter, and they interact:

  • A path reaching a work tree through a link is IN it, and walking the unresolved path would answer no. On macOS every temporary directory is reached through /var, which is a link to /private/var.
  • The path usually does NOT exist yet -- a program asks where it may write before making anything -- so EvalSymlinks on it fails outright. Resolving only when the whole path exists would make the ANSWER's form depend on whether the directory had been made, which is how this was found: the same tree came back as /var/... before it existed and /private/var/... after.

A component that does not exist cannot hold a .git, so nothing is lost by starting at the deepest one that does.

Types

type Spec

type Spec struct {
	// App is the program's name, used to make the default directory. It is
	// required: a shared parent with everybody's captures in it is a directory
	// nobody can clean up.
	App string

	// Env is an environment variable that overrides the default. Empty means
	// there is no override.
	//
	// The value is CHECKED like any other, and refused the same way. A person
	// who points it at their work tree has made the exact mistake this exists
	// to prevent, and taking their word for it would be the one case where the
	// barrier is not there.
	Env string

	// Sub is a subdirectory under the default, so a program that writes two
	// kinds of thing can keep them apart. Empty puts them directly under the
	// program's own directory.
	Sub string

	// Want is an explicit directory, which takes precedence over Env and over
	// the default. It is checked the same way.
	Want string

	// Base overrides where the default directory is rooted. Empty asks
	// os.UserConfigDir, which is the durable per-user place on every platform
	// -- ~/Library/Application Support on macOS, $XDG_CONFIG_HOME on Linux,
	// %AppData% on Windows.
	//
	// It exists so a test can put a whole run somewhere of its own without
	// setting an environment variable for the process.
	Base string
}

Spec says what is being written and where it may go.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL