Zettelstore

Check-in [2b742a0dc8]
Login

Check-in [2b742a0dc8]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Very initial version of zid migration
Timelines: family | ancestors | descendants | both | b36
Files: files | file ages | folders
SHA3-256: 2b742a0dc8ede22eba575f3ea2df15d73795a11cda343e02ffc6e7fed14eddca
User & Date: stern 2024-06-21 17:39:20
Context
2024-06-21
17:47
Add a check for already given zids ... (check-in: 8475ed0c2d user: stern tags: b36)
17:39
Very initial version of zid migration ... (check-in: 2b742a0dc8 user: stern tags: b36)
15:51
Rename ZidO constant to InvalidO (an use the correct branch name b36) ... (check-in: 4cc1d16330 user: stern tags: b36)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to zettel/id/id.go.

161
162
163
164
165
166
167



























































































	}
	res, err := ParseO(s)
	if err != nil {
		panic(err)
	}
	return res
}


































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
	}
	res, err := ParseO(s)
	if err != nil {
		panic(err)
	}
	return res
}

// ----- Base36 Zids

// Zid is the internal identifier of a zettel. Typically, it is a number in
// the range 1..36^4-1 (1..1679615). As an external representation, this
// number is encode by four digits / lowercase letters.
type Zid uint32

// Some important ZettelIDs.
const (
	Invalid = Zid(0) // Invalid is a Zid that will never be valid
)

const maxZid = 36*36*36*36 - 1

// ParseUint interprets a string as a possible zettel identifier
// and returns its integer value.
func ParseUint(s string) (uint64, error) {
	res, err := strconv.ParseUint(s, 36, 21)
	if err != nil {
		return 0, err
	}
	if res == 0 || res > maxZid {
		return res, strconv.ErrRange
	}
	return res, nil
}

// Parse interprets a string as a zettel identification and
// returns its value.
func Parse(s string) (Zid, error) {
	if len(s) != 4 {
		return Invalid, strconv.ErrSyntax
	}
	res, err := ParseUint(s)
	if err != nil {
		return Invalid, err
	}
	return Zid(res), nil
}

// MustParse tries to interpret a string as a zettel identifier and returns
// its value or panics otherwise.
func MustParse(s api.ZettelID) Zid {
	zid, err := Parse(string(s))
	if err == nil {
		return zid
	}
	panic(err)
}

// String converts the zettel identification to a string of 14 digits.
// Only defined for valid ids.
func (zid Zid) String() string {
	var result [4]byte
	zid.toByteArray(&result)
	return string(result[:])
}

// ZettelID return the zettel identification as a api.ZettelID.
func (zid Zid) ZettelID() api.ZettelID { return api.ZettelID(zid.String()) }

// Bytes converts the zettel identification to a byte slice of 14 digits.
// Only defined for valid ids.
func (zid Zid) Bytes() []byte {
	var result [4]byte
	zid.toByteArray(&result)
	return result[:]
}

// toByteArray converts the Zid into a fixed byte array, usable for printing.
//
// Based on idea by Daniel Lemire: "Converting integers to fix-digit representations quickly"
// https://lemire.me/blog/2021/11/18/converting-integers-to-fix-digit-representations-quickly/
func (zid Zid) toByteArray(result *[4]byte) {
	d12 := uint32(zid) / (36 * 36)
	d1 := d12 / 36
	d2 := d12 % 36
	d34 := uint32(zid) % (36 * 36)
	d3 := d34 / 36
	d4 := d34 % 36

	const digits = "0123456789abcdefghijklmnopqrstuvwxyz"
	result[0] = digits[d1]
	result[1] = digits[d2]
	result[2] = digits[d3]
	result[3] = digits[d4]
}

// IsValid determines if zettel id is a valid one, e.g. consists of max. 14 digits.
func (zid Zid) IsValid() bool { return 0 < zid && zid <= maxZid }

Changes to zettel/id/id_test.go.

11
12
13
14
15
16
17

18
19
20
21
22
23
24
// SPDX-FileCopyrightText: 2020-present Detlef Stern
//-----------------------------------------------------------------------------

// Package id_test provides unit tests for testing zettel id specific functions.
package id_test

import (

	"testing"

	"zettelstore.de/z/zettel/id"
)

