-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnamaste.go
131 lines (119 loc) · 3.06 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
122
123
124
125
126
127
128
129
130
131
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 n ('0=TYPE_VERSION') or an empty string if n 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"
}
// IsObject returs true if n's type is 'ocfl_object'
func (n Namaste) IsObject() bool {
return n.Type == NamasteTypeObject
}
// IsStore returns true if n's type is 'ocfl'
func (n Namaste) IsStore() bool {
return n.Type == NamasteTypeStore
}
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
}