-
Notifications
You must be signed in to change notification settings - Fork 2
/
type.go
230 lines (205 loc) · 5.8 KB
/
type.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
//-----------------------------------------------------------------------------
// Copyright (c) 2020-present Detlef Stern
//
// This file is part of Zettelstore.
//
// Zettelstore is licensed under the latest version of the EUPL (European Union
// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2020-present Detlef Stern
//-----------------------------------------------------------------------------
package meta
import (
"strconv"
"strings"
"sync"
"time"
"zettelstore.de/client.fossil/api"
"zettelstore.de/z/zettel/id"
)
// DescriptionType is a description of a specific key type.
type DescriptionType struct {
Name string
IsSet bool
}
// String returns the string representation of the given type
func (t DescriptionType) String() string { return t.Name }
var registeredTypes = make(map[string]*DescriptionType)
func registerType(name string, isSet bool) *DescriptionType {
if _, ok := registeredTypes[name]; ok {
panic("Type '" + name + "' already registered")
}
t := &DescriptionType{name, isSet}
registeredTypes[name] = t
return t
}
// Supported key types.
var (
TypeCredential = registerType(api.MetaCredential, false)
TypeEmpty = registerType(api.MetaEmpty, false)
TypeID = registerType(api.MetaID, false)
TypeIDSet = registerType(api.MetaIDSet, true)
TypeNumber = registerType(api.MetaNumber, false)
TypeString = registerType(api.MetaString, false)
TypeTagSet = registerType(api.MetaTagSet, true)
TypeTimestamp = registerType(api.MetaTimestamp, false)
TypeURL = registerType(api.MetaURL, false)
TypeWord = registerType(api.MetaWord, false)
TypeZettelmarkup = registerType(api.MetaZettelmarkup, false)
)
// Type returns a type hint for the given key. If no type hint is specified,
// TypeUnknown is returned.
func (*Meta) Type(key string) *DescriptionType {
return Type(key)
}
// Some constants for key suffixes that determine a type.
const (
SuffixKeyRole = "-role"
SuffixKeyURL = "-url"
)
var (
cachedTypedKeys = make(map[string]*DescriptionType)
mxTypedKey sync.RWMutex
suffixTypes = map[string]*DescriptionType{
"-date": TypeTimestamp,
"-number": TypeNumber,
SuffixKeyRole: TypeWord,
"-time": TypeTimestamp,
"-title": TypeZettelmarkup,
SuffixKeyURL: TypeURL,
"-zettel": TypeID,
"-zid": TypeID,
"-zids": TypeIDSet,
}
)
// Type returns a type hint for the given key. If no type hint is specified,
// TypeEmpty is returned.
func Type(key string) *DescriptionType {
if k, ok := registeredKeys[key]; ok {
return k.Type
}
mxTypedKey.RLock()
k, ok := cachedTypedKeys[key]
mxTypedKey.RUnlock()
if ok {
return k
}
for suffix, t := range suffixTypes {
if strings.HasSuffix(key, suffix) {
mxTypedKey.Lock()
defer mxTypedKey.Unlock()
cachedTypedKeys[key] = t
return t
}
}
return TypeEmpty
}
// SetList stores the given string list value under the given key.
func (m *Meta) SetList(key string, values []string) {
if key != api.KeyID {
for i, val := range values {
values[i] = trimValue(val)
}
m.pairs[key] = strings.Join(values, " ")
}
}
// SetWord stores the given word under the given key.
func (m *Meta) SetWord(key, word string) {
if slist := ListFromValue(word); len(slist) > 0 {
m.Set(key, slist[0])
}
}
// SetNow stores the current timestamp under the given key.
func (m *Meta) SetNow(key string) {
m.Set(key, time.Now().Local().Format(id.TimestampLayout))
}
// BoolValue returns the value interpreted as a bool.
func BoolValue(value string) bool {
if len(value) > 0 {
switch value[0] {
case '0', 'f', 'F', 'n', 'N':
return false
}
}
return true
}
// GetBool returns the boolean value of the given key.
func (m *Meta) GetBool(key string) bool {
if value, ok := m.Get(key); ok {
return BoolValue(value)
}
return false
}
// TimeValue returns the time value of the given value.
func TimeValue(value string) (time.Time, bool) {
if t, err := time.Parse(id.TimestampLayout, ExpandTimestamp(value)); err == nil {
return t, true
}
return time.Time{}, false
}
// ExpandTimestamp makes a short-form timestamp larger.
func ExpandTimestamp(value string) string {
switch l := len(value); l {
case 4: // YYYY
return value + "0101000000"
case 6: // YYYYMM
return value + "01000000"
case 8, 10, 12: // YYYYMMDD, YYYYMMDDhh, YYYYMMDDhhmm
return value + "000000"[:14-l]
case 14: // YYYYMMDDhhmmss
return value
default:
if l > 14 {
return value[:14]
}
return value
}
}
// ListFromValue transforms a string value into a list value.
func ListFromValue(value string) []string {
return strings.Fields(value)
}
// GetList retrieves the string list value of a given key. The bool value
// signals, whether there was a value stored or not.
func (m *Meta) GetList(key string) ([]string, bool) {
value, ok := m.Get(key)
if !ok {
return nil, false
}
return ListFromValue(value), true
}
// TagsFromValue returns the value as a sequence of normalized tags.
func TagsFromValue(value string) []string {
tags := ListFromValue(strings.ToLower(value))
for i, tag := range tags {
if len(tag) > 1 && tag[0] == '#' {
tags[i] = tag[1:]
}
}
return tags
}
// CleanTag removes the number character ('#') from a tag value and lowercases it.
func CleanTag(tag string) string {
if len(tag) > 1 && tag[0] == '#' {
return tag[1:]
}
return tag
}
// NormalizeTag adds a missing prefix "#" to the tag
func NormalizeTag(tag string) string {
if len(tag) > 0 && tag[0] == '#' {
return tag
}
return "#" + tag
}
// GetNumber retrieves the numeric value of a given key.
func (m *Meta) GetNumber(key string, def int64) int64 {
if value, ok := m.Get(key); ok {
if num, err := strconv.ParseInt(value, 10, 64); err == nil {
return num
}
}
return def
}