Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From trunk To v0.11.0
2023-12-01
| ||
13:29 | Cleanup changes file: tt tag to code tag ... (Leaf check-in: 93010b5f17 user: stern tags: trunk) | |
13:02 | Fix some omissions in the manual ... (check-in: 9804546d28 user: stern tags: trunk) | |
2023-03-28
| ||
16:28 | Version 0.11.1 ... (check-in: fede87bf67 user: stern tags: trunk, release, v0.11.1) | |
2023-03-27
| ||
14:53 | Version 0.11.0 ... (check-in: 30ca5d039f user: stern tags: trunk, release, v0.11.0) | |
10:58 | Add search term ''PICK n'' to replace ''RANDOM'' in most use cases ... (check-in: b131f5e588 user: stern tags: trunk) | |
Changes to README.md.
︙ | ︙ | |||
19 20 21 22 23 24 25 | often connects to Zettelstore via its API. Some of the software packages may be experimental. The software, including the manual, is licensed under the [European Union Public License 1.2 (or later)](https://zettelstore.de/home/file?name=LICENSE.txt&ci=trunk). | > | | 19 20 21 22 23 24 25 26 27 | often connects to Zettelstore via its API. Some of the software packages may be experimental. The software, including the manual, is licensed under the [European Union Public License 1.2 (or later)](https://zettelstore.de/home/file?name=LICENSE.txt&ci=trunk). [Stay](https://twitter.com/zettelstore) <a rel="me" href="https://nerdculture.de/@zettelstore">tuned</a> … |
Changes to VERSION.
|
| | | 1 | 0.11.0 |
Changes to ast/ast.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | // Package ast provides the abstract syntax tree for parsed zettel content. package ast import ( "net/url" | | | | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // Package ast provides the abstract syntax tree for parsed zettel content. package ast import ( "net/url" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // ZettelNode is the root node of the abstract syntax tree. // It is *not* part of the visitor pattern. type ZettelNode struct { Meta *meta.Meta // Original metadata Content domain.Content // Original content Zid id.Zid // Zettel identification. InhMeta *meta.Meta // Metadata of the zettel, with inherited values. Ast BlockSlice // Zettel abstract syntax tree is a sequence of block nodes. Syntax string // Syntax / parser that produced the Ast } // Node is the interface, all nodes must implement. |
︙ | ︙ |
Changes to ast/block.go.
1 2 3 4 5 6 7 8 9 10 11 12 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package ast | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package ast import "zettelstore.de/c/attrs" // Definition of Block nodes. // BlockSlice is a slice of BlockNodes. type BlockSlice []BlockNode func (*BlockSlice) blockNode() { /* Just a marker */ } |
︙ | ︙ |
Changes to ast/inline.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package ast import ( "unicode/utf8" | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //----------------------------------------------------------------------------- package ast import ( "unicode/utf8" "zettelstore.de/c/attrs" ) // Definitions of inline nodes. // InlineSlice is a list of BlockNodes. type InlineSlice []InlineNode |
︙ | ︙ | |||
191 192 193 194 195 196 197 | // FormatKind specifies the format that is applied to the inline nodes. type FormatKind int // Constants for FormatCode const ( _ FormatKind = iota | | | | | | | | < | | 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | // FormatKind specifies the format that is applied to the inline nodes. type FormatKind int // Constants for FormatCode const ( _ FormatKind = iota FormatEmph // Emphasized text. FormatStrong // Strongly emphasized text. FormatInsert // Inserted text. FormatDelete // Deleted text. FormatSuper // Superscripted text. FormatSub // SubscriptedText. FormatQuote // Quoted text. FormatSpan // Generic inline container. ) func (*FormatNode) inlineNode() { /* Just a marker */ } // WalkChildren walks to the formatted text. func (fn *FormatNode) WalkChildren(v Visitor) { Walk(v, &fn.Inlines) } |
︙ | ︙ |
Changes to ast/ref.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | package ast import ( "net/url" "strings" | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package ast import ( "net/url" "strings" "zettelstore.de/z/domain/id" ) // QueryPrefix is the prefix that denotes a query expression. const QueryPrefix = "query:" // ParseReference parses a string and returns a reference. func ParseReference(s string) *Reference { |
︙ | ︙ |
Changes to ast/walk_test.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package ast_test import ( "testing" | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //----------------------------------------------------------------------------- package ast_test import ( "testing" "zettelstore.de/c/attrs" "zettelstore.de/z/ast" ) func BenchmarkWalk(b *testing.B) { root := ast.BlockSlice{ &ast.HeadingNode{ Inlines: ast.CreateInlineSliceFromWords("A", "Simple", "Heading"), |
︙ | ︙ |
Changes to auth/auth.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 | package auth import ( "time" "zettelstore.de/z/box" "zettelstore.de/z/config" | | | | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package auth import ( "time" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // BaseManager allows to check some base auth modes. type BaseManager interface { // IsReadonly returns true, if the systems is configured to run in read-only-mode. IsReadonly() bool } |
︙ | ︙ | |||
38 39 40 41 42 43 44 | // TokenKind specifies for which application / usage a token is/was requested. type TokenKind int // Allowed values of token kind const ( _ TokenKind = iota | | | | 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | // TokenKind specifies for which application / usage a token is/was requested. type TokenKind int // Allowed values of token kind const ( _ TokenKind = iota KindJSON KindHTML ) // TokenData contains some important elements from a token. type TokenData struct { Token []byte Now time.Time Issued time.Time |
︙ | ︙ |
Changes to auth/cred/cred.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | // Package cred provides some function for handling credentials. package cred import ( "bytes" "golang.org/x/crypto/bcrypt" | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // Package cred provides some function for handling credentials. package cred import ( "bytes" "golang.org/x/crypto/bcrypt" "zettelstore.de/z/domain/id" ) // HashCredential returns a hashed vesion of the given credential func HashCredential(zid id.Zid, ident, credential string) (string, error) { fullCredential := createFullCredential(zid, ident, credential) res, err := bcrypt.GenerateFromPassword(fullCredential, bcrypt.DefaultCost) if err != nil { |
︙ | ︙ | |||
40 41 42 43 44 45 46 | return false, nil } return false, err } func createFullCredential(zid id.Zid, ident, credential string) []byte { var buf bytes.Buffer | | | 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | return false, nil } return false, err } func createFullCredential(zid id.Zid, ident, credential string) []byte { var buf bytes.Buffer buf.WriteString(zid.String()) buf.WriteByte(' ') buf.WriteString(ident) buf.WriteByte(' ') buf.WriteString(credential) return buf.Bytes() } |
Deleted auth/impl/digest.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to auth/impl/impl.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | import ( "errors" "hash/fnv" "io" "time" | | | | | | | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import ( "errors" "hash/fnv" "io" "time" "github.com/pascaldekloe/jwt" "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/auth/policy" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) type myAuth struct { readonly bool owner id.Zid secret []byte } |
︙ | ︙ | |||
63 64 65 66 67 68 69 | } return h.Sum(nil) } // IsReadonly returns true, if the systems is configured to run in read-only-mode. func (a *myAuth) IsReadonly() bool { return a.readonly } | | < | | | < | > > > | | > > > > | > > | < | < | | < < < < < < < | > > > | < | | | | > > > > | > | | > > | < < > | | | < < < < < | | 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 | } return h.Sum(nil) } // IsReadonly returns true, if the systems is configured to run in read-only-mode. func (a *myAuth) IsReadonly() bool { return a.readonly } const reqHash = jwt.HS512 // ErrNoIdent signals that the 'ident' key is missing. var ErrNoIdent = errors.New("auth: missing ident") // ErrOtherKind signals that the token was defined for another token kind. var ErrOtherKind = errors.New("auth: wrong token kind") // ErrNoZid signals that the 'zid' key is missing. var ErrNoZid = errors.New("auth: missing zettel id") // GetToken returns a token to be used for authentification. func (a *myAuth) GetToken(ident *meta.Meta, d time.Duration, kind auth.TokenKind) ([]byte, error) { subject, ok := ident.Get(api.KeyUserID) if !ok || subject == "" { return nil, ErrNoIdent } now := time.Now().Round(time.Second) claims := jwt.Claims{ Registered: jwt.Registered{ Subject: subject, Expires: jwt.NewNumericTime(now.Add(d)), Issued: jwt.NewNumericTime(now), }, Set: map[string]interface{}{ "zid": ident.Zid.String(), "_tk": int(kind), }, } token, err := claims.HMACSign(reqHash, a.secret) if err != nil { return nil, err } return token, nil } // ErrTokenExpired signals an exired token var ErrTokenExpired = errors.New("auth: token expired") // CheckToken checks the validity of the token and returns relevant data. func (a *myAuth) CheckToken(token []byte, k auth.TokenKind) (auth.TokenData, error) { h, err := jwt.NewHMAC(reqHash, a.secret) if err != nil { return auth.TokenData{}, err } claims, err := h.Check(token) if err != nil { return auth.TokenData{}, err } now := time.Now().Round(time.Second) expires := claims.Expires.Time() if expires.Before(now) { return auth.TokenData{}, ErrTokenExpired } ident := claims.Subject if ident == "" { return auth.TokenData{}, ErrNoIdent } if zidS, ok := claims.Set["zid"].(string); ok { if zid, err2 := id.Parse(zidS); err2 == nil { if kind, ok2 := claims.Set["_tk"].(float64); ok2 { if auth.TokenKind(kind) == k { return auth.TokenData{ Token: token, Now: now, Issued: claims.Issued.Time(), Expires: expires, Ident: ident, Zid: zid, }, nil } } return auth.TokenData{}, ErrOtherKind } } return auth.TokenData{}, ErrNoZid } func (a *myAuth) Owner() id.Zid { return a.owner } func (a *myAuth) IsOwner(zid id.Zid) bool { return zid.IsValid() && zid == a.owner } |
︙ | ︙ |
Changes to auth/policy/anon.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package policy import ( "zettelstore.de/z/auth" "zettelstore.de/z/config" | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //----------------------------------------------------------------------------- package policy import ( "zettelstore.de/z/auth" "zettelstore.de/z/config" "zettelstore.de/z/domain/meta" ) type anonPolicy struct { authConfig config.AuthConfig pre auth.Policy } |
︙ | ︙ |
Changes to auth/policy/box.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 | import ( "context" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/config" | | | | | | | > > > > | 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 | import ( "context" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/query" "zettelstore.de/z/web/server" ) // BoxWithPolicy wraps the given box inside a policy box. func BoxWithPolicy( manager auth.AuthzManager, box box.Box, authConfig config.AuthConfig, ) (box.Box, auth.Policy) { pol := newPolicy(manager, authConfig) return newBox(box, pol), pol } // polBox implements a policy box. type polBox struct { box box.Box |
︙ | ︙ | |||
47 48 49 50 51 52 53 | return pp.box.Location() } func (pp *polBox) CanCreateZettel(ctx context.Context) bool { return pp.box.CanCreateZettel(ctx) } | | | | | | | | | < < < < > > > > > > > > | | | | | | | | | | | < < < < < < < < | 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 | return pp.box.Location() } func (pp *polBox) CanCreateZettel(ctx context.Context) bool { return pp.box.CanCreateZettel(ctx) } func (pp *polBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { user := server.GetUser(ctx) if pp.policy.CanCreate(user, zettel.Meta) { return pp.box.CreateZettel(ctx, zettel) } return id.Invalid, box.NewErrNotAllowed("Create", user, id.Invalid) } func (pp *polBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { zettel, err := pp.box.GetZettel(ctx, zid) if err != nil { return domain.Zettel{}, err } user := server.GetUser(ctx) if pp.policy.CanRead(user, zettel.Meta) { return zettel, nil } return domain.Zettel{}, box.NewErrNotAllowed("GetZettel", user, zid) } func (pp *polBox) GetAllZettel(ctx context.Context, zid id.Zid) ([]domain.Zettel, error) { return pp.box.GetAllZettel(ctx, zid) } func (pp *polBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { m, err := pp.box.GetMeta(ctx, zid) if err != nil { return nil, err } user := server.GetUser(ctx) if pp.policy.CanRead(user, m) { return m, nil } return nil, box.NewErrNotAllowed("GetMeta", user, zid) } func (pp *polBox) GetAllMeta(ctx context.Context, zid id.Zid) ([]*meta.Meta, error) { return pp.box.GetAllMeta(ctx, zid) } func (pp *polBox) FetchZids(ctx context.Context) (id.Set, error) { return nil, box.NewErrNotAllowed("fetch-zids", server.GetUser(ctx), id.Invalid) } func (pp *polBox) SelectMeta(ctx context.Context, q *query.Query) ([]*meta.Meta, error) { user := server.GetUser(ctx) canRead := pp.policy.CanRead q = q.SetPreMatch(func(m *meta.Meta) bool { return canRead(user, m) }) return pp.box.SelectMeta(ctx, q) } func (pp *polBox) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool { return pp.box.CanUpdateZettel(ctx, zettel) } func (pp *polBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error { zid := zettel.Meta.Zid user := server.GetUser(ctx) if !zid.IsValid() { return &box.ErrInvalidID{Zid: zid} } // Write existing zettel oldMeta, err := pp.box.GetMeta(ctx, zid) if err != nil { return err } if pp.policy.CanWrite(user, oldMeta, zettel.Meta) { return pp.box.UpdateZettel(ctx, zettel) } return box.NewErrNotAllowed("Write", user, zid) } func (pp *polBox) AllowRenameZettel(ctx context.Context, zid id.Zid) bool { return pp.box.AllowRenameZettel(ctx, zid) } func (pp *polBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error { meta, err := pp.box.GetMeta(ctx, curZid) if err != nil { return err } user := server.GetUser(ctx) if pp.policy.CanRename(user, meta) { return pp.box.RenameZettel(ctx, curZid, newZid) } return box.NewErrNotAllowed("Rename", user, curZid) } func (pp *polBox) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { return pp.box.CanDeleteZettel(ctx, zid) } func (pp *polBox) DeleteZettel(ctx context.Context, zid id.Zid) error { meta, err := pp.box.GetMeta(ctx, zid) if err != nil { return err } user := server.GetUser(ctx) if pp.policy.CanDelete(user, meta) { return pp.box.DeleteZettel(ctx, zid) } return box.NewErrNotAllowed("Delete", user, zid) } func (pp *polBox) Refresh(ctx context.Context) error { user := server.GetUser(ctx) if pp.policy.CanRefresh(user) { return pp.box.Refresh(ctx) } return box.NewErrNotAllowed("Refresh", user, id.Invalid) } |
Changes to auth/policy/default.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package policy import ( | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package policy import ( "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/domain/meta" ) type defaultPolicy struct { manager auth.AuthzManager } func (*defaultPolicy) CanCreate(_, _ *meta.Meta) bool { return true } |
︙ | ︙ |
Changes to auth/policy/owner.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package policy import ( | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package policy import ( "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/config" "zettelstore.de/z/domain/meta" ) type ownerPolicy struct { manager auth.AuthzManager authConfig config.AuthConfig pre auth.Policy } |
︙ | ︙ |
Changes to auth/policy/policy.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | // Package policy provides some interfaces and implementation for authorizsation policies. package policy import ( "zettelstore.de/z/auth" "zettelstore.de/z/config" | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // Package policy provides some interfaces and implementation for authorizsation policies. package policy import ( "zettelstore.de/z/auth" "zettelstore.de/z/config" "zettelstore.de/z/domain/meta" ) // newPolicy creates a policy based on given constraints. func newPolicy(manager auth.AuthzManager, authConfig config.AuthConfig) auth.Policy { var pol auth.Policy if manager.IsReadonly() { pol = &roPolicy{} |
︙ | ︙ |
Changes to auth/policy/policy_test.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | package policy import ( "fmt" "testing" | | | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package policy import ( "fmt" "testing" "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) func TestPolicies(t *testing.T) { t.Parallel() testScene := []struct { readonly bool withAuth bool |
︙ | ︙ |
Changes to auth/policy/readonly.go.
1 2 3 4 5 6 7 8 9 10 11 12 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package policy | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package policy import "zettelstore.de/z/domain/meta" type roPolicy struct{} func (*roPolicy) CanCreate(_, _ *meta.Meta) bool { return false } func (*roPolicy) CanRead(_, _ *meta.Meta) bool { return true } func (*roPolicy) CanWrite(_, _, _ *meta.Meta) bool { return false } func (*roPolicy) CanRename(_, _ *meta.Meta) bool { return false } func (*roPolicy) CanDelete(_, _ *meta.Meta) bool { return false } func (*roPolicy) CanRefresh(user *meta.Meta) bool { return user != nil } |
Changes to box/box.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 | import ( "context" "errors" "fmt" "io" "time" | | | | | | > > > > > > > | > > > > > > > > > < < < < < < < < < < < < < < < < < < < | 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 | import ( "context" "errors" "fmt" "io" "time" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/query" ) // BaseBox is implemented by all Zettel boxes. type BaseBox interface { // Location returns some information where the box is located. // Format is dependent of the box. Location() string // CanCreateZettel returns true, if box could possibly create a new zettel. CanCreateZettel(ctx context.Context) bool // CreateZettel creates a new zettel. // Returns the new zettel id (and an error indication). CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) // GetZettel retrieves a specific zettel. GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) // GetMeta retrieves just the meta data of a specific zettel. GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) // CanUpdateZettel returns true, if box could possibly update the given zettel. CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool // UpdateZettel updates an existing zettel. UpdateZettel(ctx context.Context, zettel domain.Zettel) error // AllowRenameZettel returns true, if box will not disallow renaming the zettel. AllowRenameZettel(ctx context.Context, zid id.Zid) bool // RenameZettel changes the current Zid to a new Zid. RenameZettel(ctx context.Context, curZid, newZid id.Zid) error // CanDeleteZettel returns true, if box could possibly delete the given zettel. CanDeleteZettel(ctx context.Context, zid id.Zid) bool // DeleteZettel removes the zettel from the box. DeleteZettel(ctx context.Context, zid id.Zid) error } // ZidFunc is a function that processes identifier of a zettel. type ZidFunc func(id.Zid) // MetaFunc is a function that processes metadata of a zettel. type MetaFunc func(*meta.Meta) // ManagedBox is the interface of managed boxes. type ManagedBox interface { BaseBox // Apply identifier of every zettel to the given function, if predicate returns true. ApplyZid(context.Context, ZidFunc, query.RetrievePredicate) error // Apply metadata of every zettel to the given function, if predicate returns true. ApplyMeta(context.Context, MetaFunc, query.RetrievePredicate) error // ReadStats populates st with box statistics |
︙ | ︙ | |||
128 129 130 131 132 133 134 | // Refresh the box data. Refresh(context.Context) } // Box is to be used outside the box package and its descendants. type Box interface { BaseBox | < < < < < | | > > > < < < | 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 | // Refresh the box data. Refresh(context.Context) } // Box is to be used outside the box package and its descendants. type Box interface { BaseBox // FetchZids returns the set of all zettel identifer managed by the box. FetchZids(ctx context.Context) (id.Set, error) // SelectMeta returns a list of metadata that comply to the given selection criteria. SelectMeta(ctx context.Context, q *query.Query) ([]*meta.Meta, error) // GetAllZettel retrieves a specific zettel from all managed boxes. GetAllZettel(ctx context.Context, zid id.Zid) ([]domain.Zettel, error) // GetAllMeta retrieves the meta data of a specific zettel from all managed boxes. GetAllMeta(ctx context.Context, zid id.Zid) ([]*meta.Meta, error) // Refresh the data from the box and from its managed sub-boxes. Refresh(context.Context) error } // Stats record stattistics about a box. type Stats struct { // ReadOnly indicates that boxes cannot be modified. ReadOnly bool |
︙ | ︙ | |||
304 305 306 307 308 309 310 | // ErrStopped is returned if calling methods on a box that was not started. var ErrStopped = errors.New("box is stopped") // ErrReadOnly is returned if there is an attepmt to write to a read-only box. var ErrReadOnly = errors.New("read-only box") | | < | < | | | | 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | // ErrStopped is returned if calling methods on a box that was not started. var ErrStopped = errors.New("box is stopped") // ErrReadOnly is returned if there is an attepmt to write to a read-only box. var ErrReadOnly = errors.New("read-only box") // ErrNotFound is returned if a zettel was not found in the box. var ErrNotFound = errors.New("zettel not found") // ErrConflict is returned if a box operation detected a conflict.. // One example: if calculating a new zettel identifier takes too long. var ErrConflict = errors.New("conflict") // ErrCapacity is returned if a box has reached its capacity. var ErrCapacity = errors.New("capacity exceeded") // ErrInvalidID is returned if the zettel id is not appropriate for the box operation. type ErrInvalidID struct{ Zid id.Zid } func (err *ErrInvalidID) Error() string { return "invalid Zettel id: " + err.Zid.String() } |
Changes to box/compbox/compbox.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | // Package compbox provides zettel that have computed content. package compbox import ( "context" "net/url" | | > > > < < < | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // Package compbox provides zettel that have computed content. package compbox import ( "context" "net/url" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" ) func init() { manager.Register( " comp", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return getCompBox(cdata.Number, cdata.Enricher), nil |
︙ | ︙ | |||
66 67 68 69 70 71 72 | } // Setup remembers important values. func Setup(cfg *meta.Meta) { myConfig = cfg.Clone() } func (*compBox) Location() string { return "" } | > > > > > > > | | | | | | < | | | | > > > > | > > > > > | | 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 | } // Setup remembers important values. func Setup(cfg *meta.Meta) { myConfig = cfg.Clone() } func (*compBox) Location() string { return "" } func (*compBox) CanCreateZettel(context.Context) bool { return false } func (cb *compBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { cb.log.Trace().Err(box.ErrReadOnly).Msg("CreateZettel") return id.Invalid, box.ErrReadOnly } func (cb *compBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { if gen, ok := myZettel[zid]; ok && gen.meta != nil { if m := gen.meta(zid); m != nil { updateMeta(m) if genContent := gen.content; genContent != nil { cb.log.Trace().Msg("GetMeta/Content") return domain.Zettel{ Meta: m, Content: domain.NewContent(genContent(m)), }, nil } cb.log.Trace().Msg("GetMeta/NoContent") return domain.Zettel{Meta: m}, nil } } cb.log.Trace().Err(box.ErrNotFound).Msg("GetZettel/Err") return domain.Zettel{}, box.ErrNotFound } func (cb *compBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { if gen, ok := myZettel[zid]; ok { if genMeta := gen.meta; genMeta != nil { if m := genMeta(zid); m != nil { updateMeta(m) cb.log.Trace().Msg("GetMeta") return m, nil } } } cb.log.Trace().Err(box.ErrNotFound).Msg("GetMeta/Err") return nil, box.ErrNotFound } func (cb *compBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { cb.log.Trace().Int("entries", int64(len(myZettel))).Msg("ApplyMeta") for zid, gen := range myZettel { if !constraint(zid) { continue } if genMeta := gen.meta; genMeta != nil { if genMeta(zid) != nil { handle(zid) |
︙ | ︙ | |||
122 123 124 125 126 127 128 129 130 131 132 133 134 | cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } } return nil } func (*compBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { _, ok := myZettel[zid] return !ok } | > > > > > > > | > < < | > < < | 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 | cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } } return nil } func (*compBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false } func (cb *compBox) UpdateZettel(context.Context, domain.Zettel) error { cb.log.Trace().Err(box.ErrReadOnly).Msg("UpdateZettel") return box.ErrReadOnly } func (*compBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { _, ok := myZettel[zid] return !ok } func (cb *compBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error { err := box.ErrNotFound if _, ok := myZettel[curZid]; ok { err = box.ErrReadOnly } cb.log.Trace().Err(err).Msg("RenameZettel") return err } func (*compBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (cb *compBox) DeleteZettel(_ context.Context, zid id.Zid) error { err := box.ErrNotFound if _, ok := myZettel[zid]; ok { err = box.ErrReadOnly } cb.log.Trace().Err(err).Msg("DeleteZettel") return err } func (cb *compBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = true |
︙ | ︙ |
Changes to box/compbox/config.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package compbox import ( "bytes" | | | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | //----------------------------------------------------------------------------- package compbox import ( "bytes" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func genConfigZettelM(zid id.Zid) *meta.Meta { if myConfig == nil { return nil } m := meta.New(zid) |
︙ | ︙ |
Changes to box/compbox/keys.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | package compbox import ( "bytes" "fmt" | | | | | | | 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 | package compbox import ( "bytes" "fmt" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func genKeysM(zid id.Zid) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Supported Metadata Keys") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVTime).(string)) m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genKeysC(*meta.Meta) []byte { keys := meta.GetSortedKeyDescriptions() var buf bytes.Buffer buf.WriteString("|=Name<|=Type<|=Computed?:|=Property?:\n") for _, kd := range keys { fmt.Fprintf(&buf, "|%v|%v|%v|%v\n", kd.Name, kd.Type.Name, kd.IsComputed(), kd.IsProperty()) } return buf.Bytes() } |
Changes to box/compbox/log.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package compbox import ( "bytes" | | | | | | | 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 | //----------------------------------------------------------------------------- package compbox import ( "bytes" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func genLogM(zid id.Zid) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Log") m.Set(api.KeySyntax, meta.SyntaxText) m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string)) m.Set(api.KeyModified, kernel.Main.GetLastLogTime().Local().Format(id.ZidLayout)) return m } func genLogC(*meta.Meta) []byte { const tsFormat = "2006-01-02 15:04:05.999999" entries := kernel.Main.RetrieveLogEntries() var buf bytes.Buffer |
︙ | ︙ |
Changes to box/compbox/manager.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | package compbox import ( "bytes" "fmt" | | | | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package compbox import ( "bytes" "fmt" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func genManagerM(zid id.Zid) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Box Manager") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string)) return m |
︙ | ︙ |
Changes to box/compbox/parser.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 | import ( "bytes" "fmt" "sort" "strings" | | | | | | | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import ( "bytes" "fmt" "sort" "strings" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" ) func genParserM(zid id.Zid) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, "Zettelstore Supported Parser") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVTime).(string)) m.Set(api.KeyVisibility, api.ValueVisibilityLogin) |
︙ | ︙ |
Changes to box/compbox/version.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package compbox import ( | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package compbox import ( "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func getVersionMeta(zid id.Zid, title string) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, title) m.Set(api.KeyVisibility, api.ValueVisibilityExpert) return m |
︙ | ︙ |
Changes to box/constbox/base.css.
︙ | ︙ | |||
79 80 81 82 83 84 85 | h1 { font-size:1.5rem; margin:.65rem 0 } h2 { font-size:1.25rem; margin:.70rem 0 } h3 { font-size:1.15rem; margin:.75rem 0 } h4 { font-size:1.05rem; margin:.8rem 0; font-weight: bold } h5 { font-size:1.05rem; margin:.8rem 0 } h6 { font-size:1.05rem; margin:.8rem 0; font-weight: lighter } p { margin: .5rem 0 0 0 } | < | 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | h1 { font-size:1.5rem; margin:.65rem 0 } h2 { font-size:1.25rem; margin:.70rem 0 } h3 { font-size:1.15rem; margin:.75rem 0 } h4 { font-size:1.05rem; margin:.8rem 0; font-weight: bold } h5 { font-size:1.05rem; margin:.8rem 0 } h6 { font-size:1.05rem; margin:.8rem 0; font-weight: lighter } p { margin: .5rem 0 0 0 } li,figure,figcaption,dl { margin: 0 } dt { margin: .5rem 0 0 0 } dt+dd { margin-top: 0 } dd { margin: .5rem 0 0 2rem } dd > p:first-child { margin: 0 0 0 0 } blockquote { border-left: 0.5rem solid lightgray; |
︙ | ︙ |
Added box/constbox/base.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | <!DOCTYPE html> <html{{#Lang}} lang="{{Lang}}"{{/Lang}}> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="generator" content="Zettelstore"> <meta name="format-detection" content="telephone=no"> {{{MetaHeader}}} <link rel="stylesheet" href="{{{CSSBaseURL}}}"> <link rel="stylesheet" href="{{{CSSUserURL}}}"> {{#CSSRoleURL}}<link rel="stylesheet" href="{{{CSSRoleURL}}}">{{/CSSRoleURL}} <title>{{Title}}</title> </head> <body> <nav class="zs-menu"> <a href="{{{HomeURL}}}">Home</a> {{#WithUser}} <div class="zs-dropdown"> <button>User</button> <nav class="zs-dropdown-content"> {{#WithAuth}} {{#UserIsValid}} <a href="{{{UserZettelURL}}}">{{UserIdent}}</a> {{/UserIsValid}} {{^UserIsValid}} <a href="{{{LoginURL}}}">Login</a> {{/UserIsValid}} {{#UserIsValid}} <a href="{{{LogoutURL}}}">Logout</a> {{/UserIsValid}} {{/WithAuth}} </nav> </div> {{/WithUser}} <div class="zs-dropdown"> <button>Lists</button> <nav class="zs-dropdown-content"> <a href="{{{ListZettelURL}}}">List Zettel</a> <a href="{{{ListRolesURL}}}">List Roles</a> <a href="{{{ListTagsURL}}}">List Tags</a> {{#CanRefresh}} <a href="{{{RefreshURL}}}">Refresh</a> {{/CanRefresh}} </nav> </div> {{#NewZettelLinks.Has}} <div class="zs-dropdown"> <button>New</button> <nav class="zs-dropdown-content"> {{#NewZettelLinks.Links}} <a href="{{{URL}}}">{{Text}}</a> {{/NewZettelLinks.Links}} </nav> </div> {{/NewZettelLinks.Has}} <form action="{{{SearchURL}}}"> <input type="text" placeholder="Search.." name="{{QueryKeyQuery}}"> </form> </nav> <main class="content"> {{{Content}}} </main> {{#FooterHTML}}<footer><hr>{{{FooterHTML}}}</footer>{{/FooterHTML}} {{#DebugMode}}<div><b>WARNING: Debug mode is enabled. DO NOT USE IN PRODUCTION!</b></div>{{/DebugMode}} </body> </html> |
Deleted box/constbox/base.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/constbox/constbox.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 | package constbox import ( "context" _ "embed" // Allow to embed file content "net/url" | | > > > < < < | > > > > > > > | | < | | | | > > > > | | 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 | package constbox import ( "context" _ "embed" // Allow to embed file content "net/url" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" ) func init() { manager.Register( " const", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return &constBox{ log: kernel.Main.GetLogger(kernel.BoxService).Clone(). Str("box", "const").Int("boxnum", int64(cdata.Number)).Child(), number: cdata.Number, zettel: constZettelMap, enricher: cdata.Enricher, }, nil }) } type constHeader map[string]string type constZettel struct { header constHeader content domain.Content } type constBox struct { log *logger.Logger number int zettel map[id.Zid]constZettel enricher box.Enricher } func (*constBox) Location() string { return "const:" } func (*constBox) CanCreateZettel(context.Context) bool { return false } func (cb *constBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { cb.log.Trace().Err(box.ErrReadOnly).Msg("CreateZettel") return id.Invalid, box.ErrReadOnly } func (cb *constBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { if z, ok := cb.zettel[zid]; ok { cb.log.Trace().Msg("GetZettel") return domain.Zettel{Meta: meta.NewWithData(zid, z.header), Content: z.content}, nil } cb.log.Trace().Err(box.ErrNotFound).Msg("GetZettel") return domain.Zettel{}, box.ErrNotFound } func (cb *constBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { if z, ok := cb.zettel[zid]; ok { cb.log.Trace().Msg("GetMeta") return meta.NewWithData(zid, z.header), nil } cb.log.Trace().Err(box.ErrNotFound).Msg("GetMeta") return nil, box.ErrNotFound } func (cb *constBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { cb.log.Trace().Int("entries", int64(len(cb.zettel))).Msg("ApplyZid") for zid := range cb.zettel { if constraint(zid) { handle(zid) |
︙ | ︙ | |||
89 90 91 92 93 94 95 96 97 98 99 100 101 | m := meta.NewWithData(zid, zettel.header) cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } return nil } func (cb *constBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { _, ok := cb.zettel[zid] return !ok } | > > > > > > > | > < < | > < < | | | | | | | | < | | < | | | < | | > > > > > > > > | | | < | | < | | < | | | < | | < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | > > > > > > > > > | < | | | | | < < | | < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | | | | | | > > > | | | | | | | | | | < < < < < < < < < < < < < < < < < < < < < | 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 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 | m := meta.NewWithData(zid, zettel.header) cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } return nil } func (*constBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false } func (cb *constBox) UpdateZettel(context.Context, domain.Zettel) error { cb.log.Trace().Err(box.ErrReadOnly).Msg("UpdateZettel") return box.ErrReadOnly } func (cb *constBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { _, ok := cb.zettel[zid] return !ok } func (cb *constBox) RenameZettel(_ context.Context, curZid, _ id.Zid) error { err := box.ErrNotFound if _, ok := cb.zettel[curZid]; ok { err = box.ErrReadOnly } cb.log.Trace().Err(err).Msg("RenameZettel") return err } func (*constBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (cb *constBox) DeleteZettel(_ context.Context, zid id.Zid) error { err := box.ErrNotFound if _, ok := cb.zettel[zid]; ok { err = box.ErrReadOnly } cb.log.Trace().Err(err).Msg("DeleteZettel") return err } func (cb *constBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = true st.Zettel = len(cb.zettel) cb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } var constZettelMap = map[id.Zid]constZettel{ id.ConfigurationZid: { constHeader{ api.KeyTitle: "Zettelstore Runtime Configuration", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxNone, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityOwner, }, domain.NewContent(nil)}, id.MustParse(api.ZidLicense): { constHeader{ api.KeyTitle: "Zettelstore License", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxText, api.KeyCreated: "20210504135842", api.KeyLang: api.ValueLangEN, api.KeyModified: "20220131153422", api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent(contentLicense)}, id.MustParse(api.ZidAuthors): { constHeader{ api.KeyTitle: "Zettelstore Contributors", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20210504135842", api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, }, domain.NewContent(contentContributors)}, id.MustParse(api.ZidDependencies): { constHeader{ api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, api.KeyCreated: "20210504135842", api.KeyModified: "20221013105100", }, domain.NewContent(contentDependencies)}, id.BaseTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Base HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20210504135842", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentBaseMustache)}, id.LoginTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Login Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentLoginMustache)}, id.ZettelTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentZettelMustache)}, id.InfoTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Info HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentInfoMustache)}, id.ContextTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Context HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20210218181140", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentContextMustache)}, id.FormTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentFormMustache)}, id.RenameTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Rename Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentRenameMustache)}, id.DeleteTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Delete HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentDeleteMustache)}, id.ListTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentListZettelMustache)}, id.ErrorTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Error HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxMustache, api.KeyCreated: "20210305133215", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(contentErrorMustache)}, id.MustParse(api.ZidBaseCSS): { constHeader{ api.KeyTitle: "Zettelstore Base CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxCSS, api.KeyCreated: "20200804111624", api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent(contentBaseCSS)}, id.MustParse(api.ZidUserCSS): { constHeader{ api.KeyTitle: "Zettelstore User CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxCSS, api.KeyCreated: "20210622110143", api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent([]byte("/* User-defined CSS */"))}, id.RoleCSSMapZid: { constHeader{ api.KeyTitle: "Zettelstore Role to CSS Map", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxNone, api.KeyCreated: "20220321183214", api.KeyVisibility: api.ValueVisibilityExpert, }, domain.NewContent(nil)}, id.EmojiZid: { constHeader{ api.KeyTitle: "Zettelstore Generic Emoji", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxGif, api.KeyReadOnly: api.ValueTrue, api.KeyCreated: "20210504175807", api.KeyVisibility: api.ValueVisibilityPublic, }, domain.NewContent(contentEmoji)}, id.TOCNewTemplateZid: { constHeader{ api.KeyTitle: "New Menu", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20210217161829", api.KeyVisibility: api.ValueVisibilityCreator, }, domain.NewContent(contentNewTOCZettel)}, id.MustParse(api.ZidTemplateNewZettel): { constHeader{ api.KeyTitle: "New Zettel", api.KeyRole: api.ValueRoleZettel, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20201028185209", api.KeyVisibility: api.ValueVisibilityCreator, }, domain.NewContent(nil)}, id.MustParse(api.ZidTemplateNewUser): { constHeader{ api.KeyTitle: "New User", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxNone, api.KeyCreated: "20201028185209", meta.NewPrefix + api.KeyCredential: "", meta.NewPrefix + api.KeyUserID: "", meta.NewPrefix + api.KeyUserRole: api.ValueUserRoleReader, api.KeyVisibility: api.ValueVisibilityOwner, }, domain.NewContent(nil)}, id.DefaultHomeZid: { constHeader{ api.KeyTitle: "Home", api.KeyRole: api.ValueRoleZettel, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20210210190757", }, domain.NewContent(contentHomeZettel)}, } //go:embed license.txt var contentLicense []byte //go:embed contributors.zettel var contentContributors []byte //go:embed dependencies.zettel var contentDependencies []byte //go:embed base.mustache var contentBaseMustache []byte //go:embed login.mustache var contentLoginMustache []byte //go:embed zettel.mustache var contentZettelMustache []byte //go:embed info.mustache var contentInfoMustache []byte //go:embed context.mustache var contentContextMustache []byte //go:embed form.mustache var contentFormMustache []byte //go:embed rename.mustache var contentRenameMustache []byte //go:embed delete.mustache var contentDeleteMustache []byte //go:embed listzettel.mustache var contentListZettelMustache []byte //go:embed error.mustache var contentErrorMustache []byte //go:embed base.css var contentBaseCSS []byte //go:embed emoji_spin.gif var contentEmoji []byte //go:embed newtoc.zettel var contentNewTOCZettel []byte //go:embed home.zettel var contentHomeZettel []byte |
Added box/constbox/context.mustache.
> > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | <article> <header> <h1>{{Title}}</h1> <div class="zs-meta"> <a href="{{{InfoURL}}}">Info</a> · <a href="?dir=backward">Backward</a> · <a href="?dir=both">Both</a> · <a href="?dir=forward">Forward</a> · Cost:{{#Costs}} <a href="{{{URL}}}">{{{Text}}}</a>{{/Costs}} </div> </header> {{{Content}}} </article> |
Added box/constbox/delete.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | <article> <header> <h1>Delete Zettel {{Zid}}</h1> </header> <p>Do you really want to delete this zettel?</p> {{#HasShadows}} <div class="zs-info"> <h2>Infomation</h2> <p>If you delete this zettel, the previoulsy shadowed zettel from overlayed box {{ShadowedBox}} becomes available.</p> </div> {{/HasShadows}} {{#Incoming.Has}} <div class="zs-warning"> <h2>Warning!</h2> <p>If you delete this zettel, incoming references from the following zettel will become invalid.</p> <ul> {{#Incoming.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/Incoming.Links}} </ul> </div> {{/Incoming.Has}} {{#HasUselessFiles}} <div class="zs-warning"> <h2>Warning!</h2> <p>Deleting this zettel will also delete the following files, so that they will not be interpreted as content for this zettel.</p> <ul> {{#UselessFiles}} <li>{{{.}}}</li> {{/UselessFiles}} </ul> </div> {{/HasUselessFiles}} <dl> {{#MetaPairs}} <dt>{{Key}}:</dt><dd>{{Value}}</dd> {{/MetaPairs}} </dl> <form method="POST"> <input class="zs-primary" type="submit" value="Delete"> </form> </article> |
Deleted box/constbox/delete.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/constbox/dependencies.zettel.
︙ | ︙ | |||
96 97 98 99 100 101 102 103 104 105 106 107 108 109 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` === yuin/goldmark ; URL & Source : [[https://github.com/yuin/goldmark]] ; License : MIT License ``` | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` === hoisie/mustache / cbroglie/mustache ; URL & Source : [[https://github.com/hoisie/mustache]] / [[https://github.com/cbroglie/mustache]] ; License : MIT License ; Remarks : cbroglie/mustache is a fork from hoisie/mustache (starting with commit [f9b4cbf]). cbroglie/mustache does not claim a copyright and includes just the license file from hoisie/mustache. cbroglie/mustache obviously continues with the original license. ``` Copyright (c) 2009 Michael Hoisie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` === pascaldekloe/jwt ; URL & Source : [[https://github.com/pascaldekloe/jwt]] ; License : [[CC0 1.0 Universal|https://creativecommons.org/publicdomain/zero/1.0/legalcode]] ``` To the extent possible under law, Pascal S. de Kloe has waived all copyright and related or neighboring rights to JWT. This work is published from The Netherlands. https://creativecommons.org/publicdomain/zero/1.0/legalcode ``` === yuin/goldmark ; URL & Source : [[https://github.com/yuin/goldmark]] ; License : MIT License ``` |
︙ | ︙ | |||
125 126 127 128 129 130 131 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` | < < < < < < < < < < < | 170 171 172 173 174 175 176 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` |
Added box/constbox/error.mustache.
> > > > > > | 1 2 3 4 5 6 | <article> <header> <h1>{{ErrorTitle}}</h1> </header> {{ErrorText}} </article> |
Deleted box/constbox/error.sxn.
|
| < < < < |
Added box/constbox/form.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | <article> <header> <h1>{{Heading}}</h1> </header> <form action="{{FormActionURL}}" method="POST" enctype="multipart/form-data"> <div> <label for="zs-title">Title <a title="Main heading of this zettel. You can use inline zettelmarkup.">ⓘ</a></label> <input class="zs-input" type="text" id="zs-title" name="title" placeholder="Title.." value="{{MetaTitle}}" autofocus> </div> <div> <div> <label for="zs-role">Role <a title="One word, without spaces, to set the main role of this zettel.">ⓘ</a></label> <input class="zs-input" type="text" id="zs-role" {{#HasRoleData}}list="zs-role-data"{{/HasRoleData}} name="role" placeholder="role.." value="{{MetaRole}}"> {{#HasRoleData}} <datalist id="zs-role-data"> {{#RoleData}} <option value="{{.}}"> {{/RoleData}} </datalist> {{/HasRoleData}} </div> <label for="zs-tags">Tags <a title="Tags must begin with an '#' sign. They are separated by spaces.">ⓘ</a></label> <input class="zs-input" type="text" id="zs-tags" name="tags" placeholder="#tag" value="{{MetaTags}}"> </div> <div> <label for="zs-meta">Metadata <a title="Other metadata for this zettel. Each line contains a key/value pair, separated by a colon ':'.">ⓘ</a></label> <textarea class="zs-input" id="zs-meta" name="meta" rows="4" placeholder="metakey: metavalue"> {{#MetaPairsRest}} {{Key}}: {{Value}} {{/MetaPairsRest}} </textarea> </div> <div> <label for="zs-syntax">Syntax <a title="Syntax of zettel content below, one word. Typically 'zmk' (for zettelmarkup).">ⓘ</a></label> <input class="zs-input" type="text" id="zs-syntax" {{#HasSyntaxData}}list="zs-syntax-data"{{/HasSyntaxData}} name="syntax" placeholder="syntax.." value="{{MetaSyntax}}"> {{#HasSyntaxData}} <datalist id="zs-syntax-data"> {{#SyntaxData}} <option value="{{.}}"> {{/SyntaxData}} </datalist> {{/HasSyntaxData}}</div> <div> {{#IsTextContent}} <label for="zs-content">Content <a title="Content for this zettel, according to above syntax.">ⓘ</a></label> <textarea class="zs-input zs-content" id="zs-content" name="content" rows="20" placeholder="Your content..">{{Content}}</textarea> {{/IsTextContent}} </div> <div> <input class="zs-primary" type="submit" value="Submit"> <input class="zs-secondary" type="submit" value="Save" formaction="?save"> <input class="zs-upload" type="file" id="zs-file" name="file"> </div> </form> </article> |
Deleted box/constbox/form.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added box/constbox/info.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | <article> <header> <h1>Information for Zettel {{Zid}}</h1> <a href="{{{WebURL}}}">Web</a> · <a href="{{{ContextURL}}}">Context</a> {{#CanWrite}} · <a href="{{{EditURL}}}">Edit</a>{{/CanWrite}} {{#CanCopy}} · <a href="{{{CopyURL}}}">Copy</a>{{/CanCopy}} {{#CanVersion}} · <a href="{{{VersionURL}}}">Version</a>{{/CanVersion}} {{#CanFolge}} · <a href="{{{FolgeURL}}}">Folge</a>{{/CanFolge}} {{#CanRename}}· <a href="{{{RenameURL}}}">Rename</a>{{/CanRename}} {{#CanDelete}}· <a href="{{{DeleteURL}}}">Delete</a>{{/CanDelete}} </header> <h2>Interpreted Metadata</h2> <table> {{#MetaData}}<tr><td>{{Key}}</td><td>{{{Value}}}</td></tr> {{/MetaData}}</table> <h2>References</h2> {{#HasLocLinks}} <h3>Local</h3> <ul> {{#LocLinks}} {{#Valid}}<li><a href="{{{Zid}}}">{{Zid}}</a></li>{{/Valid}} {{^Valid}}<li>{{Zid}}</li>{{/Valid}} {{/LocLinks}} </ul> {{/HasLocLinks}} {{#QueryLinks.Has}} <h3>Queries</h3> <ul> {{#QueryLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/QueryLinks.Links}} </ul> {{/QueryLinks.Has}} {{#HasExtLinks}} <h3>External</h3> <ul> {{#ExtLinks}} <li><a href="{{{.}}}"{{{ExtNewWindow}}}>{{.}}</a></li> {{/ExtLinks}} </ul> {{/HasExtLinks}} <h3>Unlinked</h3> {{{UnLinksContent}}} <form> <label for="phrase">Search Phrase</label> <input class="zs-input" type="text" id="phrase" name="{{QueryKeyPhrase}}" placeholder="Phrase.." value="{{UnLinksPhrase}}"> </form> <h2>Parts and encodings</h2> <table> {{#EvalMatrix}} <tr> <th>{{Header}}</th> {{#Elements}}<td><a href="{{{URL}}}">{{Text}}</a></td> {{/Elements}} </tr> {{/EvalMatrix}} </table> <h3>Parsed (not evaluated)</h3> <table> {{#ParseMatrix}} <tr> <th>{{Header}}</th> {{#Elements}}<td><a href="{{{URL}}}">{{Text}}</a></td> {{/Elements}} </tr> {{/ParseMatrix}} </table> {{#HasShadowLinks}} <h2>Shadowed Boxes</h2> <ul>{{#ShadowLinks}}<li>{{.}}</li>{{/ShadowLinks}}</ul> {{/HasShadowLinks}} {{#Endnotes}}{{{Endnotes}}}{{/Endnotes}} </article> |
Deleted box/constbox/info.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added box/constbox/listzettel.mustache.
> > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <article> <header> <h1>{{Title}}</h1> </header> <form action="{{{SearchURL}}}"> <input class="zs-input" type="text" placeholder="Search.." name="{{QueryKeyQuery}}" value="{{QueryValue}}"> </form> {{{Content}}} {{#CanCreate}}<form action="{{{CreateURL}}}"> <input type="hidden" name="{{QueryKeyQuery}}" value="{{QueryValue}}"> <input type="hidden" name="{{QueryKeySeed}}" value="{{Seed}}"> <input class="zs-primary" type="submit" value="Save As Zettel"> </form>{{/CanCreate}} </article> |
Deleted box/constbox/listzettel.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added box/constbox/login.mustache.
> > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <article> <header> <h1>{{Title}}</h1> </header> {{#Retry}} <div class="zs-indication zs-error">Wrong user name / password. Try again.</div> {{/Retry}} <form method="POST" action=""> <div> <label for="username">User name:</label> <input class="zs-input" type="text" id="username" name="username" placeholder="Your user name.." autofocus> </div> <div> <label for="password">Password:</label> <input class="zs-input" type="password" id="password" name="password" placeholder="Your password.."> </div> <div><input class="zs-primary" type="submit" value="Login"></div> </form> </article> |
Deleted box/constbox/login.sxn.
|
| < < < < < < < < < < < < < < |
Changes to box/constbox/newtoc.zettel.
1 2 3 | This zettel lists all zettel that should act as a template for new zettel. These zettel will be included in the ""New"" menu of the WebUI. * [[New Zettel|00000000090001]] | < < | 1 2 3 4 | This zettel lists all zettel that should act as a template for new zettel. These zettel will be included in the ""New"" menu of the WebUI. * [[New Zettel|00000000090001]] * [[New User|00000000090002]] |
Deleted box/constbox/prelude.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added box/constbox/rename.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | <article> <header> <h1>Rename Zettel {{Zid}}</h1> </header> <p>Do you really want to rename this zettel?</p> {{#Incoming.Has}} <div class="zs-warning"> <h2>Warning!</h2> <p>If you rename this zettel, incoming references from the following zettel will become invalid.</p> <ul> {{#Incoming.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/Incoming.Links}} </ul> </div> {{/Incoming.Has}} {{#HasUselessFiles}} <div class="zs-warning"> <h2>Warning!</h2> <p>Renaming this zettel will also delete the following files, so that they will not be interpreted as content for a zettel with identifier {{Zid}}.</p> <ul> {{#UselessFiles}} <li>{{{.}}}</li> {{/UselessFiles}} </ul> </div> {{/HasUselessFiles}} <form method="POST"> <div> <label for="newid">New zettel id</label> <input class="zs-input" type="text" id="newzid" name="newzid" placeholder="ZID.." value="{{Zid}}" autofocus> </div> <input type="hidden" id="curzid" name="curzid" value="{{Zid}}"> <div><input class="zs-primary" type="submit" value="Rename"></div> </form> <dl> {{#MetaPairs}} <dt>{{Key}}:</dt><dd>{{Value}}</dd> {{/MetaPairs}} </dl> </article> |
Deleted box/constbox/rename.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted box/constbox/roleconfiguration.zettel.
|
| < < < < < < < < < < < < < < < < < < < < |
Deleted box/constbox/rolerole.zettel.
|
| < < < < < < < < < < |
Deleted box/constbox/roletag.zettel.
|
| < < < < < < |
Deleted box/constbox/rolezettel.zettel.
|
| < < < < < < < |
Deleted box/constbox/start.sxn.
|
| < < < < < < < < < < < < < < |
Deleted box/constbox/wuicode.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added box/constbox/zettel.mustache.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | <article> <header> <h1>{{Heading}}</h1> <div class="zs-meta"> {{#CanWrite}}<a href="{{{EditURL}}}">Edit</a> ·{{/CanWrite}} {{Zid}} · <a href="{{{InfoURL}}}">Info</a> · (<a href="{{{RoleURL}}}">{{RoleText}}</a>) {{#Tags.Has}}· {{#Tags.Links}} <a href="{{{URL}}}">{{Text}}</a>{{/Tags.Links}}{{/Tags.Has}} {{#CanCopy}}· <a href="{{{CopyURL}}}">Copy</a>{{/CanCopy}} {{#CanVersion}}· <a href="{{{VersionURL}}}">Version</a>{{/CanVersion}} {{#CanFolge}}· <a href="{{{FolgeURL}}}">Folge</a>{{/CanFolge}} {{#PredecessorRefs}}<br>Predecessor: {{{PredecessorRefs}}}{{/PredecessorRefs}} {{#PrecursorRefs}}<br>Precursor: {{{PrecursorRefs}}}{{/PrecursorRefs}} {{#HasExtURL}}<br>URL: {{{ExtURL}}}{{/HasExtURL}} {{#Author}}<br>By {{Author}}{{/Author}} </div> </header> {{{Content}}} </article> {{#NeedBottomNav}}<nav>{{/NeedBottomNav}} {{#FolgeLinks.Has}} <details open> <summary>Folgezettel</summary> <ul> {{#FolgeLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/FolgeLinks.Links}} </ul> </details> {{/FolgeLinks.Has}} {{#BackLinks.Has}} <details open> <summary>Incoming</summary> <ul> {{#BackLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/BackLinks.Links}} </ul> </details> {{/BackLinks.Has}} {{#SuccessorLinks.Has}} <details open> <summary>Successors</summary> <ul> {{#SuccessorLinks.Links}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/SuccessorLinks.Links}} </ul> </details> {{/SuccessorLinks.Has}} {{#NeedBottomNav}}</nav>{{/NeedBottomNav}} |
Deleted box/constbox/zettel.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/dirbox/dirbox.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 25 26 27 | "os" "path/filepath" "sync" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/box/notify" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" | > > > < < < | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | "os" "path/filepath" "sync" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/box/notify" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" ) func init() { manager.Register("dir", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { var log *logger.Logger if krnl := kernel.Main; krnl != nil { log = krnl.GetLogger(kernel.BoxService).Clone().Str("box", "dir").Int("boxnum", int64(cdata.Number)).Child() |
︙ | ︙ | |||
219 220 221 222 223 224 225 | return dp.fCmds[sum%dp.fSrvs] } func (dp *dirBox) CanCreateZettel(_ context.Context) bool { return !dp.readonly } | | | 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | return dp.fCmds[sum%dp.fSrvs] } func (dp *dirBox) CanCreateZettel(_ context.Context) bool { return !dp.readonly } func (dp *dirBox) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { if dp.readonly { return id.Invalid, box.ErrReadOnly } newZid, err := dp.dirSrv.SetNewDirEntry() if err != nil { return id.Invalid, err |
︙ | ︙ | |||
242 243 244 245 246 247 248 | err = dp.dirSrv.UpdateDirEntry(&entry) } dp.notifyChanged(meta.Zid) dp.log.Trace().Err(err).Zid(meta.Zid).Msg("CreateZettel") return meta.Zid, err } | | | | | | > > > > > | > > > > > > > > | 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | err = dp.dirSrv.UpdateDirEntry(&entry) } dp.notifyChanged(meta.Zid) dp.log.Trace().Err(err).Zid(meta.Zid).Msg("CreateZettel") return meta.Zid, err } func (dp *dirBox) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { entry := dp.dirSrv.GetDirEntry(zid) if !entry.IsValid() { return domain.Zettel{}, box.ErrNotFound } m, c, err := dp.srvGetMetaContent(ctx, entry, zid) if err != nil { return domain.Zettel{}, err } zettel := domain.Zettel{Meta: m, Content: domain.NewContent(c)} dp.log.Trace().Zid(zid).Msg("GetZettel") return zettel, nil } func (dp *dirBox) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { m, err := dp.doGetMeta(ctx, zid) dp.log.Trace().Zid(zid).Err(err).Msg("GetMeta") return m, err } func (dp *dirBox) doGetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { entry := dp.dirSrv.GetDirEntry(zid) if !entry.IsValid() { return nil, box.ErrNotFound } m, err := dp.srvGetMeta(ctx, entry, zid) if err != nil { return nil, err } return m, nil } func (dp *dirBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { entries := dp.dirSrv.GetDirEntries(constraint) dp.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyZid") for _, entry := range entries { handle(entry.Zid) |
︙ | ︙ | |||
286 287 288 289 290 291 292 | } dp.cdata.Enricher.Enrich(ctx, m, dp.number) handle(m) } return nil } | | | | | | | | | | 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 | } dp.cdata.Enricher.Enrich(ctx, m, dp.number) handle(m) } return nil } func (dp *dirBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return !dp.readonly } func (dp *dirBox) UpdateZettel(ctx context.Context, zettel domain.Zettel) error { if dp.readonly { return box.ErrReadOnly } meta := zettel.Meta zid := meta.Zid if !zid.IsValid() { return &box.ErrInvalidID{Zid: zid} } entry := dp.dirSrv.GetDirEntry(zid) if !entry.IsValid() { // Existing zettel, but new in this box. entry = ¬ify.DirEntry{Zid: zid} } dp.updateEntryFromMetaContent(entry, meta, zettel.Content) dp.dirSrv.UpdateDirEntry(entry) err := dp.srvSetZettel(ctx, entry, zettel) if err == nil { dp.notifyChanged(zid) } dp.log.Trace().Zid(zid).Err(err).Msg("UpdateZettel") return err } func (dp *dirBox) updateEntryFromMetaContent(entry *notify.DirEntry, m *meta.Meta, content domain.Content) { entry.SetupFromMetaContent(m, content, dp.cdata.Config.GetZettelFileSyntax) } func (dp *dirBox) AllowRenameZettel(context.Context, id.Zid) bool { return !dp.readonly } func (dp *dirBox) RenameZettel(ctx context.Context, curZid, newZid id.Zid) error { if curZid == newZid { return nil } curEntry := dp.dirSrv.GetDirEntry(curZid) if !curEntry.IsValid() { return box.ErrNotFound } if dp.readonly { return box.ErrReadOnly } // Check whether zettel with new ID already exists in this box. if _, err := dp.doGetMeta(ctx, newZid); err == nil { return &box.ErrInvalidID{Zid: newZid} } oldMeta, oldContent, err := dp.srvGetMetaContent(ctx, curEntry, curZid) if err != nil { return err } newEntry, err := dp.dirSrv.RenameDirEntry(curEntry, newZid) if err != nil { return err } oldMeta.Zid = newZid newZettel := domain.Zettel{Meta: oldMeta, Content: domain.NewContent(oldContent)} if err = dp.srvSetZettel(ctx, &newEntry, newZettel); err != nil { // "Rollback" rename. No error checking... dp.dirSrv.RenameDirEntry(&newEntry, curZid) return err } err = dp.srvDeleteZettel(ctx, curEntry, curZid) if err == nil { |
︙ | ︙ | |||
380 381 382 383 384 385 386 | func (dp *dirBox) DeleteZettel(ctx context.Context, zid id.Zid) error { if dp.readonly { return box.ErrReadOnly } entry := dp.dirSrv.GetDirEntry(zid) if !entry.IsValid() { | | | 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 | func (dp *dirBox) DeleteZettel(ctx context.Context, zid id.Zid) error { if dp.readonly { return box.ErrReadOnly } entry := dp.dirSrv.GetDirEntry(zid) if !entry.IsValid() { return box.ErrNotFound } err := dp.dirSrv.DeleteDirEntry(zid) if err != nil { return nil } err = dp.srvDeleteZettel(ctx, entry, zid) if err == nil { |
︙ | ︙ |
Changes to box/dirbox/service.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 | "io" "os" "path/filepath" "time" "zettelstore.de/z/box/filebox" "zettelstore.de/z/box/notify" "zettelstore.de/z/input" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" | > > > < < < | | | 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 | "io" "os" "path/filepath" "time" "zettelstore.de/z/box/filebox" "zettelstore.de/z/box/notify" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) func fileService(i uint32, log *logger.Logger, dirPath string, cmds <-chan fileCmd) { // Something may panic. Ensure a running service. defer func() { if r := recover(); r != nil { kernel.Main.LogRecover("FileService", r) go fileService(i, log, dirPath, cmds) } }() log.Trace().Uint("i", uint64(i)).Str("dirpath", dirPath).Msg("File service started") for cmd := range cmds { cmd.run(log, dirPath) |
︙ | ︙ | |||
164 165 166 167 168 169 170 | cmd.rc <- resGetMetaContent{m, content, err} } // COMMAND: srvSetZettel ---------------------------------------- // // Writes a new or exsting zettel. | | | | 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 | cmd.rc <- resGetMetaContent{m, content, err} } // COMMAND: srvSetZettel ---------------------------------------- // // Writes a new or exsting zettel. func (dp *dirBox) srvSetZettel(ctx context.Context, entry *notify.DirEntry, zettel domain.Zettel) error { rc := make(chan resSetZettel, 1) dp.getFileChan(zettel.Meta.Zid) <- &fileSetZettel{entry, zettel, rc} ctx, cancel := context.WithTimeout(ctx, serviceTimeout) defer cancel() select { case err := <-rc: return err case <-ctx.Done(): return ctx.Err() } } type fileSetZettel struct { entry *notify.DirEntry zettel domain.Zettel rc chan<- resSetZettel } type resSetZettel = error func (cmd *fileSetZettel) run(log *logger.Logger, dirPath string) { entry := cmd.entry zid := entry.Zid |
︙ | ︙ |
Changes to box/filebox/filebox.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | import ( "errors" "net/url" "path/filepath" "strings" | | | | | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import ( "errors" "net/url" "path/filepath" "strings" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" ) func init() { manager.Register("file", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { path := getFilepathFromURL(u) ext := strings.ToLower(filepath.Ext(path)) if ext != ".zip" { |
︙ | ︙ |
Changes to box/filebox/zipbox.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 | "archive/zip" "context" "io" "strings" "zettelstore.de/z/box" "zettelstore.de/z/box/notify" "zettelstore.de/z/input" "zettelstore.de/z/logger" "zettelstore.de/z/query" | > > > < < < | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "archive/zip" "context" "io" "strings" "zettelstore.de/z/box" "zettelstore.de/z/box/notify" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/logger" "zettelstore.de/z/query" ) type zipBox struct { log *logger.Logger number int name string enricher box.Enricher |
︙ | ︙ | |||
78 79 80 81 82 83 84 | } func (zb *zipBox) Stop(context.Context) { zb.dirSrv.Stop() zb.dirSrv = nil } | > > > > > > > > | | | | | | | | | > > > > > > > > > > > | 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 | } func (zb *zipBox) Stop(context.Context) { zb.dirSrv.Stop() zb.dirSrv = nil } func (*zipBox) CanCreateZettel(context.Context) bool { return false } func (zb *zipBox) CreateZettel(context.Context, domain.Zettel) (id.Zid, error) { err := box.ErrReadOnly zb.log.Trace().Err(err).Msg("CreateZettel") return id.Invalid, err } func (zb *zipBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { entry := zb.dirSrv.GetDirEntry(zid) if !entry.IsValid() { return domain.Zettel{}, box.ErrNotFound } reader, err := zip.OpenReader(zb.name) if err != nil { return domain.Zettel{}, err } defer reader.Close() var m *meta.Meta var src []byte var inMeta bool contentName := entry.ContentName if metaName := entry.MetaName; metaName == "" { if contentName == "" { zb.log.Panic().Zid(zid).Msg("No meta, no content in zipBox.GetZettel") } src, err = readZipFileContent(reader, entry.ContentName) if err != nil { return domain.Zettel{}, err } if entry.HasMetaInContent() { inp := input.NewInput(src) m = meta.NewFromInput(zid, inp) src = src[inp.Pos:] } else { m = CalcDefaultMeta(zid, entry.ContentExt) } } else { m, err = readZipMetaFile(reader, zid, metaName) if err != nil { return domain.Zettel{}, err } inMeta = true if contentName != "" { src, err = readZipFileContent(reader, entry.ContentName) if err != nil { return domain.Zettel{}, err } } } CleanupMeta(m, zid, entry.ContentExt, inMeta, entry.UselessFiles) zb.log.Trace().Zid(zid).Msg("GetZettel") return domain.Zettel{Meta: m, Content: domain.NewContent(src)}, nil } func (zb *zipBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { entry := zb.dirSrv.GetDirEntry(zid) if !entry.IsValid() { return nil, box.ErrNotFound } reader, err := zip.OpenReader(zb.name) if err != nil { return nil, err } defer reader.Close() m, err := zb.readZipMeta(reader, zid, entry) zb.log.Trace().Err(err).Zid(zid).Msg("GetMeta") return m, err } func (zb *zipBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { entries := zb.dirSrv.GetDirEntries(constraint) zb.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyZid") for _, entry := range entries { handle(entry.Zid) |
︙ | ︙ | |||
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | continue } zb.enricher.Enrich(ctx, m, zb.number) handle(m) } return nil } func (zb *zipBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { entry := zb.dirSrv.GetDirEntry(zid) return !entry.IsValid() } func (zb *zipBox) RenameZettel(_ context.Context, curZid, newZid id.Zid) error { err := box.ErrReadOnly if curZid == newZid { err = nil } curEntry := zb.dirSrv.GetDirEntry(curZid) if !curEntry.IsValid() { | > > > > > > > > | | | 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 | continue } zb.enricher.Enrich(ctx, m, zb.number) handle(m) } return nil } func (*zipBox) CanUpdateZettel(context.Context, domain.Zettel) bool { return false } func (zb *zipBox) UpdateZettel(context.Context, domain.Zettel) error { err := box.ErrReadOnly zb.log.Trace().Err(err).Msg("UpdateZettel") return err } func (zb *zipBox) AllowRenameZettel(_ context.Context, zid id.Zid) bool { entry := zb.dirSrv.GetDirEntry(zid) return !entry.IsValid() } func (zb *zipBox) RenameZettel(_ context.Context, curZid, newZid id.Zid) error { err := box.ErrReadOnly if curZid == newZid { err = nil } curEntry := zb.dirSrv.GetDirEntry(curZid) if !curEntry.IsValid() { err = box.ErrNotFound } zb.log.Trace().Err(err).Msg("RenameZettel") return err } func (*zipBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (zb *zipBox) DeleteZettel(_ context.Context, zid id.Zid) error { err := box.ErrReadOnly entry := zb.dirSrv.GetDirEntry(zid) if !entry.IsValid() { err = box.ErrNotFound } zb.log.Trace().Err(err).Msg("DeleteZettel") return err } func (zb *zipBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = true |
︙ | ︙ |
Changes to box/helper.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | package box import ( "net/url" "strconv" "time" | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package box import ( "net/url" "strconv" "time" "zettelstore.de/z/domain/id" ) // GetNewZid calculates a new and unused zettel identifier, based on the current date and time. func GetNewZid(testZid func(id.Zid) (bool, error)) (id.Zid, error) { withSeconds := false for i := 0; i < 90; i++ { // Must be completed within 9 seconds (less than web/server.writeTimeout) zid := id.New(withSeconds) |
︙ | ︙ |
Changes to box/manager/anteroom.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package manager import ( "sync" | | > | > | | | | > > > > > | | | | | | | > | > > | | | > | | < | | | > > | | | | | | | | | | | | | < | | | 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 | //----------------------------------------------------------------------------- package manager import ( "sync" "zettelstore.de/z/domain/id" ) type arAction int const ( arNothing arAction = iota arReload arZettel ) type anteroom struct { num uint64 next *anteroom waiting id.Set curLoad int reload bool } type anterooms struct { mx sync.Mutex nextNum uint64 first *anteroom last *anteroom maxLoad int } func newAnterooms(maxLoad int) *anterooms { return &anterooms{maxLoad: maxLoad} } func (ar *anterooms) EnqueueZettel(zid id.Zid) { if !zid.IsValid() { return } ar.mx.Lock() defer ar.mx.Unlock() if ar.first == nil { ar.first = ar.makeAnteroom(zid) ar.last = ar.first return } for room := ar.first; room != nil; room = room.next { if room.reload { continue // Do not put zettel in reload room } if _, ok := room.waiting[zid]; ok { // Zettel is already waiting. Nothing to do. return } } if room := ar.last; !room.reload && (ar.maxLoad == 0 || room.curLoad < ar.maxLoad) { room.waiting.Zid(zid) room.curLoad++ return } room := ar.makeAnteroom(zid) ar.last.next = room ar.last = room } func (ar *anterooms) makeAnteroom(zid id.Zid) *anteroom { ar.nextNum++ if zid == id.Invalid { return &anteroom{num: ar.nextNum, next: nil, waiting: nil, curLoad: 0, reload: true} } c := ar.maxLoad if c == 0 { c = 100 } waiting := id.NewSetCap(ar.maxLoad, zid) return &anteroom{num: ar.nextNum, next: nil, waiting: waiting, curLoad: 1, reload: false} } func (ar *anterooms) Reset() { ar.mx.Lock() defer ar.mx.Unlock() ar.first = ar.makeAnteroom(id.Invalid) ar.last = ar.first } func (ar *anterooms) Reload(newZids id.Set) uint64 { ar.mx.Lock() defer ar.mx.Unlock() ar.deleteReloadedRooms() if ns := len(newZids); ns > 0 { ar.nextNum++ ar.first = &anteroom{num: ar.nextNum, next: ar.first, waiting: newZids, curLoad: ns, reload: true} if ar.first.next == nil { ar.last = ar.first } return ar.nextNum } ar.first = nil ar.last = nil return 0 } func (ar *anterooms) deleteReloadedRooms() { room := ar.first for room != nil && room.reload { room = room.next } ar.first = room if room == nil { ar.last = nil } } func (ar *anterooms) Dequeue() (arAction, id.Zid, uint64) { ar.mx.Lock() defer ar.mx.Unlock() if ar.first == nil { return arNothing, id.Invalid, 0 } roomNo := ar.first.num if ar.first.waiting == nil { ar.removeFirst() return arReload, id.Invalid, roomNo } for zid := range ar.first.waiting { delete(ar.first.waiting, zid) if len(ar.first.waiting) == 0 { ar.removeFirst() } return arZettel, zid, roomNo } ar.removeFirst() return arNothing, id.Invalid, 0 } func (ar *anterooms) removeFirst() { ar.first = ar.first.next if ar.first == nil { ar.last = nil } } |
Changes to box/manager/anteroom_test.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package manager import ( "testing" | | | | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | //----------------------------------------------------------------------------- package manager import ( "testing" "zettelstore.de/z/domain/id" ) func TestSimple(t *testing.T) { t.Parallel() ar := newAnterooms(2) ar.EnqueueZettel(id.Zid(1)) action, zid, rno := ar.Dequeue() if zid != id.Zid(1) || action != arZettel || rno != 1 { t.Errorf("Expected arZettel/1/1, but got %v/%v/%v", action, zid, rno) } _, zid, _ = ar.Dequeue() if zid != id.Invalid { t.Errorf("Expected invalid Zid, but got %v", zid) } ar.EnqueueZettel(id.Zid(1)) ar.EnqueueZettel(id.Zid(2)) |
︙ | ︙ | |||
48 49 50 51 52 53 54 | if count != 3 { t.Errorf("Expected 3 dequeues, but got %v", count) } } func TestReset(t *testing.T) { t.Parallel() | | | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | if count != 3 { t.Errorf("Expected 3 dequeues, but got %v", count) } } func TestReset(t *testing.T) { t.Parallel() ar := newAnterooms(1) ar.EnqueueZettel(id.Zid(1)) ar.Reset() action, zid, _ := ar.Dequeue() if action != arReload || zid != id.Invalid { t.Errorf("Expected reload & invalid Zid, but got %v/%v", action, zid) } ar.Reload(id.NewSet(3, 4)) |
︙ | ︙ | |||
81 82 83 84 85 86 87 | t.Errorf("Expected 5/arZettel, but got %v/%v", zid, action) } action, zid, _ = ar.Dequeue() if action != arNothing || zid != id.Invalid { t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) } | | | | 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 | t.Errorf("Expected 5/arZettel, but got %v/%v", zid, action) } action, zid, _ = ar.Dequeue() if action != arNothing || zid != id.Invalid { t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) } ar = newAnterooms(1) ar.Reload(id.NewSet(id.Zid(6))) action, zid, _ = ar.Dequeue() if zid != id.Zid(6) || action != arZettel { t.Errorf("Expected 6/arZettel, but got %v/%v", zid, action) } action, zid, _ = ar.Dequeue() if action != arNothing || zid != id.Invalid { t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) } ar = newAnterooms(1) ar.EnqueueZettel(id.Zid(8)) ar.Reload(nil) action, zid, _ = ar.Dequeue() if action != arNothing || zid != id.Invalid { t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) } } |
Changes to box/manager/box.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 | import ( "context" "errors" "strings" "zettelstore.de/z/box" | | | | | | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import ( "context" "errors" "strings" "zettelstore.de/z/box" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/query" ) // Conatains all box.Box related functions // Location returns some information where the box is located. func (mgr *Manager) Location() string { if len(mgr.boxes) <= 2 { |
︙ | ︙ | |||
37 38 39 40 41 42 43 | sb.WriteString(mgr.boxes[i].Location()) } return sb.String() } // CanCreateZettel returns true, if box could possibly create a new zettel. func (mgr *Manager) CanCreateZettel(ctx context.Context) bool { | < < < < | < < | < < | < < < < < < | | < | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < < < < < < < < < < < < < | < < < < | < < < < < < < < < | | < < < < | | | | | | < < | < < < < | < < < | | | | > | < < | | 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 | sb.WriteString(mgr.boxes[i].Location()) } return sb.String() } // CanCreateZettel returns true, if box could possibly create a new zettel. func (mgr *Manager) CanCreateZettel(ctx context.Context) bool { mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() return mgr.State() == box.StartStateStarted && mgr.boxes[0].CanCreateZettel(ctx) } // CreateZettel creates a new zettel. func (mgr *Manager) CreateZettel(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { mgr.mgrLog.Debug().Msg("CreateZettel") if mgr.State() != box.StartStateStarted { return id.Invalid, box.ErrStopped } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() return mgr.boxes[0].CreateZettel(ctx, zettel) } // GetZettel retrieves a specific zettel. func (mgr *Manager) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetZettel") if mgr.State() != box.StartStateStarted { return domain.Zettel{}, box.ErrStopped } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for i, p := range mgr.boxes { if z, err := p.GetZettel(ctx, zid); err != box.ErrNotFound { if err == nil { mgr.Enrich(ctx, z.Meta, i+1) } return z, err } } return domain.Zettel{}, box.ErrNotFound } // GetAllZettel retrieves a specific zettel from all managed boxes. func (mgr *Manager) GetAllZettel(ctx context.Context, zid id.Zid) ([]domain.Zettel, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetAllZettel") if mgr.State() != box.StartStateStarted { return nil, box.ErrStopped } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() var result []domain.Zettel for i, p := range mgr.boxes { if z, err := p.GetZettel(ctx, zid); err == nil { mgr.Enrich(ctx, z.Meta, i+1) result = append(result, z) } } return result, nil } // GetMeta retrieves just the meta data of a specific zettel. func (mgr *Manager) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetMeta") if mgr.State() != box.StartStateStarted { return nil, box.ErrStopped } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() return mgr.doGetMeta(ctx, zid) } func (mgr *Manager) doGetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { for i, p := range mgr.boxes { if m, err := p.GetMeta(ctx, zid); err != box.ErrNotFound { if err == nil { mgr.Enrich(ctx, m, i+1) } return m, err } } return nil, box.ErrNotFound } // GetAllMeta retrieves the meta data of a specific zettel from all managed boxes. func (mgr *Manager) GetAllMeta(ctx context.Context, zid id.Zid) ([]*meta.Meta, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetAllMeta") if mgr.State() != box.StartStateStarted { return nil, box.ErrStopped } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() var result []*meta.Meta for i, p := range mgr.boxes { if m, err := p.GetMeta(ctx, zid); err == nil { mgr.Enrich(ctx, m, i+1) result = append(result, m) } } return result, nil } // FetchZids returns the set of all zettel identifer managed by the box. func (mgr *Manager) FetchZids(ctx context.Context) (id.Set, error) { mgr.mgrLog.Debug().Msg("FetchZids") if mgr.State() != box.StartStateStarted { return nil, box.ErrStopped } result := id.Set{} mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for _, p := range mgr.boxes { err := p.ApplyZid(ctx, func(zid id.Zid) { result.Zid(zid) }, func(id.Zid) bool { return true }) if err != nil { return nil, err } } return result, nil } type metaMap map[id.Zid]*meta.Meta // SelectMeta returns all zettel meta data that match the selection // criteria. The result is ordered by descending zettel id. func (mgr *Manager) SelectMeta(ctx context.Context, q *query.Query) ([]*meta.Meta, error) { if msg := mgr.mgrLog.Debug(); msg.Enabled() { msg.Str("query", q.String()).Msg("SelectMeta") } if mgr.State() != box.StartStateStarted { return nil, box.ErrStopped } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() compSearch := q.RetrieveAndCompile(mgr) selected := metaMap{} for _, term := range compSearch.Terms { rejected := id.Set{} handleMeta := func(m *meta.Meta) { zid := m.Zid if rejected.Contains(zid) { mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/alreadyRejected") return } if _, ok := selected[zid]; ok { mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/alreadySelected") return } if compSearch.PreMatch(m) && term.Match(m) { selected[zid] = m mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/match") } else { rejected.Zid(zid) mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/reject") } } for _, p := range mgr.boxes { if err := p.ApplyMeta(ctx, handleMeta, term.Retrieve); err != nil { return nil, err } } } result := make([]*meta.Meta, 0, len(selected)) for _, m := range selected { result = append(result, m) } return q.AfterSearch(result), nil } // CanUpdateZettel returns true, if box could possibly update the given zettel. func (mgr *Manager) CanUpdateZettel(ctx context.Context, zettel domain.Zettel) bool { mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() return mgr.State() == box.StartStateStarted && mgr.boxes[0].CanUpdateZettel(ctx, zettel) } // UpdateZettel updates an existing zettel. func (mgr *Manager) UpdateZettel(ctx context.Context, zettel domain.Zettel) error { mgr.mgrLog.Debug().Zid(zettel.Meta.Zid).Msg("UpdateZettel") if mgr.State() != box.StartStateStarted { return box.ErrStopped } // Remove all (computed) properties from metadata before storing the zettel. zettel.Meta = zettel.Meta.Clone() for _, p := range zettel.Meta.ComputedPairsRest() { if mgr.propertyKeys.Has(p.Key) { zettel.Meta.Delete(p.Key) } } return mgr.boxes[0].UpdateZettel(ctx, zettel) } // AllowRenameZettel returns true, if box will not disallow renaming the zettel. func (mgr *Manager) AllowRenameZettel(ctx context.Context, zid id.Zid) bool { if mgr.State() != box.StartStateStarted { return false } |
︙ | ︙ | |||
261 262 263 264 265 266 267 | if mgr.State() != box.StartStateStarted { return box.ErrStopped } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for i, p := range mgr.boxes { err := p.RenameZettel(ctx, curZid, newZid) | < | < | 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | if mgr.State() != box.StartStateStarted { return box.ErrStopped } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for i, p := range mgr.boxes { err := p.RenameZettel(ctx, curZid, newZid) if err != nil && !errors.Is(err, box.ErrNotFound) { for j := 0; j < i; j++ { mgr.boxes[j].RenameZettel(ctx, newZid, curZid) } return err } } return nil } // CanDeleteZettel returns true, if box could possibly delete the given zettel. func (mgr *Manager) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { if mgr.State() != box.StartStateStarted { return false |
︙ | ︙ | |||
299 300 301 302 303 304 305 | return box.ErrStopped } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for _, p := range mgr.boxes { err := p.DeleteZettel(ctx, zid) if err == nil { | < < | | < < < < < < < < < < < | 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | return box.ErrStopped } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for _, p := range mgr.boxes { err := p.DeleteZettel(ctx, zid) if err == nil { return nil } if !errors.Is(err, box.ErrNotFound) && !errors.Is(err, box.ErrReadOnly) { return err } } return box.ErrNotFound } |
Changes to box/manager/collect.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | package manager import ( "strings" "zettelstore.de/z/ast" "zettelstore.de/z/box/manager/store" | | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package manager import ( "strings" "zettelstore.de/z/ast" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/domain/id" "zettelstore.de/z/strfun" ) type collectData struct { refs id.Set words store.WordSet urls store.WordSet } |
︙ | ︙ | |||
72 73 74 75 76 77 78 | if ref.IsExternal() { data.urls.Add(strings.ToLower(ref.Value)) } if !ref.IsZettel() { return } if zid, err := id.Parse(ref.URL.Path); err == nil { | | | 72 73 74 75 76 77 78 79 80 81 | if ref.IsExternal() { data.urls.Add(strings.ToLower(ref.Value)) } if !ref.IsZettel() { return } if zid, err := id.Parse(ref.URL.Path); err == nil { data.refs.Zid(zid) } } |
Changes to box/manager/enrich.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | package manager import ( "context" "strconv" | | | | < | < | 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 | package manager import ( "context" "strconv" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // Enrich computes additional properties and updates the given metadata. func (mgr *Manager) Enrich(ctx context.Context, m *meta.Meta, boxNumber int) { // Calculate computed, but stored values. if _, ok := m.Get(api.KeyCreated); !ok { m.Set(api.KeyCreated, computeCreated(m.Zid)) } if box.DoNotEnrich(ctx) { // Enrich is called indirectly via indexer or enrichment is not requested // because of other reasons -> ignore this call, do not update metadata return } computePublished(m) m.Set(api.KeyBoxNumber, strconv.Itoa(boxNumber)) mgr.idxStore.Enrich(ctx, m) } func computeCreated(zid id.Zid) string { if zid <= 10101000000 { // A year 0000 is not allowed and therefore an artificaial Zid. // In the year 0001, the month must be > 0. |
︙ | ︙ |
Changes to box/manager/indexer.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 | "context" "fmt" "net/url" "time" "zettelstore.de/z/box" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" "zettelstore.de/z/strfun" | > > > < < < | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "context" "fmt" "net/url" "time" "zettelstore.de/z/box" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" "zettelstore.de/z/strfun" ) // SearchEqual returns all zettel that contains the given exact word. // The word must be normalized through Unicode NKFD, trimmed and not empty. func (mgr *Manager) SearchEqual(word string) id.Set { found := mgr.idxStore.SearchEqual(word) mgr.idxLog.Debug().Str("word", word).Int("found", int64(len(found))).Msg("SearchEqual") |
︙ | ︙ | |||
71 72 73 74 75 76 77 | } // idxIndexer runs in the background and updates the index data structures. // This is the main service of the idxIndexer. func (mgr *Manager) idxIndexer() { // Something may panic. Ensure a running indexer. defer func() { | | | > | > | > > > > > | < | > | 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 | } // idxIndexer runs in the background and updates the index data structures. // This is the main service of the idxIndexer. func (mgr *Manager) idxIndexer() { // Something may panic. Ensure a running indexer. defer func() { if r := recover(); r != nil { kernel.Main.LogRecover("Indexer", r) go mgr.idxIndexer() } }() timerDuration := 15 * time.Second timer := time.NewTimer(timerDuration) ctx := box.NoEnrichContext(context.Background()) for { mgr.idxWorkService(ctx) if !mgr.idxSleepService(timer, timerDuration) { return } } } func (mgr *Manager) idxWorkService(ctx context.Context) { var roomNum uint64 var start time.Time for { switch action, zid, arRoomNum := mgr.idxAr.Dequeue(); action { case arNothing: return case arReload: mgr.idxLog.Debug().Msg("reload") roomNum = 0 zids, err := mgr.FetchZids(ctx) if err == nil { start = time.Now() if rno := mgr.idxAr.Reload(zids); rno > 0 { roomNum = rno } mgr.idxMx.Lock() mgr.idxLastReload = time.Now().Local() mgr.idxSinceReload = 0 mgr.idxMx.Unlock() } case arZettel: mgr.idxLog.Debug().Zid(zid).Msg("zettel") zettel, err := mgr.GetZettel(ctx, zid) if err != nil { // Zettel was deleted or is not accessible b/c of other reasons mgr.idxLog.Trace().Zid(zid).Msg("delete") mgr.idxMx.Lock() mgr.idxSinceReload++ mgr.idxMx.Unlock() mgr.idxDeleteZettel(zid) continue } mgr.idxLog.Trace().Zid(zid).Msg("update") mgr.idxMx.Lock() if arRoomNum == roomNum { mgr.idxDurReload = time.Since(start) } mgr.idxSinceReload++ mgr.idxMx.Unlock() mgr.idxUpdateZettel(ctx, zettel) } } } func (mgr *Manager) idxSleepService(timer *time.Timer, timerDuration time.Duration) bool { select { case _, ok := <-mgr.idxReady: |
︙ | ︙ | |||
146 147 148 149 150 151 152 | <-timer.C } return false } return true } | | | | 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | <-timer.C } return false } return true } func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel domain.Zettel) { var cData collectData cData.initialize() collectZettelIndexData(parser.ParseZettel(ctx, zettel, "", mgr.rtConfig), &cData) m := zettel.Meta zi := store.NewZettelIndex(m.Zid) mgr.idxCollectFromMeta(ctx, m, zi, &cData) mgr.idxProcessData(ctx, zi, &cData) toCheck := mgr.idxStore.UpdateReferences(ctx, zi) mgr.idxCheckZettel(toCheck) } func (mgr *Manager) idxCollectFromMeta(ctx context.Context, m *meta.Meta, zi *store.ZettelIndex, cData *collectData) { |
︙ | ︙ | |||
180 181 182 183 184 185 186 | is := parser.ParseMetadata(pair.Value) collectInlineIndexData(&is, cData) case meta.TypeURL: if _, err := url.Parse(pair.Value); err == nil { cData.urls.Add(pair.Value) } default: | < | | < < < < < < < < < < < < < | | | < < < < < | | | 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 | is := parser.ParseMetadata(pair.Value) collectInlineIndexData(&is, cData) case meta.TypeURL: if _, err := url.Parse(pair.Value); err == nil { cData.urls.Add(pair.Value) } default: for _, word := range strfun.NormalizeWords(pair.Value) { cData.words.Add(word) } } } } func (mgr *Manager) idxProcessData(ctx context.Context, zi *store.ZettelIndex, cData *collectData) { for ref := range cData.refs { if _, err := mgr.GetMeta(ctx, ref); err == nil { zi.AddBackRef(ref) } else { zi.AddDeadRef(ref) } } zi.SetWords(cData.words) zi.SetUrls(cData.urls) } func (mgr *Manager) idxUpdateValue(ctx context.Context, inverseKey, value string, zi *store.ZettelIndex) { zid, err := id.Parse(value) if err != nil { return } if _, err = mgr.GetMeta(ctx, zid); err != nil { zi.AddDeadRef(zid) return } if inverseKey == "" { zi.AddBackRef(zid) return } zi.AddMetaRef(inverseKey, zid) } func (mgr *Manager) idxDeleteZettel(zid id.Zid) { toCheck := mgr.idxStore.DeleteZettel(context.Background(), zid) mgr.idxCheckZettel(toCheck) } func (mgr *Manager) idxCheckZettel(s id.Set) { for zid := range s { mgr.idxAr.EnqueueZettel(zid) } } |
Changes to box/manager/manager.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 | import ( "context" "io" "net/url" "sync" "time" | | > > < < | 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 | import ( "context" "io" "net/url" "sync" "time" "zettelstore.de/c/maps" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/box/manager/memstore" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/strfun" ) // ConnectData contains all administration related values. type ConnectData struct { Number int // number of the box, starting with 1. Config config.Config Enricher box.Enricher |
︙ | ︙ | |||
95 96 97 98 99 100 101 | done chan struct{} infos chan box.UpdateInfo propertyKeys strfun.Set // Set of property key names // Indexer data idxLog *logger.Logger idxStore store.Store | | | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | done chan struct{} infos chan box.UpdateInfo propertyKeys strfun.Set // Set of property key names // Indexer data idxLog *logger.Logger idxStore store.Store idxAr *anterooms idxReady chan struct{} // Signal a non-empty anteroom to background task // Indexer stats data idxMx sync.RWMutex idxLastReload time.Time idxDurReload time.Duration idxSinceReload uint64 |
︙ | ︙ | |||
136 137 138 139 140 141 142 | mgrLog: boxLog.Clone().Str("box", "manager").Child(), rtConfig: rtConfig, infos: make(chan box.UpdateInfo, len(boxURIs)*10), propertyKeys: propertyKeys, idxLog: boxLog.Clone().Str("box", "index").Child(), idxStore: memstore.New(), | | | 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | mgrLog: boxLog.Clone().Str("box", "manager").Child(), rtConfig: rtConfig, infos: make(chan box.UpdateInfo, len(boxURIs)*10), propertyKeys: propertyKeys, idxLog: boxLog.Clone().Str("box", "index").Child(), idxStore: memstore.New(), idxAr: newAnterooms(1000), idxReady: make(chan struct{}, 1), } cdata := ConnectData{Number: 1, Config: rtConfig, Enricher: mgr, Notify: mgr.infos} boxes := make([]box.ManagedBox, 0, len(boxURIs)+2) for _, uri := range boxURIs { p, err := Connect(uri, authManager, &cdata) if err != nil { |
︙ | ︙ | |||
179 180 181 182 183 184 185 | mgr.mxObserver.Unlock() } } func (mgr *Manager) notifier() { // The call to notify may panic. Ensure a running notifier. defer func() { | | | | 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | mgr.mxObserver.Unlock() } } func (mgr *Manager) notifier() { // The call to notify may panic. Ensure a running notifier. defer func() { if r := recover(); r != nil { kernel.Main.LogRecover("Notifier", r) go mgr.notifier() } }() tsLastEvent := time.Now() cache := destutterCache{} for { |
︙ | ︙ | |||
297 298 299 300 301 302 303 | mgr.setState(box.StartStateStopped) return err } mgr.idxAr.Reset() // Ensure an initial index run mgr.done = make(chan struct{}) go mgr.notifier() | | > > > < < < < < < < < < < < < < < < | 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 | mgr.setState(box.StartStateStopped) return err } mgr.idxAr.Reset() // Ensure an initial index run mgr.done = make(chan struct{}) go mgr.notifier() for !mgr.allBoxesStarted() { mgr.mgrLog.Trace().Msg("Wait for boxes to start") time.Sleep(time.Second) } mgr.setState(box.StartStateStarted) mgr.notifyObserver(&box.UpdateInfo{Box: mgr, Reason: box.OnReady}) go mgr.idxIndexer() return nil } func (mgr *Manager) allBoxesStarted() bool { for _, bx := range mgr.boxes { if b, ok := bx.(box.StartStopper); ok && b.State() != box.StartStateStarted { return false } } return true |
︙ | ︙ | |||
352 353 354 355 356 357 358 | // Refresh internal box data. func (mgr *Manager) Refresh(ctx context.Context) error { mgr.mgrLog.Debug().Msg("Refresh") if mgr.State() != box.StartStateStarted { return box.ErrStopped } | < > < < < < < < < < < < | 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 | // Refresh internal box data. func (mgr *Manager) Refresh(ctx context.Context) error { mgr.mgrLog.Debug().Msg("Refresh") if mgr.State() != box.StartStateStarted { return box.ErrStopped } mgr.mgrMx.Lock() defer mgr.mgrMx.Unlock() mgr.infos <- box.UpdateInfo{Reason: box.OnReload, Zid: id.Invalid} for _, bx := range mgr.boxes { if rb, ok := bx.(box.Refresher); ok { rb.Refresh(ctx) } } return nil } // ReadStats populates st with box statistics. func (mgr *Manager) ReadStats(st *box.Stats) { mgr.mgrLog.Debug().Msg("ReadStats") mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() subStats := make([]box.ManagedBoxStats, len(mgr.boxes)) for i, p := range mgr.boxes { |
︙ | ︙ |
Changes to box/manager/memstore/memstore.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | "context" "fmt" "io" "sort" "strings" "sync" | | < | | | | | < | | | | | | > > > > > > > | < | | | | < < | | | | < < < < < < < < < < | | | | | | | 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 | "context" "fmt" "io" "sort" "strings" "sync" "zettelstore.de/c/api" "zettelstore.de/c/maps" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) type metaRefs struct { forward id.Slice backward id.Slice } type zettelIndex struct { dead id.Slice forward id.Slice backward id.Slice meta map[string]metaRefs words []string urls []string } func (zi *zettelIndex) isEmpty() bool { if len(zi.forward) > 0 || len(zi.backward) > 0 || len(zi.dead) > 0 || len(zi.words) > 0 { return false } return len(zi.meta) == 0 } type stringRefs map[string]id.Slice type memStore struct { mx sync.RWMutex idx map[id.Zid]*zettelIndex dead map[id.Zid]id.Slice // map dead refs where they occur words stringRefs urls stringRefs // Stats updates uint64 } // New returns a new memory-based index store. func New() store.Store { return &memStore{ idx: make(map[id.Zid]*zettelIndex), dead: make(map[id.Zid]id.Slice), words: make(stringRefs), urls: make(stringRefs), } } func (ms *memStore) Enrich(_ context.Context, m *meta.Meta) { if ms.doEnrich(m) { ms.mx.Lock() ms.updates++ ms.mx.Unlock() } } func (ms *memStore) doEnrich(m *meta.Meta) bool { ms.mx.RLock() defer ms.mx.RUnlock() zi, ok := ms.idx[m.Zid] if !ok { return false } var updated bool if len(zi.dead) > 0 { m.Set(api.KeyDead, zi.dead.String()) updated = true } back := removeOtherMetaRefs(m, zi.backward.Copy()) if len(zi.backward) > 0 { m.Set(api.KeyBackward, zi.backward.String()) updated = true } if len(zi.forward) > 0 { m.Set(api.KeyForward, zi.forward.String()) back = remRefs(back, zi.forward) updated = true } for k, refs := range zi.meta { if len(refs.backward) > 0 { m.Set(k, refs.backward.String()) back = remRefs(back, refs.backward) updated = true } } if len(back) > 0 { m.Set(api.KeyBack, back.String()) updated = true } return updated } // SearchEqual returns all zettel that contains the given exact word. // The word must be normalized through Unicode NKFD, trimmed and not empty. func (ms *memStore) SearchEqual(word string) id.Set { ms.mx.RLock() defer ms.mx.RUnlock() result := id.NewSet() if refs, ok := ms.words[word]; ok { result.AddSlice(refs) } if refs, ok := ms.urls[word]; ok { result.AddSlice(refs) } zid, err := id.Parse(word) if err != nil { return result } zi, ok := ms.idx[zid] if !ok { |
︙ | ︙ | |||
227 228 229 230 231 232 233 | func (ms *memStore) selectWithPred(s string, pred func(string, string) bool) id.Set { // Must only be called if ms.mx is read-locked! result := id.NewSet() for word, refs := range ms.words { if !pred(word, s) { continue } | | | | | | | | | 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 | func (ms *memStore) selectWithPred(s string, pred func(string, string) bool) id.Set { // Must only be called if ms.mx is read-locked! result := id.NewSet() for word, refs := range ms.words { if !pred(word, s) { continue } result.AddSlice(refs) } for u, refs := range ms.urls { if !pred(u, s) { continue } result.AddSlice(refs) } return result } func addBackwardZids(result id.Set, zid id.Zid, zi *zettelIndex) { // Must only be called if ms.mx is read-locked! result.Zid(zid) result.AddSlice(zi.backward) for _, mref := range zi.meta { result.AddSlice(mref.backward) } } func removeOtherMetaRefs(m *meta.Meta, back id.Slice) id.Slice { for _, p := range m.PairsRest() { switch meta.Type(p.Key) { case meta.TypeID: |
︙ | ︙ | |||
268 269 270 271 272 273 274 | } return back } func (ms *memStore) UpdateReferences(_ context.Context, zidx *store.ZettelIndex) id.Set { ms.mx.Lock() defer ms.mx.Unlock() | < | < | < | < | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | < < | < < | < | < < | | < | < | | | | | | < | | | | | | | | < < < < | > | 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | } return back } func (ms *memStore) UpdateReferences(_ context.Context, zidx *store.ZettelIndex) id.Set { ms.mx.Lock() defer ms.mx.Unlock() zi, ziExist := ms.idx[zidx.Zid] if !ziExist || zi == nil { zi = &zettelIndex{} ziExist = false } // Is this zettel an old dead reference mentioned in other zettel? var toCheck id.Set if refs, ok := ms.dead[zidx.Zid]; ok { // These must be checked later again toCheck = id.NewSet(refs...) delete(ms.dead, zidx.Zid) } ms.updateDeadReferences(zidx, zi) ms.updateForwardBackwardReferences(zidx, zi) ms.updateMetadataReferences(zidx, zi) zi.words = updateWordSet(zidx.Zid, ms.words, zi.words, zidx.GetWords()) zi.urls = updateWordSet(zidx.Zid, ms.urls, zi.urls, zidx.GetUrls()) // Check if zi must be inserted into ms.idx if !ziExist && !zi.isEmpty() { ms.idx[zidx.Zid] = zi } return toCheck } func (ms *memStore) updateDeadReferences(zidx *store.ZettelIndex, zi *zettelIndex) { // Must only be called if ms.mx is write-locked! drefs := zidx.GetDeadRefs() newRefs, remRefs := refsDiff(drefs, zi.dead) zi.dead = drefs for _, ref := range remRefs { ms.dead[ref] = remRef(ms.dead[ref], zidx.Zid) } for _, ref := range newRefs { ms.dead[ref] = addRef(ms.dead[ref], zidx.Zid) } } func (ms *memStore) updateForwardBackwardReferences(zidx *store.ZettelIndex, zi *zettelIndex) { // Must only be called if ms.mx is write-locked! brefs := zidx.GetBackRefs() newRefs, remRefs := refsDiff(brefs, zi.forward) zi.forward = brefs for _, ref := range remRefs { bzi := ms.getEntry(ref) bzi.backward = remRef(bzi.backward, zidx.Zid) } for _, ref := range newRefs { bzi := ms.getEntry(ref) bzi.backward = addRef(bzi.backward, zidx.Zid) } } func (ms *memStore) updateMetadataReferences(zidx *store.ZettelIndex, zi *zettelIndex) { // Must only be called if ms.mx is write-locked! metarefs := zidx.GetMetaRefs() for key, mr := range zi.meta { if _, ok := metarefs[key]; ok { continue } ms.removeInverseMeta(zidx.Zid, key, mr.forward) } if zi.meta == nil { zi.meta = make(map[string]metaRefs) } for key, mrefs := range metarefs { mr := zi.meta[key] newRefs, remRefs := refsDiff(mrefs, mr.forward) mr.forward = mrefs zi.meta[key] = mr for _, ref := range newRefs { bzi := ms.getEntry(ref) if bzi.meta == nil { bzi.meta = make(map[string]metaRefs) } bmr := bzi.meta[key] bmr.backward = addRef(bmr.backward, zidx.Zid) bzi.meta[key] = bmr } ms.removeInverseMeta(zidx.Zid, key, remRefs) } } func updateWordSet(zid id.Zid, srefs stringRefs, prev []string, next store.WordSet) []string { // Must only be called if ms.mx is write-locked! newWords, removeWords := next.Diff(prev) for _, word := range newWords { if refs, ok := srefs[word]; ok { srefs[word] = addRef(refs, zid) continue } srefs[word] = id.Slice{zid} |
︙ | ︙ | |||
434 435 436 437 438 439 440 | continue } srefs[word] = refs2 } return next.Words() } | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < > | | | > | < | | > < > | > > | | | | | | | | | | | > < < < | 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 | continue } srefs[word] = refs2 } return next.Words() } func (ms *memStore) getEntry(zid id.Zid) *zettelIndex { // Must only be called if ms.mx is write-locked! if zi, ok := ms.idx[zid]; ok { return zi } zi := &zettelIndex{} ms.idx[zid] = zi return zi } func (ms *memStore) DeleteZettel(_ context.Context, zid id.Zid) id.Set { ms.mx.Lock() defer ms.mx.Unlock() zi, ok := ms.idx[zid] if !ok { return nil } ms.deleteDeadSources(zid, zi) toCheck := ms.deleteForwardBackward(zid, zi) if len(zi.meta) > 0 { for key, mrefs := range zi.meta { ms.removeInverseMeta(zid, key, mrefs.forward) } } ms.deleteWords(zid, zi.words) delete(ms.idx, zid) return toCheck } func (ms *memStore) deleteDeadSources(zid id.Zid, zi *zettelIndex) { // Must only be called if ms.mx is write-locked! for _, ref := range zi.dead { if drefs, ok := ms.dead[ref]; ok { drefs = remRef(drefs, zid) if len(drefs) > 0 { ms.dead[ref] = drefs } else { delete(ms.dead, ref) } } } } func (ms *memStore) deleteForwardBackward(zid id.Zid, zi *zettelIndex) id.Set { // Must only be called if ms.mx is write-locked! var toCheck id.Set for _, ref := range zi.forward { if fzi, ok := ms.idx[ref]; ok { fzi.backward = remRef(fzi.backward, zid) } } for _, ref := range zi.backward { if bzi, ok := ms.idx[ref]; ok { bzi.forward = remRef(bzi.forward, zid) if toCheck == nil { toCheck = id.NewSet() } toCheck.Zid(ref) } } return toCheck } func (ms *memStore) removeInverseMeta(zid id.Zid, key string, forward id.Slice) { // Must only be called if ms.mx is write-locked! for _, ref := range forward { bzi, ok := ms.idx[ref] if !ok || bzi.meta == nil { continue } bmr, ok := bzi.meta[key] if !ok { continue } bmr.backward = remRef(bmr.backward, zid) if len(bmr.backward) > 0 || len(bmr.forward) > 0 { bzi.meta[key] = bmr } else { delete(bzi.meta, key) if len(bzi.meta) == 0 { bzi.meta = nil } } } } func (ms *memStore) deleteWords(zid id.Zid, words []string) { // Must only be called if ms.mx is write-locked! for _, word := range words { refs, ok := ms.words[word] if !ok { continue } refs2 := remRef(refs, zid) if len(refs2) == 0 { delete(ms.words, word) continue } ms.words[word] = refs2 } } func (ms *memStore) ReadStats(st *store.Stats) { ms.mx.RLock() st.Zettel = len(ms.idx) st.Updates = ms.updates st.Words = uint64(len(ms.words)) st.Urls = uint64(len(ms.urls)) ms.mx.RUnlock() } func (ms *memStore) Dump(w io.Writer) { ms.mx.RLock() defer ms.mx.RUnlock() io.WriteString(w, "=== Dump\n") |
︙ | ︙ | |||
648 649 650 651 652 653 654 | fmt.Fprintln(w, "=====", id) zi := ms.idx[id] if len(zi.dead) > 0 { fmt.Fprintln(w, "* Dead:", zi.dead) } dumpZids(w, "* Forward:", zi.forward) dumpZids(w, "* Backward:", zi.backward) | | | 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 | fmt.Fprintln(w, "=====", id) zi := ms.idx[id] if len(zi.dead) > 0 { fmt.Fprintln(w, "* Dead:", zi.dead) } dumpZids(w, "* Forward:", zi.forward) dumpZids(w, "* Backward:", zi.backward) for k, fb := range zi.meta { fmt.Fprintln(w, "* Meta", k) dumpZids(w, "** Forward:", fb.forward) dumpZids(w, "** Backward:", fb.backward) } dumpStrings(w, "* Words", "", "", zi.words) dumpStrings(w, "* URLs", "[[", "]]", zi.urls) } |
︙ | ︙ |
Changes to box/manager/memstore/refs.go.
1 2 3 4 5 6 7 8 9 10 11 12 | //----------------------------------------------------------------------------- // Copyright (c) 2021-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. //----------------------------------------------------------------------------- package memstore | < < < | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //----------------------------------------------------------------------------- // Copyright (c) 2021-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. //----------------------------------------------------------------------------- package memstore import "zettelstore.de/z/domain/id" func refsDiff(refsN, refsO id.Slice) (newRefs, remRefs id.Slice) { npos, opos := 0, 0 for npos < len(refsN) && opos < len(refsO) { rn, ro := refsN[npos], refsO[opos] if rn == ro { npos++ |
︙ | ︙ | |||
50 51 52 53 54 55 56 | return refs } else if r < ref { lo = m + 1 } else { hi = m } } | | > > | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | return refs } else if r < ref { lo = m + 1 } else { hi = m } } refs = append(refs, id.Invalid) copy(refs[hi+1:], refs[hi:]) refs[hi] = ref return refs } func remRefs(refs, rem id.Slice) id.Slice { if len(refs) == 0 || len(rem) == 0 { return refs } |
︙ | ︙ |
Changes to box/manager/memstore/refs_test.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package memstore import ( "testing" | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //----------------------------------------------------------------------------- package memstore import ( "testing" "zettelstore.de/z/domain/id" ) func assertRefs(t *testing.T, i int, got, exp id.Slice) { t.Helper() if got == nil && exp != nil { t.Errorf("%d: got nil, but expected %v", i, exp) return |
︙ | ︙ |
Changes to box/manager/store/store.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | // Package store contains general index data for storing a zettel index. package store import ( "context" "io" | | | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // Package store contains general index data for storing a zettel index. package store import ( "context" "io" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/query" ) // Stats records statistics about the store. type Stats struct { // Zettel is the number of zettel managed by the indexer. Zettel int |
︙ | ︙ | |||
36 37 38 39 40 41 42 | } // Store all relevant zettel data. There may be multiple implementations, i.e. // memory-based, file-based, based on SQLite, ... type Store interface { query.Searcher | < < < < < < < | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | } // Store all relevant zettel data. There may be multiple implementations, i.e. // memory-based, file-based, based on SQLite, ... type Store interface { query.Searcher // Entrich metadata with data from store. Enrich(ctx context.Context, m *meta.Meta) // UpdateReferences for a specific zettel. // Returns set of zettel identifier that must also be checked for changes. UpdateReferences(context.Context, *ZettelIndex) id.Set // DeleteZettel removes index data for given zettel. // Returns set of zettel identifier that must also be checked for changes. DeleteZettel(context.Context, id.Zid) id.Set // ReadStats populates st with store statistics. ReadStats(st *Stats) // Dump the content to a Writer. Dump(io.Writer) } |
Changes to box/manager/store/zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 | //----------------------------------------------------------------------------- // Copyright (c) 2021-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. //----------------------------------------------------------------------------- package store | < | < < | < | | | | | | | < | | | | | | | | | | | > | < < | > | > | | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2021-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. //----------------------------------------------------------------------------- package store import "zettelstore.de/z/domain/id" // ZettelIndex contains all index data of a zettel. type ZettelIndex struct { Zid id.Zid // zid of the indexed zettel backrefs id.Set // set of back references metarefs map[string]id.Set // references to inverse keys deadrefs id.Set // set of dead references words WordSet urls WordSet } // NewZettelIndex creates a new zettel index. func NewZettelIndex(zid id.Zid) *ZettelIndex { return &ZettelIndex{ Zid: zid, backrefs: id.NewSet(), metarefs: make(map[string]id.Set), deadrefs: id.NewSet(), } } // AddBackRef adds a reference to a zettel where the current zettel links to // without any more information. func (zi *ZettelIndex) AddBackRef(zid id.Zid) { zi.backrefs.Zid(zid) } // AddMetaRef adds a named reference to a zettel. On that zettel, the given // metadata key should point back to the current zettel. func (zi *ZettelIndex) AddMetaRef(key string, zid id.Zid) { if zids, ok := zi.metarefs[key]; ok { zids.Zid(zid) return } zi.metarefs[key] = id.NewSet(zid) } // AddDeadRef adds a dead reference to a zettel. func (zi *ZettelIndex) AddDeadRef(zid id.Zid) { zi.deadrefs.Zid(zid) } // SetWords sets the words to the given value. func (zi *ZettelIndex) SetWords(words WordSet) { zi.words = words } // SetUrls sets the words to the given value. func (zi *ZettelIndex) SetUrls(urls WordSet) { zi.urls = urls } // GetDeadRefs returns all dead references as a sorted list. func (zi *ZettelIndex) GetDeadRefs() id.Slice { return zi.deadrefs.Sorted() } // GetBackRefs returns all back references as a sorted list. func (zi *ZettelIndex) GetBackRefs() id.Slice { return zi.backrefs.Sorted() } // GetMetaRefs returns all meta references as a map of strings to a sorted list of references func (zi *ZettelIndex) GetMetaRefs() map[string]id.Slice { if len(zi.metarefs) == 0 { return nil } result := make(map[string]id.Slice, len(zi.metarefs)) for key, refs := range zi.metarefs { result[key] = refs.Sorted() } return result } // GetWords returns a reference to the set of words. It must not be modified. func (zi *ZettelIndex) GetWords() WordSet { return zi.words } // GetUrls returns a reference to the set of URLs. It must not be modified. func (zi *ZettelIndex) GetUrls() WordSet { return zi.urls } |
Changes to box/membox/membox.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 | import ( "context" "net/url" "sync" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" | > > > < < | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | import ( "context" "net/url" "sync" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" ) func init() { manager.Register( "mem", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return &memBox{ |
︙ | ︙ | |||
43 44 45 46 47 48 49 | type memBox struct { log *logger.Logger u *url.URL cdata manager.ConnectData maxZettel int maxBytes int mx sync.RWMutex // Protects the following fields | | | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | type memBox struct { log *logger.Logger u *url.URL cdata manager.ConnectData maxZettel int maxBytes int mx sync.RWMutex // Protects the following fields zettel map[id.Zid]domain.Zettel curBytes int } func (mb *memBox) notifyChanged(zid id.Zid) { if chci := mb.cdata.Notify; chci != nil { chci <- box.UpdateInfo{Box: mb, Reason: box.OnZettel, Zid: zid} } |
︙ | ︙ | |||
68 69 70 71 72 73 74 | return box.StartStateStopped } return box.StartStateStarted } func (mb *memBox) Start(context.Context) error { mb.mx.Lock() | | | | 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 | return box.StartStateStopped } return box.StartStateStarted } func (mb *memBox) Start(context.Context) error { mb.mx.Lock() mb.zettel = make(map[id.Zid]domain.Zettel) mb.curBytes = 0 mb.mx.Unlock() mb.log.Trace().Int("max-zettel", int64(mb.maxZettel)).Int("max-bytes", int64(mb.maxBytes)).Msg("Start Box") return nil } func (mb *memBox) Stop(context.Context) { mb.mx.Lock() mb.zettel = nil mb.mx.Unlock() } func (mb *memBox) CanCreateZettel(context.Context) bool { mb.mx.RLock() defer mb.mx.RUnlock() return len(mb.zettel) < mb.maxZettel } func (mb *memBox) CreateZettel(_ context.Context, zettel domain.Zettel) (id.Zid, error) { mb.mx.Lock() newBytes := mb.curBytes + zettel.Length() if mb.maxZettel < len(mb.zettel) || mb.maxBytes < newBytes { mb.mx.Unlock() return id.Invalid, box.ErrCapacity } zid, err := box.GetNewZid(func(zid id.Zid) (bool, error) { |
︙ | ︙ | |||
113 114 115 116 117 118 119 | mb.curBytes = newBytes mb.mx.Unlock() mb.notifyChanged(zid) mb.log.Trace().Zid(zid).Msg("CreateZettel") return zid, nil } | | | | | | | | > | > > > | 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 | mb.curBytes = newBytes mb.mx.Unlock() mb.notifyChanged(zid) mb.log.Trace().Zid(zid).Msg("CreateZettel") return zid, nil } func (mb *memBox) GetZettel(_ context.Context, zid id.Zid) (domain.Zettel, error) { mb.mx.RLock() zettel, ok := mb.zettel[zid] mb.mx.RUnlock() if !ok { return domain.Zettel{}, box.ErrNotFound } zettel.Meta = zettel.Meta.Clone() mb.log.Trace().Msg("GetZettel") return zettel, nil } func (mb *memBox) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { mb.mx.RLock() zettel, ok := mb.zettel[zid] mb.mx.RUnlock() if !ok { return nil, box.ErrNotFound } mb.log.Trace().Msg("GetMeta") return zettel.Meta.Clone(), nil } func (mb *memBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { mb.mx.RLock() defer mb.mx.RUnlock() mb.log.Trace().Int("entries", int64(len(mb.zettel))).Msg("ApplyZid") for zid := range mb.zettel { |
︙ | ︙ | |||
158 159 160 161 162 163 164 | mb.cdata.Enricher.Enrich(ctx, m, mb.cdata.Number) handle(m) } } return nil } | | | | | 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 | mb.cdata.Enricher.Enrich(ctx, m, mb.cdata.Number) handle(m) } } return nil } func (mb *memBox) CanUpdateZettel(_ context.Context, zettel domain.Zettel) bool { mb.mx.RLock() defer mb.mx.RUnlock() zid := zettel.Meta.Zid if !zid.IsValid() { return false } newBytes := mb.curBytes + zettel.Length() if prevZettel, found := mb.zettel[zid]; found { newBytes -= prevZettel.Length() } return newBytes < mb.maxBytes } func (mb *memBox) UpdateZettel(_ context.Context, zettel domain.Zettel) error { m := zettel.Meta.Clone() if !m.Zid.IsValid() { return &box.ErrInvalidID{Zid: m.Zid} } mb.mx.Lock() newBytes := mb.curBytes + zettel.Length() if prevZettel, found := mb.zettel[m.Zid]; found { newBytes -= prevZettel.Length() } |
︙ | ︙ | |||
205 206 207 208 209 210 211 | func (*memBox) AllowRenameZettel(context.Context, id.Zid) bool { return true } func (mb *memBox) RenameZettel(_ context.Context, curZid, newZid id.Zid) error { mb.mx.Lock() zettel, ok := mb.zettel[curZid] if !ok { mb.mx.Unlock() | | | | 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | func (*memBox) AllowRenameZettel(context.Context, id.Zid) bool { return true } func (mb *memBox) RenameZettel(_ context.Context, curZid, newZid id.Zid) error { mb.mx.Lock() zettel, ok := mb.zettel[curZid] if !ok { mb.mx.Unlock() return box.ErrNotFound } // Check that there is no zettel with newZid if _, ok = mb.zettel[newZid]; ok { mb.mx.Unlock() return &box.ErrInvalidID{Zid: newZid} } meta := zettel.Meta.Clone() meta.Zid = newZid zettel.Meta = meta mb.zettel[newZid] = zettel delete(mb.zettel, curZid) |
︙ | ︙ | |||
238 239 240 241 242 243 244 | } func (mb *memBox) DeleteZettel(_ context.Context, zid id.Zid) error { mb.mx.Lock() oldZettel, found := mb.zettel[zid] if !found { mb.mx.Unlock() | | | 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | } func (mb *memBox) DeleteZettel(_ context.Context, zid id.Zid) error { mb.mx.Lock() oldZettel, found := mb.zettel[zid] if !found { mb.mx.Unlock() return box.ErrNotFound } delete(mb.zettel, zid) mb.curBytes -= oldZettel.Length() mb.mx.Unlock() mb.notifyChanged(zid) mb.log.Trace().Msg("DeleteZettel") return nil |
︙ | ︙ |
Changes to box/notify/directory.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 | "fmt" "path/filepath" "regexp" "strings" "sync" "zettelstore.de/z/box" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/parser" "zettelstore.de/z/query" "zettelstore.de/z/strfun" | > < | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "fmt" "path/filepath" "regexp" "strings" "sync" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/parser" "zettelstore.de/z/query" "zettelstore.de/z/strfun" ) type entrySet map[id.Zid]*DirEntry // DirServiceState signal the internal state of the service. // // The following state transitions are possible: |
︙ | ︙ | |||
187 188 189 190 191 192 193 | func (ds *DirService) RenameDirEntry(oldEntry *DirEntry, newZid id.Zid) (DirEntry, error) { ds.mx.Lock() defer ds.mx.Unlock() if ds.entries == nil { return DirEntry{}, ds.logMissingEntry("rename") } if _, found := ds.entries[newZid]; found { | | | 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | func (ds *DirService) RenameDirEntry(oldEntry *DirEntry, newZid id.Zid) (DirEntry, error) { ds.mx.Lock() defer ds.mx.Unlock() if ds.entries == nil { return DirEntry{}, ds.logMissingEntry("rename") } if _, found := ds.entries[newZid]; found { return DirEntry{}, &box.ErrInvalidID{Zid: newZid} } oldZid := oldEntry.Zid newEntry := DirEntry{ Zid: newZid, MetaName: renameFilename(oldEntry.MetaName, oldZid, newZid), ContentName: renameFilename(oldEntry.ContentName, oldZid, newZid), ContentExt: oldEntry.ContentExt, |
︙ | ︙ | |||
223 224 225 226 227 228 229 | delete(ds.entries, zid) return nil } func (ds *DirService) updateEvents(newEntries entrySet) { // Something may panic. Ensure a running service. defer func() { | | | | 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | delete(ds.entries, zid) return nil } func (ds *DirService) updateEvents(newEntries entrySet) { // Something may panic. Ensure a running service. defer func() { if r := recover(); r != nil { kernel.Main.LogRecover("DirectoryService", r) go ds.updateEvents(newEntries) } }() for ev := range ds.notifier.Events() { e, ok := ds.handleEvent(ev, newEntries) if !ok { |
︙ | ︙ |
Changes to box/notify/directory_test.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- package notify import ( "testing" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/draw" // Allow to use draw parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. | > > < < | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | //----------------------------------------------------------------------------- package notify import ( "testing" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/draw" // Allow to use draw parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) func TestSeekZid(t *testing.T) { testcases := []struct { name string zid id.Zid }{ |
︙ | ︙ | |||
47 48 49 50 51 52 53 | } func TestNewExtIsBetter(t *testing.T) { extVals := []string{ // Main Formats meta.SyntaxZmk, meta.SyntaxDraw, meta.SyntaxMarkdown, meta.SyntaxMD, // Other supported text formats | | | | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | } func TestNewExtIsBetter(t *testing.T) { extVals := []string{ // Main Formats meta.SyntaxZmk, meta.SyntaxDraw, meta.SyntaxMarkdown, meta.SyntaxMD, // Other supported text formats meta.SyntaxCSS, meta.SyntaxTxt, meta.SyntaxHTML, meta.SyntaxMustache, meta.SyntaxText, meta.SyntaxPlain, // Supported text graphics formats meta.SyntaxSVG, meta.SyntaxNone, // Supported binary graphic formats meta.SyntaxGif, meta.SyntaxPNG, meta.SyntaxJPEG, meta.SyntaxWebp, meta.SyntaxJPG, // Unsupported syntax values |
︙ | ︙ |
Changes to box/notify/entry.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package notify import ( "path/filepath" | | | | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //----------------------------------------------------------------------------- package notify import ( "path/filepath" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/parser" ) const ( extZettel = "zettel" // file contains metadata and content extBin = "bin" // file contains binary content extTxt = "txt" // file contains non-binary content ) |
︙ | ︙ | |||
44 45 46 47 48 49 50 | // HasMetaInContent returns true, if metadata will be stored in the content file. func (e *DirEntry) HasMetaInContent() bool { return e.IsValid() && extIsMetaAndContent(e.ContentExt) } // SetupFromMetaContent fills entry data based on metadata and zettel content. | | | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | // HasMetaInContent returns true, if metadata will be stored in the content file. func (e *DirEntry) HasMetaInContent() bool { return e.IsValid() && extIsMetaAndContent(e.ContentExt) } // SetupFromMetaContent fills entry data based on metadata and zettel content. func (e *DirEntry) SetupFromMetaContent(m *meta.Meta, content domain.Content, getZettelFileSyntax func() []string) { if e.Zid != m.Zid { panic("Zid differ") } if contentName := e.ContentName; contentName != "" { if !extIsMetaAndContent(e.ContentExt) && e.MetaName == "" { e.MetaName = e.calcBaseName(contentName) } |
︙ | ︙ | |||
76 77 78 79 80 81 82 | } if metaName == "" { e.MetaName = e.calcBaseName(e.ContentName) } } } | | | 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | } if metaName == "" { e.MetaName = e.calcBaseName(e.ContentName) } } } func contentExtWithMeta(syntax string, content domain.Content) string { p := parser.Get(syntax) if content.IsBinary() { if p.IsImageFormat { return syntax } return extBin } |
︙ | ︙ |
Changes to box/notify/fsdir.go.
︙ | ︙ | |||
163 164 165 166 167 168 169 | } return true } if ev.Has(fsnotify.Create) { err := fsdn.base.Add(fsdn.path) if err != nil { | | | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | } return true } if ev.Has(fsnotify.Create) { err := fsdn.base.Add(fsdn.path) if err != nil { fsdn.log.IfErr(err).Str("name", fsdn.path).Msg("Unable to add directory") select { case fsdn.events <- Event{Op: Error, Err: err}: case <-fsdn.done: fsdn.log.Trace().Int("i", 2).Msg("done dir event processing") return false } } |
︙ | ︙ |
Changes to cmd/cmd_file.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | import ( "context" "flag" "fmt" "io" "os" | | > > > < < < | | | | 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 | import ( "context" "flag" "fmt" "io" "os" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/input" "zettelstore.de/z/parser" ) // ---------- Subcommand: file ----------------------------------------------- func cmdFile(fs *flag.FlagSet) (int, error) { enc := fs.Lookup("t").Value.String() m, inp, err := getInput(fs.Args()) if m == nil { return 2, err } z := parser.ParseZettel( context.Background(), domain.Zettel{ Meta: m, Content: domain.NewContent(inp.Src[inp.Pos:]), }, m.GetDefault(api.KeySyntax, meta.SyntaxZmk), nil, ) encdr := encoder.Create(api.Encoder(enc)) if encdr == nil { fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc) return 2, nil } _, err = encdr.WriteZettel(os.Stdout, z, parser.ParseMetadata) if err != nil { return 2, err |
︙ | ︙ |
Changes to cmd/cmd_password.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | import ( "flag" "fmt" "os" "golang.org/x/term" | | | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import ( "flag" "fmt" "os" "golang.org/x/term" "zettelstore.de/c/api" "zettelstore.de/z/auth/cred" "zettelstore.de/z/domain/id" ) // ---------- Subcommand: password ------------------------------------------- func cmdPassword(fs *flag.FlagSet) (int, error) { if fs.NArg() == 0 { fmt.Fprintln(os.Stderr, "User name and user zettel identification missing") |
︙ | ︙ |
Changes to cmd/cmd_run.go.
︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 24 25 | "context" "flag" "net/http" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/kernel" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter/api" "zettelstore.de/z/web/adapter/webui" "zettelstore.de/z/web/server" | > < | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "context" "flag" "net/http" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/domain/meta" "zettelstore.de/z/kernel" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter/api" "zettelstore.de/z/web/adapter/webui" "zettelstore.de/z/web/server" ) // ---------- Subcommand: run ------------------------------------------------ func flgRun(fs *flag.FlagSet) { fs.String("c", "", "configuration file") fs.Uint("a", 0, "port number kernel service (0=disable)") |
︙ | ︙ | |||
52 53 54 55 56 57 58 | protectedBoxManager, authPolicy := authManager.BoxWithPolicy(boxManager, rtConfig) kern := kernel.Main webLog := kern.GetLogger(kernel.WebService) var getUser getUserImpl logAuth := kern.GetLogger(kernel.AuthService) logUc := kern.GetLogger(kernel.CoreService).WithUser(&getUser) | < | | > | | < < < > > < | | | | | | > > > > > > | | | 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 | protectedBoxManager, authPolicy := authManager.BoxWithPolicy(boxManager, rtConfig) kern := kernel.Main webLog := kern.GetLogger(kernel.WebService) var getUser getUserImpl logAuth := kern.GetLogger(kernel.AuthService) logUc := kern.GetLogger(kernel.CoreService).WithUser(&getUser) ucAuthenticate := usecase.NewAuthenticate(logAuth, authManager, authManager, boxManager) ucIsAuth := usecase.NewIsAuthenticated(logUc, &getUser, authManager) ucCreateZettel := usecase.NewCreateZettel(logUc, rtConfig, protectedBoxManager) ucGetMeta := usecase.NewGetMeta(protectedBoxManager) ucGetAllMeta := usecase.NewGetAllMeta(protectedBoxManager) ucGetZettel := usecase.NewGetZettel(protectedBoxManager) ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel) ucListMeta := usecase.NewListMeta(protectedBoxManager) ucEvaluate := usecase.NewEvaluate(rtConfig, ucGetZettel, ucGetMeta, ucListMeta) ucListSyntax := usecase.NewListSyntax(protectedBoxManager) ucListRoles := usecase.NewListRoles(protectedBoxManager) ucZettelContext := usecase.NewZettelContext(protectedBoxManager, rtConfig) ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager) ucRename := usecase.NewRenameZettel(logUc, protectedBoxManager) ucUnlinkedRefs := usecase.NewUnlinkedReferences(protectedBoxManager, rtConfig) ucRefresh := usecase.NewRefresh(logUc, protectedBoxManager) ucVersion := usecase.NewVersion(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) a := api.New( webLog.Clone().Str("adapter", "api").Child(), webSrv, authManager, authManager, rtConfig, authPolicy) wui := webui.New( webLog.Clone().Str("adapter", "wui").Child(), webSrv, authManager, rtConfig, authManager, boxManager, authPolicy, &ucEvaluate) webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager)) if assetDir := kern.GetConfig(kernel.WebService, kernel.WebAssetDir).(string); assetDir != "" { const assetPrefix = "/assets/" webSrv.Handle(assetPrefix, http.StripPrefix(assetPrefix, http.FileServer(http.Dir(assetDir)))) webSrv.Handle("/favicon.ico", wui.MakeFaviconHandler(assetDir)) } // Web user interface if !authManager.IsReadonly() { webSrv.AddZettelRoute('b', server.MethodGet, wui.MakeGetRenameZettelHandler(ucGetMeta)) webSrv.AddZettelRoute('b', server.MethodPost, wui.MakePostRenameZettelHandler(&ucRename)) webSrv.AddListRoute('c', server.MethodGet, wui.MakeGetZettelFromListHandler(ucListMeta, &ucEvaluate, ucListRoles, ucListSyntax)) webSrv.AddListRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('c', server.MethodGet, wui.MakeGetCreateZettelHandler( ucGetZettel, &ucCreateZettel, ucListRoles, ucListSyntax)) webSrv.AddZettelRoute('c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('d', server.MethodGet, wui.MakeGetDeleteZettelHandler(ucGetMeta, ucGetAllMeta)) webSrv.AddZettelRoute('d', server.MethodPost, wui.MakePostDeleteZettelHandler(&ucDelete)) webSrv.AddZettelRoute('e', server.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel, ucListRoles, ucListSyntax)) webSrv.AddZettelRoute('e', server.MethodPost, wui.MakeEditSetZettelHandler(&ucUpdate)) } webSrv.AddListRoute('g', server.MethodGet, wui.MakeGetGoActionHandler(&ucRefresh)) webSrv.AddListRoute('h', server.MethodGet, wui.MakeListHTMLMetaHandler(ucListMeta, &ucEvaluate)) webSrv.AddZettelRoute('h', server.MethodGet, wui.MakeGetHTMLZettelHandler(&ucEvaluate, ucGetMeta)) webSrv.AddListRoute('i', server.MethodGet, wui.MakeGetLoginOutHandler()) webSrv.AddListRoute('i', server.MethodPost, wui.MakePostLoginHandler(&ucAuthenticate)) webSrv.AddZettelRoute('i', server.MethodGet, wui.MakeGetInfoHandler( ucParseZettel, &ucEvaluate, ucGetMeta, ucGetAllMeta, ucUnlinkedRefs)) webSrv.AddZettelRoute('k', server.MethodGet, wui.MakeZettelContextHandler( ucZettelContext, &ucEvaluate)) // API webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate)) webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler()) webSrv.AddZettelRoute('o', server.MethodGet, a.MakeGetOrderHandler( usecase.NewZettelOrder(protectedBoxManager, ucEvaluate))) webSrv.AddZettelRoute('u', server.MethodGet, a.MakeListUnlinkedMetaHandler(ucGetMeta, ucUnlinkedRefs)) webSrv.AddListRoute('x', server.MethodGet, a.MakeGetDataHandler(ucVersion)) webSrv.AddListRoute('x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh)) webSrv.AddZettelRoute('x', server.MethodGet, a.MakeZettelContextHandler(ucZettelContext)) webSrv.AddListRoute('z', server.MethodGet, a.MakeQueryHandler(ucListMeta)) webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetZettelHandler(ucGetMeta, ucGetZettel, ucParseZettel, ucEvaluate)) if !authManager.IsReadonly() { webSrv.AddListRoute('z', server.MethodPost, a.MakePostCreateZettelHandler(&ucCreateZettel)) webSrv.AddZettelRoute('z', server.MethodPut, a.MakeUpdateZettelHandler(&ucUpdate)) webSrv.AddZettelRoute('z', server.MethodDelete, a.MakeDeleteZettelHandler(&ucDelete)) webSrv.AddZettelRoute('z', server.MethodMove, a.MakeRenameZettelHandler(&ucRename)) } |
︙ | ︙ |
Changes to cmd/command.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package cmd import ( "flag" | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //----------------------------------------------------------------------------- package cmd import ( "flag" "zettelstore.de/c/maps" "zettelstore.de/z/logger" ) // Command stores information about commands / sub-commands. type Command struct { Name string // command name as it appears on the command line Func CommandFunc // function that executes a command |
︙ | ︙ |
Changes to cmd/main.go.
︙ | ︙ | |||
18 19 20 21 22 23 24 | "net/url" "os" "runtime/debug" "strconv" "strings" "time" | | > > < < | 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 | "net/url" "os" "runtime/debug" "strconv" "strings" "time" "zettelstore.de/c/api" "zettelstore.de/z/auth" "zettelstore.de/z/auth/impl" "zettelstore.de/z/box" "zettelstore.de/z/box/compbox" "zettelstore.de/z/box/manager" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" ) const strRunSimple = "run-simple" func init() { RegisterCommand(Command{ Name: "help", |
︙ | ︙ | |||
84 85 86 87 88 89 90 | }) RegisterCommand(Command{ Name: "password", Func: cmdPassword, }) } | | | | | | | | | | | | 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 | }) RegisterCommand(Command{ Name: "password", Func: cmdPassword, }) } func fetchStartupConfiguration(fs *flag.FlagSet) (cfg *meta.Meta) { if configFlag := fs.Lookup("c"); configFlag != nil { if filename := configFlag.Value.String(); filename != "" { content, err := readConfiguration(filename) return createConfiguration(content, err) } } content, err := searchAndReadConfiguration() return createConfiguration(content, err) } func createConfiguration(content []byte, err error) *meta.Meta { if err != nil { return meta.New(id.Invalid) } return meta.NewFromInput(id.Invalid, input.NewInput(content)) } func readConfiguration(filename string) ([]byte, error) { return os.ReadFile(filename) } func searchAndReadConfiguration() ([]byte, error) { for _, filename := range []string{"zettelstore.cfg", "zsconfig.txt", "zscfg.txt", "_zscfg"} { if content, err := readConfiguration(filename); err == nil { return content, nil } } return readConfiguration(".zscfg") } func getConfig(fs *flag.FlagSet) *meta.Meta { cfg := fetchStartupConfiguration(fs) fs.Visit(func(flg *flag.Flag) { switch flg.Name { case "p": cfg.Set(keyListenAddr, net.JoinHostPort("127.0.0.1", flg.Value.String())) case "a": cfg.Set(keyAdminPort, flg.Value.String()) case "d": |
︙ | ︙ | |||
140 141 142 143 144 145 146 | cfg.Set(keyDebug, flg.Value.String()) case "r": cfg.Set(keyReadOnly, flg.Value.String()) case "v": cfg.Set(keyVerbose, flg.Value.String()) } }) | | | 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | cfg.Set(keyDebug, flg.Value.String()) case "r": cfg.Set(keyReadOnly, flg.Value.String()) case "v": cfg.Set(keyVerbose, flg.Value.String()) } }) return cfg } func deleteConfiguredBoxes(cfg *meta.Meta) { for _, p := range cfg.PairsRest() { if key := p.Key; strings.HasPrefix(key, kernel.BoxURIs) { cfg.Delete(key) } |
︙ | ︙ | |||
247 248 249 250 251 252 253 | return 1 } fs := command.GetFlags() if err := fs.Parse(args); err != nil { fmt.Fprintf(os.Stderr, "%s: unable to parse flags: %v %v\n", name, args, err) return 1 } | | | 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | return 1 } fs := command.GetFlags() if err := fs.Parse(args); err != nil { fmt.Fprintf(os.Stderr, "%s: unable to parse flags: %v %v\n", name, args, err) return 1 } cfg := getConfig(fs) if !setServiceConfig(cfg) { fs.Usage() return 2 } kern := kernel.Main var createManager kernel.CreateBoxManagerFunc |
︙ | ︙ | |||
286 287 288 289 290 291 292 | return nil }, ) if command.Simple { kern.SetConfig(kernel.ConfigService, kernel.ConfigSimpleMode, "true") } | | | | 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 | return nil }, ) if command.Simple { kern.SetConfig(kernel.ConfigService, kernel.ConfigSimpleMode, "true") } kern.Start(command.Header, command.LineServer) exitCode, err := command.Func(fs) if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", name, err) } kern.Shutdown(true) return exitCode } // runSimple is called, when the user just starts the software via a double click // or via a simple call “./zettelstore“ on the command line. func runSimple() int { if _, err := searchAndReadConfiguration(); err == nil { return executeCommand(strRunSimple) } dir := "./zettel" if err := os.MkdirAll(dir, 0750); err != nil { fmt.Fprintf(os.Stderr, "Unable to create zettel directory %q (%s)\n", dir, err) return 1 } |
︙ | ︙ |
Changes to cmd/register.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 23 | _ "zettelstore.de/z/box/compbox" // Allow to use computed box. _ "zettelstore.de/z/box/constbox" // Allow to use global internal box. _ "zettelstore.de/z/box/dirbox" // Allow to use directory box. _ "zettelstore.de/z/box/filebox" // Allow to use file box. _ "zettelstore.de/z/box/membox" // Allow to use in-memory box. _ "zettelstore.de/z/encoder/htmlenc" // Allow to use HTML encoder. _ "zettelstore.de/z/encoder/mdenc" // Allow to use markdown encoder. _ "zettelstore.de/z/encoder/shtmlenc" // Allow to use SHTML encoder. | > < | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | _ "zettelstore.de/z/box/compbox" // Allow to use computed box. _ "zettelstore.de/z/box/constbox" // Allow to use global internal box. _ "zettelstore.de/z/box/dirbox" // Allow to use directory box. _ "zettelstore.de/z/box/filebox" // Allow to use file box. _ "zettelstore.de/z/box/membox" // Allow to use in-memory box. _ "zettelstore.de/z/encoder/htmlenc" // Allow to use HTML encoder. _ "zettelstore.de/z/encoder/mdenc" // Allow to use markdown encoder. _ "zettelstore.de/z/encoder/sexprenc" // Allow to use sexpr encoder. _ "zettelstore.de/z/encoder/shtmlenc" // Allow to use SHTML encoder. _ "zettelstore.de/z/encoder/textenc" // Allow to use text encoder. _ "zettelstore.de/z/encoder/zmkenc" // Allow to use zmk encoder. _ "zettelstore.de/z/kernel/impl" // Allow kernel implementation to create itself _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. _ "zettelstore.de/z/parser/draw" // Allow to use draw parser. _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. _ "zettelstore.de/z/parser/none" // Allow to use none parser. _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. ) |
Changes to config/config.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | // Package config provides functions to retrieve runtime configuration data. package config import ( "context" | | | | < < < < | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | // Package config provides functions to retrieve runtime configuration data. package config import ( "context" "zettelstore.de/z/domain/meta" ) // Key values that are supported by Config.Get const ( KeyFooterZettel = "footer-zettel" KeyHomeZettel = "home-zettel" // api.KeyLang ) // Config allows to retrieve all defined configuration values that can be changed during runtime. type Config interface { AuthConfig |
︙ | ︙ |
Changes to docs/development/20210916193200.zettel.
1 2 3 4 5 | id: 20210916193200 title: Required Software role: zettel syntax: zmk created: 20210916193200 | | > > > > > < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | id: 20210916193200 title: Required Software role: zettel syntax: zmk created: 20210916193200 modified: 20230327165135 The following software must be installed: * A current, supported [[release of Go|https://go.dev/doc/devel/release]], * [[shadow|https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow]] via ``go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest``, * [[staticcheck|https://staticcheck.io/]] via ``go install honnef.co/go/tools/cmd/staticcheck@latest``, * [[unparam|https://mvdan.cc/unparam]][^[[GitHub|https://github.com/mvdan/unparam]]] via ``go install mvdan.cc/unparam@latest``, * [[govulncheck|https://golang.org/x/vuln/cmd/govulncheck]] via ``go install golang.org/x/vuln/cmd/govulncheck@latest``, * [[Fossil|https://fossil-scm.org/]], * [[Git|https://git-scm.org/]] (most dependencies are accessible via Git only). Make sure that the software is in your path, e.g. via: ```sh export PATH=$PATH:/usr/local/go/bin export PATH=$PATH:$(go env GOPATH)/bin ``` |
Changes to docs/development/20210916194900.zettel.
1 2 3 4 | id: 20210916194900 title: Checklist for Release role: zettel syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 20210916194900 title: Checklist for Release role: zettel syntax: zmk modified: 20220309105459 # Sync with the official repository #* ``fossil sync -u`` # Make sure that there is no workspace defined. #* ``ls ..`` must not have a file ''go.work'', in no parent folder. # Make sure that all dependencies are up-to-date. #* ``cat go.mod`` |
︙ | ︙ | |||
46 47 48 49 50 51 52 | # Clean up your Go workspace: #* ``go run tools/build.go clean`` (alternatively: ``make clean``). # Create the release: #* ``go run tools/build.go release`` (alternatively: ``make release``). # Remove previous executables: #* ``fossil uv remove --glob '*-PREVVERSION*'`` # Add executables for release: | | | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | # Clean up your Go workspace: #* ``go run tools/build.go clean`` (alternatively: ``make clean``). # Create the release: #* ``go run tools/build.go release`` (alternatively: ``make release``). # Remove previous executables: #* ``fossil uv remove --glob '*-PREVVERSION*'`` # Add executables for release: #* ``cd release`` #* ``fossil uv add *.zip`` #* ``cd ..`` #* Synchronize with main repository: #* ``fossil sync -u`` # Enable autosync: #* ``fossil setting autosync on`` |
Changes to docs/manual/00001000000000.zettel.
1 2 3 4 5 | id: 00001000000000 title: Zettelstore Manual role: manual tags: #manual #zettelstore syntax: zmk | < | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | id: 00001000000000 title: Zettelstore Manual role: manual tags: #manual #zettelstore syntax: zmk modified: 20220803183647 * [[Introduction|00001001000000]] * [[Design goals|00001002000000]] * [[Installation|00001003000000]] * [[Configuration|00001004000000]] * [[Structure of Zettelstore|00001005000000]] * [[Layout of a zettel|00001006000000]] * [[Zettelmarkup|00001007000000]] * [[Other markup languages|00001008000000]] * [[Security|00001010000000]] * [[API|00001012000000]] * [[Web user interface|00001014000000]] * [[Tips and Tricks|00001017000000]] * [[Troubleshooting|00001018000000]] * Frequently asked questions Licensed under the EUPL-1.2-or-later. |
Deleted docs/manual/00001000000001.zettel.
|
| < < < < < < < < |
Changes to docs/manual/00001002000000.zettel.
1 2 3 4 5 6 | id: 00001002000000 title: Design goals for the Zettelstore role: manual tags: #design #goal #manual #zettelstore syntax: zmk created: 20210126175322 | | < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | id: 00001002000000 title: Design goals for the Zettelstore role: manual tags: #design #goal #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20221018105415 Zettelstore supports the following design goals: ; Longevity of stored notes / zettel : Every zettel you create should be readable without the help of any tool, even without Zettelstore. : It should be not hard to write other software that works with your zettel. ; Single user : All zettel belong to you, only to you. Zettelstore provides its services only to one person: you. If the computer running Zettelstore is securely configured, there should be no risk that others are able to read or update your zettel. : If you want, you can customize Zettelstore in a way that some specific or all persons are able to read some of your zettel. ; Ease of installation : If you want to use the Zettelstore software, all you need is to copy the executable to an appropriate file directory and start working. |
︙ | ︙ | |||
35 36 37 38 39 40 41 | ; Simple service : The purpose of Zettelstore is to safely store your zettel and to provide some initial relations between them. : External software can be written to deeply analyze your zettel and the structures they form. ; Security by default : Without any customization, Zettelstore provides its services in a safe and secure manner and does not expose you (or other users) to security risks. : If you know what use are doing, Zettelstore allows you to relax some security-related preferences. However, even in this case, the more secure way is chosen. | < < | 31 32 33 34 35 36 37 | ; Simple service : The purpose of Zettelstore is to safely store your zettel and to provide some initial relations between them. : External software can be written to deeply analyze your zettel and the structures they form. ; Security by default : Without any customization, Zettelstore provides its services in a safe and secure manner and does not expose you (or other users) to security risks. : If you know what use are doing, Zettelstore allows you to relax some security-related preferences. However, even in this case, the more secure way is chosen. |
Changes to docs/manual/00001004020000.zettel.
1 2 3 4 5 6 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | < < < < | | 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 | id: 00001004020000 title: Configure the running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20230317183435 You can configure a running Zettelstore by modifying the special zettel with the ID [[00000000000100]]. This zettel is called __configuration zettel__. The following metadata keys change the appearance / behavior of Zettelstore. Some of them can be overwritten in an [[user zettel|00001010040200]], a subset of those may be overwritten in zettel that is currently used. See the full list of [[metadata that may be overwritten|00001004020200]]. ; [!default-copyright|''default-copyright''] : Copyright value to be used when rendering content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''copyright''. Default: (the empty string). ; [!default-license|''default-license''] : License value to be used when rendering content. Can be overwritten in a zettel with [[meta key|00001006020000]] ''license''. Default: (the empty string). ; [!default-visibility|''default-visibility''] : Visibility to be used, if zettel does not specify a value for the [[''visibility''|00001006020000#visibility]] metadata key. Default: ""login"". ; [!expert-mode|''expert-mode''] : If set to a [[boolean true value|00001006030500]], all zettel with [[visibility ""expert""|00001010070200]] will be shown (to the owner, if [[authentication is enabled|00001010040100]]; to all, otherwise). This affects most computed zettel. Default: ""False"". ; [!footer-zettel|''footer-zettel''] : Identifier of a zettel that is rendered as HTML and will be placed as the footer of every zettel in the [[web user interface|00001014000000]]. Zettel content, delivered via the [[API|00001012000000]] as JSON, etc. is not affected. If the zettel identifier is invalid or references a zettel that could not be read (possibly because of a limited [[visibility setting|00001010070200]]), nothing is written as the footer. May be [[overwritten|00001004020200]] in a user zettel. Default: (an invalid zettel identifier) ; [!home-zettel|''home-zettel''] : Specifies the identifier of the zettel, that should be presented for the default view / home view. |
︙ | ︙ | |||
54 55 56 57 58 59 60 | This value is used as a default value, if it is not set in an user's zettel or in a zettel. It is also used to specify the language for all non-zettel content, e.g. lists or search results. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. ; [!max-transclusions|''max-transclusions''] : Maximum number of indirect transclusion. This is used to avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]]. | < < < < < < < < < < < < < < < < < | 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 | This value is used as a default value, if it is not set in an user's zettel or in a zettel. It is also used to specify the language for all non-zettel content, e.g. lists or search results. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. ; [!max-transclusions|''max-transclusions''] : Maximum number of indirect transclusion. This is used to avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]]. Default: ""1024"". ; [!site-name|''site-name''] : Name of the Zettelstore instance. Will be used when displaying some lists. Default: ""Zettelstore"". ; [!yaml-header|''yaml-header''] : If [[true|00001006030500]], metadata and content will be separated by ''---\\n'' instead of an empty line (''\\n\\n''). Default: ""False"". You will probably use this key, if you are working with another software processing [[Markdown|https://daringfireball.net/projects/markdown/]] that uses a subset of [[YAML|https://yaml.org/]] to specify metadata. ; [!zettel-file-syntax|''zettel-file-syntax''] : If you create a new zettel with a syntax different to ""zmk"", Zettelstore will store the zettel as two files: one for the metadata (file without a filename extension) and another for the content (file extension based on the syntax value). If you want to specify alternative syntax values, for which you want new zettel to be stored in one file (file extension ''.zettel''), you can use this key. All values are case-insensitive, duplicate values are removed. For example, you could use this key if you're working with Markdown syntax and you want to store metadata and content in one ''.zettel'' file. If ''yaml-header'' evaluates to true, a zettel is always stored in one ''.zettel'' file. |
Changes to docs/manual/00001004020200.zettel.
1 2 3 4 5 6 | id: 00001004020200 title: Runtime configuration data that may be user specific or zettel specific role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20221205155521 | | < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | id: 00001004020200 title: Runtime configuration data that may be user specific or zettel specific role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20221205155521 modified: 20230317183403 Some metadata of the [[runtime configuration|00001004020000]] may be overwritten in an [[user zettel|00001010040200]]. A subset of those may be overwritten in zettel that is currently used. This allows to specify user specific or zettel specific behavior. The following metadata keys are supported to provide a more specific behavior: |=Key|User:|Zettel:|Remarks |[[''footer-zettel''|00001004020000#footer-zettel]]|Y|N| |[[''home-zettel''|00001004020000#home-zettel]]|Y|N| |[[''lang''|00001004020000#lang]]|Y|Y|Making it user-specific could make zettel for other user less useful |
Changes to docs/manual/00001004051200.zettel.
︙ | ︙ | |||
13 14 15 16 17 18 19 20 | ``` ; ''-t FORMAT'' : Specifies the output format. Supported values are: [[''html''|00001012920510]] (default), [[''md''|00001012920513]], [[''shtml''|00001012920525]], | > < | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | ``` ; ''-t FORMAT'' : Specifies the output format. Supported values are: [[''html''|00001012920510]] (default), [[''md''|00001012920513]], [[''sexpr''|00001012920516]], [[''shtml''|00001012920525]], [[''text''|00001012920519]], and [[''zmk''|00001012920522]]. ; ''file-1'' : Specifies the file name, where at least metadata is read. If ''file-2'' is not given, the zettel content is also read from here. ; ''file-2'' : File name where the zettel content is stored. If neither ''file-1'' nor ''file-2'' are given, metadata and zettel content are read from standard input / stdin. |
Changes to docs/manual/00001005090000.zettel.
1 2 3 4 5 6 | id: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20220909180240 The following table lists all predefined zettel with their purpose. |= Identifier :|= Title | Purpose | [[00000000000001]] | Zettelstore Version | Contains the version string of the running Zettelstore | [[00000000000002]] | Zettelstore Host | Contains the name of the computer running the Zettelstore | [[00000000000003]] | Zettelstore Operating System | Contains the operating system and CPU architecture of the computer running the Zettelstore |
︙ | ︙ | |||
25 26 27 28 29 30 31 | | [[00000000010200]] | Zettelstore Login Form HTML Template | Layout of the login form, when authentication is [[enabled|00001010040100]] | [[00000000010300]] | Zettelstore List Zettel HTML Template | Used when displaying a list of zettel | [[00000000010401]] | Zettelstore Detail HTML Template | Layout for the HTML detail view of one zettel | [[00000000010402]] | Zettelstore Info HTML Template | Layout for the information view of a specific zettel | [[00000000010403]] | Zettelstore Form HTML Template | Form that is used to create a new or to change an existing zettel that contains text | [[00000000010404]] | Zettelstore Rename Form HTML Template | View that is displayed to change the [[zettel identifier|00001006050000]] | [[00000000010405]] | Zettelstore Delete HTML Template | View to confirm the deletion of a zettel | < < < > < < < < | | < < | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | | [[00000000010200]] | Zettelstore Login Form HTML Template | Layout of the login form, when authentication is [[enabled|00001010040100]] | [[00000000010300]] | Zettelstore List Zettel HTML Template | Used when displaying a list of zettel | [[00000000010401]] | Zettelstore Detail HTML Template | Layout for the HTML detail view of one zettel | [[00000000010402]] | Zettelstore Info HTML Template | Layout for the information view of a specific zettel | [[00000000010403]] | Zettelstore Form HTML Template | Form that is used to create a new or to change an existing zettel that contains text | [[00000000010404]] | Zettelstore Rename Form HTML Template | View that is displayed to change the [[zettel identifier|00001006050000]] | [[00000000010405]] | Zettelstore Delete HTML Template | View to confirm the deletion of a zettel | [[00000000020001]] | Zettelstore Base CSS | System-defined CSS file that is included by the [[Base HTML Template|00000000010100]] | [[00000000025001]] | Zettelstore User CSS | User-defined CSS file that is included by the [[Base HTML Template|00000000010100]] | [[00000000029000]] | Zettelstore Role to CSS Map | [[Maps|00001017000000#role-css]] [[role|00001006020000#role]] to a zettel identifier that is included by the [[Base HTML Template|00000000010100]] as an CSS file | [[00000000040001]] | Generic Emoji | Image that is shown if [[original image reference|00001007040322]] is invalid | [[00000000090000]] | New Menu | Contains items that should contain in the zettel template menu | [[00000000090001]] | New Zettel | Template for a new zettel with role ""[[zettel|00001006020100]]"" | [[00000000090002]] | New User | Template for a new [[user zettel|00001010040200]] | [[00010000000000]] | Home | Default home zettel, contains some welcome information If a zettel is not linked, it is not accessible for the current user. **Important:** All identifier may change until a stable version of the software is released. |
Changes to docs/manual/00001006000000.zettel.
1 2 3 4 5 6 | id: 00001006000000 title: Layout of a Zettel role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001006000000 title: Layout of a Zettel role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20221219162703 A zettel consists of two parts: the metadata and the zettel content. Metadata gives some information mostly about the zettel content, how it should be interpreted, how it is sorted within Zettelstore. The zettel content is, well, the actual content. In many cases, the content is in plain text form. Plain text is long-lasting. However, content in binary format is also possible. |
︙ | ︙ | |||
28 29 30 31 32 33 34 | Other character encodings are not supported and will never be[^This is not a real problem, since every modern software should support UTF-8 as an encoding.]. There is support for a graphical format with a text representation: SVG. And there is support for some binary image formats, like GIF, PNG, and JPEG. === Plain, parsed, and evaluated zettel Zettelstore may present your zettel in various forms, typically retrieved with the [[endpoint|00001012920000]] ''/z/{ID}''. One way is to present the zettel as it was read by Zettelstore. | | | | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | Other character encodings are not supported and will never be[^This is not a real problem, since every modern software should support UTF-8 as an encoding.]. There is support for a graphical format with a text representation: SVG. And there is support for some binary image formats, like GIF, PNG, and JPEG. === Plain, parsed, and evaluated zettel Zettelstore may present your zettel in various forms, typically retrieved with the [[endpoint|00001012920000]] ''/z/{ID}''. One way is to present the zettel as it was read by Zettelstore. This is called ""[[plain zettel|00001003000000]]"". The second way is to present the zettel as it was recognized by Zettelstore. This is called ""[[parsed zettel|00001012053600]]"", also retrieved with the [[endpoint|00001012920000]] ''/z/{ID}'', but with additional query parameter. Such a zettel was read and analyzed. It can be presented in various [[encodings|00001012920500]].[^The [[zmk encoding|00001012920522]] allows you to compare the plain, the parsed, and the evaluated form of a zettel.] However, a zettel such as this one you are currently reading, is a ""[[evaluated zettel|00001012053500]]"", also retrieved with the [[endpoint|00001012920000]] ''/z/{ID}'' and specifying an encoding. The biggest difference to a parsed zettel is the inclusion of [[block transclusions|00001007031100]] or [[inline transclusions|00001007040324]] for an evaluated zettel. It can also be presented in various encoding, including the ""zmk"" encoding. Evaluations also applies to metadata of a zettel, if appropriate. Please note, that searching for content is based on parsed zettel. Transcluded content will only be found in transcluded zettel, but not in the zettel that transcluded the content. However, you will easily pick up that zettel by follow the [[backward|00001006020000#backward]] metadata key of the transcluded zettel. |
Changes to docs/manual/00001006020000.zettel.
1 2 3 4 5 6 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 modified: 20230320150950 Although you are free to define your own metadata, by using any key (according to the [[syntax|00001006010000]]), some keys have a special meaning that is enforced by Zettelstore. See the [[computed list of supported metadata keys|00000000000090]] for details. Most keys conform to a [[type|00001006030000]]. ; [!author|''author''] |
︙ | ︙ | |||
40 41 42 43 44 45 46 | ; [!credential|''credential''] : Contains the hashed password, as it was emitted by [[``zettelstore password``|00001004051400]]. It is internally created by hashing the password, the [[zettel identifier|00001006050000]], and the value of the ''ident'' key. It is only used for zettel with a ''role'' value of ""user"". ; [!dead|''dead''] : Property that contains all references that does __not__ identify a zettel. | < < < < < < < < < | 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | ; [!credential|''credential''] : Contains the hashed password, as it was emitted by [[``zettelstore password``|00001004051400]]. It is internally created by hashing the password, the [[zettel identifier|00001006050000]], and the value of the ''ident'' key. It is only used for zettel with a ''role'' value of ""user"". ; [!dead|''dead''] : Property that contains all references that does __not__ identify a zettel. ; [!folge|''folge''] : Is a property that contains identifier of all zettel that reference this zettel through the [[''precursor''|#precursor]] value. ; [!forward|''forward''] : Property that contains all references that identify another zettel within the content of the zettel. ; [!id|''id''] : Contains the [[zettel identifier|00001006050000]], as given by the Zettelstore. It cannot be set manually, because it is a computed value. ; [!lang|''lang''] : Language for the zettel. |
︙ | ︙ | |||
101 102 103 104 105 106 107 | : Marks a zettel as read-only. The interpretation of [[supported values|00001006020400]] for this key depends, whether authentication is [[enabled|00001010040100]] or not. ; [!role|''role''] : Defines the role of the zettel. Can be used for selecting zettel. See [[supported zettel roles|00001006020100]]. If not given, it is ignored. | < < < < < | 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | : Marks a zettel as read-only. The interpretation of [[supported values|00001006020400]] for this key depends, whether authentication is [[enabled|00001010040100]] or not. ; [!role|''role''] : Defines the role of the zettel. Can be used for selecting zettel. See [[supported zettel roles|00001006020100]]. If not given, it is ignored. ; [!successors|''successors''] : Is a property that contains identifier of all zettel that reference this zettel through the [[''predecessor''|#predecessor]] value. Therefore, it references all zettel that contain a new version of the content and/or metadata. In contrast to [[''folge''|#folge]], these are references because of technical reasons, not because of content-related reasons. In most cases, zettel referencing the current zettel should be updated to reference a successor zettel. The [[query reference|00001007040310]] [[query:backward? successors?]] lists all such zettel. ; [!summary|''summary''] : Summarizes the content of the zettel. You may use all [[inline-structued elements|00001007040000]] of Zettelmarkup. ; [!syntax|''syntax''] : Specifies the syntax that should be used for interpreting the zettel. The zettel about [[other markup languages|00001008000000]] defines supported values. If it is not given, it defaults to ''plain''. ; [!tags|''tags''] : Contains a space separated list of tags to describe the zettel further. Each Tag must begin with the number sign character (""''#''"", U+0023). |
︙ | ︙ |
Changes to docs/manual/00001006020100.zettel.
1 2 3 4 5 | id: 00001006020100 title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk | < | | < < < < < < < < < | | | 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 | id: 00001006020100 title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20220623183234 The [[''role'' key|00001006020000#role]] defines what kind of zettel you are writing. You are free to define your own roles. It is allowed to set an empty value or to omit the role. Some roles are defined for technical reasons: ; [!configuration|''configuration''] : A zettel that contains some configuration data for the Zettelstore. Most prominent is [[00000000000100]], as described in [[00001004020000]]. ; [!manual|''manual''] : All zettel that document the inner workings of the Zettelstore software. This role is only used in this specific Zettelstore. If you adhere to the process outlined by Niklas Luhmann, a zettel could have one of the following three roles: ; [!note|''note''] : A small note, to remember something. Notes are not real zettel, they just help to create a real zettel. Think of them as Post-it notes. ; [!literature|''literature''] : Contains some remarks about a book, a paper, a web page, etc. You should add a citation key for citing it. ; [!zettel|''zettel''] : A real zettel that contains your own thoughts. However, you are free to define additional roles, e.g. ''material'' for literature that is web-based only, ''slide'' for presentation slides, ''paper'' for the text of a scientific paper, ''project'' to define a project, ... |
Changes to docs/manual/00001006030000.zettel.
1 2 3 4 5 | id: 00001006030000 title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk | < | < < | | 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 | id: 00001006030000 title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20220304114106 All [[supported metadata keys|00001006020000]] conform to a type. User-defined metadata keys conform also to a type, based on the suffix of the key. |=Suffix|Type | ''-number'' | [[Number|00001006033000]] | ''-role'' | [[Word|00001006035500]] | ''-set'' | [[WordSet|00001006036000]] | ''-title'' | [[Zettelmarkup|00001006036500]] | ''-url'' | [[URL|00001006035000]] | ''-zettel'' | [[Identifier|00001006032000]] | ''-zid'' | [[Identifier|00001006032000]] | ''-zids'' | [[IdentifierSet|00001006032500]] | any other suffix | [[EString|00001006031500]] The name of the metadata key is bound to the key type Every key type has an associated validation rule to check values of the given type. There is also a rule how values are matched, e.g. against a search term when selecting some zettel. And there is a rule how values compare for sorting. * [[Credential|00001006031000]] * [[EString|00001006031500]] * [[Identifier|00001006032000]] * [[IdentifierSet|00001006032500]] * [[Number|00001006033000]] |
︙ | ︙ |
Changes to docs/manual/00001006031000.zettel.
1 2 3 4 5 6 | id: 00001006031000 title: Credential Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | id: 00001006031000 title: Credential Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130324 Values of this type denote a credential value, e.g. an encrypted password. === Allowed values All printable characters are allowed. Since a credential contains some kind of secret, the sequence of characters might have some hidden syntax to be interpreted by other parts of Zettelstore. === Query operators A credential never compares to any other value. A comparison will never match in any way. === Sorting If a list of zettel should be sorted based on a credential value, the identifier of the respective zettel is used instead. |
Changes to docs/manual/00001006031500.zettel.
1 2 3 4 5 6 | id: 00001006031500 title: EString Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 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 | id: 00001006031500 title: EString Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130448 Values of this type are just a sequence of character, possibly an empty sequence. An EString is the most general metadata key type, as it places no restrictions to the character sequence.[^Well, there are some minor restrictions that follow from the [[metadata syntax|00001006010000]].] === Allowed values All printable characters are allowed. === Query operator All comparisons are done case-insensitive, i.e. ""hell"" will be the prefix of ""Hello"". === Sorting To sort two values, the underlying encoding is used to determine which value is less than the other. Uppercase letters are typically interpreted as less than their corresponding lowercase letters, i.e. ``A < a``. Comparison is done character-wise by finding the first difference in the respective character sequence. For example, ``abc > aBc``. |
Changes to docs/manual/00001006032000.zettel.
1 2 3 4 5 6 | id: 00001006032000 title: Identifier Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | < < < < < < < | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | id: 00001006032000 title: Identifier Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914134914 Values of this type denote a [[zettel identifier|00001006050000]]. === Allowed values Must be a sequence of 14 digits (""0""--""9""). === Query operator Comparison is done with the string representation of the identifiers. For example, ""000010"" matches ""[[00001006032000]]"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. If both values are identifiers, this works well because both have the same length. |
Changes to docs/manual/00001006032500.zettel.
1 2 3 4 5 6 | id: 00001006032500 title: IdentifierSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | id: 00001006032500 title: IdentifierSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914131354 Values of this type denote a (sorted) set of [[zettel identifier|00001006050000]]. A set is different to a list, as no duplicate values are allowed. === Allowed values Must be at least one sequence of 14 digits (""0""--""9""), separated by space characters. === Query operator A value matches an identifier set value, if the value matches any of the identifier set values. For example, ""000010060325"" is a prefix ""[[00001006032000]] [[00001006032500]]"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006033000.zettel.
1 2 3 4 5 6 | id: 00001006033000 title: Number Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | < < < < < | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | id: 00001006033000 title: Number Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914131211 Values of this type denote a numeric integer value. === Allowed values Must be a sequence of digits (""0""--""9""), optionally prefixed with a ""-"" or a ""+"" character. === Query operator All comparisons are done on the given string representation of the number, ""+12"" will be treated as a different number of ""12"". === Sorting Sorting is done by comparing the numeric values. |
Changes to docs/manual/00001006033500.zettel.
1 2 3 4 5 6 | id: 00001006033500 title: String Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | id: 00001006033500 title: String Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130505 Values of this type are just a sequence of character, but not an empty sequence. === Allowed values All printable characters are allowed. There must be at least one such character. === Query operator All comparisons are done case-insensitive, i.e. ""hell"" will be the prefix of ""Hello"". === Sorting To sort two values, the underlying encoding is used to determine which value is less than the other. Uppercase letters are typically interpreted as less than their corresponding lowercase letters, i.e. ``A < a``. Comparison is done character-wise by finding the first difference in the respective character sequence. For example, ``abc > aBc``. |
Changes to docs/manual/00001006034000.zettel.
1 2 3 4 5 6 | id: 00001006034000 title: TagSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | id: 00001006034000 title: TagSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914131048 Values of this type denote a (sorted) set of tags. A set is different to a list, as no duplicate values are allowed. === Allowed values Every tag must must begin with the number sign character (""''#''"", U+0023), followed by at least one printable character. Tags are separated by space characters. All characters are mapped to their lower case values. === Query operator All comparisons are done case-sensitive, i.e. ""#hell"" will not be the prefix of ""#Hello"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006034500.zettel.
1 2 3 4 5 6 | id: 00001006034500 title: Timestamp Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | < < < < < < < < | < < | < < | > > | 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 | id: 00001006034500 title: Timestamp Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130919 Values of this type denote a point in time. === Allowed values Must be a sequence of 14 digits (""0""--""9"") (same as an [[Identifier|00001006032000]]), with the restriction that is conforms to the pattern ""YYYYMMDDhhmmss"". * YYYY is the year, * MM is the month, * DD is the day, * hh is the hour, * mm is the minute, * ss is the second. === Query operator All comparisons assume that up to 14 digits are given. === Sorting Sorting is done by comparing the [[String|00001006033500]] values. If both values are timestamp values, this works well because both have the same length. |
Changes to docs/manual/00001006035000.zettel.
1 2 3 4 5 6 | id: 00001006035000 title: URL Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | id: 00001006035000 title: URL Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130809 Values of this type denote an URL. === Allowed values All characters of an URL / URI are allowed. === Query operator All comparisons are done case-insensitive. For example, ""hello"" is the suffix of ""http://example.com/Hello"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006035500.zettel.
1 2 3 4 5 6 | id: 00001006035500 title: Word Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | id: 00001006035500 title: Word Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130655 Values of this type denote a single word. === Allowed values Must be a non-empty sequence of characters, but without the space character. All characters are mapped to their lower case values. === Query operator All comparisons are done case-insensitive, i.e. ""hell"" will be the prefix of ""Hello"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006036000.zettel.
1 2 3 4 5 6 | id: 00001006036000 title: WordSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | id: 00001006036000 title: WordSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914130725 Values of this type denote a (sorted) set of [[words|00001006035500]]. A set is different to a list, as no duplicate values are allowed. === Allowed values Must be a sequence of at least one word, separated by space characters. === Query operator All comparisons are done case-insensitive, i.e. ""hell"" will be the prefix of ""World, Hello"". === Sorting Sorting is done by comparing the [[String|00001006033500]] values. |
Changes to docs/manual/00001006036500.zettel.
1 2 3 4 5 6 | id: 00001006036500 title: Zettelmarkup Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 | | | | 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 | id: 00001006036500 title: Zettelmarkup Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220914135405 Values of this type are [[String|00001006033500]] values, interpreted as [[Zettelmarkup|00001007000000]]. === Allowed values All printable characters are allowed. There must be at least one such character. === Query operator Comparison is done similar to the full-text search: both the value to compare and the metadata value are normalized according to Unicode NKFD, ignoring everything except letters and numbers. Letters are mapped to the corresponding lower-case value. For example, ""Brücke"" will be the prefix of ""(Bruckenpfeiler,"". === Sorting To sort two values, the underlying encoding is used to determine which value is less than the other. Uppercase letters are typically interpreted as less than their corresponding lowercase letters, i.e. ``A < a``. Comparison is done character-wise by finding the first difference in the respective character sequence. For example, ``abc > aBc``. |
Changes to docs/manual/00001007030900.zettel.
1 2 3 4 5 | id: 00001007030900 title: Zettelmarkup: Comment Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | id: 00001007030900 title: Zettelmarkup: Comment Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218130330 Comment blocks are quite similar to [[verbatim blocks|00001007030500]]: both are used to enter text that should not be interpreted. While the text entered inside a verbatim block will be processed somehow, text inside a comment block will be ignored[^Well, not completely ignored: text is read, but it will typically not rendered visible.]. Comment blocks are typically used to give some internal comments, e.g. the license of a text or some internal remarks. Comment blocks begin with at least three percent sign characters (""''%''"", U+0025) at the first position of a line. You can add some [[attributes|00001007050000]] on the beginning line of a comment block, following the initiating characters. The comment block supports the default attribute: when given, the text will be rendered, e.g. as an HTML comment. When rendered to JSON, the comment block will not be ignored but it will output some JSON text. Same for other renderer. Any other character in this line will be ignored Text following the beginning line will not be interpreted, until a line begins with at least the same number of the same characters given at the beginning line. This allows to enter some percent sign characters in the text that should not be interpreted. |
︙ | ︙ |
Changes to docs/manual/00001007031140.zettel.
1 2 3 4 5 6 | id: 00001007031140 title: Zettelmarkup: Query Transclusion role: manual tags: #manual #search #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007031140 title: Zettelmarkup: Query Transclusion role: manual tags: #manual #search #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 modified: 20230116183656 A query transclusion is specified by the following sequence, starting at the first position in a line: ''{{{query:query-expression}}}''. The line must literally start with the sequence ''{{{query:''. Everything after this prefix is interpreted as a [[query expression|00001007700000]]. When evaluated, the query expression is evaluated, often resulting in a list of [[links|00001007040310]] to zettel, matching the query expression. The result replaces the query transclusion element. |
︙ | ︙ | |||
45 46 47 48 49 50 51 | : Transform the zettel list into an [[Atom 1.0|https://www.rfc-editor.org/rfc/rfc4287]]-conformant document / feed. The document is embedded into the referencing zettel. ; ''RSS'' (aggregate) : Transform the zettel list into a [[RSS 2.0|https://www.rssboard.org/rss-specification]]-conformant document / feed. The document is embedded into the referencing zettel. ; ''KEYS'' (aggregate) : Emit a list of all metadata keys, together with the number of zettel having the key. | < < < | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | : Transform the zettel list into an [[Atom 1.0|https://www.rfc-editor.org/rfc/rfc4287]]-conformant document / feed. The document is embedded into the referencing zettel. ; ''RSS'' (aggregate) : Transform the zettel list into a [[RSS 2.0|https://www.rssboard.org/rss-specification]]-conformant document / feed. The document is embedded into the referencing zettel. ; ''KEYS'' (aggregate) : Emit a list of all metadata keys, together with the number of zettel having the key. ; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]], [[WordSet|00001006036000]], or [[TagSet|00001006034000]] (aggregates) : Emit an aggregate of the given metadata key. The key can be given in any letter case[^Except if the key name collides with one of the above names. In this case use at least one lower case letter.]. Example: ```zmk {{{query:tags:#search | tags}}} ``` This is a tag cloud of all tags that are used together with the tag #search: :::example {{{query:tags:#search | tags}}} ::: |
Changes to docs/manual/00001007031300.zettel.
︙ | ︙ | |||
10 11 12 13 14 15 16 | They begin with at least three tilde characters (""''~''"", U+007E) at the first position of a line. You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. The evaluation block supports the default attribute[^Depending on the syntax value.]: when given, all spaces in the text are rendered in HTML as open box characters (U+2423). If you want to give only one attribute and this attribute is the generic attribute, you can omit the most of the attribute syntax and just specify the value. It will be interpreted as a [[syntax|00001008000000]] value to evaluate its content. Not all syntax values are supported by Zettelstore.[^Currently just ""[[draw|00001008050000]]"".] | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | They begin with at least three tilde characters (""''~''"", U+007E) at the first position of a line. You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. The evaluation block supports the default attribute[^Depending on the syntax value.]: when given, all spaces in the text are rendered in HTML as open box characters (U+2423). If you want to give only one attribute and this attribute is the generic attribute, you can omit the most of the attribute syntax and just specify the value. It will be interpreted as a [[syntax|00001008000000]] value to evaluate its content. Not all syntax values are supported by Zettelstore.[^Currently just ""[[draw|00001008050000]]"".] The main reason for an evaluation block is to be used with external software via the [[Sexpr encoding|00001012920516]]. Any other character in this line will be ignored Text following the beginning line will not be interpreted, until a line begins with at least the same number of the same characters given at the beginning line. This allows to enter some tilde characters in the text that should not be interpreted. For example: |
︙ | ︙ |
Changes to docs/manual/00001007031400.zettel.
︙ | ︙ | |||
12 13 14 15 16 17 18 | You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. A math-mode block supports the default attribute[^Depending on the syntax value.]: when given, all spaces in the text are rendered in HTML as open box characters (U+2423). If you want to give only one attribute and this attribute is the generic attribute, you can omit the most of the attribute syntax and just specify the value. It will be interpreted as a [[syntax|00001008000000]] value to evaluate its content. Alternatively, you could provide an attribute with the key ""syntax"" and use the value to specify the syntax. Not all syntax values are supported by Zettelstore.[^Currently: none.] | | | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. A math-mode block supports the default attribute[^Depending on the syntax value.]: when given, all spaces in the text are rendered in HTML as open box characters (U+2423). If you want to give only one attribute and this attribute is the generic attribute, you can omit the most of the attribute syntax and just specify the value. It will be interpreted as a [[syntax|00001008000000]] value to evaluate its content. Alternatively, you could provide an attribute with the key ""syntax"" and use the value to specify the syntax. Not all syntax values are supported by Zettelstore.[^Currently: none.] External software might support several values via the [[Sexpr encoding|00001012920516]]. Any other character in this line will be ignored Text following the beginning line will not be interpreted, until a line begins with at least the same number of the same characters given at the beginning line. This allows to enter some dollar-sign characters in the text that should not be interpreted. For example: |
︙ | ︙ |
Changes to docs/manual/00001007040100.zettel.
1 2 3 4 5 | id: 00001007040100 title: Zettelmarkup: Text Formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007040100 title: Zettelmarkup: Text Formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131003 Text formatting is the way to make your text visually different. Every text formatting element begins with two same characters. It ends when these two same characters occur the second time. It is possible that some [[attributes|00001007050000]] follow immediately, without any separating character. Text formatting can be nested, up to a reasonable limit. |
︙ | ︙ | |||
26 27 28 29 30 31 32 | * The circumflex accent character (""''^''"", U+005E) allows to enter super-scripted text. ** Example: ``e=mc^^2^^`` is rendered in HTML as: ::e=mc^^2^^::{=example}. * The comma character (""'',''"", U+002C) produces sub-scripted text. ** Example: ``H,,2,,O`` is rendered in HTML as: ::H,,2,,O::{=example}. * The quotation mark character (""''"''"", U+0022) marks an inline quotation, according to the [[specified language|00001007050100]]. ** Example: ``""To be or not""`` is rendered in HTML as: ::""To be or not""::{=example}. ** Example: ``""Sein oder nicht""{lang=de}`` is rendered in HTML as: ::""Sein oder nicht""{lang=de}::{=example}. | < < < | 25 26 27 28 29 30 31 32 33 | * The circumflex accent character (""''^''"", U+005E) allows to enter super-scripted text. ** Example: ``e=mc^^2^^`` is rendered in HTML as: ::e=mc^^2^^::{=example}. * The comma character (""'',''"", U+002C) produces sub-scripted text. ** Example: ``H,,2,,O`` is rendered in HTML as: ::H,,2,,O::{=example}. * The quotation mark character (""''"''"", U+0022) marks an inline quotation, according to the [[specified language|00001007050100]]. ** Example: ``""To be or not""`` is rendered in HTML as: ::""To be or not""::{=example}. ** Example: ``""Sein oder nicht""{lang=de}`` is rendered in HTML as: ::""Sein oder nicht""{lang=de}::{=example}. * The colon character (""'':''"", U+003A) mark some text that should belong together. It fills a similar role as [[region blocks|00001007030800]], but just for inline elements. ** Example: ``abc ::def::{=example} ghi`` is rendered in HTML as: abc ::def::{=example} ghi. |
Changes to docs/manual/00001007700000.zettel.
1 | id: 00001007700000 | | | | | | > | > > | > | | | | > > > | | | | < | > > > > > > > > > > > > > | 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 | id: 00001007700000 title: Query expression role: manual tags: #manual #search #zettelstore syntax: zmk created: 20220805150154 modified: 20220913135434 A query expression allows you to search for specific zettel and to perform some actions on them. You may select zettel based on a full-text search, based on specific metadata values, or both. A query expression consists of a __search expression__ and of an optional __action list__. Both are separated by a vertical bar character (""''|''"", U+007C). A query expression follows a [[formal syntax|00001007780000]]. === Search expression In its simplest form, a search expression just contains a string to be search for with the help of a full-text search. For example, the string ''syntax'' will search for all zettel containing the word ""syntax"". If you want to search for all zettel with a title containing the word ""syntax"", you must specify ''title:syntax''. ""title"" names the [[metadata key|00001006010000]], in this case the [[supported metadata key ""title""|00001006020000#title]]. The colon character (""'':''"") is a [[search operator|00001007705000]], in this example to specify a match. ""syntax"" is the [[search value|00001007706000]] that must match to the value of the given metadata key, here ""title"". A search expression may contain more than one search term, such as ''title:syntax''. Search terms must be separated by one or more space characters, for example ''title:syntax title:search''. All terms of a select expression must be true so that a zettel is selected. * [[Search terms|00001007702000]] * [[Search operator|00001007705000]] * [[Search value|00001007706000]] Here are [[some examples|00001007790000]] of search expressions, which can be used to manage a Zettelstore: {{{00001007790000}}} === Action List With a search expression, a list of zettel is selected. Actions allow to modify this list to a certain degree. Which actions are allowed depends on the context. However, actions are further separated into __parameter action__ and __aggregate actions__. A parameter action just sets a parameter for an aggregate action. An aggregate action transforms the list of selected zettel into a different, aggregate form. Only the first aggregate form is executed, following aggregate actions are ignored. In most contexts, valid actions include the name of metadata keys, at least of type [[Word|00001006035500]], [[WordSet|00001006036000]], or [[TagSet|00001006034000]]. |
Deleted docs/manual/00001007701000.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00001007702000.zettel.
1 2 3 4 5 6 | id: 00001007702000 title: Search term role: manual tags: #manual #search #zettelstore syntax: zmk created: 20220805150154 | | < < < < | | | | | 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 | id: 00001007702000 title: Search term role: manual tags: #manual #search #zettelstore syntax: zmk created: 20220805150154 modified: 20230327123724 A search term allows you to specify one search restriction. The result [[search expression|00001007700000]], which contains more than one search term, will be the applications of all restrictions. A search term can be one of the following (the first three term are collectively called __search literals__): * A metadata-based search, by specifying the name of a [[metadata key|00001006010000]], followed by a [[search operator|00001007705000]], followed by an optional [[search value|00001007706000]]. All zettel containing the given metadata key with a allowed value (depending on the search operator) are selected. If no search value is given, then all zettel containing the given metadata key are selected (or ignored, for a negated search operator). * An optional [[search operator|00001007705000]], followed by a [[search value|00001007706000]]. This specifies a full-text search for the given search value. **Note:** the search value will be normalized according to Unicode NKFD, ignoring everything except letters and numbers. Therefore, the following search expression are essentially the same: ''"search syntax"'' and ''search syntax''. The first is a search expression with one search value, which is normalized to two strings to be searched for. The second is a search expression containing two search values, giving two string to be searched for. * A metadata key followed by ""''?''"" or ""''!?''"". Is true, if zettel metadata contains / does not contain the given key. * The string ''OR'' signals that following search literals may occur alternatively in the result. Since search literals may be negated, it is possible to form any boolean search expression. Any search expression will be in a [[disjunctive normal form|https://en.wikipedia.org/wiki/Disjunctive_normal_form]]. It has no effect on the following search terms initiated with a special uppercase word. * The string ''PICK'', followed by a non-empty sequence of spaces and a number greater zero (called ""N""). This will pick randomly N elements of the result list. A zero value of N will produce the same result as if nothing was specified. If specified multiple times, the lower value takes precedence. Example: ''PICK 5 PICK 3'' will be interpreted as ''PICK 3''. * The string ''ORDER'', followed by a non-empty sequence of spaces and the name of a metadata key, will specify an ordering of the result list. If you include the string ''REVERSE'' after ''ORDER'' but before the metadata key, the ordering will be reversed. Example: ''ORDER published'' will order the resulting list based on the publishing data, while ''ORDER REVERSED published'' will return a reversed result order. An explicit order field will take precedence over the random order described below. If no random order is effective, a ``ORDER REVERSE id`` will be added. This makes the sort stable. Example: ``ORDER created`` will be interpreted as ``ORDER created ORDER REVERSE id``. Any ordering by zettel identifier will make following order terms to be ignored. Example: ``ORDER id ORDER created`` will be interpreted as ``ORDER id``. * The string ''RANDOM'' will provide a random order of the resulting list. Currently, only the first term specifying the order of the resulting list will be used. Other ordering terms will be ignored. A random order specification will be ignored, if there is an explicit ordering given or if the specification contains a valid ''PICK''. Example: ''RANDOM ORDER published'' will be interpreted as ''ORDER published'', ''PICK 3 RANDOM'' will be interpreted as ''PICK 3''. * The string ''OFFSET'', followed by a non-empty sequence of spaces and a number greater zero (called ""N""). This will ignore the first N elements of the result list, based on the specified sort order. A zero value of N will produce the same result as if nothing was specified. If specified multiple times, the higher value takes precedence. Example: ''OFFSET 4 OFFSET 8'' will be interpreted as ''OFFSET 8''. |
︙ | ︙ | |||
85 86 87 88 89 90 91 | This is resolved by the following rule: * A search term containing no [[search operator character|00001007705000]] is treated as a full-text search. * The first search operator character found in a search term divides the term into two pieces. If the first piece, from the beginning of the search term to the search operator character, is syntactically a metadata key, the search term is treated as a metadata-based search. * Otherwise, the search term is treated as a full-text search. If a term like ''PICK'', ''ORDER'', ''ORDER REVERSE'', ''OFFSET'', or ''LIMIT'' is not followed by an appropriate value, it is interpreted as a search value for a full-text search. | | > > > > | 81 82 83 84 85 86 87 88 89 90 91 92 | This is resolved by the following rule: * A search term containing no [[search operator character|00001007705000]] is treated as a full-text search. * The first search operator character found in a search term divides the term into two pieces. If the first piece, from the beginning of the search term to the search operator character, is syntactically a metadata key, the search term is treated as a metadata-based search. * Otherwise, the search term is treated as a full-text search. If a term like ''PICK'', ''ORDER'', ''ORDER REVERSE'', ''OFFSET'', or ''LIMIT'' is not followed by an appropriate value, it is interpreted as a search value for a full-text search. For example, ''ORDER 123'' will search for a zettel conatining the strings ""ORDER"" (case-insensitive) and ""123"". A specification ''RANDOM LIMIT n'' (with some valid value of ''n'') will be interpreted as ''PICK n''. Similar, a specification ''PICK n'', where ''n'' is greater or equal to the length of the result list, will be interpreted as ''RANDOM''. A specification ''LIMIT n'', where ''n'' is greater or equal to the length of the result list, will be ignored effectively. |
Changes to docs/manual/00001007705000.zettel.
1 2 3 4 5 | id: 00001007705000 title: Search operator role: manual tags: #manual #search #zettelstore syntax: zmk | < | | < | | | | < < < | < | | | | | | | < < < < < < | 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 | id: 00001007705000 title: Search operator role: manual tags: #manual #search #zettelstore syntax: zmk modified: 20220819194709 A search operator specifies how the comparison of a search value and a zettel should be executed. Every comparison is done case-insensitive, treating all uppercase letters the same as lowercase letters. The following are allowed search operator characters: * The exclamation mark character (""!"", U+0021) negates the meaning * The tilde character (""''~''"", U+007E) compares on matching (""match operator"") * The greater-than sign character (""''>''"", U+003E) matches if there is some prefix (""prefix operator"") * The less-than sign character (""''<''"", U+003C) compares a suffix relationship (""suffix operator"") * The colon character (""'':''"", U+003A) compares on equal words (""has operator"") * The question mark (""''?''"", U+003F) checks for an existing metadata key (""exist operator"") Since the exclamation mark character can be combined with the other, there are 10 possible combinations: # ""''!''"": is an abbreviation of the ""''!~''"" operator. # ""''~''"": is successful if the search value matched the value to be compared. # ""''!~''"": is successful if the search value does not match the value to be compared. # ""'':''"": is successful if the search value is equal to one word of the value to be compared. # ""''!:''"": is successful if the search value is not equal to any word of the value to be compared. # ""''>''"": is successful if the search value is a prefix of the value to be compared. # ""''!>''"": is successful if the search value is not a prefix of the value to be compared. # ""''<''"": is successful if the search value is a suffix of the value to be compared. # ""''!<''"": is successful if the search value is not a suffix of the value to be compared. # ""''?''"": is successful if the metadata contains the given key. # ""''!?''"": is successful if the metadata does not contain the given key. # ""''''"": a missing search operator can only occur for a full-text search. It is equal to the ""''~''"" operator. |
Deleted docs/manual/00001007710000.zettel.
|
| < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001007720000.zettel.
|
| < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001007720300.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001007720600.zettel.
|
| < < < < < < < < < < < < |
Deleted docs/manual/00001007720900.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001007721200.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001007770000.zettel.
|
| < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00001007780000.zettel.
1 2 3 4 5 6 | id: 00001007780000 title: Formal syntax of query expressions role: manual tags: #manual #reference #search #zettelstore syntax: zmk created: 20220810144539 | | | < < < < < < < < < < < < < < < | | | | | | | | | | | | | | | | | | | | | 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 | id: 00001007780000 title: Formal syntax of query expressions role: manual tags: #manual #reference #search #zettelstore syntax: zmk created: 20220810144539 modified: 20230327122634 ``` QueryExpression := SearchExpression ActionExpression? SearchExpression := SearchTerm (SPACE+ SearchTerm)*. SearchTerm := SearchOperator? SearchValue | SearchKey SearchOperator SearchValue? | SearchKey ExistOperator | "OR" | "RANDOM" | "PICK" SPACE+ PosInt | "ORDER" SPACE+ ("REVERSE" SPACE+)? SearchKey | "OFFSET" SPACE+ PosInt | "LIMIT" SPACE+ PosInt. SearchValue := Word. SearchKey := MetadataKey. SearchOperator := '!' | ('!')? ('~' | ':' | '<' | '>'). ExistOperator := '?' | '!' '?'. PosInt := '0' | ('1' .. '9') DIGIT*. ActionExpression := '|' (Word (SPACE+ Word)*)? Word := NO-SPACE NO-SPACE* ``` |
Changes to docs/manual/00001007790000.zettel.
1 2 3 4 5 6 | id: 00001007790000 title: Useful query expressions role: manual tags: #example #manual #search #zettelstore syntax: zmk created: 20220810144539 | | | < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | id: 00001007790000 title: Useful query expressions role: manual tags: #example #manual #search #zettelstore syntax: zmk created: 20220810144539 modified: 20230327122547 |= Query Expression |= Meaning | [[query:role:configuration]] | Zettel that contains some configuration data for the Zettelstore | [[query:ORDER REVERSE created LIMIT 40]] | 40 recently created zettel | [[query:ORDER REVERSE published LIMIT 40]] | 40 recently updated zettel | [[query:PICK 40]] | 40 random zettel | [[query:dead?]] | Zettel with invalid / dead links | [[query:backward!? precursor!?]] | Zettel that are not referenced by other zettel | [[query:tags!?]] | Zettel without tags |
Changes to docs/manual/00001007800000.zettel.
1 2 3 4 5 | id: 00001007800000 title: Zettelmarkup: Summary of Formatting Characters role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk | < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | id: 00001007800000 title: Zettelmarkup: Summary of Formatting Characters role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk modified: 20220810095559 The following table gives an overview about the use of all characters that begin a markup element. |= Character :|= [[Blocks|00001007030000]] <|= [[Inlines|00001007040000]] < | ''!'' | (free) | (free) | ''"'' | [[Verse block|00001007030700]] | [[Short inline quote|00001007040100]] | ''#'' | [[Ordered list|00001007030200]] | [[Tag|00001007040000]] | ''$'' | (reserved) | (reserved) | ''%'' | [[Comment block|00001007030900]] | [[Comment|00001007040000]] | ''&'' | (free) | [[Entity|00001007040000]] | ''\''' | (free) | [[Computer input|00001007040200]] | ''('' | (free) | (free) | '')'' | (free) | (free) | ''*'' | [[Unordered list|00001007030200]] | [[strongly emphasized text|00001007040100]] |
︙ | ︙ |
Changes to docs/manual/00001007903000.zettel.
1 2 3 4 5 6 | id: 00001007903000 title: Zettelmarkup: First Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk created: 20220810182917 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007903000 title: Zettelmarkup: First Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk created: 20220810182917 modified: 20220926183359 [[Zettelmarkup|00001007000000]] allows you to leave your text as it is, at least in many situations. Some characters have a special meaning, but you have to enter them is a defined way to see a visible change. Zettelmarkup is designed to be used for zettel, which are relatively short. It allows to produce longer texts, but you should probably use a different tool, if you want to produce an scientific paper, to name an example. === Paragraphs |
︙ | ︙ | |||
27 28 29 30 31 32 33 | | ''An __emphasized__ word'' | An __emphasized__ word | Put two underscore characters before and after the text you want to emphasize | ''Someone uses **bold** text'' | Someone uses **bold** text | Put two asterisks before and after the text you want to see bold | ''He says: ""I love you!""'' | Her says: ""I love you!"" | Put two quotation mark characters before and after the text you want to quote. You probably see a principle. One nice thing about the quotation mark characters: they are rendered according to the current language. | | | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | | ''An __emphasized__ word'' | An __emphasized__ word | Put two underscore characters before and after the text you want to emphasize | ''Someone uses **bold** text'' | Someone uses **bold** text | Put two asterisks before and after the text you want to see bold | ''He says: ""I love you!""'' | Her says: ""I love you!"" | Put two quotation mark characters before and after the text you want to quote. You probably see a principle. One nice thing about the quotation mark characters: they are rendered according to the current language. Examples: ""english""{lang=en}, ""french""{lang=fr}, ""german""{lang=de}, ""finnish""{lang=fi}. You will see later, how to change the current language. === Lists Quite often, text consists of lists. Zettelmarkup supports different types of lists. The most important lists are: * Unnumbered lists, |
︙ | ︙ |
Changes to docs/manual/00001007990000.zettel.
1 2 3 4 5 6 | id: 00001007990000 title: Zettelmarkup: Cheat Sheet role: manual tags: #manual #reference #zettelmarkup syntax: zmk created: 20221209191905 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | id: 00001007990000 title: Zettelmarkup: Cheat Sheet role: manual tags: #manual #reference #zettelmarkup syntax: zmk created: 20221209191905 modified: 20221209193310 === Overview This Zettelmarkup cheat sheet provides a quick overview of many Zettelmarkup elements. It can not cover any special case. If you need more information about any of these elements, please refer to the detailed description. === Basic Syntax |[[Text formatting|00001007040100]]|''__italic text__'' → __italic text__, ''**bold text**'' → **bold text**, ''""quoted text""'' → ""quoted text"" |[[Text editing|00001007040100]]|''>>inserted text>>'' → >>inserted text>>, ''~~deleted text~~'' → ~~deleted text~~ |[[Text literal formatting|00001007040200]]|''\'\'entered text\'\''' → ''entered text'', ''``source code``'' → ``source code``, ''==text output=='' → ==text output== |[[Superscript, subscript|00001007040100]]|''m^^2^^'' → m^^2^^, ''H,,2,,O'' → H,,2,,O |[[Links to other zettel|00001007040310]]|''[[Link text|00001007990000]]'' → [[Link text|00001007990000]] |[[Links to external resources|00001007040310]]|''[[Zettelstore|https://zettelstore.de]]'' → [[Zettelstore|https://zettelstore.de]] |[[Embed an image|00001007040322]]|''{{Image text|00000000040001}}'' → {{Image text|00000000040001}} |[[Embed content of first paragraph|00001007040324]]|''{{00001007990000}}'' → {{00001007990000}} |
︙ | ︙ |
Changes to docs/manual/00001008000000.zettel.
1 2 3 4 5 6 | id: 00001008000000 title: Other Markup Languages role: manual tags: #manual #zettelstore syntax: zmk created: 20210126175300 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001008000000 title: Other Markup Languages role: manual tags: #manual #zettelstore syntax: zmk created: 20210126175300 modified: 20221112111148 [[Zettelmarkup|00001007000000]] is not the only markup language you can use to define your content. Zettelstore is quite agnostic with respect to markup languages. Of course, Zettelmarkup plays an important role. However, with the exception of zettel titles, you can use any (markup) language that is supported: * CSS |
︙ | ︙ | |||
37 38 39 40 41 42 43 44 45 46 47 48 49 50 | For security reasons, equivocal elements will not be encoded in the HTML format / web user interface. The ``< script ...>`` tag is an example. See [[security aspects of Markdown|00001008010000#security-aspects]] for some details. ; [!markdown|''markdown''], [!md|''md''] : For those who desperately need [[Markdown|https://daringfireball.net/projects/markdown/]]. Since the world of Markdown is so diverse, a [[CommonMark|00001008010500]] parser is used. See [[Use Markdown within Zettelstore|00001008010000]]. ; [!none|''none''] : Only the metadata of a zettel is ""parsed"". Useful for displaying the full metadata. The [[runtime configuration zettel|00000000000100]] uses this syntax. The zettel content is ignored. ; [!svg|''svg''] : [[Scalable Vector Graphics|https://www.w3.org/TR/SVG2/]]. | > > < < < | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | For security reasons, equivocal elements will not be encoded in the HTML format / web user interface. The ``< script ...>`` tag is an example. See [[security aspects of Markdown|00001008010000#security-aspects]] for some details. ; [!markdown|''markdown''], [!md|''md''] : For those who desperately need [[Markdown|https://daringfireball.net/projects/markdown/]]. Since the world of Markdown is so diverse, a [[CommonMark|00001008010500]] parser is used. See [[Use Markdown within Zettelstore|00001008010000]]. ; [!mustache|''mustache''] : A [[Mustache template|https://mustache.github.io/]], used when rendering a zettel as HTML for the [[web user interface|00001014000000]]. ; [!none|''none''] : Only the metadata of a zettel is ""parsed"". Useful for displaying the full metadata. The [[runtime configuration zettel|00000000000100]] uses this syntax. The zettel content is ignored. ; [!svg|''svg''] : [[Scalable Vector Graphics|https://www.w3.org/TR/SVG2/]]. ; [!text|''text''], [!plain|''plain''], [!txt|''txt''] : Plain text that must not be interpreted further. ; [!zmk|''zmk''] : [[Zettelmarkup|00001007000000]]. The actual values are also listed in a zettel named [[Zettelstore Supported Parser|00000000000092]]. If you specify something else, your content will be interpreted as plain text. |
Changes to docs/manual/00001008050000.zettel.
1 | id: 00001008050000 | | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001008050000 title: The ""draw"" language role: manual tags: #graphic #manual #zettelstore syntax: zmk modified: 20220311120439 Sometimes, ""a picture is worth a thousand words"". To create some graphical representations, Zettelmarkup provides a simple mechanism. Characters like ""''|''"" or ""''-''"" already provide some visual feedback. For example, to create a picture containing two boxes that are connected via an arrow, the following representation is possible: ``` ~~~draw |
︙ | ︙ |
Changes to docs/manual/00001012000000.zettel.
1 2 3 4 5 6 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 | | | < < > > > | 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 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20221219150243 The API (short for ""**A**pplication **P**rogramming **I**nterface"") is the primary way to communicate with a running Zettelstore. Most integration with other systems and services is done through the API. The [[web user interface|00001014000000]] is just an alternative, secondary way of interacting with a Zettelstore. === Background The API is HTTP-based and uses plain text and JSON as its main encoding format for exchanging messages between a Zettelstore and its client software. There is an [[overview zettel|00001012920000]] that shows the structure of the endpoints used by the API and gives an indication about its use. === Authentication If [[authentication is enabled|00001010040100]], most API calls must include an [[access token|00001010040700]] that proves the identity of the caller. * [[Authenticate an user|00001012050200]] to obtain an access token * [[Renew an access token|00001012050400]] without costly re-authentication * [[Provide an access token|00001012050600]] when doing an API call === Zettel lists * [[List all zettel|00001012051200]] * [[Query the list of all zettel|00001012051400]] === Working with zettel * [[Create a new zettel|00001012053200]] * [[Retrieve metadata and content of an existing zettel|00001012053300]] * [[Retrieve metadata of an existing zettel|00001012053400]] * [[Retrieve evaluated metadata and content of an existing zettel in various encodings|00001012053500]] * [[Retrieve parsed metadata and content of an existing zettel in various encodings|00001012053600]] * [[Retrieve context of an existing zettel|00001012053800]] * [[Retrieve unlinked references to an existing zettel|00001012053900]] * [[Retrieve zettel order within an existing zettel|00001012054000]] * [[Update metadata and content of a zettel|00001012054200]] * [[Rename a zettel|00001012054400]] * [[Delete a zettel|00001012054600]] === Various helper methods * [[Retrieve administrative data|00001012070500]] * [[Execute some commands|00001012080100]] ** [[Check for authentication|00001012080200]] ** [[Refresh internal data|00001012080500]] |
Changes to docs/manual/00001012050200.zettel.
1 2 3 4 5 | id: 00001012050200 title: API: Authenticate a client role: manual tags: #api #manual #zettelstore syntax: zmk | < | | | | | | | | 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 | id: 00001012050200 title: API: Authenticate a client role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220107215844 Authentication for future API calls is done by sending a [[user identification|00001010040200]] and a password to the Zettelstore to obtain an [[access token|00001010040700]]. This token has to be used for other API calls. It is valid for a relatively short amount of time, as configured with the key ''token-lifetime-api'' of the [[startup configuration|00001004010000#token-lifetime-api]] (typically 10 minutes). The simplest way is to send user identification (''IDENT'') and password (''PASSWORD'') via [[HTTP Basic Authentication|https://tools.ietf.org/html/rfc7617]] and send them to the [[endpoint|00001012920000]] ''/a'' with a POST request: ```sh # curl -X POST -u IDENT:PASSWORD http://127.0.0.1:23123/a {"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTYwMTczMTI3NSwiaWF0IjoxNjAxNzMwNjc1LCJzdWIiOiJhYmMiLCJ6aWQiOiIyMDIwMTAwMzE1MDEwMCJ9.ekhXkvn146P2bMKFQcU-bNlvgbeO6sS39hs6U5EKfjIqnSInkuHYjYAIfUqf_clYRfr6YBlX5izii8XfxV8jhg","token_type":"Bearer","expires_in":600} ``` Some tools, like [[curl|https://curl.haxx.se/]], also allow to specify user identification and password as part of the URL: ```sh # curl -X POST http://IDENT:PASSWORD@127.0.0.1:23123/a {"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTYwMTczMTI3NSwiaWF0IjoxNjAxNzMwNjc1LCJzdWIiOiJhYmMiLCJ6aWQiOiIyMDIwMTAwMzE1MDEwMCJ9.ekhXkvn146P2bMKFQcU-bNlvgbeO6sS39hs6U5EKfjIqnSInkuHYjYAIfUqf_clYRfr6YBlX5izii8XfxV8jhg","token_type":"Bearer","expires_in":600} ``` If you do not want to use Basic Authentication, you can also send user identification and password as HTML form data: ```sh # curl -X POST -d 'username=IDENT&password=PASSWORD' http://127.0.0.1:23123/a {"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTYwMTczMTI3NSwiaWF0IjoxNjAxNzMwNjc1LCJzdWIiOiJhYmMiLCJ6aWQiOiIyMDIwMTAwMzE1MDEwMCJ9.ekhXkvn146P2bMKFQcU-bNlvgbeO6sS39hs6U5EKfjIqnSInkuHYjYAIfUqf_clYRfr6YBlX5izii8XfxV8jhg","token_type":"Bearer","expires_in":600} ``` In all cases, you will receive an JSON object will all [[relevant data|00001012921000]] to be used for further API calls. **Important:** obtaining a token is a time-intensive process. Zettelstore will delay every request to obtain a token for a certain amount of time. Please take into account that this request will take approximately 500 milliseconds, under certain circumstances more. However, if [[authentication is not enabled|00001010040100]] and you send an authentication request, no user identification/password checking is done and you receive an artificial token immediate, without any delay: ```sh # curl -X POST -u IDENT:PASSWORD http://127.0.0.1:23123/a {"token":"freeaccess","token_type":"Bearer","expires_in":316224000} ``` In this case, it is even possible to omit the user identification/password. === HTTP Status codes In all cases of successful authentication, a JSON object is returned, which contains the token under the key ''"token"''. A successful authentication is signaled with the HTTP status code 200, as usual. Other status codes possibly send by the Zettelstore: ; ''400'' : Unable to process the request. In most cases the form data was invalid. ; ''401'' : Authentication failed. Either the user identification is invalid or you provided the wrong password. ; ''403'' : Authentication is not active. |
Changes to docs/manual/00001012050400.zettel.
1 2 3 4 5 | id: 00001012050400 title: API: Renew an access token role: manual tags: #api #manual #zettelstore syntax: zmk | < | | | | | 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 | id: 00001012050400 title: API: Renew an access token role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220107215751 An access token is only valid for a certain duration. Since the [[authentication process|00001012050200]] will need some processing time, there is a way to renew the token without providing full authentication data. Send a HTTP PUT request to the [[endpoint|00001012920000]] ''/a'' and include the current access token in the ''Authorization'' header: ```sh # curl -X PUT -H 'Authorization: Bearer TOKEN' http://127.0.0.1:23123/a {"access_token":"eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTYwMTczMTI3NSwiaWF0IjoxNjAxNzMwNjc1LCJzdWIiOiJhYmMiLCJ6aWQiOiIyMDIwMTAwMzE1MDEwMCJ9.ekhXkvn146P2bMKFQcU-bNlvgbeO6sS39hs6U5EKfjIqnSInkuHYjYAIfUqf_clYRfr6YBlX5izii8XfxV8jhg","token_type":"Bearer","expires_in":456} ``` You may receive a new access token, or the current one if it was obtained not a long time ago. However, the lifetime of the returned [[access token|00001012921000]] is accurate. If [[authentication is not enabled|00001010040100]] and you send a renew request, no checking is done and you receive an artificial token immediate, without any delay: ```sh # curl -X PUT -H 'Authorization: Bearer freeaccess' http://127.0.0.1:23123/a {"token":"freeaccess","token_type":"Bearer","expires_in":316224000} ``` In this case, it is even possible to omit the access token. === HTTP Status codes ; ''200'' : Renew process was successful, the body contains an [[appropriate JSON object|00001012921000]]. ; ''400'' : The renew process was not successful. There are several reasons for this. Maybe access bearer token was not valid. Probably you should [[authenticate|00001012050200]] again with user identification and password. |
Changes to docs/manual/00001012051200.zettel.
1 2 3 4 5 6 | id: 00001012051200 title: API: List all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 | | < < < | < < < | | | > | | > > | | > > > > > > > | > | > > > > > > | | | | | | | | > > > > > | < < > | > | < | > > > > > > > > | > > > > > > > > > > > > > > > > > > | > > > > > > > | > > > | < > | | 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 | id: 00001012051200 title: API: List all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20221219154400 To list all zettel just send a HTTP GET request to the [[endpoint|00001012920000]] ''/z''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. Always use the endpoint ''/z'' to work with a list of zettel. Without further specifications, a plain text document is returned, with one line per zettel. Each line contains in the first 14 characters the [[zettel identifier|00001006050000]]. Separated by a space character, the title of the zettel follows: ```sh # curl http://127.0.0.1:23123/z ... 00001012051200 API: List all zettel 00001012050600 API: Provide an access token 00001012050400 API: Renew an access token 00001012050200 API: Authenticate a client ... ``` Alternatively, you may retrieve the list of all zettel as a JSON object by specifying the encoding with the query parameter ''enc=json'': ```sh # curl 'http://127.0.0.1:23123/z?enc=json' {"query":"","human":"","list":[{"id":"00001012051200","meta":{"back":"00001012000000","backward":"00001012000000 00001012920000","box-number":"1","created":"20210126175322","forward":"00001006020000 00001006050000 00001007700000 00001010040100 00001012050200 00001012051400 00001012920000 00001012921000 00001014000000","modified":"20221219150626","published":"20221219150626","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: List all zettel"},"rights":62},{"id":"00001012050600","meta":{"back":"00001012000000 00001012080500","backward":"00001012000000 00001012080500","box-number":"1","created":"00010101000000","forward":"00001012050200 00001012921000","modified":"20220218130020","published":"20220218130020","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Provide an access token"},"rights":62},{"id":"00001012050400","meta":{"back":"00001010040700 00001012000000","backward":"00001010040700 00001012000000 00001012920000 00001012921000","box-number":"1","created":"00010101000000","forward":"00001010040100 00001012050200 00001012920000 00001012921000","modified":"20220107215751","published":"20220107215751","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Renew an access token"},"rights":62},{"id":"00001012050200","meta":{"back":"00001012000000 00001012050400 00001012050600 00001012051200 00001012051400 00001012053300 00001012053400 00001012053500 00001012053600 00001012080200","backward":"00001010040700 00001012000000 00001012050400 00001012050600 00001012051200 00001012051400 00001012053300 00001012053400 00001012053500 00001012053600 00001012080200 00001012920000 00001012921000","box-number":"1","created":"00010101000000","forward":"00001004010000 00001010040100 00001010040200 00001010040700 00001012920000 00001012921000","modified":"20220107215844","published":"20220107215844","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Authenticate a client"},"rights":62}, ...]} ``` If you reformat the JSON output, you will see its structure better: ``` { "query": "", "human": "", "list": [ { "id": "00001012051200", "meta": { "back": "00001012000000", "backward": "00001012000000 00001012920000", "box-number": "1", "created": "20210126175322", "forward": "00001006020000 00001006050000 00001007700000 00001010040100 00001012050200 00001012051400 00001012920000 00001012921000 00001014000000", "modified": "20221219151200", "published": "20221219151200", "role": "manual", "syntax": "zmk", "tags": "#api #manual #zettelstore", "title": "API: List all zettel" }, "rights": 62 }, { "id": "00001012050600", "meta": { "back": "00001012000000 00001012080500", "backward": "00001012000000 00001012080500", "box-number": "1", "created": "00010101000000", "forward": "00001012050200 00001012921000", "modified": "20220218130020", "published": "20220218130020", "role": "manual", "syntax": "zmk", "tags": "#api #manual #zettelstore", "title": "API: Provide an access token" }, "rights": 62 }, { "id": "00001012050400", "meta": { "back": "00001010040700 00001012000000", "backward": "00001010040700 00001012000000 00001012920000 00001012921000", "box-number": "1", "created": "00010101000000", "forward": "00001010040100 00001012050200 00001012920000 00001012921000", "modified": "20220107215751", "published": "20220107215751", "role": "manual", "syntax": "zmk", "tags": "#api #manual #zettelstore", "title": "API: Renew an access token" }, "rights": 62 }, { "id": "00001012050200", "meta": { "back": "00001012000000 00001012050400 00001012050600 00001012051200 00001012051400 00001012053300 00001012053400 00001012053500 00001012053600 00001012 080200", "backward": "00001010040700 00001012000000 00001012050400 00001012050600 00001012051200 00001012051400 00001012053300 00001012053400 00001012053500 0000 1012053600 00001012080200 00001012920000 00001012921000", "box-number": "1", "created": "00010101000000", "forward": "00001004010000 00001010040100 00001010040200 00001010040700 00001012920000 00001012921000", "modified": "20220107215844", "published": "20220107215844", "role": "manual", "syntax": "zmk", "tags": "#api #manual #zettelstore", "title": "API: Authenticate a client" }, "rights": 62 } ] } ``` The JSON object contains a key ''"list"'' where its value is a list of zettel JSON objects. These zettel JSON objects themselves contains the keys ''"id"'' (value is a string containing the [[zettel identifier|00001006050000]]), ''"meta"'' (value as a JSON object), and ''"rights"'' (encodes the [[access rights|00001012921200]] for the given zettel). The value of key ''"meta"'' effectively contains all metadata of the identified zettel, where metadata keys are encoded as JSON object keys and metadata values encoded as JSON strings. The JSON objects keys ''"query"'' and ''"human"'' will be explained later in this manual. === Note This request (and similar others) will always return a list of metadata, provided the request was syntactically correct. There will never be a HTTP status code 403 (Forbidden), even if [[authentication was enabled|00001010040100]] and you did not provide a valid access token. In this case, the resulting list might be quite short (some zettel will have [[public visibility|00001010070200]]) or the list might be empty. With this call, you cannot differentiate between an empty result list (e.g because your search did not found a zettel with the specified term) and an empty list because of missing authorization (e.g. an invalid access token). === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an [[appropriate JSON object|00001012921000]]. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the access bearer token was not valid. |
Changes to docs/manual/00001012051400.zettel.
1 2 3 4 5 6 | id: 00001012051400 title: API: Query the list of all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20220912111111 | | < | | | | | > > > | | < < < < < < < < | 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 | id: 00001012051400 title: API: Query the list of all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20220912111111 modified: 20230320151117 The [[endpoint|00001012920000]] ''/z'' also allows you to filter the list of all zettel[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header] and optionally to provide some actions. A [[query|00001007700000]] is an optional [[search expression|00001007700000#search-expression]], together with an optional [[list of actions|00001007700000#action-list]] (described below). An empty search expression will select all zettel. An empty list of action, or no valid action, returns the list of all selected zettel metadata. Search expression and action list are separated by a vertical bar character (""''|''"", U+007C), and must be given with the query parameter ''q''. The query parameter ""''q''"" allows you to specify [[query expressions|00001007700000]] for a full-text search of all zettel content and/or restricting the search according to specific metadata. It is allowed to specify this query parameter more than once. This parameter loosely resembles the search form of the [[web user interface|00001014000000]] or those of [[Zettelmarkup's Query Transclusion|00001007031140]]. For example, if you want to retrieve all zettel that contain the string ""API"" in its title, your request will be: ```sh # curl 'http://127.0.0.1:23123/z?q=title%3AAPI' 00001012921000 API: JSON structure of an access token 00001012920500 Formats available by the API 00001012920000 Endpoints used by the API ... ``` If you want to retrieve a JSON document: ```sh # curl 'http://127.0.0.1:23123/z?q=title%3AAPI&enc=json' {"query":"title:API","human":"title HAS API","list":[{"id":"00001012921200","meta":{"back":"00001012051200 00001012051400 00001012053300 00001012053400 00001012053800 00001012053900 00001012054000","backward":"00001012051200 00001012051400 00001012053300 00001012053400 00001012053800 00001012053900 00001012054000","box-number":"1","created":"00010101000000","forward":"00001003000000 00001006020400 00001010000000 00001010040100 00001010040200 00001010070200 00001010070300","modified":"20220201171959","published":"20220201171959","role":"manual","syntax":"zmk","tags":"#api #manual #reference #zettelstore","title":"API: Encoding of Zettel Access Rights"},"rights":62},{"id":"00001012921000","meta":{"back":"00001012050600 00001012051200","backward":"00001012050200 00001012050400 00001012050600 00001012051200","box-number":"1","created":"00010101000000","forward":"00001012050200 00001012050400","published":"00010101000000","role":"manual","syntax":"zmk","tags":"#api #manual #reference #zettelstore","title":"API: JSON structure of an access token"},"rights":62}, ...] ``` The JSON object contains a key ''"list"'' where its value is a list of zettel JSON objects. These zettel JSON objects themselves contains the keys ''"id"'' (value is a string containing the [[zettel identifier|00001006050000]]), ''"meta"'' (value as a JSON object), and ''"rights"'' (encodes the [[access rights|00001012921200]] for the given zettel). The value of key ''"meta"'' effectively contains all metadata of the identified zettel, where metadata keys are encoded as JSON object keys and metadata values encoded as JSON strings. Additionally, the JSON object contains the keys ''"query"'' and ''"human"'' with a string value. Both will contain a textual description of the underlying query if you select only some zettel with a [[query expression|00001007700000]]. Without a selection, the values are the empty string. ''"query"'' returns the normalized query expression itself, while ''"human"'' is the normalized query expression to be read by humans. An implicit precondition is that the zettel must contain the given metadata key. For a metadata key like [[''title''|00001006020000#title]], which have a default value, this precondition should always be true. But the situation is different for a key like [[''url''|00001006020000#url]]. Both ``curl 'http://localhost:23123/z?q=url%3A'`` and ``curl 'http://localhost:23123/z?q=url%3A!'`` may result in an empty list. |
︙ | ︙ | |||
68 69 70 71 72 73 74 | ``` The result is a text file. The first word, separated by a horizontal tab (U+0009) contains the role name. The rest of the line consists of zettel identifier, where the corresponding zettel have this role. Zettel identifier are separated by a space character (U+0020). | < < < | | < > < | < < < > | | < > > > > < < < < < < | 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 | ``` The result is a text file. The first word, separated by a horizontal tab (U+0009) contains the role name. The rest of the line consists of zettel identifier, where the corresponding zettel have this role. Zettel identifier are separated by a space character (U+0020). Of course, this list can be returned as a JSON object: ```sh # curl 'http://127.0.0.1:23123/z?q=|role?enc=json' {"map":{"configuration":["00000000090002","00000000090000", ... ,"00000000000001"],"manual":["00001014000000", ... ,"00001000000000"],"zettel":["00010000000000", ... ,"00001012070500","00000000090001"]}} ``` The JSON object only contains the key ''"map"'' with the value of another object. This second object contains all role names as keys and the list of identifier of those zettel with this specific role as a value. Similar, to list all tags used in the Zettelstore, send a HTTP GET request to the endpoint ''/z?q=|tags''. If successful, the output is a JSON object: ```sh # curl 'http://127.0.0.1:23123/z?q=|tags&enc=json' {"map":{"#api":[:["00001012921000","00001012920800","00001012920522",...],"#authorization":["00001010040700","00001010040400",...],...,"#zettelstore":["00010000000000","00001014000000",...,"00001001000000"]}} ``` The JSON object only contains the key ''"map"'' with the value of another object. This second object contains all tags as keys and the list of identifier of those zettel with this tag as a value. If you want only those tags that occur at least 100 times, use the endpoint ''/z?q=|MIN100+tags''. You see from this that actions are separated by space characters. There are two types of actions: parameters and aggregates. The following actions are supported: ; ''MINn'' (parameter) : Emit only those values with at least __n__ aggregated values. __n__ must be a positive integer, ''MIN'' must be given in upper-case letters. ; ''MAXn'' (parameter) : Emit only those values with at most __n__ aggregated values. __n__ must be a positive integer, ''MAX'' must be given in upper-case letters. ; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]], [[WordSet|00001006036000]], or [[TagSet|00001006034000]] (aggregates) : Emit an aggregate of the given metadata key. The key can be given in any letter case. Only the first aggregate action will be executed. |
︙ | ︙ |
Deleted docs/manual/00001012051600.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001012051800.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00001012053200.zettel.
1 2 3 4 5 6 | id: 00001012053200 title: API: Create a new zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210713150005 | | | > | > > > > > > > > > > > > | > > > > > | > > > > > > > | > > > > > | | | 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 | id: 00001012053200 title: API: Create a new zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210713150005 modified: 20221219162225 A zettel is created by adding it to the [[list of zettel|00001012000000]]. Therefore, the [[endpoint|00001012920000]] to create a new zettel is also ''/z'', but you must send the data of the new zettel via a HTTP POST request. The zettel must be encoded in a [[plain|00001006000000]] format: first comes the [[metadata|00001006010000]] and the following content is separated by an empty line. This is the same format as used by storing zettel within a [[directory box|00001006010000]]. ``` # curl -X POST --data $'title: Note\n\nImportant content.' http://127.0.0.1:23123/z 20210903211500 ``` The zettel identifier of the created zettel is returned. In addition, the HTTP response header contains a key ''Location'' with a relative URL for the new zettel. A client must prepend the HTTP protocol scheme, the host name, and (optional, but often needed) the post number to make it an absolute URL. Alternatively, the body of the POST request may contain a JSON object that specifies metadata and content of the zettel to be created. To do this, you must add the query parameter ''enc=json''. The following keys of the JSON object are used: ; ''"meta"'' : References an embedded JSON object with only string values. The name/value pairs of this objects are interpreted as the metadata of the new zettel. Please consider the [[list of supported metadata keys|00001006020000]] (and their value types). ; ''"encoding"'' : States how the content is encoded. Currently, only two values are allowed: the empty string (''""'') that specifies an empty encoding, and the string ''"base64"'' that specifies the [[standard Base64 encoding|https://www.rfc-editor.org/rfc/rfc4648.txt]]. Other values will result in a HTTP response status code ''400''. ; ''"content"'' : Is a string value that contains the content of the zettel to be created. Typically, text content is not encoded, and binary content is encoded via Base64. Other keys will be ignored. Even these three keys are just optional. The body of the HTTP POST request must not be empty and it must contain a JSON object. Therefore, a body containing just ''{}'' is perfectly valid. The new zettel will have no content, and only an identifier as [[metadata|00001006020000]]: ``` # curl -X POST --data '{}' 'http://127.0.0.1:23123/z?enc=json' {"id":"20210713161000"} ``` If creating the zettel was successful, the HTTP response will contain a JSON object with one key: ; ''"id"'' : Contains the [[zettel identifier|00001006050000]] of the created zettel for further usage. As an example, a zettel with title ""Note"" and content ""Important content."" can be created by issuing: ``` # curl -X POST --data '{"meta":{"title":"Note"},"content":"Important content."}' 'http://127.0.0.1:23123/z?enc=json' {"id":"20210713163100"} ``` === HTTP Status codes ; ''201'' : Zettel creation was successful, the body contains its [[zettel identifier|00001006050000]] (JSON object or plain text). ; ''400'' : Request was not valid. There are several reasons for this. Most likely, the JSON was not formed according to above rules. ; ''403'' : You are not allowed to create a new zettel. |
Changes to docs/manual/00001012053300.zettel.
1 2 3 4 5 6 | id: 00001012053300 title: API: Retrieve metadata and content of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20211004093206 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012053300 title: API: Retrieve metadata and content of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20211004093206 modified: 20221219160613 The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. ````sh # curl 'http://127.0.0.1:23123/z/00001012053300' |
︙ | ︙ | |||
34 35 36 37 38 39 40 | The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ... ```` | | | < | < > | > > > | | | | | | | | | | | < > | | > > > > | > > | | < > | > | > > > | | 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 | The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ... ```` === JSON output Alternatively, you may retrieve the zettel as a JSON object by providinv the query parameter ''enc=json'': ```sh # curl 'http://127.0.0.1:23123/z/00001012053300?enc=json&part=zettel' {"id":"00001012053300","meta":{"back":"00001012000000 00001012054400","backward":"00001012000000 00001012054400 00001012920000","box-number":"1","created":"20211004093206","forward":"00001006020000 00001006050000 00001010040100 00001012050200 00001012053400 00001012920000 00001012920800 00001012921200","modified":"20221219160211","published":"20221219160211","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Retrieve metadata and content of an existing zettel"},"encoding":"","content":"The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].\n\nFor example, ... ``` Pretty-printed, this results in: ``` { "id": "00001012053300", "meta": { "back": "00001012000000 00001012054400", "backward": "00001012000000 00001012054400 00001012920000", "box-number": "1", "created": "20211004093206", "forward": "00001006020000 00001006050000 00001010040100 00001012050200 00001012053400 00001012920000 00001012920800 00001012921200", "modified": "20221219160211", "published": "20221219160211", "role": "manual", "syntax": "zmk", "tags": "#api #manual #zettelstore", "title": "API: Retrieve metadata and content of an existing zettel" }, "encoding": "", "content": "The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' (...) "rights": 62 } ``` The following keys of the JSON object are used: ; ''"id"'' : The zettel identifier of the zettel you requested. ; ''"meta"'' : References an embedded JSON object with only string values. The name/value pairs of this objects are interpreted as the metadata of the new zettel. Please consider the [[list of supported metadata keys|00001006020000]] (and their value types). ; ''"encoding"'' : States how the content is encoded. Currently, only two values are allowed: the empty string (''""'') that specifies an empty encoding, and the string ''"base64"'' that specifies the [[standard Base64 encoding|https://www.rfc-editor.org/rfc/rfc4648.txt]]. Other values will result in a HTTP response status code ''400''. ; ''"content"'' : Is a string value that contains the content of the zettel to be created. Typically, text content is not encoded, and binary content is encoded via Base64. ; ''"rights"'' : An integer number that describes the [[access rights|00001012921200]] for the zettel. === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object / plain zettel data. ; ''204'' : Request was valid, but there is no data to be returned. Most likely, you specified the query parameter ''part=content'', but the zettel does not contain any content. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the [[zettel identifier|00001006050000]] did not consists of exactly 14 digits. ; ''403'' : You are not allowed to retrieve data of the given zettel. ; ''404'' : Zettel not found. You probably used a zettel identifier that is not used in the Zettelstore. |
Changes to docs/manual/00001012053400.zettel.
1 2 3 4 5 6 | id: 00001012053400 title: API: Retrieve metadata of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210726174524 | | < | < | | < < < > | | | | | | | | > > > > > | > > > > > > > > < < < < | | 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 | id: 00001012053400 title: API: Retrieve metadata of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210726174524 modified: 20221219161030 The [[endpoint|00001012920000]] to work with metadata of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]][^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. To retrieve the plain metadata of a zettel, use the query parameter ''part=meta'' ````sh # curl 'http://127.0.0.1:23123/z/00001012053400?part=meta' title: API: Retrieve metadata of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk ```` To return a JSON object, use also the query parameter ''enc=json''. ```sh # curl 'http://127.0.0.1:23123/z/00001012053400?part=meta&enc=json' {"meta":{"back":"00001012000000 00001012053300","backward":"00001012000000 00001012053300 00001012920000","box-number":"1","created":"20210726174524","forward":"00001006020000 00001006050000 00001010040100 00001012050200 00001012920000 00001012921200","modified":"20220917175233","published":"20220917175233","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Retrieve metadata of an existing zettel"},"rights":62} ``` Pretty-printed, this results in: ``` { "meta": { "back": "00001012000000 00001012053300", "backward": "00001012000000 00001012053300 00001012920000", "box-number": "1", "created": "20210726174524", "forward": "00001006020000 00001006050000 00001010040100 00001012050200 00001012920000 00001012921200", "modified": "20220917175233", "published": "20220917175233", "role": "manual", "syntax": "zmk", "tags": "#api #manual #zettelstore", "title": "API: Retrieve metadata of an existing zettel" }, "rights": 62 } ``` The following keys of the JSON object are used: ; ''"meta"'' : References an embedded JSON object with only string values. The name/value pairs of this objects are interpreted as the metadata of the new zettel. Please consider the [[list of supported metadata keys|00001006020000]] (and their value types). ; ''"rights"'' : An integer number that describes the [[access rights|00001012921200]] for the zettel. === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the zettel identifier did not consist of exactly 14 digits. ; ''403'' : You are not allowed to retrieve data of the given zettel. ; ''404'' : Zettel not found. You probably used a zettel identifier that is not used in the Zettelstore. |
Changes to docs/manual/00001012053500.zettel.
1 2 3 4 5 6 | id: 00001012053500 title: API: Retrieve evaluated metadata and content of an existing zettel in various encodings role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210726174524 | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | id: 00001012053500 title: API: Retrieve evaluated metadata and content of an existing zettel in various encodings role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210726174524 modified: 20230109105303 The [[endpoint|00001012920000]] to work with evaluated metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some evaluated data about this zettel you are currently viewing in [[Sexpr encoding|00001012920516]], just send a HTTP GET request to the endpoint ''/z/00001012053500''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header] with the query parameter ''enc=sexpr''. If successful, the output is a JSON object: ```sh # curl 'http://127.0.0.1:23123/z/00001012053500?enc=sexpr' ((PARA (TEXT "The") (SPACE) (LINK-ZETTEL () "00001012920000" (TEXT "endpoint")) (SPACE) (TEXT "to") (SPACE) (TEXT "work") (SPACE) (TEXT "with") (SPACE) (TEXT "evaluated") (SPACE) (TEXT "metadata") (SPACE) (TEXT "and") (SPACE) (TEXT "content") (SPACE) (TEXT "of") (SPACE) (TEXT "a") (SPACE) (TEXT "specific") (SPACE) (TEXT "zettel") (SPACE) (TEXT "is") (SPACE) (LITERAL-INPUT () "/z/{ID}") (TEXT ",") (SPACE) (TEXT "where") (SPACE) (LITERAL-INPUT () "{ID}") ... ``` To select another encoding, you must provide the query parameter ''enc=ENCODING''. Others are ""[[html|00001012920510]]"", ""[[text|00001012920519]]"", and some [[more|00001012920500]]. In addition, you may provide a query parameter ''part=PART'' to select the relevant [[part|00001012920800]] of a zettel. ```sh |
︙ | ︙ | |||
44 45 46 47 48 49 50 | <h1>API: Retrieve evaluated metadata and content of an existing zettel in various encodings</h1> <p>The <a href="00001012920000">endpoint</a> to work with evaluated metadata and content of a specific zettel is <kbd>/z/{ID}</kbd>, ... ``` === HTTP Status codes ; ''200'' | | | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | <h1>API: Retrieve evaluated metadata and content of an existing zettel in various encodings</h1> <p>The <a href="00001012920000">endpoint</a> to work with evaluated metadata and content of a specific zettel is <kbd>/z/{ID}</kbd>, ... ``` === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the zettel identifier did not consist of exactly 14 digits or ''enc'' / ''part'' contained illegal values. ; ''403'' : You are not allowed to retrieve data of the given zettel. ; ''404'' : Zettel not found. You probably used a zettel identifier that is not used in the Zettelstore. |
Changes to docs/manual/00001012053600.zettel.
1 2 3 4 5 6 | id: 00001012053600 title: API: Retrieve parsed metadata and content of an existing zettel in various encodings role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 | | | | | 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 | id: 00001012053600 title: API: Retrieve parsed metadata and content of an existing zettel in various encodings role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20230109105034 The [[endpoint|00001012920000]] to work with parsed metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. A __parsed__ zettel is basically an [[unevaluated|00001012053500]] zettel: the zettel is read and analyzed, but its content is not __evaluated__. By using this endpoint, you are able to retrieve the structure of a zettel before it is evaluated. For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053600''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header] with the query parameter ''parseonly'' (and other appropriate query parameter). For example: ```sh # curl 'http://127.0.0.1:23123/z/00001012053600?enc=sexpr&parseonly' ((PARA (TEXT "The") (SPACE) (LINK-ZETTEL () "00001012920000" (TEXT "endpoint")) (SPACE) (TEXT "to") (SPACE) (TEXT "work") (SPACE) (TEXT "with") (SPACE) ... ``` Similar to [[retrieving an encoded zettel|00001012053500]], you can specify an [[encoding|00001012920500]] and state which [[part|00001012920800]] of a zettel you are interested in. The same default values applies to this endpoint. === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the zettel identifier did not consist of exactly 14 digits or ''enc'' / ''part'' contained illegal values. ; ''403'' : You are not allowed to retrieve data of the given zettel. ; ''404'' : Zettel not found. You probably used a zettel identifier that is not used in the Zettelstore. |
Added docs/manual/00001012053800.zettel.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | id: 00001012053800 title: API: Retrieve context of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210222151131 modified: 20221208165112 The context of an origin zettel consists of those zettel that are somehow connected to the origin zettel. Direct connections of an origin zettel to other zettel are visible via [[metadata values|00001006020000]], such as ''backward'', ''forward'' or other values with type [[identifier|00001006032000]] or [[set of identifier|00001006032500]]. The context is defined by a __direction__, a __cost__, and a __limit__: * Direction: connections are directed. For example, the metadata value of ''backward'' lists all zettel that link to the current zettel, while ''forward'' list all zettel to which the current zettel links to. When you are only interested in one direction, set the parameter ''dir'' either to the value ""backward"" or ""forward"". All other values, including a missing value, is interpreted as ""both"". * Cost: a normal direct connection has cost 5, an indirect connection is the length of the shortest path between two zettel. The cost of a folgezettel or a precursor zettel is just 1, the cost of successor zettel and predecessor zettel are 2. You should limit the cost by using the parameter ''cost''. Its default value is ""17"". A value of ""0"" does disable any cost check. * Limit: to set an upper bound for the returned context, you should use the parameter ''limit''. Its default value is ""200"". A value of ""0"" disables does not limit the number of elements returned. The search for the context of a zettel stops at the [[home zettel|00001004020000#home-zettel]]. This zettel is connected to all other zettel. If it is included, the context would become too big and therefore unusable. To retrieve the context of an existing zettel, use the [[endpoint|00001012920000]] ''/x/{ID}''[^Mnemonic: conte**X**t]. ```` # curl 'http://127.0.0.1:23123/x/00001012053800?limit=3&dir=forward&cost=5' {"id": "00001012053800","meta": {...},"rights":62,"list":[{"id": "00001012921000","meta": {...},"rights":62},{"id": "00001012920800","meta": {...},"rights":62},{"id": "00010000000000","meta": {...},"rights":62}]} ```` Formatted, this translates into:[^Metadata (key ''meta'') are hidden to make the overall structure easier to read.] ````json { "id": "00001012053800", "meta": {...}, "rights": 62, "list": [ { "id": "00001012921000", "meta": {...}, "rights":62 }, { "id": "00001012920800", "meta": {...}, "rights":62 }, { "id": "00010000000000", "meta": {...}, "rights":62 } ] } ```` === Keys The following top-level JSON keys are returned: ; ''id'' : The [[zettel identifier|00001006050000]] for which the context was requested. ; ''meta'': : The metadata of the zettel, encoded as a JSON object. ; ''rights'' : An integer number that describes the [[access rights|00001012921200]] for the given zettel. ; ''list'' : A list of JSON objects with keys ''id'', ''meta'', and ''rights'' that contains the zettel of the context. === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. ; ''400'' : Request was not valid. ; ''403'' : You are not allowed to retrieve data of the given zettel. ; ''404'' : Zettel not found. You probably used a zettel identifier that is not used in the Zettelstore. |
Added docs/manual/00001012053900.zettel.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | id: 00001012053900 title: API: Retrieve unlinked references to an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20211119133357 modified: 20220913152019 The value of a personal Zettelstore is determined in part by explicit connections between related zettel. If the number of zettel grow, some of these connections are missing. There are various reasons for this. Maybe, you forgot that a zettel exists. Or you add a zettel later, but forgot that previous zettel already mention its title. __Unlinked references__ are phrases in a zettel that mention the title of another, currently unlinked zettel. To retrieve unlinked references to an existing zettel, use the [[endpoint|00001012920000]] ''/u/{ID}''. ```` # curl 'http://127.0.0.1:23123/u/00001007000000' {"id": "00001007000000","meta": {...},"rights":62,"list": [{"id": "00001012070500","meta": {...},"rights":62},...{"id": "00001006020000","meta": {...},"rights":62}]} ```` Formatted, this translates into:[^Metadata (key ''meta'') are hidden to make the overall structure easier to read.] ````json { "id": "00001007000000", "meta": {...}, "rights": 62, "list": [ { "id": "00001012070500", "meta": {...}, "rights": 62 }, ... { "id": "00001006020000", "meta": {...}, "rights": 62 } ] } ```` This call searches within all zettel whether the title of the specified zettel occurs there. The other zettel must not link to the specified zettel. The title must not occur within a link (e.g. to another zettel), in a [[heading|00001007030300]], in a [[citation|00001007040340]], and must have a uniform formatting. The match must be exact, but is case-insensitive. If the title of the specified zettel contains some extra character that probably reduce the number of found unlinked references, you can specify the title phase to be searched for as a query parameter ''phrase'': ```` # curl 'http://127.0.0.1:23123/u/00001007000000?phrase=markdown' {"id": "00001007000000","meta": {...},"list": [{"id": "00001008010000","meta": {...},"rights":62},{"id": "00001004020000","meta": {...},"rights":62}]} ```` %%TODO: In addition, you are allowed to limit the search by a [[query expression|00001012051840]], which may search for zettel content. === Keys The following top-level JSON keys are returned: ; ''id'' : The [[zettel identifier|00001006050000]] for which the unlinked references were requested. ; ''meta'': : The metadata of the zettel, encoded as a JSON object. ; ''rights'' : An integer number that describes the [[access rights|00001012921200]] for the given zettel. ; ''list'' : A list of JSON objects with keys ''id'', ''meta'', and ''rights'' that describe zettel with unlinked references. === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. ; ''400'' : Request was not valid. ; ''403'' : You are not allowed to retrieve data of the given zettel. ; ''404'' : Zettel not found. You probably used a zettel identifier that is not used in the Zettelstore. |
Added docs/manual/00001012054000.zettel.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | id: 00001012054000 title: API: Retrieve zettel order within an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220202112451 Some zettel act as a ""table of contents"" for other zettel. The [[initial zettel|00001000000000]] of this manual is one example, the [[general API description|00001012000000]] is another. Every zettel with a certain internal structure can act as the ""table of contents"" for others. What is a ""table of contents""? Basically, it is just a list of references to other zettel. To retrieve the ""table of contents"", the software looks at first level [[list items|00001007030200]]. If an item contains a valid reference to a zettel, this reference will be interpreted as an item in the table of contents. This applies only to first level list items (ordered or unordered list), but not to deeper levels. Only the first reference to a valid zettel is collected for the table of contents. Following references to zettel within such an list item are ignored. To retrieve the zettel order of an existing zettel, use the [[endpoint|00001012920000]] ''/o/{ID}''. ```` # curl http://127.0.0.1:23123/o/00001000000000 {"id":"00001000000000","meta":{...},"rights":62,"list":[{"id":"00001001000000","meta":{...},"rights":62},{"id":"00001002000000","meta":{...},"rights":62},{"id":"00001003000000","meta":{...},"rights":62},{"id":"00001004000000","meta":{...},"rights":62},...,{"id":"00001014000000","meta":{...},"rights":62}]} ```` Formatted, this translates into:[^Metadata (key ''meta'') are hidden to make the overall structure easier to read.] ````json { "id": "00001000000000", "meta": {...}, "rights": 62, "list": [ { "id": "00001001000000", "meta": {...}, "rights": 62 }, { "id": "00001002000000", "meta": {...}, "rights": 62 }, { "id": "00001003000000", "meta": {...}, "rights": 62 }, { "id": "00001004000000", "meta": {...}, "rights": 62 }, ... { "id": "00001014000000", "meta": {...}, "rights": 62 } ] } ```` The following top-level JSON keys are returned: ; ''id'' : The [[zettel identifier|00001006050000]] for which the references were requested. ; ''meta'': : The metadata of the zettel, encoded as a JSON object. ; ''rights'' : An integer number that describes the [[access rights|00001012921200]] for the given zettel. ; ''list'' : A list of JSON objects with keys ''id'', ''meta'', and ''rights'' that describe other zettel in the defined order. === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. ; ''400'' : Request was not valid. ; ''403'' : You are not allowed to retrieve data of the given zettel. ; ''404'' : Zettel not found. You probably used a zettel identifier that is not used in the Zettelstore. |
Changes to docs/manual/00001012054200.zettel.
1 2 3 4 5 6 | id: 00001012054200 title: API: Update a zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210713150005 | | | < | | > > > > > < < > | 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 | id: 00001012054200 title: API: Update a zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210713150005 modified: 20221219155029 Updating metadata and content of a zettel is technically quite similar to [[creating a new zettel|00001012053200]]. In both cases you must provide the data for the new or updated zettel in the body of the HTTP request. One difference is the endpoint. The [[endpoint|00001012920000]] to update a zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. You must send a HTTP PUT request to that endpoint. The zettel must be encoded in a [[plain|00001006000000]] format: first comes the [[metadata|00001006010000]] and the following content is separated by an empty line. This is the same format as used by storing zettel within a [[directory box|00001006010000]]. ``` # curl -X POST --data 'title: Updated Note\n\nUpdated content.' http://127.0.0.1:23123/z/00001012054200 ``` Alternatively, you can use the JSON encoding by using the query parameter ''enc=json''. ``` # curl -X PUT --data '{}' 'http://127.0.0.1:23123/z/00001012054200?enc=json' ``` This will put some empty content and metadata to the zettel you are currently reading. As usual, some metadata will be calculated if it is empty. The body of the HTTP response is empty, if the request was successful. === HTTP Status codes ; ''204'' : Update was successful, there is no body in the response. ; ''400'' : Request was not valid. For example, the request body was not valid. ; ''403'' : You are not allowed to delete the given zettel. ; ''404'' : Zettel not found. You probably used a zettel identifier that is not used in the Zettelstore. |
Changes to docs/manual/00001012070500.zettel.
1 | id: 00001012070500 | | < | | | > | | < | < | | 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 | id: 00001012070500 title: Retrieve administrative data role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220805174216 The [[endpoint|00001012920000]] ''/x'' allows you to retrieve some (administrative) data. Currently, you can only request Zettelstore version data. ```` # curl 'http://127.0.0.1:23123/x' {"major":0,"minor":4,"patch":0,"info":"dev","hash":"cb121cc980-dirty"} ```` Zettelstore conforms somehow to the Standard [[Semantic Versioning|https://semver.org/]]. The names ""major"", ""minor"", and ""patch"" are described in this standard. The name ""info"" contains sometimes some additional information, e.g. ""dev"" for a development version, or ""preview"" for a preview version. The name ""hash"" contains some data to identify the version from a developers perspective. === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an appropriate JSON object. |
Changes to docs/manual/00001012920000.zettel.
1 2 3 4 5 6 | id: 00001012920000 title: Endpoints used by the API role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 | | > > | | | | 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 | id: 00001012920000 title: Endpoints used by the API role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20221219162335 All API endpoints conform to the pattern ''[PREFIX]LETTER[/ZETTEL-ID]'', where: ; ''PREFIX'' : is the URL prefix (default: ""/""), configured via the ''url-prefix'' [[startup configuration|00001004010000]], ; ''LETTER'' : is a single letter that specifies the resource type, ; ''ZETTEL-ID'' : is an optional 14 digits string that uniquely [[identify a zettel|00001006050000]]. The following letters are currently in use: |= Letter:| Without zettel identifier | With [[zettel identifier|00001006050000]] | Mnemonic | ''a'' | POST: [[client authentication|00001012050200]] | | **A**uthenticate | | PUT: [[renew access token|00001012050400]] | | ''o'' | | GET: [[list zettel order|00001012054000]] | **O**rder | ''u'' | | GET [[unlinked references|00001012053900]] | **U**nlinked | ''x'' | GET: [[retrieve administrative data|00001012070500]] | GET: [[list zettel context|00001012053800]] | Conte**x**t | | POST: [[execute command|00001012080100]] | ''z'' | GET: [[list zettel|00001012051200]] | GET: [[retrieve zettel|00001012053300]] | **Z**ettel | | POST: [[create new zettel|00001012053200]] | PUT: [[update a zettel|00001012054200]] | | | DELETE: [[delete zettel|00001012054600]] | | | MOVE: [[rename zettel|00001012054400]] The full URL will contain either the ""http"" oder ""https"" scheme, a host name, and an optional port number. The API examples will assume the ""http"" schema, the local host ""127.0.0.1"", the default port ""23123"", and the default empty ''PREFIX'' ""/"". Therefore, all URLs in the API documentation will begin with ""http://127.0.0.1:23123/"". |
Changes to docs/manual/00001012920500.zettel.
1 | id: 00001012920500 | | | < > < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | id: 00001012920500 title: Encodings available via the [[API|00001012000000]] role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20230316180859 A zettel representation can be encoded in various formats for further processing. * [[html|00001012920510]] * [[md|00001012920513]] * [[sexpr|00001012920516]] * [[shtml|00001012920525]] * [[text|00001012920519]] * [[zmk|00001012920522]] |
Changes to docs/manual/00001012920516.zettel.
1 | id: 00001012920516 | | | | | | | | > | > > > > > > | > > > > > > > > > > > > > > > > > > | 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 | id: 00001012920516 title: Sexpr Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20220422181104 modified: 20230316182622 A zettel representation that is a [[s-expression|https://en.wikipedia.org/wiki/S-expression]] (also known as symbolic expression). It is (relatively) easy to parse and contain all relevant information of a zettel, metadata and content. For example, take a look at the Sexpr encoding of this page, which is available via the ""Info"" sub-page of this zettel: * [[//z/00001012920516?enc=sexpr&part=zettel]], * [[//z/00001012920516?enc=sexpr&part=meta]], * [[//z/00001012920516?enc=sexpr&part=content]]. If transferred via HTTP, the content type will be ''text/plain''. === Syntax of s-expressions There are only two types of elements: atoms and lists. A list always starts with the left parenthesis (""''(''"", U+0028) and ends with a right parenthesis (""'')''"", U+0029). A list may contain a possibly empty sequence of elements, i.e. lists and / or atoms. Before the last element of a list of at least to elements, a full stop character (""''.''"", U+002E) signal a pair as the last two elements. This allows a more space economic storage of data. There are three syntactic forms for an atom: numbers, symbols and strings. A number is a non-empty sequence of digits (""0"" ... ""9""). The smallest number is ``0``, there are no negative numbers. A symbol is a non-empty sequence of printable characters, except left or right parenthesis. Unicode characters of the following categories contains printable characters in the above sense: letter (L), number (N), punctuation (P), symbol (S). Symbols are case-insensitive, i.e. ""''ZETTEL''"" and ""''zettel''"" denote the same symbol. A string starts with a quotation mark (""''"''"", U+0022), contains a possibly empty sequence of Unicode characters, and ends with a quotation mark. To allow a string to contain a quotations mark, it must be prefixed by one backslash (""''\\''"", U+005C). To allow a string to contain a backslash, it also must be prefixed by one backslash. Unicode characters with a code less than U+FF are encoded by by the sequence ""''\\xNM''"", where ''NM'' is the hex encoding of the character. Unicode characters with a code less than U+FFFF are encoded by by the sequence ""''\\uNMOP''"", where ''NMOP'' is the hex encoding of the character. Unicode characters with a code less than U+FFFFFF are encoded by by the sequence ""''\\UNMOPQR''"", where ''NMOPQR'' is the hex encoding of the character. In addition, the sequence ""''\\t''"" encodes a horizontal tab (U+0009), the sequence ""''\\n''"" encodes a line feed (U+000A). Atoms are separated by Unicode characters of category separator (Z). |
Changes to docs/manual/00001012920525.zettel.
1 2 3 4 5 6 | id: 00001012920525 title: SHTML Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230316181044 | | | | | | 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 | id: 00001012920525 title: SHTML Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230316181044 modified: 20230316182614 A zettel representation that is a [[s-expression|https://en.wikipedia.org/wiki/S-expression]], syntactically similar to the [[Sexpr encoding|00001012920516]], but denotes [[HTML|00001012920510]] semantics. It is derived from a XML encoding in s-expressions, called [[SXML|https://en.wikipedia.org/wiki/SXML]]. It is (relatively) easy to parse and contains everything to transform it into real HTML. In contrast to HTML, SHTML is easier to parse and to manipulate. For example, take a look at the SHTML encoding of this page, which is available via the ""Info"" sub-page of this zettel: * [[//z/00001012920525?enc=shtml&part=zettel]], * [[//z/00001012920525?enc=shtml&part=meta]], * [[//z/00001012920525?enc=shtml&part=content]]. If transferred via HTTP, the content type will be ''text/plain''. Internally, if a zettel should be transformed into HTML, the zettel is translated into the [[Sexpr encoding|00001012920516]], which is transformed into this SHTML encoding to produce the [[HTML encoding|00001012920510]]. === Syntax of SHTML There are only two types of elements: atoms and lists, similar to the Sexpr encoding. A list always starts with the left parenthesis (""''(''"", U+0028) and ends with a right parenthesis (""'')''"", U+0029). A list may contain a possibly empty sequence of elements, i.e. lists and / or atoms. Before the last element of a list of at least to elements, a full stop character (""''.''"", U+002E) signal a pair as the last two elements. This allows a more space economic storage of data. An HTML tag like ``< a href="link">Text</a>`` is encoded in SHTML with a list, where the first element is a symbol named a the tag. The second element is an optional encoding of the tag's attributes. Further elements are either other tag encodings or a string. The above tag is encoded as ``(a (@ (href . "link")) "Text")``. Also possible is to encode the attribute without pairs: ``(a (@ (href "link")) "Text")`` (note the missing full stop character). |
Changes to docs/manual/00001012921000.zettel.
1 | id: 00001012921000 | | < | < | > > | < < | < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012921000 title: API: JSON structure of an access token tags: #api #manual #reference #zettelstore syntax: zmk role: manual If the [[authentication process|00001012050200]] was successful, an access token with some additional data is returned. The same is true, if the access token was [[renewed|00001012050400]]. The response is structured as an JSON object, with the following named values: |=Name|Description |''access_token''|The access token itself, as string value, which is a [[JSON Web Token|https://tools.ietf.org/html/rfc7519]] (JWT, RFC 7915) |''token_type''|The type of the token, always set to ''"Bearer"'', as described in [[RFC 6750|https://tools.ietf.org/html/rfc6750]] |''expires_in''|An integer that gives a hint about the lifetime / endurance of the token, measured in seconds |
Changes to docs/manual/00001012921200.zettel.
1 2 3 4 5 | id: 00001012921200 title: API: Encoding of Zettel Access Rights role: manual tags: #api #manual #reference #zettelstore syntax: zmk | < | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | id: 00001012921200 title: API: Encoding of Zettel Access Rights role: manual tags: #api #manual #reference #zettelstore syntax: zmk modified: 20220201171959 Various API calls return a JSON key ''"rights"'' that encodes the access rights the user currently has. It is an integer number between 0 and 62.[^Not all values in this range are used.] The value ""0"" signals that something went wrong internally while determining the access rights. A value of ""1"" says, that the current user has no access right for the given zettel. In most cases, this value will not occur, because only zettel are presented, which are at least readable by the current user. Values ""2"" to ""62"" are binary encoded values, where each bit signals a special right. |
︙ | ︙ |
Deleted docs/manual/00001012930000.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001012930500.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001012931000.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001012931200.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001012931400.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001012931600.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001012931800.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted docs/manual/00001012931900.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00001017000000.zettel.
1 2 3 4 5 6 | id: 00001017000000 title: Tips and Tricks role: manual tags: #manual #zettelstore syntax: zmk created: 20220803170112 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001017000000 title: Tips and Tricks role: manual tags: #manual #zettelstore syntax: zmk created: 20220803170112 modified: 20221205154406 === Welcome Zettel * **Problem:** You want to put your Zettelstore into the public and need a starting zettel for your users. In addition, you still want a ""home zettel"", with all your references to internal, non-public zettel. Zettelstore only allows to specify one [[''home-zettel''|00001004020000#home-zettel]]. * **Solution 1:** *# Create a new zettel with all your references to internal, non-public zettel. |
︙ | ︙ | |||
25 26 27 28 29 30 31 | If multiple user should use the same home zettel, its zettel identifier must be set in all relevant user zettel. === Role-specific Layout of Zettel in Web User Interface (WebUI) [!role-css] * **Problem:** You want to add some CSS when displaying zettel of a specific [[role|00001006020000#role]]. For example, you might want to add a yellow background color for all [[configuration|00001006020100#configuration]] zettel. Or you want a multi-column layout. | | | | | | | | | | > | 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 | If multiple user should use the same home zettel, its zettel identifier must be set in all relevant user zettel. === Role-specific Layout of Zettel in Web User Interface (WebUI) [!role-css] * **Problem:** You want to add some CSS when displaying zettel of a specific [[role|00001006020000#role]]. For example, you might want to add a yellow background color for all [[configuration|00001006020100#configuration]] zettel. Or you want a multi-column layout. * **Solution:** If you enable [[''expert-mode''|00001004020000#expert-mode]], you will have access to a zettel called ""[[Zettelstore Role to CSS Map|00000000029000]]"" (its identifier is ''00000000029000''). This zettel maps a role name to a zettel that must contain the role-specific CSS code. First, create a zettel containing the needed CSS: give it any title, its role is preferably ""configuration"" (but this is not a must). Set its [[''syntax''|00001006020000#syntax]]Â must be set to ""[[css|00001008000000#css]]"". The content must contain the role-specific CSS code, for example ``body {background-color: #FFFFD0}``for a background in a light yellow color. Let's assume, the newly created CSS zettel got the identifier ''20220825200100''. Now, you have to connect this zettel to the zettel called ""Zettelstore Role CSS Map"". Since you have enabled ''expert-mode'', you are allowed to modify it. Add the following metadata ''css-configuration-zid: 20220825200100'' to assign the role-specific CSS code for the role ""configuration"" to the CSS zettel containing that CSS. In general, its role-assigning metadata must be like this pattern: ''css-ROLE-zid: ID'', where ''ROLE'' is the placeholder for the role, and ''ID'' for the zettel identifier containing CSS code. It is allowed to assign more than one role to a specific CSS zettel. * **Discussion:** you have to ensure that the CSS zettel is allowed to be read by the intended audience of the zettel with that given role. For example, if you made zettel with a specific role public visible, the CSS zettel must also have a [[''visibility: public''|00001010070200]] metadata. * **Extension:** if you have already established a role-specific layout for zettel, but you additionally want just one another zettel with another role to be rendered with the same CSS, you have to add metadata to the one zettel: ''css-role: ROLE'', where ''ROLE'' is the placeholder for the role that already is assigned to a specific CSS-based layout. === Zettel synchronization with iCloud (Apple) * **Problem:** You use Zettelstore on various macOS computers and you want to use the sameset of zettel across all computers. * **Solution:** Place your zettel in an iCloud folder. To configure Zettelstore to use the folder, you must specify its location within you directory structure as [[''box-uri-X''|00001004010000#box-uri-x]] (replace ''X'' with an appropriate number). Your iCloud folder is typically placed in the folder ''~/Library/Mobile Documents/com~apple~CloudDocs''. |
︙ | ︙ |
Deleted docs/manual/00001019990010.zettel.
|
| < < < < < < < < |
Deleted docs/manual/20231128184200.zettel.
|
| < < < < < < < |
Changes to docs/readmezip.txt.
︙ | ︙ | |||
13 14 15 16 17 18 19 | https://zettelstore.de/manual/. It is a live example of the zettelstore software, running in read-only mode. You can download it separately and it is possible to make it directly available for your local Zettelstore. The software, including the manual, is licensed under the European Union Public License 1.2 (or later). See the separate file LICENSE.txt. | | > | 13 14 15 16 17 18 19 20 21 | https://zettelstore.de/manual/. It is a live example of the zettelstore software, running in read-only mode. You can download it separately and it is possible to make it directly available for your local Zettelstore. The software, including the manual, is licensed under the European Union Public License 1.2 (or later). See the separate file LICENSE.txt. To get in contact with the developer, send an email to ds@zettelstore.de or follow Zettelstore on Twitter: https://twitter.com/zettelstore. |
Added domain/content.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 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package domain import ( "bytes" "encoding/base64" "errors" "io" "unicode" "unicode/utf8" "zettelstore.de/z/input" ) // Content is just the content of a zettel. type Content struct { data []byte isBinary bool } // NewContent creates a new content from a string. func NewContent(data []byte) Content { return Content{data: data, isBinary: IsBinary(data)} } // Length returns the number of bytes stored. func (zc *Content) Length() int { return len(zc.data) } // Equal compares two content values. func (zc *Content) Equal(o *Content) bool { if zc == nil { return o == nil } if zc.isBinary != o.isBinary { return false } return bytes.Equal(zc.data, o.data) } // Set content to new string value. func (zc *Content) Set(data []byte) { zc.data = data zc.isBinary = IsBinary(data) } // Write it to a Writer func (zc *Content) Write(w io.Writer) (int, error) { return w.Write(zc.data) } // AsString returns the content itself is a string. func (zc *Content) AsString() string { return string(zc.data) } // AsBytes returns the content itself is a byte slice. func (zc *Content) AsBytes() []byte { return zc.data } // IsBinary returns true if the content contains non-unicode values or is, // interpreted a text, with a high probability binary content. func (zc *Content) IsBinary() bool { return zc.isBinary } // TrimSpace remove some space character in content, if it is not binary content. func (zc *Content) TrimSpace() { if zc.isBinary { return } inp := input.NewInput(zc.data) pos := inp.Pos for inp.Ch != input.EOS { if input.IsEOLEOS(inp.Ch) { inp.Next() pos = inp.Pos continue } if !input.IsSpace(inp.Ch) { break } inp.Next() } zc.data = bytes.TrimRightFunc(inp.Src[pos:], unicode.IsSpace) } // Encode content for future transmission. func (zc *Content) Encode() (data, encoding string) { if !zc.isBinary { return zc.AsString(), "" } return base64.StdEncoding.EncodeToString(zc.data), "base64" } // SetDecoded content to the decoded value of the given string. func (zc *Content) SetDecoded(data, encoding string) error { switch encoding { case "": zc.data = []byte(data) case "base64": decoded, err := base64.StdEncoding.DecodeString(data) if err != nil { return err } zc.data = decoded default: return errors.New("unknown encoding " + encoding) } zc.isBinary = IsBinary(zc.data) return nil } // IsBinary returns true if the given data appears to be non-text data. func IsBinary(data []byte) bool { if !utf8.Valid(data) { return true } l := len(data) for i := 0; i < l; i++ { if data[i] == 0 { return true } } return false } |
Added domain/content_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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package domain_test import ( "testing" "zettelstore.de/z/domain" ) func TestContentIsBinary(t *testing.T) { t.Parallel() td := []struct { s string exp bool }{ {"abc", false}, {"äöü", false}, {"", false}, {string([]byte{0}), true}, } for i, tc := range td { content := domain.NewContent([]byte(tc.s)) got := content.IsBinary() if got != tc.exp { t.Errorf("TC=%d: expected %v, got %v", i, tc.exp, got) } } } func TestTrimSpace(t *testing.T) { t.Parallel() testcases := []struct { in, exp string }{ {"", ""}, {" ", ""}, {"abc", "abc"}, {" abc", " abc"}, {"abc ", "abc"}, {"abc \n", "abc"}, {"abc\n ", "abc"}, {"\nabc", "abc"}, {" \nabc", "abc"}, {" \n abc", " abc"}, {" \n\n abc", " abc"}, {" \n \n abc", " abc"}, {" \n \n abc \n \n ", " abc"}, } for _, tc := range testcases { c := domain.NewContent([]byte(tc.in)) c.TrimSpace() got := c.AsString() if got != tc.exp { t.Errorf("TrimSpace(%q) should be %q, but got %q", tc.in, tc.exp, got) } } } |
Added domain/id/id.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 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package id provides domain specific types, constants, and functions about // zettel identifier. package id import ( "strconv" "time" "zettelstore.de/c/api" ) // Zid is the internal identifier of a zettel. Typically, it is a // time stamp of the form "YYYYMMDDHHmmSS" converted to an unsigned integer. // A zettelstore implementation should try to set the last two digits to zero, // e.g. the seconds should be zero, type Zid uint64 // Some important ZettelIDs. const ( Invalid = Zid(0) // Invalid is a Zid that will never be valid ) // ZettelIDs that are used as Zid more than once. // // Note: if you change some values, ensure that you also change them in the // Constant box. They are mentioned there literally, because these // constants are not available there. var ( ConfigurationZid = MustParse(api.ZidConfiguration) BaseTemplateZid = MustParse(api.ZidBaseTemplate) LoginTemplateZid = MustParse(api.ZidLoginTemplate) ListTemplateZid = MustParse(api.ZidListTemplate) ZettelTemplateZid = MustParse(api.ZidZettelTemplate) InfoTemplateZid = MustParse(api.ZidInfoTemplate) FormTemplateZid = MustParse(api.ZidFormTemplate) RenameTemplateZid = MustParse(api.ZidRenameTemplate) DeleteTemplateZid = MustParse(api.ZidDeleteTemplate) ContextTemplateZid = MustParse(api.ZidContextTemplate) ErrorTemplateZid = MustParse(api.ZidErrorTemplate) RoleCSSMapZid = MustParse(api.ZidRoleCSSMap) EmojiZid = MustParse(api.ZidEmoji) TOCNewTemplateZid = MustParse(api.ZidTOCNewTemplate) DefaultHomeZid = MustParse(api.ZidDefaultHome) ) const maxZid = 99999999999999 // 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, 10, 47) 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) != 14 { 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 [14]byte zid.toByteArray(&result) return string(result[:]) } // Bytes converts the zettel identification to a byte slice of 14 digits. // Only defined for valid ids. func (zid Zid) Bytes() []byte { var result [14]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 *[14]byte) { date := uint64(zid) / 1000000 fullyear := date / 10000 century := fullyear / 100 year := fullyear % 100 monthday := date % 10000 month := monthday / 100 day := monthday % 100 time := uint64(zid) % 1000000 hmtime := time / 100 second := time % 100 hour := hmtime / 100 minute := hmtime % 100 result[0] = byte(century/10) + '0' result[1] = byte(century%10) + '0' result[2] = byte(year/10) + '0' result[3] = byte(year%10) + '0' result[4] = byte(month/10) + '0' result[5] = byte(month%10) + '0' result[6] = byte(day/10) + '0' result[7] = byte(day%10) + '0' result[8] = byte(hour/10) + '0' result[9] = byte(hour%10) + '0' result[10] = byte(minute/10) + '0' result[11] = byte(minute%10) + '0' result[12] = byte(second/10) + '0' result[13] = byte(second%10) + '0' } // 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 } // ZidLayout to transform a date into a Zid and into other internal dates. const ZidLayout = "20060102150405" // New returns a new zettel id based on the current time. func New(withSeconds bool) Zid { now := time.Now().Local() var s string if withSeconds { s = now.Format(ZidLayout) } else { s = now.Format("20060102150400") } res, err := Parse(s) if err != nil { panic(err) } return res } |
Added domain/id/id_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 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 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package id_test provides unit tests for testing zettel id specific functions. package id_test import ( "testing" "zettelstore.de/z/domain/id" ) func TestIsValid(t *testing.T) { t.Parallel() validIDs := []string{ "00000000000001", "00000000000020", "00000000000300", "00000000004000", "00000000050000", "00000000600000", "00000007000000", "00000080000000", "00000900000000", "00001000000000", "00020000000000", "00300000000000", "04000000000000", "50000000000000", "99999999999999", "00001007030200", "20200310195100", } 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) } s := zid.String() if s != sid { t.Errorf( "i=%d: zid=%v does not format to %q, but to %q", i, zid, sid, s) } } invalidIDs := []string{ "", "0", "a", "00000000000000", "0000000000000a", "000000000000000", "20200310T195100", } 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) } } } var sResult string // to disable compiler optimization in loop below func BenchmarkString(b *testing.B) { var s string for n := 0; n < b.N; n++ { s = id.Zid(12345678901200).String() } sResult = s } var bResult []byte // to disable compiler optimization in loop below func BenchmarkBytes(b *testing.B) { var bs []byte for n := 0; n < b.N; n++ { bs = id.Zid(12345678901200).Bytes() } bResult = bs } |
Added domain/id/set.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 | //----------------------------------------------------------------------------- // Copyright (c) 2021-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. //----------------------------------------------------------------------------- package id // Set is a set of zettel identifier type Set map[Zid]struct{} // NewSet returns a new set of identifier with the given initial values. func NewSet(zids ...Zid) Set { l := len(zids) if l < 8 { l = 8 } result := make(Set, l) result.AddSlice(zids) return result } // NewSetCap returns a new set of identifier with the given capacity and initial values. func NewSetCap(c int, zids ...Zid) Set { l := len(zids) if c < l { c = l } if c < 8 { c = 8 } result := make(Set, c) result.AddSlice(zids) return result } // Zid adds a Zid to the set. func (s Set) Zid(zid Zid) Set { if s == nil { return NewSet(zid) } s[zid] = struct{}{} return s } // Contains return true if the set is nil or if the set contains the given Zettel identifier. func (s Set) Contains(zid Zid) bool { if s != nil { _, found := s[zid] return found } return true } // Add all member from the other set. func (s Set) Add(other Set) Set { if s == nil { return other } for zid := range other { s[zid] = struct{}{} } return s } // AddSlice adds all identifier of the given slice to the set. func (s Set) AddSlice(sl Slice) { for _, zid := range sl { s[zid] = struct{}{} } } // Sorted returns the set as a sorted slice of zettel identifier. func (s Set) Sorted() Slice { if l := len(s); l > 0 { result := make(Slice, 0, l) for zid := range s { result = append(result, zid) } result.Sort() return result } return nil } // IntersectOrSet removes all zettel identifier that are not in the other set. // Both sets can be modified by this method. One of them is the set returned. // It contains the intersection of both, if s is not nil. // // If s == nil, then the other set is always returned. func (s Set) IntersectOrSet(other Set) Set { if s == nil { return other } if len(s) > len(other) { s, other = other, s } for zid := range s { _, otherOk := other[zid] if !otherOk { delete(s, zid) } } return s } // Remove all zettel identifier from 's' that are in the set 'other'. func (s Set) Remove(other Set) { if s == nil || other == nil { return } for zid := range other { delete(s, zid) } } |
Added domain/id/set_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 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 | //----------------------------------------------------------------------------- // Copyright (c) 2021-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. //----------------------------------------------------------------------------- package id_test import ( "testing" "zettelstore.de/z/domain/id" ) func TestSetContains(t *testing.T) { t.Parallel() testcases := []struct { s id.Set zid id.Zid exp bool }{ {nil, id.Invalid, true}, {nil, 14, true}, {id.NewSet(), id.Invalid, false}, {id.NewSet(), 1, false}, {id.NewSet(), id.Invalid, false}, {id.NewSet(1), 1, true}, } for i, tc := range testcases { got := tc.s.Contains(tc.zid) if got != tc.exp { t.Errorf("%d: %v.Contains(%v) == %v, but got %v", i, tc.s, tc.zid, tc.exp, got) } } } func TestSetAdd(t *testing.T) { t.Parallel() testcases := []struct { s1, s2 id.Set exp id.Slice }{ {nil, nil, nil}, {id.NewSet(), nil, nil}, {id.NewSet(), id.NewSet(), nil}, {nil, id.NewSet(1), id.Slice{1}}, {id.NewSet(1), nil, id.Slice{1}}, {id.NewSet(1), id.NewSet(), id.Slice{1}}, {id.NewSet(1), id.NewSet(2), id.Slice{1, 2}}, {id.NewSet(1), id.NewSet(1), id.Slice{1}}, } for i, tc := range testcases { sl1 := tc.s1.Sorted() sl2 := tc.s2.Sorted() got := tc.s1.Add(tc.s2).Sorted() if !got.Equal(tc.exp) { t.Errorf("%d: %v.Add(%v) should be %v, but got %v", i, sl1, sl2, tc.exp, got) } } } func TestSetSorted(t *testing.T) { t.Parallel() testcases := []struct { set id.Set exp id.Slice }{ {nil, nil}, {id.NewSet(), nil}, {id.NewSet(9, 4, 6, 1, 7), id.Slice{1, 4, 6, 7, 9}}, } for i, tc := range testcases { got := tc.set.Sorted() if !got.Equal(tc.exp) { t.Errorf("%d: %v.Sorted() should be %v, but got %v", i, tc.set, tc.exp, got) } } } func TestSetIntersectOrSet(t *testing.T) { t.Parallel() testcases := []struct { s1, s2 id.Set exp id.Slice }{ {nil, nil, nil}, {id.NewSet(), nil, nil}, {nil, id.NewSet(), nil}, {id.NewSet(), id.NewSet(), nil}, {id.NewSet(1), nil, nil}, {nil, id.NewSet(1), id.Slice{1}}, {id.NewSet(1), id.NewSet(), nil}, {id.NewSet(), id.NewSet(1), nil}, {id.NewSet(1), id.NewSet(2), nil}, {id.NewSet(2), id.NewSet(1), nil}, {id.NewSet(1), id.NewSet(1), id.Slice{1}}, } for i, tc := range testcases { sl1 := tc.s1.Sorted() sl2 := tc.s2.Sorted() got := tc.s1.IntersectOrSet(tc.s2).Sorted() if !got.Equal(tc.exp) { t.Errorf("%d: %v.IntersectOrSet(%v) should be %v, but got %v", i, sl1, sl2, tc.exp, got) } } } func TestSetRemove(t *testing.T) { t.Parallel() testcases := []struct { s1, s2 id.Set exp id.Slice }{ {nil, nil, nil}, {id.NewSet(), nil, nil}, {id.NewSet(), id.NewSet(), nil}, {id.NewSet(1), nil, id.Slice{1}}, {id.NewSet(1), id.NewSet(), id.Slice{1}}, {id.NewSet(1), id.NewSet(2), id.Slice{1}}, {id.NewSet(1), id.NewSet(1), id.Slice{}}, } for i, tc := range testcases { sl1 := tc.s1.Sorted() sl2 := tc.s2.Sorted() newS1 := id.NewSet(sl1...) newS1.Remove(tc.s2) got := newS1.Sorted() if !got.Equal(tc.exp) { t.Errorf("%d: %v.Remove(%v) should be %v, but got %v", i, sl1, sl2, tc.exp, got) } } } // func BenchmarkSet(b *testing.B) { // s := id.Set{} // for i := 0; i < b.N; i++ { // s[id.Zid(i)] = true // } // } func BenchmarkSet(b *testing.B) { s := id.Set{} for i := 0; i < b.N; i++ { s[id.Zid(i)] = struct{}{} } } |
Added domain/id/slice.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 | //----------------------------------------------------------------------------- // Copyright (c) 2021-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. //----------------------------------------------------------------------------- package id import ( "sort" "strings" ) // Slice is a sequence of zettel identifier. A special case is a sorted slice. type Slice []Zid func (zs Slice) Len() int { return len(zs) } func (zs Slice) Less(i, j int) bool { return zs[i] < zs[j] } func (zs Slice) Swap(i, j int) { zs[i], zs[j] = zs[j], zs[i] } // Sort a slice of Zids. func (zs Slice) Sort() { sort.Sort(zs) } // Copy a zettel identifier slice func (zs Slice) Copy() Slice { if zs == nil { return nil } result := make(Slice, len(zs)) copy(result, zs) return result } // Equal reports whether zs and other are the same length and contain the samle zettel // identifier. A nil argument is equivalent to an empty slice. func (zs Slice) Equal(other Slice) bool { if len(zs) != len(other) { return false } if len(zs) == 0 { return true } for i, e := range zs { if e != other[i] { return false } } return true } func (zs Slice) String() string { if len(zs) == 0 { return "" } var sb strings.Builder for i, zid := range zs { if i > 0 { sb.WriteByte(' ') } sb.WriteString(zid.String()) } return sb.String() } |
Added domain/id/slice_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 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 | //----------------------------------------------------------------------------- // Copyright (c) 2021-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. //----------------------------------------------------------------------------- package id_test import ( "testing" "zettelstore.de/z/domain/id" ) func TestSliceSort(t *testing.T) { t.Parallel() zs := id.Slice{9, 4, 6, 1, 7} zs.Sort() exp := id.Slice{1, 4, 6, 7, 9} if !zs.Equal(exp) { t.Errorf("Slice.Sort did not work. Expected %v, got %v", exp, zs) } } func TestCopy(t *testing.T) { t.Parallel() var orig id.Slice got := orig.Copy() if got != nil { t.Errorf("Nil copy resulted in %v", got) } orig = id.Slice{9, 4, 6, 1, 7} got = orig.Copy() if !orig.Equal(got) { t.Errorf("Slice.Copy did not work. Expected %v, got %v", orig, got) } } func TestSliceEqual(t *testing.T) { t.Parallel() testcases := []struct { s1, s2 id.Slice exp bool }{ {nil, nil, true}, {nil, id.Slice{}, true}, {nil, id.Slice{1}, false}, {id.Slice{1}, id.Slice{1}, true}, {id.Slice{1}, id.Slice{2}, false}, {id.Slice{1, 2}, id.Slice{2, 1}, false}, {id.Slice{1, 2}, id.Slice{1, 2}, true}, } for i, tc := range testcases { got := tc.s1.Equal(tc.s2) if got != tc.exp { t.Errorf("%d/%v.Equal(%v)==%v, but got %v", i, tc.s1, tc.s2, tc.exp, got) } got = tc.s2.Equal(tc.s1) if got != tc.exp { t.Errorf("%d/%v.Equal(%v)==%v, but got %v", i, tc.s2, tc.s1, tc.exp, got) } } } func TestSliceString(t *testing.T) { t.Parallel() testcases := []struct { in id.Slice exp string }{ {nil, ""}, {id.Slice{}, ""}, {id.Slice{1}, "00000000000001"}, {id.Slice{1, 2}, "00000000000001 00000000000002"}, } for i, tc := range testcases { got := tc.in.String() if got != tc.exp { t.Errorf("%d/%v: expected %q, but got %q", i, tc.in, tc.exp, got) } } } |
Added domain/meta/collection.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 | //----------------------------------------------------------------------------- // Copyright (c) 2022-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. //----------------------------------------------------------------------------- package meta import "sort" // Arrangement stores metadata within its categories. // Typecally a category might be a tag name, a role name, a syntax value. type Arrangement map[string][]*Meta // CreateArrangement by inspecting a given key and use the found // value as a category. func CreateArrangement(metaList []*Meta, key string) Arrangement { if len(metaList) == 0 { return nil } descr := Type(key) if descr == nil { return nil } if descr.IsSet { return createSetArrangement(metaList, key) } return createSimplearrangement(metaList, key) } func createSetArrangement(metaList []*Meta, key string) Arrangement { a := make(Arrangement) for _, m := range metaList { if vals, ok := m.GetList(key); ok { for _, val := range vals { a[val] = append(a[val], m) } } } return a } func createSimplearrangement(metaList []*Meta, key string) Arrangement { a := make(Arrangement) for _, m := range metaList { if val, ok := m.Get(key); ok && val != "" { a[val] = append(a[val], m) } } return a } // Counted returns the list of categories, together with the number of // metadata for each category. func (a Arrangement) Counted() CountedCategories { if len(a) == 0 { return nil } result := make(CountedCategories, 0, len(a)) for cat, metas := range a { result = append(result, CountedCategory{Name: cat, Count: len(metas)}) } return result } // CountedCategory contains of a name and the number how much this name occured // somewhere. type CountedCategory struct { Name string Count int } // CountedCategories is the list of CountedCategories. // Every name must occur only once. type CountedCategories []CountedCategory // SortByName sorts the list by the name attribute. // Since each name must occur only once, two CountedCategories cannot have // the same name. func (ccs CountedCategories) SortByName() { sort.Slice(ccs, func(i, j int) bool { return ccs[i].Name < ccs[j].Name }) } // SortByCount sorts the list by the count attribute, descending. // If two counts are equal, elements are sorted by name. func (ccs CountedCategories) SortByCount() { sort.Slice(ccs, func(i, j int) bool { iCount, jCount := ccs[i].Count, ccs[j].Count if iCount > jCount { return true } if iCount == jCount { return ccs[i].Name < ccs[j].Name } return false }) } // Categories returns just the category names. func (ccs CountedCategories) Categories() []string { result := make([]string, len(ccs)) for i, cc := range ccs { result[i] = cc.Name } return result } |
Added domain/meta/meta.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 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package meta provides the domain specific type 'meta'. package meta import ( "regexp" "sort" "strings" "unicode" "unicode/utf8" "zettelstore.de/c/api" "zettelstore.de/c/maps" "zettelstore.de/z/domain/id" "zettelstore.de/z/input" "zettelstore.de/z/strfun" ) type keyUsage int const ( _ keyUsage = iota usageUser // Key will be manipulated by the user usageComputed // Key is computed by zettelstore usageProperty // Key is computed and not stored by zettelstore ) // DescriptionKey formally describes each supported metadata key. type DescriptionKey struct { Name string Type *DescriptionType usage keyUsage Inverse string } // IsComputed returns true, if metadata is computed and not set by the user. func (kd *DescriptionKey) IsComputed() bool { return kd.usage >= usageComputed } // IsProperty returns true, if metadata is a computed property. func (kd *DescriptionKey) IsProperty() bool { return kd.usage >= usageProperty } // IsStoredComputed retruns true, if metadata is computed, but also stored. func (kd *DescriptionKey) IsStoredComputed() bool { return kd.usage == usageComputed } var registeredKeys = make(map[string]*DescriptionKey) func registerKey(name string, t *DescriptionType, usage keyUsage, inverse string) { if _, ok := registeredKeys[name]; ok { panic("Key '" + name + "' already defined") } if inverse != "" { if t != TypeID && t != TypeIDSet { panic("Inversable key '" + name + "' is not identifier type, but " + t.String()) } inv, ok := registeredKeys[inverse] if !ok { panic("Inverse Key '" + inverse + "' not found") } if !inv.IsComputed() { panic("Inverse Key '" + inverse + "' is not computed.") } if inv.Type != TypeIDSet { panic("Inverse Key '" + inverse + "' is not an identifier set, but " + inv.Type.String()) } } registeredKeys[name] = &DescriptionKey{name, t, usage, inverse} } // IsComputed returns true, if key denotes a computed metadata key. func IsComputed(name string) bool { if kd, ok := registeredKeys[name]; ok { return kd.IsComputed() } return false } // IsProperty returns true, if key denotes a property metadata value. func IsProperty(name string) bool { if kd, ok := registeredKeys[name]; ok { return kd.IsProperty() } return false } // IsStoredComputed returns true, if key denotes a computed metadata key that is stored. func IsStoredComputed(name string) bool { if kd, ok := registeredKeys[name]; ok { return kd.IsStoredComputed() } return false } // Inverse returns the name of the inverse key. func Inverse(name string) string { if kd, ok := registeredKeys[name]; ok { return kd.Inverse } return "" } // GetDescription returns the key description object of the given key name. func GetDescription(name string) DescriptionKey { if d, ok := registeredKeys[name]; ok { return *d } return DescriptionKey{Type: Type(name)} } // GetSortedKeyDescriptions delivers all metadata key descriptions as a slice, sorted by name. func GetSortedKeyDescriptions() []*DescriptionKey { keys := maps.Keys(registeredKeys) result := make([]*DescriptionKey, 0, len(keys)) for _, n := range keys { result = append(result, registeredKeys[n]) } return result } // Supported keys. func init() { registerKey(api.KeyID, TypeID, usageComputed, "") registerKey(api.KeyTitle, TypeEmpty, usageUser, "") registerKey(api.KeyRole, TypeWord, usageUser, "") registerKey(api.KeyTags, TypeTagSet, usageUser, "") registerKey(api.KeySyntax, TypeWord, usageUser, "") // Properties that are inverse keys registerKey(api.KeyFolge, TypeIDSet, usageProperty, "") registerKey(api.KeySuccessors, TypeIDSet, usageProperty, "") registerKey(api.KeyAuthor, TypeString, usageUser, "") registerKey(api.KeyBack, TypeIDSet, usageProperty, "") registerKey(api.KeyBackward, TypeIDSet, usageProperty, "") registerKey(api.KeyBoxNumber, TypeNumber, usageProperty, "") registerKey(api.KeyCopyright, TypeString, usageUser, "") registerKey(api.KeyCreated, TypeTimestamp, usageComputed, "") registerKey(api.KeyCredential, TypeCredential, usageUser, "") registerKey(api.KeyDead, TypeIDSet, usageProperty, "") registerKey(api.KeyForward, TypeIDSet, usageProperty, "") registerKey(api.KeyLang, TypeWord, usageUser, "") registerKey(api.KeyLicense, TypeEmpty, usageUser, "") registerKey(api.KeyModified, TypeTimestamp, usageComputed, "") registerKey(api.KeyPrecursor, TypeIDSet, usageUser, api.KeyFolge) registerKey(api.KeyPredecessor, TypeID, usageUser, api.KeySuccessors) registerKey(api.KeyPublished, TypeTimestamp, usageProperty, "") registerKey(api.KeyQuery, TypeEmpty, usageUser, "") registerKey(api.KeyReadOnly, TypeWord, usageUser, "") registerKey(api.KeySummary, TypeZettelmarkup, usageUser, "") registerKey(api.KeyURL, TypeURL, usageUser, "") registerKey(api.KeyUselessFiles, TypeString, usageProperty, "") registerKey(api.KeyUserID, TypeWord, usageUser, "") registerKey(api.KeyUserRole, TypeWord, usageUser, "") registerKey(api.KeyVisibility, TypeWord, usageUser, "") } // NewPrefix is the prefix for metadata key in template zettel for creating new zettel. const NewPrefix = "new-" // Meta contains all meta-data of a zettel. type Meta struct { Zid id.Zid pairs map[string]string YamlSep bool } // New creates a new chunk for storing metadata. func New(zid id.Zid) *Meta { return &Meta{Zid: zid, pairs: make(map[string]string, 5)} } // NewWithData creates metadata object with given data. func NewWithData(zid id.Zid, data map[string]string) *Meta { pairs := make(map[string]string, len(data)) for k, v := range data { pairs[k] = v } return &Meta{Zid: zid, pairs: pairs} } // Length returns the number of bytes stored for the metadata. func (m *Meta) Length() int { if m == nil { return 0 } result := 6 // storage needed for Zid for k, v := range m.pairs { result += len(k) + len(v) + 1 // 1 because separator } return result } // Clone returns a new copy of the metadata. func (m *Meta) Clone() *Meta { return &Meta{ Zid: m.Zid, pairs: m.Map(), YamlSep: m.YamlSep, } } // Map returns a copy of the meta data as a string map. func (m *Meta) Map() map[string]string { pairs := make(map[string]string, len(m.pairs)) for k, v := range m.pairs { pairs[k] = v } return pairs } var reKey = regexp.MustCompile("^[0-9a-z][-0-9a-z]{0,254}$") // KeyIsValid returns true, the the key is a valid string. func KeyIsValid(key string) bool { return reKey.MatchString(key) } // Pair is one key-value-pair of a Zettel meta. type Pair struct { Key string Value string } var firstKeys = []string{api.KeyTitle, api.KeyRole, api.KeyTags, api.KeySyntax} var firstKeySet strfun.Set func init() { firstKeySet = strfun.NewSet(firstKeys...) } // Set stores the given string value under the given key. func (m *Meta) Set(key, value string) { if key != api.KeyID { m.pairs[key] = trimValue(value) } } // SetNonEmpty stores the given value under the given key, if the value is non-empty. // An empty value will delete the previous association. func (m *Meta) SetNonEmpty(key, value string) { if value == "" { delete(m.pairs, key) } else if key != api.KeyID { m.pairs[key] = trimValue(value) } } func trimValue(value string) string { return strings.TrimFunc(value, input.IsSpace) } // Get retrieves the string value of a given key. The bool value signals, // whether there was a value stored or not. func (m *Meta) Get(key string) (string, bool) { if m == nil { return "", false } if key == api.KeyID { return m.Zid.String(), true } value, ok := m.pairs[key] return value, ok } // GetDefault retrieves the string value of the given key. If no value was // stored, the given default value is returned. func (m *Meta) GetDefault(key, def string) string { if value, found := m.Get(key); found { return value } return def } // GetTitle returns the title of the metadata. It is the only key that has a // defined default value: the string representation of the zettel identifier. func (m *Meta) GetTitle() string { if title, found := m.Get(api.KeyTitle); found { return title } return m.Zid.String() } // Pairs returns not computed key/values pairs stored, in a specific order. // First come the pairs with predefined keys: MetaTitleKey, MetaTagsKey, MetaSyntaxKey, // MetaContextKey. Then all other pairs are append to the list, ordered by key. func (m *Meta) Pairs() []Pair { return m.doPairs(m.getFirstKeys(), notComputedKey) } // ComputedPairs returns all key/values pairs stored, in a specific order. First come // the pairs with predefined keys: MetaTitleKey, MetaTagsKey, MetaSyntaxKey, // MetaContextKey. Then all other pairs are append to the list, ordered by key. func (m *Meta) ComputedPairs() []Pair { return m.doPairs(m.getFirstKeys(), anyKey) } // PairsRest returns not computed key/values pairs stored, except the values with // predefined keys. The pairs are ordered by key. func (m *Meta) PairsRest() []Pair { result := make([]Pair, 0, len(m.pairs)) return m.doPairs(result, notComputedKey) } // ComputedPairsRest returns all key/values pairs stored, except the values with // predefined keys. The pairs are ordered by key. func (m *Meta) ComputedPairsRest() []Pair { result := make([]Pair, 0, len(m.pairs)) return m.doPairs(result, anyKey) } func notComputedKey(key string) bool { return !IsComputed(key) } func anyKey(string) bool { return true } func (m *Meta) doPairs(firstKeys []Pair, addKeyPred func(string) bool) []Pair { keys := m.getKeysRest(addKeyPred) for _, k := range keys { firstKeys = append(firstKeys, Pair{k, m.pairs[k]}) } return firstKeys } func (m *Meta) getFirstKeys() []Pair { result := make([]Pair, 0, len(m.pairs)) for _, key := range firstKeys { if value, ok := m.pairs[key]; ok { result = append(result, Pair{key, value}) } } return result } func (m *Meta) getKeysRest(addKeyPred func(string) bool) []string { keys := make([]string, 0, len(m.pairs)) for k := range m.pairs { if !firstKeySet.Has(k) && addKeyPred(k) { keys = append(keys, k) } } sort.Strings(keys) return keys } // Delete removes a key from the data. func (m *Meta) Delete(key string) { if key != api.KeyID { delete(m.pairs, key) } } // Equal compares to metas for equality. func (m *Meta) Equal(o *Meta, allowComputed bool) bool { if m == nil && o == nil { return true } if m == nil || o == nil || m.Zid != o.Zid { return false } tested := make(strfun.Set, len(m.pairs)) for k, v := range m.pairs { tested.Set(k) if !equalValue(k, v, o, allowComputed) { return false } } for k, v := range o.pairs { if !tested.Has(k) && !equalValue(k, v, m, allowComputed) { return false } } return true } func equalValue(key, val string, other *Meta, allowComputed bool) bool { if allowComputed || !IsComputed(key) { if valO, found := other.pairs[key]; !found || val != valO { return false } } return true } // Sanitize all metadata keys and values, so that they can be written safely into a file. func (m *Meta) Sanitize() { if m == nil { return } for k, v := range m.pairs { m.pairs[RemoveNonGraphic(k)] = RemoveNonGraphic(v) } } // RemoveNonGraphic changes the given string not to include non-graphical characters. // It is needed to sanitize meta data. func RemoveNonGraphic(s string) string { if s == "" { return "" } pos := 0 var sb strings.Builder for pos < len(s) { nextPos := strings.IndexFunc(s[pos:], func(r rune) bool { return !unicode.IsGraphic(r) }) if nextPos < 0 { break } sb.WriteString(s[pos:nextPos]) sb.WriteByte(' ') _, size := utf8.DecodeRuneInString(s[nextPos:]) pos = nextPos + size } if pos == 0 { return strings.TrimSpace(s) } sb.WriteString(s[pos:]) return strings.TrimSpace(sb.String()) } |
Added domain/meta/meta_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 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 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package meta_test provides tests for the domain specific type 'meta'. package meta import ( "strings" "testing" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" ) const testID = id.Zid(98765432101234) func TestKeyIsValid(t *testing.T) { t.Parallel() validKeys := []string{"0", "a", "0-", "title", "title-----", strings.Repeat("r", 255)} for _, key := range validKeys { if !KeyIsValid(key) { t.Errorf("Key %q wrongly identified as invalid key", key) } } invalidKeys := []string{"", "-", "-a", "Title", "a_b", strings.Repeat("e", 256)} for _, key := range invalidKeys { if KeyIsValid(key) { t.Errorf("Key %q wrongly identified as valid key", key) } } } func TestTitleHeader(t *testing.T) { t.Parallel() m := New(testID) if got, ok := m.Get(api.KeyTitle); ok && got != "" { t.Errorf("Title is not empty, but %q", got) } addToMeta(m, api.KeyTitle, " ") if got, ok := m.Get(api.KeyTitle); ok && got != "" { t.Errorf("Title is not empty, but %q", got) } const st = "A simple text" addToMeta(m, api.KeyTitle, " "+st+" ") if got, ok := m.Get(api.KeyTitle); !ok || got != st { t.Errorf("Title is not %q, but %q", st, got) } addToMeta(m, api.KeyTitle, " "+st+"\t") const exp = st + " " + st if got, ok := m.Get(api.KeyTitle); !ok || got != exp { t.Errorf("Title is not %q, but %q", exp, got) } m = New(testID) const at = "A Title" addToMeta(m, api.KeyTitle, at) addToMeta(m, api.KeyTitle, " ") if got, ok := m.Get(api.KeyTitle); !ok || got != at { t.Errorf("Title is not %q, but %q", at, got) } } func checkSet(t *testing.T, exp []string, m *Meta, key string) { t.Helper() got, _ := m.GetList(key) for i, tag := range exp { if i < len(got) { if tag != got[i] { t.Errorf("Pos=%d, expected %q, got %q", i, exp[i], got[i]) } } else { t.Errorf("Expected %q, but is missing", exp[i]) } } if len(exp) < len(got) { t.Errorf("Extra tags: %q", got[len(exp):]) } } func TestTagsHeader(t *testing.T) { t.Parallel() m := New(testID) checkSet(t, []string{}, m, api.KeyTags) addToMeta(m, api.KeyTags, "") checkSet(t, []string{}, m, api.KeyTags) addToMeta(m, api.KeyTags, " #t1 #t2 #t3 #t4 ") checkSet(t, []string{"#t1", "#t2", "#t3", "#t4"}, m, api.KeyTags) addToMeta(m, api.KeyTags, "#t5") checkSet(t, []string{"#t1", "#t2", "#t3", "#t4", "#t5"}, m, api.KeyTags) addToMeta(m, api.KeyTags, "t6") checkSet(t, []string{"#t1", "#t2", "#t3", "#t4", "#t5"}, m, api.KeyTags) } func TestSyntax(t *testing.T) { t.Parallel() m := New(testID) if got, ok := m.Get(api.KeySyntax); ok || got != "" { t.Errorf("Syntax is not %q, but %q", "", got) } addToMeta(m, api.KeySyntax, " ") if got, _ := m.Get(api.KeySyntax); got != "" { t.Errorf("Syntax is not %q, but %q", "", got) } addToMeta(m, api.KeySyntax, "MarkDown") const exp = "markdown" if got, ok := m.Get(api.KeySyntax); !ok || got != exp { t.Errorf("Syntax is not %q, but %q", exp, got) } addToMeta(m, api.KeySyntax, " ") if got, _ := m.Get(api.KeySyntax); got != "" { t.Errorf("Syntax is not %q, but %q", "", got) } } func checkHeader(t *testing.T, exp map[string]string, gotP []Pair) { t.Helper() got := make(map[string]string, len(gotP)) for _, p := range gotP { got[p.Key] = p.Value if _, ok := exp[p.Key]; !ok { t.Errorf("Key %q is not expected, but has value %q", p.Key, p.Value) } } for k, v := range exp { if gv, ok := got[k]; !ok || v != gv { if ok { t.Errorf("Key %q is not %q, but %q", k, v, got[k]) } else { t.Errorf("Key %q missing, should have value %q", k, v) } } } } func TestDefaultHeader(t *testing.T) { t.Parallel() m := New(testID) addToMeta(m, "h1", "d1") addToMeta(m, "H2", "D2") addToMeta(m, "H1", "D1.1") exp := map[string]string{"h1": "d1 D1.1", "h2": "D2"} checkHeader(t, exp, m.Pairs()) addToMeta(m, "", "d0") checkHeader(t, exp, m.Pairs()) addToMeta(m, "h3", "") exp["h3"] = "" checkHeader(t, exp, m.Pairs()) addToMeta(m, "h3", " ") checkHeader(t, exp, m.Pairs()) addToMeta(m, "h4", " ") exp["h4"] = "" checkHeader(t, exp, m.Pairs()) } func TestDelete(t *testing.T) { t.Parallel() m := New(testID) m.Set("key", "val") if got, ok := m.Get("key"); !ok || got != "val" { t.Errorf("Value != %q, got: %v/%q", "val", ok, got) } m.Set("key", "") if got, ok := m.Get("key"); !ok || got != "" { t.Errorf("Value != %q, got: %v/%q", "", ok, got) } m.Delete("key") if got, ok := m.Get("key"); ok || got != "" { t.Errorf("Value != %q, got: %v/%q", "", ok, got) } } func TestEqual(t *testing.T) { t.Parallel() testcases := []struct { pairs1, pairs2 []string allowComputed bool exp bool }{ {nil, nil, true, true}, {nil, nil, false, true}, {[]string{"a", "a"}, nil, false, false}, {[]string{"a", "a"}, nil, true, false}, {[]string{api.KeyFolge, "0"}, nil, true, false}, {[]string{api.KeyFolge, "0"}, nil, false, true}, {[]string{api.KeyFolge, "0"}, []string{api.KeyFolge, "0"}, true, true}, {[]string{api.KeyFolge, "0"}, []string{api.KeyFolge, "0"}, false, true}, } for i, tc := range testcases { m1 := pairs2meta(tc.pairs1) m2 := pairs2meta(tc.pairs2) got := m1.Equal(m2, tc.allowComputed) if tc.exp != got { t.Errorf("%d: %v =?= %v: expected=%v, but got=%v", i, tc.pairs1, tc.pairs2, tc.exp, got) } got = m2.Equal(m1, tc.allowComputed) if tc.exp != got { t.Errorf("%d: %v =!= %v: expected=%v, but got=%v", i, tc.pairs1, tc.pairs2, tc.exp, got) } } // Pathologic cases var m1, m2 *Meta if !m1.Equal(m2, true) { t.Error("Nil metas should be treated equal") } m1 = New(testID) if m1.Equal(m2, true) { t.Error("Empty meta should not be equal to nil") } if m2.Equal(m1, true) { t.Error("Nil meta should should not be equal to empty") } m2 = New(testID + 1) if m1.Equal(m2, true) { t.Error("Different ID should differentiate") } if m2.Equal(m1, true) { t.Error("Different ID should differentiate") } } func pairs2meta(pairs []string) *Meta { m := New(testID) for i := 0; i < len(pairs); i = i + 2 { m.Set(pairs[i], pairs[i+1]) } return m } func TestRemoveNonGraphic(t *testing.T) { testCases := []struct { inp string exp string }{ {"", ""}, {" ", ""}, {"a", "a"}, {"a ", "a"}, {"a b", "a b"}, {"\n", ""}, {"a\n", "a"}, {"a\nb", "a b"}, {"a\tb", "a b"}, } for i, tc := range testCases { got := RemoveNonGraphic(tc.inp) if tc.exp != got { t.Errorf("%q/%d: expected %q, but got %q", tc.inp, i, tc.exp, got) } } } |
Added domain/meta/parse.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 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package meta import ( "strings" "zettelstore.de/c/api" "zettelstore.de/c/maps" "zettelstore.de/z/domain/id" "zettelstore.de/z/input" "zettelstore.de/z/strfun" ) // NewFromInput parses the meta data of a zettel. func NewFromInput(zid id.Zid, inp *input.Input) *Meta { if inp.Ch == '-' && inp.PeekN(0) == '-' && inp.PeekN(1) == '-' { skipToEOL(inp) inp.EatEOL() } meta := New(zid) for { skipSpace(inp) switch inp.Ch { case '\r': if inp.Peek() == '\n' { inp.Next() } fallthrough case '\n': inp.Next() return meta case input.EOS: return meta case '%': skipToEOL(inp) inp.EatEOL() continue } parseHeader(meta, inp) if inp.Ch == '-' && inp.PeekN(0) == '-' && inp.PeekN(1) == '-' { skipToEOL(inp) inp.EatEOL() meta.YamlSep = true return meta } } } func parseHeader(m *Meta, inp *input.Input) { pos := inp.Pos for isHeader(inp.Ch) { inp.Next() } key := inp.Src[pos:inp.Pos] skipSpace(inp) if inp.Ch == ':' { inp.Next() } var val []byte for { skipSpace(inp) pos = inp.Pos skipToEOL(inp) val = append(val, inp.Src[pos:inp.Pos]...) inp.EatEOL() if !input.IsSpace(inp.Ch) { break } val = append(val, ' ') } addToMeta(m, string(key), string(val)) } func skipSpace(inp *input.Input) { for input.IsSpace(inp.Ch) { inp.Next() } } func skipToEOL(inp *input.Input) { for { switch inp.Ch { case '\n', '\r', input.EOS: return } inp.Next() } } // Return true iff rune is valid for header key. func isHeader(ch rune) bool { return ('a' <= ch && ch <= 'z') || ('0' <= ch && ch <= '9') || ch == '-' || ('A' <= ch && ch <= 'Z') } type predValidElem func(string) bool func addToSet(set strfun.Set, elems []string, useElem predValidElem) { for _, s := range elems { if len(s) > 0 && useElem(s) { set.Set(s) } } } func addSet(m *Meta, key, val string, useElem predValidElem) { newElems := strings.Fields(val) oldElems, ok := m.GetList(key) if !ok { oldElems = nil } set := make(strfun.Set, len(newElems)+len(oldElems)) addToSet(set, newElems, useElem) if len(set) == 0 { // Nothing to add. Maybe because of rejected elements. return } addToSet(set, oldElems, useElem) m.SetList(key, maps.Keys(set)) } func addData(m *Meta, k, v string) { if o, ok := m.Get(k); !ok || o == "" { m.Set(k, v) } else if v != "" { m.Set(k, o+" "+v) } } func addToMeta(m *Meta, key, val string) { v := trimValue(val) key = strings.ToLower(key) if !KeyIsValid(key) { return } switch key { case "", api.KeyID: // Empty key and 'id' key will be ignored return } switch Type(key) { case TypeTagSet: addSet(m, key, strings.ToLower(v), func(s string) bool { return s[0] == '#' && len(s) > 1 }) case TypeWord: m.Set(key, strings.ToLower(v)) case TypeWordSet: addSet(m, key, strings.ToLower(v), func(s string) bool { return true }) case TypeID: if _, err := id.Parse(v); err == nil { m.Set(key, v) } case TypeIDSet: addSet(m, key, v, func(s string) bool { _, err := id.Parse(s) return err == nil }) case TypeTimestamp: if _, ok := TimeValue(v); ok { m.Set(key, v) } default: addData(m, key, v) } } |
Added domain/meta/parse_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 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 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package meta_test import ( "strings" "testing" "zettelstore.de/c/api" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" ) func parseMetaStr(src string) *meta.Meta { return meta.NewFromInput(testID, input.NewInput([]byte(src))) } func TestEmpty(t *testing.T) { t.Parallel() m := parseMetaStr("") if got, ok := m.Get(api.KeySyntax); ok || got != "" { t.Errorf("Syntax is not %q, but %q", "", got) } if got, ok := m.GetList(api.KeyTags); ok || len(got) > 0 { t.Errorf("Tags are not nil, but %v", got) } } func TestTitle(t *testing.T) { t.Parallel() td := []struct{ s, e string }{ {api.KeyTitle + ": a title", "a title"}, {api.KeyTitle + ": a\n\t title", "a title"}, {api.KeyTitle + ": a\n\t title\r\n x", "a title x"}, {api.KeyTitle + " AbC", "AbC"}, {api.KeyTitle + " AbC\n ded", "AbC ded"}, {api.KeyTitle + ": o\ntitle: p", "o p"}, {api.KeyTitle + ": O\n\ntitle: P", "O"}, {api.KeyTitle + ": b\r\ntitle: c", "b c"}, {api.KeyTitle + ": B\r\n\r\ntitle: C", "B"}, {api.KeyTitle + ": r\rtitle: q", "r q"}, {api.KeyTitle + ": R\r\rtitle: Q", "R"}, } for i, tc := range td { m := parseMetaStr(tc.s) if got, ok := m.Get(api.KeyTitle); !ok || got != tc.e { t.Log(m) t.Errorf("TC=%d: expected %q, got %q", i, tc.e, got) } } } func TestTags(t *testing.T) { t.Parallel() testcases := []struct { src string exp string }{ {"", ""}, {api.KeyTags + ":", ""}, {api.KeyTags + ": c", ""}, {api.KeyTags + ": #", ""}, {api.KeyTags + ": #c", "c"}, {api.KeyTags + ": #c #", "c"}, {api.KeyTags + ": #c #b", "b c"}, {api.KeyTags + ": #c # #", "c"}, {api.KeyTags + ": #c # #b", "b c"}, } for i, tc := range testcases { m := parseMetaStr(tc.src) tags, found := m.GetTags(api.KeyTags) if !found { if tc.exp != "" { t.Errorf("%d / %q: no %s found", i, tc.src, api.KeyTags) } continue } if tc.exp == "" && len(tags) > 0 { t.Errorf("%d / %q: expected no %s, but got %v", i, tc.src, api.KeyTags, tags) continue } got := strings.Join(tags, " ") if tc.exp != got { t.Errorf("%d / %q: expected %q, got: %q", i, tc.src, tc.exp, got) } } } func TestNewFromInput(t *testing.T) { t.Parallel() testcases := []struct { input string exp []meta.Pair }{ {"", []meta.Pair{}}, {" a:b", []meta.Pair{{"a", "b"}}}, {"%a:b", []meta.Pair{}}, {"a:b\r\n\r\nc:d", []meta.Pair{{"a", "b"}}}, {"a:b\r\n%c:d", []meta.Pair{{"a", "b"}}}, {"% a:b\r\n c:d", []meta.Pair{{"c", "d"}}}, {"---\r\na:b\r\n", []meta.Pair{{"a", "b"}}}, {"---\r\na:b\r\n--\r\nc:d", []meta.Pair{{"a", "b"}, {"c", "d"}}}, {"---\r\na:b\r\n---\r\nc:d", []meta.Pair{{"a", "b"}}}, {"---\r\na:b\r\n----\r\nc:d", []meta.Pair{{"a", "b"}}}, {"new-title:\nnew-url:", []meta.Pair{{"new-title", ""}, {"new-url", ""}}}, } for i, tc := range testcases { meta := parseMetaStr(tc.input) if got := meta.Pairs(); !equalPairs(tc.exp, got) { t.Errorf("TC=%d: expected=%v, got=%v", i, tc.exp, got) } } // Test, whether input position is correct. inp := input.NewInput([]byte("---\na:b\n---\nX")) m := meta.NewFromInput(testID, inp) exp := []meta.Pair{{"a", "b"}} if got := m.Pairs(); !equalPairs(exp, got) { t.Errorf("Expected=%v, got=%v", exp, got) } expCh := 'X' if gotCh := inp.Ch; gotCh != expCh { t.Errorf("Expected=%v, got=%v", expCh, gotCh) } } func equalPairs(one, two []meta.Pair) bool { if len(one) != len(two) { return false } for i := 0; i < len(one); i++ { if one[i].Key != two[i].Key || one[i].Value != two[i].Value { return false } } return true } func TestPrecursorIDSet(t *testing.T) { t.Parallel() var testdata = []struct { inp string exp string }{ {"", ""}, {"123", ""}, {"12345678901234", "12345678901234"}, {"123 12345678901234", "12345678901234"}, {"12345678901234 123", "12345678901234"}, {"01234567890123 123 12345678901234", "01234567890123 12345678901234"}, {"12345678901234 01234567890123", "01234567890123 12345678901234"}, } for i, tc := range testdata { m := parseMetaStr(api.KeyPrecursor + ": " + tc.inp) if got, ok := m.Get(api.KeyPrecursor); (!ok && tc.exp != "") || tc.exp != got { t.Errorf("TC=%d: expected %q, but got %q when parsing %q", i, tc.exp, got, tc.inp) } } } |
Added domain/meta/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 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package meta import ( "strconv" "strings" "sync" "time" "zettelstore.de/c/api" "zettelstore.de/z/domain/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) TypeWordSet = registerType(api.MetaWordSet, true) 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) } var ( cachedTypedKeys = make(map[string]*DescriptionType) mxTypedKey sync.RWMutex suffixTypes = map[string]*DescriptionType{ "-number": TypeNumber, "-role": TypeWord, "-set": TypeWordSet, "-title": TypeZettelmarkup, "-url": 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, " ") } } // SetNow stores the current timestamp under the given key. func (m *Meta) SetNow(key string) { m.Set(key, time.Now().Local().Format(id.ZidLayout)) } // 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.ZidLayout, value); err == nil { return t, true } return time.Time{}, false } // GetTime returns the time value of the given key. func (m *Meta) GetTime(key string) (time.Time, bool) { if value, ok := m.Get(key); ok { return TimeValue(value) } return time.Time{}, false } // 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 } // GetTags returns the list of tags as a string list. Each tag does not begin // with the '#' character, in contrast to `GetList`. func (m *Meta) GetTags(key string) ([]string, bool) { tagsValue, ok := m.Get(key) if !ok { return nil, false } tags := ListFromValue(strings.ToLower(tagsValue)) for i, tag := range tags { tags[i] = CleanTag(tag) } return tags, len(tags) > 0 } // 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 } // GetListOrNil retrieves the string list value of a given key. If there was // nothing stores, a nil list is returned. func (m *Meta) GetListOrNil(key string) []string { if value, ok := m.GetList(key); ok { return value } return nil } // 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 } |
Added domain/meta/type_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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package meta_test import ( "strconv" "testing" "time" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) func TestNow(t *testing.T) { t.Parallel() m := meta.New(id.Invalid) m.SetNow("key") val, ok := m.Get("key") if !ok { t.Error("Unable to get value of key") } if len(val) != 14 { t.Errorf("Value is not 14 digits long: %q", val) } if _, err := strconv.ParseInt(val, 10, 64); err != nil { t.Errorf("Unable to parse %q as an int64: %v", val, err) } if _, ok = m.GetTime("key"); !ok { t.Errorf("Unable to get time from value %q", val) } } func TestGetTime(t *testing.T) { t.Parallel() testCases := []struct { value string valid bool exp time.Time }{ {"", false, time.Time{}}, {"1", false, time.Time{}}, {"00000000000000", false, time.Time{}}, {"98765432109876", false, time.Time{}}, {"20201221111905", true, time.Date(2020, time.December, 21, 11, 19, 5, 0, time.UTC)}, } for i, tc := range testCases { got, ok := meta.TimeValue(tc.value) if ok != tc.valid { t.Errorf("%d: parsing of %q should be %v, but got %v", i, tc.value, tc.valid, ok) continue } if got != tc.exp { t.Errorf("%d: parsing of %q should return %v, but got %v", i, tc.value, tc.exp, got) } } } |
Added domain/meta/values.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 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package meta import ( "fmt" "zettelstore.de/c/api" ) // Supported syntax values. const ( SyntaxCSS = api.ValueSyntaxCSS SyntaxDraw = api.ValueSyntaxDraw SyntaxGif = api.ValueSyntaxGif SyntaxHTML = api.ValueSyntaxHTML SyntaxJPEG = "jpeg" SyntaxJPG = "jpg" SyntaxMarkdown = api.ValueSyntaxMarkdown SyntaxMD = api.ValueSyntaxMD SyntaxMustache = api.ValueSyntaxMustache SyntaxNone = api.ValueSyntaxNone SyntaxPlain = "plain" SyntaxPNG = "png" SyntaxSVG = api.ValueSyntaxSVG SyntaxText = api.ValueSyntaxText SyntaxTxt = "txt" SyntaxWebp = "webp" SyntaxZmk = api.ValueSyntaxZmk ) // Visibility enumerates the variations of the 'visibility' meta key. type Visibility int // Supported values for visibility. const ( _ Visibility = iota VisibilityUnknown VisibilityPublic VisibilityCreator VisibilityLogin VisibilityOwner VisibilityExpert ) var visMap = map[string]Visibility{ api.ValueVisibilityPublic: VisibilityPublic, api.ValueVisibilityCreator: VisibilityCreator, api.ValueVisibilityLogin: VisibilityLogin, api.ValueVisibilityOwner: VisibilityOwner, api.ValueVisibilityExpert: VisibilityExpert, } var revVisMap = map[Visibility]string{} func init() { for k, v := range visMap { revVisMap[v] = k } } // GetVisibility returns the visibility value of the given string func GetVisibility(val string) Visibility { if vis, ok := visMap[val]; ok { return vis } return VisibilityUnknown } func (v Visibility) String() string { if s, ok := revVisMap[v]; ok { return s } return fmt.Sprintf("Unknown (%d)", v) } // UserRole enumerates the supported values of meta key 'user-role'. type UserRole int // Supported values for user roles. const ( _ UserRole = iota UserRoleUnknown UserRoleCreator UserRoleReader UserRoleWriter UserRoleOwner ) var urMap = map[string]UserRole{ api.ValueUserRoleCreator: UserRoleCreator, api.ValueUserRoleReader: UserRoleReader, api.ValueUserRoleWriter: UserRoleWriter, api.ValueUserRoleOwner: UserRoleOwner, } // GetUserRole role returns the user role of the given string. func GetUserRole(val string) UserRole { if ur, ok := urMap[val]; ok { return ur } return UserRoleUnknown } |
Added domain/meta/write.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 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package meta import "io" // Write writes metadata to a writer, excluding computed and propery values. func (m *Meta) Write(w io.Writer) (int, error) { return m.doWrite(w, IsComputed) } // WriteComputed writes metadata to a writer, including computed values, // but excluding property values. func (m *Meta) WriteComputed(w io.Writer) (int, error) { return m.doWrite(w, IsProperty) } func (m *Meta) doWrite(w io.Writer, ignoreKeyPred func(string) bool) (length int, err error) { for _, p := range m.ComputedPairs() { key := p.Key if ignoreKeyPred(key) { continue } if err != nil { break } var l int l, err = io.WriteString(w, key) length += l if err == nil { l, err = w.Write(colonSpace) length += l } if err == nil { l, err = io.WriteString(w, p.Value) length += l } if err == nil { l, err = w.Write(newline) length += l } } return length, err } var ( colonSpace = []byte{':', ' '} newline = []byte{'\n'} ) |
Added domain/meta/write_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 50 51 52 53 54 55 56 57 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- package meta_test import ( "strings" "testing" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) const testID = id.Zid(98765432101234) func newMeta(title string, tags []string, syntax string) *meta.Meta { m := meta.New(testID) if title != "" { m.Set(api.KeyTitle, title) } if tags != nil { m.Set(api.KeyTags, strings.Join(tags, " ")) } if syntax != "" { m.Set(api.KeySyntax, syntax) } return m } func assertWriteMeta(t *testing.T, m *meta.Meta, expected string) { t.Helper() var sb strings.Builder m.Write(&sb) if got := sb.String(); got != expected { t.Errorf("\nExp: %q\ngot: %q", expected, got) } } func TestWriteMeta(t *testing.T) { t.Parallel() assertWriteMeta(t, newMeta("", nil, ""), "") m := newMeta("TITLE", []string{"#t1", "#t2"}, "syntax") assertWriteMeta(t, m, "title: TITLE\ntags: #t1 #t2\nsyntax: syntax\n") m = newMeta("TITLE", nil, "") m.Set("user", "zettel") m.Set("auth", "basic") assertWriteMeta(t, m, "title: TITLE\nauth: basic\nuser: zettel\n") } |
Added domain/zettel.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 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- // Package domain provides domain specific types, constants, and functions. package domain import "zettelstore.de/z/domain/meta" // Zettel is the main data object of a zettelstore. type Zettel struct { Meta *meta.Meta // Some additional meta-data. Content Content // The content of the zettel itself. } // Length returns the number of bytes to store the zettel (in a domain view, // not in a technical view). func (z Zettel) Length() int { return z.Meta.Length() + z.Content.Length() } // Equal compares two zettel for equality. func (z Zettel) Equal(o Zettel, allowComputed bool) bool { return z.Meta.Equal(o.Meta, allowComputed) && z.Content.Equal(&o.Content) } |
Changes to encoder/encoder.go.
︙ | ︙ | |||
13 14 15 16 17 18 19 | package encoder import ( "errors" "fmt" "io" | | | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package encoder import ( "errors" "fmt" "io" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/domain/meta" ) // Encoder is an interface that allows to encode different parts of a zettel. type Encoder interface { WriteZettel(io.Writer, *ast.ZettelNode, EvalMetaFunc) (int, error) WriteMeta(io.Writer, *meta.Meta, EvalMetaFunc) (int, error) WriteContent(io.Writer, *ast.ZettelNode) (int, error) |
︙ | ︙ | |||
41 42 43 44 45 46 47 | ErrNoWriteMeta = errors.New("method WriteMeta is not implemented") ErrNoWriteContent = errors.New("method WriteContent is not implemented") ErrNoWriteBlocks = errors.New("method WriteBlocks is not implemented") ErrNoWriteInlines = errors.New("method WriteInlines is not implemented") ) // Create builds a new encoder with the given options. | | | | < < < < < | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | ErrNoWriteMeta = errors.New("method WriteMeta is not implemented") ErrNoWriteContent = errors.New("method WriteContent is not implemented") ErrNoWriteBlocks = errors.New("method WriteBlocks is not implemented") ErrNoWriteInlines = errors.New("method WriteInlines is not implemented") ) // Create builds a new encoder with the given options. func Create(enc api.EncodingEnum) Encoder { if create, ok := registry[enc]; ok { return create() } return nil } // CreateFunc produces a new encoder. type CreateFunc func() Encoder var registry = map[api.EncodingEnum]CreateFunc{} // Register the encoder for later retrieval. func Register(enc api.EncodingEnum, create CreateFunc) { if _, ok := registry[enc]; ok { panic(fmt.Sprintf("Encoder %q already registered", enc)) |
︙ | ︙ |
Changes to encoder/encoder_blob_test.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package encoder_test import ( "testing" | | | | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | //----------------------------------------------------------------------------- package encoder_test import ( "testing" "zettelstore.de/c/api" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/parser" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. ) type blobTestCase struct { descr string blob []byte |
︙ | ︙ | |||
37 38 39 40 41 42 43 | 0x00, 0x00, 0x00 |