aboutsummaryrefslogtreecommitdiffhomepage
path: root/container/check/absolute.go
blob: fcb260c5c800961801b9bf2ab3a1e1a466861c17 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Package check provides types yielding values checked to meet a condition.
package check

import (
	"encoding/json"
	"errors"
	"fmt"
	"path"
	"slices"
	"strings"
	"syscall"
)

// AbsoluteError is returned by [NewAbs] and holds the invalid pathname.
type AbsoluteError struct{ Pathname string }

func (e *AbsoluteError) Error() string { return fmt.Sprintf("path %q is not absolute", e.Pathname) }
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 string }

// unsafeAbs returns [check.Absolute] on any string value.
func unsafeAbs(pathname string) *Absolute { return &Absolute{pathname} }

func (a *Absolute) String() string {
	if a.pathname == "" {
		panic("attempted use of zero Absolute")
	}
	return a.pathname
}

func (a *Absolute) Is(v *Absolute) bool {
	if a == nil && v == nil {
		return true
	}
	return a != nil && v != nil &&
		a.pathname != "" && v.pathname != "" &&
		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())) }

func (a *Absolute) GobEncode() ([]byte, error) { return []byte(a.String()), nil }
func (a *Absolute) GobDecode(data []byte) error {
	pathname := string(data)
	if !path.IsAbs(pathname) {
		return &AbsoluteError{pathname}
	}
	a.pathname = pathname
	return nil
}

func (a *Absolute) MarshalJSON() ([]byte, error) { return json.Marshal(a.String()) }
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 = 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.String() == b.String() })
}