appdir

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 13, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

README

appdir

Where a Go tool keeps its things: the global home (~/.<app>), the project home (.<app>/, found by walking up), the files and folders inside them, the prod/dev profile split, pointer keys, and moving an old home. Every path comes with the rule that picked it, so paths --json and doctor can say why.

Standard library only; macOS, Linux, Windows. A *Dirs is immutable and safe for concurrent use: resolve once, share it everywhere.

Status: v0.1.x, before 1.0: the API may still change between minor versions.

appdir architecture

Install

go get github.com/ubgo/appdir

Quick start

Resolve once with New; it reads and creates nothing. This function is from examples/quickstart, which passes seams so it runs in a temp folder; a real tool passes its own flags instead (appdir.WithHome(flagHome), appdir.WithProfile(flagProfile)) and leaves the user's home, working folder and environment to appdir.

// open resolves every home for the app. The three seams keep the example
// away from the real machine: the user's home, the working folder and the
// environment all come from the caller. WithRelease(true) keeps the prod
// profile, so folders have their plain names (.lore, not .lore-dev).
func open(home, workDir string) (*appdir.Dirs, error) {
	d, err := appdir.New(appName,
		appdir.WithHomeDir(func() (string, error) { return home, nil }),
		appdir.WithWorkDir(workDir),
		appdir.WithEnv(func(string) (string, bool) { return "", false }),
		appdir.WithRelease(true),
	)
	if err != nil {
		return nil, fmt.Errorf("appdir: %w", err)
	}
	return d, nil
}

Then create folders only when you write to them:

// ensure creates folders on first use. For a folder kind it creates the
// folder; for a file kind (the db) it creates the parent folder only and
// returns the file path for the caller to open.
func ensure(w io.Writer, root string, d *appdir.Dirs) error {
	for _, k := range []appdir.Kind{appdir.KindCache, appdir.KindLogs, appdir.KindDB} {
		p, err := d.Ensure(k)
		if err != nil {
			return fmt.Errorf("ensure %s: %w", k, err)
		}
		fmt.Fprintf(w, "Ensure(%s): %s\n", k, show(root, p))
	}
	fmt.Fprintf(w, "global home exists: %v\n", exists(d.Global().Base))
	return nil
}

show and exists are two small helpers in the same file (a path relative to the temp root, and a stat). Run it with go run ./examples/quickstart. Before opening a database, call d.CheckDB(path): a dev build refuses the prod database (Prod-db guard).

What it decides

