diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-03-17 15:35:58 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-03-17 15:39:03 +0900 |
| commit | 6d015a949e27f20c86409c7d78053f0b0493f165 (patch) | |
| tree | 4d2bc1e9596fa30ea03e4c0cc575e6a7a969249d /check/absolute.go | |
| parent | e9a72490db44426605debd3cc756a7924931d706 (diff) | |
check: move from container
This package is not container specific, and widely used across the project.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'check/absolute.go')
| -rw-r--r-- | check/absolute.go | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/check/absolute.go b/check/absolute.go new file mode 100644 index 00000000..d17d5e1a --- /dev/null +++ b/check/absolute.go @@ -0,0 +1,132 @@ +// Package check provides types yielding values checked to meet a condition. +package check + +import ( + "encoding/json" + "errors" + "fmt" + "path" + "slices" + "strings" + "syscall" + "unique" +) + +// AbsoluteError is returned by [NewAbs] and holds the invalid pathname. +type AbsoluteError string + +func (e AbsoluteError) Error() string { + return fmt.Sprintf("path %q is not absolute", string(e)) +} + +func (e AbsoluteError) Is(target error) bool { + var ce AbsoluteError + if !errors.As(target, &ce) { + return errors.Is(target, syscall.EINVAL) + } + return e == ce +} + +// Absolute holds a pathname checked to be absolute. +type Absolute struct{ pathname unique.Handle[string] } + +// ok returns whether [Absolute] is not the zero value. +func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) } + +// unsafeAbs returns [check.Absolute] on any string value. +func unsafeAbs(pathname string) *Absolute { + return &Absolute{unique.Make(pathname)} +} + +// String returns the checked pathname. +func (a *Absolute) String() string { + if !a.ok() { + panic("attempted use of zero Absolute") + } + return a.pathname.Value() +} + +// Handle returns the underlying [unique.Handle]. +func (a *Absolute) Handle() unique.Handle[string] { + return a.pathname +} + +// Is efficiently compares the underlying pathname. +func (a *Absolute) Is(v *Absolute) bool { + if a == nil && v == nil { + return true + } + return a.ok() && v.ok() && a.pathname == v.pathname +} + +// NewAbs checks pathname and returns a new [Absolute] if pathname is absolute. +func NewAbs(pathname string) (*Absolute, error) { + if !path.IsAbs(pathname) { + return nil, AbsoluteError(pathname) + } + return unsafeAbs(pathname), nil +} + +// MustAbs calls [NewAbs] and panics on error. +func MustAbs(pathname string) *Absolute { + if a, err := NewAbs(pathname); err != nil { + panic(err) + } else { + return a + } +} + +// Append calls [path.Join] with [Absolute] as the first element. +func (a *Absolute) Append(elem ...string) *Absolute { + return unsafeAbs(path.Join(append([]string{a.String()}, elem...)...)) +} + +// Dir calls [path.Dir] with [Absolute] as its argument. +func (a *Absolute) Dir() *Absolute { return unsafeAbs(path.Dir(a.String())) } + +// GobEncode returns the checked pathname. +func (a *Absolute) GobEncode() ([]byte, error) { + return []byte(a.String()), nil +} + +// GobDecode stores data if it represents an absolute pathname. +func (a *Absolute) GobDecode(data []byte) error { + pathname := string(data) + if !path.IsAbs(pathname) { + return AbsoluteError(pathname) + } + a.pathname = unique.Make(pathname) + return nil +} + +// MarshalJSON returns a JSON representation of the checked pathname. +func (a *Absolute) MarshalJSON() ([]byte, error) { + return json.Marshal(a.String()) +} + +// UnmarshalJSON stores data if it represents an absolute pathname. +func (a *Absolute) UnmarshalJSON(data []byte) error { + var pathname string + if err := json.Unmarshal(data, &pathname); err != nil { + return err + } + if !path.IsAbs(pathname) { + return AbsoluteError(pathname) + } + a.pathname = unique.Make(pathname) + return nil +} + +// SortAbs calls [slices.SortFunc] for a slice of [Absolute]. +func SortAbs(x []*Absolute) { + slices.SortFunc(x, func(a, b *Absolute) int { + return strings.Compare(a.String(), b.String()) + }) +} + +// CompactAbs calls [slices.CompactFunc] for a slice of [Absolute]. +func CompactAbs(s []*Absolute) []*Absolute { + return slices.CompactFunc(s, func(a *Absolute, b *Absolute) bool { + return a.Is(b) + }) +} |
