aboutsummaryrefslogtreecommitdiffhomepage
path: root/check/absolute.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-03-17 15:35:58 +0900
committerOphestra <cat@gensokyo.uk>2026-03-17 15:39:03 +0900
commit6d015a949e27f20c86409c7d78053f0b0493f165 (patch)
tree4d2bc1e9596fa30ea03e4c0cc575e6a7a969249d /check/absolute.go
parente9a72490db44426605debd3cc756a7924931d706 (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.go132
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)
+ })
+}