-
Notifications
You must be signed in to change notification settings - Fork 0
/
photograph.go
237 lines (200 loc) · 5.78 KB
/
photograph.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/*
Karl Ramberg
Loupe v0.1.0
photograph.go
*/
package main
import (
"errors"
"path/filepath"
"regexp"
"strconv"
"strings"
)
type Photograph struct {
date string
letter string
number string
class string
group string
version string
subversion string
extension string
}
// Initialize a new struct given the filename, validating each piece of data
func (p *Photograph) init(name string) error {
// Snag the file extension off the end
p.extension = filepath.Ext(name)
name = strings.TrimSuffix(name, p.extension)
// Split the name into it's main sections at the underscores
sections := strings.Split(name, "_")
if len(sections) != 3 {
return errors.New("filename not formatted \"identifier_group_version\"")
}
// Split the identifier into it's two parts
indentifier := strings.Split(sections[0], "-")
if len(indentifier) != 2 {
return errors.New("identifier not formatted \"date-number\"")
}
// Validate the identifier's date
validDate, err := validDate(indentifier[0])
if !validDate {
return errors.Join(errors.New("date is incorrect"), err)
}
p.date = indentifier[0]
// Validate the identifier's letter and/or number
validNumber, err := regexp.MatchString("^([A-Z]*[0-9]+)$", indentifier[1])
if !validNumber || err != nil {
return errors.Join(errors.New("number should only use capital letters and numbers"))
}
p.number = indentifier[1]
// Split the group section into it's one or more parts
groups := strings.Split(sections[1], "-")
if len(groups) > 2 {
return errors.New("use format \"class-group\" or just \"group\"")
}
if len(groups) == 2 { // Class and group
validClassAndGroup, err := validWord(groups[0] + groups[1])
if !validClassAndGroup || err != nil {
return errors.Join(errors.New("groups not formatted correctly"), err)
}
p.class = groups[0]
p.group = groups[1]
} else { // Just group
validGroup, err := validWord(groups[0])
if !validGroup || err != nil {
return errors.Join(errors.New("group not formatted correctly"), err)
}
p.class = "none"
p.group = groups[0]
}
// Split the version section into it's one or more parts
versions := strings.Split(sections[2], "-")
if len(versions) > 2 {
return errors.New("use format \"version-subversion\" or just \"version\"")
}
// Version and subversion
if len(versions) == 2 {
validVersionAndSubversion, err := validWord(versions[0] + versions[1])
if !validVersionAndSubversion || err != nil {
return errors.Join(errors.New("versions not formatted correctly"), err)
}
p.version = versions[0]
p.subversion = versions[1]
} else {
validVersion, err := validWord(versions[0])
if !validVersion || err != nil {
return errors.Join(errors.New("version not formatted correctly"), err)
}
p.version = versions[0]
p.subversion = "none"
}
return nil
}
// Construct the photograph's filename from its data
func (p *Photograph) filename() (name string) {
// Identifier
name += p.date + "-"
if p.letter != "none" {
name += p.letter
}
name += p.number + "_"
// Group(s)
if p.class != "none" {
name += p.class + "-"
}
name += p.group + "_"
// Version(s)
name += p.version
if p.subversion != "none" {
name += "-" + p.subversion
}
// Extension
name += p.extension
return
}
// Construct the directory the photograph should live in based on its group(s) and version(s)
func (p *Photograph) directory() (dir string) {
if p.class != "none" {
dir = filepath.Join(dir, p.class+"s")
}
dir = filepath.Join(dir, p.group)
dir = filepath.Join(dir, p.version+"s")
if p.subversion != "none" {
dir = filepath.Join(dir, p.subversion)
}
return
}
func (p *Photograph) identifier() (i string) {
i += p.date + "-"
if p.letter != "none" {
i += p.letter
}
i += p.number
return
}
func (p *Photograph) classDir() string {
if p.class == "none" {
return ""
}
return p.class + "s"
}
func (p *Photograph) groupDir() string {
return filepath.Join(p.classDir(), p.group)
}
func (p *Photograph) versionDir() string {
return filepath.Join(p.groupDir(), p.version+"s")
}
func (p *Photograph) subversionDir() string {
if p.subversion == "none" {
return ""
}
return filepath.Join(p.versionDir(), p.subversion)
}
var monthLen = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
// Validates a given string is a proper date in YYYYMMDD format
func validDate(date string) (bool, error) {
if len(date) != 8 {
return false, errors.New("use format YYYYMMDD")
}
// Check that all three parts are two digit integers
year, err1 := strconv.Atoi(date[0:4])
month, err2 := strconv.Atoi(date[4:6])
day, err3 := strconv.Atoi(date[6:8])
err := errors.Join(err1, err2, err3)
if err != nil {
return false, errors.New("only use digits")
}
// Check if the month is valid
if month < 1 || month > 12 {
return false, errors.New("month should be between 01 and 12")
}
// Check if the day is valid given the month
if month != 2 {
if day < 1 || day > monthLen[month-1] {
return false, errors.New("check how many days are in the month")
}
} else { // Leap year fancy math
leapYear := (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0))
if (day > 28 && !leapYear) || day > 29 {
return false, errors.New("leap years are confusing")
}
}
return true, nil
}
// Validates a given string is a lowercase alphanumeric word
func validWord(word string) (bool, error) {
valid, err := regexp.MatchString("^([a-z0-9]+)$", word)
if !valid || err != nil {
return false, errors.Join(errors.New("only use alphanumeric characters"), err)
}
return true, nil
}
// Validates that given string is a proper grouping type
func validType(input string) (bool, error) {
valid := (input == "class" || input == "group" || input == "version" || input == "subversion")
if !valid {
return false, errors.New("invalid type. Use class, group, version or subversion")
}
return true, nil
}