// Package check provides types yielding values checked to meet a condition. package check import ( "encoding" "errors" "fmt" "path/filepath" "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] } var ( _ encoding.TextAppender = new(Absolute) _ encoding.TextMarshaler = new(Absolute) _ encoding.TextUnmarshaler = new(Absolute) _ encoding.BinaryAppender = new(Absolute) _ encoding.BinaryMarshaler = new(Absolute) _ encoding.BinaryUnmarshaler = new(Absolute) ) // 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 !filepath.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 [filepath.Join] with [Absolute] as the first element. func (a *Absolute) Append(elem ...string) *Absolute { return unsafeAbs(filepath.Join(append([]string{a.String()}, elem...)...)) } // Dir calls [filepath.Dir] with [Absolute] as its argument. func (a *Absolute) Dir() *Absolute { return unsafeAbs(filepath.Dir(a.String())) } // AppendText appends the checked pathname. func (a *Absolute) AppendText(data []byte) ([]byte, error) { return append(data, a.String()...), nil } // MarshalText returns the checked pathname. func (a *Absolute) MarshalText() ([]byte, error) { return a.AppendText(nil) } // UnmarshalText stores data if it represents an absolute pathname. func (a *Absolute) UnmarshalText(data []byte) error { pathname := string(data) if !filepath.IsAbs(pathname) { return AbsoluteError(pathname) } a.pathname = unique.Make(pathname) return nil } func (a *Absolute) AppendBinary(data []byte) ([]byte, error) { return a.AppendText(data) } func (a *Absolute) MarshalBinary() ([]byte, error) { return a.MarshalText() } func (a *Absolute) UnmarshalBinary(data []byte) error { return a.UnmarshalText(data) } // 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) }) }