-
Notifications
You must be signed in to change notification settings - Fork 0
/
namaste.go
121 lines (111 loc) · 2.82 KB
/
namaste.go
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package ocfl
import (
"context"
"errors"
"fmt"
"io"
"io/fs"
"path"
"regexp"
"strings"
)
const (
NamasteTypeObject = "ocfl_object" // type string for OCFL Object declaration
NamasteTypeStore = "ocfl" // type string for OCFL Storage Root declaration
)
var (
ErrNamasteNotExist = fmt.Errorf("missing NAMASTE declaration: %w", fs.ErrNotExist)
ErrNamasteContents = errors.New("invalid NAMASTE declaration contents")
ErrNamasteMultiple = errors.New("multiple NAMASTE declarations found")
namasteRE = regexp.MustCompile(`^0=([a-z_]+)_([0-9]+\.[0-9]+)$`)
)
// Namaste represents a NAMASTE declaration
type Namaste struct {
Type string
Version Spec
}
// FindNamaste returns the NAMASTE declaration from a fs.DirEntry slice. An
// error is returned if the number of declarations is not one.
func FindNamaste(items []fs.DirEntry) (Namaste, error) {
var found []Namaste
for _, e := range items {
if e.IsDir() {
continue
}
if dec, err := ParseNamaste(e.Name()); err == nil {
found = append(found, dec)
}
}
switch len(found) {
case 0:
return Namaste{}, ErrNamasteNotExist
case 1:
return found[0], nil
default:
return Namaste{}, ErrNamasteMultiple
}
}
// Name returns the filename for d (0=TYPE_VERSION) or an empty string if d is
// empty
func (n Namaste) Name() string {
if n.Type == "" || n.Version.Empty() {
return ""
}
return "0=" + n.Type + `_` + string(n.Version)
}
// Body returns the expected file contents of the namaste declaration
func (n Namaste) Body() string {
if n.Type == "" || n.Version.Empty() {
return ""
}
return n.Type + `_` + string(n.Version) + "\n"
}
func ParseNamaste(name string) (n Namaste, err error) {
m := namasteRE.FindStringSubmatch(name)
if len(m) != 3 {
err = ErrNamasteNotExist
return
}
n.Type = m[1]
n.Version = Spec(m[2])
return n, nil
}
// ValidateNamaste validates a namaste declaration
func ValidateNamaste(ctx context.Context, fsys FS, name string) (err error) {
nam, err := ParseNamaste(path.Base(name))
if err != nil {
return
}
f, err := fsys.OpenFile(ctx, name)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
err = fmt.Errorf("opening %q: %w", name, ErrNamasteNotExist)
return
}
err = fmt.Errorf("opening %q: %w", name, err)
return
}
defer func() {
if closeErr := f.Close(); closeErr != nil {
err = errors.Join(err, closeErr)
}
}()
decl, err := io.ReadAll(f)
if err != nil {
err = fmt.Errorf("reading %q: %w", name, err)
return
}
if string(decl) != nam.Body() {
err = fmt.Errorf("contents of %q: %w", name, ErrNamasteContents)
return
}
return
}
func WriteDeclaration(ctx context.Context, root WriteFS, dir string, d Namaste) error {
cont := strings.NewReader(d.Body())
_, err := root.Write(ctx, path.Join(dir, d.Name()), cont)
if err != nil {
return fmt.Errorf(`writing declaration: %w`, err)
}
return nil
}