func TestIsValidO(t *testing.T) {
	t.Parallel()







>







11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// SPDX-FileCopyrightText: 2020-present Detlef Stern
//-----------------------------------------------------------------------------

// Package id_test provides unit tests for testing zettel id specific functions.
package id_test

import (
	"strings"
	"testing"

	"zettelstore.de/z/zettel/id"
)

func TestIsValidO(t *testing.T) {
	t.Parallel()
86
87
88
89
90
91
92
































func BenchmarkBytesO(b *testing.B) {
	var bs []byte
	for range b.N {
		bs = id.ZidO(12345678901200).Bytes()
	}
	bResult = bs
}







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
func BenchmarkBytesO(b *testing.B) {
	var bs []byte
	for range b.N {
		bs = id.ZidO(12345678901200).Bytes()
	}
	bResult = bs
}

// ----- Base36

func TestIsValid(t *testing.T) {
	t.Parallel()
	validIDs := []string{
		"0001", "0020", "0300", "4000",
		"zzzz", "ZZZZ", "Cafe", "bAbE",
	}

	for i, sid := range validIDs {
		zid, err := id.Parse(sid)
		if err != nil {
			t.Errorf("i=%d: sid=%q is not valid, but should be. err=%v", i, sid, err)
		}
		if s := zid.String(); !strings.EqualFold(s, sid) {
			t.Errorf("i=%d: zid=%v does not format to %q, but to %q", i, zid, sid, s)
		}
	}

	invalidIDs := []string{
		"", "0", "a", "de", "dfg", "abcde",
		"012.",
		"+1234", "+123",
	}

	for i, sid := range invalidIDs {
		if zid, err := id.Parse(sid); err == nil {
			t.Errorf("i=%d: sid=%q is valid (zid=%s), but should not be", i, sid, zid)
		}
	}
}

Added zettel/id/migrate.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
//-----------------------------------------------------------------------------
// Copyright (c) 2024-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: 2024-present Detlef Stern
//-----------------------------------------------------------------------------

package id

import (
	"fmt"
	"maps"
)

// This is for migration of Zid0 to Zid.

// ZidMigrator does the actual migration.
type ZidMigrator struct {
	defined, workset map[ZidO]Zid
	lastZidO         ZidO
	nextZid          Zid
	ranges           []zidRange
}

type zidRange struct {
	lowO, highO ZidO
	base        Zid
}

// NewZidMigrator creates a new zid migrator.
func NewZidMigrator() *ZidMigrator {
	defined := map[ZidO]Zid{
		0:               0,                 // Invalid
		1:               MustParse("0001"), // Zettelstore Version
		2:               MustParse("0002"), // Zettelstore Host
		3:               MustParse("0003"), // Zettelstore Operating System
		4:               MustParse("0004"), // Zettelstore License
		5:               MustParse("0005"), // Zettelstore Contributors
		6:               MustParse("0006"), // Zettelstore Dependencies
		7:               MustParse("0007"), // Zettelstore Log
		8:               MustParse("0008"), // Zettelstore Memory
		20:              MustParse("000g"), // Zettelstore Box Manager
		90:              MustParse("000t"), // Zettelstore Supported Metadata Keys
		92:              MustParse("000v"), // Zettelstore Supported Parser
		96:              MustParse("000x"), // Zettelstore Startup Configuration
		100:             MustParse("000z"), // Zettelstore Runtime Configuration
		10100:           MustParse("0010"), // Zettelstore Base HTML Template
		10200:           MustParse("0011"), // Zettelstore Login Form HTML Template
		10300:           MustParse("0012"), // Zettelstore List Zettel HTML Template
		10401:           MustParse("0013"), // Zettelstore Detail HTML Template
		10402:           MustParse("0014"), // Zettelstore Info HTML Template
		10403:           MustParse("0015"), // Zettelstore Form HTML Template
		10404:           MustParse("0016"), // Zettelstore Rename Form HTML Template
		10405:           MustParse("0017"), // Zettelstore Delete HTML Template
		10700:           MustParse("0018"), // Zettelstore Error HTML Template
		19000:           MustParse("0021"), // Zettelstore Sxn Start Code
		19990:           MustParse("0022"), // Zettelstore Sxn Base Code
		20001:           MustParse("0030"), // Zettelstore Base CSS
		25001:           MustParse("0031"), // Zettelstore User CSS
		40001:           MustParse("0032"), // Generic Emoji
		59900:           MustParse("0020"), // Zettelstore Sxn Prelude
		60010:           MustParse("0041"), // zettel
		60020:           MustParse("0042"), // confguration
		60030:           MustParse("0043"), // role
		60040:           MustParse("0044"), // tag
		90000:           MustParse("0050"), // New Menu
		90001:           MustParse("0051"), // New Zettel
		90002:           MustParse("0052"), // New User
		90003:           MustParse("0053"), // New Tag
		90004:           MustParse("0054"), // New Role
		100000000:       MustParse("0100"), // Zettelstore Manual (bis 02zz)
		200000000:       MustParse("0300"), // Reserviert (bis 0tzz)
		9000000000:      MustParse("0u00"), // Externe Anwendungen (bis 0zzz)
		DefaultHomeZidO: MustParse("1000"), // Default home zettel
	}
	return &ZidMigrator{
		defined:  defined,
		workset:  maps.Clone(defined),
		lastZidO: InvalidO,
		nextZid:  MustParse("1001"),
		ranges: []zidRange{
			{10000, 19999, MustParse("0010")},
			{20000, 29999, MustParse("0030")},
			{40000, 49999, MustParse("0032")},
			{50000, 59999, MustParse("0020")},
			{60000, 69999, MustParse("0040")},
			{90000, 99999, MustParse("0050")},
		},
	}
}

// Migrate an old Zid to a new one.
//
// Old zids must increase.
func (zm *ZidMigrator) Migrate(zidO ZidO) (Zid, error) {
	if zid, found := zm.workset[zidO]; found {
		return zid, nil
	}
	if zidO <= zm.lastZidO {
		return Invalid, fmt.Errorf("out of sequence: %v", zidO)
	}
	zm.lastZidO = zidO
	if (zidO < 10000) ||
		(30000 <= zidO && zidO < 40000) ||
		(70000 <= zidO && zidO < 90000) ||
		(100000 <= zidO && zidO < 100000000) ||
		(200000000 <= zidO && zidO < 9000001000) ||
		(9000002000 <= zidO && zidO < DefaultHomeZidO) {
		return 0, fmt.Errorf("old Zid out of supported range: %v", zidO)
	}
	if DefaultHomeZidO < zidO {
		zid := zm.nextZid
		zm.nextZid++
		zm.workset[zidO] = zid
		return zid, nil
	}
	for _, zr := range zm.ranges {
		if zidO < zr.lowO || zr.highO < zidO {
			continue
		}
		zid := zm.retrieveNextInRange(zr.lowO, zr.highO)
		zm.workset[zidO] = zid
		return zid, nil
	}
	return Invalid, nil
}

func (zm *ZidMigrator) retrieveNextInRange(lowO, highO ZidO) Zid {
	var currentMax Zid
	for zidO, zid := range zm.workset {
		if lowO <= zidO && zidO <= highO && currentMax < zid {
			currentMax = zid
		}
	}
	return currentMax + 1
}

Added zettel/id/migrate_test.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
//-----------------------------------------------------------------------------
// Copyright (c) 2024-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: 2024-present Detlef Stern
//-----------------------------------------------------------------------------

package id_test

import (
	"testing"

	"zettelstore.de/z/zettel/id"
)

func TestMigrat(t *testing.T) {
	testcases := []struct {
		inp []id.ZidO
		err string
		exp []id.Zid
	}{
		{[]id.ZidO{1, 2, 3}, "", []id.Zid{1, 2, 3}},
		{[]id.ZidO{3, 2, 1}, "", []id.Zid{3, 2, 1}},
		{[]id.ZidO{20240224123456, 19700101000000}, "out of sequence: 19700101000000", []id.Zid{46657, 1}},
	}
	for i, tc := range testcases {
		migrator := id.NewZidMigrator()
		for pos, zidO := range tc.inp {
			zid, err := migrator.Migrate(zidO)
			if err != nil {
				if tc.err == "" {
					t.Errorf("%d: no error expected, but got: %v", i, err)
				} else if err.Error() != tc.err {
					t.Errorf("%d: error %v expected, but got %v", i, tc.err, err)
				}
				continue
			}
			if exp := tc.exp[pos]; exp != zid {
				t.Errorf("%d: %v should migrate to %v, but got: %v", i, zidO, exp, zid)
			}
		}
	}
}