Question Default Change it with
Global home ~/.<app> (~/.<app>-dev for dev) --home, <APP>_HOME
Project home nearest .<app>/ walking up, stopping at a repo root, $HOME, a disk change or <APP>_CEILING_DIRS; a linked worktree uses the main checkout WithLookup(appdir.HereOnly), home: / db: pointer keys
Layout home.jsonc, config.jsonc, config.local.jsonc, shared/, <app>.db, backups/, cache/, run/, logs/ WithKind, WithoutKind, <APP>_<KIND>_DIR
Data scope project home (ModeProject) WithMode(appdir.ModeGlobal), Scoped per kind
What goes to git home.jsonc, config.jsonc, .gitignore, shared/** WithGitCommit, WithGitIgnore
Profile release build → prod, anything else → dev --profile, <APP>_PROFILE, WithRelease, WithProfiles
Socket run/<app>.sock, a per-user fallback folder when too long, a named pipe on Windows WithSocketMaxLen, WithRunFile

Every default is an option: see the options reference.

Documentation

  • Learning path: fourteen steps from the first call to a production setup, each with a guide and a runnable example.
  • Choosing: decision guides for the mode and scopes, finding and moving homes, profiles, and the options per app type.
  • Feature index: every feature with its guide and example.
  • Examples: sixteen runnable programs, each tested by go test ./examples/....
  • SPEC: the design contract.

License

Apache License 2.0. See LICENSE.

Documentation

Overview

Package appdir decides where a tool keeps its things: the global home (~/.<app>), the project home (.<app>/ found by walking up), the folders and files inside them, the prod/dev profile split, pointer keys, and moving an old home. It implements the folder parts of the app standard; the full contract is docs/SPEC.md.

New only reads: it stats folders and reads home.jsonc and config pointer keys, and never creates anything. Folders appear on the first Ensure. Every resolved path records the Rule that picked it, so a tool's `paths --json` and `doctor` can say why a path is what it is.

d, err := appdir.New("lore", appdir.WithHome(flagHome), appdir.WithProfile(flagProfile))
if err != nil { return err }
db, err := d.Ensure(appdir.KindDB) // creates .lore/ folders lazily; ErrNoProject outside a project

appdir keeps no package-level mutable state; every outside input (env, home dir, working dir, build info) has a seam option.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidOption    = errors.New("invalid option")
	ErrUnknownProfile   = errors.New("unknown profile")
	ErrUnknownKind      = errors.New("unknown kind")
	ErrNoProject        = errors.New("no project home")
	ErrNoHome           = errors.New("no home folder")
	ErrEnvVar           = errors.New("variable not set")
	ErrPointerAbsolute  = errors.New("absolute pointer in a committed file")
	ErrPointerMissing   = errors.New("pointer target does not exist")
	ErrPointerNotHome   = errors.New("pointer target is not a home of this app")
	ErrLayoutTooNew     = errors.New("home layout is newer than this binary")
	ErrRootOwned        = errors.New("running as root in another user's home")
	ErrSymlinkEscape    = errors.New("symlink leads outside the home")
	ErrUnsafeTemp       = errors.New("unsafe temp folder")
	ErrProdDB           = errors.New("dev build refuses the prod database")
	ErrBusy             = errors.New("busy")
	ErrGitignoreMarkers = errors.New("gitignore markers are broken")
	ErrNotHome          = errors.New("folder belongs to another app")
	// ErrBadHomeFile: init found a home.jsonc that exists but cannot be read
	// or parsed. It is the user's file, so init refuses rather than replace
	// it; the reader's own error is wrapped too (fs.ErrPermission, ...).
	ErrBadHomeFile = errors.New("home file cannot be read")
)

Sentinel errors. They are the only package-level variables in appdir and are never reassigned. Every returned error wraps one of them with the path, key or variable it is about, so callers use errors.Is and users read a full sentence. Mapping to exit codes is the tool's job (standard section 11). ds:def id=sentinel-errors-mwtzt4d9 owner=@khanakia stability=stable

Functions

func CleanEnv

func CleanEnv(environ []string, prefix string) []string

CleanEnv returns environ without any <prefix>_* variable: the Go twin of a dev-env.sh wrapper. Pure function. ds:def id=cleanenv-276nsr8w owner=@khanakia stability=stable

func ForTest

func ForTest(t testing.TB, app string) string

ForTest isolates a test from the developer's real homes (standard section 13): it points HOME, USERPROFILE, <APP>_HOME, XDG_*, CLAUDE_CONFIG_DIR, CODEX_HOME and TMPDIR into a temp root, unsets <APP>_PROFILE and the GIT_* variables that leak a parent repo, and sets APPDIR_TEST_ROOT so New panics on any path outside it. Returns the root. ds:def id=fortest-56v4yh8s owner=@khanakia stability=stable

Types

type ConfigFile

type ConfigFile struct {
	Path      string  `json:"path"`
	Home      Scope   `json:"home"`
	Local     bool    `json:"local"`
	Committed bool    `json:"committed"`
	Profile   Profile `json:"profile,omitempty"` // set on the profile's own config.local.jsonc
}

ConfigFile is one config file in read order (later wins per key).

type ConfigReader

type ConfigReader func(path string) (map[string]any, error)

ConfigReader reads one JSONC file into a map. A missing file must return an error wrapping fs.ErrNotExist.

type DBAccess

type DBAccess int

DBAccess is the result of the prod-db guard.

const (
	AccessReadWrite DBAccess = iota
	AccessReadOnly           // dev build on the prod db with <APP>_ALLOW_PROD=1: never migrate
)

DB access levels.

func (DBAccess) String

func (a DBAccess) String() string

String implements fmt.Stringer.

type Dirs

type Dirs struct {
	// contains filtered or unexported fields
}

Dirs is the resolved set of homes and paths for one app. It is immutable and safe for concurrent use.

func New

func New(app string, opts ...Option) (*Dirs, error)

New resolves every home and path for app. It never creates anything. It fails only for a bad option set or an unknown profile; a missing project home is reported by the methods that need one. ds:def id=new-mes9nm7s owner=@khanakia stability=stable

func (*Dirs) App

func (d *Dirs) App() string

App returns the app name.

func (*Dirs) Banner

func (d *Dirs) Banner() string

Banner returns the one stderr line for a non-release profile ("[lore] profile: dev → ~/.lore-dev"), or "" for the release profile. ds:def id=dirs-banner-cwbb5293 owner=@khanakia stability=stable

func (*Dirs) CheckDB

func (d *Dirs) CheckDB(path string) (DBAccess, error)

CheckDB is the prod-db guard (SPEC 8.4). Call it before opening a database and before any migration. ds:def id=dirs-checkdb-2r8b9xmq owner=@khanakia stability=stable

func (*Dirs) ChildEnv

func (d *Dirs) ChildEnv(environ []string) []string

ChildEnv returns environ with <APP>_PROFILE set to this profile, for every child process the tool starts (SPEC 8.5). ds:def id=dirs-childenv-myszxsrp owner=@khanakia stability=stable

func (*Dirs) ConfigFiles

func (d *Dirs) ConfigFiles() []ConfigFile

ConfigFiles returns the config files in read order (SPEC 6.1). Missing files are listed; the loader treats them as empty. ds:def id=dirs-configfiles-nzbecu5q owner=@khanakia stability=stable

func (*Dirs) DaemonDiscovery

func (d *Dirs) DaemonDiscovery() (string, Profile, error)

DaemonDiscovery returns the discovery file of the daemon profile. A client reads only this file and the socket it names, never the other profile's db. ds:def id=dirs-daemondiscovery-wb9urrua owner=@khanakia stability=stable

func (*Dirs) DaemonProfile

func (d *Dirs) DaemonProfile() Profile

DaemonProfile returns the profile whose daemon a client talks to: --daemon-profile, then <APP>_DAEMON_PROFILE, then its own profile.

func (*Dirs) Ensure

func (d *Dirs) Ensure(k Kind) (string, error)

Ensure creates a kind's folder (or a file kind's parent folder) on first use and returns the kind's path. It is the lazy creation point: reads never create anything (SPEC 4.8). ds:def id=dirs-ensure-mxbz3fmd owner=@khanakia stability=stable

func (*Dirs) EnsureSocketDir

func (d *Dirs) EnsureSocketDir() (Socket, error)

EnsureSocketDir creates what a daemon needs before it listens: run/ through Ensure in every case, plus the private fallback folder (0700, with the symlink and owner checks) when the socket is not under run/. run/ is created even for a fallback or a Windows pipe because the daemon writes run/socket (the pointer file), the pid and discovery.json there; one code path for every OS keeps the Windows pipe case covered by the Unix tests. ds:def id=dirs-ensuresocketdir-r9rr28ym owner=@khanakia stability=stable

func (*Dirs) EnvName

func (d *Dirs) EnvName(suffix string) string

EnvName returns "<PREFIX>_" + suffix.

func (*Dirs) EnvPrefix

func (d *Dirs) EnvPrefix() string

EnvPrefix returns the env prefix ("LORE").

func (*Dirs) ForProfile

func (d *Dirs) ForProfile(p Profile) (*Dirs, error)

ForProfile resolves the same app with the same options as if --profile p were given. It reads only; it never opens the other profile's files. ds:def id=dirs-forprofile-g2gegg5p owner=@khanakia stability=stable

func (*Dirs) GitignoreBlock

func (d *Dirs) GitignoreBlock() string

GitignoreBlock returns the marker block for .<app>/.gitignore (SPEC 5.7): ignore everything, then re-include the committed files, folders first because git cannot re-include a file under an excluded folder. ds:def id=dirs-gitignoreblock-8bujrmug owner=@khanakia stability=stable

func (*Dirs) Global

func (d *Dirs) Global() Home

Global returns the global home. With no $HOME and no override, its Rule is RuleDefaults and Base/Data are empty.

func (*Dirs) HasProject

func (d *Dirs) HasProject() bool

HasProject reports whether a usable project home was found.

func (*Dirs) InitHome

func (d *Dirs) InitHome(path string, s Scope) error

InitHome creates a home at path with home.jsonc for scope: the `<app> init --home X` command, also used to create a pointer target. ds:def id=dirs-inithome-934qhf2j owner=@khanakia stability=stable

func (*Dirs) InitProject

func (d *Dirs) InitProject(dir string) (string, error)

InitProject creates <dir>/.<app>/ with home.jsonc and, inside a git work tree, the .gitignore block: the `<app> init` command. A project home is never created any other way. Returns the new marker folder. ds:def id=dirs-initproject-wpmefq6e owner=@khanakia stability=stable

func (*Dirs) IsRelease

func (d *Dirs) IsRelease() bool

IsRelease reports whether this binary is a release build.

func (*Dirs) KindEnv

func (d *Dirs) KindEnv(k Kind) string

KindEnv returns the <APP>_<KIND>_DIR name of a kind, or "" for anchored kinds.

func (*Dirs) Kinds

func (d *Dirs) Kinds() []KindSpec

Kinds returns the layout in effect, in stable order.

func (*Dirs) Mode

func (d *Dirs) Mode() Mode

Mode returns the tool's mode.

func (*Dirs) MoveLegacy

func (d *Dirs) MoveLegacy(ctx context.Context) (MoveResult, error)

MoveLegacy moves an old global home to the new one on first run (SPEC 9, standard section 8). It never deletes user data and never moves committed project folders. With several WithLegacyHome entries, the first that exists is used. ds:def id=dirs-movelegacy-aw98ygmb owner=@khanakia stability=stable

func (*Dirs) Name

func (d *Dirs) Name(base string) string

Name returns base for the release profile and base-<profile> otherwise. Every label, unit, marker, pipe and item name goes through it.

func (*Dirs) NameWith

func (d *Dirs) NameWith(base, sep string) string

NameWith is Name with a custom separator: NameWith("com.org.app", ".") gives "com.org.app.dev". ds:def id=dirs-namewith-uyg39uh6 owner=@khanakia stability=stable

func (*Dirs) Path

func (d *Dirs) Path(k Kind) (string, error)

Path returns a kind's path in its effective home (reads only). For ScopeBoth kinds that is the project home when there is one, else the global home. ds:def id=dirs-path-asygw3vm owner=@khanakia stability=stable

func (*Dirs) PathIn

func (d *Dirs) PathIn(k Kind, s Scope) (string, error)

PathIn returns a kind's path in one home: ScopeGlobal or ScopeProject. ds:def id=dirs-pathin-2j8zfrym owner=@khanakia stability=stable

func (*Dirs) Paths

func (d *Dirs) Paths() []Entry

Paths returns every kind's path, for paths --json and doctor. ScopeBoth kinds appear once per home. ds:def id=dirs-paths-6kdzcjzg owner=@khanakia stability=stable

func (*Dirs) Profile

func (d *Dirs) Profile() Profile

Profile returns the profile in effect.

func (*Dirs) Project

func (d *Dirs) Project() (Home, error)

Project returns the project home, or an error wrapping ErrNoProject (or the pointer error that made it unusable).

func (*Dirs) ReleaseReason

func (d *Dirs) ReleaseReason() string

ReleaseReason says which rule decided release or dev.

func (*Dirs) RunPath

func (d *Dirs) RunPath(f RunFile) (string, error)

RunPath returns a file in run/ (reads only).

func (*Dirs) Socket

func (d *Dirs) Socket() (Socket, error)

Socket returns the socket path: run/<app>.sock when short enough, else a per-user fallback (platform rules in socket_<os>.go). ds:def id=dirs-socket-brsjjca9 owner=@khanakia stability=stable

func (*Dirs) VerifyFile

func (d *Dirs) VerifyFile(path string) error

VerifyFile applies the symlink rule (SPEC 5.6): a file may be a symlink only when its real path is inside the real path of the home it belongs to. ds:def id=dirs-verifyfile-2nyrnmv3 owner=@khanakia stability=stable

func (*Dirs) Warnings

func (d *Dirs) Warnings() []Warning

Warnings returns what the tool should print once.

func (*Dirs) WriteGitignore

func (d *Dirs) WriteGitignore() (bool, error)

WriteGitignore writes the block into the project home's .gitignore. It returns false (and writes nothing) when the home is not in a git work tree.

type Entry

type Entry struct {
	Kind     Kind      `json:"kind"`
	Home     Scope     `json:"home"`
	Path     string    `json:"path"`
	Type     EntryType `json:"type"`
	Git      GitRule   `json:"git"`
	Rule     Rule      `json:"rule"`
	Fallback bool      `json:"fallback"`
	Note     string    `json:"note,omitempty"`
	Error    string    `json:"error,omitempty"`
	// contains filtered or unexported fields
}

Entry is one kind's resolved path, for paths --json and doctor.

type EntryType

type EntryType string

EntryType says whether a kind is a file or a folder.

const (
	TypeFile   EntryType = "file"
	TypeFolder EntryType = "folder"
)

Entry types.

type GitRule

type GitRule string

GitRule says whether a kind is committed.

const (
	GitCommit GitRule = "commit"
	GitIgnore GitRule = "ignore"
)

Git rules.

type Home

type Home struct {
	Scope    Scope     `json:"scope"`            // global or project
	Marker   string    `json:"marker,omitempty"` // the .<app>/ that was found (project only)
	Base     string    `json:"base"`             // holds home.jsonc and the config files
	Data     string    `json:"data"`             // holds every other kind
	Rule     Rule      `json:"rule"`
	Fallback bool      `json:"fallback"`
	Note     string    `json:"note,omitempty"`
	Info     *HomeInfo `json:"info,omitempty"` // parsed home.jsonc, nil when absent
}

Home is one resolved home folder.

type HomeInfo

type HomeInfo struct {
	App    string `json:"app"`
	Layout int    `json:"layout"`
	Scope  Scope  `json:"scope"`
}

HomeInfo is the content of home.jsonc.

type Kind

type Kind string

Kind names one entry in a home's layout: a file or a folder.

const (
	KindHome        Kind = "home"         // home.jsonc: app, layout, scope
	KindConfig      Kind = "config"       // config.jsonc: settings, committed
	KindConfigLocal Kind = "config-local" // config.local.jsonc: this machine only
	KindGitignore   Kind = "gitignore"    // .gitignore: written between markers
	KindShared      Kind = "shared"       // shared/: team-shared files, committed
	KindDB          Kind = "db"           // <app>.db
	KindBackups     Kind = "backups"      // backups/
	KindCache       Kind = "cache"        // cache/: safe to delete
	KindRun         Kind = "run"          // run/: lock, pid, socket, discovery
	KindLogs        Kind = "logs"         // logs/
)

Standard kinds, used when the tool's author changes nothing (SPEC 5.1).

type KindOption

type KindOption func(*KindSpec)

KindOption changes one KindSpec inside WithKind.

func Anchored

func Anchored() KindOption

Anchored keeps the kind in the home root of both profiles and gives it no <APP>_<KIND>_DIR: it is part of what makes a folder a home.

func Commit

func Commit() KindOption

Commit puts the kind into git.

func Disposable

func Disposable() KindOption

Disposable marks the kind safe to delete (cache-like): it gets CACHEDIR.TAG and may fall back to the temp folder.

func File

func File(p string) KindOption

File makes the kind a file at p inside the home.

func Folder

func Folder(p string) KindOption

Folder makes the kind a folder at p inside the home.

func Ignore

func Ignore() KindOption

Ignore keeps the kind out of git.

func Scoped

func Scoped(s Scope) KindOption

Scoped sets which home the kind lives in.

type KindSpec

type KindSpec struct {
	Name       Kind      `json:"name"`
	Path       string    `json:"path"` // slash-separated, relative to the home
	Type       EntryType `json:"type"`
	Git        GitRule   `json:"git"`
	Scope      Scope     `json:"scope"` // "" until New fills in the mode's data scope
	Disposable bool      `json:"disposable"`
	Anchored   bool      `json:"anchored"` // lives in the home root, no env override
}

KindSpec describes one entry of the layout. Kinds() returns the specs in effect, so paths --json and doctor can show the names the author chose.

type LegacyHome

type LegacyHome struct {
	Dir         string   // folder name under the user's home, e.g. ".pm"
	DaemonLocks []string // lock files inside it to probe; default ["run/daemon.lock"]
}

LegacyHome names an old global home folder to move from (standard section 8).

type LegacyVerify

type LegacyVerify func(src, dst string) error

LegacyVerify runs after a cross-disk copy in MoveLegacy, before the old folder is renamed away; a tool passes its database integrity check here.

type Lookup

type Lookup string

Lookup is the project lookup style.

const (
	WalkUp   Lookup = "walk-up"
	HereOnly Lookup = "here-only"
)

Lookup styles.

type Mode

type Mode string

Mode is the kind of tool: it sets the default scope of data kinds.

const (
	ModeProject Mode = "project"
	ModeGlobal  Mode = "global"
)

Modes. ModeProject is the default because it fails safe (SPEC 5.2).

type MoveResult

type MoveResult struct {
	Status MoveStatus `json:"status"`
	From   string     `json:"from,omitempty"`
	To     string     `json:"to,omitempty"`
	Note   string     `json:"note,omitempty"`
}

MoveResult is the outcome of MoveLegacy.

type MoveStatus

type MoveStatus string

MoveStatus is the outcome of MoveLegacy.

const (
	MoveNone      MoveStatus = "none"
	MoveDone      MoveStatus = "moved"
	MoveBothExist MoveStatus = "both-exist"
	MoveSkipped   MoveStatus = "skipped"
	MoveAlready   MoveStatus = "already"
)

Move outcomes.

type Option

type Option func(*config)

Option changes one setting of New. Every choice in the standard is an option with the standard's default (rule zero, D17).

func WithAllowRoot

func WithAllowRoot(allow bool) Option

WithAllowRoot passes the tool's --allow-root flag.

func WithBuildInfo

func WithBuildInfo(fn func() (*debug.BuildInfo, bool)) Option

WithBuildInfo replaces debug.ReadBuildInfo for release detection.

func WithCacheTag

func WithCacheTag(on bool) Option

WithCacheTag turns CACHEDIR.TAG and OS backup/index exclusions on or off. Default on.

func WithConfigReader

func WithConfigReader(fn ConfigReader) Option

WithConfigReader replaces the JSONC reader for home.jsonc and pointer keys.

func WithDaemonProfile

func WithDaemonProfile(name string) Option

WithDaemonProfile passes the tool's --daemon-profile flag.

func WithDevProfile

func WithDevProfile(p Profile) Option

WithDevProfile sets the profile of a non-release build. Default dev.

func WithDirName

func WithDirName(name string) Option

WithDirName sets the home folder name. Default "." + app.

func WithEnv

func WithEnv(fn func(string) (string, bool)) Option

WithEnv replaces the environment lookup (seam for tests and ForProfile).

func WithEnvPrefix

func WithEnvPrefix(prefix string) Option

WithEnvPrefix sets the env prefix. Default: the app name upper-cased, with "-" and "." turned into "_".

func WithGitCommit

func WithGitCommit(patterns ...string) Option

WithGitCommit adds committed gitignore patterns (relative to the home).

func WithGitIgnore

func WithGitIgnore(patterns ...string) Option

WithGitIgnore forces patterns out of git; it wins over every commit rule.

func WithHome

func WithHome(path string) Option

WithHome passes the tool's --home flag. Empty means not given.

func WithHomeDir

func WithHomeDir(fn func() (string, error)) Option

WithHomeDir replaces the user home lookup.

func WithKind

func WithKind(name Kind, opts ...KindOption) Option

WithKind adds a kind, or changes an existing one in place (rename = change its path). A new kind starts as Folder(name), Ignore(), data scope.

func WithLayoutVersion

func WithLayoutVersion(n int) Option

WithLayoutVersion sets the layout version this binary knows. Default 1.

func WithLegacyHome

func WithLegacyHome(l LegacyHome) Option

WithLegacyHome names an old global home that MoveLegacy moves from.

func WithLegacyVerify

func WithLegacyVerify(fn LegacyVerify) Option

WithLegacyVerify adds a check after a cross-disk legacy copy.

func WithLookup

func WithLookup(l Lookup) Option

WithLookup sets the project lookup style. Default WalkUp.

func WithMode

func WithMode(m Mode) Option

WithMode sets the default scope of data kinds. Default ModeProject.

func WithPerm

func WithPerm(dir, file fs.FileMode) Option

WithPerm sets the modes of folders and files appdir creates. Default 0700 / 0600.

func WithProfile

func WithProfile(name string) Option

WithProfile passes the tool's --profile flag. Empty means not given.

func WithProfiles

func WithProfiles(names ...Profile) Option

WithProfiles sets the allowed profiles. Default prod, dev.

func WithRelease

func WithRelease(release bool) Option

WithRelease states whether this binary is a release build, from the tool's own -ldflags -X variable. Without it, appdir reads Go's build info.

func WithReleaseProfile

func WithReleaseProfile(p Profile) Option

WithReleaseProfile sets the profile of a release build, the one that keeps base names. Default prod.

func WithRunFile

func WithRunFile(f RunFile, name string) Option

WithRunFile renames one file in run/.

func WithSocketMaxLen

func WithSocketMaxLen(n int) Option

WithSocketMaxLen sets the longest socket path before the /tmp fallback. Default 100 bytes (macOS allows about 104).

func WithTempFallback

func WithTempFallback(on bool) Option

WithTempFallback turns the temp fallback of disposable kinds on or off. Default on.

func WithTestGuard

func WithTestGuard(on bool) Option

WithTestGuard turns the test-binary path guard on or off. Default on.

func WithWorkDir

func WithWorkDir(dir string) Option

WithWorkDir fixes the folder the project lookup starts from.

func WithoutKind

func WithoutKind(name Kind) Option

WithoutKind drops a kind.

type Profile

type Profile string

Profile names a set of homes (prod, dev, or an author-added name).

const (
	ProfileProd Profile = "prod"
	ProfileDev  Profile = "dev"
)

Default profiles.

type Rule

type Rule string

Rule names the resolution rule that picked a path, for paths --json and doctor.

const (
	RuleFlag     Rule = "flag"     // --home (WithHome)
	RuleEnvHome  Rule = "env-home" // <APP>_HOME
	RuleEnvKind  Rule = "env-kind" // <APP>_<KIND>_DIR
	RuleProject  Rule = "project"  // found by lookup
	RuleWorktree Rule = "worktree" // main checkout of a linked worktree
	RulePointer  Rule = "pointer"  // home: or db: key
	RuleGlobal   Rule = "global"   // ~/.<app>
	RuleTemp     Rule = "temp"     // <tmp>/<app>-<uid>/
	RuleDefaults Rule = "defaults" // no $HOME: global config is code defaults
)

Rules.

type RunFile

type RunFile string

RunFile names a file in run/.

const (
	RunLock       RunFile = "lock"        // run/lock: migrations, layout changes
	RunDaemonLock RunFile = "daemon-lock" // run/daemon.lock: held for a daemon's life
	RunPID        RunFile = "pid"         // run/<app>.pid: display only
	RunDiscovery  RunFile = "discovery"   // run/discovery.json
	RunSocket     RunFile = "socket"      // run/<app>.sock
	RunSocketFile RunFile = "socket-file" // run/socket: path of a fallback socket
)

Run files (SPEC 7).

type Scope

type Scope string

Scope says which home a kind lives in.

const (
	ScopeGlobal            Scope = "global"
	ScopeProject           Scope = "project"
	ScopeProjectElseGlobal Scope = "project-else-global"
	ScopeBoth              Scope = "both" // one copy per home (config files, home.jsonc)
)

Scopes. ScopeGlobal and ScopeProject also name the two homes in results.

type Socket

type Socket struct {
	Path        string `json:"path"`
	Fallback    bool   `json:"fallback"`     // not under run/; the daemon writes Path to PointerFile
	PointerFile string `json:"pointer_file"` // run/socket
	Dir         string `json:"dir"`          // folder to create; "" for a Windows pipe
}

Socket is where a daemon listens (SPEC 7).

type Warning

type Warning struct {
	Code    WarningCode `json:"code"`
	Message string      `json:"message"`
}

Warning is something the tool should print once (to stderr).

type WarningCode

type WarningCode string

WarningCode identifies a warning.

const (
	WarnProfileEnvIgnored WarningCode = "profile-env-ignored"
	WarnEnvPointsToProd   WarningCode = "env-points-to-prod"
	WarnPointerChain      WarningCode = "pointer-chain-ignored"
	WarnPointerProfile    WarningCode = "pointer-ignored-for-profile"
	WarnCeilingRelative   WarningCode = "ceiling-relative"
	// WarnCeilingDropped: an <APP>_CEILING_DIRS entry that is not relative
	// but does not expand to an absolute path ($VAR without braces, an unset
	// ${VAR}, ~user); it is ignored, and without this warning a ceiling would
	// silently have no effect (finding D5).
	WarnCeilingDropped      WarningCode = "ceiling-dropped"
	WarnWorktreeMainMissing WarningCode = "worktree-main-missing"
)

Warning codes.

Directories

Path Synopsis
examples
config-files command
Command config-files lists the config files appdir hands to a config loader, in read order, for the prod and dev profiles, with a home: pointer, with --home, with no $HOME and with config.local.jsonc dropped.
Command config-files lists the config files appdir hands to a config loader, in read order, for the prod and dev profiles, with a home: pointer, with --home, with no $HOME and with config.local.jsonc dropped.
custom-layout command
Command custom-layout changes the standard layout of a home and shows what follows from it: the kinds in effect and their env names, the gitignore block, writing it into a .gitignore that also has hand-written lines, Ignore() and unknown kinds, init without a home file, Ensure with owner-only permissions and CACHEDIR.TAG, WithPerm and WithCacheTag(false), the symlink rule, VerifyFile and the layout-version guard.
Command custom-layout changes the standard layout of a home and shows what follows from it: the kinds in effect and their env names, the gitignore block, writing it into a .gitignore that also has hand-written lines, Ignore() and unknown kinds, init without a home file, Ensure with owner-only permissions and CACHEDIR.TAG, WithPerm and WithCacheTag(false), the symlink rule, VerifyFile and the layout-version guard.
daemon-paths command
Command daemon-paths shows the paths a tool with a background daemon needs: the files in run/, the socket (with its fallback when the path is too long for a Unix socket), and how a client finds the daemon of another profile.
Command daemon-paths shows the paths a tool with a background daemon needs: the files in run/, the socket (with its fallback when the path is too long for a Unix socket), and how a client finds the daemon of another profile.
demo command
Command demo prints what appdir resolves for an app named "demo", as text or as JSON.
Command demo prints what appdir resolves for an app named "demo", as text or as JSON.
doctor command
Command doctor builds the two reports every tool on the standard ships: `paths --json` (every kind, its path and the rule that picked it) and `doctor` (config files, warnings, layout, gitignore block).
Command doctor builds the two reports every tool on the standard ships: `paths --json` (every kind, its path and the rule that picked it) and `doctor` (config files, warnings, layout, gitignore block).
fallbacks command
Command fallbacks shows what appdir does when the user's home is missing or cannot be written: config uses code defaults, data kinds fail with a clear error, and disposable kinds (the cache) use a private temp folder.
Command fallbacks shows what appdir does when the user's home is missing or cannot be written: config uses code defaults, data kinds fail with a clear error, and disposable kinds (the cache) use a private temp folder.
internal/doccheck/mdlinks command
Command mdlinks fails when a relative link or #anchor in the repository's Markdown does not resolve.
Command mdlinks fails when a relative link or #anchor in the repository's Markdown does not resolve.
internal/doccheck/mdwrap command
Command mdwrap reports Markdown files with hard-wrapped prose: a paragraph line that continues on the next physical line.
Command mdwrap reports Markdown files with hard-wrapped prose: a paragraph line that continues on the next physical line.
internal/doccheck/snippets command
Command snippets keeps code blocks in the guides identical to the example programs they are copied from.
Command snippets keeps code blocks in the guides identical to the example programs they are copied from.
internal/doccheck/words command
Command words reports files that contain a forbidden word, matched without regard to case.
Command words reports files that contain a forbidden word, matched without regard to case.
legacy-move command
Command legacy-move shows MoveLegacy: a tool that used to keep its global home in ~/.demo-old moves it to ~/.demo on the first run of the new version, leaves MOVED.txt behind, and reports every other outcome (already moved, both exist, skipped, busy) without ever deleting user data.
Command legacy-move shows MoveLegacy: a tool that used to keep its global home in ~/.demo-old moves it to ~/.demo on the first run of the new version, leaves MOVED.txt behind, and reports every other outcome (already moved, both exist, skipped, busy) without ever deleting user data.
lookup command
Command lookup shows how appdir finds the project home: the walk-up from the working folder, the walls that stop it (a repo root, the user's home, a ceiling folder), the real-path rule, nearest-wins in a monorepo, the here-only style, and the ErrNoProject message when nothing is found.
Command lookup shows how appdir finds the project home: the walk-up from the working folder, the walls that stop it (a repo root, the user's home, a ceiling folder), the real-path rule, nearest-wins in a monorepo, the here-only style, and the ErrNoProject message when nothing is found.
overrides command
Command overrides shows every way a user moves folders: --home (WithHome) and <APP>_HOME make one home for everything, <APP>_<KIND>_DIR moves one kind, and user paths expand ~ and ${VAR}.
Command overrides shows every way a user moves folders: --home (WithHome) and <APP>_HOME make one home for everything, <APP>_<KIND>_DIR moves one kind, and user paths expand ~ and ${VAR}.
pointers command
Command pointers shows the home: and db: pointer keys of a project home: a committed config.jsonc (with comments and trailing commas) that moves the project home into a private folder next to the repo, the errors a wrong pointer gives, the one-hop rule, what the dev profile does with both keys, the config files listed under a broken pointer, and an empty local home: that does not clear the committed one.
Command pointers shows the home: and db: pointer keys of a project home: a committed config.jsonc (with comments and trailing commas) that moves the project home into a private folder next to the repo, the errors a wrong pointer gives, the one-hop rule, what the dev profile does with both keys, the config files listed under a broken pointer, and an empty local home: that does not clear the committed one.
prod-db-guard command
Command prod-db-guard shows CheckDB, the guard a tool calls before it opens a database or runs a migration: a dev build refuses the prod databases, opens them read-only with DEMO_ALLOW_PROD=1, and opens its own read-write.
Command prod-db-guard shows CheckDB, the guard a tool calls before it opens a database or runs a migration: a dev build refuses the prod databases, opens them read-only with DEMO_ALLOW_PROD=1, and opens its own read-write.
profiles command
Command profiles shows how appdir picks a profile (flag, env, build), how a build is recognised as a release, how profiles get their own folders and names, and how a tool passes its profile to child processes.
Command profiles shows how appdir picks a profile (flag, env, build), how a build is recognised as a release, how profiles get their own folders and names, and how a tool passes its profile to child processes.
project-init command
Command project-init shows the two write calls that make a home: InitProject (the `lore init` command) and InitHome (`lore init --home X`), what they write into home.jsonc, and what they refuse.
Command project-init shows the two write calls that make a home: InitProject (the `lore init` command) and InitHome (`lore init --home X`), what they write into home.jsonc, and what they refuse.
quickstart command
Command quickstart is the first program a tool writes with appdir: resolve every home with New, read paths with Path, PathIn and Paths, and create a folder only when it is needed with Ensure.
Command quickstart is the first program a tool writes with appdir: resolve every home with New, read paths with Path, PathIn and Paths, and create a folder only when it is needed with Ensure.
testing-setup command
Command testing-setup is a tiny tool written so it can be tested without touching the developer's real homes.
Command testing-setup is a tiny tool written so it can be tested without touching the developer's real homes.
worktree command
Command worktree shows how appdir treats git worktrees and submodules without running git: a linked worktree uses the main checkout's .lore/, a main checkout without one makes the worktree look for its own (with a warning when the worktree has one; silent when neither has), a submodule is its own project, a worktree of a bare repo uses itself, and here-only lookup still uses the main checkout's home.
Command worktree shows how appdir treats git worktrees and submodules without running git: a linked worktree uses the main checkout's .lore/, a main checkout without one makes the worktree look for its own (with a warning when the worktree has one; silent when neither has), a submodule is its own project, a worktree of a bare repo uses itself, and here-only lookup still uses the main checkout's home.
internal
flock
Package flock is the minimal OS file lock appdir needs: flock(LOCK_EX|LOCK_NB) on Unix, LockFileEx with LOCKFILE_FAIL_IMMEDIATELY on Windows.
Package flock is the minimal OS file lock appdir needs: flock(LOCK_EX|LOCK_NB) on Unix, LockFileEx with LOCKFILE_FAIL_IMMEDIATELY on Windows.
jsonclite
Package jsonclite turns JSONC (JSON with // and /* */ comments and trailing commas) into plain JSON.
Package jsonclite turns JSONC (JSON with // and /* */ comments and trailing commas) into plain JSON.

Jump to

Keyboard shortcuts

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