Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.16.0 To trunk
2024-09-05
| ||
13:52 | Update Golang dependencies ... (Leaf check-in: 49708c56a4 user: t73fde tags: trunk) | |
2024-08-30
| ||
13:59 | Index content only for zettel with id >= 000100000000 ... (check-in: e8cf69aef9 user: stern tags: trunk) | |
2023-12-28
| ||
16:41 | Fix sxn code that removed role-based customization, esp. for an additional action if role is "tag" ... (check-in: e721174596 user: t73fde tags: release-0.16) | |
2023-11-30
| ||
18:10 | Increase version to 0.17.0-dev to begin next development cycle ... (check-in: 9d654c5606 user: stern tags: trunk) | |
16:08 | Version 0.16.0 ... (check-in: a5afffaf5f user: stern tags: trunk, release, v0.16.0) | |
2023-11-29
| ||
16:53 | Add predefined role zettel ... (check-in: 514e400b28 user: stern tags: trunk) | |
Changes to .fossil-settings/ignore-glob.
1 2 | bin/* releases/* | < | 1 2 | bin/* releases/* |
Changes to Makefile.
1 2 3 4 5 6 7 8 9 | ## 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. | | | | | | | | | | 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 | ## 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. .PHONY: check relcheck api version build release clean check: go run tools/check/check.go relcheck: go run tools/check/check.go -r api: go run tools/testapi/testapi.go version: @echo $(shell go run tools/build/build.go version) build: go run tools/build/build.go build release: go run tools/build/build.go release clean: go run tools/clean/clean.go |
Changes to README.md.
︙ | ︙ | |||
9 10 11 12 13 14 15 | gradually, one major focus is a long-term store of these notes, hence the name “Zettelstore”. To get an initial impression, take a look at the [manual](https://zettelstore.de/manual/). It is a live example of the zettelstore software, running in read-only mode. | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | gradually, one major focus is a long-term store of these notes, hence the name “Zettelstore”. To get an initial impression, take a look at the [manual](https://zettelstore.de/manual/). It is a live example of the zettelstore software, running in read-only mode. [Zettelstore Client](https://t73f.de/r/zsc) provides client software to access Zettelstore via its API more easily, [Zettelstore Contrib](https://zettelstore.de/contrib) contains contributed software, which 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). |
︙ | ︙ |
Changes to VERSION.
|
| | | 1 | 0.19.0-dev |
Changes to ast/ast.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 provides the abstract syntax tree for parsed zettel content. package ast import ( "net/url" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package ast provides the abstract syntax tree for parsed zettel content. package ast import ( "net/url" |
︙ | ︙ |
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 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package ast import "t73f.de/r/zsc/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.
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 ast 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 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package ast import ( "t73f.de/r/zsc/attrs" ) // Definitions of inline nodes. // InlineSlice is a list of BlockNodes. type InlineSlice []InlineNode func (*InlineSlice) inlineNode() { /* Just a marker */ } // WalkChildren walks down to the list. func (is *InlineSlice) WalkChildren(v Visitor) { for _, in := range *is { Walk(v, in) } } // -------------------------------------------------------------------------- // TextNode just contains some text. type TextNode struct { Text string // The text itself. } func (*TextNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. func (*TextNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // BreakNode signals a new line that must / should be interpreted as a new line break. type BreakNode struct { Hard bool // Hard line break? } |
︙ | ︙ |
Changes to ast/ref.go.
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 ( "net/url" "strings" "zettelstore.de/z/zettel/id" ) // QueryPrefix is the prefix that denotes a query expression. | > > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package ast import ( "net/url" "strings" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" ) // QueryPrefix is the prefix that denotes a query expression. const QueryPrefix = api.QueryPrefix // ParseReference parses a string and returns a reference. func ParseReference(s string) *Reference { if invalidReference(s) { return &Reference{URL: nil, Value: s, State: RefStateInvalid} } if strings.HasPrefix(s, QueryPrefix) { |
︙ | ︙ |
Changes to ast/ref_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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_test import ( "testing" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package ast_test import ( "testing" |
︙ | ︙ |
Changes to ast/walk.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 ast // Visitor is a visitor for walking the AST. type Visitor interface { Visit(node Node) Visitor | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package ast // Visitor is a visitor for walking the AST. type Visitor interface { Visit(node Node) Visitor |
︙ | ︙ |
Changes to ast/walk_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 ast_test import ( "testing" | > > > | | | | | | | | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package ast_test import ( "testing" "t73f.de/r/zsc/attrs" "zettelstore.de/z/ast" ) func BenchmarkWalk(b *testing.B) { root := ast.BlockSlice{ &ast.HeadingNode{ Inlines: ast.InlineSlice{&ast.TextNode{Text: "A Simple Heading"}}, }, &ast.ParaNode{ Inlines: ast.InlineSlice{&ast.TextNode{Text: "This is the introduction."}}, }, &ast.NestedListNode{ Kind: ast.NestedListUnordered, Items: []ast.ItemSlice{ []ast.ItemNode{ &ast.ParaNode{ Inlines: ast.InlineSlice{&ast.TextNode{Text: "Item 1"}}, }, }, []ast.ItemNode{ &ast.ParaNode{ Inlines: ast.InlineSlice{&ast.TextNode{Text: "Item 2"}}, }, }, }, }, &ast.ParaNode{ Inlines: ast.InlineSlice{&ast.TextNode{Text: "This is some intermediate text."}}, }, ast.CreateParaNode( &ast.FormatNode{ Kind: ast.FormatEmph, Attrs: attrs.Attributes(map[string]string{ "": "class", "color": "green", }), Inlines: ast.InlineSlice{&ast.TextNode{Text: "This is some emphasized text."}}, }, &ast.TextNode{Text: " "}, &ast.LinkNode{ Ref: &ast.Reference{Value: "http://zettelstore.de"}, Inlines: ast.InlineSlice{&ast.TextNode{Text: "URL text."}}, }, ), } v := benchVisitor{} b.ResetTimer() for range b.N { ast.Walk(&v, &root) } } type benchVisitor struct{} func (bv *benchVisitor) Visit(ast.Node) ast.Visitor { return bv } |
Changes to auth/auth.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 auth provides services for authentification / authorization. package auth import ( "time" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package auth provides services for authentification / authorization. package auth import ( "time" |
︙ | ︙ | |||
88 89 90 91 92 93 94 | // User is allowed to read zettel CanRead(user, m *meta.Meta) bool // User is allowed to write zettel. CanWrite(user, oldMeta, newMeta *meta.Meta) bool | < < < | 91 92 93 94 95 96 97 98 99 100 101 102 103 | // User is allowed to read zettel CanRead(user, m *meta.Meta) bool // User is allowed to write zettel. CanWrite(user, oldMeta, newMeta *meta.Meta) bool // User is allowed to delete zettel. CanDelete(user, m *meta.Meta) bool // User is allowed to refresh box data. CanRefresh(user *meta.Meta) bool } |
Changes to auth/cred/cred.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 cred provides some function for handling credentials. package cred import ( "bytes" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package cred provides some function for handling credentials. package cred import ( "bytes" |
︙ | ︙ |
Changes to auth/impl/digest.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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 impl import ( "bytes" "crypto" "crypto/hmac" "encoding/base64" | > > > | | | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "bytes" "crypto" "crypto/hmac" "encoding/base64" "t73f.de/r/sx" "t73f.de/r/sx/sxreader" ) var encoding = base64.RawURLEncoding const digestAlg = crypto.SHA384 func sign(claim sx.Object, secret []byte) ([]byte, error) { var buf bytes.Buffer _, err := sx.Print(&buf, claim) if err != nil { return nil, err } token := make([]byte, encoding.EncodedLen(buf.Len())) encoding.Encode(token, buf.Bytes()) digest := hmac.New(digestAlg.New, secret) _, err = digest.Write(buf.Bytes()) if err != nil { return nil, err } dig := digest.Sum(nil) encDig := make([]byte, encoding.EncodedLen(len(dig))) encoding.Encode(encDig, dig) |
︙ | ︙ |
Changes to auth/impl/impl.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 impl provides services for authentification / authorization. package impl import ( "errors" "hash/fnv" "io" "time" | > > > | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package impl provides services for authentification / authorization. package impl import ( "errors" "hash/fnv" "io" "time" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/sexp" "zettelstore.de/z/auth" "zettelstore.de/z/auth/policy" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/kernel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" |
︙ | ︙ | |||
85 86 87 88 89 90 91 | if !ok || subject == "" { return nil, ErrNoIdent } now := time.Now().Round(time.Second) sClaim := sx.MakeList( sx.Int64(kind), | | | 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | if !ok || subject == "" { return nil, ErrNoIdent } now := time.Now().Round(time.Second) sClaim := sx.MakeList( sx.Int64(kind), sx.MakeString(subject), sx.Int64(now.Unix()), sx.Int64(now.Add(d).Unix()), sx.Int64(ident.Zid), ) return sign(sClaim, a.secret) } |
︙ | ︙ | |||
118 119 120 121 122 123 124 | vals, err := sexp.ParseList(obj, "isiii") if err != nil { return ErrMalformedToken } if auth.TokenKind(vals[0].(sx.Int64)) != k { return ErrOtherKind } | | | | 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 | vals, err := sexp.ParseList(obj, "isiii") if err != nil { return ErrMalformedToken } if auth.TokenKind(vals[0].(sx.Int64)) != k { return ErrOtherKind } ident := vals[1].(sx.String).GetValue() if ident == "" { return ErrNoIdent } issued := time.Unix(int64(vals[2].(sx.Int64)), 0) expires := time.Unix(int64(vals[3].(sx.Int64)), 0) now := time.Now().Round(time.Second) if expires.Before(now) { return ErrTokenExpired } zid := id.Zid(vals[4].(sx.Int64)) if !zid.IsValid() { return ErrNoZid } tokenData.Ident = string(ident) tokenData.Issued = issued tokenData.Now = now tokenData.Expires = expires tokenData.Zid = zid return nil } |
︙ | ︙ |
Changes to auth/policy/anon.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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/auth" "zettelstore.de/z/config" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package policy import ( "zettelstore.de/z/auth" "zettelstore.de/z/config" |
︙ | ︙ | |||
29 30 31 32 33 34 35 | return ap.pre.CanRead(user, m) && ap.checkVisibility(m) } func (ap *anonPolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return ap.pre.CanWrite(user, oldMeta, newMeta) && ap.checkVisibility(oldMeta) } | < < < < | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | return ap.pre.CanRead(user, m) && ap.checkVisibility(m) } func (ap *anonPolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return ap.pre.CanWrite(user, oldMeta, newMeta) && ap.checkVisibility(oldMeta) } func (ap *anonPolicy) CanDelete(user, m *meta.Meta) bool { return ap.pre.CanDelete(user, m) && ap.checkVisibility(m) } func (ap *anonPolicy) CanRefresh(user *meta.Meta) bool { if ap.authConfig.GetExpertMode() || ap.authConfig.GetSimpleMode() { return true |
︙ | ︙ |
Changes to auth/policy/box.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package policy import ( "context" |
︙ | ︙ | |||
71 72 73 74 75 76 77 | return zettel.Zettel{}, box.NewErrNotAllowed("GetZettel", user, zid) } func (pp *polBox) GetAllZettel(ctx context.Context, zid id.Zid) ([]zettel.Zettel, error) { return pp.box.GetAllZettel(ctx, zid) } | | | 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | return zettel.Zettel{}, box.NewErrNotAllowed("GetZettel", user, zid) } func (pp *polBox) GetAllZettel(ctx context.Context, zid id.Zid) ([]zettel.Zettel, error) { return pp.box.GetAllZettel(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) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { m, err := pp.box.GetMeta(ctx, zid) if err != nil { return nil, err |
︙ | ︙ | |||
115 116 117 118 119 120 121 | } if pp.policy.CanWrite(user, oldZettel.Meta, zettel.Meta) { return pp.box.UpdateZettel(ctx, zettel) } return box.NewErrNotAllowed("Write", user, zid) } | < < < < < < < < < < < < < < < < | 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | } if pp.policy.CanWrite(user, oldZettel.Meta, zettel.Meta) { return pp.box.UpdateZettel(ctx, zettel) } return box.NewErrNotAllowed("Write", user, zid) } 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 { z, err := pp.box.GetZettel(ctx, zid) if err != nil { |
︙ | ︙ |
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 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package policy import ( "t73f.de/r/zsc/api" "zettelstore.de/z/auth" "zettelstore.de/z/zettel/meta" ) type defaultPolicy struct { manager auth.AuthzManager } func (*defaultPolicy) CanCreate(_, _ *meta.Meta) bool { return true } func (*defaultPolicy) CanRead(_, _ *meta.Meta) bool { return true } func (d *defaultPolicy) CanWrite(user, oldMeta, _ *meta.Meta) bool { return d.canChange(user, oldMeta) } func (d *defaultPolicy) CanDelete(user, m *meta.Meta) bool { return d.canChange(user, m) } func (*defaultPolicy) CanRefresh(user *meta.Meta) bool { return user != nil } func (d *defaultPolicy) canChange(user, m *meta.Meta) bool { metaRo, ok := m.Get(api.KeyReadOnly) if !ok { |
︙ | ︙ |
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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package policy import ( "t73f.de/r/zsc/api" "zettelstore.de/z/auth" "zettelstore.de/z/config" "zettelstore.de/z/zettel/meta" ) type ownerPolicy struct { manager auth.AuthzManager |
︙ | ︙ | |||
108 109 110 111 112 113 114 | switch userRole := o.manager.GetUserRole(user); userRole { case meta.UserRoleReader, meta.UserRoleCreator: return false } return o.userCanCreate(user, newMeta) } | < < < < < < < < < < | 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | switch userRole := o.manager.GetUserRole(user); userRole { case meta.UserRoleReader, meta.UserRoleCreator: return false } return o.userCanCreate(user, newMeta) } func (o *ownerPolicy) CanDelete(user, m *meta.Meta) bool { if user == nil || !o.pre.CanDelete(user, m) { return false } if res, ok := o.checkVisibility(user, o.authConfig.GetVisibility(m)); ok { return res } |
︙ | ︙ |
Changes to auth/policy/policy.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 provides some interfaces and implementation for authorizsation policies. package policy import ( "zettelstore.de/z/auth" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package policy provides some interfaces and implementation for authorizsation policies. package policy import ( "zettelstore.de/z/auth" |
︙ | ︙ | |||
53 54 55 56 57 58 59 | } func (p *prePolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return oldMeta != nil && newMeta != nil && oldMeta.Zid == newMeta.Zid && p.post.CanWrite(user, oldMeta, newMeta) } | < < < < | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | } func (p *prePolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { return oldMeta != nil && newMeta != nil && oldMeta.Zid == newMeta.Zid && p.post.CanWrite(user, oldMeta, newMeta) } func (p *prePolicy) CanDelete(user, m *meta.Meta) bool { return m != nil && p.post.CanDelete(user, m) } func (p *prePolicy) CanRefresh(user *meta.Meta) bool { return p.post.CanRefresh(user) } |
Changes to auth/policy/policy_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 ( "fmt" "testing" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package policy import ( "fmt" "testing" "t73f.de/r/zsc/api" "zettelstore.de/z/auth" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func TestPolicies(t *testing.T) { t.Parallel() |
︙ | ︙ | |||
52 53 54 55 56 57 58 | ) name := fmt.Sprintf("readonly=%v/withauth=%v/expert=%v/simple=%v", ts.readonly, ts.withAuth, ts.expert, ts.simple) t.Run(name, func(tt *testing.T) { testCreate(tt, pol, ts.withAuth, ts.readonly) testRead(tt, pol, ts.withAuth, ts.expert) testWrite(tt, pol, ts.withAuth, ts.readonly, ts.expert) | < | 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | ) name := fmt.Sprintf("readonly=%v/withauth=%v/expert=%v/simple=%v", ts.readonly, ts.withAuth, ts.expert, ts.simple) t.Run(name, func(tt *testing.T) { testCreate(tt, pol, ts.withAuth, ts.readonly) testRead(tt, pol, ts.withAuth, ts.expert) testWrite(tt, pol, ts.withAuth, ts.readonly, ts.expert) testDelete(tt, pol, ts.withAuth, ts.readonly, ts.expert) testRefresh(tt, pol, ts.withAuth, ts.expert, ts.simple) }) } } type testAuthzManager struct { |
︙ | ︙ | |||
388 389 390 391 392 393 394 | {owner, roTrue, roTrue, false}, {owner2, roTrue, roTrue, false}, } for _, tc := range testCases { t.Run("Write", func(tt *testing.T) { got := pol.CanWrite(tc.user, tc.old, tc.new) if tc.exp != got { | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 390 391 392 393 394 395 396 397 398 399 400 401 402 403 | {owner, roTrue, roTrue, false}, {owner2, roTrue, roTrue, false}, } for _, tc := range testCases { t.Run("Write", func(tt *testing.T) { got := pol.CanWrite(tc.user, tc.old, tc.new) if tc.exp != got { tt.Errorf("exp=%v, but got=%v", tc.exp, got) } }) } } func testDelete(t *testing.T, pol auth.Policy, withAuth, readonly, expert bool) { |
︙ | ︙ |
Changes to auth/policy/readonly.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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/zettel/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 } | > > > < | 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package policy import "zettelstore.de/z/zettel/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) CanDelete(_, _ *meta.Meta) bool { return false } func (*roPolicy) CanRefresh(user *meta.Meta) bool { return user != nil } |
Changes to box/box.go.
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 box provides a generic interface to zettel boxes. package box import ( "context" "errors" "fmt" "io" "time" | > > > | < < < < < < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package box provides a generic interface to zettel boxes. package box import ( "context" "errors" "fmt" "io" "time" "t73f.de/r/zsc/api" "zettelstore.de/z/query" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // 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 // GetZettel retrieves a specific zettel. GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, 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 } |
︙ | ︙ | |||
131 132 133 134 135 136 137 | // Box is to be used outside the box package and its descendants. type Box interface { BaseBox WriteBox // FetchZids returns the set of all zettel identifer managed by the box. | | | 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | // Box is to be used outside the box package and its descendants. type Box interface { BaseBox WriteBox // FetchZids returns the set of all zettel identifer managed by the box. FetchZids(ctx context.Context) (*id.Set, error) // GetMeta returns the metadata of the zettel with the given identifier. GetMeta(context.Context, id.Zid) (*meta.Meta, error) // SelectMeta returns a list of metadata that comply to the given selection criteria. // If `metaSeq` is `nil`, the box assumes metadata of all available zettel. SelectMeta(ctx context.Context, metaSeq []*meta.Meta, q *query.Query) ([]*meta.Meta, error) |
︙ | ︙ | |||
205 206 207 208 209 210 211 | type UpdateReason uint8 // Values for Reason const ( _ UpdateReason = iota OnReady // Box is started and fully operational OnReload // Box was reloaded | | > | 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | type UpdateReason uint8 // Values for Reason const ( _ UpdateReason = iota OnReady // Box is started and fully operational OnReload // Box was reloaded OnZettel // Something with an existing zettel happened OnDelete // A zettel was deleted ) // UpdateInfo contains all the data about a changed zettel. type UpdateInfo struct { Box BaseBox Reason UpdateReason Zid id.Zid |
︙ | ︙ | |||
243 244 245 246 247 248 249 | return context.WithValue(ctx, ctxNoEnrichKey, &ctxNoEnrichKey) } type ctxNoEnrichType struct{} var ctxNoEnrichKey ctxNoEnrichType | | | | | 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | return context.WithValue(ctx, ctxNoEnrichKey, &ctxNoEnrichKey) } type ctxNoEnrichType struct{} var ctxNoEnrichKey ctxNoEnrichType // DoEnrich determines if the context is not marked to not enrich metadata. func DoEnrich(ctx context.Context) bool { _, ok := ctx.Value(ctxNoEnrichKey).(*ctxNoEnrichType) return !ok } // NoEnrichQuery provides a context that signals not to enrich, if the query does not need this. func NoEnrichQuery(ctx context.Context, q *query.Query) context.Context { if q.EnrichNeeded() { return ctx } |
︙ | ︙ |
Changes to box/compbox/compbox.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 provides zettel that have computed content. package compbox import ( "context" "net/url" | > > > | | > | | | | | > > > > > > | > > > > | > | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package compbox provides zettel that have computed content. package compbox import ( "context" "net/url" "t73f.de/r/zsc/api" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func init() { manager.Register( " comp", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { return getCompBox(cdata.Number, cdata.Enricher, cdata.Mapper), nil }) } type compBox struct { log *logger.Logger number int enricher box.Enricher mapper manager.Mapper } var myConfig *meta.Meta var myZettel = map[id.Zid]struct { meta func(id.Zid) *meta.Meta content func(context.Context, *compBox) []byte }{ id.MustParse(api.ZidVersion): {genVersionBuildM, genVersionBuildC}, id.MustParse(api.ZidHost): {genVersionHostM, genVersionHostC}, id.MustParse(api.ZidOperatingSystem): {genVersionOSM, genVersionOSC}, id.MustParse(api.ZidLog): {genLogM, genLogC}, id.MustParse(api.ZidMemory): {genMemoryM, genMemoryC}, id.MustParse(api.ZidSx): {genSxM, genSxC}, // id.MustParse(api.ZidHTTP): {genHttpM, genHttpC}, // id.MustParse(api.ZidAPI): {genApiM, genApiC}, // id.MustParse(api.ZidWebUI): {genWebUiM, genWebUiC}, // id.MustParse(api.ZidConsole): {genConsoleM, genConsoleC}, id.MustParse(api.ZidBoxManager): {genManagerM, genManagerC}, // id.MustParse(api.ZidIndex): {genIndexM, genIndexC}, // id.MustParse(api.ZidQuery): {genQueryM, genQueryC}, id.MustParse(api.ZidMetadataKey): {genKeysM, genKeysC}, id.MustParse(api.ZidParser): {genParserM, genParserC}, id.MustParse(api.ZidStartupConfiguration): {genConfigZettelM, genConfigZettelC}, id.MustParse(api.ZidWarnings): {genWarningsM, genWarningsC}, 9999999996: {genMappingM, genMappingC}, // TEMP for v0.19-dev } // Get returns the one program box. func getCompBox(boxNumber int, mf box.Enricher, mapper manager.Mapper) *compBox { return &compBox{ log: kernel.Main.GetLogger(kernel.BoxService).Clone(). Str("box", "comp").Int("boxnum", int64(boxNumber)).Child(), number: boxNumber, enricher: mf, mapper: mapper, } } // Setup remembers important values. func Setup(cfg *meta.Meta) { myConfig = cfg.Clone() } func (*compBox) Location() string { return "" } func (cb *compBox) GetZettel(ctx context.Context, zid id.Zid) (zettel.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("GetZettel/Content") return zettel.Zettel{ Meta: m, Content: zettel.NewContent(genContent(ctx, cb)), }, nil } cb.log.Trace().Msg("GetZettel/NoContent") return zettel.Zettel{Meta: m}, nil } } err := box.ErrZettelNotFound{Zid: zid} |
︙ | ︙ | |||
123 124 125 126 127 128 129 | handle(m) } } } return nil } | < < < < < < < < < < < < < < < > > > > > > > > > | 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 | handle(m) } } } return nil } func (*compBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (cb *compBox) DeleteZettel(_ context.Context, zid id.Zid) (err error) { if _, ok := myZettel[zid]; ok { err = box.ErrReadOnly } else { err = box.ErrZettelNotFound{Zid: zid} } cb.log.Trace().Err(err).Msg("DeleteZettel") return err } func (cb *compBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = true st.Zettel = len(myZettel) cb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } func getTitledMeta(zid id.Zid, title string) *meta.Meta { m := meta.New(zid) m.Set(api.KeyTitle, title) return m } func updateMeta(m *meta.Meta) { if _, ok := m.Get(api.KeySyntax); !ok { m.Set(api.KeySyntax, meta.SyntaxZmk) } m.Set(api.KeyRole, api.ValueRoleConfiguration) if _, ok := m.Get(api.KeyCreated); !ok { m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreStarted).(string)) } m.Set(api.KeyLang, api.ValueLangEN) m.Set(api.KeyReadOnly, api.ValueTrue) if _, ok := m.Get(api.KeyVisibility); !ok { m.Set(api.KeyVisibility, api.ValueVisibilityExpert) } } |
Changes to box/compbox/config.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 ( "bytes" | > > > > < < < | < < < | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package compbox import ( "bytes" "context" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func genConfigZettelM(zid id.Zid) *meta.Meta { if myConfig == nil { return nil } return getTitledMeta(zid, "Zettelstore Startup Configuration") } func genConfigZettelC(context.Context, *compBox) []byte { var buf bytes.Buffer for i, p := range myConfig.Pairs() { if i > 0 { buf.WriteByte('\n') } buf.WriteString("; ''") buf.WriteString(p.Key) |
︙ | ︙ |
Changes to box/compbox/keys.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 ( "bytes" "fmt" | > > > > | < | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package compbox import ( "bytes" "context" "fmt" "t73f.de/r/zsc/api" "zettelstore.de/z/kernel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func genKeysM(zid id.Zid) *meta.Meta { m := getTitledMeta(zid, "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(context.Context, *compBox) []byte { keys := meta.GetSortedKeyDescriptions() var buf bytes.Buffer buf.WriteString("|=Name<|=Type<|=Computed?:|=Property?:\n") for _, kd := range keys { fmt.Fprintf(&buf, "|[[%v|query:%v?]]|%v|%v|%v\n", kd.Name, kd.Name, kd.Type.Name, kd.IsComputed(), kd.IsProperty()) } |
︙ | ︙ |
Changes to box/compbox/log.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 compbox import ( "bytes" | > > > > | < | < | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package compbox import ( "bytes" "context" "t73f.de/r/zsc/api" "zettelstore.de/z/kernel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func genLogM(zid id.Zid) *meta.Meta { m := getTitledMeta(zid, "Zettelstore Log") m.Set(api.KeySyntax, meta.SyntaxText) m.Set(api.KeyModified, kernel.Main.GetLastLogTime().Local().Format(id.TimestampLayout)) return m } func genLogC(context.Context, *compBox) []byte { const tsFormat = "2006-01-02 15:04:05.999999" entries := kernel.Main.RetrieveLogEntries() var buf bytes.Buffer for _, entry := range entries { ts := entry.TS.Format(tsFormat) buf.WriteString(ts) for j := len(ts); j < len(tsFormat); j++ { |
︙ | ︙ |
Changes to box/compbox/manager.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 compbox import ( "bytes" "fmt" | > > > > < < | < < | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package compbox import ( "bytes" "context" "fmt" "zettelstore.de/z/kernel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func genManagerM(zid id.Zid) *meta.Meta { return getTitledMeta(zid, "Zettelstore Box Manager") } func genManagerC(context.Context, *compBox) []byte { kvl := kernel.Main.GetServiceStatistics(kernel.BoxService) if len(kvl) == 0 { return nil } var buf bytes.Buffer buf.WriteString("|=Name|=Value>\n") for _, kv := range kvl { |
︙ | ︙ |
Added box/compbox/mapping.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 | //----------------------------------------------------------------------------- // Copyright (c) 2024-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2024-present Detlef Stern //----------------------------------------------------------------------------- package compbox import ( "bytes" "context" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // Zettelstore Identifier Mapping. // // In the first stage of migration process, it is a computed zettel showing a // hypothetical mapping. In later stages, it will be stored as a normal zettel // that is updated when a new zettel is created or an old zettel is deleted. func genMappingM(zid id.Zid) *meta.Meta { m := getTitledMeta(zid, "Zettelstore Identifier Mapping View (TEMP for v0.19-dev)") m.Set(api.KeySyntax, meta.SyntaxText) m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genMappingC(ctx context.Context, cb *compBox) []byte { src, err := cb.mapper.FetchAsBytes(ctx) if err != nil { var buf bytes.Buffer buf.WriteString("**Error while fetching: ") buf.WriteString(err.Error()) buf.WriteString("**\n") return buf.Bytes() } return src } |
Added box/compbox/memory.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 | //----------------------------------------------------------------------------- // Copyright (c) 2024-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2024-present Detlef Stern //----------------------------------------------------------------------------- package compbox import ( "bytes" "context" "fmt" "os" "runtime" "zettelstore.de/z/kernel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func genMemoryM(zid id.Zid) *meta.Meta { return getTitledMeta(zid, "Zettelstore Memory") } func genMemoryC(context.Context, *compBox) []byte { pageSize := os.Getpagesize() var m runtime.MemStats runtime.GC() runtime.ReadMemStats(&m) var buf bytes.Buffer buf.WriteString("|=Name|=Value>\n") fmt.Fprintf(&buf, "|Page Size|%d\n", pageSize) fmt.Fprintf(&buf, "|Pages|%d\n", m.HeapSys/uint64(pageSize)) fmt.Fprintf(&buf, "|Heap Objects|%d\n", m.HeapObjects) fmt.Fprintf(&buf, "|Heap Sys (KiB)|%d\n", m.HeapSys/1024) fmt.Fprintf(&buf, "|Heap Inuse (KiB)|%d\n", m.HeapInuse/1024) debug := kernel.Main.GetConfig(kernel.CoreService, kernel.CoreDebug).(bool) if debug { for i, bysize := range m.BySize { fmt.Fprintf(&buf, "|Size %2d: %d|%d - %d → %d\n", i, bysize.Size, bysize.Mallocs, bysize.Frees, bysize.Mallocs-bysize.Frees) } } return buf.Bytes() } |
Changes to box/compbox/parser.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 compbox import ( "bytes" "fmt" | > > > > | | < | | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package compbox import ( "bytes" "context" "fmt" "slices" "strings" "t73f.de/r/zsc/api" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func genParserM(zid id.Zid) *meta.Meta { m := getTitledMeta(zid, "Zettelstore Supported Parser") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVTime).(string)) m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genParserC(context.Context, *compBox) []byte { var buf bytes.Buffer buf.WriteString("|=Syntax<|=Alt. Value(s):|=Text Parser?:|=Text Format?:|=Image Format?:\n") syntaxes := parser.GetSyntaxes() slices.Sort(syntaxes) for _, syntax := range syntaxes { info := parser.Get(syntax) if info.Name != syntax { continue } altNames := info.AltNames slices.Sort(altNames) fmt.Fprintf( &buf, "|%v|%v|%v|%v|%v\n", syntax, strings.Join(altNames, ", "), info.IsASTParser, info.IsTextFormat, info.IsImageFormat) } return buf.Bytes() } |
Added box/compbox/sx.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 | //----------------------------------------------------------------------------- // Copyright (c) 2024-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2024-present Detlef Stern //----------------------------------------------------------------------------- package compbox import ( "bytes" "context" "fmt" "t73f.de/r/sx" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func genSxM(zid id.Zid) *meta.Meta { return getTitledMeta(zid, "Zettelstore Sx Engine") } func genSxC(context.Context, *compBox) []byte { var buf bytes.Buffer buf.WriteString("|=Name|=Value>\n") fmt.Fprintf(&buf, "|Symbols|%d\n", sx.MakeSymbol("NIL").Factory().Size()) return buf.Bytes() } |
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 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package compbox import ( "context" "t73f.de/r/zsc/api" "zettelstore.de/z/kernel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func genVersionBuildM(zid id.Zid) *meta.Meta { m := getTitledMeta(zid, "Zettelstore Version") m.Set(api.KeyCreated, kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVTime).(string)) m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genVersionBuildC(context.Context, *compBox) []byte { return []byte(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) } func genVersionHostM(zid id.Zid) *meta.Meta { return getTitledMeta(zid, "Zettelstore Host") } func genVersionHostC(context.Context, *compBox) []byte { return []byte(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreHostname).(string)) } func genVersionOSM(zid id.Zid) *meta.Meta { return getTitledMeta(zid, "Zettelstore Operating System") } func genVersionOSC(context.Context, *compBox) []byte { goOS := kernel.Main.GetConfig(kernel.CoreService, kernel.CoreGoOS).(string) goArch := kernel.Main.GetConfig(kernel.CoreService, kernel.CoreGoArch).(string) result := make([]byte, 0, len(goOS)+len(goArch)+1) result = append(result, goOS...) result = append(result, '/') return append(result, goArch...) } |
Added box/compbox/warnings.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 | //----------------------------------------------------------------------------- // Copyright (c) 2024-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2024-present Detlef Stern //----------------------------------------------------------------------------- package compbox import ( "bytes" "context" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func genWarningsM(zid id.Zid) *meta.Meta { m := getTitledMeta(zid, "Zettelstore Warnings") m.Set(api.KeyVisibility, api.ValueVisibilityLogin) return m } func genWarningsC(ctx context.Context, cb *compBox) []byte { var buf bytes.Buffer buf.WriteString("* [[Zettel without stored creation date|query:created-missing:true]]\n") buf.WriteString("* [[Zettel with strange creation date|query:created<19700101000000]]\n") ws, err := cb.mapper.Warnings(ctx) if err != nil { buf.WriteString("**Error while fetching: ") buf.WriteString(err.Error()) buf.WriteString("**\n") return buf.Bytes() } first := true ws.ForEach(func(zid id.Zid) { if first { first = false buf.WriteString("=== Mapper Warnings\n") } buf.WriteString("* [[") buf.WriteString(zid.String()) buf.WriteString("]]\n") }) return buf.Bytes() } |
Changes to box/constbox/base.css.
1 2 3 4 | *,*::before,*::after { box-sizing: border-box; } html { | > > > > > > > > > > > > > > < | 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 | /*----------------------------------------------------------------------------- * Copyright (c) 2020-present Detlef Stern * * This file is part of Zettelstore. * * Zettelstore is licensed under the latest version of the EUPL (European Union * Public License). Please see file LICENSE.txt for your rights and obligations * under this license. * * SPDX-License-Identifier: EUPL-1.2 * SPDX-FileCopyrightText: 2020-present Detlef Stern *----------------------------------------------------------------------------- */ *,*::before,*::after { box-sizing: border-box; } html { font-family: serif; scroll-behavior: smooth; height: 100%; } body { margin: 0; min-height: 100vh; |
︙ | ︙ | |||
71 72 73 74 75 76 77 | .zs-dropdown:hover > .zs-dropdown-content { display: block } main { padding: 0 1rem } article > * + * { margin-top: .5rem } article header { padding: 0; margin: 0; } | | | | | | | | | | | | | | | | < | < > | | < < < | < | 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 | .zs-dropdown:hover > .zs-dropdown-content { display: block } main { padding: 0 1rem } article > * + * { margin-top: .5rem } article header { padding: 0; margin: 0; } h1,h2,h3,h4,h5,h6 { font-family:sans-serif; font-weight:normal; margin:.4em 0 } h1 { font-size:1.5em } h2 { font-size:1.25em } h3 { font-size:1.15em } h4 { font-size:1.05em; font-weight: bold } h5 { font-size:1.05em } h6 { font-size:1.05em; font-weight: lighter } p { margin: .5em 0 0 0 } p.zs-meta-zettel { margin-top: .5em; margin-left: .5em } li,figure,figcaption,dl { margin: 0 } dt { margin: .5em 0 0 0 } dt+dd { margin-top: 0 } dd { margin: .5em 0 0 2em } dd > p:first-child { margin: 0 0 0 0 } blockquote { border-left: .5em solid lightgray; padding-left: 1em; margin-left: 1em; margin-right: 2em; } blockquote p { margin-bottom: .5em } table { border-collapse: collapse; border-spacing: 0; max-width: 100%; } td, th {text-align: left; padding: .25em .5em;} th { font-weight: bold } thead th { border-bottom: 2px solid hsl(0, 0%, 70%) } td { border-bottom: 1px solid hsl(0, 0%, 85%) } main form { padding: 0 .5em; margin: .5em 0 0 0; } main form:after { content: "."; display: block; |
︙ | ︙ | |||
141 142 143 144 145 146 147 | input.zs-secondary { float:left } input.zs-upload { padding-left: 1em; padding-right: 1em; } a:not([class]) { text-decoration-skip-ink: auto } a.broken { text-decoration: line-through } | | | | | | | | | | | | | | | | | | | | | | | 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 | input.zs-secondary { float:left } input.zs-upload { padding-left: 1em; padding-right: 1em; } a:not([class]) { text-decoration-skip-ink: auto } a.broken { text-decoration: line-through } a[rel~="external"]::after { content: "âžš"; display: inline-block } img { max-width: 100% } img.right { float: right } ol.zs-endnotes { padding-top: .5em; border-top: 1px solid; } kbd { font-family:monospace } code,pre { font-family: monospace; font-size: 85%; } code { padding: .1em .2em; background: #f0f0f0; border: 1px solid #ccc; border-radius: .25em; } pre { padding: .5em .7em; max-width: 100%; overflow: auto; border: 1px solid #ccc; border-radius: .5em; background: #f0f0f0; } pre code { font-size: 95%; position: relative; padding: 0; border: none; } div.zs-indication { padding: .5em .7em; max-width: 100%; border-radius: .5em; border: 1px solid black; } div.zs-indication p:first-child { margin-top: 0 } span.zs-indication { border: 1px solid black; border-radius: .25em; padding: .1rem .2em; font-size: 95%; } .zs-info { background-color: lightblue; padding: .5em 1em; } .zs-warning { background-color: lightyellow; padding: .5em 1em; } .zs-error { background-color: lightpink; border-style: none !important; font-weight: bold; } td.left, th.left { text-align:left } td.center, th.center { text-align:center } td.right, th.right { text-align:right } .zs-font-size-0 { font-size:75% } .zs-font-size-1 { font-size:83% } .zs-font-size-2 { font-size:100% } .zs-font-size-3 { font-size:117% } .zs-font-size-4 { font-size:150% } .zs-font-size-5 { font-size:200% } .zs-deprecated { border-style: dashed; padding: .2em } .zs-meta { font-size:.75rem; color:#444; margin-bottom:1em; } .zs-meta a { color:#444 } h1+.zs-meta { margin-top:-1em } nav > details { margin-top:1em } details > summary { width: 100%; background-color: #eee; font-family:sans-serif; } details > ul { margin-top:0; padding-left:2em; background-color: #eee; } footer { padding: 0 1em } @media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } |
Changes to box/constbox/base.sxn.
1 2 3 4 5 6 7 8 9 10 | `(@@@@ (html ,@(if 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"))) ,@META-HEADER (link (@ (rel "stylesheet") (href ,css-base-url))) (link (@ (rel "stylesheet") (href ,css-user-url))) | > > > > > > > > > > > > > | | 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 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(@@@@ (html ,@(if 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"))) ,@META-HEADER (link (@ (rel "stylesheet") (href ,css-base-url))) (link (@ (rel "stylesheet") (href ,css-user-url))) ,@(ROLE-DEFAULT-meta (current-binding)) (title ,title)) (body (nav (@ (class "zs-menu")) (a (@ (href ,home-url)) "Home") ,@(if with-auth `((div (@ (class "zs-dropdown")) (button "User") |
︙ | ︙ | |||
35 36 37 38 39 40 41 | ,@(if new-zettel-links `((div (@ (class "zs-dropdown")) (button "New") (nav (@ (class "zs-dropdown-content")) ,@(map wui-link new-zettel-links) ))) ) | | > > | | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | ,@(if new-zettel-links `((div (@ (class "zs-dropdown")) (button "New") (nav (@ (class "zs-dropdown-content")) ,@(map wui-link new-zettel-links) ))) ) (search (form (@ (action ,search-url)) (input (@ (type "search") (inputmode "search") (name ,query-key-query) (title "General search field, with same behaviour as search field in search result list") (placeholder "Search..") (dir "auto"))))) ) (main (@ (class "content")) ,DETAIL) ,@(if FOOTER `((footer (hr) ,@FOOTER))) ,@(if debug-mode '((div (b "WARNING: Debug mode is enabled. DO NOT USE IN PRODUCTION!")))) ))) |
Changes to box/constbox/constbox.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 constbox puts zettel inside the executable. package constbox import ( "context" _ "embed" // Allow to embed file content "net/url" | > > > | | 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package constbox puts zettel inside the executable. package constbox import ( "context" _ "embed" // Allow to embed file content "net/url" "t73f.de/r/zsc/api" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/query" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" |
︙ | ︙ | |||
90 91 92 93 94 95 96 | cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } return nil } | < < < < < < < < < < < < < < < | 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | cb.enricher.Enrich(ctx, m, cb.number) handle(m) } } return nil } func (*constBox) CanDeleteZettel(context.Context, id.Zid) bool { return false } func (cb *constBox) DeleteZettel(_ context.Context, zid id.Zid) (err error) { if _, ok := cb.zettel[zid]; ok { err = box.ErrReadOnly } else { err = box.ErrZettelNotFound{Zid: zid} |
︙ | ︙ | |||
165 166 167 168 169 170 171 | api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityPublic, api.KeyCreated: "20210504135842", | | | | | | | < < < < < < < < < < | | | > | | | | 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 | api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityPublic, api.KeyCreated: "20210504135842", api.KeyModified: "20240418095500", }, zettel.NewContent(contentDependencies)}, id.BaseTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Base HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230510155100", api.KeyModified: "20240219145300", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentBaseSxn)}, id.LoginTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Login Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20200804111624", api.KeyModified: "20240219145200", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentLoginSxn)}, id.ZettelTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230510155300", api.KeyModified: "20240826110000", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentZettelSxn)}, id.InfoTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Info HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20200804111624", api.KeyModified: "20240826110800", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentInfoSxn)}, id.FormTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Form HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20200804111624", api.KeyModified: "20240219145200", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentFormSxn)}, id.DeleteTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Delete HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20200804111624", api.KeyModified: "20240826110800", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentDeleteSxn)}, id.ListTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore List Zettel HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230704122100", api.KeyModified: "20240219145200", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentListZettelSxn)}, id.ErrorTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Error HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20210305133215", api.KeyModified: "20240219145200", api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentErrorSxn)}, id.StartSxnZid: { constHeader{ api.KeyTitle: "Zettelstore Sxn Start Code", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230824160700", api.KeyModified: "20240219145200", api.KeyVisibility: api.ValueVisibilityExpert, api.KeyPrecursor: string(api.ZidSxnBase), }, zettel.NewContent(contentStartCodeSxn)}, id.BaseSxnZid: { constHeader{ api.KeyTitle: "Zettelstore Sxn Base Code", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20230619132800", api.KeyModified: "20240826142000", api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, api.KeyPrecursor: string(api.ZidSxnPrelude), }, zettel.NewContent(contentBaseCodeSxn)}, id.PreludeSxnZid: { constHeader{ api.KeyTitle: "Zettelstore Sxn Prelude", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxSxn, api.KeyCreated: "20231006181700", api.KeyModified: "20240222121200", api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityExpert, }, zettel.NewContent(contentPreludeSxn)}, id.MustParse(api.ZidBaseCSS): { constHeader{ api.KeyTitle: "Zettelstore Base CSS", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxCSS, api.KeyCreated: "20200804111624", api.KeyModified: "20240827143500", api.KeyVisibility: api.ValueVisibilityPublic, }, zettel.NewContent(contentBaseCSS)}, id.MustParse(api.ZidUserCSS): { constHeader{ api.KeyTitle: "Zettelstore User CSS", api.KeyRole: api.ValueRoleConfiguration, |
︙ | ︙ | |||
416 417 418 419 420 421 422 423 424 425 426 427 428 429 | api.KeyRole: api.ValueRoleRole, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20231129162000", api.KeyLang: api.ValueLangEN, api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(contentRoleTag)}, id.DefaultHomeZid: { constHeader{ api.KeyTitle: "Home", api.KeyRole: api.ValueRoleZettel, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20210210190757", | > > > > > > > > > > > > > > > > > > > > > | 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 | api.KeyRole: api.ValueRoleRole, api.KeySyntax: meta.SyntaxZmk, api.KeyCreated: "20231129162000", api.KeyLang: api.ValueLangEN, api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(contentRoleTag)}, id.MustParse(api.ZidAppDirectory): { constHeader{ api.KeyTitle: "Zettelstore Application Directory", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxNone, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20240703235900", api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(nil)}, id.MustParse(api.ZidMapping): { constHeader{ api.KeyTitle: "Zettelstore Identifier Mapping", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: meta.SyntaxText, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20240807114600", api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, }, zettel.NewContent(nil)}, id.DefaultHomeZid: { constHeader{ api.KeyTitle: "Home", api.KeyRole: api.ValueRoleZettel, api.KeySyntax: meta.SyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyCreated: "20210210190757", |
︙ | ︙ | |||
451 452 453 454 455 456 457 | //go:embed info.sxn var contentInfoSxn []byte //go:embed form.sxn var contentFormSxn []byte | < < < | 451 452 453 454 455 456 457 458 459 460 461 462 463 464 | //go:embed info.sxn var contentInfoSxn []byte //go:embed form.sxn var contentFormSxn []byte //go:embed delete.sxn var contentDeleteSxn []byte //go:embed listzettel.sxn var contentListZettelSxn []byte //go:embed error.sxn |
︙ | ︙ |
Changes to box/constbox/delete.sxn.
1 | `(article | > > > > > > > > > > > > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Delete Zettel " ,zid " / " ,zid-n)) (p "Do you really want to delete this zettel?") ,@(if shadowed-box `((div (@ (class "zs-info")) (h2 "Information") (p "If you delete this zettel, the previously shadowed zettel from overlayed box " ,shadowed-box " becomes available.") )) ) |
︙ | ︙ |
Changes to box/constbox/dependencies.zettel.
︙ | ︙ | |||
126 127 128 129 130 131 132 | 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. ``` | | | | | > > | > > | | 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | 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. ``` === Sx, SxWebs, Webs, Zettelstore-Client These are companion projects, written by the main developer of Zettelstore. They are published under the same license, [[EUPL v1.2, or later|00000000000004]]. ; URL & Source Sx : [[https://t73f.de/r/sx]] ; URL & Source SxWebs : [[https://t73f.de/r/sxwebs]] ; URL & Source Webs : [[https://t73f.de/r/webs]] ; URL & Source Zettelstore-Client : [[https://t73f.de/r/zsc]] ; License: : European Union Public License, version 1.2 (EUPL v1.2), or later. |
Changes to box/constbox/error.sxn.
1 2 3 4 | `(article (header (h1 ,heading)) ,message ) | > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading)) ,message ) |
Changes to box/constbox/form.sxn.
1 2 3 4 5 | `(article (header (h1 ,heading)) (form (@ (action ,form-action-url) (method "POST") (enctype "multipart/form-data")) (div (label (@ (for "zs-title")) "Title " (a (@ (title "Main heading of this zettel.")) (@H "ⓘ"))) | > > > > > > > > > > > > > | > > > > | | > > | > > > > | | > > | 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 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading)) (form (@ (action ,form-action-url) (method "POST") (enctype "multipart/form-data")) (div (label (@ (for "zs-title")) "Title " (a (@ (title "Main heading of this zettel.")) (@H "ⓘ"))) (input (@ (class "zs-input") (type "text") (id "zs-title") (name "title") (title "Title of this zettel") (placeholder "Title..") (value ,meta-title) (dir "auto") (autofocus)))) (div (label (@ (for "zs-role")) "Role " (a (@ (title "One word, without spaces, to set the main role of this zettel.")) (@H "ⓘ"))) (input (@ (class "zs-input") (type "text") (pattern "\\w*") (id "zs-role") (name "role") (title "One word, letters and digits, but no spaces, to set the main role of the zettel.") (placeholder "role..") (value ,meta-role) (dir "auto") ,@(if role-data '((list "zs-role-data"))) )) ,@(wui-datalist "zs-role-data" role-data) ) (div (label (@ (for "zs-tags")) "Tags " (a (@ (title "Tags must begin with an '#' sign. They are separated by spaces.")) (@H "ⓘ"))) (input (@ (class "zs-input") (type "text") (id "zs-tags") (name "tags") (title "Tags/keywords to categorize the zettel. Each tags is a word that begins with a '#' character; they are separated by spaces") (placeholder "#tag") (value ,meta-tags) (dir "auto")))) (div (label (@ (for "zs-meta")) "Metadata " (a (@ (title "Other metadata for this zettel. Each line contains a key/value pair, separated by a colon ':'.")) (@H "ⓘ"))) (textarea (@ (class "zs-input") (id "zs-meta") (name "meta") (rows "4") (title "Additional metadata about the zettel") (placeholder "metakey: metavalue") (dir "auto")) ,meta)) (div (label (@ (for "zs-syntax")) "Syntax " (a (@ (title "Syntax of zettel content below, one word. Typically 'zmk' (for zettelmarkup).")) (@H "ⓘ"))) (input (@ (class "zs-input") (type "text") (pattern "\\w*") (id "zs-syntax") (name "syntax") (title "Syntax/format of zettel content below, one word, letters and digits, no spaces.") (placeholder "syntax..") (value ,meta-syntax) (dir "auto") ,@(if syntax-data '((list "zs-syntax-data"))) )) ,@(wui-datalist "zs-syntax-data" syntax-data) ) ,@(if (bound? 'content) `((div (label (@ (for "zs-content")) "Content " (a (@ (title "Content for this zettel, according to above syntax.")) (@H "ⓘ"))) (textarea (@ (class "zs-input zs-content") (id "zs-content") (name "content") (rows "20") (title "Zettel content, according to the given syntax") (placeholder "Zettel content..") (dir "auto")) ,content) )) ) (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"))) )) |
︙ | ︙ |
Changes to box/constbox/info.sxn.
1 | `(article | > > > > > > > > > > > > > | > | < | | | 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 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Information for Zettel " ,zid " / " ,zid-n) (p (a (@ (href ,web-url)) "Web") (@H " · ") (a (@ (href ,context-url)) "Context") (@H " / ") (a (@ (href ,context-full-url)) "Full") ,@(if (bound? 'edit-url) `((@H " · ") (a (@ (href ,edit-url)) "Edit"))) ,@(ROLE-DEFAULT-actions (current-binding)) ,@(if (bound? 'reindex-url) `((@H " · ") (a (@ (href ,reindex-url)) "Reindex"))) ,@(if (bound? 'delete-url) `((@H " · ") (a (@ (href ,delete-url)) "Delete"))) ) ) (h2 "Interpreted Metadata") (table ,@(map wui-info-meta-table-row metadata)) (h2 "References") ,@(if local-links `((h3 "Local") (ul ,@(map wui-local-link local-links)))) ,@(if query-links `((h3 "Queries") (ul ,@(map wui-item-link query-links)))) ,@(if ext-links `((h3 "External") (ul ,@(map wui-item-popup-link ext-links)))) (h3 "Unlinked") ,@unlinked-content (form (label (@ (for "phrase")) "Search Phrase") (input (@ (class "zs-input") (type "text") (id "phrase") (name ,query-key-phrase) (placeholder "Phrase..") (value ,phrase))) |
︙ | ︙ |
Changes to box/constbox/listzettel.sxn.
1 2 | `(article (header (h1 ,heading)) | > > > > > > > > > > > > > | > > | > | > | | | > > | 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 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading)) (search (form (@ (action ,search-url)) (input (@ (class "zs-input") (type "search") (inputmode "search") (name ,query-key-query) (title "Contains the search that leads to the list below. You're allowed to modify it") (placeholder "Search..") (value ,query-value) (dir "auto"))))) ,@(if (bound? 'tag-zettel) `((p (@ (class "zs-meta-zettel")) "Tag zettel: " ,@tag-zettel)) ) ,@(if (bound? 'create-tag-zettel) `((p (@ (class "zs-meta-zettel")) "Create tag zettel: " ,@create-tag-zettel)) ) ,@(if (bound? 'role-zettel) `((p (@ (class "zs-meta-zettel")) "Role zettel: " ,@role-zettel)) ) ,@(if (bound? 'create-role-zettel) `((p (@ (class "zs-meta-zettel")) "Create role zettel: " ,@create-role-zettel)) ) ,@content ,@endnotes (form (@ (action ,(if (bound? 'create-url) create-url))) ,(if (bound? 'data-url) `(@L "Other encodings" ,(if (> num-entries 3) `(@L " of these " ,num-entries " entries: ") ": ") (a (@ (href ,data-url)) "data") ", " (a (@ (href ,plain-url)) "plain") ) ) ,@(if (bound? 'create-url) `((input (@ (type "hidden") (name ,query-key-query) (value ,query-value))) (input (@ (type "hidden") (name ,query-key-seed) (value ,seed))) (input (@ (class "zs-primary") (type "submit") (value "Save As Zettel"))) ) ) ) ) |
Changes to box/constbox/login.sxn.
1 2 3 4 5 6 7 | `(article (header (h1 "Login")) ,@(if retry '((div (@ (class "zs-indication zs-error")) "Wrong user name / password. Try again."))) (form (@ (method "POST") (action "")) (div (label (@ (for "username")) "User name:") (input (@ (class "zs-input") (type "text") (id "username") (name "username") (placeholder "Your user name..") (autofocus)))) | > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Login")) ,@(if retry '((div (@ (class "zs-indication zs-error")) "Wrong user name / password. Try again."))) (form (@ (method "POST") (action "")) (div (label (@ (for "username")) "User name:") (input (@ (class "zs-input") (type "text") (id "username") (name "username") (placeholder "Your user name..") (autofocus)))) |
︙ | ︙ |
Changes to box/constbox/prelude.sxn.
1 2 3 4 5 6 7 8 9 10 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-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. ;;;---------------------------------------------------------------------------- | > > > | | < < < | < | < < < < < < | < < < < | < | | | | > > > > > > > > > > > > > | 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 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- ;;; This zettel contains sxn definitions that are independent of specific ;;; subsystems, such as WebUI, API, or other. It just contains generic code to ;;; be used in all places. It asumes that the symbols NIL and T are defined. ;; not macro (defmacro not (x) `(if ,x NIL T)) ;; not= macro, to negate an equivalence (defmacro not= args `(not (= ,@args))) ;; let* macro ;; ;; (let* (BINDING ...) EXPR ...), where SYMBOL may occur in later bindings. (defmacro let* (bindings . body) (if (null? bindings) `(begin ,@body) `(let ((,(caar bindings) ,(cadar bindings))) (let* ,(cdr bindings) ,@body)))) ;; cond macro ;; ;; (cond ((COND EXPR) ...)) (defmacro cond clauses (if (null? clauses) () (let* ((clause (car clauses)) (the-cond (car clause))) (if (= the-cond T) `(begin ,@(cdr clause)) `(if ,the-cond (begin ,@(cdr clause)) (cond ,@(cdr clauses))))))) ;; and macro ;; ;; (and EXPR ...) (defmacro and args (cond ((null? args) T) ((null? (cdr args)) (car args)) |
︙ | ︙ |
Deleted box/constbox/rename.sxn.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/constbox/start.sxn.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-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. ;;;---------------------------------------------------------------------------- ;;; This zettel is the start of the loading sequence for Sx code used in the ;;; Zettelstore. Via the precursor metadata, dependend zettel are evaluated ;;; before this zettel. You must always depend, directly or indirectly on the ;;; "Zettelstore Sxn Base Code" zettel. It provides the base definitions. | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- ;;; This zettel is the start of the loading sequence for Sx code used in the ;;; Zettelstore. Via the precursor metadata, dependend zettel are evaluated ;;; before this zettel. You must always depend, directly or indirectly on the ;;; "Zettelstore Sxn Base Code" zettel. It provides the base definitions. |
Changes to box/constbox/wuicode.sxn.
1 2 3 4 5 6 7 8 9 10 11 12 13 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-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. ;;;---------------------------------------------------------------------------- ;; Contains WebUI specific code, but not related to a specific template. ;; wui-list-item returns the argument as a HTML list item. | > > > | | | | | | < < < | < < | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > > > > > > | | | | | > > > > > | | 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 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- ;; Contains WebUI specific code, but not related to a specific template. ;; wui-list-item returns the argument as a HTML list item. (defun wui-item (s) `(li ,s)) ;; wui-info-meta-table-row takes a pair and translates it into a HTML table row ;; with two columns. (defun wui-info-meta-table-row (p) `(tr (td (@ (class zs-info-meta-key)) ,(car p)) (td (@ (class zs-info-meta-value)) ,(cdr p)))) ;; wui-local-link translates a local link into HTML. (defun wui-local-link (l) `(li (a (@ (href ,l )) ,l))) ;; wui-link takes a link (title . url) and returns a HTML reference. (defun wui-link (q) `(a (@ (href ,(cdr q))) ,(car q))) ;; wui-item-link taks a pair (text . url) and returns a HTML link inside ;; a list item. (defun wui-item-link (q) `(li ,(wui-link q))) ;; wui-tdata-link taks a pair (text . url) and returns a HTML link inside ;; a table data item. (defun wui-tdata-link (q) `(td ,(wui-link q))) ;; wui-item-popup-link is like 'wui-item-link, but the HTML link will open ;; a new tab / window. (defun wui-item-popup-link (e) `(li (a (@ (href ,e) (target "_blank") (rel "external noreferrer")) ,e))) ;; wui-option-value returns a value for an HTML option element. (defun wui-option-value (v) `(option (@ (value ,v)))) ;; wui-datalist returns a HTML datalist with the given HTML identifier and a ;; list of values. (defun wui-datalist (id lst) (if lst `((datalist (@ (id ,id)) ,@(map wui-option-value lst))))) ;; wui-pair-desc-item takes a pair '(term . text) and returns a list with ;; a HTML description term and a HTML description data. (defun wui-pair-desc-item (p) `((dt ,(car p)) (dd ,(cdr p)))) ;; wui-meta-desc returns a HTML description list made from the list of pairs ;; given. (defun wui-meta-desc (l) `(dl ,@(apply append (map wui-pair-desc-item l)))) ;; wui-enc-matrix returns the HTML table of all encodings and parts. (defun wui-enc-matrix (matrix) `(table ,@(map (lambda (row) `(tr (th ,(car row)) ,@(map wui-tdata-link (cdr row)))) matrix))) ;; CSS-ROLE-map is a mapping (pair list, assoc list) of role names to zettel ;; identifier. It is used in the base template to update the metadata of the ;; HTML page to include some role specific CSS code. ;; Referenced in function "ROLE-DEFAULT-meta". (defvar CSS-ROLE-map '()) ;; ROLE-DEFAULT-meta returns some metadata for the base template. Any role ;; specific code should include the returned list of this function. (defun ROLE-DEFAULT-meta (binding) `(,@(let* ((meta-role (binding-lookup 'meta-role binding)) (entry (assoc CSS-ROLE-map meta-role))) (if (pair? entry) `((link (@ (rel "stylesheet") (href ,(zid-content-path (cdr entry)))))) ) ) ) ) ;; ACTION-SEPARATOR defines a HTML value that separates actions links. (defvar ACTION-SEPARATOR '(@H " · ")) ;; ROLE-DEFAULT-actions returns the default text for actions. (defun ROLE-DEFAULT-actions (binding) `(,@(let ((copy-url (binding-lookup 'copy-url binding))) (if (defined? copy-url) `((@H " · ") (a (@ (href ,copy-url)) "Copy")))) ,@(let ((version-url (binding-lookup 'version-url binding))) (if (defined? version-url) `((@H " · ") (a (@ (href ,version-url)) "Version")))) ,@(let ((child-url (binding-lookup 'child-url binding))) (if (defined? child-url) `((@H " · ") (a (@ (href ,child-url)) "Child")))) ,@(let ((folge-url (binding-lookup 'folge-url binding))) (if (defined? folge-url) `((@H " · ") (a (@ (href ,folge-url)) "Folge")))) ) ) ;; ROLE-tag-actions returns an additional action "Zettel" for zettel with role "tag". (defun ROLE-tag-actions (binding) `(,@(ROLE-DEFAULT-actions binding) ,@(let ((title (binding-lookup 'title binding))) (if (and (defined? title) title) `(,ACTION-SEPARATOR (a (@ (href ,(query->url (concat "tags:" title)))) "Zettel")) ) ) ) ) ;; ROLE-role-actions returns an additional action "Zettel" for zettel with role "role". (defun ROLE-role-actions (binding) `(,@(ROLE-DEFAULT-actions binding) ,@(let ((title (binding-lookup 'title binding))) (if (and (defined? title) title) `(,ACTION-SEPARATOR (a (@ (href ,(query->url (concat "role:" title)))) "Zettel")) ) ) ) ) ;; ROLE-DEFAULT-heading returns the default text for headings, below the ;; references of a zettel. In most cases it should be called from an ;; overwriting function. (defun ROLE-DEFAULT-heading (binding) `(,@(let ((meta-url (binding-lookup 'meta-url binding))) (if (defined? meta-url) `((br) "URL: " ,(url-to-html meta-url)))) ,@(let ((urls (binding-lookup 'urls binding))) (if (defined? urls) (map (lambda (u) `(@L (br) ,(car u) ": " ,(url-to-html (cdr u)))) urls) ) ) ,@(let ((meta-author (binding-lookup 'meta-author binding))) (if (and (defined? meta-author) meta-author) `((br) "By " ,meta-author))) ) ) |
Changes to box/constbox/zettel.sxn.
1 2 3 4 5 | `(article (header (h1 ,heading) (div (@ (class "zs-meta")) ,@(if (bound? 'edit-url) `((a (@ (href ,edit-url)) "Edit") (@H " · "))) | > > > > > > > > > > > > > | | | | 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 | ;;;---------------------------------------------------------------------------- ;;; Copyright (c) 2023-present Detlef Stern ;;; ;;; This file is part of Zettelstore. ;;; ;;; Zettelstore is licensed under the latest version of the EUPL (European ;;; Union Public License). Please see file LICENSE.txt for your rights and ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading) (div (@ (class "zs-meta")) ,@(if (bound? 'edit-url) `((a (@ (href ,edit-url)) "Edit") (@H " · "))) ,zid " / " ,zid-n (@H " · ") (a (@ (href ,info-url)) "Info") (@H " · ") "(" ,@(if (bound? 'role-url) `((a (@ (href ,role-url)) ,meta-role))) ,@(if (and (bound? 'folge-role-url) (bound? 'meta-folge-role)) `((@H " → ") (a (@ (href ,folge-role-url)) ,meta-folge-role))) ")" ,@(if tag-refs `((@H " · ") ,@tag-refs)) ,@(ROLE-DEFAULT-actions (current-binding)) ,@(if predecessor-refs `((br) "Predecessor: " ,predecessor-refs)) ,@(if precursor-refs `((br) "Precursor: " ,precursor-refs)) ,@(if superior-refs `((br) "Superior: " ,superior-refs)) ,@(ROLE-DEFAULT-heading (current-binding)) ) ) ,@content ,endnotes ,@(if (or folge-links subordinate-links back-links successor-links) `((nav ,@(if folge-links `((details (@ (,folge-open)) (summary "Folgezettel") (ul ,@(map wui-item-link folge-links))))) |
︙ | ︙ |
Changes to box/dirbox/dirbox.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 dirbox provides a directory-based zettel box. package dirbox import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package dirbox provides a directory-based zettel box. package dirbox import ( "context" |
︙ | ︙ | |||
85 86 87 88 89 90 91 | _ notifyTypeSpec = iota dirNotifyAny dirNotifySimple dirNotifyFS ) func getDirSrvInfo(log *logger.Logger, notifyType string) notifyTypeSpec { | | | 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | _ notifyTypeSpec = iota dirNotifyAny dirNotifySimple dirNotifyFS ) func getDirSrvInfo(log *logger.Logger, notifyType string) notifyTypeSpec { for range 2 { switch notifyType { case kernel.BoxDirTypeNotify: return dirNotifyFS case kernel.BoxDirTypeSimple: return dirNotifySimple default: notifyType = kernel.Main.GetConfig(kernel.BoxService, kernel.BoxDefaultDirType).(string) |
︙ | ︙ | |||
147 148 149 150 151 152 153 | return box.StartStateStopped } func (dp *dirBox) Start(context.Context) error { dp.mxCmds.Lock() defer dp.mxCmds.Unlock() dp.fCmds = make([]chan fileCmd, 0, dp.fSrvs) | | | | 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 | return box.StartStateStopped } func (dp *dirBox) Start(context.Context) error { dp.mxCmds.Lock() defer dp.mxCmds.Unlock() dp.fCmds = make([]chan fileCmd, 0, dp.fSrvs) for i := range dp.fSrvs { cc := make(chan fileCmd) go fileService(i, dp.log.Clone().Str("sub", "file").Uint("fn", uint64(i)).Child(), dp.dir, cc) dp.fCmds = append(dp.fCmds, cc) } var notifier notify.Notifier var err error switch dp.notifySpec { case dirNotifySimple: notifier, err = notify.NewSimpleDirNotifier(dp.log.Clone().Str("notify", "simple").Child(), dp.dir) default: notifier, err = notify.NewFSDirNotifier(dp.log.Clone().Str("notify", "fs").Child(), dp.dir) } if err != nil { dp.log.Error().Err(err).Msg("Unable to create directory supervisor") dp.stopFileServices() return err } dp.dirSrv = notify.NewDirService( dp, dp.log.Clone().Str("sub", "dirsrv").Child(), notifier, |
︙ | ︙ | |||
196 197 198 199 200 201 202 | func (dp *dirBox) stopFileServices() { for _, c := range dp.fCmds { close(c) } } | | | | | 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | func (dp *dirBox) stopFileServices() { for _, c := range dp.fCmds { close(c) } } func (dp *dirBox) notifyChanged(zid id.Zid, reason box.UpdateReason) { if chci := dp.cdata.Notify; chci != nil { dp.log.Trace().Zid(zid).Uint("reason", uint64(reason)).Msg("notifyChanged") chci <- box.UpdateInfo{Box: dp, Reason: reason, Zid: zid} } } func (dp *dirBox) getFileChan(zid id.Zid) chan fileCmd { // Based on https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function sum := 2166136261 ^ uint32(zid) sum *= 16777619 |
︙ | ︙ | |||
237 238 239 240 241 242 243 | entry := notify.DirEntry{Zid: newZid} dp.updateEntryFromMetaContent(&entry, meta, zettel.Content) err = dp.srvSetZettel(ctx, &entry, zettel) if err == nil { err = dp.dirSrv.UpdateDirEntry(&entry) } | | | 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 | entry := notify.DirEntry{Zid: newZid} dp.updateEntryFromMetaContent(&entry, meta, zettel.Content) err = dp.srvSetZettel(ctx, &entry, zettel) if err == nil { err = dp.dirSrv.UpdateDirEntry(&entry) } dp.notifyChanged(meta.Zid, box.OnZettel) dp.log.Trace().Err(err).Zid(meta.Zid).Msg("CreateZettel") return meta.Zid, err } func (dp *dirBox) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) { entry := dp.dirSrv.GetDirEntry(zid) if !entry.IsValid() { |
︙ | ︙ | |||
309 310 311 312 313 314 315 | // 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 { | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 | // 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, box.OnZettel) } dp.log.Trace().Zid(zid).Err(err).Msg("UpdateZettel") return err } func (dp *dirBox) updateEntryFromMetaContent(entry *notify.DirEntry, m *meta.Meta, content zettel.Content) { entry.SetupFromMetaContent(m, content, dp.cdata.Config.GetZettelFileSyntax) } func (dp *dirBox) CanDeleteZettel(_ context.Context, zid id.Zid) bool { if dp.readonly { return false } entry := dp.dirSrv.GetDirEntry(zid) return entry.IsValid() } |
︙ | ︙ | |||
388 389 390 391 392 393 394 | } err := dp.dirSrv.DeleteDirEntry(zid) if err != nil { return nil } err = dp.srvDeleteZettel(ctx, entry, zid) if err == nil { | | | 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | } err := dp.dirSrv.DeleteDirEntry(zid) if err != nil { return nil } err = dp.srvDeleteZettel(ctx, entry, zid) if err == nil { dp.notifyChanged(zid, box.OnDelete) } dp.log.Trace().Zid(zid).Err(err).Msg("DeleteZettel") return err } func (dp *dirBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = dp.readonly st.Zettel = dp.dirSrv.NumDirEntries() dp.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } |
Changes to box/dirbox/dirbox_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 dirbox import "testing" func TestIsPrime(t *testing.T) { | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package dirbox import "testing" func TestIsPrime(t *testing.T) { |
︙ | ︙ | |||
28 29 30 31 32 33 34 | if got != tc.exp { t.Errorf("isPrime(%d)=%v, but got %v", tc.n, tc.exp, got) } } } func TestMakePrime(t *testing.T) { | | | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | if got != tc.exp { t.Errorf("isPrime(%d)=%v, but got %v", tc.n, tc.exp, got) } } } func TestMakePrime(t *testing.T) { for i := range uint32(1500) { np := makePrime(i) if np < i { t.Errorf("makePrime(%d) < %d", i, np) continue } if !isPrime(np) { t.Errorf("makePrime(%d) == %d is not prime", i, np) |
︙ | ︙ |
Changes to box/dirbox/service.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- // 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 dirbox import ( "context" "io" "os" "path/filepath" "time" "zettelstore.de/z/box/filebox" "zettelstore.de/z/box/notify" | > > > > > < | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package dirbox import ( "context" "fmt" "io" "os" "path/filepath" "time" "t73f.de/r/zsc/input" "zettelstore.de/z/box/filebox" "zettelstore.de/z/box/notify" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func fileService(i uint32, log *logger.Logger, dirPath string, cmds <-chan fileCmd) { // Something may panic. Ensure a running service. defer func() { if ri := recover(); ri != nil { kernel.Main.LogRecover("FileService", ri) go fileService(i, log, dirPath, cmds) } }() log.Debug().Uint("i", uint64(i)).Str("dirpath", dirPath).Msg("File service started") for cmd := range cmds { cmd.run(dirPath) } log.Debug().Uint("i", uint64(i)).Str("dirpath", dirPath).Msg("File service stopped") } type fileCmd interface { run(string) } const serviceTimeout = 5 * time.Second // must be shorter than the web servers timeout values for reading+writing. // COMMAND: srvGetMeta ---------------------------------------- // // Retrieves the meta data from a zettel. |
︙ | ︙ | |||
71 72 73 74 75 76 77 | rc chan<- resGetMeta } type resGetMeta struct { meta *meta.Meta err error } | | | < | | 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 | rc chan<- resGetMeta } type resGetMeta struct { meta *meta.Meta err error } func (cmd *fileGetMeta) run(dirPath string) { var m *meta.Meta var err error entry := cmd.entry zid := entry.Zid if metaName := entry.MetaName; metaName == "" { contentName := entry.ContentName contentExt := entry.ContentExt if contentName == "" || contentExt == "" { err = fmt.Errorf("no meta, no content in getMeta, zid=%v", zid) } else if entry.HasMetaInContent() { m, _, err = parseMetaContentFile(zid, filepath.Join(dirPath, contentName)) } else { m = filebox.CalcDefaultMeta(zid, contentExt) } } else { m, err = parseMetaFile(zid, filepath.Join(dirPath, metaName)) } |
︙ | ︙ | |||
124 125 126 127 128 129 130 | } type resGetMetaContent struct { meta *meta.Meta content []byte err error } | | | < | | 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 | } type resGetMetaContent struct { meta *meta.Meta content []byte err error } func (cmd *fileGetMetaContent) run(dirPath string) { var m *meta.Meta var content []byte var err error entry := cmd.entry zid := entry.Zid contentName := entry.ContentName contentExt := entry.ContentExt contentPath := filepath.Join(dirPath, contentName) if metaName := entry.MetaName; metaName == "" { if contentName == "" || contentExt == "" { err = fmt.Errorf("no meta, no content in getMetaContent, zid=%v", zid) } else if entry.HasMetaInContent() { m, content, err = parseMetaContentFile(zid, contentPath) } else { m = filebox.CalcDefaultMeta(zid, contentExt) content, err = os.ReadFile(contentPath) } } else { m, err = parseMetaFile(zid, filepath.Join(dirPath, metaName)) |
︙ | ︙ | |||
184 185 186 187 188 189 190 | type fileSetZettel struct { entry *notify.DirEntry zettel zettel.Zettel rc chan<- resSetZettel } type resSetZettel = error | | > | | | | | | | | | > | | 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 | type fileSetZettel struct { entry *notify.DirEntry zettel zettel.Zettel rc chan<- resSetZettel } type resSetZettel = error func (cmd *fileSetZettel) run(dirPath string) { var err error entry := cmd.entry zid := entry.Zid contentName := entry.ContentName m := cmd.zettel.Meta content := cmd.zettel.Content.AsBytes() metaName := entry.MetaName if metaName == "" { if contentName == "" { err = fmt.Errorf("no meta, no content in setZettel, zid=%v", zid) } else { contentPath := filepath.Join(dirPath, contentName) if entry.HasMetaInContent() { err = writeZettelFile(contentPath, m, content) cmd.rc <- err return } err = writeFileContent(contentPath, content) } cmd.rc <- err return } err = writeMetaFile(filepath.Join(dirPath, metaName), m) if err == nil && contentName != "" { err = writeFileContent(filepath.Join(dirPath, contentName), content) } cmd.rc <- err } func writeMetaFile(metaPath string, m *meta.Meta) error { |
︙ | ︙ | |||
233 234 235 236 237 238 239 | } func writeZettelFile(contentPath string, m *meta.Meta, content []byte) error { zettelFile, err := openFileWrite(contentPath) if err != nil { return err } | < | < | 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | } func writeZettelFile(contentPath string, m *meta.Meta, content []byte) error { zettelFile, err := openFileWrite(contentPath) if err != nil { return err } err = writeMetaHeader(zettelFile, m) if err == nil { _, err = zettelFile.Write(content) } if err1 := zettelFile.Close(); err == nil { err = err1 } return err |
︙ | ︙ | |||
296 297 298 299 300 301 302 | type fileDeleteZettel struct { entry *notify.DirEntry rc chan<- resDeleteZettel } type resDeleteZettel = error | | | | | > | 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 | type fileDeleteZettel struct { entry *notify.DirEntry rc chan<- resDeleteZettel } type resDeleteZettel = error func (cmd *fileDeleteZettel) run(dirPath string) { var err error entry := cmd.entry contentName := entry.ContentName contentPath := filepath.Join(dirPath, contentName) if metaName := entry.MetaName; metaName == "" { if contentName == "" { err = fmt.Errorf("no meta, no content in deleteZettel, zid=%v", entry.Zid) } else { err = os.Remove(contentPath) } } else { if contentName != "" { err = os.Remove(contentPath) } err1 := os.Remove(filepath.Join(dirPath, metaName)) if err == nil { err = err1 |
︙ | ︙ | |||
355 356 357 358 359 360 361 362 363 | m, entry.Zid, entry.ContentExt, entry.MetaName != "", entry.UselessFiles, ) } func openFileWrite(path string) (*os.File, error) { | > > > > > > | | 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 | m, entry.Zid, entry.ContentExt, entry.MetaName != "", entry.UselessFiles, ) } // fileMode to create a new file: user, group, and all are allowed to read and write. // // If you want to forbid others or the group to read or to write, you must set // umask(1) accordingly. const fileMode os.FileMode = 0666 // func openFileWrite(path string) (*os.File, error) { return os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileMode) } func writeFileZid(w io.Writer, zid id.Zid) error { _, err := io.WriteString(w, "id: ") if err == nil { _, err = w.Write(zid.Bytes()) if err == nil { |
︙ | ︙ |
Changes to box/filebox/filebox.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 filebox provides boxes that are stored in a file. package filebox import ( "errors" "net/url" "path/filepath" "strings" | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package filebox provides boxes that are stored in a file. package filebox import ( "errors" "net/url" "path/filepath" "strings" "t73f.de/r/zsc/api" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/kernel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ |
Changes to box/filebox/zipbox.go.
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 filebox import ( "archive/zip" "context" "io" "strings" "zettelstore.de/z/box" "zettelstore.de/z/box/notify" | > > > > > < | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package filebox import ( "archive/zip" "context" "fmt" "io" "strings" "t73f.de/r/zsc/input" "zettelstore.de/z/box" "zettelstore.de/z/box/notify" "zettelstore.de/z/logger" "zettelstore.de/z/query" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ | |||
96 97 98 99 100 101 102 | var m *meta.Meta var src []byte var inMeta bool contentName := entry.ContentName if metaName := entry.MetaName; metaName == "" { if contentName == "" { | | > | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | var m *meta.Meta var src []byte var inMeta bool contentName := entry.ContentName if metaName := entry.MetaName; metaName == "" { if contentName == "" { err = fmt.Errorf("no meta, no content in getZettel, zid=%v", zid) return zettel.Zettel{}, err } src, err = readZipFileContent(reader, entry.ContentName) if err != nil { return zettel.Zettel{}, err } if entry.HasMetaInContent() { inp := input.NewInput(src) |
︙ | ︙ | |||
163 164 165 166 167 168 169 | } zb.enricher.Enrich(ctx, m, zb.number) handle(m) } return nil } | < < < < < < < < < < < < < < < < < < | 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | } zb.enricher.Enrich(ctx, m, zb.number) handle(m) } return nil } 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.ErrZettelNotFound{Zid: zid} |
︙ | ︙ | |||
205 206 207 208 209 210 211 | func (zb *zipBox) readZipMeta(reader *zip.ReadCloser, zid id.Zid, entry *notify.DirEntry) (m *meta.Meta, err error) { var inMeta bool if metaName := entry.MetaName; metaName == "" { contentName := entry.ContentName contentExt := entry.ContentExt if contentName == "" || contentExt == "" { | | < | | 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | func (zb *zipBox) readZipMeta(reader *zip.ReadCloser, zid id.Zid, entry *notify.DirEntry) (m *meta.Meta, err error) { var inMeta bool if metaName := entry.MetaName; metaName == "" { contentName := entry.ContentName contentExt := entry.ContentExt if contentName == "" || contentExt == "" { err = fmt.Errorf("no meta, no content in getMeta, zid=%v", zid) } else if entry.HasMetaInContent() { m, err = readZipMetaFile(reader, zid, contentName) } else { m = CalcDefaultMeta(zid, contentExt) } } else { m, err = readZipMetaFile(reader, zid, metaName) } |
︙ | ︙ |
Changes to box/helper.go.
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) 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 box import ( "net/url" "strconv" "time" "zettelstore.de/z/zettel/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 | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package box import ( "net/url" "strconv" "time" "zettelstore.de/z/zettel/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 range 90 { // Must be completed within 9 seconds (less than web/server.writeTimeout) zid := id.New(withSeconds) found, err := testZid(zid) if err != nil { return id.Invalid, err } if found { return zid, nil |
︙ | ︙ |
Changes to box/manager/anteroom.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 | //----------------------------------------------------------------------------- // 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 manager import ( "sync" "zettelstore.de/z/zettel/id" ) type arAction int const ( arNothing arAction = iota arReload arZettel ) type anteroom struct { next *anteroom | > > > | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package manager import ( "sync" "zettelstore.de/z/zettel/id" ) type arAction int const ( arNothing arAction = iota arReload arZettel ) type anteroom struct { next *anteroom waiting *id.Set curLoad int reload bool } type anteroomQueue struct { mx sync.Mutex first *anteroom last *anteroom maxLoad int } func newAnteroomQueue(maxLoad int) *anteroomQueue { return &anteroomQueue{maxLoad: maxLoad} } func (ar *anteroomQueue) EnqueueZettel(zid id.Zid) { if !zid.IsValid() || zid == id.MappingZid || zid == 9999999996 { 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 room.waiting.Contains(zid) { // Zettel is already waiting. Nothing to do. return } } if room := ar.last; !room.reload && (ar.maxLoad == 0 || room.curLoad < ar.maxLoad) { room.waiting.Add(zid) room.curLoad++ |
︙ | ︙ | |||
81 82 83 84 85 86 87 | func (ar *anteroomQueue) Reset() { ar.mx.Lock() defer ar.mx.Unlock() ar.first = &anteroom{next: nil, waiting: nil, curLoad: 0, reload: true} ar.last = ar.first } | | | | | 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | func (ar *anteroomQueue) Reset() { ar.mx.Lock() defer ar.mx.Unlock() ar.first = &anteroom{next: nil, waiting: nil, curLoad: 0, reload: true} ar.last = ar.first } func (ar *anteroomQueue) Reload(allZids *id.Set) { ar.mx.Lock() defer ar.mx.Unlock() ar.deleteReloadedRooms() if !allZids.IsEmpty() { ar.first = &anteroom{next: ar.first, waiting: allZids, curLoad: allZids.Length(), reload: true} if ar.first.next == nil { ar.last = ar.first } } else { ar.first = nil ar.last = nil } |
︙ | ︙ | |||
117 118 119 120 121 122 123 | defer ar.mx.Unlock() first := ar.first if first != nil { if first.waiting == nil && first.reload { ar.removeFirst() return arReload, id.Invalid, false } | | | < | 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | defer ar.mx.Unlock() first := ar.first if first != nil { if first.waiting == nil && first.reload { ar.removeFirst() return arReload, id.Invalid, false } if zid, found := first.waiting.Pop(); found { if first.waiting.IsEmpty() { ar.removeFirst() } return arZettel, zid, first.reload } ar.removeFirst() } return arNothing, id.Invalid, false |
︙ | ︙ |
Changes to box/manager/anteroom_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 manager import ( "testing" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package manager import ( "testing" |
︙ | ︙ |
Changes to box/manager/box.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 manager import ( "context" "errors" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package manager import ( "context" "errors" |
︙ | ︙ | |||
26 27 28 29 30 31 32 | // Location returns some information where the box is located. func (mgr *Manager) Location() string { if len(mgr.boxes) <= 2 { return "NONE" } var sb strings.Builder | | | | | | | | | | > > | > > > > > > > > > > > > > > > > > > | | > > > | | | | | < > > > > > > > > > > | | | | | | | | | | 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 | // Location returns some information where the box is located. func (mgr *Manager) Location() string { if len(mgr.boxes) <= 2 { return "NONE" } var sb strings.Builder for i := range len(mgr.boxes) - 2 { if i > 0 { sb.WriteString(", ") } 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 { if err := mgr.checkContinue(ctx); err != nil { return false } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if box, isWriteBox := mgr.boxes[0].(box.WriteBox); isWriteBox { return box.CanCreateZettel(ctx) } return false } // CreateZettel creates a new zettel. func (mgr *Manager) CreateZettel(ctx context.Context, ztl zettel.Zettel) (id.Zid, error) { mgr.mgrLog.Debug().Msg("CreateZettel") if err := mgr.checkContinue(ctx); err != nil { return id.Invalid, err } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if box, isWriteBox := mgr.boxes[0].(box.WriteBox); isWriteBox { ztl.Meta = mgr.cleanMetaProperties(ztl.Meta) zidO, err := box.CreateZettel(ctx, ztl) if err == nil { mgr.idxUpdateZettel(ctx, ztl) err = mgr.createMapping(ctx, zidO) } return zidO, err } return id.Invalid, box.ErrReadOnly } func (mgr *Manager) createMapping(ctx context.Context, zidO id.Zid) error { mgr.mappingMx.Lock() defer mgr.mappingMx.Unlock() mappingZettel, err := mgr.getZettel(ctx, id.MappingZid) if err != nil { mgr.mgrLog.Error().Err(err).Msg("Unable to get mapping zettel") return err } zidN := mgr.zidMapper.GetZidN(zidO) mappingZettel.Content = zettel.NewContent(mgr.zidMapper.AsBytes()) if err = mgr.UpdateZettel(ctx, mappingZettel); err != nil { mgr.mgrLog.Error().Err(err).Zid(zidO).Uint("zidN", uint64(zidN)).Msg("Unable to update mapping zettel") return err } mgr.mgrLog.Debug().Zid(zidO).Msg("add to mapping zettel") return nil } // GetZettel retrieves a specific zettel. func (mgr *Manager) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetZettel") if err := mgr.checkContinue(ctx); err != nil { return zettel.Zettel{}, err } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() return mgr.getZettel(ctx, zid) } func (mgr *Manager) getZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) { for i, p := range mgr.boxes { var errZNF box.ErrZettelNotFound if z, err := p.GetZettel(ctx, zid); !errors.As(err, &errZNF) { if err == nil { mgr.Enrich(ctx, z.Meta, i+1) } return z, err } } return zettel.Zettel{}, box.ErrZettelNotFound{Zid: zid} } // GetAllZettel retrieves a specific zettel from all managed boxes. func (mgr *Manager) GetAllZettel(ctx context.Context, zid id.Zid) ([]zettel.Zettel, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetAllZettel") if err := mgr.checkContinue(ctx); err != nil { return nil, err } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() var result []zettel.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 } // 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 err := mgr.checkContinue(ctx); err != nil { return nil, err } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() return mgr.fetchZids(ctx) } func (mgr *Manager) fetchZids(ctx context.Context) (*id.Set, error) { numZettel := 0 for _, p := range mgr.boxes { var mbstats box.ManagedBoxStats p.ReadStats(&mbstats) numZettel += mbstats.Zettel } result := id.NewSetCap(numZettel) for _, p := range mgr.boxes { err := p.ApplyZid(ctx, func(zid id.Zid) { result.Add(zid) }, query.AlwaysIncluded) if err != nil { return nil, err } } return result, nil } func (mgr *Manager) hasZettel(ctx context.Context, zid id.Zid) bool { mgr.mgrLog.Debug().Zid(zid).Msg("HasZettel") if err := mgr.checkContinue(ctx); err != nil { return false } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for _, bx := range mgr.boxes { if bx.HasZettel(ctx, zid) { return true } } return false } func (mgr *Manager) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) { mgr.mgrLog.Debug().Zid(zid).Msg("GetMeta") if err := mgr.checkContinue(ctx); err != nil { return nil, err } m, err := mgr.idxStore.GetMeta(ctx, zid) if err != nil { return nil, err } mgr.Enrich(ctx, m, 0) return m, nil } // 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, metaSeq []*meta.Meta, q *query.Query) ([]*meta.Meta, error) { if msg := mgr.mgrLog.Debug(); msg.Enabled() { msg.Str("query", q.String()).Msg("SelectMeta") } if err := mgr.checkContinue(ctx); err != nil { return nil, err } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() compSearch := q.RetrieveAndCompile(ctx, mgr, metaSeq) if result := compSearch.Result(); result != nil { mgr.mgrLog.Trace().Int("count", int64(len(result))).Msg("found without ApplyMeta") return result, nil } selected := map[id.Zid]*meta.Meta{} for _, term := range compSearch.Terms { rejected := id.NewSet() 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 } |
︙ | ︙ | |||
207 208 209 210 211 212 213 | result = compSearch.AfterSearch(result) mgr.mgrLog.Trace().Int("count", int64(len(result))).Msg("found with ApplyMeta") return result, nil } // CanUpdateZettel returns true, if box could possibly update the given zettel. func (mgr *Manager) CanUpdateZettel(ctx context.Context, zettel zettel.Zettel) bool { | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < > | | | | | | > > | | > > > > > > > > > > > > > > > > > | 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 | result = compSearch.AfterSearch(result) mgr.mgrLog.Trace().Int("count", int64(len(result))).Msg("found with ApplyMeta") return result, nil } // CanUpdateZettel returns true, if box could possibly update the given zettel. func (mgr *Manager) CanUpdateZettel(ctx context.Context, zettel zettel.Zettel) bool { if err := mgr.checkContinue(ctx); err != nil { return false } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() if box, isWriteBox := mgr.boxes[0].(box.WriteBox); isWriteBox { return box.CanUpdateZettel(ctx, zettel) } return false } // UpdateZettel updates an existing zettel. func (mgr *Manager) UpdateZettel(ctx context.Context, zettel zettel.Zettel) error { mgr.mgrLog.Debug().Zid(zettel.Meta.Zid).Msg("UpdateZettel") if err := mgr.checkContinue(ctx); err != nil { return err } if box, isWriteBox := mgr.boxes[0].(box.WriteBox); isWriteBox { zettel.Meta = mgr.cleanMetaProperties(zettel.Meta) if err := box.UpdateZettel(ctx, zettel); err != nil { return err } mgr.idxUpdateZettel(ctx, zettel) return nil } return box.ErrReadOnly } // CanDeleteZettel returns true, if box could possibly delete the given zettel. func (mgr *Manager) CanDeleteZettel(ctx context.Context, zid id.Zid) bool { if err := mgr.checkContinue(ctx); err != nil { return false } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for _, p := range mgr.boxes { if p.CanDeleteZettel(ctx, zid) { return true } } return false } // DeleteZettel removes the zettel from the box. func (mgr *Manager) DeleteZettel(ctx context.Context, zidO id.Zid) error { mgr.mgrLog.Debug().Zid(zidO).Msg("DeleteZettel") if err := mgr.checkContinue(ctx); err != nil { return err } mgr.mgrMx.RLock() defer mgr.mgrMx.RUnlock() for _, p := range mgr.boxes { err := p.DeleteZettel(ctx, zidO) if err == nil { mgr.idxDeleteZettel(ctx, zidO) err = mgr.deleteMapping(ctx, zidO) return err } var errZNF box.ErrZettelNotFound if !errors.As(err, &errZNF) && !errors.Is(err, box.ErrReadOnly) { return err } } return box.ErrZettelNotFound{Zid: zidO} } func (mgr *Manager) deleteMapping(ctx context.Context, zidO id.Zid) error { mgr.mappingMx.Lock() defer mgr.mappingMx.Unlock() mappingZettel, err := mgr.getZettel(ctx, id.MappingZid) if err != nil { mgr.mgrLog.Error().Err(err).Msg("Unable to get mapping zettel") return err } mgr.zidMapper.DeleteO(zidO) mappingZettel.Content = zettel.NewContent(mgr.zidMapper.AsBytes()) if err = mgr.UpdateZettel(ctx, mappingZettel); err != nil { mgr.mgrLog.Error().Err(err).Zid(zidO).Msg("Unable to update mapping zettel") return err } mgr.mgrLog.Debug().Zid(zidO).Msg("remove from mapping zettel") return nil } // Remove all (computed) properties from metadata before storing the zettel. func (mgr *Manager) cleanMetaProperties(m *meta.Meta) *meta.Meta { result := m.Clone() for _, p := range result.ComputedPairsRest() { if mgr.propertyKeys.Has(p.Key) { result.Delete(p.Key) } } return result } |
Changes to box/manager/collect.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //----------------------------------------------------------------------------- // 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 manager import ( "strings" "zettelstore.de/z/ast" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" ) type collectData struct { | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package manager import ( "strings" "zettelstore.de/z/ast" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" ) type collectData struct { refs *id.Set words store.WordSet urls store.WordSet } func (data *collectData) initialize() { data.refs = id.NewSet() data.words = store.NewWordSet() |
︙ | ︙ |
Changes to box/manager/enrich.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 manager import ( "context" "strconv" | > > > | > > > > > > | > > > > > > > > | > | < < < < | | | | | > > > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package manager import ( "context" "strconv" "t73f.de/r/zsc/api" "zettelstore.de/z/box" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // Enrich computes additional properties and updates the given metadata. func (mgr *Manager) Enrich(ctx context.Context, m *meta.Meta, boxNumber int) { // Calculate new zid if m.ZidN.IsValid() { if zidN, found := mgr.zidMapper.LookupZidN(m.Zid); found && m.ZidN != zidN { mgr.mgrLog.Error().Zid(m.Zid). Uint("stored", uint64(m.ZidN)).Uint("mapped", uint64(zidN)). Msg("mapped != stored") } } else { if zidN, found := mgr.zidMapper.LookupZidN(m.Zid); found { m.ZidN = zidN } else { mgr.mgrLog.Error().Zid(m.Zid).Msg("no mapping found") } } // Calculate computed, but stored values. _, hasCreated := m.Get(api.KeyCreated) if !hasCreated { m.Set(api.KeyCreated, computeCreated(m.Zid)) } if box.DoEnrich(ctx) { computePublished(m) if boxNumber > 0 { m.Set(api.KeyBoxNumber, strconv.Itoa(boxNumber)) } mgr.idxStore.Enrich(ctx, m) } if !hasCreated { m.Set(meta.KeyCreatedMissing, api.ValueTrue) } } func computeCreated(zid id.Zid) string { if zid <= 10101000000 { // A year 0000 is not allowed and therefore an artificial Zid. // In the year 0001, the month must be > 0. // In the month 000101, the day must be > 0. return "00010101000000" } seconds := zid % 100 if seconds > 59 { seconds = 59 |
︙ | ︙ |
Changes to box/manager/indexer.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 manager import ( "context" "fmt" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package manager import ( "context" "fmt" |
︙ | ︙ | |||
24 25 26 27 28 29 30 | "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // SearchEqual returns all zettel that contains the given exact word. // The word must be normalized through Unicode NKFD, trimmed and not empty. | | | | | | | | | | 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 | "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // 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(found.Length())).Msg("SearchEqual") if msg := mgr.idxLog.Trace(); msg.Enabled() { msg.Str("ids", fmt.Sprint(found)).Msg("IDs") } return found } // SearchPrefix returns all zettel that have a word with the given prefix. // The prefix must be normalized through Unicode NKFD, trimmed and not empty. func (mgr *Manager) SearchPrefix(prefix string) *id.Set { found := mgr.idxStore.SearchPrefix(prefix) mgr.idxLog.Debug().Str("prefix", prefix).Int("found", int64(found.Length())).Msg("SearchPrefix") if msg := mgr.idxLog.Trace(); msg.Enabled() { msg.Str("ids", fmt.Sprint(found)).Msg("IDs") } return found } // SearchSuffix returns all zettel that have a word with the given suffix. // The suffix must be normalized through Unicode NKFD, trimmed and not empty. func (mgr *Manager) SearchSuffix(suffix string) *id.Set { found := mgr.idxStore.SearchSuffix(suffix) mgr.idxLog.Debug().Str("suffix", suffix).Int("found", int64(found.Length())).Msg("SearchSuffix") if msg := mgr.idxLog.Trace(); msg.Enabled() { msg.Str("ids", fmt.Sprint(found)).Msg("IDs") } return found } // SearchContains returns all zettel that contains the given string. // The string must be normalized through Unicode NKFD, trimmed and not empty. func (mgr *Manager) SearchContains(s string) *id.Set { found := mgr.idxStore.SearchContains(s) mgr.idxLog.Debug().Str("s", s).Int("found", int64(found.Length())).Msg("SearchContains") if msg := mgr.idxLog.Trace(); msg.Enabled() { msg.Str("ids", fmt.Sprint(found)).Msg("IDs") } return found } // idxIndexer runs in the background and updates the index data structures. |
︙ | ︙ | |||
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | if !ok { return false } case _, ok := <-timer.C: if !ok { return false } timer.Reset(timerDuration) case <-mgr.done: if !timer.Stop() { <-timer.C } return false } return true } func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel zettel.Zettel) { var cData collectData cData.initialize() | > > | > > > > > | 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 | if !ok { return false } case _, ok := <-timer.C: if !ok { return false } // mgr.idxStore.Optimize() // TODO: make it less often, for example once per 10 minutes timer.Reset(timerDuration) case <-mgr.done: if !timer.Stop() { <-timer.C } return false } return true } func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel zettel.Zettel) { var cData collectData cData.initialize() if mustIndexZettel(zettel.Meta) { collectZettelIndexData(parser.ParseZettel(ctx, zettel, "", mgr.rtConfig), &cData) } m := zettel.Meta zi := store.NewZettelIndex(m) mgr.idxCollectFromMeta(ctx, m, zi, &cData) mgr.idxProcessData(ctx, zi, &cData) toCheck := mgr.idxStore.UpdateReferences(ctx, zi) mgr.idxCheckZettel(toCheck) } func mustIndexZettel(m *meta.Meta) bool { return m.Zid >= id.DefaultHomeZid } func (mgr *Manager) idxCollectFromMeta(ctx context.Context, m *meta.Meta, zi *store.ZettelIndex, cData *collectData) { for _, pair := range m.ComputedPairs() { descr := meta.GetDescription(pair.Key) if descr.IsProperty() { continue } |
︙ | ︙ | |||
202 203 204 205 206 207 208 | } } else { stWords.Add(value) } } func (mgr *Manager) idxProcessData(ctx context.Context, zi *store.ZettelIndex, cData *collectData) { | | | | | < < < < < | | | | 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 | } } else { stWords.Add(value) } } func (mgr *Manager) idxProcessData(ctx context.Context, zi *store.ZettelIndex, cData *collectData) { cData.refs.ForEach(func(ref id.Zid) { if mgr.hasZettel(ctx, ref) { 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 !mgr.hasZettel(ctx, zid) { zi.AddDeadRef(zid) return } if inverseKey == "" { zi.AddBackRef(zid) return } zi.AddInverseRef(inverseKey, zid) } func (mgr *Manager) idxDeleteZettel(ctx context.Context, zid id.Zid) { toCheck := mgr.idxStore.DeleteZettel(ctx, zid) mgr.idxCheckZettel(toCheck) } func (mgr *Manager) idxCheckZettel(s *id.Set) { s.ForEach(func(zid id.Zid) { mgr.idxAr.EnqueueZettel(zid) }) } |
Changes to box/manager/manager.go.
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 manager coordinates the various boxes and indexes of a Zettelstore. package manager import ( "context" "io" "net/url" "sync" "time" | > > > > < | > > > > > > > > > | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package manager coordinates the various boxes and indexes of a Zettelstore. package manager import ( "bytes" "context" "io" "net/url" "sync" "time" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/box/manager/mapstore" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/config" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // ConnectData contains all administration related values. type ConnectData struct { Number int // number of the box, starting with 1. Config config.Config Enricher box.Enricher Notify chan<- box.UpdateInfo Mapper Mapper } // Mapper allows to inspect the mapping between old-style and new-style zettel identifier. type Mapper interface { Warnings(context.Context) (*id.Set, error) // Fetch problematic zettel identifier FetchAsBytes(context.Context) ([]byte, error) } // Connect returns a handle to the specified box. func Connect(u *url.URL, authManager auth.BaseManager, cdata *ConnectData) (box.ManagedBox, error) { if authManager.IsReadonly() { rawURL := u.String() // TODO: the following is wrong under some circumstances: |
︙ | ︙ | |||
75 76 77 78 79 80 81 | func Register(scheme string, create createFunc) { if _, ok := registry[scheme]; ok { panic(scheme) } registry[scheme] = create } | < < < > > | 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 | func Register(scheme string, create createFunc) { if _, ok := registry[scheme]; ok { panic(scheme) } registry[scheme] = create } // Manager is a coordinating box. type Manager struct { mgrLog *logger.Logger stateMx sync.RWMutex state box.StartState mgrMx sync.RWMutex rtConfig config.Config boxes []box.ManagedBox observers []box.UpdateFunc mxObserver sync.RWMutex done chan struct{} infos chan box.UpdateInfo propertyKeys strfun.Set // Set of property key names zidMapper *zidMapper mappingMx sync.Mutex // protects updates to mapping zettel // Indexer data idxLog *logger.Logger idxStore store.Store idxAr *anteroomQueue idxReady chan struct{} // Signal a non-empty anteroom to background task |
︙ | ︙ | |||
135 136 137 138 139 140 141 | mgr := &Manager{ 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(), | | > > | | 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | mgr := &Manager{ 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: createIdxStore(rtConfig), idxAr: newAnteroomQueue(1000), idxReady: make(chan struct{}, 1), } mgr.zidMapper = NewZidMapper(mgr) cdata := ConnectData{Number: 1, Config: rtConfig, Enricher: mgr, Notify: mgr.infos, Mapper: mgr.zidMapper} boxes := make([]box.ManagedBox, 0, len(boxURIs)+2) for _, uri := range boxURIs { p, err := Connect(uri, authManager, &cdata) if err != nil { return nil, err } if p != nil { |
︙ | ︙ | |||
165 166 167 168 169 170 171 172 173 174 175 176 177 178 | return nil, err } cdata.Number++ boxes = append(boxes, constbox, compbox) mgr.boxes = boxes return mgr, nil } // RegisterObserver registers an observer that will be notified // if a zettel was found to be changed. func (mgr *Manager) RegisterObserver(f box.UpdateFunc) { if f != nil { mgr.mxObserver.Lock() mgr.observers = append(mgr.observers, f) | > > > > | 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | return nil, err } cdata.Number++ boxes = append(boxes, constbox, compbox) mgr.boxes = boxes return mgr, nil } func createIdxStore(_ config.Config) store.Store { return mapstore.New() } // RegisterObserver registers an observer that will be notified // if a zettel was found to be changed. func (mgr *Manager) RegisterObserver(f box.UpdateFunc) { if f != nil { mgr.mxObserver.Lock() mgr.observers = append(mgr.observers, f) |
︙ | ︙ | |||
206 207 208 209 210 211 212 | reason, zid := ci.Reason, ci.Zid mgr.mgrLog.Debug().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier") if ignoreUpdate(cache, now, reason, zid) { mgr.mgrLog.Trace().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier ignored") continue } | > | | | 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | reason, zid := ci.Reason, ci.Zid mgr.mgrLog.Debug().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier") if ignoreUpdate(cache, now, reason, zid) { mgr.mgrLog.Trace().Uint("reason", uint64(reason)).Zid(zid).Msg("notifier ignored") continue } isStarted := mgr.State() == box.StartStateStarted mgr.idxEnqueue(reason, zid, isStarted) if ci.Box == nil { ci.Box = mgr } if isStarted { mgr.notifyObserver(&ci) } } case <-mgr.done: return } } |
︙ | ︙ | |||
239 240 241 242 243 244 245 | cache[zid] = destutterData{ deadAt: now.Add(500 * time.Millisecond), reason: reason, } return false } | | > > > > > | > > > > > | | 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 | cache[zid] = destutterData{ deadAt: now.Add(500 * time.Millisecond), reason: reason, } return false } func (mgr *Manager) idxEnqueue(reason box.UpdateReason, zidO id.Zid, isStarted bool) { switch reason { case box.OnReady: return case box.OnReload: mgr.idxAr.Reset() case box.OnZettel: if isStarted && zidO != id.MappingZid { if _, found := mgr.zidMapper.LookupZidN(zidO); !found { mgr.createMapping(context.Background(), zidO) } } mgr.idxAr.EnqueueZettel(zidO) case box.OnDelete: if isStarted && zidO != id.MappingZid { mgr.deleteMapping(context.Background(), zidO) } mgr.idxAr.EnqueueZettel(zidO) default: mgr.mgrLog.Error().Uint("reason", uint64(reason)).Zid(zidO).Msg("Unknown notification reason") return } select { case mgr.idxReady <- struct{}{}: default: } } |
︙ | ︙ | |||
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | return err } mgr.idxAr.Reset() // Ensure an initial index run mgr.done = make(chan struct{}) go mgr.notifier() mgr.waitBoxesAreStarted() mgr.setState(box.StartStateStarted) mgr.notifyObserver(&box.UpdateInfo{Box: mgr, Reason: box.OnReady}) go mgr.idxIndexer() return nil } func (mgr *Manager) waitBoxesAreStarted() { const waitTime = 10 * time.Millisecond const waitLoop = int(1 * time.Second / waitTime) for i := 1; !mgr.allBoxesStarted(); i++ { if i%waitLoop == 0 { if time.Duration(i)*waitTime > time.Minute { | > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | 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 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 | return err } mgr.idxAr.Reset() // Ensure an initial index run mgr.done = make(chan struct{}) go mgr.notifier() mgr.waitBoxesAreStarted() mgr.setupIdentifierMapping() mgr.setState(box.StartStateStarted) mgr.notifyObserver(&box.UpdateInfo{Box: mgr, Reason: box.OnReady}) go mgr.idxIndexer() return nil } func (mgr *Manager) waitBoxesAreStarted() { const waitTime = 10 * time.Millisecond const waitLoop = int(1 * time.Second / waitTime) for i := 1; !mgr.allBoxesStarted(); i++ { if i%waitLoop == 0 { if time.Duration(i)*waitTime > time.Minute { mgr.mgrLog.Info().Msg("Waiting for more than one minute to start") } else { mgr.mgrLog.Trace().Msg("Wait for boxes to start") } } time.Sleep(waitTime) } } 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 } func (mgr *Manager) setupIdentifierMapping() { ctx := context.Background() z, err := mgr.getZettel(ctx, id.MappingZid) if err != nil { mgr.mgrLog.Error().Err(err).Msg("unable to get identifier mapping zettel") return } if z.Content.IsBinary() { mgr.mgrLog.Error().Msg("identifier mapping is non-text zettel") return } z.Content.TrimSpace() content := z.Content.AsBytes() if err = mgr.zidMapper.parseAndUpdate(content); err != nil { mgr.mgrLog.Error().Err(err).Msg("identifier zettel parsing") } mapping, err := mgr.zidMapper.FetchAsBytes(ctx) if err != nil { mgr.mgrLog.Error().Err(err).Msg("unable to get current identifier mapping") return } if !bytes.Equal(content, mapping) { z.Content = zettel.NewContent(mapping) if err = mgr.UpdateZettel(ctx, z); err != nil { mgr.mgrLog.Error().Err(err).Msg("unable to write identifier mapping zettel") } else { mgr.mgrLog.Info().Msg("Mapping was updated") } } else { mgr.mgrLog.Info().Msg("No mapping update") } } // Stop the started box. Now only the Start() function is allowed. func (mgr *Manager) Stop(ctx context.Context) { mgr.mgrMx.Lock() defer mgr.mgrMx.Unlock() if err := mgr.checkContinue(ctx); err != nil { return } mgr.setState(box.StartStateStopping) close(mgr.done) for _, p := range mgr.boxes { if ss, ok := p.(box.StartStopper); ok { ss.Stop(ctx) } } mgr.setState(box.StartStateStopped) } // Refresh internal box data. func (mgr *Manager) Refresh(ctx context.Context) error { mgr.mgrLog.Debug().Msg("Refresh") if err := mgr.checkContinue(ctx); err != nil { return err } mgr.infos <- box.UpdateInfo{Reason: box.OnReload, Zid: id.Invalid} mgr.mgrMx.Lock() defer mgr.mgrMx.Unlock() for _, bx := range mgr.boxes { if rb, ok := bx.(box.Refresher); ok { rb.Refresh(ctx) } } return nil } // ReIndex data of the given zettel. func (mgr *Manager) ReIndex(ctx context.Context, zid id.Zid) error { mgr.mgrLog.Debug().Msg("ReIndex") if err := mgr.checkContinue(ctx); err != nil { return err } mgr.infos <- box.UpdateInfo{Box: mgr, Reason: box.OnZettel, Zid: zid} return nil } // ReadStats populates st with box statistics. func (mgr *Manager) ReadStats(st *box.Stats) { mgr.mgrLog.Debug().Msg("ReadStats") mgr.mgrMx.RLock() |
︙ | ︙ | |||
412 413 414 415 416 417 418 | st.IndexedUrls = storeSt.Urls } // Dump internal data structures to a Writer. func (mgr *Manager) Dump(w io.Writer) { mgr.idxStore.Dump(w) } | > > > > > > > | 477 478 479 480 481 482 483 484 485 486 487 488 489 490 | st.IndexedUrls = storeSt.Urls } // Dump internal data structures to a Writer. func (mgr *Manager) Dump(w io.Writer) { mgr.idxStore.Dump(w) } func (mgr *Manager) checkContinue(ctx context.Context) error { if mgr.State() != box.StartStateStarted { return box.ErrStopped } return ctx.Err() } |
Added box/manager/mapstore/mapstore.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 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 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package mapstore stored the index in main memory via a Go map. package mapstore import ( "context" "fmt" "io" "slices" "strings" "sync" "t73f.de/r/zsc/api" "t73f.de/r/zsc/maps" "zettelstore.de/z/box" "zettelstore.de/z/box/manager/store" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) type zettelData struct { meta *meta.Meta // a local copy of the metadata, without computed keys dead *id.Set // set of dead references in this zettel forward *id.Set // set of forward references in this zettel backward *id.Set // set of zettel that reference with zettel otherRefs map[string]bidiRefs words []string // list of words of this zettel urls []string // list of urls of this zettel } type bidiRefs struct { forward *id.Set backward *id.Set } func (zd *zettelData) optimize() { zd.dead.Optimize() zd.forward.Optimize() zd.backward.Optimize() for _, bidi := range zd.otherRefs { bidi.forward.Optimize() bidi.backward.Optimize() } } type mapStore struct { mx sync.RWMutex intern map[string]string // map to intern strings idx map[id.Zid]*zettelData dead map[id.Zid]*id.Set // map dead refs where they occur words stringRefs urls stringRefs // Stats mxStats sync.Mutex updates uint64 } type stringRefs map[string]*id.Set // New returns a new memory-based index store. func New() store.Store { return &mapStore{ intern: make(map[string]string, 1024), idx: make(map[id.Zid]*zettelData), dead: make(map[id.Zid]*id.Set), words: make(stringRefs), urls: make(stringRefs), } } func (ms *mapStore) GetMeta(_ context.Context, zid id.Zid) (*meta.Meta, error) { ms.mx.RLock() defer ms.mx.RUnlock() if zi, found := ms.idx[zid]; found && zi.meta != nil { // zi.meta is nil, if zettel was referenced, but is not indexed yet. return zi.meta.Clone(), nil } return nil, box.ErrZettelNotFound{Zid: zid} } func (ms *mapStore) Enrich(_ context.Context, m *meta.Meta) { if ms.doEnrich(m) { ms.mxStats.Lock() ms.updates++ ms.mxStats.Unlock() } } func (ms *mapStore) 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 !zi.dead.IsEmpty() { m.Set(api.KeyDead, zi.dead.MetaString()) updated = true } back := removeOtherMetaRefs(m, zi.backward.Clone()) if !zi.backward.IsEmpty() { m.Set(api.KeyBackward, zi.backward.MetaString()) updated = true } if !zi.forward.IsEmpty() { m.Set(api.KeyForward, zi.forward.MetaString()) back.ISubstract(zi.forward) updated = true } for k, refs := range zi.otherRefs { if !refs.backward.IsEmpty() { m.Set(k, refs.backward.MetaString()) back.ISubstract(refs.backward) updated = true } } if !back.IsEmpty() { m.Set(api.KeyBack, back.MetaString()) 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 *mapStore) SearchEqual(word string) *id.Set { ms.mx.RLock() defer ms.mx.RUnlock() result := id.NewSet() if refs, ok := ms.words[word]; ok { result = result.IUnion(refs) } if refs, ok := ms.urls[word]; ok { result = result.IUnion(refs) } zid, err := id.Parse(word) if err != nil { return result } zi, ok := ms.idx[zid] if !ok { return result } return addBackwardZids(result, zid, zi) } // SearchPrefix returns all zettel that have a word with the given prefix. // The prefix must be normalized through Unicode NKFD, trimmed and not empty. func (ms *mapStore) SearchPrefix(prefix string) *id.Set { ms.mx.RLock() defer ms.mx.RUnlock() result := ms.selectWithPred(prefix, strings.HasPrefix) l := len(prefix) if l > 14 { return result } maxZid, err := id.Parse(prefix + "99999999999999"[:14-l]) if err != nil { return result } var minZid id.Zid if l < 14 && prefix == "0000000000000"[:l] { minZid = id.Zid(1) } else { minZid, err = id.Parse(prefix + "00000000000000"[:14-l]) if err != nil { return result } } for zid, zi := range ms.idx { if minZid <= zid && zid <= maxZid { result = addBackwardZids(result, zid, zi) } } return result } // SearchSuffix returns all zettel that have a word with the given suffix. // The suffix must be normalized through Unicode NKFD, trimmed and not empty. func (ms *mapStore) SearchSuffix(suffix string) *id.Set { ms.mx.RLock() defer ms.mx.RUnlock() result := ms.selectWithPred(suffix, strings.HasSuffix) l := len(suffix) if l > 14 { return result } val, err := id.ParseUint(suffix) if err != nil { return result } modulo := uint64(1) for range l { modulo *= 10 } for zid, zi := range ms.idx { if uint64(zid)%modulo == val { result = addBackwardZids(result, zid, zi) } } return result } // SearchContains returns all zettel that contains the given string. // The string must be normalized through Unicode NKFD, trimmed and not empty. func (ms *mapStore) SearchContains(s string) *id.Set { ms.mx.RLock() defer ms.mx.RUnlock() result := ms.selectWithPred(s, strings.Contains) if len(s) > 14 { return result } if _, err := id.ParseUint(s); err != nil { return result } for zid, zi := range ms.idx { if strings.Contains(zid.String(), s) { result = addBackwardZids(result, zid, zi) } } return result } func (ms *mapStore) 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.IUnion(refs) } for u, refs := range ms.urls { if !pred(u, s) { continue } result.IUnion(refs) } return result } func addBackwardZids(result *id.Set, zid id.Zid, zi *zettelData) *id.Set { // Must only be called if ms.mx is read-locked! result = result.Add(zid) result = result.IUnion(zi.backward) for _, mref := range zi.otherRefs { result = result.IUnion(mref.backward) } return result } func removeOtherMetaRefs(m *meta.Meta, back *id.Set) *id.Set { for _, p := range m.PairsRest() { switch meta.Type(p.Key) { case meta.TypeID: if zid, err := id.Parse(p.Value); err == nil { back = back.Remove(zid) } case meta.TypeIDSet: for _, val := range meta.ListFromValue(p.Value) { if zid, err := id.Parse(val); err == nil { back = back.Remove(zid) } } } } return back } func (ms *mapStore) UpdateReferences(_ context.Context, zidx *store.ZettelIndex) *id.Set { ms.mx.Lock() defer ms.mx.Unlock() m := ms.makeMeta(zidx) zi, ziExist := ms.idx[zidx.Zid] if !ziExist || zi == nil { zi = &zettelData{} 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 = refs delete(ms.dead, zidx.Zid) } zi.meta = m ms.updateDeadReferences(zidx, zi) ids := ms.updateForwardBackwardReferences(zidx, zi) toCheck = toCheck.IUnion(ids) ids = ms.updateMetadataReferences(zidx, zi) toCheck = toCheck.IUnion(ids) zi.words = updateStrings(zidx.Zid, ms.words, zi.words, zidx.GetWords()) zi.urls = updateStrings(zidx.Zid, ms.urls, zi.urls, zidx.GetUrls()) // Check if zi must be inserted into ms.idx if !ziExist { ms.idx[zidx.Zid] = zi } zi.optimize() return toCheck } var internableKeys = map[string]bool{ api.KeyRole: true, api.KeySyntax: true, api.KeyFolgeRole: true, api.KeyLang: true, api.KeyReadOnly: true, } func isInternableValue(key string) bool { if internableKeys[key] { return true } return strings.HasSuffix(key, meta.SuffixKeyRole) } func (ms *mapStore) internString(s string) string { if is, found := ms.intern[s]; found { return is } ms.intern[s] = s return s } func (ms *mapStore) makeMeta(zidx *store.ZettelIndex) *meta.Meta { origM := zidx.GetMeta() copyM := meta.New(origM.Zid) for _, p := range origM.Pairs() { key := ms.internString(p.Key) if isInternableValue(key) { copyM.Set(key, ms.internString(p.Value)) } else if key == api.KeyBoxNumber || !meta.IsComputed(key) { copyM.Set(key, p.Value) } } return copyM } func (ms *mapStore) updateDeadReferences(zidx *store.ZettelIndex, zi *zettelData) { // Must only be called if ms.mx is write-locked! drefs := zidx.GetDeadRefs() newRefs, remRefs := zi.dead.Diff(drefs) zi.dead = drefs remRefs.ForEach(func(ref id.Zid) { ms.dead[ref] = ms.dead[ref].Remove(zidx.Zid) }) newRefs.ForEach(func(ref id.Zid) { ms.dead[ref] = ms.dead[ref].Add(zidx.Zid) }) } func (ms *mapStore) updateForwardBackwardReferences(zidx *store.ZettelIndex, zi *zettelData) *id.Set { // Must only be called if ms.mx is write-locked! brefs := zidx.GetBackRefs() newRefs, remRefs := zi.forward.Diff(brefs) zi.forward = brefs var toCheck *id.Set remRefs.ForEach(func(ref id.Zid) { bzi := ms.getOrCreateEntry(ref) bzi.backward = bzi.backward.Remove(zidx.Zid) if bzi.meta == nil { toCheck = toCheck.Add(ref) } }) newRefs.ForEach(func(ref id.Zid) { bzi := ms.getOrCreateEntry(ref) bzi.backward = bzi.backward.Add(zidx.Zid) if bzi.meta == nil { toCheck = toCheck.Add(ref) } }) return toCheck } func (ms *mapStore) updateMetadataReferences(zidx *store.ZettelIndex, zi *zettelData) *id.Set { // Must only be called if ms.mx is write-locked! inverseRefs := zidx.GetInverseRefs() for key, mr := range zi.otherRefs { if _, ok := inverseRefs[key]; ok { continue } ms.removeInverseMeta(zidx.Zid, key, mr.forward) } if zi.otherRefs == nil { zi.otherRefs = make(map[string]bidiRefs) } var toCheck *id.Set for key, mrefs := range inverseRefs { mr := zi.otherRefs[key] newRefs, remRefs := mr.forward.Diff(mrefs) mr.forward = mrefs zi.otherRefs[key] = mr newRefs.ForEach(func(ref id.Zid) { bzi := ms.getOrCreateEntry(ref) if bzi.otherRefs == nil { bzi.otherRefs = make(map[string]bidiRefs) } bmr := bzi.otherRefs[key] bmr.backward = bmr.backward.Add(zidx.Zid) bzi.otherRefs[key] = bmr if bzi.meta == nil { toCheck = toCheck.Add(ref) } }) ms.removeInverseMeta(zidx.Zid, key, remRefs) } return toCheck } func updateStrings(zid id.Zid, srefs stringRefs, prev []string, next store.WordSet) []string { newWords, removeWords := next.Diff(prev) for _, word := range newWords { srefs[word] = srefs[word].Add(zid) } for _, word := range removeWords { refs, ok := srefs[word] if !ok { continue } refs = refs.Remove(zid) if refs.IsEmpty() { delete(srefs, word) continue } srefs[word] = refs } return next.Words() } func (ms *mapStore) getOrCreateEntry(zid id.Zid) *zettelData { // Must only be called if ms.mx is write-locked! if zi, ok := ms.idx[zid]; ok { return zi } zi := &zettelData{} ms.idx[zid] = zi return zi } func (ms *mapStore) DeleteZettel(_ context.Context, zid id.Zid) *id.Set { ms.mx.Lock() defer ms.mx.Unlock() return ms.doDeleteZettel(zid) } func (ms *mapStore) doDeleteZettel(zid id.Zid) *id.Set { // Must only be called if ms.mx is write-locked! zi, ok := ms.idx[zid] if !ok { return nil } ms.deleteDeadSources(zid, zi) toCheck := ms.deleteForwardBackward(zid, zi) for key, mrefs := range zi.otherRefs { ms.removeInverseMeta(zid, key, mrefs.forward) } deleteStrings(ms.words, zi.words, zid) deleteStrings(ms.urls, zi.urls, zid) delete(ms.idx, zid) return toCheck } func (ms *mapStore) deleteDeadSources(zid id.Zid, zi *zettelData) { // Must only be called if ms.mx is write-locked! zi.dead.ForEach(func(ref id.Zid) { if drefs, ok := ms.dead[ref]; ok { if drefs = drefs.Remove(zid); drefs.IsEmpty() { delete(ms.dead, ref) } else { ms.dead[ref] = drefs } } }) } func (ms *mapStore) deleteForwardBackward(zid id.Zid, zi *zettelData) *id.Set { // Must only be called if ms.mx is write-locked! zi.forward.ForEach(func(ref id.Zid) { if fzi, ok := ms.idx[ref]; ok { fzi.backward = fzi.backward.Remove(zid) } }) var toCheck *id.Set zi.backward.ForEach(func(ref id.Zid) { if bzi, ok := ms.idx[ref]; ok { bzi.forward = bzi.forward.Remove(zid) toCheck = toCheck.Add(ref) } }) return toCheck } func (ms *mapStore) removeInverseMeta(zid id.Zid, key string, forward *id.Set) { // Must only be called if ms.mx is write-locked! forward.ForEach(func(ref id.Zid) { bzi, ok := ms.idx[ref] if !ok || bzi.otherRefs == nil { return } bmr, ok := bzi.otherRefs[key] if !ok { return } bmr.backward = bmr.backward.Remove(zid) if !bmr.backward.IsEmpty() || !bmr.forward.IsEmpty() { bzi.otherRefs[key] = bmr } else { delete(bzi.otherRefs, key) if len(bzi.otherRefs) == 0 { bzi.otherRefs = nil } } }) } func deleteStrings(msStringMap stringRefs, curStrings []string, zid id.Zid) { // Must only be called if ms.mx is write-locked! for _, word := range curStrings { refs, ok := msStringMap[word] if !ok { continue } refs = refs.Remove(zid) if refs.IsEmpty() { delete(msStringMap, word) continue } msStringMap[word] = refs } } func (ms *mapStore) Optimize() { ms.mx.Lock() defer ms.mx.Unlock() // No need to optimize ms.idx: is already done via ms.UpdateReferences for _, dead := range ms.dead { dead.Optimize() } for _, s := range ms.words { s.Optimize() } for _, s := range ms.urls { s.Optimize() } } func (ms *mapStore) ReadStats(st *store.Stats) { ms.mx.RLock() st.Zettel = len(ms.idx) st.Words = uint64(len(ms.words)) st.Urls = uint64(len(ms.urls)) ms.mx.RUnlock() ms.mxStats.Lock() st.Updates = ms.updates ms.mxStats.Unlock() } func (ms *mapStore) Dump(w io.Writer) { ms.mx.RLock() defer ms.mx.RUnlock() io.WriteString(w, "=== Dump\n") ms.dumpIndex(w) ms.dumpDead(w) dumpStringRefs(w, "Words", "", "", ms.words) dumpStringRefs(w, "URLs", "[[", "]]", ms.urls) } func (ms *mapStore) dumpIndex(w io.Writer) { if len(ms.idx) == 0 { return } io.WriteString(w, "==== Zettel Index\n") zids := make(id.Slice, 0, len(ms.idx)) for id := range ms.idx { zids = append(zids, id) } zids.Sort() for _, id := range zids { fmt.Fprintln(w, "=====", id) zi := ms.idx[id] if !zi.dead.IsEmpty() { fmt.Fprintln(w, "* Dead:", zi.dead) } dumpSet(w, "* Forward:", zi.forward) dumpSet(w, "* Backward:", zi.backward) otherRefs := make([]string, 0, len(zi.otherRefs)) for k := range zi.otherRefs { otherRefs = append(otherRefs, k) } slices.Sort(otherRefs) for _, k := range otherRefs { fmt.Fprintln(w, "* Meta", k) dumpSet(w, "** Forward:", zi.otherRefs[k].forward) dumpSet(w, "** Backward:", zi.otherRefs[k].backward) } dumpStrings(w, "* Words", "", "", zi.words) dumpStrings(w, "* URLs", "[[", "]]", zi.urls) } } func (ms *mapStore) dumpDead(w io.Writer) { if len(ms.dead) == 0 { return } fmt.Fprintf(w, "==== Dead References\n") zids := make(id.Slice, 0, len(ms.dead)) for id := range ms.dead { zids = append(zids, id) } zids.Sort() for _, id := range zids { fmt.Fprintln(w, ";", id) fmt.Fprintln(w, ":", ms.dead[id]) } } func dumpSet(w io.Writer, prefix string, s *id.Set) { if !s.IsEmpty() { io.WriteString(w, prefix) s.ForEach(func(zid id.Zid) { io.WriteString(w, " ") w.Write(zid.Bytes()) }) fmt.Fprintln(w) } } func dumpStrings(w io.Writer, title, preString, postString string, slice []string) { if len(slice) > 0 { sl := make([]string, len(slice)) copy(sl, slice) slices.Sort(sl) fmt.Fprintln(w, title) for _, s := range sl { fmt.Fprintf(w, "** %s%s%s\n", preString, s, postString) } } } func dumpStringRefs(w io.Writer, title, preString, postString string, srefs stringRefs) { if len(srefs) == 0 { return } fmt.Fprintln(w, "====", title) for _, s := range maps.Keys(srefs) { fmt.Fprintf(w, "; %s%s%s\n", preString, s, postString) fmt.Fprintln(w, ":", srefs[s]) } } |
Deleted box/manager/memstore/memstore.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted box/manager/memstore/refs.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted box/manager/memstore/refs_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to box/manager/store/store.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 contains general index data for storing a zettel index. package store import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package store contains general index data for storing a zettel index. package store import ( "context" |
︙ | ︙ | |||
44 45 46 47 48 49 50 | GetMeta(context.Context, id.Zid) (*meta.Meta, error) // 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. | | < < < < | > > > | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | GetMeta(context.Context, id.Zid) (*meta.Meta, error) // 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 // Optimize removes unneeded space. Optimize() // ReadStats populates st with store statistics. ReadStats(st *Stats) // Dump the content to a Writer. Dump(io.Writer) } |
Changes to box/manager/store/wordset.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 // WordSet contains the set of all words, with the count of their occurrences. type WordSet map[string]int | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package store // WordSet contains the set of all words, with the count of their occurrences. type WordSet map[string]int |
︙ | ︙ |
Changes to box/manager/store/wordset_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // 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_test 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 25 26 27 28 29 30 31 32 33 34 35 36 37 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package store_test import ( "slices" "testing" "zettelstore.de/z/box/manager/store" ) func equalWordList(exp, got []string) bool { if len(exp) != len(got) { return false } if len(got) == 0 { return len(exp) == 0 } slices.Sort(got) for i, w := range exp { if w != got[i] { return false } } return true } |
︙ | ︙ |
Changes to box/manager/store/zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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/zettel/id" "zettelstore.de/z/zettel/meta" ) // ZettelIndex contains all index data of a zettel. type ZettelIndex struct { | > > > | | | | | | | < < | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package store import ( "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // ZettelIndex contains all index data of a zettel. type ZettelIndex struct { Zid id.Zid // zid of the indexed zettel meta *meta.Meta // full metadata backrefs *id.Set // set of back references inverseRefs map[string]*id.Set // references of inverse keys deadrefs *id.Set // set of dead references words WordSet urls WordSet } // NewZettelIndex creates a new zettel index. func NewZettelIndex(m *meta.Meta) *ZettelIndex { return &ZettelIndex{ Zid: m.Zid, meta: m, backrefs: id.NewSet(), inverseRefs: 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.Add(zid) } // AddInverseRef adds a named reference to a zettel. On that zettel, the given // metadata key should point back to the current zettel. func (zi *ZettelIndex) AddInverseRef(key string, zid id.Zid) { if zids, ok := zi.inverseRefs[key]; ok { zids.Add(zid) return |
︙ | ︙ | |||
61 62 63 64 65 66 67 | // 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. | | | | | | | 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 | // 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.Set { return zi.deadrefs } // GetMeta return just the raw metadata. func (zi *ZettelIndex) GetMeta() *meta.Meta { return zi.meta } // GetBackRefs returns all back references as a sorted list. func (zi *ZettelIndex) GetBackRefs() *id.Set { return zi.backrefs } // GetInverseRefs returns all inverse meta references as a map of strings to a sorted list of references func (zi *ZettelIndex) GetInverseRefs() map[string]*id.Set { if len(zi.inverseRefs) == 0 { return nil } result := make(map[string]*id.Set, len(zi.inverseRefs)) for key, refs := range zi.inverseRefs { result[key] = refs } 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 } |
Added box/manager/zidmapper.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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package manager import ( "bytes" "context" "fmt" "maps" "sync" "time" "t73f.de/r/zsc/input" "zettelstore.de/z/zettel/id" ) // zidMapper transforms old-style zettel identifier (14 digits) into new one (4 alphanums). // // Since there are no new-style identifier defined, there is only support for old-style // identifier by checking, whether they are suported as new-style or not. // // This will change in later versions. type zidMapper struct { fetcher zidfetcher defined map[id.Zid]id.ZidN // predefined mapping, constant after creation mx sync.RWMutex // protect toNew ... nextZidN toNew map[id.Zid]id.ZidN // working mapping old->new toOld map[id.ZidN]id.Zid // working mapping new->old nextZidM id.ZidN // next zid for manual hadManual bool nextZidN id.ZidN // next zid for normal zettel } type zidfetcher interface { fetchZids(context.Context) (*id.Set, error) } // NewZidMapper creates a new ZipMapper. func NewZidMapper(fetcher zidfetcher) *zidMapper { defined := map[id.Zid]id.ZidN{ id.Invalid: id.InvalidN, 1: id.MustParseN("0001"), // ZidVersion 2: id.MustParseN("0002"), // ZidHost 3: id.MustParseN("0003"), // ZidOperatingSystem 4: id.MustParseN("0004"), // ZidLicense 5: id.MustParseN("0005"), // ZidAuthors 6: id.MustParseN("0006"), // ZidDependencies 7: id.MustParseN("0007"), // ZidLog 8: id.MustParseN("0008"), // ZidMemory 9: id.MustParseN("0009"), // ZidSx 10: id.MustParseN("000a"), // ZidHTTP 11: id.MustParseN("000b"), // ZidAPI 12: id.MustParseN("000c"), // ZidWebUI 13: id.MustParseN("000d"), // ZidConsole 20: id.MustParseN("000e"), // ZidBoxManager 21: id.MustParseN("000f"), // ZidZettel 22: id.MustParseN("000g"), // ZidIndex 23: id.MustParseN("000h"), // ZidQuery 90: id.MustParseN("000i"), // ZidMetadataKey 92: id.MustParseN("000j"), // ZidParser 96: id.MustParseN("000k"), // ZidStartupConfiguration 100: id.MustParseN("000l"), // ZidRuntimeConfiguration 101: id.MustParseN("000m"), // ZidDirectory 102: id.MustParseN("000n"), // ZidWarnings 10100: id.MustParseN("000s"), // Base HTML Template 10200: id.MustParseN("000t"), // Login Form Template 10300: id.MustParseN("000u"), // List Zettel Template 10401: id.MustParseN("000v"), // Detail Template 10402: id.MustParseN("000w"), // Info Template 10403: id.MustParseN("000x"), // Form Template 10405: id.MustParseN("000y"), // Delete Template 10700: id.MustParseN("000z"), // Error Template 19000: id.MustParseN("000q"), // Sxn Start Code 19990: id.MustParseN("000r"), // Sxn Base Code 20001: id.MustParseN("0010"), // Base CSS 25001: id.MustParseN("0011"), // User CSS 40001: id.MustParseN("000o"), // Generic Emoji 59900: id.MustParseN("000p"), // Sxn Prelude 60010: id.MustParseN("0012"), // zettel 60020: id.MustParseN("0013"), // confguration 60030: id.MustParseN("0014"), // role 60040: id.MustParseN("0015"), // tag 90000: id.MustParseN("0016"), // New Menu 90001: id.MustParseN("0017"), // New Zettel 90002: id.MustParseN("0018"), // New User 90003: id.MustParseN("0019"), // New Tag 90004: id.MustParseN("001a"), // New Role // 100000000, // Manual -> 0020-00yz 9999999996: id.MustParseN("00zw"), // Current ZidMapping, TEMP for v0.19-dev 9999999997: id.MustParseN("00zx"), // ZidSession 9999999998: id.MustParseN("00zy"), // ZidAppDirectory 9999999999: id.MustParseN("00zz"), // ZidMapping 10000000000: id.MustParseN("0100"), // ZidDefaultHome } toNew := maps.Clone(defined) toOld := make(map[id.ZidN]id.Zid, len(toNew)) for o, n := range toNew { if _, found := toOld[n]; found { panic("duplicate predefined zid") } toOld[n] = o } return &zidMapper{ fetcher: fetcher, defined: defined, toNew: toNew, toOld: toOld, nextZidM: id.MustParseN("0020"), hadManual: false, nextZidN: id.MustParseN("0101"), } } // isWellDefined returns true, if the given zettel identifier is predefined // (as stated in the manual), or is part of the manual itself, or is greater than // 19699999999999. func (zm *zidMapper) isWellDefined(zid id.Zid) bool { if _, found := zm.defined[zid]; found || (1000000000 <= zid && zid <= 1099999999) { return true } if _, err := time.Parse("20060102150405", zid.String()); err != nil { return false } return 19700000000000 <= zid } // Warnings returns all zettel identifier with warnings. func (zm *zidMapper) Warnings(ctx context.Context) (*id.Set, error) { allZids, err := zm.fetcher.fetchZids(ctx) if err != nil { return nil, err } warnings := id.NewSet() allZids.ForEach(func(zid id.Zid) { if !zm.isWellDefined(zid) { warnings = warnings.Add(zid) } }) return warnings, nil } func (zm *zidMapper) LookupZidN(zidO id.Zid) (id.ZidN, bool) { if !zidO.IsValid() { panic(zidO) } zm.mx.RLock() zidN, found := zm.toNew[zidO] zm.mx.RUnlock() return zidN, found } func (zm *zidMapper) GetZidN(zidO id.Zid) id.ZidN { if zidN, found := zm.LookupZidN(zidO); found { return zidN } zm.mx.Lock() defer zm.mx.Unlock() // Double check to avoid races if zidN, found := zm.toNew[zidO]; found { return zidN } if 1000000000 <= zidO && zidO <= 1099999999 { if zidO == 1000000000 { zm.hadManual = true } if zm.hadManual { zidN := zm.nextZidM zm.nextZidM++ zm.toNew[zidO] = zidN zm.toOld[zidN] = zidO return zidN } } zidN := zm.nextZidN zm.nextZidN++ zm.toNew[zidO] = zidN zm.toOld[zidN] = zidO return zidN } func (zm *zidMapper) DeleteO(zidO id.Zid) { if _, found := zm.defined[zidO]; found { return } zm.mx.Lock() if zidN, found := zm.toNew[zidO]; found { delete(zm.toNew, zidO) delete(zm.toOld, zidN) if lastZidN := zm.nextZidN - 1; zidN == lastZidN { zm.nextZidN = lastZidN } } zm.mx.Unlock() } // AsBytes returns the current mapping as lines, where each line contains the // old and the new zettel identifier. func (zm *zidMapper) AsBytes() []byte { zm.mx.RLock() defer zm.mx.RUnlock() return zm.asBytes() } func (zm *zidMapper) asBytes() []byte { allZidsO := id.NewSetCap(len(zm.toNew)) for zidO := range zm.toNew { allZidsO = allZidsO.Add(zidO) } var buf bytes.Buffer first := true allZidsO.ForEach(func(zidO id.Zid) { if !first { buf.WriteByte('\n') } first = false zidN := zm.toNew[zidO] buf.WriteString(zidO.String()) buf.WriteByte(' ') buf.WriteString(zidN.String()) }) return buf.Bytes() } // FetchAsBytes fetches all zettel identifier and returns the mapping as lines, // where each line contains the old zid and the new zid. func (zm *zidMapper) FetchAsBytes(ctx context.Context) ([]byte, error) { allZids, err := zm.fetcher.fetchZids(ctx) if err != nil { return nil, err } allZids.ForEach(func(zidO id.Zid) { _ = zm.GetZidN(zidO) }) zm.mx.Lock() defer zm.mx.Unlock() if len(zm.toNew) != allZids.Length() { for zidO, zidN := range zm.toNew { if allZids.Contains(zidO) { continue } delete(zm.toNew, zidO) delete(zm.toOld, zidN) } } return zm.asBytes(), nil } func (zm *zidMapper) parseAndUpdate(content []byte) (err error) { zm.mx.Lock() defer zm.mx.Unlock() inp := input.NewInput(content) for inp.Ch != input.EOS { inp.SkipSpace() pos := inp.Pos zidO := readZidO(inp) if !zidO.IsValid() { inp.SkipToEOL() inp.EatEOL() if err == nil { err = fmt.Errorf("unable to parse old zid: %q", string(inp.Src[pos:inp.Pos])) } continue } inp.SkipSpace() zidN := readZidN(inp) if !zidN.IsValid() { inp.SkipToEOL() inp.EatEOL() if err == nil { err = fmt.Errorf("unable to parse new zid: %q", string(inp.Src[pos:inp.Pos])) } continue } inp.SkipToEOL() inp.EatEOL() if oldZidN, found := zm.toNew[zidO]; found { if oldZidN != zidN { err = fmt.Errorf("old zid %v already mapped to %v, overwrite: %v", zidO, oldZidN, zidN) } continue } zm.toNew[zidO] = zidN zm.toOld[zidN] = zidO zm.nextZidN = max(zm.nextZidN, zidN+1) } return err } func readZidO(inp *input.Input) id.Zid { pos := inp.Pos for '0' <= inp.Ch && inp.Ch <= '9' { inp.Next() } zidO, _ := id.Parse(string(inp.Src[pos:inp.Pos])) return zidO } func readZidN(inp *input.Input) id.ZidN { pos := inp.Pos for ('0' <= inp.Ch && inp.Ch <= '9') || ('a' <= inp.Ch && inp.Ch <= 'z') || ('A' <= inp.Ch && inp.Ch <= 'Z') { inp.Next() } zidN, _ := id.ParseN(string(inp.Src[pos:inp.Pos])) return zidN } |
Changes to box/membox/membox.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 membox stores zettel volatile in main memory. package membox import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package membox stores zettel volatile in main memory. package membox import ( "context" |
︙ | ︙ | |||
47 48 49 50 51 52 53 | maxZettel int maxBytes int mx sync.RWMutex // Protects the following fields zettel map[id.Zid]zettel.Zettel curBytes int } | | | | 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | maxZettel int maxBytes int mx sync.RWMutex // Protects the following fields zettel map[id.Zid]zettel.Zettel curBytes int } func (mb *memBox) notifyChanged(zid id.Zid, reason box.UpdateReason) { if chci := mb.cdata.Notify; chci != nil { chci <- box.UpdateInfo{Box: mb, Reason: reason, Zid: zid} } } func (mb *memBox) Location() string { return mb.u.String() } |
︙ | ︙ | |||
108 109 110 111 112 113 114 | } meta := zettel.Meta.Clone() meta.Zid = zid zettel.Meta = meta mb.zettel[zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() | > | | 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | } meta := zettel.Meta.Clone() meta.Zid = zid zettel.Meta = meta mb.zettel[zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() mb.notifyChanged(zid, box.OnZettel) mb.log.Trace().Zid(zid).Msg("CreateZettel") return zid, nil } func (mb *memBox) GetZettel(_ context.Context, zid id.Zid) (zettel.Zettel, error) { mb.mx.RLock() z, ok := mb.zettel[zid] |
︙ | ︙ | |||
193 194 195 196 197 198 199 | return box.ErrCapacity } zettel.Meta = m mb.zettel[m.Zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | 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 | return box.ErrCapacity } zettel.Meta = m mb.zettel[m.Zid] = zettel mb.curBytes = newBytes mb.mx.Unlock() mb.notifyChanged(m.Zid, box.OnZettel) mb.log.Trace().Msg("UpdateZettel") return nil } func (mb *memBox) CanDeleteZettel(_ context.Context, zid id.Zid) bool { mb.mx.RLock() _, ok := mb.zettel[zid] mb.mx.RUnlock() return ok } 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.ErrZettelNotFound{Zid: zid} } delete(mb.zettel, zid) mb.curBytes -= oldZettel.Length() mb.mx.Unlock() mb.notifyChanged(zid, box.OnDelete) mb.log.Trace().Msg("DeleteZettel") return nil } func (mb *memBox) ReadStats(st *box.ManagedBoxStats) { st.ReadOnly = false mb.mx.RLock() st.Zettel = len(mb.zettel) mb.mx.RUnlock() mb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") } |
Changes to box/notify/directory.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 notify import ( "errors" "fmt" "path/filepath" "regexp" | > > > < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package notify import ( "errors" "fmt" "path/filepath" "regexp" "sync" "zettelstore.de/z/box" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/parser" "zettelstore.de/z/query" |
︙ | ︙ | |||
179 180 181 182 183 184 185 | if ds.entries == nil { return ds.logMissingEntry("update") } ds.entries[entry.Zid] = &entry return nil } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | if ds.entries == nil { return ds.logMissingEntry("update") } ds.entries[entry.Zid] = &entry return nil } // DeleteDirEntry removes a entry from the directory. func (ds *DirService) DeleteDirEntry(zid id.Zid) error { ds.mx.Lock() defer ds.mx.Unlock() if ds.entries == nil { return ds.logMissingEntry("delete") } |
︙ | ︙ | |||
253 254 255 256 257 258 259 | return nil, false } switch ev.Op { case Error: newEntries = nil if state != DsMissing { | | | 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | return nil, false } switch ev.Op { case Error: newEntries = nil if state != DsMissing { ds.log.Error().Err(ev.Err).Msg("Notifier confused") } case Make: newEntries = make(entrySet) case List: if ev.Name == "" { zids := getNewZids(newEntries) ds.mx.Lock() |
︙ | ︙ | |||
284 285 286 287 288 289 290 | ds.log.Error().Str("path", ds.dirPath).Msg("Zettel directory missing") return nil, true case Update: ds.mx.Lock() zid := ds.onUpdateFileEvent(ds.entries, ev.Name) ds.mx.Unlock() if zid != id.Invalid { | | | | | | | | 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 | ds.log.Error().Str("path", ds.dirPath).Msg("Zettel directory missing") return nil, true case Update: ds.mx.Lock() zid := ds.onUpdateFileEvent(ds.entries, ev.Name) ds.mx.Unlock() if zid != id.Invalid { ds.notifyChange(zid, box.OnZettel) } case Delete: ds.mx.Lock() zid := ds.onDeleteFileEvent(ds.entries, ev.Name) ds.mx.Unlock() if zid != id.Invalid { ds.notifyChange(zid, box.OnDelete) } default: ds.log.Error().Str("event", fmt.Sprintf("%v", ev)).Msg("Unknown zettel notification event") } return newEntries, true } func getNewZids(entries entrySet) id.Slice { zids := make(id.Slice, 0, len(entries)) for zid := range entries { zids = append(zids, zid) } return zids } func (ds *DirService) onCreateDirectory(zids id.Slice, prevEntries entrySet) { for _, zid := range zids { ds.notifyChange(zid, box.OnZettel) delete(prevEntries, zid) } // These were previously stored, by are not found now. // Notify system that these were deleted, e.g. for updating the index. for zid := range prevEntries { ds.notifyChange(zid, box.OnDelete) } } func (ds *DirService) onDestroyDirectory() { ds.mx.Lock() entries := ds.entries ds.entries = nil ds.state = DsMissing ds.mx.Unlock() for zid := range entries { ds.notifyChange(zid, box.OnDelete) } } var validFileName = regexp.MustCompile(`^(\d{14})`) func matchValidFileName(name string) []string { return validFileName.FindStringSubmatch(name) |
︙ | ︙ | |||
369 370 371 372 373 374 375 | zid := seekZid(name) if zid == id.Invalid { return id.Invalid } entry := fetchdirEntry(entries, zid) dupName1, dupName2 := ds.updateEntry(entry, name) if dupName1 != "" { | | | | 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 | zid := seekZid(name) if zid == id.Invalid { return id.Invalid } entry := fetchdirEntry(entries, zid) dupName1, dupName2 := ds.updateEntry(entry, name) if dupName1 != "" { ds.log.Info().Str("name", dupName1).Msg("Duplicate content (is ignored)") if dupName2 != "" { ds.log.Info().Str("name", dupName2).Msg("Duplicate content (is ignored)") } return id.Invalid } return zid } func (ds *DirService) onDeleteFileEvent(entries entrySet, name string) id.Zid { |
︙ | ︙ | |||
598 599 600 601 602 603 604 | newLen := len(newExt) if oldLen != newLen { return newLen < oldLen } return newExt < oldExt } | | | | | 570 571 572 573 574 575 576 577 578 579 580 581 582 | newLen := len(newExt) if oldLen != newLen { return newLen < oldLen } return newExt < oldExt } func (ds *DirService) notifyChange(zid id.Zid, reason box.UpdateReason) { if chci := ds.infos; chci != nil { ds.log.Trace().Zid(zid).Uint("reason", uint64(reason)).Msg("notifyChange") chci <- box.UpdateInfo{Box: ds.box, Reason: reason, Zid: zid} } } |
Changes to box/notify/directory_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 notify import ( "testing" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package notify import ( "testing" |
︙ | ︙ |
Changes to box/notify/entry.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 notify import ( "path/filepath" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package notify import ( "path/filepath" "t73f.de/r/zsc/api" "zettelstore.de/z/parser" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) const ( |
︙ | ︙ | |||
55 56 57 58 59 60 61 | if contentName := e.ContentName; contentName != "" { if !extIsMetaAndContent(e.ContentExt) && e.MetaName == "" { e.MetaName = e.calcBaseName(contentName) } return } | | | 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | if contentName := e.ContentName; contentName != "" { if !extIsMetaAndContent(e.ContentExt) && e.MetaName == "" { e.MetaName = e.calcBaseName(contentName) } return } syntax := m.GetDefault(api.KeySyntax, meta.DefaultSyntax) ext := calcContentExt(syntax, m.YamlSep, getZettelFileSyntax) metaName := e.MetaName eimc := extIsMetaAndContent(ext) if eimc { if metaName != "" { ext = contentExtWithMeta(syntax, content) } |
︙ | ︙ |
Changes to box/notify/fsdir.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 notify import ( "os" "path/filepath" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package notify import ( "os" "path/filepath" |
︙ | ︙ | |||
51 52 53 54 55 56 57 | log.Error(). Str("parentDir", absParentDir).Err(errParent). Str("path", absPath).Err(err). Msg("Unable to access Zettel directory and its parent directory") watcher.Close() return nil, err } | < | | | | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | log.Error(). Str("parentDir", absParentDir).Err(errParent). Str("path", absPath).Err(err). Msg("Unable to access Zettel directory and its parent directory") watcher.Close() return nil, err } log.Info().Str("parentDir", absParentDir).Err(errParent). Msg("Parent of Zettel directory cannot be supervised") log.Info().Str("path", absPath). Msg("Zettelstore might not detect a deletion or movement of the Zettel directory") } else if err != nil { // Not a problem, if container is not available. It might become available later. log.Info().Err(err).Str("path", absPath).Msg("Zettel directory currently not available") } fsdn := &fsdirNotifier{ log: log, events: make(chan Event), refresh: make(chan struct{}), done: make(chan struct{}), |
︙ | ︙ |
Changes to box/notify/helper.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 notify import ( "archive/zip" "os" "zettelstore.de/z/logger" ) | > > > < < < < < | 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) 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package notify import ( "archive/zip" "os" "zettelstore.de/z/logger" ) // EntryFetcher return a list of (file) names of an directory. type EntryFetcher interface { Fetch() ([]string, error) } type dirPathFetcher struct { dirPath string |
︙ | ︙ |
Changes to box/notify/notify.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 notify provides some notification services to be used by box services. package notify import "fmt" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package notify provides some notification services to be used by box services. package notify import "fmt" |
︙ | ︙ |
Changes to box/notify/simpledir.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 notify import ( "path/filepath" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package notify import ( "path/filepath" |
︙ | ︙ |
Changes to cmd/cmd_file.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 cmd import ( "context" "flag" "fmt" "io" "os" | > > > | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package cmd import ( "context" "flag" "fmt" "io" "os" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "zettelstore.de/z/encoder" "zettelstore.de/z/parser" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // ---------- 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(), zettel.Zettel{ Meta: m, Content: zettel.NewContent(inp.Src[inp.Pos:]), }, m.GetDefault(api.KeySyntax, meta.DefaultSyntax), nil, ) encdr := encoder.Create(api.Encoder(enc), &encoder.CreateParameter{Lang: m.GetDefault(api.KeyLang, api.ValueLangEN)}) if encdr == nil { fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc) return 2, nil } |
︙ | ︙ |
Changes to cmd/cmd_password.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 cmd import ( "flag" "fmt" "os" "golang.org/x/term" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package cmd import ( "flag" "fmt" "os" "golang.org/x/term" "t73f.de/r/zsc/api" "zettelstore.de/z/auth/cred" "zettelstore.de/z/zettel/id" ) // ---------- Subcommand: password ------------------------------------------- func cmdPassword(fs *flag.FlagSet) (int, error) { |
︙ | ︙ |
Changes to cmd/cmd_run.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 cmd import ( "context" "flag" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package cmd import ( "context" "flag" |
︙ | ︙ | |||
68 69 70 71 72 73 74 | ucQuery.SetEvaluate(&ucEvaluate) ucTagZettel := usecase.NewTagZettel(protectedBoxManager, &ucQuery) ucRoleZettel := usecase.NewRoleZettel(protectedBoxManager, &ucQuery) ucListSyntax := usecase.NewListSyntax(protectedBoxManager) ucListRoles := usecase.NewListRoles(protectedBoxManager) ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager) | < < < | 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 | ucQuery.SetEvaluate(&ucEvaluate) ucTagZettel := usecase.NewTagZettel(protectedBoxManager, &ucQuery) ucRoleZettel := usecase.NewRoleZettel(protectedBoxManager, &ucQuery) ucListSyntax := usecase.NewListSyntax(protectedBoxManager) ucListRoles := usecase.NewListRoles(protectedBoxManager) ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager) ucRefresh := usecase.NewRefresh(logUc, protectedBoxManager) ucReIndex := usecase.NewReIndex(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.AddListRoute('c', server.MethodGet, wui.MakeGetZettelFromListHandler(&ucQuery, &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(ucGetZettel, ucGetAllZettel)) webSrv.AddZettelRoute('d', server.MethodPost, wui.MakePostDeleteZettelHandler(&ucDelete)) |
︙ | ︙ | |||
120 121 122 123 124 125 126 | webSrv.AddListRoute('x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh)) webSrv.AddListRoute('z', server.MethodGet, a.MakeQueryHandler(&ucQuery, &ucTagZettel, &ucRoleZettel, &ucReIndex)) webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetZettelHandler(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)) | < | 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | webSrv.AddListRoute('x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh)) webSrv.AddListRoute('z', server.MethodGet, a.MakeQueryHandler(&ucQuery, &ucTagZettel, &ucRoleZettel, &ucReIndex)) webSrv.AddZettelRoute('z', server.MethodGet, a.MakeGetZettelHandler(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)) } if authManager.WithAuth() { webSrv.SetUserRetriever(usecase.NewGetUserByZid(boxManager)) } } type getUserImpl struct{} func (*getUserImpl) GetUser(ctx context.Context) *meta.Meta { return server.GetUser(ctx) } |
Changes to cmd/command.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 cmd import ( "flag" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package cmd import ( "flag" "t73f.de/r/zsc/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.
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 cmd import ( "crypto/sha256" "flag" "fmt" "net" "net/url" "os" "runtime/debug" "strconv" "strings" "time" | > > > | > < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package cmd import ( "crypto/sha256" "flag" "fmt" "net" "net/url" "os" "runtime/debug" "strconv" "strings" "time" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "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/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ | |||
230 231 232 233 234 235 236 | return err == nil } func setConfigValue(err error, subsys kernel.Service, key string, val any) error { if err == nil { err = kernel.Main.SetConfig(subsys, key, fmt.Sprint(val)) if err != nil { | | | 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | return err == nil } func setConfigValue(err error, subsys kernel.Service, key string, val any) error { if err == nil { err = kernel.Main.SetConfig(subsys, key, fmt.Sprint(val)) if err != nil { kernel.Main.GetKernelLogger().Error().Str("key", key).Str("value", fmt.Sprint(val)).Err(err).Msg("Unable to set configuration") } } return err } func executeCommand(name string, args ...string) int { command, ok := Get(name) |
︙ | ︙ |
Changes to cmd/register.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 cmd provides command generic functions. package cmd // Mention all needed encoders, parsers and stores to have them registered. import ( | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package cmd provides command generic functions. package cmd // Mention all needed encoders, parsers and stores to have them registered. import ( |
︙ | ︙ |
Changes to cmd/zettelstore/main.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 main is the starting point for the zettelstore command. package main import ( "os" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package main is the starting point for the zettelstore command. package main import ( "os" |
︙ | ︙ |
Changes to collect/collect.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 collect provides functions to collect items from a syntax tree. package collect import "zettelstore.de/z/ast" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package collect provides functions to collect items from a syntax tree. package collect import "zettelstore.de/z/ast" |
︙ | ︙ |
Changes to collect/collect_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 collect_test provides some unit test for collectors. package collect_test import ( "testing" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package collect_test provides some unit test for collectors. package collect_test import ( "testing" |
︙ | ︙ |
Changes to collect/order.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 collect provides functions to collect items from a syntax tree. package collect import "zettelstore.de/z/ast" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package collect provides functions to collect items from a syntax tree. package collect import "zettelstore.de/z/ast" |
︙ | ︙ |
Deleted collect/split.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to config/config.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 config provides functions to retrieve runtime configuration data. package config import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package config provides functions to retrieve runtime configuration data. package config import ( "context" |
︙ | ︙ |
Changes to docs/development/00010000000000.zettel.
1 2 3 4 5 | id: 00010000000000 title: Developments Notes role: zettel syntax: zmk created: 00010101000000 | | > | 1 2 3 4 5 6 7 8 9 10 11 | id: 00010000000000 title: Developments Notes role: zettel syntax: zmk created: 00010101000000 modified: 20231218182020 * [[Required Software|20210916193200]] * [[Fuzzing tests|20221026184300]] * [[Checklist for Release|20210916194900]] * [[Development tools|20231218181900]] |
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 24 25 26 27 28 | id: 20210916193200 title: Required Software role: zettel syntax: zmk created: 20210916193200 modified: 20231213194509 The following software must be installed: * A current, supported [[release of Go|https://go.dev/doc/devel/release]], * [[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 ``` The internal build tool need the following software. It can be installed / updated via the build tool itself: ``go run tools/devtools/devtools.go``. Otherwise you can install the software by hand: * [[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``, |
Changes to docs/development/20210916194900.zettel.
1 2 3 4 5 | id: 20210916194900 title: Checklist for Release role: zettel syntax: zmk created: 20210916194900 | | | | | | 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 | id: 20210916194900 title: Checklist for Release role: zettel syntax: zmk created: 20210916194900 modified: 20231213194631 # 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`` # Clean up your Go workspace: #* ``go run tools/clean/clean.go`` (alternatively: ``make clean``). # All internal tests must succeed: #* ``go run tools/check/check.go -r`` (alternatively: ``make relcheck``). # The API tests must succeed on every development platform: #* ``go run tools/testapi/testapi.go`` (alternatively: ``make api``). # Run [[linkchecker|https://linkchecker.github.io/linkchecker/]] with the manual: #* ``go run -race cmd/zettelstore/main.go run -d docs/manual`` #* ``linkchecker http://127.0.0.1:23123 2>&1 | tee lc.txt`` #* Check all ""Error: 404 Not Found"" #* Check all ""Error: 403 Forbidden"": allowed for endpoint ''/p'' with encoding ''html'' for those zettel that are accessible only in ''expert-mode''. #* Try to resolve other error messages and warnings #* Warnings about empty content can be ignored |
︙ | ︙ | |||
40 41 42 43 44 45 46 | # Disable Fossil autosync mode: #* ``fossil setting autosync off`` # Commit the new release version: #* ``fossil commit --tag release --tag vVERSION -m "Version VERSION"`` #* **Important:** the tag must follow the given pattern, e.g. ''v0.0.15''. Otherwise client will not be able to import ''zettelkasten.de/z''. # Clean up your Go workspace: | | | | 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | # Disable Fossil autosync mode: #* ``fossil setting autosync off`` # Commit the new release version: #* ``fossil commit --tag release --tag vVERSION -m "Version VERSION"`` #* **Important:** the tag must follow the given pattern, e.g. ''v0.0.15''. Otherwise client will not be able to import ''zettelkasten.de/z''. # Clean up your Go workspace: #* ``go run tools/clean/clean.go`` (alternatively: ``make clean``). # Create the release: #* ``go run tools/build/build.go release`` (alternatively: ``make release``). # Remove previous executables: #* ``fossil uv remove --glob '*-PREVVERSION*'`` # Add executables for release: #* ``cd releases`` #* ``fossil uv add *.zip`` #* ``cd ..`` #* Synchronize with main repository: #* ``fossil sync -u`` # Enable autosync: #* ``fossil setting autosync on`` |
Added docs/development/20231218181900.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 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 | id: 20231218181900 title: Development tools role: zettel syntax: zmk created: 20231218181956 modified: 20231218184500 The source code contains some tools to assist the development of Zettelstore. These are located in the ''tools'' directory. Most tool support the generic option ``-v``, which log internal activities. Some of the tools can be called easier by using ``make``, that reads in a provided ''Makefile''. === Check The ""check"" tool automates some testing activities. It is called via the command line: ``` # go run tools/check/check.go ``` There is an additional option ``-r`` to check in advance of a release. The following checks are executed: * Execution of unit tests, like ``go test ./...`` * Analyze the source code for general problems, as in ``go vet ./...`` * Tries to find shadowed variable, via ``shadow ./...`` * Performs some additional checks on the source code, via ``staticcheck ./...`` * Checks the usage of function parameters and usage of return values, via ``unparam ./...``. In case the option ''-r'' is set, the check includes exported functions and internal tests. * In case option ''-r'' is set, the source code is checked against the vulnerability database, via ``govulncheck ./...`` Please note, that most of the tools above are not automatically installed in a standard Go distribution. Use the command ""devtools"" to install them. === Devtools The following command installs all needed tools: ``` # go run tooles/devtools/devtools.go ``` It will also automatically update these tools. === TestAPI The following command will perform some high-level tests: ```sh # go run tools/testapi/testapi.go ``` Basically, a Zettelstore will be started and then API calls will be made to simulate some typical activities with the Zettelstore. If a Zettelstore is already running on port 23123, this Zettelstore will be used instead. Even if the API test should clean up later, some zettel might stay created if a test fails. This feature is used, if you want to have more control on the running Zettelstore. You should start it with the following command: ```sh # go run -race cmd/zettelstore/main.go run -c testdata/testbox/19700101000000.zettel ``` This allows you to debug failing API tests. === HTMLlint The following command will check the generated HTML code for validity: ```sh # go run tools/htmllint/htmllint.go ``` In addition, you might specify the URL od a running Zettelstore. Otherwise ''http://localhost:23123'' is used. This command fetches first the list of all zettel. This list is used to check the generated HTML code (''ZID'' is the paceholder for the zettel identification): * Check all zettel HTML encodings, via the path ''/z/ZID?enc=html&part=zettel'' * Check all zettel web views, via the path ''/h/ZID'' * The info page of all zettel is checked, via path ''/i/ZID'' * A subset of max. 100 zettel will be checked for the validity of their edit page, via ''/e/ZID'' * 10 random zettel are checked for a valid create form, via ''/c/ZID'' * A maximum of 200 random zettel are checked for a valid delete dialog, via ''/d/ZID'' Depending on the selected Zettelstore, the command might take a long time. You can shorten the time, if you disable any zettel query in the footer. === Build The ""build"" tool allows to build the software, either for tests or for a release. The following command will create a Zettelstore executable for the architecture of the current computer: ```sh # go tools/build/build.go build ``` You will find the executable in the ''bin'' directory. A full release will be build in the directory ''releases'', containing ZIP files for the computer architectures ""Linux/amd64"", ""Linux/arm"", ""MacOS/arm64"", ""MacOS/amd64"", and ""Windows/amd64"". In addition, the manual is also build as a ZIP file: ```sh # go run tools/build/build.go release ``` If you just want the ZIP file with the manual, please use: ```sh # go run tools/build/build.go manual ``` In case you want to check the version of the Zettelstore to be build, use: ```sh # go run tools/build/build.go version ``` === Clean To remove the directories ''bin'' and ''releases'', as well as all cached Go libraries used by Zettelstore, execute: ```sh # go run tools/clean/clean.go ``` Internally, the following commands are executed ```sh # rm -rf bin releases # go clean ./... # go clean -cache -modcache -testcache ``` |
Changes to docs/manual/00000000000100.zettel.
1 2 3 4 | id: 00000000000100 title: Zettelstore Runtime Configuration role: configuration syntax: none | | | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00000000000100 title: Zettelstore Runtime Configuration role: configuration syntax: none created: 20210126175322 default-copyright: (c) 2020-present by Detlef Stern <ds@zettelstore.de> default-license: EUPL-1.2-or-later default-visibility: public footer-zettel: 00001000000100 home-zettel: 00001000000000 modified: 20221205173642 site-name: Zettelstore Manual |
︙ | ︙ |
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 | id: 00001000000000 title: Zettelstore Manual role: manual tags: #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20231125185455 show-back-links: false * [[Introduction|00001001000000]] * [[Design goals|00001002000000]] * [[Installation|00001003000000]] * [[Configuration|00001004000000]] |
︙ | ︙ |
Added docs/manual/00001000000002.zettel.
> > > > > > > | 1 2 3 4 5 6 7 | id: 00001000000002 title: manual role: role syntax: zmk created: 20231128184200 Zettel with the role ""manual"" contain the manual of the zettelstore. |
Changes to docs/manual/00001001000000.zettel.
1 2 3 4 5 6 | id: 00001001000000 title: Introduction to the Zettelstore role: manual tags: #introduction #manual #zettelstore syntax: zmk | > > < | < < | < | | < | | < < < | < | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | id: 00001001000000 title: Introduction to the Zettelstore role: manual tags: #introduction #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20240710184612 [[Personal knowledge management|https://en.wikipedia.org/wiki/Personal_knowledge_management]] involves collecting, classifying, storing, searching, retrieving, assessing, evaluating, and sharing knowledge as a daily activity. It's done by most individuals, not necessarily as part of their main business. It's essential for knowledge workers, such as students, researchers, lecturers, software developers, scientists, engineers, architects, etc. Many hobbyists build up a significant amount of knowledge, even if they do not need to think for a living. Personal knowledge management can be seen as a prerequisite for many kinds of collaboration. Zettelstore is software that collects and relates your notes (""zettel"") to represent and enhance your knowledge, supporting the ""[[Zettelkasten method|https://en.wikipedia.org/wiki/Zettelkasten]]"". The method is based on creating many individual notes, each with one idea or piece of information, that is related to each other. Since knowledge is typically built up gradually, one major focus is a long-term store of these notes, hence the name ""Zettelstore"". |
Changes to docs/manual/00001003000000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003000000 title: Installation of the Zettelstore software role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20220119145756 === The curious user You just want to check out the Zettelstore software * Grab the appropriate executable and copy it into any directory * Start the Zettelstore software, e.g. with a double click[^On Windows and macOS, the operating system tries to protect you from possible malicious software. If you encounter problem, please take a look on the [[Troubleshooting|00001018000000]]Â page.] | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001003000000 title: Installation of the Zettelstore software role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20220119145756 === The curious user You just want to check out the Zettelstore software * Grab the appropriate executable and copy it into any directory * Start the Zettelstore software, e.g. with a double click[^On Windows and macOS, the operating system tries to protect you from possible malicious software. If you encounter problem, please take a look on the [[Troubleshooting|00001018000000]]Â page.] |
︙ | ︙ |
Changes to docs/manual/00001003300000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003300000 title: Zettelstore installation for the intermediate user role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20220114175754 You already tried the Zettelstore software and now you want to use it permanently. Zettelstore should start automatically when you log into your computer. * Grab the appropriate executable and copy it into the appropriate directory * If you want to place your zettel into another directory, or if you want more than one [[Zettelstore box|00001004011200]], or if you want to [[enable authentication|00001010040100]], or if you want to tweak your Zettelstore in some other way, create an appropriate [[startup configuration file|00001004010000]]. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001003300000 title: Zettelstore installation for the intermediate user role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20211125191727 modified: 20220114175754 You already tried the Zettelstore software and now you want to use it permanently. Zettelstore should start automatically when you log into your computer. * Grab the appropriate executable and copy it into the appropriate directory * If you want to place your zettel into another directory, or if you want more than one [[Zettelstore box|00001004011200]], or if you want to [[enable authentication|00001010040100]], or if you want to tweak your Zettelstore in some other way, create an appropriate [[startup configuration file|00001004010000]]. |
︙ | ︙ |
Changes to docs/manual/00001003305000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003305000 title: Enable Zettelstore to start automatically on Windows role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20220218125541 Windows is a complicated beast. There are several ways to automatically start Zettelstore. === Startup folder One way is to use the [[autostart folder|https://support.microsoft.com/en-us/windows/add-an-app-to-run-automatically-at-startup-in-windows-10-150da165-dcd9-7230-517b-cf3c295d89dd]]. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001003305000 title: Enable Zettelstore to start automatically on Windows role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20211125191727 modified: 20220218125541 Windows is a complicated beast. There are several ways to automatically start Zettelstore. === Startup folder One way is to use the [[autostart folder|https://support.microsoft.com/en-us/windows/add-an-app-to-run-automatically-at-startup-in-windows-10-150da165-dcd9-7230-517b-cf3c295d89dd]]. |
︙ | ︙ |
Changes to docs/manual/00001003310000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003310000 title: Enable Zettelstore to start automatically on macOS role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20220119124635 There are several ways to automatically start Zettelstore. * [[Login Items|#login-items]] * [[Launch Agent|#launch-agent]] | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001003310000 title: Enable Zettelstore to start automatically on macOS role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20220114181521 modified: 20220119124635 There are several ways to automatically start Zettelstore. * [[Login Items|#login-items]] * [[Launch Agent|#launch-agent]] |
︙ | ︙ |
Changes to docs/manual/00001003315000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003315000 title: Enable Zettelstore to start automatically on Linux role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20220307104944 Since there is no such thing as the one Linux, there are too many different ways to automatically start Zettelstore. * One way is to interpret your Linux desktop system as a server and use the [[recipe to install Zettelstore on a server|00001003600000]]. ** See below for a lighter alternative. * If you are using the [[Gnome Desktop|https://www.gnome.org/]], you could use the tool [[Tweak|https://wiki.gnome.org/action/show/Apps/Tweaks]] (formerly known as ""GNOME Tweak Tool"" or just ""Tweak Tool""). | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001003315000 title: Enable Zettelstore to start automatically on Linux role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20220114181521 modified: 20220307104944 Since there is no such thing as the one Linux, there are too many different ways to automatically start Zettelstore. * One way is to interpret your Linux desktop system as a server and use the [[recipe to install Zettelstore on a server|00001003600000]]. ** See below for a lighter alternative. * If you are using the [[Gnome Desktop|https://www.gnome.org/]], you could use the tool [[Tweak|https://wiki.gnome.org/action/show/Apps/Tweaks]] (formerly known as ""GNOME Tweak Tool"" or just ""Tweak Tool""). |
︙ | ︙ |
Changes to docs/manual/00001003600000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001003600000 title: Installation of Zettelstore on a server role: manual tags: #installation #manual #zettelstore syntax: zmk modified: 20211125185833 You want to provide a shared Zettelstore that can be used from your various devices. Installing Zettelstore as a Linux service is not that hard. Grab the appropriate executable and copy it into the appropriate directory: ```sh | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001003600000 title: Installation of Zettelstore on a server role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20211125191727 modified: 20211125185833 You want to provide a shared Zettelstore that can be used from your various devices. Installing Zettelstore as a Linux service is not that hard. Grab the appropriate executable and copy it into the appropriate directory: ```sh |
︙ | ︙ |
Changes to docs/manual/00001004000000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004000000 title: Configuration of Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20210510153233 There are some levels to change the behavior and/or the appearance of Zettelstore. # The first level is the way to start Zettelstore services and to manage it via command line (and, in part, via a graphical user interface). #* [[Command line parameters|00001004050000]] # As an intermediate user, you usually want to have more control over how Zettelstore is started. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004000000 title: Configuration of Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20210510153233 There are some levels to change the behavior and/or the appearance of Zettelstore. # The first level is the way to start Zettelstore services and to manage it via command line (and, in part, via a graphical user interface). #* [[Command line parameters|00001004050000]] # As an intermediate user, you usually want to have more control over how Zettelstore is started. |
︙ | ︙ |
Changes to docs/manual/00001004010000.zettel.
1 2 3 4 5 6 | id: 00001004010000 title: Zettelstore startup configuration 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 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 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20240710183532 The configuration file, specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup options. These cannot be stored in a [[configuration zettel|00001004020000]] because they are needed before Zettelstore can start or because of security reasons. For example, Zettelstore needs to know in advance on which network address it must listen or where zettel are stored. An attacker that is able to change the owner can do anything. Therefore, only the owner of the computer on which Zettelstore runs can change this information. The file for startup configuration must be created via a text editor in advance. The syntax of the configuration file is the same as for any zettel metadata. The following keys are supported: ; [!admin-port|''admin-port''] : Specifies the TCP port through which you can reach the [[administrator console|00001004100000]]. A value of ""0"" (the default) disables it. The administrator console will only be enabled if Zettelstore is started with the [[''run'' sub-command|00001004051000]]. On most operating systems, the value must be greater than ""1024"" unless you start Zettelstore with the full privileges of a system administrator (which is not recommended). Default: ""0"" ; [!asset-dir|''asset-dir''] : Allows to specify a directory whose files are allowed be transferred directly with the help of the web server. The URL prefix for these files is ''/assets/''. You can use this if you want to transfer files that are too large for a zettel, such as presentation, PDF, music or video files. Files within the given directory will not be managed by Zettelstore.[^They will be managed by Zettelstore just in the very special case that the directory is one of the configured [[boxes|#box-uri-x]].] If you specify only the URL prefix in your web client, the contents of the directory are listed. To avoid this, create an empty file in the directory named ""index.html"". Default: """", no asset directory is set, the URL prefix ''/assets/'' is invalid. ; [!base-url|''base-url''] : Sets the absolute base URL for the service. Note: [[''url-prefix''|#url-prefix]] must be the suffix of ''base-url'', otherwise the web service will not start. Default: ""http://127.0.0.1:23123/"". ; [!box-uri-x|''box-uri-X''], where __X__ is a number greater or equal to one : Specifies a [[box|00001004011200]] where zettel are stored. During startup, __X__ is incremented, starting with one, until no key is found. This allows to configuring than one box. If no ''box-uri-1'' key is given, the overall effect will be the same as if only ''box-uri-1'' was specified with the value ""dir://.zettel"". In this case, even a key ''box-uri-2'' will be ignored. ; [!debug-mode|''debug-mode''] : If set to [[true|00001006030500]], allows to debug the Zettelstore software (mostly used by the developers). Disables any timeout values of the internal web server and does not send some security-related data. Sets [[''log-level''|#log-level]] to ""debug"". Do not enable it for a production server. Default: ""false"" ; [!default-dir-box-type|''default-dir-box-type''] : Specifies the default value for the (sub-)type of [[directory boxes|00001004011400#type]], in which Zettel are typically stored. Default: ""notify"" ; [!insecure-cookie|''insecure-cookie''] : Must be set to [[true|00001006030500]] if authentication is enabled and Zettelstore is not accessible via HTTPS (but via HTTP). Otherwise web browsers are free to ignore the authentication cookie. Default: ""false"" ; [!insecure-html|''insecure-html''] : Allows to use HTML, e.g. within supported markup languages, even if this might introduce security-related problems. However, HTML containing the ``<script>`` or the ``<iframe>`` tag is always ignored. But due to ""clever"" ways of combining HTML, CSS, JavaScript, there might be some negative security consequences. Please be aware of this! Allowed values: ""html"" (allow zettel with [[syntax ""html""|00001008000000#html]]), ""markdown"" (""html"", plus allow inline HTML for Markdown markup only), ""zettelmarkup"" (""markdown"", plus allow inline HTML for Zettelmarkup). Any other value is interpreted as ""secure"". Default: ""secure"". ; [!listen-addr|''listen-addr''] : Configures the network address, where the Zettelstore service is listening for requests. The syntax is: ''[NETWORKIP]:PORT'', where ''NETWORKIP'' is the IP address of the networking interface (or something like ""0.0.0.0"" if you want to listen on all network interfaces), and ''PORT'' is the TCP port. Default value: ""127.0.0.1:23123"" ; [!log-level|''log-level''] : Specify the [[logging level|00001004059700]] for the whole application or for a given (internal) service, overwriting the level ""debug"" set by configuration [[''debug-mode''|#debug-mode]]. Can be changed at runtime, even for specific internal services, with the ''log-level'' command of the [[administrator console|00001004101000#log-level]]. Several specifications are separated by the semicolon character (""'';''"", U+003B). Each consists of an optional service name, together with the colon character (""'':''"", U+003A), followed by the logging level. Default: ""info"". Examples: ""error"" will produce just error messages (e.g. no ""info"" messages). ""error;web:debug"" will emit debugging messages for the web component of Zettelstore while still producing error messages for all other components. When you are familiar with operating the Zettelstore, you might set the level to ""error"" to receive fewer noisy messages from it. ; [!max-request-size|''max-request-size''] : It limits the maximum byte size of a web request body to prevent clients from accidentally or maliciously sending a large request and wasting server resources. The minimum value is 1024. Default: 16777216 (16 MiB). ; [!owner|''owner''] : [[Identifier|00001006050000]] of a zettel that contains data about the owner of the Zettelstore. The owner has full authorization for the Zettelstore. Only if set to some value, user [[authentication|00001010000000]] is enabled. Ensure that the key [[''secret''|#secret]] is set to a value of at least 16 bytes, otherwise the Zettelstore will not start for security reasons. ; [!persistent-cookie|''persistent-cookie''] : A [[boolean value|00001006030500]] to make the access cookie persistent. This is helpful if you access the Zettelstore via a mobile device. On these, the operating system is free to stop the web browser and to remove temporary cookies. Therefore, an authenticated user will be logged off. If ""true"", a persistent cookie is used. Its lifetime exceeds the lifetime of the authentication token by 30 seconds (see option ''token-lifetime-html''). Default: ""false"" ; [!read-only-mode|''read-only-mode''] : If set to a [[true value|00001006030500]] the Zettelstore service puts into a read-only mode. No changes are possible. Default: ""false"". ; [!secret|''secret''] : A string value to make the communication with external clients strong enough so that sessions of the [[web user interface|00001014000000]] or [[API access token|00001010040700]] cannot be altered by some external unfriendly party. The string must have a length of at least 16 bytes. This value is only needed to be set if [[authentication is enabled|00001010040100]] by setting the key [[''owner''|#owner]] to some user identification value. ; [!token-lifetime-api|''token-lifetime-api''], [!token-lifetime-html|''token-lifetime-html''] : Define lifetime of access tokens in minutes. Values are only valid if authentication is enabled, i.e. key ''owner'' is set. ''token-lifetime-api'' is for accessing Zettelstore via its [[API|00001012000000]]. Default: ""10"". ''token-lifetime-html'' specifies the lifetime for the HTML views. It is automatically extended when a new HTML view is rendered. Default: ""60"". ; [!url-prefix|''url-prefix''] : Add the given string as a prefix to the local part of a Zettelstore local URL/URI when rendering zettel representations. It must begin and end with a slash character (""''/''"", U+002F). Note: ''url-prefix'' must be the suffix of [[''base-url''|#base-url]], otherwise the web service will not start. Default: ""/"". This allows to use a forwarding proxy [[server|00001010090100]] in front of the Zettelstore. ; [!verbose-mode|''verbose-mode''] : Be more verbose when logging data, if set to a [[true value|00001006030500]]. Default: ""false"" |
Changes to docs/manual/00001004011200.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004011200 title: Zettelstore boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220307121547 A Zettelstore must store its zettel somehow and somewhere. In most cases you want to store your zettel as files in a directory. Under certain circumstances you may want to store your zettel elsewhere. An example are the [[predefined zettel|00001005090000]] that come with a Zettelstore. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004011200 title: Zettelstore boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20220307121547 A Zettelstore must store its zettel somehow and somewhere. In most cases you want to store your zettel as files in a directory. Under certain circumstances you may want to store your zettel elsewhere. An example are the [[predefined zettel|00001005090000]] that come with a Zettelstore. |
︙ | ︙ |
Changes to docs/manual/00001004011400.zettel.
1 2 3 4 5 | id: 00001004011400 title: Configure file directory boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk | > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001004011400 title: Configure file directory boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20240710180215 Under certain circumstances, it is preferable to further configure a file directory box. This is done by appending query parameters after the base box URI ''dir:\//DIR''. The following parameters are supported: |= Parameter:|Description|Default value:| |
︙ | ︙ | |||
52 53 54 55 56 57 58 | === Readonly Sometimes you may want to provide zettel from a file directory box, but you want to disallow any changes. If you provide the query parameter ''readonly'' (with or without a corresponding value), the box will disallow any changes. ``` box-uri-1: dir:///home/zettel?readonly ``` | | | 53 54 55 56 57 58 59 60 | === Readonly Sometimes you may want to provide zettel from a file directory box, but you want to disallow any changes. If you provide the query parameter ''readonly'' (with or without a corresponding value), the box will disallow any changes. ``` box-uri-1: dir:///home/zettel?readonly ``` If you put the whole Zettelstore in [[read-only|00001004010000#read-only-mode]] [[mode|00001004051000]], all configured file directory boxes will be in read-only mode too, even if not explicitly configured. |
Changes to docs/manual/00001004011600.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004011600 title: Configure memory boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220307122554 Under most circumstances, it is preferable to further configure a memory box. This is done by appending query parameters after the base box URI ''mem:''. The following parameters are supported: | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004011600 title: Configure memory boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20220307112918 modified: 20220307122554 Under most circumstances, it is preferable to further configure a memory box. This is done by appending query parameters after the base box URI ''mem:''. The following parameters are supported: |
︙ | ︙ |
Changes to docs/manual/00001004050200.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004050200 title: The ''help'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk modified: 20210712233414 Lists all implemented sub-commands. Example: ``` # zettelstore help | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004050200 title: The ''help'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20210712233414 Lists all implemented sub-commands. Example: ``` # zettelstore help |
︙ | ︙ |
Changes to docs/manual/00001004050400.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004050400 title: The ''version'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk modified: 20211124182041 Emits some information about the Zettelstore's version. This allows you to check, whether your installed Zettelstore is The name of the software (""Zettelstore"") and the build version information is given, as well as the compiler version, and an indication about the operating system and the processor architecture of that computer. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004050400 title: The ''version'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20211124182041 Emits some information about the Zettelstore's version. This allows you to check, whether your installed Zettelstore is The name of the software (""Zettelstore"") and the build version information is given, as well as the compiler version, and an indication about the operating system and the processor architecture of that computer. |
︙ | ︙ |
Changes to docs/manual/00001004051000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004051000 title: The ''run'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk modified: 20220724162050 === ``zettelstore run`` This starts the web service. ``` zettelstore run [-a PORT] [-c CONFIGFILE] [-d DIR] [-debug] [-p PORT] [-r] [-v] | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004051000 title: The ''run'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20220724162050 === ``zettelstore run`` This starts the web service. ``` zettelstore run [-a PORT] [-c CONFIGFILE] [-d DIR] [-debug] [-p PORT] [-r] [-v] |
︙ | ︙ |
Changes to docs/manual/00001004051400.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004051400 title: The ''password'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk modified: 20210712234305 This sub-command is used to create a hashed password for to be authenticated users. It reads a password from standard input (two times, both must be equal) and writes the hashed password to standard output. The general usage is: | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004051400 title: The ''password'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20210712234305 This sub-command is used to create a hashed password for to be authenticated users. It reads a password from standard input (two times, both must be equal) and writes the hashed password to standard output. The general usage is: |
︙ | ︙ |
Changes to docs/manual/00001004059700.zettel.
1 2 3 4 5 | id: 00001004059700 title: List of supported logging levels role: manual tags: #configuration #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 | id: 00001004059700 title: List of supported logging levels role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20211204182643 modified: 20240221134619 Zettelstore supports various levels of logging output. This allows you to see the inner workings of Zettelstore, or to avoid it. Each level has an associated name and number. A lower number signals more logging output. |= Name | Number :| Description | Trace | 1 | Show most of the inner workings | Debug | 2 | Show many internal values that might be interesting for a [[Zettelstore developer|00000000000005]]. | Info | 3 | Display information about an event. In most cases, there is no required action expected from you. | Error | 4 | Notify about an error, which was handled automatically. Something is broken. User intervention may be required, some important functionality may be disabled. Monitor the application. | Mandatory | 5 | Important message will be shown, e.g. the Zettelstore version at startup time. | Disabled | 6 | No messages will be shown If you set the logging level to a certain value, only messages with the same or higher numerical value will be shown. E.g. if you set the logging level to ""error"", no ""trace"", ""debug"", and ""info"" messages are shown, but ""error"" and ""mandatory"" messages. |
Changes to docs/manual/00001004059900.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004059900 title: Command line flags for profiling the application role: manual tags: #command #configuration #manual #zettelstore syntax: zmk modified: 20211122174951 If you want to measure potential bottlenecks within the software Zettelstore, there are two [[command line|00001004050000]] flags for enabling the measurement (also called __profiling__): ; ''-cpuprofile FILE'' : Enables CPU profiling. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004059900 title: Command line flags for profiling the application role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20211122170506 modified: 20211122174951 If you want to measure potential bottlenecks within the software Zettelstore, there are two [[command line|00001004050000]] flags for enabling the measurement (also called __profiling__): ; ''-cpuprofile FILE'' : Enables CPU profiling. |
︙ | ︙ |
Changes to docs/manual/00001004100000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004100000 title: Zettelstore Administrator Console role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20211103162926 The administrator console is a service accessible only on the same computer on which Zettelstore is running. It allows an experienced user to monitor and control some of the inner workings of Zettelstore. You enable the administrator console by specifying a TCP port number greater than zero (better: greater than 1024) for it, either via the [[command-line parameter ''-a''|00001004051000#a]] or via the ''admin-port'' key of the [[startup configuration file|00001004010000#admin-port]]. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004100000 title: Zettelstore Administrator Console role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210510141304 modified: 20211103162926 The administrator console is a service accessible only on the same computer on which Zettelstore is running. It allows an experienced user to monitor and control some of the inner workings of Zettelstore. You enable the administrator console by specifying a TCP port number greater than zero (better: greater than 1024) for it, either via the [[command-line parameter ''-a''|00001004051000#a]] or via the ''admin-port'' key of the [[startup configuration file|00001004010000#admin-port]]. |
︙ | ︙ |
Changes to docs/manual/00001004101000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001004101000 title: List of supported commands of the administrator console role: manual tags: #configuration #manual #zettelstore syntax: zmk modified: 20220823194553 ; [!bye|''bye''] : Closes the connection to the administrator console. ; [!config|''config SERVICE''] : Displays all valid configuration keys for the given service. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001004101000 title: List of supported commands of the administrator console role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210510141304 modified: 20220823194553 ; [!bye|''bye''] : Closes the connection to the administrator console. ; [!config|''config SERVICE''] : Displays all valid configuration keys for the given service. |
︙ | ︙ |
Changes to docs/manual/00001005000000.zettel.
1 2 3 4 5 | id: 00001005000000 title: Structure of Zettelstore role: manual tags: #design #manual #zettelstore syntax: zmk | > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001005000000 title: Structure of Zettelstore role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20240711183257 Zettelstore is a software that manages your zettel. Since every zettel must be readable without any special tool, most zettel has to be stored as ordinary files within specific directories. Typically, file names and file content must comply to specific rules so that Zettelstore can manage them. If you add, delete, or change zettel files with other tools, e.g. a text editor, Zettelstore will monitor these actions. Zettelstore provides additional services to the user. |
︙ | ︙ | |||
24 25 26 27 28 29 30 | Your zettel are stored typically as files in a specific directory. If you have not explicitly specified the directory, a default directory will be used. The directory has to be specified at [[startup time|00001004010000]]. Nested directories are not supported (yet). Every file in this directory that should be monitored by Zettelstore must have a file name that begins with 14 digits (0-9), the [[zettel identifier|00001006050000]]. If you create a new zettel via the [[web user interface|00001014000000]] or via the [[API|00001012053200]], the zettel identifier will be the timestamp of the current date and time (format is ''YYYYMMDDhhmmss''). | | | | | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | Your zettel are stored typically as files in a specific directory. If you have not explicitly specified the directory, a default directory will be used. The directory has to be specified at [[startup time|00001004010000]]. Nested directories are not supported (yet). Every file in this directory that should be monitored by Zettelstore must have a file name that begins with 14 digits (0-9), the [[zettel identifier|00001006050000]]. If you create a new zettel via the [[web user interface|00001014000000]] or via the [[API|00001012053200]], the zettel identifier will be the timestamp of the current date and time (format is ''YYYYMMDDhhmmss''). This allows zettel to be sorted naturally by creation time.[^Zettel identifier format will be migrated to a new format after version 0.19, without reference to the creation date. See [[Alphanumeric Zettel Identifier|00001006050200]] for some details.] Since the only restriction on zettel identifiers are the 14 digits, you are free to use other digit sequences.[^Zettel identifier format will be migrated to a new format after version 0.19, without reference to the creation date.] The [[configuration zettel|00001004020000]] is one prominent example, as well as these manual zettel. You can create these special zettel by manually renaming the underlying zettel files. It is allowed that the file name contains other characters after the 14 digits. These are ignored by Zettelstore. Two filename extensions are used by Zettelstore: # ''.zettel'' is a format that stores metadata and content together in one file, # the empty file extension is used, when the content must be stored in its own file, e.g. image data; |
︙ | ︙ | |||
69 70 71 72 73 74 75 | To allow changing predefined zettel, both the file store and the internal zettel store are internally chained together. If you change a zettel, it will be always stored as a file. If a zettel is requested, Zettelstore will first try to read that zettel from a file. If such a file was not found, the internal zettel store is searched secondly. Therefore, the file store ""shadows"" the internal zettel store. | | | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | To allow changing predefined zettel, both the file store and the internal zettel store are internally chained together. If you change a zettel, it will be always stored as a file. If a zettel is requested, Zettelstore will first try to read that zettel from a file. If such a file was not found, the internal zettel store is searched secondly. Therefore, the file store ""shadows"" the internal zettel store. If you want to read the original zettel, you have to delete the zettel (which removes it from the file directory). Now we have two places where zettel are stored: in the specific directory and within the Zettelstore software. * [[List of predefined zettel|00001005090000]] === Boxes: alternative ways to store zettel As described above, a zettel may be stored as a file inside a directory or inside the Zettelstore software itself. Zettelstore allows other ways to store zettel by providing an abstraction called __box__.[^Formerly, zettel were stored physically in boxes, often made of wood.] |
︙ | ︙ |
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 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 | id: 00001005090000 title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20240711183318 The following table lists all predefined zettel with their purpose.[^Zettel identifier format will be migrated to a new format after version 0.19.] |= 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 | [[00000000000004]] | Zettelstore License | Lists the license of Zettelstore | [[00000000000005]] | Zettelstore Contributors | Lists all contributors of Zettelstore | [[00000000000006]] | Zettelstore Dependencies | Lists all licensed content | [[00000000000007]] | Zettelstore Log | Lists the last 8192 log messages | [[00000000000008]] | Zettelstore Memory | Some statistics about main memory usage | [[00000000000009]] | Zettelstore Sx Engine | Statistics about the [[Sx|https://t73f.de/r/sx]] engine, which interprets symbolic expressions | [[00000000000020]] | Zettelstore Box Manager | Contains some statistics about zettel boxes and the the index process | [[00000000000090]] | Zettelstore Supported Metadata Keys | Contains all supported metadata keys, their [[types|00001006030000]], and more | [[00000000000092]] | Zettelstore Supported Parser | Lists all supported values for metadata [[syntax|00001006020000#syntax]] that are recognized by Zettelstore | [[00000000000096]] | Zettelstore Startup Configuration | Contains the effective values of the [[startup configuration|00001004010000]] | [[00000000000100]] | Zettelstore Runtime Configuration | Allows to [[configure Zettelstore at runtime|00001004020000]] | [[00000000000102]] | Zettelstore Warnings | Warnings about potential problematic zettel identifier | [[00000000010100]] | Zettelstore Base HTML Template | Contains the general layout of the HTML view | [[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 | [[00000000010405]] | Zettelstore Delete HTML Template | View to confirm the deletion of a zettel | [[00000000010700]] | Zettelstore Error HTML Template | View to show an error message | [[00000000019000]] | Zettelstore Sxn Start Code | Starting point of sxn functions to build the templates | [[00000000019990]] | Zettelstore Sxn Base Code | Base sxn functions to build the templates | [[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]] | [[00000000040001]] | Generic Emoji | Image that is shown if [[original image reference|00001007040322]] is invalid | [[00000000060010]] | zettel | [[Role zettel|00001012051800]] for the role ""[[zettel|00001006020100#zettel]]"" | [[00000000060020]] | confguration | [[Role zettel|00001012051800]] for the role ""[[confguration|00001006020100#confguration]]"" | [[00000000060030]] | role | [[Role zettel|00001012051800]] for the role ""[[role|00001006020100#role]]"" | [[00000000060040]] | tag | [[Role zettel|00001012051800]] for the role ""[[tag|00001006020100#tag]]"" | [[00000000090000]] | New Menu | Contains items that should be in the zettel template menu | [[00000000090001]] | New Zettel | Template for a new zettel with role ""[[zettel|00001006020100#zettel]]"" | [[00000000090002]] | New User | Template for a new [[user zettel|00001010040200]] | [[00000000090003]] | New Tag | Template for a new [[tag zettel|00001006020100#tag]] | [[00000000090004]] | New Role | Template for a new [[role zettel|00001006020100#role]] | [[00009999999998]] | Zettelstore Application Directory | Maps application name to application specific zettel | [[00010000000000]] | Home | Default home zettel, contains some welcome information If a zettel is not linked, it is not accessible for the current user. In most cases, you must at least enable [[''expert-mode''|00001004020000#expert-mode]]. **Important:** All identifier may change until a stable version of the software is released. |
Changes to docs/manual/00001006010000.zettel.
1 2 3 4 5 | id: 00001006010000 title: Syntax of Metadata role: manual tags: #manual #syntax #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 | id: 00001006010000 title: Syntax of Metadata role: manual tags: #manual #syntax #zettelstore syntax: zmk created: 20210126175322 modified: 20240219193158 The metadata of a zettel is a collection of key-value pairs. The syntax roughly resembles the internal header of an email ([[RFC5322|https://tools.ietf.org/html/rfc5322]]). The key is a sequence of alphanumeric characters, a hyphen-minus character (""''-''"", U+002D) is also allowed. It begins at the first position of a new line. Uppercase letters of a key are translated to their lowercase equivalence. A key is separated from its value either by * a colon character (""'':''""), * a non-empty sequence of space characters, * a sequence of space characters, followed by a colon, followed by a sequence of space characters. A value is a sequence of printable characters. If the value should be continued in the following line, that following line (""continuation line"") must begin with a non-empty sequence of space characters. The rest of the following line will be interpreted as the next part of the value. There can be more than one continuation line for a value. A non-continuation line that contains a possibly empty sequence of characters, followed by the percent sign character (""''%''"") is treated as a comment line. It will be ignored. |
︙ | ︙ |
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: 20240711183409 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''] |
︙ | ︙ | |||
33 34 35 36 37 38 39 40 41 42 43 44 45 46 | This is a computed value. There is no need to set it via Zettelstore. If it is not stored within a zettel, it will be computed based on the value of the [[Zettel Identifier|00001006050000]]: if it contains a value >= 19700101000000, it will be coerced to da date/time; otherwise the version time of the running software will be used. Please note that the value von ''created'' will be different (in most cases) to the value of [[''id''|#id]] / the zettel identifier, because it is exact up to the second. When calculating a zettel identifier, Zettelstore tries to set the second value to zero, if possible. ; [!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. | > > > > > > | 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | This is a computed value. There is no need to set it via Zettelstore. If it is not stored within a zettel, it will be computed based on the value of the [[Zettel Identifier|00001006050000]]: if it contains a value >= 19700101000000, it will be coerced to da date/time; otherwise the version time of the running software will be used. Please note that the value von ''created'' will be different (in most cases) to the value of [[''id''|#id]] / the zettel identifier, because it is exact up to the second. When calculating a zettel identifier, Zettelstore tries to set the second value to zero, if possible. ; [!created-missing|''created-missing''] : If set to ""true"", the value of [[''created''|#created]] was not stored within a zettel. To allow the migration of [[zettel identifier|00001006050000]] to a new scheme, you should update the value of ''created'' to a reasonable value. Otherwise you might lose that information in future releases. This key will be removed when the migration to a new zettel identifier format has been completed. ; [!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. |
︙ | ︙ | |||
132 133 134 135 136 137 138 | ; [!url|''url''] : Defines an URL / URI for this zettel that possibly references external material. One use case is to specify the document that the current zettel comments on. The URL will be rendered special in the [[web user interface|00001014000000]] if you use the default template. ; [!useless-files|''useless-files''] : Contains the file names that are rejected to serve the content of a zettel. Is used for [[directory boxes|00001004011400]] and [[file boxes|00001004011200#file]]. | | | 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | ; [!url|''url''] : Defines an URL / URI for this zettel that possibly references external material. One use case is to specify the document that the current zettel comments on. The URL will be rendered special in the [[web user interface|00001014000000]] if you use the default template. ; [!useless-files|''useless-files''] : Contains the file names that are rejected to serve the content of a zettel. Is used for [[directory boxes|00001004011400]] and [[file boxes|00001004011200#file]]. If a zettel is deleted, these files will also be deleted. ; [!user-id|''user-id''] : Provides some unique user identification for an [[user zettel|00001010040200]]. It is used as a user name for authentication. It is only used for zettel with a ''role'' value of ""user"". ; [!user-role|''user-role''] : Defines the basic privileges of an authenticated user, e.g. reading / changing zettel. |
︙ | ︙ |
Changes to docs/manual/00001006020400.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001006020400 title: Supported values for metadata key ''read-only'' role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk modified: 20211124132040 A zettel can be marked as read-only, if it contains a metadata value for key [[''read-only''|00001006020000#read-only]]. If user authentication is [[enabled|00001010040100]], it is possible to allow some users to change the zettel, depending on their [[user role|00001010070300]]. Otherwise, the read-only mark is just a binary value. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001006020400 title: Supported values for metadata key ''read-only'' role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 modified: 20211124132040 A zettel can be marked as read-only, if it contains a metadata value for key [[''read-only''|00001006020000#read-only]]. If user authentication is [[enabled|00001010040100]], it is possible to allow some users to change the zettel, depending on their [[user role|00001010070300]]. Otherwise, the read-only mark is just a binary value. |
︙ | ︙ |
Changes to docs/manual/00001006030000.zettel.
1 2 3 4 5 6 | id: 00001006030000 title: Supported Key Types 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 15 16 17 18 19 20 21 22 23 | id: 00001006030000 title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 modified: 20240219161909 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 | ''-date'' | [[Timestamp|00001006034500]] | ''-number'' | [[Number|00001006033000]] | ''-role'' | [[Word|00001006035500]] | ''-time'' | [[Timestamp|00001006034500]] | ''-title'' | [[Zettelmarkup|00001006036500]] | ''-url'' | [[URL|00001006035000]] | ''-zettel'' | [[Identifier|00001006032000]] | ''-zid'' | [[Identifier|00001006032000]] | ''-zids'' | [[IdentifierSet|00001006032500]] | any other suffix | [[EString|00001006031500]] |
︙ | ︙ | |||
35 36 37 38 39 40 41 | * [[IdentifierSet|00001006032500]] * [[Number|00001006033000]] * [[String|00001006033500]] * [[TagSet|00001006034000]] * [[Timestamp|00001006034500]] * [[URL|00001006035000]] * [[Word|00001006035500]] | < | 34 35 36 37 38 39 40 41 | * [[IdentifierSet|00001006032500]] * [[Number|00001006033000]] * [[String|00001006033500]] * [[TagSet|00001006034000]] * [[Timestamp|00001006034500]] * [[URL|00001006035000]] * [[Word|00001006035500]] * [[Zettelmarkup|00001006036500]] |
Changes to docs/manual/00001006030500.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001006030500 title: Boolean Value role: manual tags: #manual #reference #zettel #zettelstore syntax: zmk modified: 20220304114040 On some places, metadata values are interpreted as a truth value. Every character sequence that begins with a ""0"", ""F"", ""N"", ""f"", or a ""n"" is interpreted as the boolean ""false"" value. All values are interpreted as the boolean ""true"" value. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001006030500 title: Boolean Value role: manual tags: #manual #reference #zettel #zettelstore syntax: zmk created: 20210212135017 modified: 20220304114040 On some places, metadata values are interpreted as a truth value. Every character sequence that begins with a ""0"", ""F"", ""N"", ""f"", or a ""n"" is interpreted as the boolean ""false"" value. All values are interpreted as the boolean ""true"" value. |
︙ | ︙ |
Deleted docs/manual/00001006036000.zettel.
|
| < < < < < < < < < < < < < < < < < < < < |
Changes to docs/manual/00001006050000.zettel.
1 2 3 4 5 | id: 00001006050000 title: Zettel identifier role: manual tags: #design #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 | id: 00001006050000 title: Zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20240708154551 Each zettel is given a unique identifier. To some degree, the zettel identifier is part of the metadata. Basically, the identifier is given by the [[Zettelstore|00001005000000]] software. === Timestamp-based identifier Every zettel identifier consists of 14 digits. They resemble a timestamp: the first four digits could represent the year, the next two represent the month, following by day, hour, minute, and second.[^Zettel identifier format will be migrated to a new format after version 0.19, without reference to the creation date.] This allows to order zettel chronologically in a canonical way. In most cases the zettel identifier is the timestamp when the zettel was created. However, the Zettelstore software just checks for exactly 14 digits. Anybody is free to assign a ""non-timestamp"" identifier to a zettel, e.g. with a month part of ""35"" or with ""99"" as the last two digits. Some zettel identifier are [[reserved|00001006055000]] and should not be used otherwise. All identifiers of zettel initially provided by an empty Zettelstore begin with ""000000"", except the home zettel ''00010000000000''. Zettel identifier of this manual have be chosen to begin with ""000010"". A zettel can have any identifier that contains 14 digits and that is not in use by another zettel managed by the same Zettelstore. === Identifiers with four alphanumeric characters In the future, above identifier format will change. The migration to the new format starts with Zettelstore version 0.18 and will last approximately until version 0.22. Above described format of 14 digits will be changed to four alphanumeric characters, i.e. the digits ''0'' to ''9'', and the letters ''a'' to ''z''. You might note that using 14 digits you are allowed a little less than 10^^14^^ Zettel, i.e. more than 999 trillion zettel, while the new scheme only allows you to create 36^^4^^-1 zettel (1679615 zettel, to be exact). Since Zettelstore is a single-user system, more than a million zettel should be enough. However, there must be a way to replace an identifier with 14 digits by an identifier with four characters. As a first step, the list of [[reserved zettel identifier|00001006055000]] is updated, as well as ways of client software to use predefined identifier. |
Added docs/manual/00001006050200.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 | id: 00001006050200 title: Alphanumeric Zettel Identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20240705200557 modified: 20240807173414 precursor: 00001006050000 Timestamp-based zettel identifier (14 digits) will be migrated to a new format. Instead of using the current date and time of zettel creation, the new format is based in incrementing zettel identifier. When creating a new zettel, its identifier is calculated by adding one to the current maximum zettel identifier. The external representation if the new format identifier is a sequence of four alphanumeric characters, i.e. the 36 characters ''0'' … ''9'', and ''a'' … ''z''. The external representation is basically a ""base-36"" encoding of the number. The characters ''A'' … ''Z'' are mapped to the lower-case ''a'' … ''z''. === Migration process Please note: the following is just a plan. Plans tend to be revised if they get in contact with reality. ; Version 0.18 (current) : Provides some tools to check your own zettelstore for problematic zettel identifier. For example, zettel without metadata key ''created'' should be updated by the user, especially if the zettel identifier is below ''19700101000000''. Most likely, this is the case for zettel created before version 0.7 (2022-08-17). Zettel [[Zettelstore Warnings|00000000000102]] (''00000000000102'') lists these problematic zettel identifier. You should update your zettel to remove these warnings to ensure a smooth migration. If you have developed an application, that defines a specific zettel identifier to be used as application configuration, you should must the new zettel [[Zettelstore Application Directory|00009999999998]] (''00009999999998''). There is an explicit, but preliminary mapping of the old format to the new one, and vice versa. This mapping will be calculated with the order of the identifier in the old format. The zettel [[Zettelstore Identifier Mapping|00009999999999]] (''00009999999999'') will show this mapping. ; Version 0.19 : The new identifier format will be used initially internal. Operation to rename a zettel, i.e. assigning a new identifier to a zettel, is removed permanently. The old format with 14 digits is still used to create URIs and to link zettel. You will have some time to update your zettel data if you detect some issues. ; Version 0.20 : The internal search index is based on the new format identifier. ; Version 0.21 : The new format is used to calculate URIs and to form links. ; Version 0.22 : Old format identifier are full legacy. |
Changes to docs/manual/00001006055000.zettel.
1 2 3 4 5 | id: 00001006055000 title: Reserved zettel identifier role: manual tags: #design #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 | id: 00001006055000 title: Reserved zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210721105704 modified: 20240711183638 [[Zettel identifier|00001006050000]] are typically created by examine the current date and time. By renaming the name of the underlying zettel file, you are able to provide any sequence of 14 digits[^Zettel identifier format will be migrated to a new format after version 0.19.]. To make things easier, you must not use zettel identifier that begin with four zeroes (''0000''). All zettel provided by an empty zettelstore begin with six zeroes[^Exception: the predefined home zettel ''00010000000000''. But you can [[configure|00001004020000#home-zettel]] another zettel with another identifier as the new home zettel.]. Zettel identifier of this manual have be chosen to begin with ''000010''. However, some external applications may need at least one defined zettel identifier to work properly. Zettel [[Zettelstore Application Directory|00009999999998]] (''00009999999998'') can be used to associate a name to a zettel identifier. For example, if your application is named ""app"", you create a metadata key ''app-zid''. Its value is the zettel identifier of the zettel that configures your application. === Reserved Zettel Identifier |= From | To | Description | 00000000000000 | 0000000000000 | This is an invalid zettel identifier | 00000000000001 | 0000099999999 | [[Predefined zettel|00001005090000]] | 00001000000000 | 0000109999999 | This [[Zettelstore manual|00001000000000]] | 00001100000000 | 0000899999999 | Reserved, do not use. | 00009000000000 | 0000999999999 | Reserved for applications (legacy) Since the format of zettel identifier will change in the near future, no external application is allowed to use the range ''00000000000001'' … ''0000999999999''. ==== External Applications (Legacy) |= From | To | Description | 00009000001000 | 00009000001999 | [[Zettel Presenter|https://zettelstore.de/contrib]], an application to display zettel as a HTML-based slideshow |
Changes to docs/manual/00001007010000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007010000 title: Zettelmarkup: General Principles role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20211124175047 Any document can be thought as a sequence of paragraphs and other [[block-structured elements|00001007030000]] (""blocks""), such as [[headings|00001007030300]], [[lists|00001007030200]], quotations, and code blocks. Some of these blocks can contain other blocks, for example lists may contain other lists or paragraphs. Other blocks contain [[inline-structured elements|00001007040000]] (""inlines""), such as text, [[links|00001007040310]], emphasized text, and images. With the exception of lists and tables, the markup for blocks always begins at the first position of a line with three or more identical characters. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007010000 title: Zettelmarkup: General Principles role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20211124175047 Any document can be thought as a sequence of paragraphs and other [[block-structured elements|00001007030000]] (""blocks""), such as [[headings|00001007030300]], [[lists|00001007030200]], quotations, and code blocks. Some of these blocks can contain other blocks, for example lists may contain other lists or paragraphs. Other blocks contain [[inline-structured elements|00001007040000]] (""inlines""), such as text, [[links|00001007040310]], emphasized text, and images. With the exception of lists and tables, the markup for blocks always begins at the first position of a line with three or more identical characters. |
︙ | ︙ |
Changes to docs/manual/00001007020000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007020000 title: Zettelmarkup: Basic Definitions role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218130713 Every Zettelmarkup content consists of a sequence of Unicode code-points. Unicode code-points are called in the following as **character**s. Characters are encoded with UTF-8. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007020000 title: Zettelmarkup: Basic Definitions role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220218130713 Every Zettelmarkup content consists of a sequence of Unicode code-points. Unicode code-points are called in the following as **character**s. Characters are encoded with UTF-8. |
︙ | ︙ |
Changes to docs/manual/00001007030000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030000 title: Zettelmarkup: Block-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220311181036 Every markup for blocks-structured elements (""blocks"") begins at the very first position of a line. There are five kinds of blocks: lists, one-line blocks, line-range blocks, tables, and paragraphs. === Lists | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007030000 title: Zettelmarkup: Block-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220311181036 Every markup for blocks-structured elements (""blocks"") begins at the very first position of a line. There are five kinds of blocks: lists, one-line blocks, line-range blocks, tables, and paragraphs. === Lists |
︙ | ︙ |
Changes to docs/manual/00001007030100.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030100 title: Zettelmarkup: Description Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131155 A description list is a sequence of terms to be described together with the descriptions of each term. Every term can described in multiple ways. A description term (short: __term__) is specified with one semicolon (""'';''"", U+003B) at the first position, followed by a space character and the described term, specified as a sequence of line elements. If the following lines should also be part of the term, exactly two spaces must be given at the beginning of each following line. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007030100 title: Zettelmarkup: Description Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220218131155 A description list is a sequence of terms to be described together with the descriptions of each term. Every term can described in multiple ways. A description term (short: __term__) is specified with one semicolon (""'';''"", U+003B) at the first position, followed by a space character and the described term, specified as a sequence of line elements. If the following lines should also be part of the term, exactly two spaces must be given at the beginning of each following line. |
︙ | ︙ |
Changes to docs/manual/00001007030200.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030200 title: Zettelmarkup: Nested Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218133902 There are thee kinds of lists that can be nested: ordered lists, unordered lists, and quotation lists. Ordered lists are specified with the number sign (""''#''"", U+0023), unordered lists use the asterisk (""''*''"", U+002A), and quotation lists are specified with the greater-than sing (""''>''"", U+003E). Let's call these three characters __list characters__. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007030200 title: Zettelmarkup: Nested Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220218133902 There are thee kinds of lists that can be nested: ordered lists, unordered lists, and quotation lists. Ordered lists are specified with the number sign (""''#''"", U+0023), unordered lists use the asterisk (""''*''"", U+002A), and quotation lists are specified with the greater-than sing (""''>''"", U+003E). Let's call these three characters __list characters__. |
︙ | ︙ |
Changes to docs/manual/00001007030300.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030300 title: Zettelmarkup: Headings role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218133755 To specify a (sub-) section of a zettel, you should use the headings syntax: at the beginning of a new line type at least three equal signs (""''=''"", U+003D), plus at least one space and enter the text of the heading as [[inline elements|00001007040000]]. ```zmk | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007030300 title: Zettelmarkup: Headings role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220218133755 To specify a (sub-) section of a zettel, you should use the headings syntax: at the beginning of a new line type at least three equal signs (""''=''"", U+003D), plus at least one space and enter the text of the heading as [[inline elements|00001007040000]]. ```zmk |
︙ | ︙ |
Changes to docs/manual/00001007030500.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030500 title: Zettelmarkup: Verbatim Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131500 Verbatim blocks are used to enter text that should not be interpreted. They begin with at least three grave accent characters (""''`''"", U+0060) at the first position of a line. Alternatively, a modifier letter grave accent (""''Ë‹''"", U+02CB) is also allowed[^On some devices, such as an iPhone / iPad, a grave accent character is harder to enter and is often confused with a modifier letter grave accent.]. You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007030500 title: Zettelmarkup: Verbatim Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220218131500 Verbatim blocks are used to enter text that should not be interpreted. They begin with at least three grave accent characters (""''`''"", U+0060) at the first position of a line. Alternatively, a modifier letter grave accent (""''Ë‹''"", U+02CB) is also allowed[^On some devices, such as an iPhone / iPad, a grave accent character is harder to enter and is often confused with a modifier letter grave accent.]. You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. |
︙ | ︙ |
Changes to docs/manual/00001007030600.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030600 title: Zettelmarkup: Quotation Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131806 A simple way to enter a quotation is to use the [[quotation list|00001007030200]]. A quotation list loosely follows the convention of quoting text within emails. However, if you want to attribute the quotation to someone, a quotation block is more appropriately. This kind of line-range block begins with at least three less-than characters (""''<''"", U+003C) at the first position of a line. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007030600 title: Zettelmarkup: Quotation Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220218131806 A simple way to enter a quotation is to use the [[quotation list|00001007030200]]. A quotation list loosely follows the convention of quoting text within emails. However, if you want to attribute the quotation to someone, a quotation block is more appropriately. This kind of line-range block begins with at least three less-than characters (""''<''"", U+003C) at the first position of a line. |
︙ | ︙ |
Changes to docs/manual/00001007030700.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030700 title: Zettelmarkup: Verse Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218132432 Sometimes, you want to enter text with significant space characters at the beginning of each line and with significant line endings. Poetry is one typical example. Of course, you could help yourself with hard space characters and hard line breaks, by entering a backslash character before a space character and at the end of each line. Using a verse block might be easier. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007030700 title: Zettelmarkup: Verse Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220218132432 Sometimes, you want to enter text with significant space characters at the beginning of each line and with significant line endings. Poetry is one typical example. Of course, you could help yourself with hard space characters and hard line breaks, by entering a backslash character before a space character and at the end of each line. Using a verse block might be easier. |
︙ | ︙ |
Changes to docs/manual/00001007030800.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007030800 title: Zettelmarkup: Region Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220323190829 Region blocks does not directly have a visual representation. They just group a range of lines. You can use region blocks to enter [[attributes|00001007050000]] that apply only to this range of lines. One example is to enter a multi-line warning that should be visible. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007030800 title: Zettelmarkup: Region Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220323190829 Region blocks does not directly have a visual representation. They just group a range of lines. You can use region blocks to enter [[attributes|00001007050000]] that apply only to this range of lines. One example is to enter a multi-line warning that should be visible. |
︙ | ︙ |
Changes to docs/manual/00001007031000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007031000 title: Zettelmarkup: Tables role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218131107 Tables are used to show some data in a two-dimensional fashion. In zettelmarkup, table are not specified explicitly, but by entering __table rows__. Therefore, a table can be seen as a sequence of table rows. A table row is nothing as a sequence of __table cells__. The length of a table is the number of table rows, the width of a table is the maximum length of its rows. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007031000 title: Zettelmarkup: Tables role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220218131107 Tables are used to show some data in a two-dimensional fashion. In zettelmarkup, table are not specified explicitly, but by entering __table rows__. Therefore, a table can be seen as a sequence of table rows. A table row is nothing as a sequence of __table cells__. The length of a table is the number of table rows, the width of a table is the maximum length of its rows. |
︙ | ︙ |
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: 20240219161800 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 64 65 66 67 68 69 | : 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. ; ''REDIRECT'', ''REINDEX'' (aggregate) : Will be ignored. These actions may have been copied from an existing [[API query call|00001012051400]] (or from a WebUI query), but are here superfluous (and possibly harmful). ; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]] or of type [[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.]. To allow some kind of backward compatibility, an action written in uppercase letters that leads to an empty result list, will be ignored. In this case the list of selected zettel is returned. 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/00001007040200.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007040200 title: Zettelmarkup: Literal-like formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220311185110 There are some reasons to mark text that should be rendered as uninterpreted: * Mark text as literal, sometimes as part of a program. * Mark text as input you give into a computer via a keyboard. * Mark text as output from some computer, e.g. shown at the command line. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007040200 title: Zettelmarkup: Literal-like formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220311185110 There are some reasons to mark text that should be rendered as uninterpreted: * Mark text as literal, sometimes as part of a program. * Mark text as input you give into a computer via a keyboard. * Mark text as output from some computer, e.g. shown at the command line. |
︙ | ︙ |
Changes to docs/manual/00001007040300.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007040300 title: Zettelmarkup: Reference-like text role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20210810172531 An important aspect of knowledge work is to interconnect your zettel as well as provide links to (external) material. There are several kinds of references that are allowed in Zettelmarkup: * [[Links to other zettel or to (external) material|00001007040310]] * [[Embedded zettel or (external) material|00001007040320]] (""inline transclusion"") | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007040300 title: Zettelmarkup: Reference-like text role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20210810172531 An important aspect of knowledge work is to interconnect your zettel as well as provide links to (external) material. There are several kinds of references that are allowed in Zettelmarkup: * [[Links to other zettel or to (external) material|00001007040310]] * [[Embedded zettel or (external) material|00001007040320]] (""inline transclusion"") |
︙ | ︙ |
Changes to docs/manual/00001007040324.zettel.
1 2 3 4 5 6 | id: 00001007040324 title: Zettelmarkup: Inline-mode Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210811154251 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007040324 title: Zettelmarkup: Inline-mode Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210811154251 modified: 20231222164501 Inline-mode transclusion applies to all zettel that are parsed in a non-trivial way, e.g. as structured textual content. For example, textual content is assumed if the [[syntax|00001006020000#syntax]] of a zettel is ""zmk"" ([[Zettelmarkup|00001007000000]]), or ""markdown"" / ""md"" ([[Markdown|00001008010000]]). Since this type of transclusion is at the level of [[inline-structured elements|00001007040000]], the transclude specification must be replaced with some inline-structured elements. First, the referenced zettel is read. |
︙ | ︙ | |||
34 35 36 37 38 39 40 | Example: ``{{00001007040322#spin}}`` is rendered as ::{{00001007040322#spin}}::{=example}. ** Just specifying the fragment identifier will reference something in the current page. This is not allowed, to prevent a possible endless recursion. * If the reference is a [[hosted or based|00001007040310#link-specifications]] link / URL to an image, that image will be rendered. | | | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | Example: ``{{00001007040322#spin}}`` is rendered as ::{{00001007040322#spin}}::{=example}. ** Just specifying the fragment identifier will reference something in the current page. This is not allowed, to prevent a possible endless recursion. * If the reference is a [[hosted or based|00001007040310#link-specifications]] link / URL to an image, that image will be rendered. Example: ``{{//z/00000000040001}}{alt=Emoji}`` is rendered as ::{{//z/00000000040001}}{alt=Emoji}::{=example} If no inline-structured elements are found, the transclude specification is replaced by an error message. To avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]] (also known as ""XML bomb""), the total number of transclusions / expansions is limited. The limit can be controlled by setting the value [[''max-transclusions''|00001004020000#max-transclusions]] of the runtime configuration zettel. === See also [[Full transclusion|00001007031100]] does not work inside some text, but is used for [[block-structured elements|00001007030000]]. |
Changes to docs/manual/00001007040330.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007040330 title: Zettelmarkup: Footnotes role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218130100 A footnote begins with a left square bracket, followed by a circumflex accent (""''^''"", U+005E), followed by some text, and ends with a right square bracket. Example: ``Main text[^Footnote text.].`` is rendered in HTML as: ::Main text[^Footnote text.].::{=example}. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007040330 title: Zettelmarkup: Footnotes role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210810155955 modified: 20220218130100 A footnote begins with a left square bracket, followed by a circumflex accent (""''^''"", U+005E), followed by some text, and ends with a right square bracket. Example: ``Main text[^Footnote text.].`` is rendered in HTML as: ::Main text[^Footnote text.].::{=example}. |
Changes to docs/manual/00001007040340.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007040340 title: Zettelmarkup: Citation Key role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218133447 A citation key references some external material that is part of a bibliographical collection. Currently, Zettelstore implements this only partially, it is ""work in progress"". However, the syntax is: beginning with a left square bracket and followed by an at sign character (""''@''"", U+0040), a the citation key is given. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007040340 title: Zettelmarkup: Citation Key role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210810155955 modified: 20220218133447 A citation key references some external material that is part of a bibliographical collection. Currently, Zettelstore implements this only partially, it is ""work in progress"". However, the syntax is: beginning with a left square bracket and followed by an at sign character (""''@''"", U+0040), a the citation key is given. |
︙ | ︙ |
Changes to docs/manual/00001007040350.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007040350 title: Zettelmarkup: Mark role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220218133206 A mark allows to name a point within a zettel. This is useful if you want to reference some content in a zettel, either with a [[link|00001007040310]] or with an [[inline-mode transclusion|00001007040324]]. A mark begins with a left square bracket, followed by an exclamation mark character (""''!''"", U+0021). Now the optional mark name follows. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007040350 title: Zettelmarkup: Mark role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210810155955 modified: 20220218133206 A mark allows to name a point within a zettel. This is useful if you want to reference some content in a zettel, either with a [[link|00001007040310]] or with an [[inline-mode transclusion|00001007040324]]. A mark begins with a left square bracket, followed by an exclamation mark character (""''!''"", U+0021). Now the optional mark name follows. |
︙ | ︙ |
Changes to docs/manual/00001007050000.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001007050000 title: Zettelmarkup: Attributes role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220630194106 Attributes allows to modify the way how material is presented. Alternatively, they provide additional information to markup elements. To some degree, attributes are similar to [[HTML attributes|https://html.spec.whatwg.org/multipage/dom.html#global-attributes]]. Typical use cases for attributes are to specify the (natural) [[language|00001007050100]] for a text region, to specify the [[programming language|00001007050200]] for highlighting program code, or to make white space visible in plain text. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007050000 title: Zettelmarkup: Attributes role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220630194106 Attributes allows to modify the way how material is presented. Alternatively, they provide additional information to markup elements. To some degree, attributes are similar to [[HTML attributes|https://html.spec.whatwg.org/multipage/dom.html#global-attributes]]. Typical use cases for attributes are to specify the (natural) [[language|00001007050100]] for a text region, to specify the [[programming language|00001007050200]] for highlighting program code, or to make white space visible in plain text. |
︙ | ︙ |
Changes to docs/manual/00001007050200.zettel.
1 2 3 4 5 6 7 | id: 00001007050200 title: Zettelmarkup: Supported Attribute Values for Programming Languages tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk role: manual TBD | > | 1 2 3 4 5 6 7 8 | id: 00001007050200 title: Zettelmarkup: Supported Attribute Values for Programming Languages tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk role: manual created: 20210126175322 TBD |
Changes to docs/manual/00001007706000.zettel.
1 2 3 4 5 6 7 8 9 10 | id: 00001007706000 title: Search value role: manual tags: #manual #search #zettelstore syntax: zmk modified: 20220807162031 A search value specifies a value to be searched for, depending on the [[search operator|00001007705000]]. A search value should be lower case, because all comparisons are done in a case-insensitive way and there are some upper case keywords planned. | > | 1 2 3 4 5 6 7 8 9 10 11 | id: 00001007706000 title: Search value role: manual tags: #manual #search #zettelstore syntax: zmk created: 20220805150154 modified: 20220807162031 A search value specifies a value to be searched for, depending on the [[search operator|00001007705000]]. A search value should be lower case, because all comparisons are done in a case-insensitive way and there are some upper case keywords planned. |
Changes to docs/manual/00001007720300.zettel.
1 2 3 4 5 6 | id: 00001007720300 title: Query: Context Directive role: manual tags: #manual #search #zettelstore syntax: zmk created: 20230707204706 | | > | | > | | | | > > | | 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 | id: 00001007720300 title: Query: Context Directive role: manual tags: #manual #search #zettelstore syntax: zmk created: 20230707204706 modified: 20240209191045 A context directive calculates the __context__ of a list of zettel identifier. It starts with the keyword ''CONTEXT''. Optionally you may specify some context details, after the keyword ''CONTEXT'', separated by space characters. These are: * ''FULL'': additionally search for zettel with the same tags, * ''BACKWARD'': search for context only though backward links, * ''FORWARD'': search for context only through forward links, * ''COST'': one or more space characters, and a positive integer: set the maximum __cost__ (default: 17), * ''MAX'': one or more space characters, and a positive integer: set the maximum number of context zettel (default: 200). If no ''BACKWARD'' and ''FORWARD'' is specified, a search for context zettel will be done though backward and forward links. The cost of a context zettel is calculated iteratively: * Each of the specified zettel hast a cost of one. * A zettel found as a single folge zettel or single precursor zettel has the cost of the originating zettel, plus one. * A zettel found as a single subordinate zettel or single superior zettel has the cost of the originating zettel, plus 1.2. * A zettel found as a single successor zettel or single predecessor zettel has the cost of the originating zettel, plus seven. * A zettel found via another link without being part of a [[set of zettel identifier|00001006032500]], has the cost of the originating zettel, plus two. * A zettel which is part of a set of zettel identifier, has the cost of the originating zettel, plus one of the four choices above and multiplied with roughly a linear-logarithmic value based on the size of the set. * A zettel with the same tag, has the cost of the originating zettel, plus a linear-logarithmic number based on the number of zettel with this tag. If a zettel belongs to more than one tag compared with the current zettel, there is a discount of 90% per additional tag. This only applies if the ''FULL'' directive was specified. The maximum cost is only checked for all zettel that are not directly reachable from the initial, specified list of zettel. This ensures that initial zettel that have only a highly used tag, will also produce some context zettel. Despite its possibly complicated structure, this algorithm ensures in practice that the zettel context is a list of zettel, where the first elements are ""near"" to the specified zettel and the last elements are more ""distant"" to the specified zettel. It also penalties zettel that acts as a ""hub"" to other zettel, to make it more likely that only relevant zettel appear on the context list. This directive may be specified only once as a query directive. A second occurence of ''CONTEXT'' is interpreted as a [[search expression|00001007701000]]. In most cases it is easier to adjust the maximum cost than to perform another context search, which is relatively expensive in terms of retrieving effort. |
Changes to docs/manual/00001007720900.zettel.
1 2 3 4 5 | id: 00001007720900 title: Query: Items Directive role: manual tags: #manual #search #zettelstore syntax: zmk | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007720900 title: Query: Items Directive role: manual tags: #manual #search #zettelstore syntax: zmk created: 20230729102142 modified: 20230729120755 The items directive works on zettel that 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""? |
︙ | ︙ |
Changes to docs/manual/00001007770000.zettel.
1 2 3 4 5 6 | id: 00001007770000 title: Query: Action List role: manual tags: #manual #search #zettelstore syntax: zmk created: 20230707205246 | | | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | id: 00001007770000 title: Query: Action List role: manual tags: #manual #search #zettelstore syntax: zmk created: 20230707205246 modified: 20240219161813 With a [[list of zettel identifier|00001007710000]], a [[query directives|00001007720000]], or a [[search expression|00001007701000]], 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]] or [[TagSet|00001006034000]]. To allow some kind of backward compatibility, an action written in uppercase letters that leads to an empty result list, will be ignored. In this case the list of selected zettel is returned. |
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 | id: 00001007780000 title: Formal syntax of query expressions role: manual tags: #manual #reference #search #zettelstore syntax: zmk created: 20220810144539 modified: 20240219155949 ``` QueryExpression := ZettelList? QueryDirective* SearchExpression ActionExpression? ZettelList := (ZID (SPACE+ ZID)*). ZID := '0'+ ('1' .. '9'') DIGIT* | ('1' .. '9') DIGIT*. QueryDirective := ContextDirective | IdentDirective | ItemsDirective | UnlinkedDirective. ContextDirective := "CONTEXT" (SPACE+ ContextDetail)*. ContextDetail := "FULL" | "BACKWARD" | "FORWARD" | "COST" SPACE+ PosInt | "MAX" SPACE+ PosInt. IdentDirective := IDENT. ItemsDirective := ITEMS. UnlinkedDirective := UNLINKED (SPACE+ PHRASE SPACE+ Word)*. SearchExpression := SearchTerm (SPACE+ SearchTerm)*. |
︙ | ︙ | |||
38 39 40 41 42 43 44 45 46 | SearchOperator := '!' | ('!')? ('~' | ':' | '[' | '}'). ExistOperator := '?' | '!' '?'. PosInt := '0' | ('1' .. '9') DIGIT*. ActionExpression := '|' (Word (SPACE+ Word)*)? Word := NO-SPACE NO-SPACE* ``` | > > > > > > > > > > | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | SearchOperator := '!' | ('!')? ('~' | ':' | '[' | '}'). ExistOperator := '?' | '!' '?'. PosInt := '0' | ('1' .. '9') DIGIT*. ActionExpression := '|' (Word (SPACE+ Word)*)? Action := Word | 'ATOM' | 'KEYS' | 'N' NO-SPACE* | 'MAX' PosInt | 'MIN' PosInt | 'REDIRECT' | 'REINDEX' | 'RSS' | 'TITLE' (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 17 18 19 | id: 00001007790000 title: Useful query expressions role: manual tags: #example #manual #search #zettelstore syntax: zmk created: 20220810144539 modified: 20240216003702 |= 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, ordered by zettel identifier | [[query:dead?]] | Zettel with invalid / dead links | [[query:backward!? precursor!?]] | Zettel that are not referenced by other zettel | [[query:tags!?]] | Zettel without tags | [[query:expire? ORDER expire]] | Zettel with an expire date, ordered from the nearest to the latest | [[query:00001007700000 CONTEXT]] | Zettel within the context of the [[given zettel|00001007700000]] | [[query:PICK 1 | REDIRECT]] | Redirect to a random zettel |
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 | id: 00001007800000 title: Zettelmarkup: Summary of Formatting Characters role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20231113191330 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]] |
︙ | ︙ |
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: 20231201135849 [[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}. 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: 20231201140000 === 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"", ''##marked text##'' → ##marked 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: 20240413160242 [[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 |
︙ | ︙ | |||
45 46 47 48 49 50 51 | : 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/]]. ; [!sxn|''sxn''] | | | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | : 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/]]. ; [!sxn|''sxn''] : S-Expressions, as implemented by [[Sx|https://t73f.de/r/sx]]. Often used to specify templates when rendering a zettel as HTML for the [[web user interface|00001014000000]] (with the help of sxhtml]). ; [!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/00001010040100.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001010040100 title: Enable authentication role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk modified: 20220419192817 To enable authentication, you must create a zettel that stores [[authentication data|00001010040200]] for the owner. Then you must reference this zettel within the [[startup configuration|00001004010000#owner]] under the key ''owner''. Once the startup configuration contains a valid [[zettel identifier|00001006050000]] under that key, authentication is enabled. Please note that you must also set key ''secret'' of the [[startup configuration|00001004010000#secret]] to some random string data (minimum length is 16 bytes) to secure the data exchanged with a client system. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001010040100 title: Enable authentication role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20220419192817 To enable authentication, you must create a zettel that stores [[authentication data|00001010040200]] for the owner. Then you must reference this zettel within the [[startup configuration|00001004010000#owner]] under the key ''owner''. Once the startup configuration contains a valid [[zettel identifier|00001006050000]] under that key, authentication is enabled. Please note that you must also set key ''secret'' of the [[startup configuration|00001004010000#secret]] to some random string data (minimum length is 16 bytes) to secure the data exchanged with a client system. |
Changes to docs/manual/00001010040400.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001010040400 title: Authentication process role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk modified: 20211127174943 When someone tries to authenticate itself with an user identifier / ""user name"" and a password, the following process is executed: # If meta key ''owner'' of the configuration zettel does not have a valid [[zettel identifier|00001006050000]] as value, authentication fails. # Retrieve all zettel, where the meta key ''user-id'' has the same value as the given user identification. If the list is empty, authentication fails. # From above list, the zettel with the numerically smallest identifier is selected. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001010040400 title: Authentication process role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20211127174943 When someone tries to authenticate itself with an user identifier / ""user name"" and a password, the following process is executed: # If meta key ''owner'' of the configuration zettel does not have a valid [[zettel identifier|00001006050000]] as value, authentication fails. # Retrieve all zettel, where the meta key ''user-id'' has the same value as the given user identification. If the list is empty, authentication fails. # From above list, the zettel with the numerically smallest identifier is selected. |
︙ | ︙ |
Changes to docs/manual/00001010040700.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001010040700 title: Access token role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk modified: 20211202120950 If an user is authenticated, an ""access token"" is created that must be sent with every request to prove the identity of the caller. Otherwise the user will not be recognized by Zettelstore. If the user was authenticated via the [[web user interface|00001014000000]], the access token is stored in a [[""session cookie""|https://en.wikipedia.org/wiki/HTTP_cookie#Session_cookie]]. When the web browser is closed, theses cookies are not saved. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001010040700 title: Access token role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20211202120950 If an user is authenticated, an ""access token"" is created that must be sent with every request to prove the identity of the caller. Otherwise the user will not be recognized by Zettelstore. If the user was authenticated via the [[web user interface|00001014000000]], the access token is stored in a [[""session cookie""|https://en.wikipedia.org/wiki/HTTP_cookie#Session_cookie]]. When the web browser is closed, theses cookies are not saved. |
︙ | ︙ |
Changes to docs/manual/00001010070300.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001010070300 title: User roles role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk modified: 20220214175212 Every user is associated with some basic privileges. These are specified in the [[user zettel|00001010040200]] with the key ''user-role''. The following values are supported: ; [!reader|""reader""] | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001010070300 title: User roles role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20220214175212 Every user is associated with some basic privileges. These are specified in the [[user zettel|00001010040200]] with the key ''user-role''. The following values are supported: ; [!reader|""reader""] |
︙ | ︙ |
Changes to docs/manual/00001010070400.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001010070400 title: Authorization and read-only mode role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk modified: 20211103164251 It is possible to enable both the read-only mode of the Zettelstore __and__ authentication/authorization. Both modes are independent from each other. This gives four use cases: ; Not read-only, no authorization | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001010070400 title: Authorization and read-only mode role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20211103164251 It is possible to enable both the read-only mode of the Zettelstore __and__ authentication/authorization. Both modes are independent from each other. This gives four use cases: ; Not read-only, no authorization |
︙ | ︙ |
Changes to docs/manual/00001010070600.zettel.
1 2 3 4 5 | id: 00001010070600 title: Access rules role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk | > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001010070600 title: Access rules role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20240711183714 Whether an operation of the Zettelstore is allowed or rejected, depends on various factors. The following rules are checked first, in this order: # In read-only mode, every operation except the ""Read"" operation is rejected. # If there is no owner, authentication is disabled and every operation is allowed for everybody. |
︙ | ︙ | |||
38 39 40 41 42 43 44 | ** If the zettel is the [[user zettel|00001010040200]] of the authenticated user, proceed as follows: *** If some sensitive meta values are changed (e.g. user identifier, zettel role, user role, but not hashed password), reject the access *** Since the user just updates some uncritical values, grant the access In other words: a user is allowed to change its user zettel, even if s/he has no writer privilege and if only uncritical data is changed. ** If the ''user-role'' of the user is ""reader"", reject the access. ** If the user is not allowed to create a new zettel, reject the access. ** Otherwise grant the access. | < < < | 39 40 41 42 43 44 45 46 47 48 49 | ** If the zettel is the [[user zettel|00001010040200]] of the authenticated user, proceed as follows: *** If some sensitive meta values are changed (e.g. user identifier, zettel role, user role, but not hashed password), reject the access *** Since the user just updates some uncritical values, grant the access In other words: a user is allowed to change its user zettel, even if s/he has no writer privilege and if only uncritical data is changed. ** If the ''user-role'' of the user is ""reader"", reject the access. ** If the user is not allowed to create a new zettel, reject the access. ** Otherwise grant the access. * Delete a zettel ** Reject the access. Only the owner of the Zettelstore is allowed to delete a zettel. This may change in the future. |
Changes to docs/manual/00001010090100.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001010090100 title: External server to encrypt message transport role: manual tags: #configuration #encryption #manual #security #zettelstore syntax: zmk modified: 20220217180826 Since Zettelstore does not encrypt the messages it exchanges with its clients, you may need some additional software to enable encryption. === Public-key encryption To enable encryption, you probably use some kind of encryption keys. In most cases, you need to deploy a ""public-key encryption"" process, where your side publish a public encryption key that only works with a corresponding private decryption key. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001010090100 title: External server to encrypt message transport role: manual tags: #configuration #encryption #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20220217180826 Since Zettelstore does not encrypt the messages it exchanges with its clients, you may need some additional software to enable encryption. === Public-key encryption To enable encryption, you probably use some kind of encryption keys. In most cases, you need to deploy a ""public-key encryption"" process, where your side publish a public encryption key that only works with a corresponding private decryption key. |
︙ | ︙ |
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 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20240711183736 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 [[symbolic expressions|00001012930000]] as its main encoding formats for exchanging messages between a Zettelstore and its client software. |
︙ | ︙ | |||
30 31 32 33 34 35 36 | === 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]] * [[Update metadata and content of a zettel|00001012054200]] | < | 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | === 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]] * [[Update metadata and content of a zettel|00001012054200]] * [[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/00001012050600.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001012050600 title: API: Provide an access token role: manual tags: #api #manual #zettelstore syntax: zmk modified: 20220218130020 The [[authentication process|00001012050200]] provides you with an [[access token|00001012921000]]. Most API calls need such an access token, so that they know the identity of the caller. You send the access token in the ""Authorization"" request header field, as described in [[RFC 6750, section 2.1|https://tools.ietf.org/html/rfc6750#section-2.1]]. You need to use the ""Bearer"" authentication scheme to transmit the access token. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012050600 title: API: Provide an access token role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20220218130020 The [[authentication process|00001012050200]] provides you with an [[access token|00001012921000]]. Most API calls need such an access token, so that they know the identity of the caller. You send the access token in the ""Authorization"" request header field, as described in [[RFC 6750, section 2.1|https://tools.ietf.org/html/rfc6750#section-2.1]]. You need to use the ""Bearer"" authentication scheme to transmit the access token. |
︙ | ︙ |
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 | id: 00001012051400 title: API: Query the list of all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20220912111111 modified: 20240711161320 precursor: 00001012051200 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. |
︙ | ︙ | |||
105 106 107 108 109 110 111 112 113 114 115 | 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. ; ''REINDEX'' (aggregate) : Updates the internal search index for the selected zettel, roughly similar to the [[refresh|00001012080500]] API call. It is not really an aggregate, since it is used only for its side effect. It is allowed to specify another aggregate. | > > > > > | > | > > > > | 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 | 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. ; ''KEYS'' (aggregate) : Emit a list of all metadata keys, together with the number of zettel having the key. ; ''REDIRECT'' (aggregate) : Performs a HTTP redirect to the first selected zettel, using HTTP status code 302. The zettel identifier is in the body. ; ''REINDEX'' (aggregate) : Updates the internal search index for the selected zettel, roughly similar to the [[refresh|00001012080500]] API call. It is not really an aggregate, since it is used only for its side effect. It is allowed to specify another aggregate. ; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]] or [[TagSet|00001006034000]] (aggregates) : Emit an aggregate of the given metadata key. The key can be given in any letter case. First, ''REINDEX'' actions are executed, then ''REDIRECT''. If no ''REDIRECT'' was found the first other aggregate action will be executed. To allow some kind of backward compatibility, an action written in uppercase letters that leads to an empty result list, will be ignored. In this case the list of selected zettel is returned. === HTTP Status codes ; ''200'' : Query was successful. ; ''204'' : Query was successful, but results in no content. Most likely, you specified no appropriate aggregator. ; ''302'' : Query was successful, redirect to first zettel in list. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the access bearer token was not valid, or you forgot to specify a valid query. |
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 22 | 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: 20240620171057 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 [[Sz 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=sz''. If successful, the output is a symbolic expression value: ```sh # curl 'http://127.0.0.1:23123/z/00001012053500?enc=sz' (BLOCK (PARA (TEXT "The ") (LINK-ZETTEL () "00001012920000" (TEXT "endpoint")) (TEXT " to work with parsed metadata and content of a specific zettel is ") (LITERAL-INPUT () "/z/{ID}") (TEXT ", where ") (LITERAL-INPUT () "{ID}") (TEXT " is a placeholder for the ") ... ``` 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 # curl 'http://127.0.0.1:23123/z/00001012053500?enc=html&part=zettel' |
︙ | ︙ |
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 | 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: 20240620170909 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=sz&parseonly' (BLOCK (PARA (TEXT "The ") (LINK-ZETTEL () "00001012920000" (TEXT "endpoint")) (TEXT " to work with parsed metadata and content of a specific zettel is ") (LITERAL-INPUT () "/z/{ID}") (TEXT ", where ") ... ``` 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'' |
︙ | ︙ |
Deleted docs/manual/00001012054400.zettel.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
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 | id: 00001012920000 title: Endpoints used by the API role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20240711183819 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]] | | ''x'' | GET: [[retrieve administrative data|00001012070500]] | | E**x**ecute | | POST: [[execute command|00001012080100]] | ''z'' | GET: [[list zettel|00001012051200]]/[[query zettel|00001012051400]] | GET: [[retrieve zettel|00001012053300]] | **Z**ettel | | POST: [[create new zettel|00001012053200]] | PUT: [[update zettel|00001012054200]] | | | DELETE: [[delete zettel|00001012054600]] 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/00001012920510.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001012920510 title: HTML Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk modified: 20210726193034 A zettel representation in HTML. This representation is different form the [[web user interface|00001014000000]] as it contains the zettel representation only and no additional data such as the menu bar. It is intended to be used by external clients. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012920510 title: HTML Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20210726193034 A zettel representation in HTML. This representation is different form the [[web user interface|00001014000000]] as it contains the zettel representation only and no additional data such as the menu bar. It is intended to be used by external clients. |
︙ | ︙ |
Changes to docs/manual/00001012920519.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001012920519 title: Text Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk modified: 20210726193119 A zettel representation contains just all textual data of a zettel. Could be used for creating a search index. Every line may contain zero, one, or more words, separated by space character. | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012920519 title: Text Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20210726193119 A zettel representation contains just all textual data of a zettel. Could be used for creating a search index. Every line may contain zero, one, or more words, separated by space character. |
︙ | ︙ |
Changes to docs/manual/00001012920522.zettel.
1 2 3 4 5 6 7 8 9 10 11 | id: 00001012920522 title: Zmk Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk modified: 20211124140857 A zettel representation that tries to recreate a [[Zettelmarkup|00001007000000]] representation of the zettel. Useful if you want to convert [[other markup languages|00001008000000]] to Zettelmarkup (e.g. [[Markdown|00001008010000]]). If transferred via HTTP, the content type will be ''text/plain''. | > | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001012920522 title: Zmk Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20211124140857 A zettel representation that tries to recreate a [[Zettelmarkup|00001007000000]] representation of the zettel. Useful if you want to convert [[other markup languages|00001008000000]] to Zettelmarkup (e.g. [[Markdown|00001008010000]]). If transferred via HTTP, the content type will be ''text/plain''. |
Changes to docs/manual/00001012920800.zettel.
1 2 3 4 5 6 7 8 9 10 11 12 | id: 00001012920800 title: Values to specify zettel parts role: manual tags: #api #manual #reference #zettelstore syntax: zmk modified: 20220214175335 When working with [[zettel|00001006000000]], you could work with the whole zettel, with its metadata, or with its content: ; [!zettel|''zettel''] : Specifies that you work with a zettel as a whole. Contains identifier, metadata, and content of a zettel. ; [!meta|''meta''] | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001012920800 title: Values to specify zettel parts role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20220214175335 When working with [[zettel|00001006000000]], you could work with the whole zettel, with its metadata, or with its content: ; [!zettel|''zettel''] : Specifies that you work with a zettel as a whole. Contains identifier, metadata, and content of a zettel. ; [!meta|''meta''] |
︙ | ︙ |
Changes to docs/manual/00001012921200.zettel.
1 2 3 4 5 6 | id: 00001012921200 title: API: Encoding of Zettel Access Rights role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20220201173115 | | | | 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 | id: 00001012921200 title: API: Encoding of Zettel Access Rights role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20220201173115 modified: 20240711183931 Various API calls return a symbolic expression list ''(rights N)'', with ''N'' as a number, that encodes the access rights the user currently has. ''N'' 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. |=Bit number:|Bit value:|Meaning | 1 | 2 | User is allowed to create a new zettel | 2 | 4 | User is allowed to read the zettel | 3 | 8 | User is allowed to update the zettel | 4 | 16 | (not in use; was assigned to an operation) | 5 | 32 | User is allowed to delete the zettel The algorithm to calculate the actual access rights from the value is relatively simple: # Search for the biggest bit value that is less than the rights value. This is an access right for the current user. # Subtract the bit value from the rights value. Remember the difference as the new rights value. |
︙ | ︙ |
Changes to docs/manual/00001012930500.zettel.
1 2 3 4 5 6 | id: 00001012930500 title: Syntax of Symbolic Expressions role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20230403151127 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012930500 title: Syntax of Symbolic Expressions role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20230403151127 modified: 20240413160345 === Syntax of 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. Internally, lists are composed of __cells__. A cell allows to store two values. |
︙ | ︙ | |||
67 68 69 70 71 72 73 | 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). === See also | | | 67 68 69 70 71 72 73 74 75 76 77 | 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). === See also * Currently, Zettelstore uses [[Sx|https://t73f.de/r/sx]] (""Symbolic eXpression framework"") to implement symbolic expressions. The project page might contain additional information about the full syntax. Zettelstore only uses lists, numbers, string, and symbols to represent zettel. |
Changes to docs/manual/00001012931000.zettel.
1 2 3 4 5 6 | id: 00001012931000 title: Encoding of Sz role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403153903 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012931000 title: Encoding of Sz role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403153903 modified: 20240123120319 Zettel in a [[Sz encoding|00001012920516]] are represented as a [[symbolic expression|00001012930000]]. To process these symbolic expressions, you need to know, how a specific part of a zettel is represented by a symbolic expression. Basically, each part of a zettel is represented as a list, often a nested list. The first element of that list is always an unique symbol, which denotes that part. The meaning / semantic of all other elements depend on that symbol. |
︙ | ︙ | |||
25 26 27 28 29 30 31 | Metadata is represented by a list, where the first element is the symbol ''META''. Following elements represent each metadatum[^""Metadatum"" is used as the singular form of metadata.] of a zettel in standard order. Standard order is: [[Title|00001006020000#title]], [[Role|00001006020000#role]], [[Tags|00001006020000#tags]], [[Syntax|00001006020000#syntax]], all other [[keys|00001006020000]] in alphabetic order. :::syntax | | | | < | | 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 | Metadata is represented by a list, where the first element is the symbol ''META''. Following elements represent each metadatum[^""Metadatum"" is used as the singular form of metadata.] of a zettel in standard order. Standard order is: [[Title|00001006020000#title]], [[Role|00001006020000#role]], [[Tags|00001006020000#tags]], [[Syntax|00001006020000#syntax]], all other [[keys|00001006020000]] in alphabetic order. :::syntax __Metadata__ **=** ''(META'' [[__Metadatum__|00001012931200]] … '')''. ::: === Content Zettel content is represented by a block. :::syntax __Content__ **=** [[__Block__|#block]]. ::: ==== Block A block is represented by a list with the symbol ''BLOCK'' as the first element. All following elements represent a nested [[block-structured element|00001007030000]]. :::syntax [!block|__Block__] **=** ''(BLOCK'' [[__BlockElement__|00001012931400]] … '')''. ::: ==== Inline Both block-structured elements and some metadata values may contain [[inline-structured elements|00001007040000]]. Similar, inline-structured elements are represented as follows: :::syntax __Inline__ **=** ''(INLINE'' [[__InlineElement__|00001012931600]] … '')''. ::: ==== Attribute [[Attributes|00001007050000]] may be specified for both block- and inline- structured elements. Attributes are represented by the following schema. :::syntax __Attribute__ **=** ''('' **[** [[__AttributeKeyValue__|00001012931800]] … **]** ')'. ::: Either, there are no attributes. These are specified by the empty list ''()''. Or there are attributes. In this case, the first element of the list must be the symbol ''quote'': ''(quote'' ''('' A,,1,, A,,2,, … A,,n,, '')'''')''. |
︙ | ︙ |
Changes to docs/manual/00001012931200.zettel.
1 2 3 4 5 6 | id: 00001012931200 title: Encoding of Sz Metadata role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161618 | | < < < | | < < < | | | | 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: 00001012931200 title: Encoding of Sz Metadata role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161618 modified: 20240219161848 A single metadata (""metadatum"") is represented by a triple: a symbol representing the type, a symbol representing the key, and either a string or a list that represent the value. The symbol depends on the [[metadata key type|00001006030000]]. The value also depends somehow on the key type: a set of values is represented as a list, all other values are represented by a string, even if it is a number. The following table maps key types to symbols and to the type of the value representation. |=Key Type<| Symbol<| Value< | [[Credential|00001006031000]] | ''CREDENTIAL'' | string | [[EString|00001006031500]] | ''EMPTY-STRING'' | string | [[Identifier|00001006032000]] | ''ZID'' | string | [[IdentifierSet|00001006032500]] | ''ZID-SET'' | ListValue | [[Number|00001006033000]] | ''NUMBER'' | string | [[String|00001006033500]] | ''STRING'' | string | [[TagSet|00001006034000]] | ''TAG-SET'' | ListValue | [[Timestamp|00001006034500]] | ''TIMESTAMP'' | string | [[URL|00001006035000]] | ''URL'' | string | [[Word|00001006035500]] | ''WORD'' | string | [[Zettelmarkup|00001006036500]] | ''ZETTELMARKUP'' | string :::syntax __ListValue__ **=** ''('' String,,1,, String,,2,, … String,,n,, '')''. ::: Examples: * The title of this zettel is represented as: ''(EMPTY-STRING title "Encoding of Sz Metadata")'' * The tags of this zettel are represented as: ''(TAG-SET tags ("#api" "#manual" "#reference" "#zettelstore"))'' |
Changes to docs/manual/00001012931400.zettel.
1 2 3 4 5 6 | id: 00001012931400 title: Encoding of Sz Block Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161803 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012931400 title: Encoding of Sz Block Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161803 modified: 20240123120132 === ''PARA'' :::syntax __Paragraph__ **=** ''(PARA'' [[__InlineElement__|00001012931600]] … '')''. ::: A paragraph is just a list of inline elements. |
︙ | ︙ | |||
46 47 48 49 50 51 52 | === ''DESCRIPTION'' :::syntax __Description__ **=** ''(DESCRIPTION'' __DescriptionTerm__ __DescriptionValues__ __DescriptionTerm__ __DescriptionValues__ … '')''. ::: A description is a sequence of one ore more terms and values. :::syntax | | | | | | | 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 | === ''DESCRIPTION'' :::syntax __Description__ **=** ''(DESCRIPTION'' __DescriptionTerm__ __DescriptionValues__ __DescriptionTerm__ __DescriptionValues__ … '')''. ::: A description is a sequence of one ore more terms and values. :::syntax __DescriptionTerm__ **=** ''('' [[__InlineElement__|00001012931600]] … '')''. ::: A description term is just an inline-structured value. :::syntax __DescriptionValues__ **=** ''(BLOCK'' [[__Block__|00001012931000#block]] … '')''. ::: Description values are sequences of blocks. === ''TABLE'' :::syntax __Table__ **=** ''(TABLE'' __TableHeader__ __TableRow__ … '')''. ::: A table is a table header and a sequence of table rows. :::syntax __TableHeader__ **=** ''()'' **|** ''('' __TableCell__ … '')''. ::: A table header is either the empty list or a list of table cells. :::syntax __TableRow__ **=** ''('' __TableCell__ … '')''. ::: A table row is a list of table cells. === ''CELL'', ''CELL-*'' There are four kinds of table cells, one for each possible cell alignment. The structure is the same for all kind. :::syntax __TableCell__ **=** __DefaultCell__ **|** __CenterCell__ **|** __LeftCell__ **|** __RightCell__. |
︙ | ︙ | |||
105 106 107 108 109 110 111 | === ''REGION-*'' The following lists specifies different kinds of regions. A region treat a sequence of block elements to be belonging together in certain ways. The have a similar structure. :::syntax | | | | | 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 | === ''REGION-*'' The following lists specifies different kinds of regions. A region treat a sequence of block elements to be belonging together in certain ways. The have a similar structure. :::syntax __BlockRegion__ **=** ''(REGION-BLOCK'' [[__Attributes__|00001012931000#attribute]] ''('' [[__BlockElement__|00001012931400]] … '')'' [[__InlineElement__|00001012931600]] … '')''. ::: A block region just treats the block to belong in an unspecified way. Typically, the reason is given in the attributes. The inline describes the block. :::syntax __QuoteRegion__ **=** ''(REGION-QUOTE'' [[__Attributes__|00001012931000#attribute]] ''('' [[__BlockElement__|00001012931400]] … '')'' [[__InlineElement__|00001012931600]] … '')''. ::: A block region just treats the block to contain a longer quotation. Attributes may further specify the quotation. The inline typically describes author / source of the quotation. :::syntax __VerseRegion__ **=** ''(REGION-VERSE'' [[__Attributes__|00001012931000#attribute]] ''('' [[__BlockElement__|00001012931400]] … '')'' [[__InlineElement__|00001012931600]] … '')''. ::: A block region just treats the block to contain a verse. Soft line break are transformed into hard line breaks to save the structure of the verse / poem. Attributes may further specify something. The inline typically describes author / source of the verse. === ''VERBATIM-*'' |
︙ | ︙ | |||
163 164 165 166 167 168 169 | :::syntax __ZettelVerbatim__ **=** ''(VERBATIM-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as (nested) zettel content. === ''BLOB'' :::syntax | | | | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | :::syntax __ZettelVerbatim__ **=** ''(VERBATIM-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as (nested) zettel content. === ''BLOB'' :::syntax __BLOB__ **=** ''(BLOB'' ''('' [[__InlineElement__|00001012931600]] … '')'' String,,1,, String,,2,, '')''. ::: A BLOB contains an image in block mode. The inline elements states some description. The first string contains the syntax of the image. The second string contains the actual image. If the syntax is ""SVG"", then the second string contains the SVG code. Otherwise the (binary) image data is encoded with base64. === ''TRANSCLUDE'' :::syntax __Transclude__ **=** ''(TRANSCLUDE'' [[__Attributes__|00001012931000#attribute]] [[__Reference__|00001012931900]] '')''. ::: A transclude list only occurs for a parsed zettel, but not for a evaluated zettel. Evaluating a zettel also means that all transclusions are resolved. __Reference__ denotes the zettel to be transcluded. |
Changes to docs/manual/00001012931600.zettel.
1 2 3 4 5 6 | id: 00001012931600 title: Encoding of Sz Inline Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161845 | | | < < < < < < < < | | 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: 00001012931600 title: Encoding of Sz Inline Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161845 modified: 20240620170546 === ''TEXT'' :::syntax __Text__ **=** ''(TEXT'' String '')''. ::: Specifies the string as some text content, including white space characters. === ''SOFT'' :::syntax __Soft__ **=** ''(SOFT)''. ::: Denotes a soft line break. It will be often encoded as a space character, but signals the point in the textual content, where a line break occurred. === ''HARD'' :::syntax __Hard__ **=** ''(HARD)''. ::: Specifies a hard line break, i.e. the user wants to have a line break here. |
︙ | ︙ | |||
120 121 122 123 124 125 126 | The first string is the mark string used in the content. The second string is the mark string transformed to a slug, i.e. transformed to lower case, remove non-ASCII characters. The third string is the slug string, but made unique for the whole zettel. Then follows the marked text as a sequence of __InlineElement__s. === ''ENDNOTE'' :::syntax | | | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | The first string is the mark string used in the content. The second string is the mark string transformed to a slug, i.e. transformed to lower case, remove non-ASCII characters. The third string is the slug string, but made unique for the whole zettel. Then follows the marked text as a sequence of __InlineElement__s. === ''ENDNOTE'' :::syntax __Endnote__ **=** ''(ENDNOTE'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''. ::: Specifies endnote / footnote text. === ''FORMAT-*'' The following lists specifies some inline text formatting. The structure is always the same, the initial symbol denotes the actual formatting. |
︙ | ︙ |
Changes to docs/manual/00001012931800.zettel.
1 2 3 4 5 6 | id: 00001012931800 title: Encoding of Sz Attribute Values role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161923 | | | | < | | 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: 00001012931800 title: Encoding of Sz Attribute Values role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161923 modified: 20240122115245 An attribute is represented by a single cell. The first element of the cell references the attribute key, the second value the corresponding value. :::syntax __AttributeKeyValue__ **=** ''('' __AttributeKey__ ''.'' __AttributeValue__ '')''. ::: __AttributeKey__ and __AttributeValue__ are [[string values|00001012930500]]. An empty key denotes the generic attribute. A key with the value ''"-"'' specifies the default attribute. In this case, the attribute value is not interpreted. Some examples: * ''()'' represents the absence of attributes, * ''(("-" . ""))'' represent the default attribute, * ''(("-" . "") ("" . "syntax"))'' adds the generic attribute with the value ""syntax"", * ''(("lang" . "en"))'' denotes the attribute key ""lang"" with a value ""en"". |
Changes to docs/manual/00001012931900.zettel.
1 2 3 4 5 6 | id: 00001012931900 title: Encoding of Sz Reference Values role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230405123046 | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | id: 00001012931900 title: Encoding of Sz Reference Values role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230405123046 modified: 20240122094720 A reference is encoded as the actual reference value, and a symbol describing the state of that actual reference value. :::syntax __Reference__ **=** ''('' __ReferenceState__ String '')''. ::: The string contains the actual reference value. :::syntax __ReferenceState__ **=** ''INVALID'' **|** ''ZETTEL'' **|** ''SELF'' **|** ''FOUND'' **|** ''BROKEN'' **|** ''HOSTED'' **|** ''BASED'' **|** ''QUERY'' **|** ''EXTERNAL''. ::: The meaning of the state symbols corresponds to that of the symbols used for the description of [[link references|00001012931600#link]]. |
︙ | ︙ | |||
29 30 31 32 33 34 35 36 37 38 | ; ''FOUND'' : The reference value is a valid reference to an existing zettel. This value is only possible after evaluating the zettel. ; ''BROKEN'' : The reference value is a valid reference to an missing zettel. This value is only possible after evaluating the zettel. ; ''HOSTED'' ; ''BASED'' ; ''QUERY'' ; ''EXTERNAL'' | > > > > | 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | ; ''FOUND'' : The reference value is a valid reference to an existing zettel. This value is only possible after evaluating the zettel. ; ''BROKEN'' : The reference value is a valid reference to an missing zettel. This value is only possible after evaluating the zettel. ; ''HOSTED'' : The reference value starts with one slash character, denoting an absolute local reference. ; ''BASED'' : The reference value starts with two slash characters, denoting a local reference interpreted relative to the Zettelstore base URL. ; ''QUERY'' : The reference value contains a query expression. ; ''EXTERNAL'' : The reference value contains a full URL, referencing a resource outside of the Zettelstore server. |
Changes to docs/manual/00001014000000.zettel.
1 2 3 4 5 6 7 8 | id: 00001014000000 title: Web user interface tags: #manual #webui #zettelstore syntax: zmk role: manual The Web user interface is just a secondary way to interact with a Zettelstore. Using external software that interacts via the [[API|00001012000000]] is the recommended way. | > | 1 2 3 4 5 6 7 8 9 | id: 00001014000000 title: Web user interface tags: #manual #webui #zettelstore syntax: zmk role: manual created: 20210126175322 The Web user interface is just a secondary way to interact with a Zettelstore. Using external software that interacts via the [[API|00001012000000]] is the recommended way. |
Changes to docs/manual/00001018000000.zettel.
1 2 3 4 5 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk created: 20211027105921 modified: 20240830155745 This page lists some problems and their solutions that may occur when using your Zettelstore. === Installation * **Problem:** When you double-click on the Zettelstore executable icon, macOS complains that Zettelstore is an application from an unknown developer. Therefore, it will not start Zettelstore. ** **Solution:** Press the ''Ctrl'' key while opening the context menu of the Zettelstore executable with a right-click. |
︙ | ︙ | |||
24 25 26 27 28 29 30 | The difference between these two is the missing encryption of user name / password and for the answer of the Zettelstore if you use the ''http://'' schema. To be secure by default, the Zettelstore will not work in an insecure environment. ** **Solution 1:** If you are sure that your communication medium is safe, even if you use the ''http:/\/'' schema (for example, you are running the Zettelstore on the same computer you are working on, or if the Zettelstore is running on a computer in your protected local network), then you could add the entry ''insecure-cookie: true'' in you [[startup configuration|00001004010000#insecure-cookie]] file. ** **Solution 2:** If you are not sure about the security of your communication medium (for example, if unknown persons might use your local network), then you should run an [[external server|00001010090100]] in front of your Zettelstore to enable the use of the ''https://'' schema. === Working with Zettel Files * **Problem:** When you delete a zettel file by removing it from the ""disk"", e.g. by dropping it into the trash folder, by dragging into another folder, or by removing it from the command line, Zettelstore sometimes did not detect that change. | | | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | The difference between these two is the missing encryption of user name / password and for the answer of the Zettelstore if you use the ''http://'' schema. To be secure by default, the Zettelstore will not work in an insecure environment. ** **Solution 1:** If you are sure that your communication medium is safe, even if you use the ''http:/\/'' schema (for example, you are running the Zettelstore on the same computer you are working on, or if the Zettelstore is running on a computer in your protected local network), then you could add the entry ''insecure-cookie: true'' in you [[startup configuration|00001004010000#insecure-cookie]] file. ** **Solution 2:** If you are not sure about the security of your communication medium (for example, if unknown persons might use your local network), then you should run an [[external server|00001010090100]] in front of your Zettelstore to enable the use of the ''https://'' schema. === Working with Zettel Files * **Problem:** When you delete a zettel file by removing it from the ""disk"", e.g. by dropping it into the trash folder, by dragging into another folder, or by removing it from the command line, Zettelstore sometimes did not detect that change. If you access the zettel via Zettelstore, an error is reported. ** **Explanation:** Sometimes, the operating system does not tell Zettelstore about the removed zettel. This occurs mostly under MacOS. ** **Solution 1:** If you are running Zettelstore in [[""simple-mode""|00001004051100]] or if you have enabled [[''expert-mode''|00001004020000#expert-mode]], you are allowed to refresh the internal data by selecting ""Refresh"" in the Web User Interface (you find it in the menu ""Lists""). ** **Solution 2:** There is an [[API|00001012080500]] call to make Zettelstore aware of this change. ** **Solution 3:** If you have an enabled [[Administrator Console|00001004100000]] you can use the command [[''refresh''|00001004101000#refresh]] to make your changes visible. ** **Solution 4:** You configure the zettel box as [[""simple""|00001004011400]]. |
︙ | ︙ | |||
50 51 52 53 54 55 56 | But attackers may find other ways to deploy their malicious code. Therefore, Zettelstore disallows any HTML content as a default. If you know what you are doing, e.g. because you will never copy HTML code you do not understand, you can relax this default. ** **Solution 1:** If you want zettel with syntax ""html"" not to be ignored, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""html"". ** **Solution 2:** If you want zettel with syntax ""html"" not to be ignored, **and** want to allow HTML in Markdown, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""markdown"". ** **Solution 3:** If you want zettel with syntax ""html"" not to be ignored, **and** want to allow HTML in Markdown, **and** want to use HTML code within Zettelmarkup, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""zettelmarkup"". | > > > > > > | 50 51 52 53 54 55 56 57 58 59 60 61 62 | But attackers may find other ways to deploy their malicious code. Therefore, Zettelstore disallows any HTML content as a default. If you know what you are doing, e.g. because you will never copy HTML code you do not understand, you can relax this default. ** **Solution 1:** If you want zettel with syntax ""html"" not to be ignored, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""html"". ** **Solution 2:** If you want zettel with syntax ""html"" not to be ignored, **and** want to allow HTML in Markdown, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""markdown"". ** **Solution 3:** If you want zettel with syntax ""html"" not to be ignored, **and** want to allow HTML in Markdown, **and** want to use HTML code within Zettelmarkup, you set the startup configuration key [[''insecure-html''|00001004010000#insecure-html]] to the value ""zettelmarkup"". === Search for specific content * **Problem:** If you are searching for zettel with zettel content ""EUPL"", the zettel with Zettelstore's [[License|00000000000004]] is not shown, but it does contain the character sequence ""EUPL"". ** **Solution:** The content of zettel with a zettel identifier less or equal ''00009999999999'' is not searched. These zettel are predefined zettel, sometimes computed zettel, with some content not related to your research. For these zettel, only the metadata can be searched. |
Deleted docs/manual/20231128184200.zettel.
|
| < < < < < < < |
Changes to encoder/encoder.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 encoder provides a generic interface to encode the abstract syntax // tree into some text form. package encoder import ( "errors" "fmt" "io" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package encoder provides a generic interface to encode the abstract syntax // tree into some text form. package encoder import ( "errors" "fmt" "io" "t73f.de/r/zsc/api" "zettelstore.de/z/ast" "zettelstore.de/z/zettel/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) |
︙ | ︙ |
Changes to encoder/encoder_blob_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 encoder_test import ( "testing" | > > > | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package encoder_test import ( "testing" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "zettelstore.de/z/config" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. ) |
︙ | ︙ | |||
37 38 39 40 41 42 43 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x7e, 0x9b, 0x55, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x62, 0x00, 0x00, 0x00, 0x06, 0x00, 0x03, 0x36, 0x37, 0x7c, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, }, expect: expectMap{ encoderHTML: `<p><img alt="PNG" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="></p>`, | | | 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x7e, 0x9b, 0x55, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x62, 0x00, 0x00, 0x00, 0x06, 0x00, 0x03, 0x36, 0x37, 0x7c, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, }, expect: expectMap{ encoderHTML: `<p><img alt="PNG" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="></p>`, encoderSz: `(BLOCK (BLOB ((TEXT "PNG")) "png" "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="))`, encoderSHTML: `((p (img (@ (alt . "PNG") (src . "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==")))))`, encoderText: "", encoderZmk: `%% Unable to display BLOB with description 'PNG' and syntax 'png'.`, }, }, } |
︙ | ︙ |
Changes to encoder/encoder_block_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 encoder_test var tcsBlock = []zmkTestCase{ { descr: "Empty Zettelmarkup should produce near nothing", | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package encoder_test var tcsBlock = []zmkTestCase{ { descr: "Empty Zettelmarkup should produce near nothing", |
︙ | ︙ | |||
25 26 27 28 29 30 31 | }, { descr: "Simple text: Hello, world", zmk: "Hello, world", expect: expectMap{ encoderHTML: "<p>Hello, world</p>", encoderMD: "Hello, world", | | | | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | }, { descr: "Simple text: Hello, world", zmk: "Hello, world", expect: expectMap{ encoderHTML: "<p>Hello, world</p>", encoderMD: "Hello, world", encoderSz: `(BLOCK (PARA (TEXT "Hello, world")))`, encoderSHTML: `((p "Hello, world"))`, encoderText: "Hello, world", encoderZmk: useZmk, }, }, { descr: "Simple block comment", zmk: "%%%\nNo\nrender\n%%%", |
︙ | ︙ | |||
49 50 51 52 53 54 55 | }, { descr: "Rendered block comment", zmk: "%%%{-}\nRender\n%%%", expect: expectMap{ encoderHTML: "<!--\nRender\n-->\n", encoderMD: "", | | | | | | | | | 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 | }, { descr: "Rendered block comment", zmk: "%%%{-}\nRender\n%%%", expect: expectMap{ encoderHTML: "<!--\nRender\n-->\n", encoderMD: "", encoderSz: `(BLOCK (VERBATIM-COMMENT (("-" . "")) "Render"))`, encoderSHTML: "((@@@ \"Render\"))", encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Simple Heading", zmk: `=== Top Job`, expect: expectMap{ encoderHTML: "<h2 id=\"top-job\">Top Job</h2>", encoderMD: "# Top Job", encoderSz: `(BLOCK (HEADING 1 () "top-job" "top-job" (TEXT "Top Job")))`, encoderSHTML: `((h2 (@ (id . "top-job")) "Top Job"))`, encoderText: `Top Job`, encoderZmk: useZmk, }, }, { descr: "Simple List", zmk: "* A\n* B\n* C", expect: expectMap{ |
︙ | ︙ | |||
121 122 123 124 125 126 127 | }, { descr: "Thematic break with attribute", zmk: `---{lang="zmk"}`, expect: expectMap{ encoderHTML: `<hr lang="zmk">`, encoderMD: "---", | | | 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | }, { descr: "Thematic break with attribute", zmk: `---{lang="zmk"}`, expect: expectMap{ encoderHTML: `<hr lang="zmk">`, encoderMD: "---", encoderSz: `(BLOCK (THEMATIC (("lang" . "zmk"))))`, encoderSHTML: `((hr (@ (lang . "zmk"))))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "No list after paragraph", |
︙ | ︙ | |||
155 156 157 158 159 160 161 | encoderZmk: useZmk, }, }, { descr: "Simple List Quote", zmk: "> ToBeOrNotToBe", expect: expectMap{ | | | | | | | | | | | | | | | 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 | encoderZmk: useZmk, }, }, { descr: "Simple List Quote", zmk: "> ToBeOrNotToBe", expect: expectMap{ encoderHTML: "<blockquote>ToBeOrNotToBe</blockquote>", encoderMD: "> ToBeOrNotToBe", encoderSz: `(BLOCK (QUOTATION (INLINE (TEXT "ToBeOrNotToBe"))))`, encoderSHTML: `((blockquote (@L "ToBeOrNotToBe")))`, encoderText: "ToBeOrNotToBe", encoderZmk: useZmk, }, }, { descr: "Simple Quote Block", zmk: "<<<\nToBeOrNotToBe\n<<< Romeo Julia", expect: expectMap{ encoderHTML: "<blockquote><p>ToBeOrNotToBe</p><cite>Romeo Julia</cite></blockquote>", encoderMD: "> ToBeOrNotToBe", encoderSz: `(BLOCK (REGION-QUOTE () ((PARA (TEXT "ToBeOrNotToBe"))) (TEXT "Romeo Julia")))`, encoderSHTML: `((blockquote (p "ToBeOrNotToBe") (cite "Romeo Julia")))`, encoderText: "ToBeOrNotToBe\nRomeo Julia", encoderZmk: useZmk, }, }, { descr: "Quote Block with multiple paragraphs", zmk: "<<<\nToBeOr\n\nNotToBe\n<<< Romeo", expect: expectMap{ encoderHTML: "<blockquote><p>ToBeOr</p><p>NotToBe</p><cite>Romeo</cite></blockquote>", encoderMD: "> ToBeOr\n\n> NotToBe", encoderSz: `(BLOCK (REGION-QUOTE () ((PARA (TEXT "ToBeOr")) (PARA (TEXT "NotToBe"))) (TEXT "Romeo")))`, encoderSHTML: `((blockquote (p "ToBeOr") (p "NotToBe") (cite "Romeo")))`, encoderText: "ToBeOr\nNotToBe\nRomeo", encoderZmk: useZmk, }, }, { descr: "Verse block", zmk: `""" A line another line Back Paragraph Spacy Para """ Author`, expect: expectMap{ encoderHTML: "<div><p>A\u00a0line<br>\u00a0\u00a0another\u00a0line<br>Back</p><p>Paragraph</p><p>\u00a0\u00a0\u00a0\u00a0Spacy\u00a0\u00a0Para</p><cite>Author</cite></div>", encoderMD: "", encoderSz: "(BLOCK (REGION-VERSE () ((PARA (TEXT \"A\u00a0line\") (HARD) (TEXT \"\u00a0\u00a0another\u00a0line\") (HARD) (TEXT \"Back\")) (PARA (TEXT \"Paragraph\")) (PARA (TEXT \"\u00a0\u00a0\u00a0\u00a0Spacy\u00a0\u00a0Para\"))) (TEXT \"Author\")))", encoderSHTML: "((div (p \"A\u00a0line\" (br) \"\u00a0\u00a0another\u00a0line\" (br) \"Back\") (p \"Paragraph\") (p \"\u00a0\u00a0\u00a0\u00a0Spacy\u00a0\u00a0Para\") (cite \"Author\")))", encoderText: "A line\n another line\nBack\nParagraph\n Spacy Para\nAuthor", encoderZmk: "\"\"\"\nA\u00a0line\\\n\u00a0\u00a0another\u00a0line\\\nBack\nParagraph\n\u00a0\u00a0\u00a0\u00a0Spacy\u00a0\u00a0Para\n\"\"\" Author", }, }, { descr: "Span Block", zmk: `::: A simple span and much more :::`, expect: expectMap{ encoderHTML: "<div><p>A simple span and much more</p></div>", encoderMD: "", encoderSz: `(BLOCK (REGION-BLOCK () ((PARA (TEXT "A simple") (SOFT) (TEXT " span") (SOFT) (TEXT "and much more")))))`, encoderSHTML: `((div (p "A simple" " " " span" " " "and much more")))`, encoderText: `A simple span and much more`, encoderZmk: useZmk, }, }, { descr: "Simple Verbatim Code", zmk: "```\nHello\nWorld\n```", |
︙ | ︙ | |||
241 242 243 244 245 246 247 | }, { descr: "Simple Verbatim Code with visible spaces", zmk: "```{-}\nHello World\n```", expect: expectMap{ encoderHTML: "<pre><code>Hello\u2423World</code></pre>", encoderMD: " Hello World", | | | 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | }, { descr: "Simple Verbatim Code with visible spaces", zmk: "```{-}\nHello World\n```", expect: expectMap{ encoderHTML: "<pre><code>Hello\u2423World</code></pre>", encoderMD: " Hello World", encoderSz: `(BLOCK (VERBATIM-CODE (("-" . "")) "Hello World"))`, encoderSHTML: "((pre (code \"Hello\u2423World\")))", encoderText: "Hello World", encoderZmk: useZmk, }, }, { descr: "Simple Verbatim Eval", |
︙ | ︙ | |||
277 278 279 280 281 282 283 | }, { descr: "Simple Description List", zmk: "; Zettel\n: Paper\n: Note\n; Zettelkasten\n: Slip box", expect: expectMap{ encoderHTML: "<dl><dt>Zettel</dt><dd><p>Paper</p></dd><dd><p>Note</p></dd><dt>Zettelkasten</dt><dd><p>Slip box</p></dd></dl>", encoderMD: "", | | | | | | | | | | | | | | 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 | }, { descr: "Simple Description List", zmk: "; Zettel\n: Paper\n: Note\n; Zettelkasten\n: Slip box", expect: expectMap{ encoderHTML: "<dl><dt>Zettel</dt><dd><p>Paper</p></dd><dd><p>Note</p></dd><dt>Zettelkasten</dt><dd><p>Slip box</p></dd></dl>", encoderMD: "", encoderSz: `(BLOCK (DESCRIPTION ((TEXT "Zettel")) (BLOCK (BLOCK (PARA (TEXT "Paper"))) (BLOCK (PARA (TEXT "Note")))) ((TEXT "Zettelkasten")) (BLOCK (BLOCK (PARA (TEXT "Slip box"))))))`, encoderSHTML: `((dl (dt "Zettel") (dd (p "Paper")) (dd (p "Note")) (dt "Zettelkasten") (dd (p "Slip box"))))`, encoderText: "Zettel\nPaper\nNote\nZettelkasten\nSlip box", encoderZmk: useZmk, }, }, { descr: "Description List with paragraphs as item", zmk: "; Zettel\n: Paper\n\n Note\n; Zettelkasten\n: Slip box", expect: expectMap{ encoderHTML: "<dl><dt>Zettel</dt><dd><p>Paper</p><p>Note</p></dd><dt>Zettelkasten</dt><dd><p>Slip box</p></dd></dl>", encoderMD: "", encoderSz: `(BLOCK (DESCRIPTION ((TEXT "Zettel")) (BLOCK (BLOCK (PARA (TEXT "Paper")) (PARA (TEXT "Note")))) ((TEXT "Zettelkasten")) (BLOCK (BLOCK (PARA (TEXT "Slip box"))))))`, encoderSHTML: `((dl (dt "Zettel") (dd (p "Paper") (p "Note")) (dt "Zettelkasten") (dd (p "Slip box"))))`, encoderText: "Zettel\nPaper\nNote\nZettelkasten\nSlip box", encoderZmk: useZmk, }, }, { descr: "Description List with keys, but no descriptions", zmk: "; K1\n: D11\n: D12\n; K2\n; K3\n: D31", expect: expectMap{ encoderHTML: "<dl><dt>K1</dt><dd><p>D11</p></dd><dd><p>D12</p></dd><dt>K2</dt><dt>K3</dt><dd><p>D31</p></dd></dl>", encoderMD: "", encoderSz: `(BLOCK (DESCRIPTION ((TEXT "K1")) (BLOCK (BLOCK (PARA (TEXT "D11"))) (BLOCK (PARA (TEXT "D12")))) ((TEXT "K2")) (BLOCK) ((TEXT "K3")) (BLOCK (BLOCK (PARA (TEXT "D31"))))))`, encoderSHTML: `((dl (dt "K1") (dd (p "D11")) (dd (p "D12")) (dt "K2") (dt "K3") (dd (p "D31"))))`, encoderText: "K1\nD11\nD12\nK2\nK3\nD31", encoderZmk: useZmk, }, }, { descr: "Simple Table", zmk: "|c1|c2|c3\n|d1||d3", expect: expectMap{ encoderHTML: `<table><tbody><tr><td>c1</td><td>c2</td><td>c3</td></tr><tr><td>d1</td><td></td><td>d3</td></tr></tbody></table>`, encoderMD: "", encoderSz: `(BLOCK (TABLE () ((CELL (TEXT "c1")) (CELL (TEXT "c2")) (CELL (TEXT "c3"))) ((CELL (TEXT "d1")) (CELL) (CELL (TEXT "d3")))))`, encoderSHTML: `((table (tbody (tr (td "c1") (td "c2") (td "c3")) (tr (td "d1") (td) (td "d3")))))`, encoderText: "c1 c2 c3\nd1 d3", encoderZmk: useZmk, }, }, { descr: "Table with alignment and comment", zmk: `|h1>|=h2|h3:| |%--+---+---+ |<c1|c2|:c3| |f1|f2|=f3`, expect: expectMap{ encoderHTML: `<table><thead><tr><th class="right">h1</th><th>h2</th><th class="center">h3</th></tr></thead><tbody><tr><td class="left">c1</td><td>c2</td><td class="center">c3</td></tr><tr><td class="right">f1</td><td>f2</td><td class="center">=f3</td></tr></tbody></table>`, encoderMD: "", encoderSz: `(BLOCK (TABLE ((CELL-RIGHT (TEXT "h1")) (CELL (TEXT "h2")) (CELL-CENTER (TEXT "h3"))) ((CELL-LEFT (TEXT "c1")) (CELL (TEXT "c2")) (CELL-CENTER (TEXT "c3"))) ((CELL-RIGHT (TEXT "f1")) (CELL (TEXT "f2")) (CELL-CENTER (TEXT "=f3")))))`, encoderSHTML: `((table (thead (tr (th (@ (class . "right")) "h1") (th "h2") (th (@ (class . "center")) "h3"))) (tbody (tr (td (@ (class . "left")) "c1") (td "c2") (td (@ (class . "center")) "c3")) (tr (td (@ (class . "right")) "f1") (td "f2") (td (@ (class . "center")) "=f3")))))`, encoderText: "h1 h2 h3\nc1 c2 c3\nf1 f2 =f3", encoderZmk: `|=h1>|=h2|=h3: |<c1|c2|c3 |f1|f2|=f3`, }, }, { descr: "Simple Endnote", zmk: `Text[^Endnote]`, expect: expectMap{ encoderHTML: "<p>Text<sup id=\"fnref:1\"><a class=\"zs-noteref\" href=\"#fn:1\" role=\"doc-noteref\">1</a></sup></p><ol class=\"zs-endnotes\"><li class=\"zs-endnote\" id=\"fn:1\" role=\"doc-endnote\" value=\"1\">Endnote <a class=\"zs-endnote-backref\" href=\"#fnref:1\" role=\"doc-backlink\">\u21a9\ufe0e</a></li></ol>", encoderMD: "Text", encoderSz: `(BLOCK (PARA (TEXT "Text") (ENDNOTE () (TEXT "Endnote"))))`, encoderSHTML: "((p \"Text\" (sup (@ (id . \"fnref:1\")) (a (@ (class . \"zs-noteref\") (href . \"#fn:1\") (role . \"doc-noteref\")) \"1\"))))", encoderText: "Text Endnote", encoderZmk: useZmk, }, }, { descr: "Nested Endnotes", zmk: `Text[^Endnote[^Nested]]`, expect: expectMap{ encoderHTML: "<p>Text<sup id=\"fnref:1\"><a class=\"zs-noteref\" href=\"#fn:1\" role=\"doc-noteref\">1</a></sup></p><ol class=\"zs-endnotes\"><li class=\"zs-endnote\" id=\"fn:1\" role=\"doc-endnote\" value=\"1\">Endnote<sup id=\"fnref:2\"><a class=\"zs-noteref\" href=\"#fn:2\" role=\"doc-noteref\">2</a></sup> <a class=\"zs-endnote-backref\" href=\"#fnref:1\" role=\"doc-backlink\">\u21a9\ufe0e</a></li><li class=\"zs-endnote\" id=\"fn:2\" role=\"doc-endnote\" value=\"2\">Nested <a class=\"zs-endnote-backref\" href=\"#fnref:2\" role=\"doc-backlink\">\u21a9\ufe0e</a></li></ol>", encoderMD: "Text", encoderSz: `(BLOCK (PARA (TEXT "Text") (ENDNOTE () (TEXT "Endnote") (ENDNOTE () (TEXT "Nested")))))`, encoderSHTML: "((p \"Text\" (sup (@ (id . \"fnref:1\")) (a (@ (class . \"zs-noteref\") (href . \"#fn:1\") (role . \"doc-noteref\")) \"1\"))))", encoderText: "Text Endnote Nested", encoderZmk: useZmk, }, }, { descr: "Transclusion", zmk: `{{{http://example.com/image}}}{width="100px"}`, expect: expectMap{ encoderHTML: `<p><img class="external" src="http://example.com/image" width="100px"></p>`, encoderMD: "", encoderSz: `(BLOCK (TRANSCLUDE (("width" . "100px")) (EXTERNAL "http://example.com/image")))`, encoderSHTML: `((p (img (@ (class . "external") (src . "http://example.com/image") (width . "100px")))))`, encoderText: "", encoderZmk: useZmk, }, }, { descr: "A paragraph with a inline comment only should be empty in HTML", |
︙ | ︙ |
Changes to encoder/encoder_inline_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 encoder_test var tcsInline = []zmkTestCase{ { descr: "Empty Zettelmarkup should produce near nothing (inline)", | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package encoder_test var tcsInline = []zmkTestCase{ { descr: "Empty Zettelmarkup should produce near nothing (inline)", |
︙ | ︙ | |||
25 26 27 28 29 30 31 | }, { descr: "Simple text: Hello, world (inline)", zmk: `Hello, world`, expect: expectMap{ encoderHTML: "Hello, world", encoderMD: "Hello, world", | | | | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | }, { descr: "Simple text: Hello, world (inline)", zmk: `Hello, world`, expect: expectMap{ encoderHTML: "Hello, world", encoderMD: "Hello, world", encoderSz: `(INLINE (TEXT "Hello, world"))`, encoderSHTML: `("Hello, world")`, encoderText: "Hello, world", encoderZmk: useZmk, }, }, { descr: "Soft Break", zmk: "soft\nbreak", |
︙ | ︙ | |||
144 145 146 147 148 149 150 | }, }, { descr: "Quotes formatting", zmk: `""quotes""`, expect: expectMap{ encoderHTML: "“quotes”", | | | | | | | | | | | | | | 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 | }, }, { descr: "Quotes formatting", zmk: `""quotes""`, expect: expectMap{ encoderHTML: "“quotes”", encoderMD: "“quotes”", encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "quotes")))`, encoderSHTML: `((@L (@H "“") "quotes" (@H "”")))`, encoderText: `quotes`, encoderZmk: useZmk, }, }, { descr: "Quotes formatting (german)", zmk: `""quotes""{lang=de}`, expect: expectMap{ encoderHTML: `<span lang="de">„quotes“</span>`, encoderMD: "„quotes“", encoderSz: `(INLINE (FORMAT-QUOTE (("lang" . "de")) (TEXT "quotes")))`, encoderSHTML: `((span (@ (lang . "de")) (@H "„") "quotes" (@H "“")))`, encoderText: `quotes`, encoderZmk: `""quotes""{lang="de"}`, }, }, { descr: "Empty quotes (default)", zmk: `""""`, expect: expectMap{ encoderHTML: `“”`, encoderMD: "“”", encoderSz: `(INLINE (FORMAT-QUOTE ()))`, encoderSHTML: `((@L (@H "“" "”")))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Empty quotes (unknown)", zmk: `""""{lang=unknown}`, expect: expectMap{ encoderHTML: `<span lang="unknown">""</span>`, encoderMD: """", encoderSz: `(INLINE (FORMAT-QUOTE (("lang" . "unknown"))))`, encoderSHTML: `((span (@ (lang . "unknown")) (@H """ """)))`, encoderText: ``, encoderZmk: `""""{lang="unknown"}`, }, }, { descr: "Nested quotes (default)", zmk: `""say: ::""yes, ::""or?""::""::""`, expect: expectMap{ encoderHTML: `“say: <span>‘yes, <span>“or?”</span>’</span>”`, encoderMD: `“say: ‘yes, “or?”’”`, encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "say: ") (FORMAT-SPAN () (FORMAT-QUOTE () (TEXT "yes, ") (FORMAT-SPAN () (FORMAT-QUOTE () (TEXT "or?")))))))`, encoderSHTML: `((@L (@H "“") "say: " (span (@L (@H "‘") "yes, " (span (@L (@H "“") "or?" (@H "”"))) (@H "’"))) (@H "”")))`, encoderText: `say: yes, or?`, encoderZmk: useZmk, }, }, { descr: "Two quotes", zmk: `""yes"" or ""no""`, expect: expectMap{ encoderHTML: `“yes” or “no”`, encoderMD: `“yes” or “no”`, encoderSz: `(INLINE (FORMAT-QUOTE () (TEXT "yes")) (TEXT " or ") (FORMAT-QUOTE () (TEXT "no")))`, encoderSHTML: `((@L (@H "“") "yes" (@H "”")) " or " (@L (@H "“") "no" (@H "”")))`, encoderText: `yes or no`, encoderZmk: useZmk, }, }, { descr: "Mark formatting", zmk: `##marked##`, |
︙ | ︙ | |||
253 254 255 256 257 258 259 | }, { descr: "Code formatting with visible space", zmk: "``x y``{-}", expect: expectMap{ encoderHTML: "<code>x\u2423y</code>", encoderMD: "`x y`", | | | | | | | 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 | }, { descr: "Code formatting with visible space", zmk: "``x y``{-}", expect: expectMap{ encoderHTML: "<code>x\u2423y</code>", encoderMD: "`x y`", encoderSz: `(INLINE (LITERAL-CODE (("-" . "")) "x y"))`, encoderSHTML: "((code \"x\u2423y\"))", encoderText: `x y`, encoderZmk: useZmk, }, }, { descr: "HTML in Code formatting", zmk: "``<script `` abc", expect: expectMap{ encoderHTML: "<code><script </code> abc", encoderMD: "`<script ` abc", encoderSz: `(INLINE (LITERAL-CODE () "<script ") (TEXT " abc"))`, encoderSHTML: `((code "<script ") " abc")`, encoderText: `<script abc`, encoderZmk: useZmk, }, }, { descr: "Input formatting", zmk: `''input''`, expect: expectMap{ encoderHTML: `<kbd>input</kbd>`, encoderMD: "`input`", encoderSz: `(INLINE (LITERAL-INPUT () "input"))`, encoderSHTML: `((kbd "input"))`, encoderText: `input`, encoderZmk: useZmk, }, }, { descr: "Output formatting", zmk: `==output==`, expect: expectMap{ encoderHTML: `<samp>output</samp>`, encoderMD: "`output`", encoderSz: `(INLINE (LITERAL-OUTPUT () "output"))`, encoderSHTML: `((samp "output"))`, encoderText: `output`, encoderZmk: useZmk, }, }, { |
︙ | ︙ | |||
312 313 314 315 316 317 318 | }, }, { descr: "Nested Span Quote formatting", zmk: `::""abc""::{lang=fr}`, expect: expectMap{ encoderHTML: `<span lang="fr">« abc »</span>`, | | | | 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | }, }, { descr: "Nested Span Quote formatting", zmk: `::""abc""::{lang=fr}`, expect: expectMap{ encoderHTML: `<span lang="fr">« abc »</span>`, encoderMD: "« abc »", encoderSz: `(INLINE (FORMAT-SPAN (("lang" . "fr")) (FORMAT-QUOTE () (TEXT "abc"))))`, encoderSHTML: `((span (@ (lang . "fr")) (@L (@H "«" " ") "abc" (@H " " "»"))))`, encoderText: `abc`, encoderZmk: `::""abc""::{lang="fr"}`, }, }, { descr: "Simple Citation", |
︙ | ︙ | |||
348 349 350 351 352 353 354 | }, }, { descr: "No comment", zmk: `% comment`, expect: expectMap{ encoderHTML: `% comment`, encoderMD: "% comment", | | | | 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 | }, }, { descr: "No comment", zmk: `% comment`, expect: expectMap{ encoderHTML: `% comment`, encoderMD: "% comment", encoderSz: `(INLINE (TEXT "% comment"))`, encoderSHTML: `("% comment")`, encoderText: `% comment`, encoderZmk: useZmk, }, }, { descr: "Line comment (nogen HTML)", zmk: `%% line comment`, |
︙ | ︙ | |||
372 373 374 375 376 377 378 | }, { descr: "Line comment", zmk: `%%{-} line comment`, expect: expectMap{ encoderHTML: `<!-- line comment -->`, encoderMD: "", | | | | | | 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 | }, { descr: "Line comment", zmk: `%%{-} line comment`, expect: expectMap{ encoderHTML: `<!-- line comment -->`, encoderMD: "", encoderSz: `(INLINE (LITERAL-COMMENT (("-" . "")) "line comment"))`, encoderSHTML: `((@@ "line comment"))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Comment after text", zmk: `Text %%{-} comment`, expect: expectMap{ encoderHTML: `Text<!-- comment -->`, encoderMD: "Text", encoderSz: `(INLINE (TEXT "Text") (LITERAL-COMMENT (("-" . "")) "comment"))`, encoderSHTML: `("Text" (@@ "comment"))`, encoderText: `Text`, encoderZmk: useZmk, }, }, { descr: "Comment after text and with -->", zmk: `Text %%{-} comment --> end`, expect: expectMap{ encoderHTML: `Text<!-- comment --> end -->`, encoderMD: "Text", encoderSz: `(INLINE (TEXT "Text") (LITERAL-COMMENT (("-" . "")) "comment --> end"))`, encoderSHTML: `("Text" (@@ "comment --> end"))`, encoderText: `Text`, encoderZmk: useZmk, }, }, { descr: "Simple inline endnote", zmk: `[^endnote]`, expect: expectMap{ encoderHTML: `<sup id="fnref:1"><a class="zs-noteref" href="#fn:1" role="doc-noteref">1</a></sup>`, encoderMD: "", encoderSz: `(INLINE (ENDNOTE () (TEXT "endnote")))`, encoderSHTML: `((sup (@ (id . "fnref:1")) (a (@ (class . "zs-noteref") (href . "#fn:1") (role . "doc-noteref")) "1")))`, encoderText: `endnote`, encoderZmk: useZmk, }, }, { descr: "Simple mark", |
︙ | ︙ | |||
432 433 434 435 436 437 438 | }, { descr: "Mark with text", zmk: `[!mark|with text]`, expect: expectMap{ encoderHTML: `<a id="mark">with text</a>`, encoderMD: "with text", | | | | 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 | }, { descr: "Mark with text", zmk: `[!mark|with text]`, expect: expectMap{ encoderHTML: `<a id="mark">with text</a>`, encoderMD: "with text", encoderSz: `(INLINE (MARK "mark" "mark" "mark" (TEXT "with text")))`, encoderSHTML: `((a (@ (id . "mark")) "with text"))`, encoderText: `with text`, encoderZmk: useZmk, }, }, { descr: "Invalid Link", zmk: `[[link|00000000000000]]`, |
︙ | ︙ | |||
466 467 468 469 470 471 472 | encoderZmk: useZmk, }, }, { descr: "Dummy Link", zmk: `[[abc]]`, expect: expectMap{ | | | | | | | | 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 497 498 499 500 501 502 503 504 505 506 507 508 509 510 | encoderZmk: useZmk, }, }, { descr: "Dummy Link", zmk: `[[abc]]`, expect: expectMap{ encoderHTML: `<a href="abc" rel="external">abc</a>`, encoderMD: "[abc](abc)", encoderSz: `(INLINE (LINK-EXTERNAL () "abc"))`, encoderSHTML: `((a (@ (href . "abc") (rel . "external")) "abc"))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Simple URL", zmk: `[[https://zettelstore.de]]`, expect: expectMap{ encoderHTML: `<a href="https://zettelstore.de" rel="external">https://zettelstore.de</a>`, encoderMD: "<https://zettelstore.de>", encoderSz: `(INLINE (LINK-EXTERNAL () "https://zettelstore.de"))`, encoderSHTML: `((a (@ (href . "https://zettelstore.de") (rel . "external")) "https://zettelstore.de"))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "URL with Text", zmk: `[[Home|https://zettelstore.de]]`, expect: expectMap{ encoderHTML: `<a href="https://zettelstore.de" rel="external">Home</a>`, encoderMD: "[Home](https://zettelstore.de)", encoderSz: `(INLINE (LINK-EXTERNAL () "https://zettelstore.de" (TEXT "Home")))`, encoderSHTML: `((a (@ (href . "https://zettelstore.de") (rel . "external")) "Home"))`, encoderText: `Home`, encoderZmk: useZmk, }, }, { descr: "Simple Zettel ID", zmk: `[[00000000000100]]`, |
︙ | ︙ | |||
624 625 626 627 628 629 630 | }, { descr: "Dummy Embed", zmk: `{{abc}}`, expect: expectMap{ encoderHTML: `<img src="abc">`, encoderMD: "![abc](abc)", | | | 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 | }, { descr: "Dummy Embed", zmk: `{{abc}}`, expect: expectMap{ encoderHTML: `<img src="abc">`, encoderMD: "![abc](abc)", encoderSz: `(INLINE (EMBED () (EXTERNAL "abc") ""))`, encoderSHTML: `((img (@ (src . "abc"))))`, encoderText: ``, encoderZmk: useZmk, }, }, { descr: "Inline HTML Zettel", |
︙ | ︙ | |||
648 649 650 651 652 653 654 | }, { descr: "Inline Text Zettel", zmk: `@@<hr>@@{="text"}`, expect: expectMap{ encoderHTML: ``, encoderMD: "<hr>", | | | 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 | }, { descr: "Inline Text Zettel", zmk: `@@<hr>@@{="text"}`, expect: expectMap{ encoderHTML: ``, encoderMD: "<hr>", encoderSz: `(INLINE (LITERAL-ZETTEL (("" . "text")) "<hr>"))`, encoderSHTML: `(())`, encoderText: `<hr>`, encoderZmk: useZmk, }, }, { descr: "", |
︙ | ︙ |
Changes to encoder/encoder_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 encoder_test import ( "fmt" "strings" "testing" | > > > | | > < | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package encoder_test import ( "fmt" "strings" "testing" "t73f.de/r/sx/sxreader" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/encoder" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" _ "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. _ "zettelstore.de/z/encoder/szenc" // Allow to use sz encoder. |
︙ | ︙ | |||
112 113 114 115 116 117 118 | return } val, err := sxreader.MakeReader(strings.NewReader(exp)).Read() if err != nil { t.Error(err) return } | | | 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | return } val, err := sxreader.MakeReader(strings.NewReader(exp)).Read() if err != nil { t.Error(err) return } got := val.String() if exp != got { prefix := fmt.Sprintf("Test #%d", testNum) if d := descr; d != "" { prefix += "\nReason: " + d } prefix += "\nMode: " + pe.mode() t.Errorf("%s\n\nExpected: %q\nGot: %q", prefix, exp, got) |
︙ | ︙ |
Changes to encoder/htmlenc/htmlenc.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 htmlenc encodes the abstract syntax tree into HTML5 via zettelstore-client. package htmlenc import ( "io" "strings" | > > > | | | | > > | > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- // Package htmlenc encodes the abstract syntax tree into HTML5 via zettelstore-client. package htmlenc import ( "io" "strings" "t73f.de/r/sx" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/shtml" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/encoder/szenc" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register( api.EncoderHTML, func(params *encoder.CreateParameter) encoder.Encoder { return Create(params) }, ) } // Create an encoder. func Create(params *encoder.CreateParameter) *Encoder { // We need a new transformer every time, because tx.inVerse must be unique. // If we can refactor it out, the transformer can be created only once. return &Encoder{ tx: szenc.NewTransformer(), th: shtml.NewEvaluator(1), lang: params.Lang, textEnc: textenc.Create(), } } type Encoder struct { tx *szenc.Transformer |
︙ | ︙ | |||
71 72 73 74 75 76 77 | } xast := he.tx.GetSz(&zn.Ast) hast, err := he.th.Evaluate(xast, &env) if err != nil { return 0, err } | | < < | < | | < | < | | | | < | < | | | | | | | | | | 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 | } xast := he.tx.GetSz(&zn.Ast) hast, err := he.th.Evaluate(xast, &env) if err != nil { return 0, err } hen := shtml.Endnotes(&env) var head sx.ListBuilder head.Add(shtml.SymHead) head.Add(sx.Nil().Cons(sx.Nil().Cons(sx.Cons(sx.MakeSymbol("charset"), sx.MakeString("utf-8"))).Cons(sxhtml.SymAttr)).Cons(shtml.SymMeta)) head.ExtendBang(hm) var sb strings.Builder if hasTitle { he.textEnc.WriteInlines(&sb, &isTitle) } else { sb.Write(zn.Meta.Zid.Bytes()) } head.Add(sx.MakeList(shtml.SymAttrTitle, sx.MakeString(sb.String()))) var body sx.ListBuilder body.Add(shtml.SymBody) if hasTitle { body.Add(htitle.Cons(shtml.SymH1)) } body.ExtendBang(hast) if hen != nil { body.Add(sx.Cons(shtml.SymHR, nil)) body.Add(hen) } doc := sx.MakeList( sxhtml.SymDoctype, sx.MakeList(shtml.SymHtml, head.List(), body.List()), ) gen := sxhtml.NewGenerator().SetNewline() return gen.WriteHTML(w, doc) } // WriteMeta encodes meta data as HTML5. func (he *Encoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { env := shtml.MakeEnvironment(he.lang) hm, err := he.th.Evaluate(he.tx.GetMeta(m, evalMeta), &env) if err != nil { return 0, err } gen := sxhtml.NewGenerator().SetNewline() return gen.WriteListHTML(w, hm) } func (he *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return he.WriteBlocks(w, &zn.Ast) } // WriteBlocks encodes a block slice. func (he *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { env := shtml.MakeEnvironment(he.lang) hobj, err := he.th.Evaluate(he.tx.GetSz(bs), &env) if err == nil { gen := sxhtml.NewGenerator() length, err2 := gen.WriteListHTML(w, hobj) if err2 != nil { return length, err2 } l, err2 := gen.WriteHTML(w, shtml.Endnotes(&env)) length += l return length, err2 } return 0, err } // WriteInlines writes an inline slice to the writer func (he *Encoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { env := shtml.MakeEnvironment(he.lang) hobj, err := he.th.Evaluate(he.tx.GetSz(is), &env) if err == nil { gen := sxhtml.NewGenerator() length, err2 := gen.WriteListHTML(w, hobj) if err2 != nil { return length, err2 } return length, nil } return 0, err } |
Changes to encoder/mdenc/mdenc.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 mdenc encodes the abstract syntax tree back into Markdown. package mdenc import ( "io" | > > > | > > > > | > | > | > | > | < | | | | | | | | | | | | | > > | | > > > > > > > > > > > > > > > > > > > > > | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- // Package mdenc encodes the abstract syntax tree back into Markdown. package mdenc import ( "io" "t73f.de/r/zsc/api" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/shtml" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register( api.EncoderMD, func(params *encoder.CreateParameter) encoder.Encoder { return Create(params) }, ) } // Create an encoder. func Create(params *encoder.CreateParameter) *Encoder { return &Encoder{lang: params.Lang} } type Encoder struct { lang string } // WriteZettel writes the encoded zettel to the writer. func (me *Encoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w, me.lang) v.acceptMeta(zn.InhMeta, evalMeta) if zn.InhMeta.YamlSep { v.b.WriteString("---\n") } else { v.b.WriteByte('\n') } ast.Walk(v, &zn.Ast) length, err := v.b.Flush() return length, err } // WriteMeta encodes meta data as markdown. func (me *Encoder) WriteMeta(w io.Writer, m *meta.Meta, evalMeta encoder.EvalMetaFunc) (int, error) { v := newVisitor(w, me.lang) v.acceptMeta(m, evalMeta) length, err := v.b.Flush() return length, err } func (v *visitor) acceptMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) { for _, p := range m.ComputedPairs() { key := p.Key v.b.WriteStrings(key, ": ") if meta.Type(key) == meta.TypeZettelmarkup { is := evalMeta(p.Value) ast.Walk(v, &is) } else { v.b.WriteString(p.Value) } v.b.WriteByte('\n') } } func (me *Encoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) { return me.WriteBlocks(w, &zn.Ast) } // WriteBlocks writes the content of a block slice to the writer. func (me *Encoder) WriteBlocks(w io.Writer, bs *ast.BlockSlice) (int, error) { v := newVisitor(w, me.lang) ast.Walk(v, bs) length, err := v.b.Flush() return length, err } // WriteInlines writes an inline slice to the writer func (me *Encoder) WriteInlines(w io.Writer, is *ast.InlineSlice) (int, error) { v := newVisitor(w, me.lang) ast.Walk(v, is) length, err := v.b.Flush() return length, err } // visitor writes the abstract syntax tree to an EncWriter. type visitor struct { b encoder.EncWriter listInfo []int listPrefix string langStack shtml.LangStack quoteNesting uint } func newVisitor(w io.Writer, lang string) *visitor { return &visitor{b: encoder.NewEncWriter(w), langStack: shtml.NewLangStack(lang)} } // pushAttribute adds the current attributes to the visitor. func (v *visitor) pushAttributes(a attrs.Attributes) { if value, ok := a.Get("lang"); ok { v.langStack.Push(value) } else { v.langStack.Dup() } } // popAttributes removes the current attributes from the visitor. func (v *visitor) popAttributes() { v.langStack.Pop() } // getLanguage returns the current language, func (v *visitor) getLanguage() string { return v.langStack.Top() } func (v *visitor) getQuotes() (string, string, bool) { qi := shtml.GetQuoteInfo(v.getLanguage()) leftQ, rightQ := qi.GetQuotes(v.quoteNesting) return leftQ, rightQ, qi.GetNBSp() } func (v *visitor) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.BlockSlice: v.visitBlockSlice(n) case *ast.VerbatimNode: |
︙ | ︙ | |||
113 114 115 116 117 118 119 | case *ast.NestedListNode: v.visitNestedList(n) case *ast.DescriptionListNode: return nil // Should write no content case *ast.TableNode: return nil // Should write no content case *ast.TextNode: | < < | | 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | case *ast.NestedListNode: v.visitNestedList(n) case *ast.DescriptionListNode: return nil // Should write no content case *ast.TableNode: return nil // Should write no content case *ast.TextNode: v.b.WriteString(n.Text) case *ast.BreakNode: v.visitBreak(n) case *ast.LinkNode: v.visitLink(n) case *ast.EmbedRefNode: v.visitEmbedRef(n) case *ast.FootnoteNode: |
︙ | ︙ | |||
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 | } } func (v *visitor) visitRegion(rn *ast.RegionNode) { if rn.Kind != ast.RegionQuote { return } first := true for _, bn := range rn.Blocks { pn, ok := bn.(*ast.ParaNode) if !ok { continue } if !first { v.b.WriteString("\n\n") } first = false v.b.WriteString("> ") ast.Walk(v, &pn.Inlines) } } func (v *visitor) visitHeading(hn *ast.HeadingNode) { const headingSigns = "###### " v.b.WriteString(headingSigns[len(headingSigns)-hn.Level-1:]) ast.Walk(v, &hn.Inlines) } func (v *visitor) visitNestedList(ln *ast.NestedListNode) { switch ln.Kind { | > > > > > > | 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 | } } func (v *visitor) visitRegion(rn *ast.RegionNode) { if rn.Kind != ast.RegionQuote { return } v.pushAttributes(rn.Attrs) defer v.popAttributes() first := true for _, bn := range rn.Blocks { pn, ok := bn.(*ast.ParaNode) if !ok { continue } if !first { v.b.WriteString("\n\n") } first = false v.b.WriteString("> ") ast.Walk(v, &pn.Inlines) } } func (v *visitor) visitHeading(hn *ast.HeadingNode) { v.pushAttributes(hn.Attrs) defer v.popAttributes() const headingSigns = "###### " v.b.WriteString(headingSigns[len(headingSigns)-hn.Level-1:]) ast.Walk(v, &hn.Inlines) } func (v *visitor) visitNestedList(ln *ast.NestedListNode) { switch ln.Kind { |
︙ | ︙ | |||
259 260 261 262 263 264 265 | ast.Walk(v, in) } } v.listPrefix = prefix } | < < < < > > > > > > | 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 | ast.Walk(v, in) } } v.listPrefix = prefix } func (v *visitor) visitBreak(bn *ast.BreakNode) { if bn.Hard { v.b.WriteString("\\\n") } else { v.b.WriteByte('\n') } if l := len(v.listInfo); l > 0 { if v.listPrefix == "" { v.writeSpaces(4*l - 4 + v.listInfo[l-1]) } else { v.writeSpaces(4*l - 4) v.b.WriteString(v.listPrefix) } } } func (v *visitor) visitLink(ln *ast.LinkNode) { v.pushAttributes(ln.Attrs) defer v.popAttributes() v.writeReference(ln.Ref, ln.Inlines) } func (v *visitor) visitEmbedRef(en *ast.EmbedRefNode) { v.pushAttributes(en.Attrs) defer v.popAttributes() v.b.WriteByte('!') v.writeReference(en.Ref, en.Inlines) } func (v *visitor) writeReference(ref *ast.Reference, is ast.InlineSlice) { if ref.State == ast.RefStateQuery { ast.Walk(v, &is) |
︙ | ︙ | |||
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | if ref.State != ast.RefStateExternal || ref.URL == nil { return false } return ref.URL.Scheme != "" } func (v *visitor) visitFormat(fn *ast.FormatNode) { switch fn.Kind { case ast.FormatEmph: v.b.WriteByte('*') ast.Walk(v, &fn.Inlines) v.b.WriteByte('*') case ast.FormatStrong: v.b.WriteString("__") ast.Walk(v, &fn.Inlines) v.b.WriteString("__") case ast.FormatQuote: | > > > | < < > > > > > > > > > > > > > > > | | | 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 | if ref.State != ast.RefStateExternal || ref.URL == nil { return false } return ref.URL.Scheme != "" } func (v *visitor) visitFormat(fn *ast.FormatNode) { v.pushAttributes(fn.Attrs) defer v.popAttributes() switch fn.Kind { case ast.FormatEmph: v.b.WriteByte('*') ast.Walk(v, &fn.Inlines) v.b.WriteByte('*') case ast.FormatStrong: v.b.WriteString("__") ast.Walk(v, &fn.Inlines) v.b.WriteString("__") case ast.FormatQuote: v.writeQuote(fn) case ast.FormatMark: v.b.WriteString("<mark>") ast.Walk(v, &fn.Inlines) v.b.WriteString("</mark>") default: ast.Walk(v, &fn.Inlines) } } func (v *visitor) writeQuote(fn *ast.FormatNode) { leftQ, rightQ, withNbsp := v.getQuotes() v.b.WriteString(leftQ) if withNbsp { v.b.WriteString(" ") } v.quoteNesting++ ast.Walk(v, &fn.Inlines) v.quoteNesting-- if withNbsp { v.b.WriteString(" ") } v.b.WriteString(rightQ) } func (v *visitor) visitLiteral(ln *ast.LiteralNode) { switch ln.Kind { case ast.LiteralProg, ast.LiteralInput, ast.LiteralOutput: v.b.WriteByte('`') v.b.Write(ln.Content) v.b.WriteByte('`') case ast.LiteralComment, ast.LiteralHTML: // ignore everything default: v.b.Write(ln.Content) } } func (v *visitor) writeSpaces(n int) { for range n { v.b.WriteByte(' ') } } |
Changes to encoder/shtmlenc/shtmlenc.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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 shtmlenc encodes the abstract syntax tree into a s-expr which represents HTML. package shtmlenc import ( "io" | > > > | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- // Package shtmlenc encodes the abstract syntax tree into a s-expr which represents HTML. package shtmlenc import ( "io" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/shtml" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/encoder/szenc" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register(api.EncoderSHTML, func(params *encoder.CreateParameter) encoder.Encoder { return Create(params) }) } // Create a SHTML encoder func Create(params *encoder.CreateParameter) *Encoder { // We need a new transformer every time, because tx.inVerse must be unique. // If we can refactor it out, the transformer can be created only once. return &Encoder{ tx: szenc.NewTransformer(), th: shtml.NewEvaluator(1), lang: params.Lang, } } type Encoder struct { tx *szenc.Transformer th *shtml.Evaluator |
︙ | ︙ |
Changes to encoder/szenc/szenc.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 szenc encodes the abstract syntax tree into a s-expr for zettel. package szenc import ( "io" | > > > | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- // Package szenc encodes the abstract syntax tree into a s-expr for zettel. package szenc import ( "io" "t73f.de/r/sx" "t73f.de/r/zsc/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register(api.EncoderSz, func(*encoder.CreateParameter) encoder.Encoder { return Create() }) |
︙ | ︙ |
Changes to encoder/szenc/transform.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 szenc import ( "encoding/base64" "fmt" "strings" | > > > | | | < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | | | | | | | | | | < < | | < < < < < | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | < | > | > > > > | | | | | | | | | | | | | | | | | > > > > > > > | | | | | > > > > > > > > > > > > | | | | | | | | | | | | | | | | | > > > > > > > > > > > > | | < < < < < > | > > > > > > > > > > > > | | | | | | | < | | | | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package szenc import ( "encoding/base64" "fmt" "strings" "t73f.de/r/sx" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/sz" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) // NewTransformer returns a new transformer to create s-expressions from AST nodes. func NewTransformer() *Transformer { t := Transformer{} return &t } type Transformer struct { inVerse bool } func (t *Transformer) GetSz(node ast.Node) *sx.Pair { switch n := node.(type) { case *ast.BlockSlice: return t.getBlockList(n).Cons(sz.SymBlock) case *ast.InlineSlice: return t.getInlineList(*n).Cons(sz.SymInline) case *ast.ParaNode: return t.getInlineList(n.Inlines).Cons(sz.SymPara) case *ast.VerbatimNode: return sx.MakeList( mapGetS(mapVerbatimKindS, n.Kind), getAttributes(n.Attrs), sx.MakeString(string(n.Content)), ) case *ast.RegionNode: return t.getRegion(n) case *ast.HeadingNode: return t.getInlineList(n.Inlines). Cons(sx.MakeString(n.Fragment)). Cons(sx.MakeString(n.Slug)). Cons(getAttributes(n.Attrs)). Cons(sx.Int64(int64(n.Level))). Cons(sz.SymHeading) case *ast.HRuleNode: return sx.MakeList(sz.SymThematic, getAttributes(n.Attrs)) case *ast.NestedListNode: return t.getNestedList(n) case *ast.DescriptionListNode: return t.getDescriptionList(n) case *ast.TableNode: return t.getTable(n) case *ast.TranscludeNode: return sx.MakeList(sz.SymTransclude, getAttributes(n.Attrs), getReference(n.Ref)) case *ast.BLOBNode: return t.getBLOB(n) case *ast.TextNode: return sx.MakeList(sz.SymText, sx.MakeString(n.Text)) case *ast.BreakNode: if n.Hard { return sx.MakeList(sz.SymHard) } return sx.MakeList(sz.SymSoft) case *ast.LinkNode: return t.getLink(n) case *ast.EmbedRefNode: return t.getInlineList(n.Inlines). Cons(sx.MakeString(n.Syntax)). Cons(getReference(n.Ref)). Cons(getAttributes(n.Attrs)). Cons(sz.SymEmbed) case *ast.EmbedBLOBNode: return t.getEmbedBLOB(n) case *ast.CiteNode: return t.getInlineList(n.Inlines). Cons(sx.MakeString(n.Key)). Cons(getAttributes(n.Attrs)). Cons(sz.SymCite) case *ast.FootnoteNode: // (ENDNODE attrs InlineElement ...) return t.getInlineList(n.Inlines).Cons(getAttributes(n.Attrs)).Cons(sz.SymEndnote) case *ast.MarkNode: return t.getInlineList(n.Inlines). Cons(sx.MakeString(n.Fragment)). Cons(sx.MakeString(n.Slug)). Cons(sx.MakeString(n.Mark)). Cons(sz.SymMark) case *ast.FormatNode: return t.getInlineList(n.Inlines). Cons(getAttributes(n.Attrs)). Cons(mapGetS(mapFormatKindS, n.Kind)) case *ast.LiteralNode: return sx.MakeList( mapGetS(mapLiteralKindS, n.Kind), getAttributes(n.Attrs), sx.MakeString(string(n.Content)), ) } return sx.MakeList(sz.SymUnknown, sx.MakeString(fmt.Sprintf("%T %v", node, node))) } var mapVerbatimKindS = map[ast.VerbatimKind]*sx.Symbol{ ast.VerbatimZettel: sz.SymVerbatimZettel, ast.VerbatimProg: sz.SymVerbatimProg, ast.VerbatimEval: sz.SymVerbatimEval, ast.VerbatimMath: sz.SymVerbatimMath, ast.VerbatimComment: sz.SymVerbatimComment, ast.VerbatimHTML: sz.SymVerbatimHTML, } var mapFormatKindS = map[ast.FormatKind]*sx.Symbol{ ast.FormatEmph: sz.SymFormatEmph, ast.FormatStrong: sz.SymFormatStrong, ast.FormatDelete: sz.SymFormatDelete, ast.FormatInsert: sz.SymFormatInsert, ast.FormatSuper: sz.SymFormatSuper, ast.FormatSub: sz.SymFormatSub, ast.FormatQuote: sz.SymFormatQuote, ast.FormatMark: sz.SymFormatMark, ast.FormatSpan: sz.SymFormatSpan, } var mapLiteralKindS = map[ast.LiteralKind]*sx.Symbol{ ast.LiteralZettel: sz.SymLiteralZettel, ast.LiteralProg: sz.SymLiteralProg, ast.LiteralInput: sz.SymLiteralInput, ast.LiteralOutput: sz.SymLiteralOutput, ast.LiteralComment: sz.SymLiteralComment, ast.LiteralHTML: sz.SymLiteralHTML, ast.LiteralMath: sz.SymLiteralMath, } var mapRegionKindS = map[ast.RegionKind]*sx.Symbol{ ast.RegionSpan: sz.SymRegionBlock, ast.RegionQuote: sz.SymRegionQuote, ast.RegionVerse: sz.SymRegionVerse, } func (t *Transformer) getRegion(rn *ast.RegionNode) *sx.Pair { saveInVerse := t.inVerse if rn.Kind == ast.RegionVerse { t.inVerse = true } symBlocks := t.getBlockList(&rn.Blocks) t.inVerse = saveInVerse return t.getInlineList(rn.Inlines). Cons(symBlocks). Cons(getAttributes(rn.Attrs)). Cons(mapGetS(mapRegionKindS, rn.Kind)) } var mapNestedListKindS = map[ast.NestedListKind]*sx.Symbol{ ast.NestedListOrdered: sz.SymListOrdered, ast.NestedListUnordered: sz.SymListUnordered, ast.NestedListQuote: sz.SymListQuote, } func (t *Transformer) getNestedList(ln *ast.NestedListNode) *sx.Pair { nlistObjs := make(sx.Vector, len(ln.Items)+1) nlistObjs[0] = mapGetS(mapNestedListKindS, ln.Kind) isCompact := isCompactList(ln.Items) for i, item := range ln.Items { if isCompact && len(item) > 0 { paragraph := t.GetSz(item[0]) nlistObjs[i+1] = paragraph.Tail().Cons(sz.SymInline) continue } itemObjs := make(sx.Vector, len(item)) for j, in := range item { itemObjs[j] = t.GetSz(in) } if isCompact { nlistObjs[i+1] = sx.MakeList(itemObjs...).Cons(sz.SymInline) } else { nlistObjs[i+1] = sx.MakeList(itemObjs...).Cons(sz.SymBlock) } } return sx.MakeList(nlistObjs...) } func isCompactList(itemSlice []ast.ItemSlice) bool { for _, items := range itemSlice { if len(items) > 1 { return false } if len(items) == 1 { if _, ok := items[0].(*ast.ParaNode); !ok { return false } } } return true } func (t *Transformer) getDescriptionList(dn *ast.DescriptionListNode) *sx.Pair { dlObjs := make(sx.Vector, 2*len(dn.Descriptions)+1) dlObjs[0] = sz.SymDescription for i, def := range dn.Descriptions { dlObjs[2*i+1] = t.getInlineList(def.Term) descObjs := make(sx.Vector, len(def.Descriptions)) for j, b := range def.Descriptions { dVal := make(sx.Vector, len(b)) for k, dn := range b { dVal[k] = t.GetSz(dn) } descObjs[j] = sx.MakeList(dVal...).Cons(sz.SymBlock) } dlObjs[2*i+2] = sx.MakeList(descObjs...).Cons(sz.SymBlock) } return sx.MakeList(dlObjs...) } func (t *Transformer) getTable(tn *ast.TableNode) *sx.Pair { tObjs := make(sx.Vector, len(tn.Rows)+2) tObjs[0] = sz.SymTable tObjs[1] = t.getHeader(tn.Header) for i, row := range tn.Rows { tObjs[i+2] = t.getRow(row) } return sx.MakeList(tObjs...) } func (t *Transformer) getHeader(header ast.TableRow) *sx.Pair { if len(header) == 0 { return nil } return t.getRow(header) } func (t *Transformer) getRow(row ast.TableRow) *sx.Pair { rObjs := make(sx.Vector, len(row)) for i, cell := range row { rObjs[i] = t.getCell(cell) } return sx.MakeList(rObjs...) } var alignmentSymbolS = map[ast.Alignment]*sx.Symbol{ ast.AlignDefault: sz.SymCell, ast.AlignLeft: sz.SymCellLeft, ast.AlignCenter: sz.SymCellCenter, ast.AlignRight: sz.SymCellRight, } func (t *Transformer) getCell(cell *ast.TableCell) *sx.Pair { return t.getInlineList(cell.Inlines).Cons(mapGetS(alignmentSymbolS, cell.Align)) } func (t *Transformer) getBLOB(bn *ast.BLOBNode) *sx.Pair { var lastObj sx.Object if bn.Syntax == meta.SyntaxSVG { lastObj = sx.MakeString(string(bn.Blob)) } else { lastObj = getBase64String(bn.Blob) } return sx.MakeList( sz.SymBLOB, t.getInlineList(bn.Description), sx.MakeString(bn.Syntax), lastObj, ) } var mapRefStateLink = map[ast.RefState]*sx.Symbol{ ast.RefStateInvalid: sz.SymLinkInvalid, ast.RefStateZettel: sz.SymLinkZettel, ast.RefStateSelf: sz.SymLinkSelf, ast.RefStateFound: sz.SymLinkFound, ast.RefStateBroken: sz.SymLinkBroken, ast.RefStateHosted: sz.SymLinkHosted, ast.RefStateBased: sz.SymLinkBased, ast.RefStateQuery: sz.SymLinkQuery, ast.RefStateExternal: sz.SymLinkExternal, } func (t *Transformer) getLink(ln *ast.LinkNode) *sx.Pair { return t.getInlineList(ln.Inlines). Cons(sx.MakeString(ln.Ref.Value)). Cons(getAttributes(ln.Attrs)). Cons(mapGetS(mapRefStateLink, ln.Ref.State)) } func (t *Transformer) getEmbedBLOB(en *ast.EmbedBLOBNode) *sx.Pair { tail := t.getInlineList(en.Inlines) if en.Syntax == meta.SyntaxSVG { tail = tail.Cons(sx.MakeString(string(en.Blob))) } else { tail = tail.Cons(getBase64String(en.Blob)) } return tail.Cons(sx.MakeString(en.Syntax)).Cons(getAttributes(en.Attrs)).Cons(sz.SymEmbedBLOB) } func (t *Transformer) getBlockList(bs *ast.BlockSlice) *sx.Pair { objs := make(sx.Vector, len(*bs)) for i, n := range *bs { objs[i] = t.GetSz(n) } return sx.MakeList(objs...) } func (t *Transformer) getInlineList(is ast.InlineSlice) *sx.Pair { objs := make(sx.Vector, len(is)) for i, n := range is { objs[i] = t.GetSz(n) } return sx.MakeList(objs...) } func getAttributes(a attrs.Attributes) sx.Object { if a.IsEmpty() { return sx.Nil() } keys := a.Keys() objs := make(sx.Vector, 0, len(keys)) for _, k := range keys { objs = append(objs, sx.Cons(sx.MakeString(k), sx.MakeString(a[k]))) } return sx.MakeList(objs...) } var mapRefStateS = map[ast.RefState]*sx.Symbol{ ast.RefStateInvalid: sz.SymRefStateInvalid, ast.RefStateZettel: sz.SymRefStateZettel, ast.RefStateSelf: sz.SymRefStateSelf, ast.RefStateFound: sz.SymRefStateFound, ast.RefStateBroken: sz.SymRefStateBroken, ast.RefStateHosted: sz.SymRefStateHosted, ast.RefStateBased: sz.SymRefStateBased, ast.RefStateQuery: sz.SymRefStateQuery, ast.RefStateExternal: sz.SymRefStateExternal, } func getReference(ref *ast.Reference) *sx.Pair { return sx.MakeList(mapGetS(mapRefStateS, ref.State), sx.MakeString(ref.Value)) } var mapMetaTypeS = map[*meta.DescriptionType]*sx.Symbol{ meta.TypeCredential: sz.SymTypeCredential, meta.TypeEmpty: sz.SymTypeEmpty, meta.TypeID: sz.SymTypeID, meta.TypeIDSet: sz.SymTypeIDSet, meta.TypeNumber: sz.SymTypeNumber, meta.TypeString: sz.SymTypeString, meta.TypeTagSet: sz.SymTypeTagSet, meta.TypeTimestamp: sz.SymTypeTimestamp, meta.TypeURL: sz.SymTypeURL, meta.TypeWord: sz.SymTypeWord, meta.TypeZettelmarkup: sz.SymTypeZettelmarkup, } func (t *Transformer) GetMeta(m *meta.Meta, evalMeta encoder.EvalMetaFunc) *sx.Pair { pairs := m.ComputedPairs() objs := make(sx.Vector, 0, len(pairs)) for _, p := range pairs { key := p.Key ty := m.Type(key) symType := mapGetS(mapMetaTypeS, ty) var obj sx.Object if ty.IsSet { setList := meta.ListFromValue(p.Value) setObjs := make(sx.Vector, len(setList)) for i, val := range setList { setObjs[i] = sx.MakeString(val) } obj = sx.MakeList(setObjs...) } else if ty == meta.TypeZettelmarkup { is := evalMeta(p.Value) obj = t.getInlineList(is) } else { obj = sx.MakeString(p.Value) } objs = append(objs, sx.Nil().Cons(obj).Cons(sx.MakeSymbol(key)).Cons(symType)) } return sx.MakeList(objs...).Cons(sz.SymMeta) } func mapGetS[T comparable](m map[T]*sx.Symbol, k T) *sx.Symbol { if result, found := m[k]; found { return result } return sx.MakeSymbol(fmt.Sprintf("**%v:NOT-FOUND**", k)) } func getBase64String(data []byte) sx.String { var sb strings.Builder encoder := base64.NewEncoder(base64.StdEncoding, &sb) _, err := encoder.Write(data) if err == nil { err = encoder.Close() } if err == nil { return sx.MakeString(sb.String()) } return sx.MakeString("") } |
Changes to encoder/textenc/textenc.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 textenc encodes the abstract syntax tree into its text. package textenc import ( "io" | > > > | > | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package textenc encodes the abstract syntax tree into its text. package textenc import ( "io" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/zettel/meta" ) func init() { encoder.Register(api.EncoderText, func(*encoder.CreateParameter) encoder.Encoder { return Create() }) |
︙ | ︙ | |||
125 126 127 128 129 130 131 | return nil case *ast.TableNode: v.visitTable(n) return nil case *ast.TranscludeNode, *ast.BLOBNode: return nil case *ast.TextNode: | | < < < | 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | return nil case *ast.TableNode: v.visitTable(n) return nil case *ast.TranscludeNode, *ast.BLOBNode: return nil case *ast.TextNode: v.visitText(n.Text) return nil case *ast.BreakNode: if n.Hard { v.b.WriteByte('\n') } else { v.b.WriteByte(' ') } |
︙ | ︙ | |||
223 224 225 226 227 228 229 230 231 232 233 234 235 | func (v *visitor) visitInlineSlice(is *ast.InlineSlice) { for i, in := range *is { v.inlinePos = i ast.Walk(v, in) } v.inlinePos = 0 } func (v *visitor) writePosChar(pos int, ch byte) { if pos > 0 { v.b.WriteByte(ch) } } | > > > > > > > > > > > > > > > | 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 | func (v *visitor) visitInlineSlice(is *ast.InlineSlice) { for i, in := range *is { v.inlinePos = i ast.Walk(v, in) } v.inlinePos = 0 } func (v *visitor) visitText(s string) { spaceFound := false for _, ch := range s { if input.IsSpace(ch) { if !spaceFound { v.b.WriteByte(' ') spaceFound = true } continue } spaceFound = false v.b.WriteString(string(ch)) } } func (v *visitor) writePosChar(pos int, ch byte) { if pos > 0 { v.b.WriteByte(ch) } } |
Changes to encoder/write.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 encoder import ( "encoding/base64" "io" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package encoder import ( "encoding/base64" "io" |
︙ | ︙ |
Changes to encoder/zmkenc/zmkenc.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 zmkenc encodes the abstract syntax tree back into Zettelmarkup. package zmkenc import ( "fmt" "io" "strings" | > > > | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package zmkenc encodes the abstract syntax tree back into Zettelmarkup. package zmkenc import ( "fmt" "io" "strings" "t73f.de/r/zsc/api" "t73f.de/r/zsc/attrs" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ | |||
133 134 135 136 137 138 139 | case *ast.TranscludeNode: v.b.WriteStrings("{{{", n.Ref.String(), "}}}") v.visitAttributes(n.Attrs) case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.visitText(n) | < < | 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | case *ast.TranscludeNode: v.b.WriteStrings("{{{", n.Ref.String(), "}}}") v.visitAttributes(n.Attrs) case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.visitText(n) case *ast.BreakNode: v.visitBreak(n) case *ast.LinkNode: v.visitLink(n) case *ast.EmbedRefNode: v.visitEmbedRef(n) case *ast.EmbedBLOBNode: |
︙ | ︙ | |||
368 369 370 371 372 373 374 | last = i + 1 continue } if i < len(tn.Text)-1 { s := tn.Text[i : i+2] if escapeSeqs.Has(s) { v.b.WriteString(tn.Text[last:i]) | | | 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | last = i + 1 continue } if i < len(tn.Text)-1 { s := tn.Text[i : i+2] if escapeSeqs.Has(s) { v.b.WriteString(tn.Text[last:i]) for j := range len(s) { v.b.WriteBytes('\\', s[j]) } i++ last = i + 1 continue } } |
︙ | ︙ | |||
523 524 525 526 527 528 529 | } } v.b.WriteByte('}') } func (v *visitor) writeEscaped(s string, toEscape byte) { last := 0 | | | 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 | } } v.b.WriteByte('}') } func (v *visitor) writeEscaped(s string, toEscape byte) { last := 0 for i := range len(s) { if b := s[i]; b == toEscape || b == '\\' { v.b.WriteString(s[last:i]) v.b.WriteBytes('\\', b) last = i + 1 } } v.b.WriteString(s[last:]) } func syntaxToHTML(a attrs.Attributes) attrs.Attributes { return a.Clone().Set("", meta.SyntaxHTML).Remove(api.KeySyntax) } |
Changes to encoding/atom/atom.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 atom provides an Atom encoding. package atom import ( "bytes" "time" | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- // Package atom provides an Atom encoding. package atom import ( "bytes" "time" "t73f.de/r/zsc/api" "zettelstore.de/z/config" "zettelstore.de/z/encoding" "zettelstore.de/z/encoding/xml" "zettelstore.de/z/kernel" "zettelstore.de/z/query" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" |
︙ | ︙ | |||
77 78 79 80 81 82 83 | entryUpdated := "" if val, found := m.Get(api.KeyPublished); found { if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil { entryUpdated = published.UTC().Format(time.RFC3339) } } | | | 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | entryUpdated := "" if val, found := m.Get(api.KeyPublished); found { if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil { entryUpdated = published.UTC().Format(time.RFC3339) } } link := c.NewURLBuilderAbs().SetZid(m.Zid.ZettelID()).String() buf.WriteString(" <entry>\n") xml.WriteTag(buf, " ", "title", encoding.TitleAsText(m)) xml.WriteTag(buf, " ", "id", link) buf.WriteString(` <link rel="self" href="`) strfun.XMLEscape(buf, link) buf.WriteString(`"/>` + "\n") |
︙ | ︙ |
Changes to encoding/encoding.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 encoding provides helper functions for encodings. package encoding import ( "time" | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- // Package encoding provides helper functions for encodings. package encoding import ( "time" "t73f.de/r/zsc/api" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // LastUpdated returns the formated time of the zettel which was updated at the latest time. func LastUpdated(ml []*meta.Meta, timeFormat string) string { |
︙ | ︙ |
Changes to encoding/rss/rss.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 rss provides a RSS encoding. package rss import ( "bytes" "context" "time" | > > > | | 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) 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- // Package rss provides a RSS encoding. package rss import ( "bytes" "context" "time" "t73f.de/r/zsc/api" "zettelstore.de/z/config" "zettelstore.de/z/encoding" "zettelstore.de/z/encoding/xml" "zettelstore.de/z/kernel" "zettelstore.de/z/query" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" |
︙ | ︙ | |||
87 88 89 90 91 92 93 | itemPublished := "" if val, found := m.Get(api.KeyPublished); found { if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil { itemPublished = published.UTC().Format(time.RFC1123Z) } } | | | 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | itemPublished := "" if val, found := m.Get(api.KeyPublished); found { if published, err := time.ParseInLocation(id.TimestampLayout, val, time.Local); err == nil { itemPublished = published.UTC().Format(time.RFC1123Z) } } link := c.NewURLBuilderAbs().SetZid(m.Zid.ZettelID()).String() buf.WriteString(" <item>\n") xml.WriteTag(buf, " ", "title", encoding.TitleAsText(m)) xml.WriteTag(buf, " ", "link", link) xml.WriteTag(buf, " ", "guid", link) if itemPublished != "" { xml.WriteTag(buf, " ", "pubDate", itemPublished) |
︙ | ︙ |
Changes to encoding/xml/xml.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 xml provides helper for a XML-based encoding. package xml import ( "bytes" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- // Package xml provides helper for a XML-based encoding. package xml import ( "bytes" |
︙ | ︙ |
Changes to evaluator/evaluator.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- // 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 evaluator interprets and evaluates the AST. package evaluator import ( "context" "errors" "fmt" "path" "strconv" "strings" | > > > > > > | | > < > | > > > > > > > > > > > > > > > > > > > > > > > > > | | < > > > > | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package evaluator interprets and evaluates the AST. package evaluator import ( "bytes" "context" "errors" "fmt" "path" "strconv" "strings" "t73f.de/r/sx/sxbuiltins" "t73f.de/r/sx/sxreader" "t73f.de/r/zsc/api" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/parser" "zettelstore.de/z/parser/cleaner" "zettelstore.de/z/parser/draw" "zettelstore.de/z/query" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // Port contains all methods to retrieve zettel (or part of it) to evaluate a zettel. type Port interface { GetZettel(context.Context, id.Zid) (zettel.Zettel, error) QueryMeta(ctx context.Context, q *query.Query) ([]*meta.Meta, error) } // EvaluateZettel evaluates the given zettel in the given context, with the // given ports, and the given environment. func EvaluateZettel(ctx context.Context, port Port, rtConfig config.Config, zn *ast.ZettelNode) { switch zn.Syntax { case meta.SyntaxNone: // AST is empty, evaluate to a description list of metadata. zn.Ast = evaluateMetadata(zn.Meta) case meta.SyntaxSxn: zn.Ast = evaluateSxn(zn.Ast) default: EvaluateBlock(ctx, port, rtConfig, &zn.Ast) } } func evaluateSxn(bs ast.BlockSlice) ast.BlockSlice { // Check for structure made in parser/plain/plain.go:parseSxnBlocks if len(bs) == 1 { // If len(bs) > 1 --> an error was found during parsing if vn, isVerbatim := bs[0].(*ast.VerbatimNode); isVerbatim && vn.Kind == ast.VerbatimProg { if classAttr, hasClass := vn.Attrs.Get(""); hasClass && classAttr == meta.SyntaxSxn { rd := sxreader.MakeReader(bytes.NewReader(vn.Content)) if objs, err := rd.ReadAll(); err == nil { result := make(ast.BlockSlice, len(objs)) for i, obj := range objs { var buf bytes.Buffer sxbuiltins.Print(&buf, obj) result[i] = &ast.VerbatimNode{ Kind: ast.VerbatimProg, Attrs: attrs.Attributes{"": classAttr}, Content: buf.Bytes(), } } return result } } } } return bs } // EvaluateBlock evaluates the given block list in the given context, with // the given ports, and the given environment. func EvaluateBlock(ctx context.Context, port Port, rtConfig config.Config, bns *ast.BlockSlice) { evaluateNode(ctx, port, rtConfig, bns) cleaner.CleanBlockSlice(bns, true) |
︙ | ︙ | |||
257 258 259 260 261 262 263 | ml, err := e.port.QueryMeta(e.ctx, q) if err != nil { if errors.Is(err, &box.ErrNotAllowed{}) { return nil } return makeBlockNode(createInlineErrorText(nil, "Unable", "to", "search", "zettel")) } | | | 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | ml, err := e.port.QueryMeta(e.ctx, q) if err != nil { if errors.Is(err, &box.ErrNotAllowed{}) { return nil } return makeBlockNode(createInlineErrorText(nil, "Unable", "to", "search", "zettel")) } result, _ := QueryAction(e.ctx, q, ml, e.rtConfig) if result != nil { ast.Walk(e, result) } return result } func (e *evaluator) checkMaxTransclusions(ref *ast.Reference) ast.InlineNode { |
︙ | ︙ | |||
403 404 405 406 407 408 409 | if errors.Is(err, &box.ErrNotAllowed{}) { return nil } e.transcludeCount++ return createInlineErrorImage(en) } | | | 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 | if errors.Is(err, &box.ErrNotAllowed{}) { return nil } e.transcludeCount++ return createInlineErrorImage(en) } if syntax := zettel.Meta.GetDefault(api.KeySyntax, meta.DefaultSyntax); parser.IsImageFormat(syntax) { e.updateImageRefNode(en, zettel.Meta, syntax) return en } else if !parser.IsASTParser(syntax) { // Not embeddable. e.transcludeCount++ return createInlineErrorText(ref, "Not", "embeddable (syntax="+syntax+")") } |
︙ | ︙ | |||
532 533 534 535 536 537 538 | func (e *evaluator) evaluateEmbeddedInline(content []byte, syntax string) ast.InlineSlice { is := parser.ParseInlines(input.NewInput(content), syntax) ast.Walk(e, &is) return is } func (e *evaluator) evaluateEmbeddedZettel(zettel zettel.Zettel) *ast.ZettelNode { | | | 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 | func (e *evaluator) evaluateEmbeddedInline(content []byte, syntax string) ast.InlineSlice { is := parser.ParseInlines(input.NewInput(content), syntax) ast.Walk(e, &is) return is } func (e *evaluator) evaluateEmbeddedZettel(zettel zettel.Zettel) *ast.ZettelNode { zn := parser.ParseZettel(e.ctx, zettel, zettel.Meta.GetDefault(api.KeySyntax, meta.DefaultSyntax), e.rtConfig) ast.Walk(e, &zn.Ast) return zn } func findInlineSlice(bs *ast.BlockSlice, fragment string) ast.InlineSlice { if fragment == "" { return firstInlinesToEmbed(*bs) |
︙ | ︙ | |||
596 597 598 599 600 601 602 | ast.Walk(fs, bn) } } func (fs *fragmentSearcher) visitInlineSlice(is *ast.InlineSlice) { for i, in := range *is { if mn, ok := in.(*ast.MarkNode); ok && mn.Fragment == fs.fragment { | | | | < | 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 | ast.Walk(fs, bn) } } func (fs *fragmentSearcher) visitInlineSlice(is *ast.InlineSlice) { for i, in := range *is { if mn, ok := in.(*ast.MarkNode); ok && mn.Fragment == fs.fragment { ris := skipBreakeNodes((*is)[i+1:]) if len(mn.Inlines) > 0 { fs.result = append(ast.InlineSlice{}, mn.Inlines...) fs.result = append(fs.result, &ast.TextNode{Text: " "}) fs.result = append(fs.result, ris...) } else { fs.result = ris } return } ast.Walk(fs, in) } } func skipBreakeNodes(ins ast.InlineSlice) ast.InlineSlice { for i, in := range ins { switch in.(type) { case *ast.BreakNode: default: return ins[i:] } } return nil } |
Changes to evaluator/list.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 evaluator import ( "bytes" "context" "math" | > > > | | | | | | | | | | | | | | | > > | > > | | | | | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package evaluator import ( "bytes" "context" "math" "slices" "strconv" "strings" "t73f.de/r/zsc/api" "t73f.de/r/zsc/attrs" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/encoding/atom" "zettelstore.de/z/encoding/rss" "zettelstore.de/z/parser" "zettelstore.de/z/query" "zettelstore.de/z/zettel/meta" ) // QueryAction transforms a list of metadata according to query actions into a AST nested list. func QueryAction(ctx context.Context, q *query.Query, ml []*meta.Meta, rtConfig config.Config) (ast.BlockNode, int) { ap := actionPara{ ctx: ctx, q: q, ml: ml, kind: ast.NestedListUnordered, min: -1, max: -1, title: rtConfig.GetSiteName(), } actions := q.Actions() if len(actions) == 0 { return ap.createBlockNodeMeta("") } acts := make([]string, 0, len(actions)) for i, act := range actions { if strings.HasPrefix(act, api.NumberedAction[0:1]) { ap.kind = ast.NestedListOrdered continue } if strings.HasPrefix(act, api.MinAction) { if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 { ap.min = num continue } } if strings.HasPrefix(act, api.MaxAction) { if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 { ap.max = num continue } } if act == api.TitleAction && i+1 < len(actions) { ap.title = strings.Join(actions[i+1:], " ") break } if act == api.ReIndexAction { continue } acts = append(acts, act) } var firstUnknowAct string for _, act := range acts { switch act { case api.AtomAction: return ap.createBlockNodeAtom(rtConfig) case api.RSSAction: return ap.createBlockNodeRSS(rtConfig) case api.KeysAction: return ap.createBlockNodeMetaKeys() } key := strings.ToLower(act) switch meta.Type(key) { case meta.TypeWord: return ap.createBlockNodeWord(key) case meta.TypeTagSet: return ap.createBlockNodeTagSet(key) } if firstUnknowAct == "" { firstUnknowAct = act } } bn, numItems := ap.createBlockNodeMeta(strings.ToLower(firstUnknowAct)) if bn != nil && numItems == 0 && firstUnknowAct == strings.ToUpper(firstUnknowAct) { bn, numItems = ap.createBlockNodeMeta("") } return bn, numItems } type actionPara struct { ctx context.Context q *query.Query ml []*meta.Meta kind ast.NestedListKind min int max int title string } func (ap *actionPara) createBlockNodeWord(key string) (ast.BlockNode, int) { var buf bytes.Buffer ccs, bufLen := ap.prepareCatAction(key, &buf) if len(ccs) == 0 { return nil, 0 } items := make([]ast.ItemSlice, 0, len(ccs)) ccs.SortByName() for _, cat := range ccs { buf.WriteString(cat.Name) items = append(items, ast.ItemSlice{ast.CreateParaNode(&ast.LinkNode{ Attrs: nil, Ref: ast.ParseReference(buf.String()), Inlines: ast.InlineSlice{&ast.TextNode{Text: cat.Name}}, })}) buf.Truncate(bufLen) } return &ast.NestedListNode{ Kind: ap.kind, Items: items, Attrs: nil, }, len(items) } func (ap *actionPara) createBlockNodeTagSet(key string) (ast.BlockNode, int) { var buf bytes.Buffer ccs, bufLen := ap.prepareCatAction(key, &buf) if len(ccs) == 0 { return nil, 0 } ccs.SortByCount() ccs = ap.limitTags(ccs) countMap := ap.calcFontSizes(ccs) para := make(ast.InlineSlice, 0, len(ccs)) ccs.SortByName() for i, cat := range ccs { if i > 0 { para = append(para, &ast.TextNode{Text: " "}) } buf.WriteString(cat.Name) para = append(para, &ast.LinkNode{ Attrs: countMap[cat.Count], Ref: ast.ParseReference(buf.String()), Inlines: ast.InlineSlice{ &ast.TextNode{Text: cat.Name}, }, }, &ast.FormatNode{ Kind: ast.FormatSuper, Attrs: nil, Inlines: ast.InlineSlice{&ast.TextNode{Text: strconv.Itoa(cat.Count)}}, }, ) buf.Truncate(bufLen) } return &ast.ParaNode{Inlines: para}, len(ccs) } func (ap *actionPara) limitTags(ccs meta.CountedCategories) meta.CountedCategories { if min, max := ap.min, ap.max; min > 0 || max > 0 { if min < 0 { min = ccs[len(ccs)-1].Count } |
︙ | ︙ | |||
183 184 185 186 187 188 189 | } return temp } } return ccs } | | | | 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | } return temp } } return ccs } func (ap *actionPara) createBlockNodeMetaKeys() (ast.BlockNode, int) { arr := make(meta.Arrangement, 128) for _, m := range ap.ml { for k := range m.Map() { arr[k] = append(arr[k], m) } } if len(arr) == 0 { return nil, 0 } ccs := arr.Counted() ccs.SortByName() var buf bytes.Buffer bufLen := ap.prepareSimpleQuery(&buf) items := make([]ast.ItemSlice, 0, len(ccs)) |
︙ | ︙ | |||
215 216 217 218 219 220 221 | items = append(items, ast.ItemSlice{ast.CreateParaNode( &ast.LinkNode{ Attrs: nil, Ref: ast.ParseReference(q1), Inlines: ast.InlineSlice{&ast.TextNode{Text: cat.Name}}, }, | | | | | | | 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 | items = append(items, ast.ItemSlice{ast.CreateParaNode( &ast.LinkNode{ Attrs: nil, Ref: ast.ParseReference(q1), Inlines: ast.InlineSlice{&ast.TextNode{Text: cat.Name}}, }, &ast.TextNode{Text: " "}, &ast.TextNode{Text: "(" + strconv.Itoa(cat.Count) + ", "}, &ast.LinkNode{ Attrs: nil, Ref: ast.ParseReference(q2), Inlines: ast.InlineSlice{&ast.TextNode{Text: "values"}}, }, &ast.TextNode{Text: ")"}, )}) } return &ast.NestedListNode{ Kind: ap.kind, Items: items, Attrs: nil, }, len(items) } func (ap *actionPara) createBlockNodeMeta(key string) (ast.BlockNode, int) { if len(ap.ml) == 0 { return nil, 0 } items := make([]ast.ItemSlice, 0, len(ap.ml)) for _, m := range ap.ml { if key != "" { if _, found := m.Get(key); !found { continue } } items = append(items, ast.ItemSlice{ast.CreateParaNode(&ast.LinkNode{ Attrs: nil, Ref: ast.ParseReference(m.Zid.String()), Inlines: parser.ParseSpacedText(m.GetTitle()), })}) } return &ast.NestedListNode{ Kind: ap.kind, Items: items, Attrs: nil, }, len(items) } func (ap *actionPara) prepareCatAction(key string, buf *bytes.Buffer) (meta.CountedCategories, int) { if len(ap.ml) == 0 { return nil, 0 } ccs := meta.CreateArrangement(ap.ml, key).Counted() |
︙ | ︙ | |||
290 291 292 293 294 295 296 | const fontSizes = 6 // Must be the number of CSS classes zs-font-size-* in base.css const fontSizes64 = float64(fontSizes) func (*actionPara) calcFontSizes(ccs meta.CountedCategories) map[int]attrs.Attributes { var fsAttrs [fontSizes]attrs.Attributes var a attrs.Attributes | | | | 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 | const fontSizes = 6 // Must be the number of CSS classes zs-font-size-* in base.css const fontSizes64 = float64(fontSizes) func (*actionPara) calcFontSizes(ccs meta.CountedCategories) map[int]attrs.Attributes { var fsAttrs [fontSizes]attrs.Attributes var a attrs.Attributes for i := range fontSizes { fsAttrs[i] = a.AddClass("zs-font-size-" + strconv.Itoa(i)) } countMap := make(map[int]int, len(ccs)) for _, cat := range ccs { countMap[cat.Count]++ } countList := make([]int, 0, len(countMap)) for count := range countMap { countList = append(countList, count) } slices.Sort(countList) result := make(map[int]attrs.Attributes, len(countList)) if len(countList) <= fontSizes { // If we have less different counts, center them inside the fsAttrs vector. curSize := (fontSizes - len(countList)) / 2 for _, count := range countList { result[count] = fsAttrs[curSize] |
︙ | ︙ | |||
340 341 342 343 344 345 346 | } } return result } func calcBudget(total, curSize float64) float64 { return math.Round(total / (fontSizes64 - curSize)) } | | | | | | 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 | } } return result } func calcBudget(total, curSize float64) float64 { return math.Round(total / (fontSizes64 - curSize)) } func (ap *actionPara) createBlockNodeRSS(cfg config.Config) (ast.BlockNode, int) { var rssConfig rss.Configuration rssConfig.Setup(ap.ctx, cfg) rssConfig.Title = ap.title data := rssConfig.Marshal(ap.q, ap.ml) return &ast.VerbatimNode{ Kind: ast.VerbatimProg, Attrs: attrs.Attributes{"lang": "xml"}, Content: data, }, len(ap.ml) } func (ap *actionPara) createBlockNodeAtom(cfg config.Config) (ast.BlockNode, int) { var atomConfig atom.Configuration atomConfig.Setup(cfg) atomConfig.Title = ap.title data := atomConfig.Marshal(ap.q, ap.ml) return &ast.VerbatimNode{ Kind: ast.VerbatimProg, Attrs: attrs.Attributes{"lang": "xml"}, Content: data, }, len(ap.ml) } |
Changes to evaluator/metadata.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 evaluator import ( "zettelstore.de/z/ast" "zettelstore.de/z/zettel/meta" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package evaluator import ( "zettelstore.de/z/ast" "zettelstore.de/z/zettel/meta" |
︙ | ︙ | |||
43 44 45 46 47 48 49 | sliceData = []string{value} } makeLink := dt == meta.TypeID || dt == meta.TypeIDSet result := make(ast.InlineSlice, 0, 2*len(sliceData)-1) for i, val := range sliceData { if i > 0 { | | | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | sliceData = []string{value} } makeLink := dt == meta.TypeID || dt == meta.TypeIDSet result := make(ast.InlineSlice, 0, 2*len(sliceData)-1) for i, val := range sliceData { if i > 0 { result = append(result, &ast.TextNode{Text: " "}) } tn := &ast.TextNode{Text: val} if makeLink { result = append(result, &ast.LinkNode{ Ref: ast.ParseReference(val), Inlines: ast.InlineSlice{tn}, }) } else { result = append(result, tn) } } return result } |
Changes to go.mod.
1 2 | module zettelstore.de/z | | | | | | | > | > | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | module zettelstore.de/z go 1.23 require ( github.com/fsnotify/fsnotify v1.7.0 github.com/yuin/goldmark v1.7.4 golang.org/x/crypto v0.26.0 golang.org/x/term v0.24.0 golang.org/x/text v0.18.0 t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5 t73f.de/r/sxwebs v0.0.0-20240814085618-5b4b5c496c94 t73f.de/r/zsc v0.0.0-20240826124629-97640fce4430 ) require ( golang.org/x/sys v0.25.0 // indirect t73f.de/r/webs v0.0.0-20240814085020-19dac746d568 // indirect ) |
Changes to go.sum.
1 2 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= | | | | | | | | | | | | | | | > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5 h1:ug4hohM6pK28M8Uo0o3+XvjBure2wfEtuCnHVIdqBZY= t73f.de/r/sx v0.0.0-20240814083626-4df0ec6454b5/go.mod h1:VRvsWoBErPKvMieDMMk1hsh1tb9sA4ijEQWGw/TbtQ0= t73f.de/r/sxwebs v0.0.0-20240814085618-5b4b5c496c94 h1:gLneaEyYotvcY/dDznzdcSXK1RqsJVi2AfeYDc1iVwM= t73f.de/r/sxwebs v0.0.0-20240814085618-5b4b5c496c94/go.mod h1:83W3QFkmrniIKv6R+Xq+imvbSolhoutTnNhW0ErJoco= t73f.de/r/webs v0.0.0-20240814085020-19dac746d568 h1:Pa+vO2r++qhcShv0p7t/gIrJ1DHPMn4gopEXLxDmoRg= t73f.de/r/webs v0.0.0-20240814085020-19dac746d568/go.mod h1:NSoOON8be62MfQZzlCApK27Jt2zhIa6Vrmo9RJ4tOnQ= t73f.de/r/zsc v0.0.0-20240826124629-97640fce4430 h1:35PQJZlo05a1rJHTreTQn6bBfTcII9UN2lMxr/7YUFk= t73f.de/r/zsc v0.0.0-20240826124629-97640fce4430/go.mod h1:PWnU0AvNxVumQiQBMBr9GeGTaAv8ZD78voHaPIs0omI= |
Deleted input/entity.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted input/entity_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted input/input.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted input/input_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted input/runes.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to kernel/impl/auth.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 impl import ( "errors" "sync" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "errors" "sync" |
︙ | ︙ | |||
68 69 70 71 72 73 74 | func (as *authService) Start(*myKernel) error { as.mxService.Lock() defer as.mxService.Unlock() readonlyMode := as.GetNextConfig(kernel.AuthReadonly).(bool) owner := as.GetNextConfig(kernel.AuthOwner).(id.Zid) authMgr, err := as.createManager(readonlyMode, owner) if err != nil { | | | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | func (as *authService) Start(*myKernel) error { as.mxService.Lock() defer as.mxService.Unlock() readonlyMode := as.GetNextConfig(kernel.AuthReadonly).(bool) owner := as.GetNextConfig(kernel.AuthOwner).(id.Zid) authMgr, err := as.createManager(readonlyMode, owner) if err != nil { as.logger.Error().Err(err).Msg("Unable to create manager") return err } as.logger.Info().Msg("Start Manager") as.manager = authMgr return nil } |
︙ | ︙ |
Changes to kernel/impl/box.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 impl import ( "context" "errors" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "context" "errors" |
︙ | ︙ | |||
78 79 80 81 82 83 84 | } boxURIs = append(boxURIs, u.(*url.URL)) } ps.mxService.Lock() defer ps.mxService.Unlock() mgr, err := ps.createManager(boxURIs, kern.auth.manager, &kern.cfg) if err != nil { | | | | 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | } boxURIs = append(boxURIs, u.(*url.URL)) } ps.mxService.Lock() defer ps.mxService.Unlock() mgr, err := ps.createManager(boxURIs, kern.auth.manager, &kern.cfg) if err != nil { ps.logger.Error().Err(err).Msg("Unable to create manager") return err } ps.logger.Info().Str("location", mgr.Location()).Msg("Start Manager") if err = mgr.Start(context.Background()); err != nil { ps.logger.Error().Err(err).Msg("Unable to start manager") return err } kern.cfg.setBox(mgr) ps.manager = mgr return nil } |
︙ | ︙ |
Changes to kernel/impl/cfg.go.
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 impl import ( "context" "errors" "fmt" "strconv" "strings" "sync" | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "context" "errors" "fmt" "strconv" "strings" "sync" "t73f.de/r/zsc/api" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" |
︙ | ︙ | |||
177 178 179 180 181 182 183 | } cs.mxService.Unlock() cs.SwitchNextToCur() // Poor man's restart return nil } func (cs *configService) observe(ci box.UpdateInfo) { | | | 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | } cs.mxService.Unlock() cs.SwitchNextToCur() // Poor man's restart return nil } func (cs *configService) observe(ci box.UpdateInfo) { if (ci.Reason != box.OnZettel && ci.Reason != box.OnDelete) || ci.Zid == id.ConfigurationZid { cs.logger.Debug().Uint("reason", uint64(ci.Reason)).Zid(ci.Zid).Msg("observe") go func() { cs.mxService.RLock() mgr := cs.manager cs.mxService.RUnlock() if mgr != nil { cs.doUpdate(mgr) |
︙ | ︙ |
Changes to kernel/impl/cmd.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 impl import ( "fmt" "io" "os" "runtime/metrics" | > > > | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "fmt" "io" "os" "runtime/metrics" "slices" "strconv" "strings" "t73f.de/r/zsc/maps" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/strfun" ) type cmdSession struct { w io.Writer |
︙ | ︙ | |||
235 236 237 238 239 240 241 | listConfig func(*cmdSession, service), getConfig func(service, string) interface{}) { if len(args) == 0 { keys := make([]int, 0, len(sess.kern.srvs)) for k := range sess.kern.srvs { keys = append(keys, int(k)) } | | | 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | listConfig func(*cmdSession, service), getConfig func(service, string) interface{}) { if len(args) == 0 { keys := make([]int, 0, len(sess.kern.srvs)) for k := range sess.kern.srvs { keys = append(keys, int(k)) } slices.Sort(keys) for i, k := range keys { if i > 0 { sess.println() } srvD := sess.kern.srvs[kernel.Service(k)] sess.println("%% Service", srvD.name) listConfig(sess, srvD.srv) |
︙ | ︙ | |||
538 539 540 541 542 543 544 | workDir = err.Error() } execName, err := os.Executable() if err != nil { execName = err.Error() } envs := os.Environ() | | | 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 | workDir = err.Error() } execName, err := os.Executable() if err != nil { execName = err.Error() } envs := os.Environ() slices.Sort(envs) table := [][]string{ {"Key", "Value"}, {"workdir", workDir}, {"executable", execName}, } for _, env := range envs { |
︙ | ︙ |
Changes to kernel/impl/config.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 impl import ( "errors" "fmt" | > > > | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "errors" "fmt" "slices" "strconv" "strings" "sync" "t73f.de/r/zsc/maps" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/zettel/id" ) type parseFunc func(string) (any, error) type configDescription struct { |
︙ | ︙ | |||
194 195 196 197 198 199 200 | if val == nil { break } keys = append(keys, key) } } } | | | 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | if val == nil { break } keys = append(keys, key) } } } slices.Sort(keys) return keys } func (cfg *srvConfig) Freeze() { cfg.mxConfig.Lock() cfg.frozen = true cfg.mxConfig.Unlock() |
︙ | ︙ |
Changes to kernel/impl/core.go.
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 impl import ( "fmt" "net" "os" "runtime" "sync" "time" | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "fmt" "net" "os" "runtime" "sync" "time" "t73f.de/r/zsc/maps" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" ) type coreService struct { |
︙ | ︙ |
Changes to kernel/impl/impl.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 impl provides the kernel implementation. package impl import ( "errors" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package impl provides the kernel implementation. package impl import ( "errors" |
︙ | ︙ | |||
67 68 69 70 71 72 73 | srv service srvnum kernel.Service } type serviceDependency map[kernel.Service][]kernel.Service const ( defaultNormalLogLevel = logger.InfoLevel | | | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | srv service srvnum kernel.Service } type serviceDependency map[kernel.Service][]kernel.Service const ( defaultNormalLogLevel = logger.InfoLevel defaultSimpleLogLevel = logger.ErrorLevel ) // create a new kernel. func init() { kernel.Main = createKernel() } |
︙ | ︙ | |||
95 96 97 98 99 100 101 | kernel.AuthService: {&kern.auth, "auth", defaultNormalLogLevel}, kernel.BoxService: {&kern.box, "box", defaultNormalLogLevel}, kernel.WebService: {&kern.web, "web", defaultNormalLogLevel}, } kern.srvNames = make(map[string]serviceData, len(kern.srvs)) for key, srvD := range kern.srvs { if _, ok := kern.srvNames[srvD.name]; ok { | | | 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | kernel.AuthService: {&kern.auth, "auth", defaultNormalLogLevel}, kernel.BoxService: {&kern.box, "box", defaultNormalLogLevel}, kernel.WebService: {&kern.web, "web", defaultNormalLogLevel}, } kern.srvNames = make(map[string]serviceData, len(kern.srvs)) for key, srvD := range kern.srvs { if _, ok := kern.srvNames[srvD.name]; ok { kern.logger.Error().Str("service", srvD.name).Msg("Service data already set, ignore") } kern.srvNames[srvD.name] = serviceData{srvD.srv, key} l := logger.New(lw, strings.ToUpper(srvD.name)).SetLevel(srvD.logLevel) kern.logger.Debug().Str("service", srvD.name).Msg("Initialize") srvD.srv.Initialize(l) } kern.depStart = serviceDependency{ |
︙ | ︙ | |||
162 163 164 165 166 167 168 | logger.Mandatory().Msg("Licensed under the latest version of the EUPL (European Union Public License)") if configFilename != "" { logger.Mandatory().Str("filename", configFilename).Msg("Configuration file found") } else { logger.Mandatory().Msg("No configuration file found / used") } if kern.core.GetCurConfig(kernel.CoreDebug).(bool) { | | | | | 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | logger.Mandatory().Msg("Licensed under the latest version of the EUPL (European Union Public License)") if configFilename != "" { logger.Mandatory().Str("filename", configFilename).Msg("Configuration file found") } else { logger.Mandatory().Msg("No configuration file found / used") } if kern.core.GetCurConfig(kernel.CoreDebug).(bool) { logger.Info().Msg("----------------------------------------") logger.Info().Msg("DEBUG MODE, DO NO USE THIS IN PRODUCTION") logger.Info().Msg("----------------------------------------") } if kern.auth.GetCurConfig(kernel.AuthReadonly).(bool) { logger.Info().Msg("Read-only mode") } } if lineServer { port := kern.core.GetNextConfig(kernel.CorePort).(int) |
︙ | ︙ | |||
274 275 276 277 278 279 280 | // LogRecover outputs some information about the previous panic. func (kern *myKernel) LogRecover(name string, recoverInfo interface{}) bool { return kern.doLogRecover(name, recoverInfo) } func (kern *myKernel) doLogRecover(name string, recoverInfo interface{}) bool { stack := debug.Stack() | | | 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | // LogRecover outputs some information about the previous panic. func (kern *myKernel) LogRecover(name string, recoverInfo interface{}) bool { return kern.doLogRecover(name, recoverInfo) } func (kern *myKernel) doLogRecover(name string, recoverInfo interface{}) bool { stack := debug.Stack() kern.logger.Error().Str("recovered_from", fmt.Sprint(recoverInfo)).Bytes("stack", stack).Msg(name) kern.core.updateRecoverInfo(name, recoverInfo, stack) return true } // --- Profiling --------------------------------------------------------- var errProfileInWork = errors.New("already profiling") |
︙ | ︙ |
Changes to kernel/impl/log.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 impl import ( "os" "sync" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "os" "sync" |
︙ | ︙ | |||
72 73 74 75 76 77 78 | } buf = append(buf, msg...) buf = append(buf, details...) buf = append(buf, '\n') _, err := os.Stdout.Write(buf) klw.mx.Unlock() | < < < | 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | } buf = append(buf, msg...) buf = append(buf, details...) buf = append(buf, '\n') _, err := os.Stdout.Write(buf) klw.mx.Unlock() return err } func addTimestamp(buf *[]byte, ts time.Time) { year, month, day := ts.Date() itoa(buf, year, 4) *buf = append(*buf, '-') |
︙ | ︙ | |||
122 123 124 125 126 127 128 | defer klw.mx.RUnlock() if !klw.full { if klw.writePos == 0 { return nil } result := make([]kernel.LogEntry, klw.writePos) | | | | 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 | defer klw.mx.RUnlock() if !klw.full { if klw.writePos == 0 { return nil } result := make([]kernel.LogEntry, klw.writePos) for i := range klw.writePos { copyE2E(&result[i], &klw.data[i]) } return result } result := make([]kernel.LogEntry, cap(klw.data)) pos := 0 for j := klw.writePos; j < cap(klw.data); j++ { copyE2E(&result[pos], &klw.data[j]) pos++ } for j := range klw.writePos { copyE2E(&result[pos], &klw.data[j]) pos++ } return result } func (klw *kernelLogWriter) getLastLogTime() time.Time { |
︙ | ︙ |
Changes to kernel/impl/server.go.
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 impl import ( "bufio" "net" ) func startLineServer(kern *myKernel, listenAddr string) error { ln, err := net.Listen("tcp", listenAddr) if err != nil { | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "bufio" "net" ) func startLineServer(kern *myKernel, listenAddr string) error { ln, err := net.Listen("tcp", listenAddr) if err != nil { kern.logger.Error().Err(err).Msg("Unable to start administration console") return err } kern.logger.Mandatory().Str("listen", listenAddr).Msg("Start administration console") go func() { lineServer(ln, kern) }() return nil } |
︙ | ︙ |
Changes to kernel/impl/web.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 impl import ( "errors" "net" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "errors" "net" |
︙ | ︙ | |||
142 143 144 145 146 147 148 | secureCookie := ws.GetNextConfig(kernel.WebSecureCookie).(bool) maxRequestSize := ws.GetNextConfig(kernel.WebMaxRequestSize).(int64) if maxRequestSize < 1024 { maxRequestSize = 1024 } if !strings.HasSuffix(baseURL, urlPrefix) { | | | | | | 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 | secureCookie := ws.GetNextConfig(kernel.WebSecureCookie).(bool) maxRequestSize := ws.GetNextConfig(kernel.WebMaxRequestSize).(int64) if maxRequestSize < 1024 { maxRequestSize = 1024 } if !strings.HasSuffix(baseURL, urlPrefix) { ws.logger.Error().Str("base-url", baseURL).Str("url-prefix", urlPrefix).Msg( "url-prefix is not a suffix of base-url") return errWrongBasePrefix } if lap := netip.MustParseAddrPort(listenAddr); !kern.auth.manager.WithAuth() && !lap.Addr().IsLoopback() { ws.logger.Info().Str("listen", listenAddr).Msg("service may be reached from outside, but authentication is not enabled") } srvw := impl.New(ws.logger, listenAddr, baseURL, urlPrefix, persistentCookie, secureCookie, maxRequestSize, kern.auth.manager) err := kern.web.setupServer(srvw, kern.box.manager, kern.auth.manager, &kern.cfg) if err != nil { ws.logger.Error().Err(err).Msg("Unable to create") return err } if kern.core.GetNextConfig(kernel.CoreDebug).(bool) { srvw.SetDebug() } if err = srvw.Run(); err != nil { ws.logger.Error().Err(err).Msg("Unable to start") return err } ws.logger.Info().Str("listen", listenAddr).Str("base-url", baseURL).Msg("Start Service") ws.mxService.Lock() ws.srvw = srvw ws.mxService.Unlock() |
︙ | ︙ |
Changes to kernel/kernel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 kernel provides the main kernel service. package kernel import ( "io" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package kernel provides the main kernel service. package kernel import ( "io" |
︙ | ︙ |
Changes to logger/logger.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 logger implements a logging package for use in the Zettelstore. package logger import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package logger implements a logging package for use in the Zettelstore. package logger import ( "context" |
︙ | ︙ | |||
25 26 27 28 29 30 31 | type Level uint8 // Constants for Level const ( NoLevel Level = iota // the absent log level TraceLevel // Log most internal activities DebugLevel // Log most data updates | < < < < < < < < < < < < | 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 | type Level uint8 // Constants for Level const ( NoLevel Level = iota // the absent log level TraceLevel // Log most internal activities DebugLevel // Log most data updates InfoLevel // Log normal activities ErrorLevel // Log (persistent) errors MandatoryLevel // Log only mandatory events NeverLevel // Logging is disabled ) var logLevel = [...]string{ " ", "TRACE", "DEBUG", "INFO ", "ERROR", ">>>>>", "NEVER", } var strLevel = [...]string{ "", "trace", "debug", "info", "error", "mandatory", "disabled", } // IsValid returns true, if the level is a valid level func (l Level) IsValid() bool { return TraceLevel <= l && l <= NeverLevel } |
︙ | ︙ | |||
167 168 169 170 171 172 173 | // Trace creates a tracing message. func (l *Logger) Trace() *Message { return newMessage(l, TraceLevel) } // Debug creates a debug message. func (l *Logger) Debug() *Message { return newMessage(l, DebugLevel) } | < < < < < < < < < < < < | 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | // Trace creates a tracing message. func (l *Logger) Trace() *Message { return newMessage(l, TraceLevel) } // Debug creates a debug message. func (l *Logger) Debug() *Message { return newMessage(l, DebugLevel) } // Info creates a message suitable for information data. func (l *Logger) Info() *Message { return newMessage(l, InfoLevel) } // Error creates a message suitable for errors. func (l *Logger) Error() *Message { return newMessage(l, ErrorLevel) } // Mandatory creates a message that will always logged, except when logging // is disabled. func (l *Logger) Mandatory() *Message { return newMessage(l, MandatoryLevel) } // Clone creates a message to clone the logger. func (l *Logger) Clone() *Message { msg := newMessage(l, NeverLevel) |
︙ | ︙ |
Changes to logger/logger_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 logger_test import ( "fmt" "os" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package logger_test import ( "fmt" "os" |
︙ | ︙ | |||
23 24 25 26 27 28 29 | testcases := []struct { text string exp logger.Level }{ {"tra", logger.TraceLevel}, {"deb", logger.DebugLevel}, {"info", logger.InfoLevel}, | < < < | | | | | 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 | testcases := []struct { text string exp logger.Level }{ {"tra", logger.TraceLevel}, {"deb", logger.DebugLevel}, {"info", logger.InfoLevel}, {"err", logger.ErrorLevel}, {"manda", logger.MandatoryLevel}, {"dis", logger.NeverLevel}, {"d", logger.Level(0)}, } for i, tc := range testcases { got := logger.ParseLevel(tc.text) if got != tc.exp { t.Errorf("%d: ParseLevel(%q) == %q, but got %q", i, tc.text, tc.exp, got) } } } func BenchmarkDisabled(b *testing.B) { log := logger.New(&stderrLogWriter{}, "").SetLevel(logger.NeverLevel) for range b.N { log.Info().Str("key", "val").Msg("Benchmark") } } type stderrLogWriter struct{} func (*stderrLogWriter) WriteMessage(level logger.Level, ts time.Time, prefix, msg string, details []byte) error { fmt.Fprintf(os.Stderr, "%v %v %v %v %v\n", level.Format(), ts, prefix, msg, string(details)) return nil } type testLogWriter struct{} func (*testLogWriter) WriteMessage(logger.Level, time.Time, string, string, []byte) error { return nil } func BenchmarkStrMessage(b *testing.B) { log := logger.New(&testLogWriter{}, "") for range b.N { log.Info().Str("key", "val").Msg("Benchmark") } } func BenchmarkMessage(b *testing.B) { log := logger.New(&testLogWriter{}, "") for range b.N { log.Info().Msg("Benchmark") } } func BenchmarkCloneStrMessage(b *testing.B) { log := logger.New(&testLogWriter{}, "").Clone().Str("sss", "ttt").Child() for range b.N { log.Info().Msg("123456789") } } |
Changes to logger/message.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 logger import ( "context" "net/http" "strconv" "sync" | > > > | | 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) 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package logger import ( "context" "net/http" "strconv" "sync" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" ) // Message presents a message to log. type Message struct { logger *Logger level Level |
︙ | ︙ |
Changes to parser/blob/blob.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //----------------------------------------------------------------------------- // 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 blob provides a parser of binary data. package blob 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 25 26 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package blob provides a parser of binary data. package blob import ( "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func init() { parser.Register(&parser.Info{ Name: meta.SyntaxGif, |
︙ | ︙ |
Changes to parser/cleaner/cleaner.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 cleaner provides functions to clean up the parsed AST. package cleaner import ( "strconv" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package cleaner provides functions to clean up the parsed AST. package cleaner import ( "strconv" |
︙ | ︙ |
Changes to parser/draw/canvas.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. //----------------------------------------------------------------------------- package draw import ( "bytes" "fmt" "image" | > > > | | 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 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package draw import ( "bytes" "fmt" "image" "slices" "unicode/utf8" ) // newCanvas returns a new Canvas, initialized from the provided data. If tabWidth is set to a non-negative // value, that value will be used to convert tabs to spaces within the grid. Creation of the Canvas // can fail if the diagram contains invalid UTF-8 sequences. func newCanvas(data []byte) (*canvas, error) { |
︙ | ︙ | |||
87 88 89 90 91 92 93 | // size returns the visual dimensions of the Canvas. func (c *canvas) size() image.Point { return c.siz } // findObjects finds all objects (lines, polygons, and text) within the underlying grid. func (c *canvas) findObjects() { c.findPaths() c.findTexts() | | | | | 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | // size returns the visual dimensions of the Canvas. func (c *canvas) size() image.Point { return c.siz } // findObjects finds all objects (lines, polygons, and text) within the underlying grid. func (c *canvas) findObjects() { c.findPaths() c.findTexts() slices.SortFunc(c.objs, compare) } // findPaths by starting with a point that wasn't yet visited, beginning at the top // left of the grid. func (c *canvas) findPaths() { for y := range c.siz.Y { p := point{y: y} for x := range c.siz.X { p.x = x if c.isVisited(p) { continue } ch := c.at(p) if !ch.isPathStart() { continue |
︙ | ︙ | |||
123 124 125 126 127 128 129 | c.objs = append(c.objs, objs...) } } } // findTexts with a second pass through the grid attempts to identify any text within the grid. func (c *canvas) findTexts() { | | | | 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | c.objs = append(c.objs, objs...) } } } // findTexts with a second pass through the grid attempts to identify any text within the grid. func (c *canvas) findTexts() { for y := range c.siz.Y { p := point{} p.y = y for x := range c.siz.X { p.x = x if c.isVisited(p) { continue } ch := c.at(p) if !ch.isTextStart() { continue |
︙ | ︙ |
Changes to parser/draw/canvas_test.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. //----------------------------------------------------------------------------- package draw import ( "reflect" "strings" | > > > | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package draw import ( "reflect" "strings" |
︙ | ︙ | |||
654 655 656 657 658 659 660 | " | | | | |", " +-----+-------+---------+---+", "", "", } chunk := []byte(strings.Join(data, "\n")) input := make([]byte, 0, len(chunk)*b.N) | | | 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 | " | | | | |", " +-----+-------+---------+---+", "", "", } chunk := []byte(strings.Join(data, "\n")) input := make([]byte, 0, len(chunk)*b.N) for range b.N { input = append(input, chunk...) } expected := 30 * b.N b.ResetTimer() c, err := newCanvas(input) if err != nil { b.Fatalf("Error creating canvas: %s", err) |
︙ | ︙ |
Changes to parser/draw/char.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. //----------------------------------------------------------------------------- package draw import "unicode" type char rune | > > > | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package draw import "unicode" type char rune |
︙ | ︙ |
Changes to parser/draw/draw.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 draw provides a parser to create SVG from ASCII drawing. // // It is not a parser registered by the general parser framework (directed by // metadata "syntax" of a zettel). It will be used when a zettel is evaluated. package draw import ( "strconv" | > > > | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- // Package draw provides a parser to create SVG from ASCII drawing. // // It is not a parser registered by the general parser framework (directed by // metadata "syntax" of a zettel). It will be used when a zettel is evaluated. package draw import ( "strconv" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func init() { parser.Register(&parser.Info{ Name: meta.SyntaxDraw, |
︙ | ︙ | |||
121 122 123 124 125 126 127 | return n } } return defVal } func canvasErrMsg(err error) ast.InlineSlice { | | | | 124 125 126 127 128 129 130 131 132 133 134 135 136 | return n } } return defVal } func canvasErrMsg(err error) ast.InlineSlice { return ast.InlineSlice{&ast.TextNode{Text: "Error: " + err.Error()}} } func noSVGErrMsg() ast.InlineSlice { return ast.InlineSlice{&ast.TextNode{Text: "NO IMAGE"}} } |
Changes to parser/draw/draw_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 draw_test import ( "testing" | > > > | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package draw_test import ( "testing" "t73f.de/r/zsc/input" "zettelstore.de/z/config" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func FuzzParseBlocks(f *testing.F) { f.Fuzz(func(t *testing.T, src []byte) { t.Parallel() |
︙ | ︙ |
Changes to parser/draw/object.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 21 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. //----------------------------------------------------------------------------- package draw | > > > | > > > | 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 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package draw import ( "cmp" "fmt" ) // object represents one of an open path, a closed path, or text. type object struct { // points always starts with the top most, then left most point, proceeding to the right. points []point text []rune corners []point |
︙ | ︙ | |||
97 98 99 100 101 102 103 | o.text[i] = rune(ch) } } // objects implements a sortable collection of Object interfaces. type objects []*object | < < | < < < < | | | | 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 | o.text[i] = rune(ch) } } // objects implements a sortable collection of Object interfaces. type objects []*object func compare(l, r *object) int { // TODO(dhobsd): This doesn't catch every z-index case we could possibly want. We should // support z-indexing of objects through an a2s tag. lt := l.isJustText() rt := r.isJustText() if lt != rt { return 1 } lp := l.Points()[0] rp := r.Points()[0] if lp.y != rp.y { return cmp.Compare(lp.y, rp.y) } return cmp.Compare(lp.x, rp.x) } const ( dirNone = iota // No directionality dirH // Horizontal dirV // Vertical dirSE // South-East |
︙ | ︙ |
Changes to parser/draw/point.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. //----------------------------------------------------------------------------- package draw import "fmt" // A renderHint suggests ways the SVG renderer may appropriately represent this point. | > > > | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package draw import "fmt" // A renderHint suggests ways the SVG renderer may appropriately represent this point. |
︙ | ︙ |
Changes to parser/draw/svg.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. //----------------------------------------------------------------------------- package draw import ( "bytes" "fmt" | > > > | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package draw import ( "bytes" "fmt" |
︙ | ︙ |
Changes to parser/draw/svg_test.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. //----------------------------------------------------------------------------- package draw import ( "strings" "testing" | > > > | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // license, but later changed to fulfil the needs of Zettelstore. The following // statements affects the original code as found on // https://github.com/asciitosvg/asciitosvg (Commit: // ca82a5ce41e2190a05e07af6e8b3ea4e3256a283, 2020-11-20): // // Copyright 2012 - 2018 The ASCIIToSVG Contributors // All rights reserved. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package draw import ( "strings" "testing" |
︙ | ︙ |
Changes to parser/markdown/markdown.go.
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 markdown provides a parser for markdown. package markdown import ( "bytes" "fmt" "strconv" "strings" gm "github.com/yuin/goldmark" gmAst "github.com/yuin/goldmark/ast" gmText "github.com/yuin/goldmark/text" | > > > | > < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package markdown provides a parser for markdown. package markdown import ( "bytes" "fmt" "strconv" "strings" gm "github.com/yuin/goldmark" gmAst "github.com/yuin/goldmark/ast" gmText "github.com/yuin/goldmark/text" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func init() { parser.Register(&parser.Info{ Name: meta.SyntaxMarkdown, |
︙ | ︙ | |||
145 146 147 148 149 150 151 | Content: p.acceptRawText(node), } } func (p *mdP) acceptRawText(node gmAst.Node) []byte { lines := node.Lines() result := make([]byte, 0, 512) | | | 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | Content: p.acceptRawText(node), } } func (p *mdP) acceptRawText(node gmAst.Node) []byte { lines := node.Lines() result := make([]byte, 0, 512) for i := range lines.Len() { s := lines.At(i) line := s.Value(p.source) if l := len(line); l > 0 { if l > 1 && line[l-2] == '\r' && line[l-1] == '\n' { line = line[0 : l-2] } else if line[l-1] == '\n' || line[l-1] == '\r' { line = line[0 : l-1] |
︙ | ︙ | |||
266 267 268 269 270 271 272 273 | return p.acceptRawHTML(n) } panic(fmt.Sprintf("Unhandled inline node %v", node.Kind())) } func (p *mdP) acceptText(node *gmAst.Text) ast.InlineSlice { segment := node.Segment if node.IsRaw() { | > > > > | < | < | < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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 | return p.acceptRawHTML(n) } panic(fmt.Sprintf("Unhandled inline node %v", node.Kind())) } func (p *mdP) acceptText(node *gmAst.Text) ast.InlineSlice { segment := node.Segment text := segment.Value(p.source) if text == nil { return nil } if node.IsRaw() { return ast.InlineSlice{&ast.TextNode{Text: string(text)}} } result := make(ast.InlineSlice, 0, 2) in := &ast.TextNode{Text: cleanText(text, true)} result = append(result, in) if node.HardLineBreak() { result = append(result, &ast.BreakNode{Hard: true}) } else if node.SoftLineBreak() { result = append(result, &ast.BreakNode{Hard: false}) } return result } var ignoreAfterBS = map[byte]struct{}{ '!': {}, '"': {}, '#': {}, '$': {}, '%': {}, '&': {}, '\'': {}, '(': {}, ')': {}, '*': {}, '+': {}, ',': {}, '-': {}, '.': {}, '/': {}, ':': {}, ';': {}, '<': {}, '=': {}, '>': {}, '?': {}, '@': {}, '[': {}, '\\': {}, |
︙ | ︙ | |||
461 462 463 464 465 466 467 | Attrs: nil, // TODO }, } } func (p *mdP) acceptRawHTML(node *gmAst.RawHTML) ast.InlineSlice { segs := make([][]byte, 0, node.Segments.Len()) | | | 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 | Attrs: nil, // TODO }, } } func (p *mdP) acceptRawHTML(node *gmAst.RawHTML) ast.InlineSlice { segs := make([][]byte, 0, node.Segments.Len()) for i := range node.Segments.Len() { segment := node.Segments.At(i) segs = append(segs, segment.Value(p.source)) } return ast.InlineSlice{ &ast.LiteralNode{ Kind: ast.LiteralHTML, Attrs: nil, // TODO: add HTML as language Content: bytes.Join(segs, nil), }, } } |
Deleted parser/markdown/markdown_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to parser/none/none.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //----------------------------------------------------------------------------- // 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 none provides a none-parser, e.g. for zettel with just metadata. package none 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 25 26 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package none provides a none-parser, e.g. for zettel with just metadata. package none import ( "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func init() { parser.Register(&parser.Info{ Name: meta.SyntaxNone, |
︙ | ︙ |
Changes to parser/parser.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 parser provides a generic interface to a range of different parsers. package parser import ( "context" "fmt" "strings" | > > > | > < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package parser provides a generic interface to a range of different parsers. package parser import ( "context" "fmt" "strings" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/parser/cleaner" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/meta" ) // Info describes a single parser. // |
︙ | ︙ | |||
81 82 83 84 85 86 87 | pi, ok := registry[syntax] if !ok { return false } return pi.IsASTParser } | < < < < < < < < < < < < | | | | | 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 | pi, ok := registry[syntax] if !ok { return false } return pi.IsASTParser } // IsImageFormat returns whether the given syntax is known to be an image format. func IsImageFormat(syntax string) bool { pi, ok := registry[syntax] if !ok { return false } return pi.IsImageFormat } // ParseBlocks parses some input and returns a slice of block nodes. func ParseBlocks(inp *input.Input, m *meta.Meta, syntax string, hi config.HTMLInsecurity) ast.BlockSlice { bs := Get(syntax).ParseBlocks(inp, m, syntax) cleaner.CleanBlockSlice(&bs, hi.AllowHTML(syntax)) return bs } // ParseInlines parses some input and returns a slice of inline nodes. func ParseInlines(inp *input.Input, syntax string) ast.InlineSlice { // Do not clean, because we don't know the context where this function will be called. return Get(syntax).ParseInlines(inp, syntax) } // ParseMetadata parses a string as Zettelmarkup, resulting in an inline slice. // Typically used to parse the title or other metadata of type Zettelmarkup. func ParseMetadata(value string) ast.InlineSlice { return ParseInlines(input.NewInput([]byte(value)), meta.SyntaxZmk) } // ParseSpacedText returns an inline slice that consists just of test and space node. // No Zettelmarkup parsing is done. It is typically used to transform the zettel title into an inline slice. func ParseSpacedText(s string) ast.InlineSlice { return ast.InlineSlice{&ast.TextNode{Text: strings.Join(meta.ListFromValue(s), " ")}} } // NormalizedSpacedText returns the given string, but normalize multiple spaces to one space. func NormalizedSpacedText(s string) string { return strings.Join(meta.ListFromValue(s), " ") } // ParseDescription returns a suitable description stored in the metadata as an inline slice. // This is done for an image in most cases. func ParseDescription(m *meta.Meta) ast.InlineSlice { if m == nil { return nil } if descr, found := m.Get(api.KeySummary); found { in := ParseMetadata(descr) cleaner.CleanInlineLinks(&in) return in } if title, found := m.Get(api.KeyTitle); found { return ParseSpacedText(title) } return ast.InlineSlice{&ast.TextNode{Text: "Zettel without title: " + m.Zid.String()}} } // ParseZettel parses the zettel based on the syntax. func ParseZettel(ctx context.Context, zettel zettel.Zettel, syntax string, rtConfig config.Config) *ast.ZettelNode { m := zettel.Meta inhMeta := m if rtConfig != nil { inhMeta = rtConfig.AddDefaultValues(ctx, inhMeta) } if syntax == "" { syntax = inhMeta.GetDefault(api.KeySyntax, meta.DefaultSyntax) } parseMeta := inhMeta if syntax == meta.SyntaxNone { parseMeta = m } hi := config.NoHTML if rtConfig != nil { hi = rtConfig.GetHTMLInsecurity() } return &ast.ZettelNode{ Meta: m, Content: zettel.Content, Zid: m.Zid, InhMeta: inhMeta, Ast: ParseBlocks(input.NewInput(zettel.Content.AsBytes()), parseMeta, syntax, hi), Syntax: syntax, } } |
Changes to parser/parser_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 parser_test import ( "testing" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package parser_test import ( "testing" |
︙ | ︙ | |||
26 27 28 29 30 31 32 | ) func TestParserType(t *testing.T) { syntaxSet := strfun.NewSet(parser.GetSyntaxes()...) testCases := []struct { syntax string ast bool | < | | | | | | | | | | | | | | | | | < < < | 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 | ) func TestParserType(t *testing.T) { syntaxSet := strfun.NewSet(parser.GetSyntaxes()...) testCases := []struct { syntax string ast bool image bool }{ {meta.SyntaxHTML, false, false}, {meta.SyntaxCSS, false, false}, {meta.SyntaxDraw, true, false}, {meta.SyntaxGif, false, true}, {meta.SyntaxJPEG, false, true}, {meta.SyntaxJPG, false, true}, {meta.SyntaxMarkdown, true, false}, {meta.SyntaxMD, true, false}, {meta.SyntaxNone, false, false}, {meta.SyntaxPlain, false, false}, {meta.SyntaxPNG, false, true}, {meta.SyntaxSVG, false, true}, {meta.SyntaxSxn, false, false}, {meta.SyntaxText, false, false}, {meta.SyntaxTxt, false, false}, {meta.SyntaxWebp, false, true}, {meta.SyntaxZmk, true, false}, } for _, tc := range testCases { delete(syntaxSet, tc.syntax) if got := parser.IsASTParser(tc.syntax); got != tc.ast { t.Errorf("Syntax %q is AST: %v, but got %v", tc.syntax, tc.ast, got) } if got := parser.IsImageFormat(tc.syntax); got != tc.image { t.Errorf("Syntax %q is image: %v, but got %v", tc.syntax, tc.image, got) } } for syntax := range syntaxSet { t.Errorf("Forgot to test syntax %q", syntax) } } |
Changes to parser/plain/plain.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 plain provides a parser for plain text data. package plain import ( "bytes" | > > > < | | | < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package plain provides a parser for plain text data. package plain import ( "bytes" "t73f.de/r/sx/sxreader" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func init() { parser.Register(&parser.Info{ Name: meta.SyntaxTxt, |
︙ | ︙ | |||
119 120 121 122 123 124 125 | return ast.InlineSlice{&ast.EmbedBLOBNode{ Blob: []byte(svgSrc), Syntax: syntax, }} } func scanSVG(inp *input.Input) string { | < | < | | > > | > > | | < | | | | | | > > | | | < < < < < < < < < < < | 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 | return ast.InlineSlice{&ast.EmbedBLOBNode{ Blob: []byte(svgSrc), Syntax: syntax, }} } func scanSVG(inp *input.Input) string { inp.SkipSpace() pos := inp.Pos if !inp.Accept("<svg") { return "" } ch := inp.Ch if input.IsSpace(ch) || input.IsEOLEOS(ch) || ch == '>' { // TODO: check proper end </svg> return string(inp.Src[pos:]) } return "" } func parseSxnBlocks(inp *input.Input, _ *meta.Meta, syntax string) ast.BlockSlice { rd := sxreader.MakeReader(bytes.NewReader(inp.Src)) _, err := rd.ReadAll() result := ast.BlockSlice{ &ast.VerbatimNode{ Kind: ast.VerbatimProg, Attrs: attrs.Attributes{"": syntax}, Content: inp.ScanLineContent(), }, } if err != nil { result = append(result, ast.CreateParaNode(&ast.TextNode{ Text: err.Error(), })) } return result } func parseSxnInlines(inp *input.Input, syntax string) ast.InlineSlice { inp.SkipToEOL() return ast.InlineSlice{&ast.LiteralNode{ Kind: ast.LiteralProg, Attrs: attrs.Attributes{"": syntax}, Content: append([]byte(nil), inp.Src[0:inp.Pos]...), }} } |
Added parser/plain/plain_test.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | //----------------------------------------------------------------------------- // Copyright (c) 2024-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2024-present Detlef Stern //----------------------------------------------------------------------------- package plain_test import ( "testing" "t73f.de/r/zsc/input" "zettelstore.de/z/encoder/szenc" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func TestParseSVG(t *testing.T) { testCases := []struct { name string src string exp string }{ {"common", " <svg bla", "(INLINE (EMBED-BLOB () \"svg\" \"<svg bla\"))"}, {"inkscape", "<svg\nbla", "(INLINE (EMBED-BLOB () \"svg\" \"<svg\\nbla\"))"}, {"selfmade", "<svg>", "(INLINE (EMBED-BLOB () \"svg\" \"<svg>\"))"}, {"error", "<svgbla", "(INLINE)"}, {"error-", "<svg-bla", "(INLINE)"}, {"error#", "<svg2bla", "(INLINE)"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { inp := input.NewInput([]byte(tc.src)) is := parser.ParseInlines(inp, meta.SyntaxSVG) trans := szenc.NewTransformer() lst := trans.GetSz(&is) if got := lst.String(); tc.exp != got { t.Errorf("\nexp: %q\ngot: %q", tc.exp, got) } }) } } |
Changes to parser/zettelmark/block.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 zettelmark import ( "fmt" | > > > | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package zettelmark import ( "fmt" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" ) // parseBlockSlice parses a sequence of blocks. func (cp *zmkP) parseBlockSlice() ast.BlockSlice { inp := cp.inp var lastPara *ast.ParaNode bs := ast.BlockSlice{} |
︙ | ︙ | |||
107 108 109 110 111 112 113 | } func startsWithSpaceSoftBreak(pn *ast.ParaNode) bool { ins := pn.Inlines if len(ins) < 2 { return false } | < > > > > > > | > > > > > | 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 | } func startsWithSpaceSoftBreak(pn *ast.ParaNode) bool { ins := pn.Inlines if len(ins) < 2 { return false } _, isBreak := ins[1].(*ast.BreakNode) return isBreak && isSpaceText(ins[0]) } func isSpaceText(node ast.InlineNode) bool { if tn, isText := node.(*ast.TextNode); isText { for _, ch := range tn.Text { if !input.IsSpace(ch) { return false } } return true } return false } func (cp *zmkP) cleanupListsAfterEOL() { for _, l := range cp.lists { if lits := len(l.Items); lits > 0 { l.Items[lits-1] = append(l.Items[lits-1], &nullItemNode{}) } |
︙ | ︙ | |||
271 272 273 274 275 276 277 278 | if !cont { lastPara, _ = bn.(*ast.ParaNode) } } } func (cp *zmkP) parseRegionLastLine(rn *ast.RegionNode) { cp.clearStacked() // remove any lists defined in the region | > | | | 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | if !cont { lastPara, _ = bn.(*ast.ParaNode) } } } func (cp *zmkP) parseRegionLastLine(rn *ast.RegionNode) { inp := cp.inp cp.clearStacked() // remove any lists defined in the region inp.SkipSpace() for { switch inp.Ch { case input.EOS, '\n', '\r': return } in := cp.parseInline() if in == nil { return } |
︙ | ︙ | |||
297 298 299 300 301 302 303 | if delims < 3 { return nil, false } if inp.Ch != ' ' { return nil, false } inp.Next() | | | 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | if delims < 3 { return nil, false } if inp.Ch != ' ' { return nil, false } inp.Next() inp.SkipSpace() if delims > 7 { delims = 7 } hn = &ast.HeadingNode{Level: delims - 2, Inlines: nil} for { if input.IsEOLEOS(inp.Ch) { return hn, true |
︙ | ︙ | |||
344 345 346 347 348 349 350 | // parseNestedList parses a list. func (cp *zmkP) parseNestedList() (res ast.BlockNode, success bool) { inp := cp.inp kinds := cp.parseNestedListKinds() if kinds == nil { return nil, false } | | | 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 | // parseNestedList parses a list. func (cp *zmkP) parseNestedList() (res ast.BlockNode, success bool) { inp := cp.inp kinds := cp.parseNestedListKinds() if kinds == nil { return nil, false } inp.SkipSpace() if kinds[len(kinds)-1] != ast.NestedListQuote && input.IsEOLEOS(inp.Ch) { return nil, false } if len(kinds) < len(cp.lists) { cp.lists = cp.lists[:len(kinds)] } |
︙ | ︙ | |||
404 405 406 407 408 409 410 | } } return ln, newLnCount } func (cp *zmkP) cleanupParsedNestedList(newLnCount int) (res ast.BlockNode, success bool) { listDepth := len(cp.lists) | | | 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 | } } return ln, newLnCount } func (cp *zmkP) cleanupParsedNestedList(newLnCount int) (res ast.BlockNode, success bool) { listDepth := len(cp.lists) for i := range newLnCount { childPos := listDepth - i - 1 parentPos := childPos - 1 if parentPos < 0 { return cp.lists[0], true } if prevItems := cp.lists[parentPos].Items; len(prevItems) > 0 { lastItem := len(prevItems) - 1 |
︙ | ︙ | |||
429 430 431 432 433 434 435 | func (cp *zmkP) parseDefTerm() (res ast.BlockNode, success bool) { inp := cp.inp inp.Next() if inp.Ch != ' ' { return nil, false } inp.Next() | | | 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 | func (cp *zmkP) parseDefTerm() (res ast.BlockNode, success bool) { inp := cp.inp inp.Next() if inp.Ch != ' ' { return nil, false } inp.Next() inp.SkipSpace() descrl := cp.descrl if descrl == nil { descrl = &ast.DescriptionListNode{} cp.descrl = descrl } descrl.Descriptions = append(descrl.Descriptions, ast.Description{}) defPos := len(descrl.Descriptions) - 1 |
︙ | ︙ | |||
463 464 465 466 467 468 469 | func (cp *zmkP) parseDefDescr() (res ast.BlockNode, success bool) { inp := cp.inp inp.Next() if inp.Ch != ' ' { return nil, false } inp.Next() | | | 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 | func (cp *zmkP) parseDefDescr() (res ast.BlockNode, success bool) { inp := cp.inp inp.Next() if inp.Ch != ' ' { return nil, false } inp.Next() inp.SkipSpace() descrl := cp.descrl if descrl == nil || len(descrl.Descriptions) == 0 { return nil, false } defPos := len(descrl.Descriptions) - 1 if len(descrl.Descriptions[defPos].Term) == 0 { return nil, false |
︙ | ︙ |
Changes to parser/zettelmark/inline.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 zettelmark import ( "bytes" "fmt" "strings" | > > > | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package zettelmark import ( "bytes" "fmt" "strings" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/zettel/meta" ) // parseInlineSlice parses a sequence of Inlines until EOS. func (cp *zmkP) parseInlineSlice() (ins ast.InlineSlice) { inp := cp.inp for inp.Ch != input.EOS { |
︙ | ︙ | |||
44 45 46 47 48 49 50 | var in ast.InlineNode success := false switch inp.Ch { case input.EOS: return nil case '\n', '\r': return cp.parseSoftBreak() | < < | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | var in ast.InlineNode success := false switch inp.Ch { case input.EOS: return nil case '\n', '\r': return cp.parseSoftBreak() case '[': inp.Next() switch inp.Ch { case '[': in, success = cp.parseLink() case '@': in, success = cp.parseCite() |
︙ | ︙ | |||
98 99 100 101 102 103 104 | return cp.parseBackslashRest() } for { inp.Next() switch inp.Ch { // The following case must contain all runes that occur in parseInline! // Plus the closing brackets ] and } and ) and the middle | | | | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | return cp.parseBackslashRest() } for { inp.Next() switch inp.Ch { // The following case must contain all runes that occur in parseInline! // Plus the closing brackets ] and } and ) and the middle | case input.EOS, '\n', '\r', '[', ']', '{', '}', '(', ')', '|', '%', '_', '*', '>', '~', '^', ',', '"', '#', ':', '\'', '@', '`', runeModGrave, '$', '=', '\\', '-', '&': return &ast.TextNode{Text: string(inp.Src[pos:inp.Pos])} } } } func (cp *zmkP) parseBackslash() ast.InlineNode { inp := cp.inp |
︙ | ︙ | |||
130 131 132 133 134 135 136 | return &ast.TextNode{Text: "\u00a0"} } pos := inp.Pos inp.Next() return &ast.TextNode{Text: string(inp.Src[pos:inp.Pos])} } | < < < < < < < < < < < < < | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | return &ast.TextNode{Text: "\u00a0"} } pos := inp.Pos inp.Next() return &ast.TextNode{Text: string(inp.Src[pos:inp.Pos])} } func (cp *zmkP) parseSoftBreak() *ast.BreakNode { cp.inp.EatEOL() return &ast.BreakNode{} } func (cp *zmkP) parseLink() (*ast.LinkNode, bool) { if ref, is, ok := cp.parseReference('[', ']'); ok { |
︙ | ︙ | |||
169 170 171 172 173 174 175 | func hasQueryPrefix(src []byte) bool { return len(src) > len(ast.QueryPrefix) && string(src[:len(ast.QueryPrefix)]) == ast.QueryPrefix } func (cp *zmkP) parseReference(openCh, closeCh rune) (ref string, is ast.InlineSlice, _ bool) { inp := cp.inp inp.Next() | | | 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | func hasQueryPrefix(src []byte) bool { return len(src) > len(ast.QueryPrefix) && string(src[:len(ast.QueryPrefix)]) == ast.QueryPrefix } func (cp *zmkP) parseReference(openCh, closeCh rune) (ref string, is ast.InlineSlice, _ bool) { inp := cp.inp inp.Next() inp.SkipSpace() if inp.Ch == openCh { // Additional opening chars result in a fail return "", nil, false } pos := inp.Pos if !hasQueryPrefix(inp.Src[pos:]) { hasSpace, ok := cp.readReferenceToSep(closeCh) |
︙ | ︙ | |||
202 203 204 205 206 207 208 | if hasSpace { return "", nil, false } inp.SetPos(pos) } } | | | 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | if hasSpace { return "", nil, false } inp.SetPos(pos) } } inp.SkipSpace() pos = inp.Pos if !cp.readReferenceToClose(closeCh) { return "", nil, false } ref = strings.TrimSpace(string(inp.Src[pos:inp.Pos])) inp.Next() if inp.Ch != closeCh { |
︙ | ︙ | |||
321 322 323 324 325 326 327 | return nil, false } attrs := cp.parseInlineAttributes() return &ast.FootnoteNode{Inlines: ins, Attrs: attrs}, true } func (cp *zmkP) parseLinkLikeRest() (ast.InlineSlice, bool) { | > | < | 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | return nil, false } attrs := cp.parseInlineAttributes() return &ast.FootnoteNode{Inlines: ins, Attrs: attrs}, true } func (cp *zmkP) parseLinkLikeRest() (ast.InlineSlice, bool) { inp := cp.inp inp.SkipSpace() ins := ast.InlineSlice{} for inp.Ch != ']' { in := cp.parseInline() if in == nil { return nil, false } ins = append(ins, in) if _, ok := in.(*ast.BreakNode); ok && input.IsEOLEOS(inp.Ch) { |
︙ | ︙ | |||
392 393 394 395 396 397 398 | if inp.Ch != '%' { return nil, false } for inp.Ch == '%' { inp.Next() } attrs := cp.parseInlineAttributes() | | | 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 | if inp.Ch != '%' { return nil, false } for inp.Ch == '%' { inp.Next() } attrs := cp.parseInlineAttributes() inp.SkipSpace() pos := inp.Pos for { if input.IsEOLEOS(inp.Ch) { return &ast.LiteralNode{ Kind: ast.LiteralComment, Attrs: attrs, Content: append([]byte(nil), inp.Src[pos:inp.Pos]...), |
︙ | ︙ |
Changes to parser/zettelmark/node.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 zettelmark import "zettelstore.de/z/ast" // Internal nodes for parsing zettelmark. These will be removed in | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package zettelmark import "zettelstore.de/z/ast" // Internal nodes for parsing zettelmark. These will be removed in |
︙ | ︙ |
Changes to parser/zettelmark/post-processor.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 zettelmark import ( "strings" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package zettelmark import ( "strings" |
︙ | ︙ | |||
69 70 71 72 73 74 75 76 77 78 | func (pp *postProcessor) visitRegion(rn *ast.RegionNode) { oldVerse := pp.inVerse if rn.Kind == ast.RegionVerse { pp.inVerse = true } pp.visitBlockSlice(&rn.Blocks) if len(rn.Inlines) > 0 { pp.visitInlineSlice(&rn.Inlines) } | > < | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | func (pp *postProcessor) visitRegion(rn *ast.RegionNode) { oldVerse := pp.inVerse if rn.Kind == ast.RegionVerse { pp.inVerse = true } pp.visitBlockSlice(&rn.Blocks) pp.inVerse = oldVerse if len(rn.Inlines) > 0 { pp.visitInlineSlice(&rn.Inlines) } } func (pp *postProcessor) visitNestedList(ln *ast.NestedListNode) { for i, item := range ln.Items { ln.Items[i] = pp.processItemSlice(item) } if ln.Kind != ast.NestedListQuote { |
︙ | ︙ | |||
127 128 129 130 131 132 133 | } } } func (pp *postProcessor) visitTable(tn *ast.TableNode) { width := tableWidth(tn) tn.Align = make([]ast.Alignment, width) | | | 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | } } } func (pp *postProcessor) visitTable(tn *ast.TableNode) { width := tableWidth(tn) tn.Align = make([]ast.Alignment, width) for i := range width { tn.Align[i] = ast.AlignDefault } if len(tn.Rows) > 0 && isHeaderRow(tn.Rows[0]) { tn.Header = tn.Rows[0] tn.Rows = tn.Rows[1:] pp.visitTableHeader(tn) } |
︙ | ︙ | |||
361 362 363 364 365 366 367 | } // processInlineSliceHead removes leading spaces and empty text. func (pp *postProcessor) processInlineSliceHead(is *ast.InlineSlice) { ins := *is for i, in := range ins { switch in := in.(type) { | | | | > > > > > | 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 | } // processInlineSliceHead removes leading spaces and empty text. func (pp *postProcessor) processInlineSliceHead(is *ast.InlineSlice) { ins := *is for i, in := range ins { switch in := in.(type) { case *ast.TextNode: if pp.inVerse && len(in.Text) > 0 { *is = ins[i:] return } for len(in.Text) > 0 { if ch := in.Text[0]; ch != ' ' && ch != '\t' { break } in.Text = in.Text[1:] } if len(in.Text) > 0 { *is = ins[i:] return } default: *is = ins[i:] return |
︙ | ︙ | |||
403 404 405 406 407 408 409 | ins := *is fromPos, toPos := 0, 0 for fromPos < maxPos { ins[toPos] = ins[fromPos] fromPos++ switch in := ins[toPos].(type) { case *ast.TextNode: | > > > > | > > > > > > > > > | > > > > > > > > | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < > < > > > > > > > > > > | 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 | ins := *is fromPos, toPos := 0, 0 for fromPos < maxPos { ins[toPos] = ins[fromPos] fromPos++ switch in := ins[toPos].(type) { case *ast.TextNode: // Merge following TextNodes for fromPos < maxPos { if tn, ok := ins[fromPos].(*ast.TextNode); ok { in.Text = in.Text + tn.Text fromPos++ } else { break } } if in.Text == "" { continue } if ch := in.Text[len(in.Text)-1]; ch == ' ' && fromPos < maxPos { switch nn := ins[fromPos].(type) { case *ast.BreakNode: nn.Hard = true in.Text = removeTrailingSpaces(in.Text) case *ast.LiteralNode: if nn.Kind == ast.LiteralComment { in.Text = removeTrailingSpaces(in.Text) } } } if pp.inVerse { in.Text = strings.ReplaceAll(in.Text, " ", "\u00a0") } case *ast.BreakNode: if pp.inVerse { in.Hard = true } } toPos++ } return toPos } // processInlineSliceTail removes empty text nodes, breaks and spaces at the end. func (*postProcessor) processInlineSliceTail(is *ast.InlineSlice, toPos int) int { ins := *is for toPos > 0 { switch n := ins[toPos-1].(type) { case *ast.TextNode: n.Text = removeTrailingSpaces(n.Text) if len(n.Text) > 0 { return toPos } case *ast.BreakNode: default: return toPos } toPos-- ins[toPos] = nil // Kill node to enable garbage collection } return toPos } func removeTrailingSpaces(s string) string { for len(s) > 0 { if ch := s[len(s)-1]; ch != ' ' && ch != '\t' { return s } s = s[0 : len(s)-1] } return "" } |
Changes to parser/zettelmark/zettelmark.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 zettelmark provides a parser for zettelmarkup. package zettelmark import ( "strings" "unicode" | > > > | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package zettelmark provides a parser for zettelmarkup. package zettelmark import ( "strings" "unicode" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func init() { parser.Register(&parser.Info{ Name: meta.SyntaxZmk, |
︙ | ︙ | |||
149 150 151 152 153 154 155 | inp.Next() } if pos < inp.Pos { return attrs.Attributes{"": string(inp.Src[pos:inp.Pos])} } // No immediate name: skip spaces | | | 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | inp.Next() } if pos < inp.Pos { return attrs.Attributes{"": string(inp.Src[pos:inp.Pos])} } // No immediate name: skip spaces inp.SkipSpace() return cp.parseInlineAttributes() } func (cp *zmkP) parseInlineAttributes() attrs.Attributes { inp := cp.inp pos := inp.Pos if attrs, success := cp.doParseAttributes(); success { |
︙ | ︙ | |||
233 234 235 236 237 238 239 | inp.EatEOL() default: return } } } | < < < < < < | 236 237 238 239 240 241 242 243 244 245 | inp.EatEOL() default: return } } } func isNameRune(ch rune) bool { return unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '-' || ch == '_' } |
Changes to parser/zettelmark/zettelmark_fuzz_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 zettelmark_test import ( "testing" | > > > | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package zettelmark_test import ( "testing" "t73f.de/r/zsc/input" "zettelstore.de/z/config" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) func FuzzParseBlocks(f *testing.F) { f.Fuzz(func(t *testing.T, src []byte) { t.Parallel() |
︙ | ︙ |
Changes to parser/zettelmark/zettelmark_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 zettelmark_test provides some tests for the zettelmarkup parser. package zettelmark_test import ( "fmt" "strings" "testing" | > > > | > < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package zettelmark_test provides some tests for the zettelmarkup parser. package zettelmark_test import ( "fmt" "strings" "testing" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) type TestCase struct{ source, want string } type TestCases []TestCase |
︙ | ︙ | |||
67 68 69 70 71 72 73 | }) } func TestText(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"abcd", "(PARA abcd)"}, | | | | 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 | }) } func TestText(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"abcd", "(PARA abcd)"}, {"ab cd", "(PARA ab cd)"}, {"abcd ", "(PARA abcd)"}, {" abcd", "(PARA abcd)"}, {"\\", "(PARA \\)"}, {"\\\n", ""}, {"\\\ndef", "(PARA HB def)"}, {"\\\r", ""}, {"\\\rdef", "(PARA HB def)"}, {"\\\r\n", ""}, {"\\\r\ndef", "(PARA HB def)"}, {"\\a", "(PARA a)"}, {"\\aa", "(PARA aa)"}, {"a\\a", "(PARA aa)"}, {"\\+", "(PARA +)"}, {"\\ ", "(PARA \u00a0)"}, {"http://a, http://b", "(PARA http://a, http://b)"}, }) } func TestSpace(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {" ", ""}, |
︙ | ︙ | |||
125 126 127 128 129 130 131 | {"[", "(PARA [)"}, {"[[", "(PARA [[)"}, {"[[|", "(PARA [[|)"}, {"[[]", "(PARA [[])"}, {"[[|]", "(PARA [[|])"}, {"[[]]", "(PARA [[]])"}, {"[[|]]", "(PARA [[|]])"}, | | | | | 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 | {"[", "(PARA [)"}, {"[[", "(PARA [[)"}, {"[[|", "(PARA [[|)"}, {"[[]", "(PARA [[])"}, {"[[|]", "(PARA [[|])"}, {"[[]]", "(PARA [[]])"}, {"[[|]]", "(PARA [[|]])"}, {"[[ ]]", "(PARA [[ ]])"}, {"[[\n]]", "(PARA [[ SB ]])"}, {"[[ a]]", "(PARA (LINK a))"}, {"[[a ]]", "(PARA [[a ]])"}, {"[[a\n]]", "(PARA [[a SB ]])"}, {"[[a]]", "(PARA (LINK a))"}, {"[[12345678901234]]", "(PARA (LINK 12345678901234))"}, {"[[a]", "(PARA [[a])"}, {"[[|a]]", "(PARA [[|a]])"}, {"[[b|]]", "(PARA [[b|]])"}, {"[[b|a]]", "(PARA (LINK a b))"}, {"[[b| a]]", "(PARA (LINK a b))"}, {"[[b%c|a]]", "(PARA (LINK a b%c))"}, {"[[b%%c|a]]", "(PARA [[b {% c|a]]})"}, {"[[b|a]", "(PARA [[b|a])"}, {"[[b\nc|a]]", "(PARA (LINK a b SB c))"}, {"[[b c|a#n]]", "(PARA (LINK a#n b c))"}, {"[[a]]go", "(PARA (LINK a) go)"}, {"[[b|a]]{go}", "(PARA (LINK a b)[ATTR go])"}, {"[[[[a]]|b]]", "(PARA [[ (LINK a) |b]])"}, {"[[a[b]c|d]]", "(PARA (LINK d a[b]c))"}, {"[[[b]c|d]]", "(PARA [ (LINK d b]c))"}, {"[[a[]c|d]]", "(PARA (LINK d a[]c))"}, {"[[a[b]|d]]", "(PARA (LINK d a[b]))"}, |
︙ | ︙ | |||
175 176 177 178 179 180 181 | func TestCite(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"[@", "(PARA [@)"}, {"[@]", "(PARA [@])"}, {"[@a]", "(PARA (CITE a))"}, | | | 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | func TestCite(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"[@", "(PARA [@)"}, {"[@]", "(PARA [@])"}, {"[@a]", "(PARA (CITE a))"}, {"[@ a]", "(PARA [@ a])"}, {"[@a ]", "(PARA (CITE a))"}, {"[@a\n]", "(PARA (CITE a))"}, {"[@a\nx]", "(PARA (CITE a SB x))"}, {"[@a\n\n]", "(PARA [@a)(PARA ])"}, {"[@a,\n]", "(PARA (CITE a))"}, {"[@a,n]", "(PARA (CITE a n))"}, {"[@a| n]", "(PARA (CITE a n))"}, |
︙ | ︙ | |||
213 214 215 216 217 218 219 | {"{", "(PARA {)"}, {"{{", "(PARA {{)"}, {"{{|", "(PARA {{|)"}, {"{{}", "(PARA {{})"}, {"{{|}", "(PARA {{|})"}, {"{{}}", "(PARA {{}})"}, {"{{|}}", "(PARA {{|}})"}, | | | | | 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 | {"{", "(PARA {)"}, {"{{", "(PARA {{)"}, {"{{|", "(PARA {{|)"}, {"{{}", "(PARA {{})"}, {"{{|}", "(PARA {{|})"}, {"{{}}", "(PARA {{}})"}, {"{{|}}", "(PARA {{|}})"}, {"{{ }}", "(PARA {{ }})"}, {"{{\n}}", "(PARA {{ SB }})"}, {"{{a }}", "(PARA {{a }})"}, {"{{a\n}}", "(PARA {{a SB }})"}, {"{{a}}", "(PARA (EMBED a))"}, {"{{12345678901234}}", "(PARA (EMBED 12345678901234))"}, {"{{ a}}", "(PARA (EMBED a))"}, {"{{a}", "(PARA {{a})"}, {"{{|a}}", "(PARA {{|a}})"}, {"{{b|}}", "(PARA {{b|}})"}, {"{{b|a}}", "(PARA (EMBED a b))"}, {"{{b| a}}", "(PARA (EMBED a b))"}, {"{{b|a}", "(PARA {{b|a})"}, {"{{b\nc|a}}", "(PARA (EMBED a b SB c))"}, {"{{b c|a#n}}", "(PARA (EMBED a#n b c))"}, {"{{a}}{go}", "(PARA (EMBED a)[ATTR go])"}, {"{{{{a}}|b}}", "(PARA {{ (EMBED a) |b}})"}, {"{{\\|}}", "(PARA (EMBED %5C%7C))"}, {"{{\\||a}}", "(PARA (EMBED a |))"}, {"{{b\\||a}}", "(PARA (EMBED a b|))"}, {"{{b\\|c|a}}", "(PARA (EMBED a b|c))"}, {"{{\\}}}", "(PARA (EMBED %5C%7D))"}, |
︙ | ︙ | |||
251 252 253 254 255 256 257 | func TestMark(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"[!", "(PARA [!)"}, {"[!\n", "(PARA [!)"}, {"[!]", "(PARA (MARK #*))"}, {"[!][!]", "(PARA (MARK #*) (MARK #*-1))"}, | | | | | 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 | func TestMark(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"[!", "(PARA [!)"}, {"[!\n", "(PARA [!)"}, {"[!]", "(PARA (MARK #*))"}, {"[!][!]", "(PARA (MARK #*) (MARK #*-1))"}, {"[! ]", "(PARA [! ])"}, {"[!a]", "(PARA (MARK \"a\" #a))"}, {"[!a][!a]", "(PARA (MARK \"a\" #a) (MARK \"a\" #a-1))"}, {"[!a ]", "(PARA [!a ])"}, {"[!a_]", "(PARA (MARK \"a_\" #a))"}, {"[!a_][!a]", "(PARA (MARK \"a_\" #a) (MARK \"a\" #a-1))"}, {"[!a-b]", "(PARA (MARK \"a-b\" #a-b))"}, {"[!a|b]", "(PARA (MARK \"a\" #a b))"}, {"[!a|]", "(PARA (MARK \"a\" #a))"}, {"[!|b]", "(PARA (MARK #* b))"}, {"[!|b c]", "(PARA (MARK #* b c))"}, }) } func TestComment(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"%", "(PARA %)"}, |
︙ | ︙ | |||
410 411 412 413 414 415 416 | // Good cases {"<", "(PARA <)"}, {"0", "(PARA 0)"}, {"J", "(PARA J)"}, {"J", "(PARA J)"}, {"…", "(PARA \u2026)"}, {" ", "(PARA \u00a0)"}, | | | 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 | // Good cases {"<", "(PARA <)"}, {"0", "(PARA 0)"}, {"J", "(PARA J)"}, {"J", "(PARA J)"}, {"…", "(PARA \u2026)"}, {" ", "(PARA \u00a0)"}, {"E: &,?;c.", "(PARA E: &,?;c.)"}, }) } func TestVerbatimZettel(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"@@@\n@@@", "(ZETTEL)"}, |
︙ | ︙ | |||
520 521 522 523 524 525 526 | })) } func TestHeading(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"=h", "(PARA =h)"}, | | | | | | | | 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 | })) } func TestHeading(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"=h", "(PARA =h)"}, {"= h", "(PARA = h)"}, {"==h", "(PARA ==h)"}, {"== h", "(PARA == h)"}, {"===h", "(PARA ===h)"}, {"=== h", "(H1 h #h)"}, {"=== h", "(H1 h #h)"}, {"==== h", "(H2 h #h)"}, {"===== h", "(H3 h #h)"}, {"====== h", "(H4 h #h)"}, {"======= h", "(H5 h #h)"}, {"======== h", "(H5 h #h)"}, {"=", "(PARA =)"}, {"=== h=__=a__", "(H1 h= {_ =a} #h-a)"}, {"=\n", "(PARA =)"}, {"a=", "(PARA a=)"}, {" =", "(PARA =)"}, {"=== h\na", "(H1 h #h)(PARA a)"}, {"=== h i {-}", "(H1 h i #h-i)[ATTR -]"}, {"=== h {{a}}", "(H1 h (EMBED a) #h)"}, {"=== h{{a}}", "(H1 h (EMBED a) #h)"}, {"=== {{a}}", "(H1 (EMBED a))"}, {"=== h {{a}}{-}", "(H1 h (EMBED a)[ATTR -] #h)"}, {"=== h {{a}} {-}", "(H1 h (EMBED a) #h)[ATTR -]"}, {"=== h {-}{{a}}", "(H1 h #h)[ATTR -]"}, {"=== h{id=abc}", "(H1 h #h)[ATTR id=abc]"}, {"=== h\n=== h", "(H1 h #h)(H1 h #h-1)"}, }) } func TestHRule(t *testing.T) { |
︙ | ︙ | |||
614 615 616 617 618 619 620 | {">", "(QL {})"}, }) } func TestQuoteList(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ | | | 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 | {">", "(QL {})"}, }) } func TestQuoteList(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"> w1 w2", "(QL {(PARA w1 w2)})"}, {"> w1\n> w2", "(QL {(PARA w1 SB w2)})"}, {"> w1\n>\n>w2", "(QL {(PARA w1)} {})(PARA >w2)"}, }) } func TestEnumAfterPara(t *testing.T) { t.Parallel() |
︙ | ︙ | |||
639 640 641 642 643 644 645 | {"; ", "(PARA ;)"}, {"; abc", "(DL (DT abc))"}, {"; abc\ndef", "(DL (DT abc))(PARA def)"}, {"; abc\n def", "(DL (DT abc))(PARA def)"}, {"; abc\n def", "(DL (DT abc SB def))"}, {":", "(PARA :)"}, {": ", "(PARA :)"}, | | | 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 | {"; ", "(PARA ;)"}, {"; abc", "(DL (DT abc))"}, {"; abc\ndef", "(DL (DT abc))(PARA def)"}, {"; abc\n def", "(DL (DT abc))(PARA def)"}, {"; abc\n def", "(DL (DT abc SB def))"}, {":", "(PARA :)"}, {": ", "(PARA :)"}, {": abc", "(PARA : abc)"}, {"; abc\n: def", "(DL (DT abc) (DD (PARA def)))"}, {"; abc\n: def\nghi", "(DL (DT abc) (DD (PARA def)))(PARA ghi)"}, {"; abc\n: def\n ghi", "(DL (DT abc) (DD (PARA def)))(PARA ghi)"}, {"; abc\n: def\n ghi", "(DL (DT abc) (DD (PARA def SB ghi)))"}, {"; abc\n: def\n\n ghi", "(DL (DT abc) (DD (PARA def)(PARA ghi)))"}, {"; abc\n:", "(DL (DT abc))(PARA :)"}, {"; abc\n: def\n: ghi", "(DL (DT abc) (DD (PARA def)) (DD (PARA ghi)))"}, |
︙ | ︙ | |||
743 744 745 746 747 748 749 | }) checkTcs(t, replace("\"", TestCases{ {"::a::{py=3}", "(PARA {: a}[ATTR py=3])"}, {"::a::{py=$2 3$}", "(PARA {: a}[ATTR py=$2 3$])"}, {"::a::{py=$2\\$3$}", "(PARA {: a}[ATTR py=2$3])"}, {"::a::{py=2$3}", "(PARA {: a}[ATTR py=2$3])"}, {"::a::{py=$2\n3$}", "(PARA {: a}[ATTR py=$2\n3$])"}, | | | 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 | }) checkTcs(t, replace("\"", TestCases{ {"::a::{py=3}", "(PARA {: a}[ATTR py=3])"}, {"::a::{py=$2 3$}", "(PARA {: a}[ATTR py=$2 3$])"}, {"::a::{py=$2\\$3$}", "(PARA {: a}[ATTR py=2$3])"}, {"::a::{py=2$3}", "(PARA {: a}[ATTR py=2$3])"}, {"::a::{py=$2\n3$}", "(PARA {: a}[ATTR py=$2\n3$])"}, {"::a::{py=$2 3}", "(PARA {: a} {py=$2 3})"}, {"::a::{py=2 py=3}", "(PARA {: a}[ATTR py=$2 3$])"}, {"::a::{.go .py}", "(PARA {: a}[ATTR class=$go py$])"}, })) } func TestTemp(t *testing.T) { |
︙ | ︙ | |||
874 875 876 877 878 879 880 | tv.visitAttributes(n.Attrs) case *ast.BLOBNode: tv.sb.WriteString("(BLOB ") tv.sb.WriteString(n.Syntax) tv.sb.WriteString(")") case *ast.TextNode: tv.sb.WriteString(n.Text) | < < < < < < | 877 878 879 880 881 882 883 884 885 886 887 888 889 890 | tv.visitAttributes(n.Attrs) case *ast.BLOBNode: tv.sb.WriteString("(BLOB ") tv.sb.WriteString(n.Syntax) tv.sb.WriteString(")") case *ast.TextNode: tv.sb.WriteString(n.Text) case *ast.BreakNode: if n.Hard { tv.sb.WriteString("HB") } else { tv.sb.WriteString("SB") } case *ast.LinkNode: |
︙ | ︙ |
Changes to query/compiled.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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 query 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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package query import ( "math/rand/v2" "slices" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // Compiled is a compiled query, to be used in a Box type Compiled struct { hasQuery bool seed int pick int order []sortOrder offset int // <= 0: no offset limit int // <= 0: no limit startMeta []*meta.Meta PreMatch MetaMatchFunc // Precondition for Match and Retrieve Terms []CompiledTerm sortFunc sortFunc } // MetaMatchFunc is a function determine whethe some metadata should be selected or not. type MetaMatchFunc func(*meta.Meta) bool func matchAlways(*meta.Meta) bool { return true } func matchNever(*meta.Meta) bool { return false } |
︙ | ︙ | |||
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | if term.Match(m) && term.Retrieve(m.Zid) { result = append(result, m) break } } } result = c.pickElements(result) result = c.sortElements(result) result = c.offsetElements(result) return limitElements(result, c.limit) } // AfterSearch applies all terms to the metadata list that was searched. // // This includes sorting, offset, limit, and picking. func (c *Compiled) AfterSearch(metaList []*meta.Meta) []*meta.Meta { if len(metaList) == 0 { return metaList } if !c.hasQuery { | > > > > > > > > | | > | > | | 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 | if term.Match(m) && term.Retrieve(m.Zid) { result = append(result, m) break } } } result = c.pickElements(result) c.ensureSortFunc() result = c.sortElements(result) result = c.offsetElements(result) return limitElements(result, c.limit) } func (c *Compiled) ensureSortFunc() { if c.sortFunc == nil { c.sortFunc = buildSortFunc(c.order) } } // AfterSearch applies all terms to the metadata list that was searched. // // This includes sorting, offset, limit, and picking. func (c *Compiled) AfterSearch(metaList []*meta.Meta) []*meta.Meta { if len(metaList) == 0 { return metaList } if !c.hasQuery { slices.SortFunc(metaList, defaultMetaSort) return metaList } if c.isDeterministic() { // We need to sort to make it deterministic if len(c.order) == 0 || c.order[0].isRandom() { slices.SortFunc(metaList, defaultMetaSort) } else { c.ensureSortFunc() slices.SortFunc(metaList, c.sortFunc) } } metaList = c.pickElements(metaList) if c.isDeterministic() { if len(c.order) > 0 && c.order[0].isRandom() { metaList = c.sortRandomly(metaList) } } else { metaList = c.sortElements(metaList) } metaList = c.offsetElements(metaList) return limitElements(metaList, c.limit) } func (c *Compiled) sortElements(metaList []*meta.Meta) []*meta.Meta { if len(c.order) > 0 { if c.order[0].isRandom() { metaList = c.sortRandomly(metaList) } else { c.ensureSortFunc() slices.SortFunc(metaList, c.sortFunc) } } return metaList } func (c *Compiled) offsetElements(metaList []*meta.Meta) []*meta.Meta { if c.offset == 0 { |
︙ | ︙ | |||
135 136 137 138 139 140 141 | } if limit := c.limit; limit > 0 && limit < count { count = limit c.limit = 0 } order := make([]int, len(metaList)) | | | | | | | < < < < < | 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 | } if limit := c.limit; limit > 0 && limit < count { count = limit c.limit = 0 } order := make([]int, len(metaList)) for i := range len(metaList) { order[i] = i } rnd := c.newRandom() picked := make([]int, count) for i := range count { last := len(order) - i n := rnd.IntN(last) picked[i] = order[n] order[n] = order[last-1] } order = nil slices.Sort(picked) result := make([]*meta.Meta, count) for i, p := range picked { result[i] = metaList[p] } return result } func (c *Compiled) sortRandomly(metaList []*meta.Meta) []*meta.Meta { rnd := c.newRandom() rnd.Shuffle( len(metaList), func(i, j int) { metaList[i], metaList[j] = metaList[j], metaList[i] }, ) return metaList } func (c *Compiled) newRandom() *rand.Rand { seed := c.seed if seed <= 0 { seed = rand.IntN(10000) + 10001 } return rand.New(rand.NewPCG(uint64(seed), uint64(seed))) } func limitElements(metaList []*meta.Meta, limit int) []*meta.Meta { if limit > 0 && limit < len(metaList) { return metaList[:limit] } return metaList } |
Changes to query/context.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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 query import ( "container/heap" "context" | > > > > | > > > > > | > > > < | < | | | | | | | > > | | | | | | > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package query import ( "container/heap" "context" "math" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // ContextSpec contains all specification values for calculating a context. type ContextSpec struct { Direction ContextDirection MaxCost int MaxCount int Full bool } // ContextDirection specifies the direction a context should be calculated. type ContextDirection uint8 const ( ContextDirBoth ContextDirection = iota ContextDirForward ContextDirBackward ) // ContextPort is the collection of box methods needed by this directive. type ContextPort interface { GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) SelectMeta(ctx context.Context, metaSeq []*meta.Meta, q *Query) ([]*meta.Meta, error) } func (spec *ContextSpec) Print(pe *PrintEnv) { pe.printSpace() pe.writeString(api.ContextDirective) if spec.Full { pe.printSpace() pe.writeString(api.FullDirective) } switch spec.Direction { case ContextDirBackward: pe.printSpace() pe.writeString(api.BackwardDirective) case ContextDirForward: pe.printSpace() pe.writeString(api.ForwardDirective) } pe.printPosInt(api.CostDirective, spec.MaxCost) pe.printPosInt(api.MaxDirective, spec.MaxCount) } func (spec *ContextSpec) Execute(ctx context.Context, startSeq []*meta.Meta, port ContextPort) []*meta.Meta { maxCost := float64(spec.MaxCost) if maxCost <= 0 { maxCost = 17 } maxCount := spec.MaxCount if maxCount <= 0 { maxCount = 200 } tasks := newQueue(startSeq, maxCost, maxCount, port) isBackward := spec.Direction == ContextDirBoth || spec.Direction == ContextDirBackward isForward := spec.Direction == ContextDirBoth || spec.Direction == ContextDirForward result := []*meta.Meta{} for { m, cost := tasks.next() if m == nil { break } result = append(result, m) for _, p := range m.ComputedPairsRest() { tasks.addPair(ctx, p.Key, p.Value, cost, isBackward, isForward) } if !spec.Full { continue } if tags, found := m.GetList(api.KeyTags); found { tasks.addTags(ctx, tags, cost) } } return result } type ztlCtxItem struct { cost float64 meta *meta.Meta } type ztlCtxQueue []ztlCtxItem func (q ztlCtxQueue) Len() int { return len(q) } func (q ztlCtxQueue) Less(i, j int) bool { return q[i].cost < q[j].cost } func (q ztlCtxQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] } func (q *ztlCtxQueue) Push(x any) { *q = append(*q, x.(ztlCtxItem)) } func (q *ztlCtxQueue) Pop() any { old := *q n := len(old) item := old[n-1] old[n-1].meta = nil // avoid memory leak *q = old[0 : n-1] return item } type contextTask struct { port ContextPort seen *id.Set queue ztlCtxQueue maxCost float64 limit int tagMetas map[string][]*meta.Meta tagZids map[string]*id.Set // just the zids of tagMetas metaZid map[id.Zid]*meta.Meta // maps zid to meta for all meta retrieved with tags } func newQueue(startSeq []*meta.Meta, maxCost float64, limit int, port ContextPort) *contextTask { result := &contextTask{ port: port, seen: id.NewSet(), maxCost: maxCost, limit: limit, tagMetas: make(map[string][]*meta.Meta), tagZids: make(map[string]*id.Set), metaZid: make(map[id.Zid]*meta.Meta), } queue := make(ztlCtxQueue, 0, len(startSeq)) for _, m := range startSeq { queue = append(queue, ztlCtxItem{cost: 1, meta: m}) } heap.Init(&queue) result.queue = queue return result } func (ct *contextTask) addPair(ctx context.Context, key, value string, curCost float64, isBackward, isForward bool) { if key == api.KeyBack { return } newCost := curCost + contextCost(key) if key == api.KeyBackward { if isBackward { ct.addIDSet(ctx, newCost, value) |
︙ | ︙ | |||
158 159 160 161 162 163 164 | if t := meta.Type(key); t == meta.TypeID { ct.addID(ctx, newCost, value) } else if t == meta.TypeIDSet { ct.addIDSet(ctx, newCost, value) } } | | > > | < | | | < < < < < < | < < | | > > | | > > | | < | | > | > > > > > > > | | > > > | | | < > | | > | > > > > > | < < < | | | | | | > | < | | > | | < < > > | | | | < | < | | | | 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 | if t := meta.Type(key); t == meta.TypeID { ct.addID(ctx, newCost, value) } else if t == meta.TypeIDSet { ct.addIDSet(ctx, newCost, value) } } func contextCost(key string) float64 { switch key { case api.KeyFolge, api.KeyPrecursor: return 1 case api.KeySubordinates, api.KeySuperior: return 1.5 case api.KeySuccessors, api.KeyPredecessor: return 7 } return 2 } func (ct *contextTask) addID(ctx context.Context, newCost float64, value string) { if zid, errParse := id.Parse(value); errParse == nil { if m, errGetMeta := ct.port.GetMeta(ctx, zid); errGetMeta == nil { ct.addMeta(m, newCost) } } } func (ct *contextTask) addMeta(m *meta.Meta, newCost float64) { // If len(zc.seen) <= 1, the initial zettel is processed. In this case allow all // other zettel that are directly reachable, without taking the cost into account. // Of course, the limit ist still relevant. if !ct.hasLimit() && (ct.seen.Length() <= 1 || ct.maxCost == 0 || newCost <= ct.maxCost) { if !ct.seen.Contains(m.Zid) { heap.Push(&ct.queue, ztlCtxItem{cost: newCost, meta: m}) } } } func (ct *contextTask) addIDSet(ctx context.Context, newCost float64, value string) { elems := meta.ListFromValue(value) refCost := referenceCost(newCost, len(elems)) for _, val := range elems { ct.addID(ctx, refCost, val) } } func referenceCost(baseCost float64, numReferences int) float64 { nRefs := float64(numReferences) return nRefs*math.Log2(nRefs+1) + baseCost } func (ct *contextTask) addTags(ctx context.Context, tags []string, baseCost float64) { var zidSet *id.Set for _, tag := range tags { zs := ct.updateTagData(ctx, tag) zidSet = zidSet.IUnion(zs) } zidSet.ForEach(func(zid id.Zid) { minCost := math.MaxFloat64 costFactor := 1.1 for _, tag := range tags { tagZids := ct.tagZids[tag] if tagZids.Contains(zid) { cost := tagCost(baseCost, tagZids.Length()) if cost < minCost { minCost = cost } costFactor /= 1.1 } } ct.addMeta(ct.metaZid[zid], minCost*costFactor) }) } func (ct *contextTask) updateTagData(ctx context.Context, tag string) *id.Set { if _, found := ct.tagMetas[tag]; found { return ct.tagZids[tag] } q := Parse(api.KeyTags + api.SearchOperatorHas + tag + " ORDER REVERSE " + api.KeyID) ml, err := ct.port.SelectMeta(ctx, nil, q) if err != nil { ml = nil } ct.tagMetas[tag] = ml zids := id.NewSetCap(len(ml)) for _, m := range ml { zid := m.Zid zids = zids.Add(zid) if _, found := ct.metaZid[zid]; !found { ct.metaZid[zid] = m } } ct.tagZids[tag] = zids return zids } func tagCost(baseCost float64, numTags int) float64 { nTags := float64(numTags) return nTags*math.Log2(nTags+1) + baseCost } func (ct *contextTask) next() (*meta.Meta, float64) { if ct.hasLimit() { return nil, -1 } for len(ct.queue) > 0 { item := heap.Pop(&ct.queue).(ztlCtxItem) m := item.meta zid := m.Zid if ct.seen.Contains(zid) { continue } ct.seen.Add(zid) return m, item.cost } return nil, -1 } func (ct *contextTask) hasLimit() bool { limit := ct.limit return limit > 0 && ct.seen.Length() >= limit } |
Changes to query/parser.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 query import ( "strconv" | > > > | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package query import ( "strconv" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // Parse the query specification and return a Query object. func Parse(spec string) (q *Query) { return q.Parse(spec) } |
︙ | ︙ | |||
38 39 40 41 42 43 44 | type parserState struct { inp *input.Input } func (ps *parserState) mustStop() bool { return ps.inp.Ch == input.EOS } func (ps *parserState) acceptSingleKw(s string) bool { | > | > | | > | < | | | | 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 | type parserState struct { inp *input.Input } func (ps *parserState) mustStop() bool { return ps.inp.Ch == input.EOS } func (ps *parserState) acceptSingleKw(s string) bool { inp := ps.inp if inp.Accept(s) && (inp.IsSpace() || ps.isActionSep() || ps.mustStop()) { return true } return false } func (ps *parserState) acceptKwArgs(s string) bool { inp := ps.inp if inp.Accept(s) && inp.IsSpace() { inp.SkipSpace() return true } return false } const ( actionSeparatorChar = '|' existOperatorChar = '?' searchOperatorNotChar = '!' searchOperatorEqualChar = '=' searchOperatorHasChar = ':' searchOperatorPrefixChar = '[' searchOperatorSuffixChar = ']' searchOperatorMatchChar = '~' searchOperatorLessChar = '<' searchOperatorGreaterChar = '>' ) func (ps *parserState) parse(q *Query) *Query { inp := ps.inp inp.SkipSpace() if ps.mustStop() { return q } firstPos := inp.Pos zidSet := id.NewSet() for { pos := inp.Pos zid, found := ps.scanZid() if !found { inp.SetPos(pos) break } if !zidSet.Contains(zid) { zidSet.Add(zid) q = createIfNeeded(q) q.zids = append(q.zids, zid) } inp.SkipSpace() if ps.mustStop() { q.zids = nil break } } hasContext := false for { inp.SkipSpace() if ps.mustStop() { break } pos := inp.Pos if ps.acceptSingleKw(api.ContextDirective) { if hasContext { inp.SetPos(pos) |
︙ | ︙ | |||
134 135 136 137 138 139 140 | } if q != nil && len(q.directives) == 0 { inp.SetPos(firstPos) // No directive -> restart at beginning q.zids = nil } for { | | | 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | } if q != nil && len(q.directives) == 0 { inp.SetPos(firstPos) // No directive -> restart at beginning q.zids = nil } for { inp.SkipSpace() if ps.mustStop() { break } pos := inp.Pos if ps.acceptSingleKw(api.OrDirective) { q = createIfNeeded(q) if !q.terms[len(q.terms)-1].isEmpty() { |
︙ | ︙ | |||
196 197 198 199 200 201 202 | return q } func (ps *parserState) parseContext(q *Query) *Query { inp := ps.inp spec := &ContextSpec{} for { | | > > > > > | 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | return q } func (ps *parserState) parseContext(q *Query) *Query { inp := ps.inp spec := &ContextSpec{} for { inp.SkipSpace() if ps.mustStop() { break } pos := inp.Pos if ps.acceptSingleKw(api.FullDirective) { spec.Full = true continue } inp.SetPos(pos) if ps.acceptSingleKw(api.BackwardDirective) { spec.Direction = ContextDirBackward continue } inp.SetPos(pos) if ps.acceptSingleKw(api.ForwardDirective) { spec.Direction = ContextDirForward |
︙ | ︙ | |||
256 257 258 259 260 261 262 | } func (ps *parserState) parseUnlinked(q *Query) *Query { inp := ps.inp spec := &UnlinkedSpec{} for { | | | 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | } func (ps *parserState) parseUnlinked(q *Query) *Query { inp := ps.inp spec := &UnlinkedSpec{} for { inp.SkipSpace() if ps.mustStop() { break } pos := inp.Pos if ps.acceptKwArgs(api.PhraseDirective) { if word := ps.scanWord(); len(word) > 0 { spec.words = append(spec.words, string(word)) |
︙ | ︙ | |||
332 333 334 335 336 337 338 | if q.limit == 0 || q.limit >= num { q.limit = num } return q, true } func (ps *parserState) parseActions(q *Query) *Query { | > | | | 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 | if q.limit == 0 || q.limit >= num { q.limit = num } return q, true } func (ps *parserState) parseActions(q *Query) *Query { inp := ps.inp inp.Next() var words []string for { inp.SkipSpace() word := ps.scanWord() if len(word) == 0 { break } words = append(words, string(word)) } if len(words) > 0 { |
︙ | ︙ | |||
363 364 365 366 367 368 369 | } text, key := ps.scanSearchTextOrKey(hasOp) if len(key) > 0 { // Assert: hasOp == false op, hasOp = ps.scanSearchOp() // Assert hasOp == true if op == cmpExist || op == cmpNotExist { | | | 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 | } text, key := ps.scanSearchTextOrKey(hasOp) if len(key) > 0 { // Assert: hasOp == false op, hasOp = ps.scanSearchOp() // Assert hasOp == true if op == cmpExist || op == cmpNotExist { if inp.IsSpace() || ps.isActionSep() || ps.mustStop() { return q.addKey(string(key), op) } ps.inp.SetPos(pos) hasOp = false text = ps.scanWord() key = nil } else { |
︙ | ︙ | |||
402 403 404 405 406 407 408 | } func (ps *parserState) scanSearchTextOrKey(hasOp bool) ([]byte, []byte) { inp := ps.inp pos := inp.Pos allowKey := !hasOp | | | | 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 | } func (ps *parserState) scanSearchTextOrKey(hasOp bool) ([]byte, []byte) { inp := ps.inp pos := inp.Pos allowKey := !hasOp for !inp.IsSpace() && !ps.isActionSep() && !ps.mustStop() { if allowKey { switch inp.Ch { case searchOperatorNotChar, existOperatorChar, searchOperatorEqualChar, searchOperatorHasChar, searchOperatorPrefixChar, searchOperatorSuffixChar, searchOperatorMatchChar, searchOperatorLessChar, searchOperatorGreaterChar: allowKey = false if key := inp.Src[pos:inp.Pos]; meta.KeyIsValid(string(key)) { return nil, key } } } inp.Next() } return inp.Src[pos:inp.Pos], nil } func (ps *parserState) scanWord() []byte { inp := ps.inp pos := inp.Pos for !inp.IsSpace() && !ps.isActionSep() && !ps.mustStop() { inp.Next() } return inp.Src[pos:inp.Pos] } func (ps *parserState) scanPosInt() (int, bool) { word := ps.scanWord() |
︙ | ︙ | |||
500 501 502 503 504 505 506 | } if negate { return op.negate(), true } return op, true } | < < < < < < < < < < < < < < < < < | 511 512 513 514 515 516 517 518 519 520 | } if negate { return op.negate(), true } return op, true } func (ps *parserState) isActionSep() bool { return ps.inp.Ch == actionSeparatorChar } |
Changes to query/parser_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 query_test import ( "testing" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package query_test import ( "testing" |
︙ | ︙ | |||
31 32 33 34 35 36 37 38 39 40 41 42 43 44 | {"1 ITEMS", "00000000000001 ITEMS"}, {"ITEMS", "ITEMS"}, {"CONTEXT", "CONTEXT"}, {"CONTEXT a", "CONTEXT a"}, {"0 CONTEXT", "0 CONTEXT"}, {"1 CONTEXT", "00000000000001 CONTEXT"}, {"00000000000001 CONTEXT", "00000000000001 CONTEXT"}, {"100000000000001 CONTEXT", "100000000000001 CONTEXT"}, {"1 CONTEXT BACKWARD", "00000000000001 CONTEXT BACKWARD"}, {"1 CONTEXT FORWARD", "00000000000001 CONTEXT FORWARD"}, {"1 CONTEXT COST ", "00000000000001 CONTEXT COST"}, {"1 CONTEXT COST 3", "00000000000001 CONTEXT COST 3"}, {"1 CONTEXT COST x", "00000000000001 CONTEXT COST x"}, {"1 CONTEXT MAX 5", "00000000000001 CONTEXT MAX 5"}, {"1 CONTEXT MAX y", "00000000000001 CONTEXT MAX y"}, {"1 CONTEXT MAX 5 COST 7", "00000000000001 CONTEXT COST 7 MAX 5"}, {"1 CONTEXT | N", "00000000000001 CONTEXT | N"}, | > | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | {"1 ITEMS", "00000000000001 ITEMS"}, {"ITEMS", "ITEMS"}, {"CONTEXT", "CONTEXT"}, {"CONTEXT a", "CONTEXT a"}, {"0 CONTEXT", "0 CONTEXT"}, {"1 CONTEXT", "00000000000001 CONTEXT"}, {"00000000000001 CONTEXT", "00000000000001 CONTEXT"}, {"100000000000001 CONTEXT", "100000000000001 CONTEXT"}, {"1 CONTEXT FULL", "00000000000001 CONTEXT FULL"}, {"1 CONTEXT BACKWARD", "00000000000001 CONTEXT BACKWARD"}, {"1 CONTEXT FORWARD", "00000000000001 CONTEXT FORWARD"}, {"1 CONTEXT COST ", "00000000000001 CONTEXT COST"}, {"1 CONTEXT COST 3", "00000000000001 CONTEXT COST 3"}, {"1 CONTEXT COST x", "00000000000001 CONTEXT COST x"}, {"1 CONTEXT MAX 5", "00000000000001 CONTEXT MAX 5"}, {"1 CONTEXT MAX y", "00000000000001 CONTEXT MAX y"}, {"1 CONTEXT MAX 5 COST 7", "00000000000001 CONTEXT COST 7 MAX 5"}, {"1 CONTEXT | N", "00000000000001 CONTEXT | N"}, |
︙ | ︙ |
Changes to query/print.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 query import ( "io" "strconv" "strings" | > > > | | | 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package query import ( "io" "strconv" "strings" "t73f.de/r/zsc/api" "t73f.de/r/zsc/maps" "zettelstore.de/z/zettel/id" ) var op2string = map[compareOp]string{ cmpExist: api.ExistOperator, cmpNotExist: api.ExistNotOperator, cmpEqual: api.SearchOperatorEqual, |
︙ | ︙ |
Changes to query/query.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 query provides a query for zettel. package query import ( "context" | > > > | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package query provides a query for zettel. package query import ( "context" "math/rand/v2" "slices" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // Searcher is used to select zettel identifier based on search criteria. type Searcher interface { // Select all zettel that contains the given exact word. // The word must be normalized through Unicode NKFD, trimmed and not empty. SearchEqual(word string) *id.Set // Select all zettel that have a word with the given prefix. // The prefix must be normalized through Unicode NKFD, trimmed and not empty. SearchPrefix(prefix string) *id.Set // Select all zettel that have a word with the given suffix. // The suffix must be normalized through Unicode NKFD, trimmed and not empty. SearchSuffix(suffix string) *id.Set // Select all zettel that contains the given string. // The string must be normalized through Unicode NKFD, trimmed and not empty. SearchContains(s string) *id.Set } // Query specifies a mechanism for querying zettel. type Query struct { // Präfixed zettel identifier. zids []id.Zid |
︙ | ︙ | |||
243 244 245 246 247 248 249 | func (q *Query) addKey(key string, op compareOp) *Query { q = createIfNeeded(q) q.terms[len(q.terms)-1].addKey(key, op) return q } | > > > > > > > > > > | > | > | 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 | func (q *Query) addKey(key string, op compareOp) *Query { q = createIfNeeded(q) q.terms[len(q.terms)-1].addKey(key, op) return q } var missingMap = map[compareOp]bool{ cmpNotExist: true, cmpNotEqual: true, cmpHasNot: true, cmpNoMatch: true, } // GetMetaValues returns the slice of all values specified for a given metadata key. // If `withMissing` is true, all values are returned. Otherwise only those, // where the comparison operator will positively search for a value. func (q *Query) GetMetaValues(key string, withMissing bool) (vals []string) { if q == nil { return nil } for _, term := range q.terms { if mvs, hasMv := term.mvals[key]; hasMv { for _, ev := range mvs { if withMissing || !missingMap[ev.op] { vals = append(vals, ev.value) } } } } slices.Sort(vals) return slices.Compact(vals) } |
︙ | ︙ | |||
287 288 289 290 291 292 293 | return q.seed, q.seed > 0 } // SetDeterministic signals that the result should be the same if the seed is the same. func (q *Query) SetDeterministic() *Query { q = createIfNeeded(q) if q.seed <= 0 { | | | 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | return q.seed, q.seed > 0 } // SetDeterministic signals that the result should be the same if the seed is the same. func (q *Query) SetDeterministic() *Query { q = createIfNeeded(q) if q.seed <= 0 { q.seed = int(rand.IntN(10000) + 1) } return q } // Actions returns the slice of action specifications func (q *Query) Actions() []string { if q == nil { |
︙ | ︙ | |||
392 393 394 395 396 397 398 | cTerm.Match = matchAlways } result.Terms = append(result.Terms, cTerm) } return result } | | | | | | | 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 | cTerm.Match = matchAlways } result.Terms = append(result.Terms, cTerm) } return result } func metaList2idSet(ml []*meta.Meta) *id.Set { if ml == nil { return nil } result := id.NewSetCap(len(ml)) for _, m := range ml { result = result.Add(m.Zid) } return result } func (ct *conjTerms) retrieveAndCompileTerm(searcher Searcher, startSet *id.Set) CompiledTerm { match := ct.compileMeta() // Match might add some searches var pred RetrievePredicate if searcher != nil { pred = ct.retrieveIndex(searcher) if startSet != nil { if pred == nil { pred = startSet.ContainsOrNil } else { predSet := id.NewSetCap(startSet.Length()) startSet.ForEach(func(zid id.Zid) { if pred(zid) { predSet = predSet.Add(zid) } }) pred = predSet.ContainsOrNil } } } return CompiledTerm{Match: match, Retrieve: pred} } |
︙ | ︙ | |||
442 443 444 445 446 447 448 | positives := retrievePositives(normCalls, plainCalls) if positives == nil { // No positive search for words, must contain only words for a negative search. // Otherwise len(search) == 0 (see above) negatives := retrieveNegatives(negCalls) return func(zid id.Zid) bool { return !negatives.ContainsOrNil(zid) } } | | | | | | 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 | positives := retrievePositives(normCalls, plainCalls) if positives == nil { // No positive search for words, must contain only words for a negative search. // Otherwise len(search) == 0 (see above) negatives := retrieveNegatives(negCalls) return func(zid id.Zid) bool { return !negatives.ContainsOrNil(zid) } } if positives.IsEmpty() { // Positive search didn't found anything. We can omit the negative search. return neverIncluded } if len(negCalls) == 0 { // Positive search found something, but there is no negative search. return positives.Contains } negatives := retrieveNegatives(negCalls) if negatives == nil { return positives.Contains } return func(zid id.Zid) bool { return positives.Contains(zid) && !negatives.ContainsOrNil(zid) } } // Limit returns only s.GetLimit() elements of the given list. func (q *Query) Limit(metaList []*meta.Meta) []*meta.Meta { if q == nil { return metaList } return limitElements(metaList, q.limit) } |
Changes to query/retrieve.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 | //----------------------------------------------------------------------------- // 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 query // This file contains helper functions to search within the index. import ( "fmt" "strings" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" ) type searchOp struct { s string op compareOp } | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package query // This file contains helper functions to search within the index. import ( "fmt" "strings" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" ) type searchOp struct { s string op compareOp } type searchFunc func(string) *id.Set type searchCallMap map[searchOp]searchFunc var cmpPred = map[compareOp]func(string, string) bool{ cmpEqual: stringEqual, cmpPrefix: strings.HasPrefix, cmpSuffix: strings.HasSuffix, cmpMatch: strings.Contains, |
︙ | ︙ | |||
97 98 99 100 101 102 103 | if _, found := plainCalls[val]; found { return true } } return false } | | | | | | | | | | | 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 | if _, found := plainCalls[val]; found { return true } } return false } func retrievePositives(normCalls, plainCalls searchCallMap) *id.Set { if isSuperset(normCalls, plainCalls) { var normResult *id.Set for c, sf := range normCalls { normResult = normResult.IntersectOrSet(sf(c.s)) } return normResult } type searchResults map[searchOp]*id.Set var cache searchResults var plainResult *id.Set for c, sf := range plainCalls { result := sf(c.s) if _, found := normCalls[c]; found { if cache == nil { cache = make(searchResults) } cache[c] = result } plainResult = plainResult.IntersectOrSet(result) } var normResult *id.Set for c, sf := range normCalls { if cache != nil { if result, found := cache[c]; found { normResult = normResult.IntersectOrSet(result) continue } } normResult = normResult.IntersectOrSet(sf(c.s)) } return normResult.IUnion(plainResult) } func isSuperset(normCalls, plainCalls searchCallMap) bool { for c := range plainCalls { if _, found := normCalls[c]; !found { return false } } return true } func retrieveNegatives(negCalls searchCallMap) *id.Set { var negatives *id.Set for val, sf := range negCalls { negatives = negatives.IUnion(sf(val.s)) } return negatives } func getSearchFunc(searcher Searcher, op compareOp) searchFunc { switch op { case cmpEqual: |
︙ | ︙ |
Changes to query/select.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 query import ( "fmt" "strconv" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package query import ( "fmt" "strconv" |
︙ | ︙ | |||
117 118 119 120 121 122 123 | return createMatchTimestampFunc(values, addSearch) case meta.TypeNumber: return createMatchNumberFunc(values, addSearch) case meta.TypeTagSet: return createMatchTagSetFunc(values, addSearch) case meta.TypeWord: return createMatchWordFunc(values, addSearch) | < < | | 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 createMatchTimestampFunc(values, addSearch) case meta.TypeNumber: return createMatchNumberFunc(values, addSearch) case meta.TypeTagSet: return createMatchTagSetFunc(values, addSearch) case meta.TypeWord: return createMatchWordFunc(values, addSearch) case meta.TypeZettelmarkup: return createMatchZmkFunc(values, addSearch) } return createMatchStringFunc(values, addSearch) } func createMatchIDFunc(values []expValue, addSearch addSearchFunc) matchValueFunc { preds := valuesToIDPredicates(values, addSearch) return func(value string) bool { for _, pred := range preds { if !pred(value) { return false } } return true } } func createMatchIDSetFunc(values []expValue, addSearch addSearchFunc) matchValueFunc { predList := valuesToSetPredicates(preprocessSet(values), addSearch) return func(value string) bool { ids := meta.ListFromValue(value) for _, preds := range predList { for _, pred := range preds { if !pred(ids) { return false } |
︙ | ︙ | |||
177 178 179 180 181 182 183 | } } return true } } func createMatchTagSetFunc(values []expValue, addSearch addSearchFunc) matchValueFunc { | | | 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | } } return true } } func createMatchTagSetFunc(values []expValue, addSearch addSearchFunc) matchValueFunc { predList := valuesToSetPredicates(processTagSet(preprocessSet(sliceToLower(values))), addSearch) return func(value string) bool { tags := meta.TagsFromValue(value) for _, preds := range predList { for _, pred := range preds { if !pred(tags) { return false } |
︙ | ︙ | |||
230 231 232 233 234 235 236 | return func(value string) bool { value = strings.ToLower(value) for _, pred := range preds { if !pred(value) { return false } } | < < < < < < < < < < < < < < < | 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | return func(value string) bool { value = strings.ToLower(value) for _, pred := range preds { if !pred(value) { return false } } return true } } func sliceToLower(sl []expValue) []expValue { result := make([]expValue, 0, len(sl)) for _, s := range sl { |
︙ | ︙ | |||
307 308 309 310 311 312 313 | } } return true } func zmk2text(zmk string) string { isASCII, hasUpper, needParse := true, false, false | | | 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | } } return true } func zmk2text(zmk string) string { isASCII, hasUpper, needParse := true, false, false for i := range len(zmk) { ch := zmk[i] if ch >= utf8.RuneSelf { isASCII = false break } hasUpper = hasUpper || ('A' <= ch && ch <= 'Z') needParse = needParse || !(('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z') || ('0' <= ch && ch <= '9') || ch == ' ') |
︙ | ︙ | |||
382 383 384 385 386 387 388 | result[i] = createWordCompareFunc(value, op) } } return result } func isDigits(s string) bool { | | | 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 | result[i] = createWordCompareFunc(value, op) } } return result } func isDigits(s string) bool { for i := range len(s) { if ch := s[i]; ch < '0' || '9' < ch { return false } } return true } |
︙ | ︙ | |||
562 563 564 565 566 567 568 | default: panic(fmt.Sprintf("Unknown compare operation %d with value %q", cmpOp, cmpVal)) } } type stringSetPredicate func(value []string) bool | | | 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 | default: panic(fmt.Sprintf("Unknown compare operation %d with value %q", cmpOp, cmpVal)) } } type stringSetPredicate func(value []string) bool func valuesToSetPredicates(values [][]expValue, addSearch addSearchFunc) [][]stringSetPredicate { result := make([][]stringSetPredicate, len(values)) for i, val := range values { elemPreds := make([]stringSetPredicate, len(val)) for j, v := range val { opVal := v.value // loop variable is used in closure --> save needed value switch op := disambiguateWordOp(v.op); op { case cmpEqual: |
︙ | ︙ |
Changes to query/select_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 query_test import ( "context" "testing" | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package query_test import ( "context" "testing" "t73f.de/r/zsc/api" "zettelstore.de/z/query" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func TestMatchZidNegate(t *testing.T) { q := query.Parse(api.KeyID + api.SearchOperatorHasNot + string(api.ZidVersion) + " " + api.KeyID + api.SearchOperatorHasNot + string(api.ZidLicense)) |
︙ | ︙ |
Changes to query/sorter.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 query import ( "strconv" | > > > > | | | | | < | | | < < | < | < | > > | | | | | | > > | | | | > > > | | | | > > > | | | | | > | | > | > | | | > | > > | | | | > | | > | > | | | > > > > > > > > > > | | > > > > > > | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package query import ( "cmp" "strconv" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/meta" ) type sortFunc func(i, j *meta.Meta) int func buildSortFunc(order []sortOrder) sortFunc { hasID := false sortFuncs := make([]sortFunc, 0, len(order)+1) for _, o := range order { sortFuncs = append(sortFuncs, o.buildSortfunc()) if o.key == api.KeyID { hasID = true break } } if !hasID { sortFuncs = append(sortFuncs, defaultMetaSort) } if len(sortFuncs) == 1 { return sortFuncs[0] } return func(i, j *meta.Meta) int { for _, sf := range sortFuncs { if result := sf(i, j); result != 0 { return result } } return 0 } } func (so *sortOrder) buildSortfunc() sortFunc { key := so.key keyType := meta.Type(key) if key == api.KeyID || keyType == meta.TypeCredential { if so.descending { return defaultMetaSort } return func(i, j *meta.Meta) int { return cmp.Compare(i.Zid, j.Zid) } } if keyType == meta.TypeTimestamp { return createSortTimestampFunc(key, so.descending) } if keyType == meta.TypeNumber { return createSortNumberFunc(key, so.descending) } return createSortStringFunc(key, so.descending) } func defaultMetaSort(i, j *meta.Meta) int { return cmp.Compare(j.Zid, i.Zid) } func createSortTimestampFunc(key string, descending bool) sortFunc { if descending { return func(i, j *meta.Meta) int { iVal, iOk := i.Get(key) jVal, jOk := j.Get(key) if result := compareFound(jOk, iOk); result != 0 { return result } return cmp.Compare(meta.ExpandTimestamp(jVal), meta.ExpandTimestamp(iVal)) } } return func(i, j *meta.Meta) int { iVal, iOk := i.Get(key) jVal, jOk := j.Get(key) if result := compareFound(iOk, jOk); result != 0 { return result } return cmp.Compare(meta.ExpandTimestamp(iVal), meta.ExpandTimestamp(jVal)) } } func createSortNumberFunc(key string, descending bool) sortFunc { if descending { return func(i, j *meta.Meta) int { iVal, iOk := getNum(i, key) jVal, jOk := getNum(j, key) if result := compareFound(jOk, iOk); result != 0 { return result } return cmp.Compare(jVal, iVal) } } return func(i, j *meta.Meta) int { iVal, iOk := getNum(i, key) jVal, jOk := getNum(j, key) if result := compareFound(iOk, jOk); result != 0 { return result } return cmp.Compare(iVal, jVal) } } func getNum(m *meta.Meta, key string) (int64, bool) { if s, ok := m.Get(key); ok { if i, err := strconv.ParseInt(s, 10, 64); err == nil { return i, true } } return 0, false } func createSortStringFunc(key string, descending bool) sortFunc { if descending { return func(i, j *meta.Meta) int { iVal, iOk := i.Get(key) jVal, jOk := j.Get(key) if result := compareFound(jOk, iOk); result != 0 { return result } return cmp.Compare(jVal, iVal) } } return func(i, j *meta.Meta) int { iVal, iOk := i.Get(key) jVal, jOk := j.Get(key) if result := compareFound(iOk, jOk); result != 0 { return result } return cmp.Compare(iVal, jVal) } } func compareFound(iOk, jOk bool) int { if iOk { if jOk { return 0 } return 1 } if jOk { return -1 } return 0 } |
Changes to query/specs.go.
1 2 3 4 5 6 7 8 9 10 11 12 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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 query | > > > | | 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) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package query import "t73f.de/r/zsc/api" // IdentSpec contains all specification values to calculate the ident directive. type IdentSpec struct{} func (spec *IdentSpec) Print(pe *PrintEnv) { pe.printSpace() pe.writeString(api.IdentDirective) |
︙ | ︙ |
Changes to query/unlinked.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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 query 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) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package query import ( "t73f.de/r/zsc/api" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/meta" ) // UnlinkedSpec contains all specification values to calculate unlinked references. type UnlinkedSpec struct { words []string |
︙ | ︙ |
Changes to strfun/escape.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 strfun import "io" var ( | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package strfun import "io" var ( |
︙ | ︙ |
Changes to strfun/set.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 strfun // Set ist a set of strings. type Set map[string]struct{} | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package strfun // Set ist a set of strings. type Set map[string]struct{} |
︙ | ︙ |
Changes to strfun/slugify.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 strfun import ( "strings" "unicode" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package strfun import ( "strings" "unicode" |
︙ | ︙ |
Changes to strfun/slugify_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 strfun_test import ( "testing" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package strfun_test import ( "testing" |
︙ | ︙ | |||
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | if got := strfun.Slugify(test.in); got != test.exp { t.Errorf("%q: %q != %q", test.in, got, test.exp) } } } func eqStringSlide(got, exp []string) bool { if len(got) != len(exp) { return false } for i, g := range got { if g != exp[i] { return false } } return true } func TestNormalizeWord(t *testing.T) { t.Parallel() tests := []struct { in string exp []string }{ | > > > | | | | | 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 | if got := strfun.Slugify(test.in); got != test.exp { t.Errorf("%q: %q != %q", test.in, got, test.exp) } } } func eqStringSlide(got, exp []string) bool { if got == nil { return exp == nil } if len(got) != len(exp) { return false } for i, g := range got { if g != exp[i] { return false } } return true } func TestNormalizeWord(t *testing.T) { t.Parallel() tests := []struct { in string exp []string }{ {"", nil}, {" ", nil}, {"ˋ", nil}, // No single diacritic char, such as U+02CB {"simple test", []string{"simple", "test"}}, {"I'm a go developer", []string{"i", "m", "a", "go", "developer"}}, {"-!->simple test<-!-", []string{"simple", "test"}}, {"äöüÄÖÜß", []string{"aouaouß"}}, {"\"aèf", []string{"aef"}}, {"a#b", []string{"a", "b"}}, {"*", nil}, {"123", []string{"123"}}, {"1²3", []string{"123"}}, {"Period.", []string{"period"}}, {" WORD NUMBER ", []string{"word", "number"}}, } for _, test := range tests { if got := strfun.NormalizeWords(test.in); !eqStringSlide(got, test.exp) { t.Errorf("%q: %q != %q", test.in, got, test.exp) } } } |
Changes to strfun/strfun.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 strfun provides some string functions. package strfun import ( "strings" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package strfun provides some string functions. package strfun import ( "strings" |
︙ | ︙ | |||
36 37 38 39 40 41 42 | runes[maxLen-1] = '\u2025' } var sb strings.Builder for _, r := range runes { sb.WriteRune(r) } | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | runes[maxLen-1] = '\u2025' } var sb strings.Builder for _, r := range runes { sb.WriteRune(r) } for range maxLen - len(runes) { sb.WriteRune(pad) } return sb.String() } // SplitLines splits the given string into a list of lines. func SplitLines(s string) []string { |
︙ | ︙ |
Changes to strfun/strfun_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 strfun_test import ( "testing" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package strfun_test import ( "testing" |
︙ | ︙ |
Added testdata/testbox/00009999999998.zettel.
> > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 | id: 00009999999998 title: Zettelstore Application Directory role: configuration syntax: none app-zid: 00009999999998 created: 20240703235900 lang: en modified: 20240708125724 nozid-zid: 9999999998 noappzid: 00009999999998 visibility: login |
Changes to tests/client/client_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //----------------------------------------------------------------------------- // Copyright (c) 2021-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore client 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 client provides a client for accessing the Zettelstore via its API. package client_test import ( "context" "flag" "fmt" "net/http" "net/url" "strconv" "testing" | > > > > > | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2021-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package client provides a client for accessing the Zettelstore via its API. package client_test import ( "context" "flag" "fmt" "io" "net/http" "net/url" "slices" "strconv" "testing" "t73f.de/r/zsc/api" "t73f.de/r/zsc/client" "zettelstore.de/z/kernel" ) func nextZid(zid api.ZettelID) api.ZettelID { numVal, err := strconv.ParseUint(string(zid), 10, 64) if err != nil { panic(err) |
︙ | ︙ | |||
45 46 47 48 49 50 51 | } } } func TestListZettel(t *testing.T) { const ( | | | | | | 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | } } } func TestListZettel(t *testing.T) { const ( ownerZettel = 60 configRoleZettel = 38 writerZettel = ownerZettel - 25 readerZettel = ownerZettel - 25 creatorZettel = 10 publicZettel = 5 ) testdata := []struct { user string exp int |
︙ | ︙ | |||
221 222 223 224 225 226 227 | } checkListZid(t, metaSeq, 0, api.ZidTemplateNewZettel) checkListZid(t, metaSeq, 1, api.ZidTemplateNewRole) checkListZid(t, metaSeq, 2, api.ZidTemplateNewTag) checkListZid(t, metaSeq, 3, api.ZidTemplateNewUser) } | | | | | | | | | < > | | | | | | | < > | | | | < < | | < > | < < < | < > | > | | < < < < > | | | < < < < > > > | 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 | } checkListZid(t, metaSeq, 0, api.ZidTemplateNewZettel) checkListZid(t, metaSeq, 1, api.ZidTemplateNewRole) checkListZid(t, metaSeq, 2, api.ZidTemplateNewTag) checkListZid(t, metaSeq, 3, api.ZidTemplateNewUser) } func TestGetZettelContext(t *testing.T) { const ( allUserZid = api.ZettelID("20211019200500") ownerZid = api.ZettelID("20210629163300") writerZid = api.ZettelID("20210629165000") readerZid = api.ZettelID("20210629165024") creatorZid = api.ZettelID("20210629165050") limitAll = 3 ) t.Parallel() c := getClient() c.SetAuth("owner", "owner") rl, err := c.QueryZettel(context.Background(), string(ownerZid)+" CONTEXT LIMIT "+strconv.Itoa(limitAll)) if err != nil { t.Error(err) return } checkZidList(t, []api.ZettelID{ownerZid, allUserZid, writerZid}, rl) rl, err = c.QueryZettel(context.Background(), string(ownerZid)+" CONTEXT BACKWARD") if err != nil { t.Error(err) return } checkZidList(t, []api.ZettelID{ownerZid, allUserZid}, rl) } func checkZidList(t *testing.T, exp []api.ZettelID, got [][]byte) { t.Helper() if len(exp) != len(got) { t.Errorf("expected a list fo length %d, but got %d", len(exp), len(got)) return } for i, expZid := range exp { if gotZid := api.ZettelID(got[i][:14]); expZid != gotZid { t.Errorf("lists differ at pos %d: expected id %v, but got %v", i, expZid, gotZid) } } } func TestGetUnlinkedReferences(t *testing.T) { t.Parallel() c := getClient() c.SetAuth("owner", "owner") _, _, metaSeq, err := c.QueryZettelData(context.Background(), string(api.ZidDefaultHome)+" "+api.UnlinkedDirective) if err != nil { |
︙ | ︙ | |||
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 | exp := api.ZettelID("00000000060010") if err != nil { t.Error(err) } else if zid != exp { t.Errorf("role zettel for zettel should be %q, but got %q", exp, zid) } } func TestVersion(t *testing.T) { t.Parallel() c := getClient() ver, err := c.GetVersionInfo(context.Background()) if err != nil { t.Error(err) return } if ver.Major != -1 || ver.Minor != -1 || ver.Patch != -1 || ver.Info != kernel.CoreDefaultVersion || ver.Hash != "" { t.Error(ver) } } var baseURL string func init() { flag.StringVar(&baseURL, "base-url", "", "Base URL") } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | exp := api.ZettelID("00000000060010") if err != nil { t.Error(err) } else if zid != exp { t.Errorf("role zettel for zettel should be %q, but got %q", exp, zid) } } func TestRedirect(t *testing.T) { t.Parallel() c := getClient() search := api.OrderDirective + " " + api.ReverseDirective + " " + api.KeyID + api.ActionSeparator + api.RedirectAction ub := c.NewURLBuilder('z').AppendQuery(search) respRedirect, err := http.Get(ub.String()) if err != nil { t.Error(err) return } defer respRedirect.Body.Close() bodyRedirect, err := io.ReadAll(respRedirect.Body) if err != nil { t.Error(err) return } ub.ClearQuery().SetZid(api.ZidEmoji) respEmoji, err := http.Get(ub.String()) if err != nil { t.Error(err) return } defer respEmoji.Body.Close() bodyEmoji, err := io.ReadAll(respEmoji.Body) if err != nil { t.Error(err) return } if !slices.Equal(bodyRedirect, bodyEmoji) { t.Error("Wrong redirect") t.Error("REDIRECT", respRedirect) t.Error("EXPECTED", respEmoji) } } func TestVersion(t *testing.T) { t.Parallel() c := getClient() ver, err := c.GetVersionInfo(context.Background()) if err != nil { t.Error(err) return } if ver.Major != -1 || ver.Minor != -1 || ver.Patch != -1 || ver.Info != kernel.CoreDefaultVersion || ver.Hash != "" { t.Error(ver) } } func TestApplicationZid(t *testing.T) { c := getClient() c.SetAuth("reader", "reader") zid, err := c.GetApplicationZid(context.Background(), "app") if err != nil { t.Error(err) return } if zid != api.ZidAppDirectory { t.Errorf("c.GetApplicationZid(\"app\") should result in %q, but got: %q", api.ZidAppDirectory, zid) } if zid, err = c.GetApplicationZid(context.Background(), "noappzid"); err == nil { t.Errorf(`c.GetApplicationZid("nozid") should result in error, but got: %v`, zid) } if zid, err = c.GetApplicationZid(context.Background(), "nozid"); err == nil { t.Errorf(`c.GetApplicationZid("nozid") should result in error, but got: %v`, zid) } } var baseURL string func init() { flag.StringVar(&baseURL, "base-url", "", "Base URL") } |
︙ | ︙ |
Changes to tests/client/crud_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // Copyright (c) 2021-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore client 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 client_test import ( "context" "strings" "testing" | > > > | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2021-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore client is licensed under the latest version of the EUPL // (European Union Public License). Please see file LICENSE.txt for your rights // and obligations under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package client_test import ( "context" "strings" "testing" "t73f.de/r/zsc/api" "t73f.de/r/zsc/client" ) // --------------------------------------------------------------------------- // Tests that change the Zettelstore must nor run parallel to other tests. func TestCreateGetDeleteZettel(t *testing.T) { // Is not to be allowed to run in parallel with other tests. zettel := `title: A Test Example content.` c := getClient() c.SetAuth("owner", "owner") zid, err := c.CreateZettel(context.Background(), []byte(zettel)) |
︙ | ︙ | |||
45 46 47 48 49 50 51 | } exp := `title: A Test Example content.` if string(data) != exp { t.Errorf("Expected zettel data: %q, but got %q", exp, data) } | < < < < < | < | | < < < < < < | < | | 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 | } exp := `title: A Test Example content.` if string(data) != exp { t.Errorf("Expected zettel data: %q, but got %q", exp, data) } doDelete(t, c, zid) } func TestCreateGetDeleteZettelDataCreator(t *testing.T) { // Is not to be allowed to run in parallel with other tests. c := getClient() c.SetAuth("creator", "creator") zid, err := c.CreateZettelData(context.Background(), api.ZettelData{ Meta: nil, Encoding: "", Content: "Example", }) if err != nil { t.Error("Cannot create zettel:", err) return } if !zid.IsValid() { t.Error("Invalid zettel ID", zid) return } c.SetAuth("owner", "owner") doDelete(t, c, zid) } func TestCreateGetDeleteZettelData(t *testing.T) { // Is not to be allowed to run in parallel with other tests. c := getClient() c.SetAuth("owner", "owner") wrongModified := "19691231115959" |
︙ | ︙ |
Changes to tests/client/embed_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 client_test import ( "context" "strings" "testing" | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package client_test import ( "context" "strings" "testing" "t73f.de/r/zsc/api" ) const ( abcZid = api.ZettelID("20211020121000") abc10Zid = api.ZettelID("20211020121100") ) |
︙ | ︙ |
Changes to tests/markdown_test.go.
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 tests import ( "bytes" "encoding/json" "fmt" "os" "strings" "testing" | > > > | > < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package tests import ( "bytes" "encoding/json" "fmt" "os" "strings" "testing" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/encoder" _ "zettelstore.de/z/encoder/htmlenc" _ "zettelstore.de/z/encoder/mdenc" _ "zettelstore.de/z/encoder/shtmlenc" _ "zettelstore.de/z/encoder/szenc" _ "zettelstore.de/z/encoder/textenc" _ "zettelstore.de/z/encoder/zmkenc" "zettelstore.de/z/parser" _ "zettelstore.de/z/parser/markdown" _ "zettelstore.de/z/parser/zettelmark" "zettelstore.de/z/zettel/meta" ) type markdownTestCase struct { |
︙ | ︙ |
Changes to tests/naughtystrings_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 tests import ( "bufio" "io" "os" "path/filepath" "testing" | > > > | > < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package tests import ( "bufio" "io" "os" "path/filepath" "testing" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" _ "zettelstore.de/z/cmd" "zettelstore.de/z/encoder" "zettelstore.de/z/parser" "zettelstore.de/z/zettel/meta" ) // Test all parser / encoder with a list of "naughty strings", i.e. unusual strings // that often crash software. |
︙ | ︙ |
Changes to tests/regression_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 | //----------------------------------------------------------------------------- // 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 tests provides some higher-level tests. package tests import ( "context" "fmt" "io" "net/url" "os" "path/filepath" "strings" "testing" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package tests provides some higher-level tests. package tests import ( "context" "fmt" "io" "net/url" "os" "path/filepath" "strings" "testing" "t73f.de/r/zsc/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/config" "zettelstore.de/z/encoder" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" |
︙ | ︙ |
Deleted tools/build.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added tools/build/build.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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package main provides a command to build and run the software. package main import ( "archive/zip" "bytes" "flag" "fmt" "io" "io/fs" "os" "path/filepath" "strings" "time" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "zettelstore.de/z/strfun" "zettelstore.de/z/tools" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func readVersionFile() (string, error) { content, err := os.ReadFile("VERSION") if err != nil { return "", err } return strings.TrimFunc(string(content), func(r rune) bool { return r <= ' ' }), nil } func getVersion() string { base, err := readVersionFile() if err != nil { base = "dev" } return base } var dirtyPrefixes = []string{ "DELETED ", "ADDED ", "UPDATED ", "CONFLICT ", "EDITED ", "RENAMED ", "EXTRA "} const dirtySuffix = "-dirty" func readFossilDirty() (string, error) { s, err := tools.ExecuteCommand(nil, "fossil", "status", "--differ") if err != nil { return "", err } for _, line := range strfun.SplitLines(s) { for _, prefix := range dirtyPrefixes { if strings.HasPrefix(line, prefix) { return dirtySuffix, nil } } } return "", nil } func getFossilDirty() string { fossil, err := readFossilDirty() if err != nil { return "" } return fossil } func cmdBuild() error { return doBuild(tools.EnvDirectProxy, getVersion(), "bin/zettelstore") } func doBuild(env []string, version, target string) error { env = append(env, "CGO_ENABLED=0") env = append(env, tools.EnvGoVCS...) out, err := tools.ExecuteCommand( env, "go", "build", "-tags", "osusergo,netgo", "-trimpath", "-ldflags", fmt.Sprintf("-X main.version=%v -w", version), "-o", target, "zettelstore.de/z/cmd/zettelstore", ) if err != nil { return err } if len(out) > 0 { fmt.Println(out) } return nil } func cmdHelp() { fmt.Println(`Usage: go run tools/build/build.go [-v] COMMAND Options: -v Verbose output. Commands: build Build the software for local computer. help Output this text. manual Create a ZIP file with all manual zettel release Create the software for various platforms and put them in appropriate named ZIP files. version Print the current version of the software. All commands can be abbreviated as long as they remain unique.`) } func main() { flag.BoolVar(&tools.Verbose, "v", false, "Verbose output") flag.Parse() var err error args := flag.Args() if len(args) < 1 { cmdHelp() } else { switch args[0] { case "b", "bu", "bui", "buil", "build": err = cmdBuild() case "m", "ma", "man", "manu", "manua", "manual": err = cmdManual() case "r", "re", "rel", "rele", "relea", "releas", "release": err = cmdRelease() case "v", "ve", "ver", "vers", "versi", "versio", "version": fmt.Print(getVersion()) case "h", "he", "hel", "help": cmdHelp() default: fmt.Fprintf(os.Stderr, "Unknown command %q\n", args[0]) cmdHelp() os.Exit(1) } } if err != nil { fmt.Fprintln(os.Stderr, err) } } // --- manual func cmdManual() error { base := getReleaseVersionData() return createManualZip(".", base) } func createManualZip(path, base string) error { manualPath := filepath.Join("docs", "manual") entries, err := os.ReadDir(manualPath) if err != nil { return err } zipName := filepath.Join(path, "manual-"+base+".zip") zipFile, err := os.OpenFile(zipName, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return err } defer zipFile.Close() zipWriter := zip.NewWriter(zipFile) defer zipWriter.Close() for _, entry := range entries { if err = createManualZipEntry(manualPath, entry, zipWriter); err != nil { return err } } return nil } const versionZid = "00001000000001" func createManualZipEntry(path string, entry fs.DirEntry, zipWriter *zip.Writer) error { info, err := entry.Info() if err != nil { return err } fh, err := zip.FileInfoHeader(info) if err != nil { return err } name := entry.Name() fh.Name = name fh.Method = zip.Deflate w, err := zipWriter.CreateHeader(fh) if err != nil { return err } manualFile, err := os.Open(filepath.Join(path, name)) if err != nil { return err } defer manualFile.Close() if name != versionZid+".zettel" { _, err = io.Copy(w, manualFile) return err } data, err := io.ReadAll(manualFile) if err != nil { return err } inp := input.NewInput(data) m := meta.NewFromInput(id.MustParse(versionZid), inp) m.SetNow(api.KeyModified) var buf bytes.Buffer if _, err = fmt.Fprintf(&buf, "id: %s\n", versionZid); err != nil { return err } if _, err = m.WriteComputed(&buf); err != nil { return err } version := getVersion() if _, err = fmt.Fprintf(&buf, "\n%s", version); err != nil { return err } _, err = io.Copy(w, &buf) return err } //--- release func cmdRelease() error { if err := tools.Check(true); err != nil { return err } base := getReleaseVersionData() releases := []struct { arch string os string env []string name string }{ {"amd64", "linux", nil, "zettelstore"}, {"arm", "linux", []string{"GOARM=6"}, "zettelstore"}, {"arm64", "darwin", nil, "zettelstore"}, {"amd64", "windows", nil, "zettelstore.exe"}, {"arm64", "android", nil, "zettelstore"}, } for _, rel := range releases { env := append([]string{}, rel.env...) env = append(env, "GOARCH="+rel.arch, "GOOS="+rel.os) env = append(env, tools.EnvDirectProxy...) env = append(env, tools.EnvGoVCS...) zsName := filepath.Join("releases", rel.name) if err := doBuild(env, base, zsName); err != nil { return err } zipName := fmt.Sprintf("zettelstore-%v-%v-%v.zip", base, rel.os, rel.arch) if err := createReleaseZip(zsName, zipName, rel.name); err != nil { return err } if err := os.Remove(zsName); err != nil { return err } } return createManualZip("releases", base) } func getReleaseVersionData() string { if fossil := getFossilDirty(); fossil != "" { fmt.Fprintln(os.Stderr, "Warning: releasing a dirty version") } base := getVersion() if strings.HasSuffix(base, "dev") { return base[:len(base)-3] + "preview-" + time.Now().Local().Format("20060102") } return base } func createReleaseZip(zsName, zipName, fileName string) error { zipFile, err := os.OpenFile(filepath.Join("releases", zipName), os.O_RDWR|os.O_CREATE, 0600) if err != nil { return err } defer zipFile.Close() zw := zip.NewWriter(zipFile) defer zw.Close() err = addFileToZip(zw, zsName, fileName) if err != nil { return err } err = addFileToZip(zw, "LICENSE.txt", "LICENSE.txt") if err != nil { return err } err = addFileToZip(zw, "docs/readmezip.txt", "README.txt") return err } func addFileToZip(zipFile *zip.Writer, filepath, filename string) error { zsFile, err := os.Open(filepath) if err != nil { return err } defer zsFile.Close() stat, err := zsFile.Stat() if err != nil { return err } fh, err := zip.FileInfoHeader(stat) if err != nil { return err } fh.Name = filename fh.Method = zip.Deflate w, err := zipFile.CreateHeader(fh) if err != nil { return err } _, err = io.Copy(w, zsFile) return err } |
Added tools/check/check.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 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- // Package main provides a command to execute unit tests. package main import ( "flag" "fmt" "os" "zettelstore.de/z/tools" ) var release bool func main() { flag.BoolVar(&release, "r", false, "Release check") flag.BoolVar(&tools.Verbose, "v", false, "Verbose output") flag.Parse() if err := tools.Check(release); err != nil { fmt.Fprintln(os.Stderr, err) } } |
Added tools/clean/clean.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 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- // Package main provides a command to clean / remove development artifacts. package main import ( "flag" "fmt" "os" "zettelstore.de/z/tools" ) func main() { flag.BoolVar(&tools.Verbose, "v", false, "Verbose output") flag.Parse() if err := cmdClean(); err != nil { fmt.Fprintln(os.Stderr, err) } } func cmdClean() error { for _, dir := range []string{"bin", "releases"} { err := os.RemoveAll(dir) if err != nil { return err } } out, err := tools.ExecuteCommand(nil, "go", "clean", "./...") if err != nil { return err } if len(out) > 0 { fmt.Println(out) } out, err = tools.ExecuteCommand(nil, "go", "clean", "-cache", "-modcache", "-testcache") if err != nil { return err } if len(out) > 0 { fmt.Println(out) } return nil } |
Added tools/devtools/devtools.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 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- // Package main provides a command to install development tools. package main import ( "flag" "fmt" "os" "zettelstore.de/z/tools" ) func main() { flag.BoolVar(&tools.Verbose, "v", false, "Verbose output") flag.Parse() if err := cmdTools(); err != nil { fmt.Fprintln(os.Stderr, err) } } func cmdTools() error { tools := []struct{ name, pack string }{ {"shadow", "golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest"}, {"unparam", "mvdan.cc/unparam@latest"}, {"staticcheck", "honnef.co/go/tools/cmd/staticcheck@latest"}, {"govulncheck", "golang.org/x/vuln/cmd/govulncheck@latest"}, {"deadcode", "golang.org/x/tools/cmd/deadcode@latest"}, {"errcheck", "github.com/kisielk/errcheck@latest"}, } for _, tool := range tools { err := doGoInstall(tool.pack) if err != nil { return err } } return nil } func doGoInstall(pack string) error { out, err := tools.ExecuteCommand(nil, "go", "install", pack) if err != nil { fmt.Fprintln(os.Stderr, "Unable to install package", pack) if len(out) > 0 { fmt.Fprintln(os.Stderr, out) } } return err } |
Added tools/htmllint/htmllint.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 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package main import ( "context" "flag" "fmt" "log" "math/rand/v2" "net/url" "os" "regexp" "slices" "strings" "t73f.de/r/zsc/api" "t73f.de/r/zsc/client" "zettelstore.de/z/tools" ) func main() { flag.BoolVar(&tools.Verbose, "v", false, "Verbose output") flag.Parse() if err := cmdValidateHTML(flag.Args()); err != nil { fmt.Fprintln(os.Stderr, err) } } func cmdValidateHTML(args []string) error { rawURL := "http://localhost:23123" if len(args) > 0 { rawURL = args[0] } u, err := url.Parse(rawURL) if err != nil { return err } client := client.NewClient(u) _, _, metaList, err := client.QueryZettelData(context.Background(), "") if err != nil { return err } zids, perm := calculateZids(metaList) for _, kd := range keyDescr { msgCount := 0 fmt.Fprintf(os.Stderr, "Now checking: %s\n", kd.text) for _, zid := range zidsToUse(zids, perm, kd.sampleSize) { var nmsgs int nmsgs, err = validateHTML(client, kd.uc, api.ZettelID(zid)) if err != nil { fmt.Fprintf(os.Stderr, "* error while validating zettel %v with: %v\n", zid, err) msgCount += 1 } else { msgCount += nmsgs } } if msgCount == 1 { fmt.Fprintln(os.Stderr, "==> found 1 possible issue") } else if msgCount > 1 { fmt.Fprintf(os.Stderr, "==> found %v possible issues\n", msgCount) } } return nil } func calculateZids(metaList []api.ZidMetaRights) ([]string, []int) { zids := make([]string, len(metaList)) for i, m := range metaList { zids[i] = string(m.ID) } slices.Sort(zids) return zids, rand.Perm(len(metaList)) } func zidsToUse(zids []string, perm []int, sampleSize int) []string { if sampleSize < 0 || len(perm) <= sampleSize { return zids } if sampleSize == 0 { return nil } result := make([]string, sampleSize) for i := range sampleSize { result[i] = zids[perm[i]] } slices.Sort(result) return result } var keyDescr = []struct { uc urlCreator text string sampleSize int }{ {getHTMLZettel, "zettel HTML encoding", -1}, {createJustKey('h'), "zettel web view", -1}, {createJustKey('i'), "zettel info view", -1}, {createJustKey('e'), "zettel edit form", 100}, {createJustKey('c'), "zettel create form", 10}, {createJustKey('d'), "zettel delete dialog", 200}, } type urlCreator func(*client.Client, api.ZettelID) *api.URLBuilder func createJustKey(key byte) urlCreator { return func(c *client.Client, zid api.ZettelID) *api.URLBuilder { return c.NewURLBuilder(key).SetZid(zid) } } func getHTMLZettel(client *client.Client, zid api.ZettelID) *api.URLBuilder { return client.NewURLBuilder('z').SetZid(zid). AppendKVQuery(api.QueryKeyEncoding, api.EncodingHTML). AppendKVQuery(api.QueryKeyPart, api.PartZettel) } func validateHTML(client *client.Client, uc urlCreator, zid api.ZettelID) (int, error) { ub := uc(client, zid) if tools.Verbose { fmt.Fprintf(os.Stderr, "GET %v\n", ub) } data, err := client.Get(context.Background(), ub) if err != nil { return 0, err } if len(data) == 0 { return 0, nil } _, stderr, err := tools.ExecuteFilter(data, nil, "tidy", "-e", "-q", "-lang", "en") if err != nil { switch err.Error() { case "exit status 1": case "exit status 2": default: log.Println("SERR", stderr) return 0, err } } if stderr == "" { return 0, nil } if msgs := filterTidyMessages(strings.Split(stderr, "\n")); len(msgs) > 0 { fmt.Fprintln(os.Stderr, zid) for _, msg := range msgs { fmt.Fprintln(os.Stderr, "-", msg) } return len(msgs), nil } return 0, nil } var reLine = regexp.MustCompile(`line \d+ column \d+ - (.+): (.+)`) func filterTidyMessages(lines []string) []string { result := make([]string, 0, len(lines)) for _, line := range lines { line = strings.TrimSpace(line) if line == "" { continue } matches := reLine.FindStringSubmatch(line) if len(matches) <= 1 { if line == "This document has errors that must be fixed before" || line == "using HTML Tidy to generate a tidied up version." { continue } result = append(result, "!!!"+line) continue } if matches[1] == "Error" { if len(matches) > 2 { if matches[2] == "<search> is not recognized!" { continue } } } if matches[1] != "Warning" { result = append(result, "???"+line) continue } if len(matches) > 2 { switch matches[2] { case "discarding unexpected <search>", "discarding unexpected </search>", `<input> proprietary attribute "inputmode"`: continue } } result = append(result, line) } return result } |
Added tools/testapi/testapi.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 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- // Package main provides a command to test the API package main import ( "errors" "flag" "fmt" "io" "net" "os" "os/exec" "strings" "time" "zettelstore.de/z/tools" ) func main() { flag.BoolVar(&tools.Verbose, "v", false, "Verbose output") flag.Parse() if err := cmdTestAPI(); err != nil { fmt.Fprintln(os.Stderr, err) } } type zsInfo struct { cmd *exec.Cmd out strings.Builder adminAddress string } func cmdTestAPI() error { var err error var info zsInfo needServer := !addressInUse(":23123") if needServer { err = startZettelstore(&info) } if err != nil { return err } err = tools.CheckGoTest("zettelstore.de/z/tests/client", "-base-url", "http://127.0.0.1:23123") if needServer { err1 := stopZettelstore(&info) if err == nil { err = err1 } } return err } func startZettelstore(info *zsInfo) error { info.adminAddress = ":2323" name, arg := "go", []string{ "run", "cmd/zettelstore/main.go", "run", "-c", "./testdata/testbox/19700101000000.zettel", "-a", info.adminAddress[1:]} tools.LogCommand("FORK", nil, name, arg) cmd := tools.PrepareCommand(tools.EnvGoVCS, name, arg, nil, &info.out, os.Stderr) if !tools.Verbose { cmd.Stderr = nil } err := cmd.Start() time.Sleep(2 * time.Second) for range 100 { time.Sleep(time.Millisecond * 100) if addressInUse(info.adminAddress) { info.cmd = cmd return err } } time.Sleep(4 * time.Second) // Wait for all zettel to be indexed. return errors.New("zettelstore did not start") } func stopZettelstore(i *zsInfo) error { conn, err := net.Dial("tcp", i.adminAddress) if err != nil { fmt.Println("Unable to stop Zettelstore") return err } io.WriteString(conn, "shutdown\n") conn.Close() err = i.cmd.Wait() return err } func addressInUse(address string) bool { conn, err := net.Dial("tcp", address) if err != nil { return false } conn.Close() return true } |
Added tools/tools.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 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- // Package tools provides a collection of functions to build needed tools. package tools import ( "bytes" "errors" "fmt" "io" "os" "os/exec" "strings" "zettelstore.de/z/strfun" ) var EnvDirectProxy = []string{"GOPROXY=direct"} var EnvGoVCS = []string{"GOVCS=zettelstore.de:fossil,t73f.de:fossil"} var Verbose bool func ExecuteCommand(env []string, name string, arg ...string) (string, error) { LogCommand("EXEC", env, name, arg) var out strings.Builder cmd := PrepareCommand(env, name, arg, nil, &out, os.Stderr) err := cmd.Run() return out.String(), err } func ExecuteFilter(data []byte, env []string, name string, arg ...string) (string, string, error) { LogCommand("EXEC", env, name, arg) var stdout, stderr strings.Builder cmd := PrepareCommand(env, name, arg, bytes.NewReader(data), &stdout, &stderr) err := cmd.Run() return stdout.String(), stderr.String(), err } func PrepareCommand(env []string, name string, arg []string, in io.Reader, stdout, stderr io.Writer) *exec.Cmd { if len(env) > 0 { env = append(env, os.Environ()...) } cmd := exec.Command(name, arg...) cmd.Env = env cmd.Stdin = in cmd.Stdout = stdout cmd.Stderr = stderr return cmd } func LogCommand(exec string, env []string, name string, arg []string) { if Verbose { if len(env) > 0 { for i, e := range env { fmt.Fprintf(os.Stderr, "ENV%d %v\n", i+1, e) } } fmt.Fprintln(os.Stderr, exec, name, arg) } } func Check(forRelease bool) error { if err := CheckGoTest("./..."); err != nil { return err } if err := checkGoVet(); err != nil { return err } if err := checkShadow(forRelease); err != nil { return err } if err := checkStaticcheck(); err != nil { return err } if err := checkUnparam(forRelease); err != nil { return err } if forRelease { if err := checkGoVulncheck(); err != nil { return err } } return checkFossilExtra() } func CheckGoTest(pkg string, testParams ...string) error { var env []string env = append(env, EnvDirectProxy...) env = append(env, EnvGoVCS...) args := []string{"test", pkg} args = append(args, testParams...) out, err := ExecuteCommand(env, "go", args...) if err != nil { for _, line := range strfun.SplitLines(out) { if strings.HasPrefix(line, "ok") || strings.HasPrefix(line, "?") { continue } fmt.Fprintln(os.Stderr, line) } } return err } func checkGoVet() error { out, err := ExecuteCommand(EnvGoVCS, "go", "vet", "./...") if err != nil { fmt.Fprintln(os.Stderr, "Some checks failed") if len(out) > 0 { fmt.Fprintln(os.Stderr, out) } } return err } func checkShadow(forRelease bool) error { path, err := findExecStrict("shadow", forRelease) if path == "" { return err } out, err := ExecuteCommand(EnvGoVCS, path, "-strict", "./...") if err != nil { fmt.Fprintln(os.Stderr, "Some shadowed variables found") if len(out) > 0 { fmt.Fprintln(os.Stderr, out) } } return err } func checkStaticcheck() error { out, err := ExecuteCommand(EnvGoVCS, "staticcheck", "./...") if err != nil { fmt.Fprintln(os.Stderr, "Some staticcheck problems found") if len(out) > 0 { fmt.Fprintln(os.Stderr, out) } } return err } func checkUnparam(forRelease bool) error { path, err := findExecStrict("unparam", forRelease) if path == "" { return err } out, err := ExecuteCommand(EnvGoVCS, path, "./...") if err != nil { fmt.Fprintln(os.Stderr, "Some unparam problems found") if len(out) > 0 { fmt.Fprintln(os.Stderr, out) } } if forRelease { if out2, err2 := ExecuteCommand(nil, path, "-exported", "-tests", "./..."); err2 != nil { fmt.Fprintln(os.Stderr, "Some optional unparam problems found") if len(out2) > 0 { fmt.Fprintln(os.Stderr, out2) } } } return err } func checkGoVulncheck() error { out, err := ExecuteCommand(EnvGoVCS, "govulncheck", "./...") if err != nil { fmt.Fprintln(os.Stderr, "Some checks failed") if len(out) > 0 { fmt.Fprintln(os.Stderr, out) } } return err } func findExec(cmd string) string { if path, err := ExecuteCommand(nil, "which", cmd); err == nil && path != "" { return strings.TrimSpace(path) } return "" } func findExecStrict(cmd string, forRelease bool) (string, error) { path := findExec(cmd) if path != "" || !forRelease { return path, nil } return "", errors.New("Command '" + cmd + "' not installed, but required for release") } func checkFossilExtra() error { out, err := ExecuteCommand(nil, "fossil", "extra") if err != nil { fmt.Fprintln(os.Stderr, "Unable to execute 'fossil extra'") return err } if len(out) > 0 { fmt.Fprint(os.Stderr, "Warning: unversioned file(s):") for i, extra := range strfun.SplitLines(out) { if i > 0 { fmt.Fprint(os.Stderr, ",") } fmt.Fprintf(os.Stderr, " %q", extra) } fmt.Fprintln(os.Stderr) } return nil } |
Changes to usecase/authenticate.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" | > > > | | | 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" "math/rand/v2" "net/http" "time" "t73f.de/r/zsc/api" "zettelstore.de/z/auth" "zettelstore.de/z/auth/cred" "zettelstore.de/z/logger" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ | |||
83 84 85 86 87 88 89 | "$2a$10$WHcSO3G9afJ3zlOYQR1suuf83bCXED2jmzjti/MH4YH4l2mivDuze", id.Invalid, "", "") } // addDelay after credential checking to allow some CPU time for other tasks. // durDelay is the normal delay, if time spend for checking is smaller than // the minimum delay minDelay. In addition some jitter (+/- 50 ms) is added. func addDelay(start time.Time, durDelay, minDelay time.Duration) { | | | 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | "$2a$10$WHcSO3G9afJ3zlOYQR1suuf83bCXED2jmzjti/MH4YH4l2mivDuze", id.Invalid, "", "") } // addDelay after credential checking to allow some CPU time for other tasks. // durDelay is the normal delay, if time spend for checking is smaller than // the minimum delay minDelay. In addition some jitter (+/- 50 ms) is added. func addDelay(start time.Time, durDelay, minDelay time.Duration) { jitter := time.Duration(rand.IntN(100)-50) * time.Millisecond if elapsed := time.Since(start); elapsed+minDelay < durDelay { time.Sleep(durDelay - elapsed + jitter) } else { time.Sleep(minDelay + jitter) } } |
︙ | ︙ | |||
126 127 128 129 130 131 132 | IsAuthenticatedAndValid IsAuthenticatedAndInvalid ) // Run executes the use case. func (uc *IsAuthenticated) Run(ctx context.Context) IsAuthenticatedResult { if !uc.authz.WithAuth() { | | | | | 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | IsAuthenticatedAndValid IsAuthenticatedAndInvalid ) // Run executes the use case. func (uc *IsAuthenticated) Run(ctx context.Context) IsAuthenticatedResult { if !uc.authz.WithAuth() { uc.log.Info().Str("auth", "disabled").Msg("IsAuthenticated") return IsAuthenticatedDisabled } if uc.port.GetUser(ctx) == nil { uc.log.Info().Msg("IsAuthenticated is false") return IsAuthenticatedAndInvalid } uc.log.Info().Msg("IsAuthenticated is true") return IsAuthenticatedAndValid } |
Changes to usecase/create_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" "time" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" "time" "t73f.de/r/zsc/api" "zettelstore.de/z/config" "zettelstore.de/z/logger" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ | |||
112 113 114 115 116 117 118 | content.TrimSpace() return zettel.Zettel{Meta: m, Content: content} } func updateMetaRoleTagsSyntax(m, orig *meta.Meta) { m.SetNonEmpty(api.KeyRole, orig.GetDefault(api.KeyRole, "")) m.SetNonEmpty(api.KeyTags, orig.GetDefault(api.KeyTags, "")) | | | 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | content.TrimSpace() return zettel.Zettel{Meta: m, Content: content} } func updateMetaRoleTagsSyntax(m, orig *meta.Meta) { m.SetNonEmpty(api.KeyRole, orig.GetDefault(api.KeyRole, "")) m.SetNonEmpty(api.KeyTags, orig.GetDefault(api.KeyTags, "")) m.SetNonEmpty(api.KeySyntax, orig.GetDefault(api.KeySyntax, meta.DefaultSyntax)) } func prependTitle(title, s0, s1 string) string { if len(title) > 0 { return s1 + title } return s0 |
︙ | ︙ |
Changes to usecase/delete_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" |
︙ | ︙ |
Changes to usecase/evaluate.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" |
︙ | ︙ |
Changes to usecase/get_all_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" |
︙ | ︙ |
Changes to usecase/get_special_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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 usecase import ( "context" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" "t73f.de/r/zsc/api" "zettelstore.de/z/query" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // TagZettel is the usecase of retrieving a "tag zettel", i.e. a zettel that |
︙ | ︙ |
Changes to usecase/get_user.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" "t73f.de/r/zsc/api" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/query" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ |
Changes to usecase/get_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" |
︙ | ︙ |
Changes to usecase/lists.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" "t73f.de/r/zsc/api" "zettelstore.de/z/box" "zettelstore.de/z/parser" "zettelstore.de/z/query" "zettelstore.de/z/zettel/meta" ) // -------- List syntax ------------------------------------------------------ |
︙ | ︙ |
Changes to usecase/parse_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" |
︙ | ︙ |
Changes to usecase/query.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" "errors" "fmt" "strings" | > > > | | 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" "errors" "fmt" "strings" "t73f.de/r/zsc/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/collect" "zettelstore.de/z/parser" "zettelstore.de/z/query" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel" |
︙ | ︙ | |||
113 114 115 116 117 118 119 | func (uc *Query) processContextDirective(ctx context.Context, spec *query.ContextSpec, metaSeq []*meta.Meta) []*meta.Meta { return spec.Execute(ctx, metaSeq, uc.port) } func (uc *Query) processItemsDirective(ctx context.Context, _ *query.ItemsSpec, metaSeq []*meta.Meta) []*meta.Meta { result := make([]*meta.Meta, 0, len(metaSeq)) for _, m := range metaSeq { | | | 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | func (uc *Query) processContextDirective(ctx context.Context, spec *query.ContextSpec, metaSeq []*meta.Meta) []*meta.Meta { return spec.Execute(ctx, metaSeq, uc.port) } func (uc *Query) processItemsDirective(ctx context.Context, _ *query.ItemsSpec, metaSeq []*meta.Meta) []*meta.Meta { result := make([]*meta.Meta, 0, len(metaSeq)) for _, m := range metaSeq { zn, err := uc.ucEvaluate.Run(ctx, m.Zid, m.GetDefault(api.KeySyntax, meta.DefaultSyntax)) if err != nil { continue } for _, ref := range collect.Order(zn) { if collectedZid, err2 := id.Parse(ref.URL.Path); err2 == nil { if z, err3 := uc.port.GetZettel(ctx, collectedZid); err3 == nil { result = append(result, z.Meta) |
︙ | ︙ | |||
167 168 169 170 171 172 173 | } } } candidates = filterByZid(candidates, refZids) return uc.filterCandidates(ctx, candidates, words) } | | | | 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | } } } candidates = filterByZid(candidates, refZids) return uc.filterCandidates(ctx, candidates, words) } func filterByZid(candidates []*meta.Meta, ignoreSeq *id.Set) []*meta.Meta { result := make([]*meta.Meta, 0, len(candidates)) for _, m := range candidates { if !ignoreSeq.Contains(m.Zid) { result = append(result, m) } } return result } func (uc *Query) filterCandidates(ctx context.Context, candidates []*meta.Meta, words []string) []*meta.Meta { |
︙ | ︙ | |||
203 204 205 206 207 208 209 | ast.Walk(&v, &is) if v.found { result = append(result, cand) continue candLoop } } | | | 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | ast.Walk(&v, &is) if v.found { result = append(result, cand) continue candLoop } } syntax := zettel.Meta.GetDefault(api.KeySyntax, meta.DefaultSyntax) if !parser.IsASTParser(syntax) { continue } zn := uc.ucEvaluate.RunZettel(ctx, zettel, syntax) ast.Walk(&v, &zn.Ast) if v.found { result = append(result, cand) |
︙ | ︙ | |||
257 258 259 260 261 262 263 | func (v *unlinkedVisitor) splitInlineTextList(is *ast.InlineSlice) []string { var result []string var curList []string for _, in := range *is { switch n := in.(type) { case *ast.TextNode: curList = append(curList, strfun.MakeWords(n.Text)...) | < | 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 | func (v *unlinkedVisitor) splitInlineTextList(is *ast.InlineSlice) []string { var result []string var curList []string for _, in := range *is { switch n := in.(type) { case *ast.TextNode: curList = append(curList, strfun.MakeWords(n.Text)...) default: if curList != nil { result = append(result, v.joinWords(curList)) curList = nil } } } if curList != nil { result = append(result, v.joinWords(curList)) } return result } |
Changes to usecase/refresh.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" |
︙ | ︙ |
Changes to usecase/reindex.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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 usecase import ( "context" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" |
︙ | ︙ | |||
32 33 34 35 36 37 38 | func NewReIndex(log *logger.Logger, port ReIndexPort) ReIndex { return ReIndex{log: log, port: port} } // Run executes the use case. func (uc *ReIndex) Run(ctx context.Context, zid id.Zid) error { err := uc.port.ReIndex(ctx, zid) | | | 35 36 37 38 39 40 41 42 43 44 | func NewReIndex(log *logger.Logger, port ReIndexPort) ReIndex { return ReIndex{log: log, port: port} } // Run executes the use case. func (uc *ReIndex) Run(ctx context.Context, zid id.Zid) error { err := uc.port.ReIndex(ctx, zid) uc.log.Info().User(ctx).Err(err).Zid(zid).Msg("ReIndex zettel") return err } |
Deleted usecase/rename_zettel.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to usecase/update_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 usecase import ( "context" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "context" "t73f.de/r/zsc/api" "zettelstore.de/z/box" "zettelstore.de/z/logger" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ | |||
66 67 68 69 70 71 72 | } if !hasContent { zettel.Content = oldZettel.Content } zettel.Content.TrimSpace() err = uc.port.UpdateZettel(ctx, zettel) | | | 69 70 71 72 73 74 75 76 77 78 | } if !hasContent { zettel.Content = oldZettel.Content } zettel.Content.TrimSpace() err = uc.port.UpdateZettel(ctx, zettel) uc.log.Info().User(ctx).Zid(m.Zid).Err(err).Msg("Update zettel") return err } |
Changes to usecase/usecase.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 usecase provides (business) use cases for the zettelstore. package usecase | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package usecase provides (business) use cases for the zettelstore. package usecase |
Changes to usecase/version.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 usecase import ( "regexp" "strconv" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package usecase import ( "regexp" "strconv" |
︙ | ︙ |
Added web/adapter/adapter.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | //----------------------------------------------------------------------------- // Copyright (c) 2024-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2024-present Detlef Stern //----------------------------------------------------------------------------- // Package adapter provides handlers for web requests, and some helper tools. package adapter import ( "context" "t73f.de/r/zsc/api" "zettelstore.de/z/usecase" "zettelstore.de/z/zettel/meta" ) // TryReIndex executes a re-index if the appropriate query action is given. func TryReIndex(ctx context.Context, actions []string, metaSeq []*meta.Meta, reIndex *usecase.ReIndex) ([]string, error) { if lenActions := len(actions); lenActions > 0 { tempActions := make([]string, 0, lenActions) hasReIndex := false for _, act := range actions { if !hasReIndex && act == api.ReIndexAction { hasReIndex = true var errAction error for _, m := range metaSeq { if err := reIndex.Run(ctx, m.Zid); err != nil { errAction = err } } if errAction != nil { return nil, errAction } continue } tempActions = append(tempActions, act) } return tempActions, nil } return nil, nil } |
Changes to web/adapter/api/api.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 api provides api handlers for web requests. package api import ( "bytes" "context" "net/http" "time" | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package api provides api handlers for web requests. package api import ( "bytes" "context" "net/http" "time" "t73f.de/r/zsc/api" "zettelstore.de/z/auth" "zettelstore.de/z/config" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/meta" |
︙ | ︙ | |||
51 52 53 54 55 56 57 | policy: pol, tokenLifetime: kernel.Main.GetConfig(kernel.WebService, kernel.WebTokenLifetimeAPI).(time.Duration), } return a } | < < < | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | policy: pol, tokenLifetime: kernel.Main.GetConfig(kernel.WebService, kernel.WebTokenLifetimeAPI).(time.Duration), } return a } // NewURLBuilder creates a new URL builder object with the given key. func (a *API) NewURLBuilder(key byte) *api.URLBuilder { return a.b.NewURLBuilder(key) } func (a *API) getAuthData(ctx context.Context) *server.AuthData { return server.GetAuthData(ctx) } func (a *API) withAuth() bool { return a.authz.WithAuth() } |
︙ | ︙ | |||
92 93 94 95 96 97 98 | } if pol.CanRead(user, m) { result |= api.ZettelCanRead } if pol.CanWrite(user, m, m) { result |= api.ZettelCanWrite } | < < < | 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | } if pol.CanRead(user, m) { result |= api.ZettelCanRead } if pol.CanWrite(user, m, m) { result |= api.ZettelCanWrite } if pol.CanDelete(user, m) { result |= api.ZettelCanDelete } if result == 0 { return api.ZettelCanNone } return result } |
Changes to web/adapter/api/command.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 api import ( "context" "net/http" | > > > | | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package api import ( "context" "net/http" "t73f.de/r/zsc/api" "zettelstore.de/z/usecase" ) // MakePostCommandHandler creates a new HTTP handler to execute certain commands. func (a *API) MakePostCommandHandler( ucIsAuth *usecase.IsAuthenticated, ucRefresh *usecase.Refresh, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() switch api.Command(r.URL.Query().Get(api.QueryKeyCommand)) { case api.CommandAuthenticated: handleIsAuthenticated(ctx, w, ucIsAuth) return case api.CommandRefresh: err := ucRefresh.Run(ctx) if err != nil { a.reportUsecaseError(w, err) return } w.WriteHeader(http.StatusNoContent) return } http.Error(w, "Unknown command", http.StatusBadRequest) }) } func handleIsAuthenticated(ctx context.Context, w http.ResponseWriter, ucIsAuth *usecase.IsAuthenticated) { switch ucIsAuth.Run(ctx) { case usecase.IsAuthenticatedDisabled: w.WriteHeader(http.StatusOK) case usecase.IsAuthenticatedAndValid: |
︙ | ︙ |
Changes to web/adapter/api/create_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 api import ( "net/http" | > > > | | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package api import ( "net/http" "t73f.de/r/sx" "t73f.de/r/zsc/api" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/content" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" ) // MakePostCreateZettelHandler creates a new HTTP handler to store content of // an existing zettel. func (a *API) MakePostCreateZettelHandler(createZettel *usecase.CreateZettel) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() enc, encStr := getEncoding(r, q) var zettel zettel.Zettel var err error switch enc { case api.EncoderPlain: zettel, err = buildZettelFromPlainData(r, id.Invalid) |
︙ | ︙ | |||
49 50 51 52 53 54 55 | if err != nil { a.reportUsecaseError(w, err) return } var result []byte var contentType string | | | | | 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 | if err != nil { a.reportUsecaseError(w, err) return } var result []byte var contentType string location := a.NewURLBuilder('z').SetZid(newZid.ZettelID()) switch enc { case api.EncoderPlain: result = newZid.Bytes() contentType = content.PlainText case api.EncoderData: result = []byte(sx.Int64(newZid).String()) contentType = content.SXPF default: panic(encStr) } h := adapter.PrepareHeader(w, contentType) h.Set(api.HeaderLocation, location.String()) w.WriteHeader(http.StatusCreated) if _, err = w.Write(result); err != nil { a.log.Error().Err(err).Zid(newZid).Msg("Create Zettel") } }) } |
Changes to web/adapter/api/delete_zettel.go.
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 api import ( "net/http" "zettelstore.de/z/usecase" "zettelstore.de/z/zettel/id" ) // MakeDeleteZettelHandler creates a new HTTP handler to delete a 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package api import ( "net/http" "zettelstore.de/z/usecase" "zettelstore.de/z/zettel/id" ) // MakeDeleteZettelHandler creates a new HTTP handler to delete a zettel. func (a *API) MakeDeleteZettelHandler(deleteZettel *usecase.DeleteZettel) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } if err = deleteZettel.Run(r.Context(), zid); err != nil { a.reportUsecaseError(w, err) return } w.WriteHeader(http.StatusNoContent) }) } |
Changes to web/adapter/api/get_data.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 api import ( "net/http" | > > > | | | | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package api import ( "net/http" "t73f.de/r/sx" "zettelstore.de/z/usecase" "zettelstore.de/z/zettel/id" ) // MakeGetDataHandler creates a new HTTP handler to return zettelstore data. func (a *API) MakeGetDataHandler(ucVersion usecase.Version) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { version := ucVersion.Run() err := a.writeObject(w, id.Invalid, sx.MakeList( sx.Int64(version.Major), sx.Int64(version.Minor), sx.Int64(version.Patch), sx.MakeString(version.Info), sx.MakeString(version.Hash), )) if err != nil { a.log.Error().Err(err).Msg("Write Version Info") } }) } |
Changes to web/adapter/api/get_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 api import ( "bytes" "context" "fmt" "net/http" | > > > | | | > | > > > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package api import ( "bytes" "context" "fmt" "net/http" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/sexp" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/encoder" "zettelstore.de/z/parser" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/content" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeGetZettelHandler creates a new HTTP handler to return a zettel in various encodings. func (a *API) MakeGetZettelHandler( getZettel usecase.GetZettel, parseZettel usecase.ParseZettel, evaluate usecase.Evaluate, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } q := r.URL.Query() |
︙ | ︙ | |||
62 63 64 65 66 67 68 | } if err != nil { a.reportUsecaseError(w, err) return } a.writeEncodedZettelPart(ctx, w, zn, em, enc, encStr, part) } | | | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | } if err != nil { a.reportUsecaseError(w, err) return } a.writeEncodedZettelPart(ctx, w, zn, em, enc, encStr, part) } }) } func (a *API) writePlainData(w http.ResponseWriter, ctx context.Context, zid id.Zid, part partType, getZettel usecase.GetZettel) { var buf bytes.Buffer var contentType string var err error |
︙ | ︙ | |||
91 92 93 94 95 96 97 | } case partMeta: contentType = content.PlainText _, err = z.Meta.Write(&buf) case partContent: | | | | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | } case partMeta: contentType = content.PlainText _, err = z.Meta.Write(&buf) case partContent: contentType = content.MIMEFromSyntax(z.Meta.GetDefault(api.KeySyntax, meta.DefaultSyntax)) _, err = z.Content.Write(&buf) } if err != nil { a.log.Error().Err(err).Zid(zid).Msg("Unable to store plain zettel/part in buffer") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if err = writeBuffer(w, &buf, contentType); err != nil { a.log.Error().Err(err).Zid(zid).Msg("Write Plain data") } } |
︙ | ︙ | |||
159 160 161 162 163 164 165 | _, err = encdr.WriteZettel(&buf, zn, evalMeta) case partMeta: _, err = encdr.WriteMeta(&buf, zn.InhMeta, evalMeta) case partContent: _, err = encdr.WriteContent(&buf, zn) } if err != nil { | | | 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | _, err = encdr.WriteZettel(&buf, zn, evalMeta) case partMeta: _, err = encdr.WriteMeta(&buf, zn.InhMeta, evalMeta) case partContent: _, err = encdr.WriteContent(&buf, zn) } if err != nil { a.log.Error().Err(err).Zid(zn.Zid).Msg("Unable to store data in buffer") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if buf.Len() == 0 { w.WriteHeader(http.StatusNoContent) return } if err = writeBuffer(w, &buf, content.MIMEFromEncoding(enc)); err != nil { a.log.Error().Err(err).Zid(zn.Zid).Msg("Write Encoded Zettel") } } |
Changes to web/adapter/api/login.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 api import ( "net/http" "time" | > > > | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package api import ( "net/http" "time" "t73f.de/r/sx" "zettelstore.de/z/auth" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel/id" ) // MakePostLoginHandler creates a new HTTP handler to authenticate the given user via API. func (a *API) MakePostLoginHandler(ucAuth *usecase.Authenticate) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !a.withAuth() { if err := a.writeToken(w, "freeaccess", 24*366*10*time.Hour); err != nil { a.log.Error().Err(err).Msg("Login/free") } return } var token []byte |
︙ | ︙ | |||
44 45 46 47 48 49 50 | http.Error(w, "Authentication failed", http.StatusUnauthorized) return } if err := a.writeToken(w, string(token), a.tokenLifetime); err != nil { a.log.Error().Err(err).Msg("Login") } | | | | | 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 | http.Error(w, "Authentication failed", http.StatusUnauthorized) return } if err := a.writeToken(w, string(token), a.tokenLifetime); err != nil { a.log.Error().Err(err).Msg("Login") } }) } func retrieveIdentCred(r *http.Request) (string, string) { if ident, cred, ok := adapter.GetCredentialsViaForm(r); ok { return ident, cred } if ident, cred, ok := r.BasicAuth(); ok { return ident, cred } return "", "" } // MakeRenewAuthHandler creates a new HTTP handler to renew the authenticate of a user. func (a *API) MakeRenewAuthHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if !a.withAuth() { if err := a.writeToken(w, "freeaccess", 24*366*10*time.Hour); err != nil { a.log.Error().Err(err).Msg("Refresh/free") } return } |
︙ | ︙ | |||
91 92 93 94 95 96 97 | if err != nil { a.reportUsecaseError(w, err) return } if err = a.writeToken(w, string(token), a.tokenLifetime); err != nil { a.log.Error().Err(err).Msg("Write renewed token") } | | | | | 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | if err != nil { a.reportUsecaseError(w, err) return } if err = a.writeToken(w, string(token), a.tokenLifetime); err != nil { a.log.Error().Err(err).Msg("Write renewed token") } }) } func (a *API) writeToken(w http.ResponseWriter, token string, lifetime time.Duration) error { return a.writeObject(w, id.Invalid, sx.MakeList( sx.MakeString("Bearer"), sx.MakeString(token), sx.Int64(int64(lifetime/time.Second)), )) } |
Changes to web/adapter/api/query.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- // 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 api import ( "bytes" "fmt" "io" "net/http" "net/url" "strconv" "strings" | > > > | | | | > > > > > | | < < < < > > > > > > > > > > > > > > > > > > < | | | | | | | < < < < < < < | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package api import ( "bytes" "fmt" "io" "net/http" "net/url" "strconv" "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/sexp" "zettelstore.de/z/query" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/content" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeQueryHandler creates a new HTTP handler to perform a query. func (a *API) MakeQueryHandler( queryMeta *usecase.Query, tagZettel *usecase.TagZettel, roleZettel *usecase.RoleZettel, reIndex *usecase.ReIndex, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() urlQuery := r.URL.Query() if a.handleTagZettel(w, r, tagZettel, urlQuery) || a.handleRoleZettel(w, r, roleZettel, urlQuery) { return } sq := adapter.GetQuery(urlQuery) metaSeq, err := queryMeta.Run(ctx, sq) if err != nil { a.reportUsecaseError(w, err) return } actions, err := adapter.TryReIndex(ctx, sq.Actions(), metaSeq, reIndex) if err != nil { a.reportUsecaseError(w, err) return } if len(actions) > 0 { if len(metaSeq) > 0 { for _, act := range actions { if act == api.RedirectAction { zid := metaSeq[0].Zid ub := a.NewURLBuilder('z').SetZid(zid.ZettelID()) a.redirectFound(w, r, ub, zid) return } } } } var encoder zettelEncoder var contentType string switch enc, _ := getEncoding(r, urlQuery); enc { case api.EncoderPlain: encoder = &plainZettelEncoder{} contentType = content.PlainText case api.EncoderData: encoder = &dataZettelEncoder{ sq: sq, getRights: func(m *meta.Meta) api.ZettelRights { return a.getRights(ctx, m) }, } contentType = content.SXPF default: http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } var buf bytes.Buffer err = queryAction(&buf, encoder, metaSeq, actions) if err != nil { a.log.Error().Err(err).Str("query", sq.String()).Msg("execute query action") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } if err = writeBuffer(w, &buf, contentType); err != nil { a.log.Error().Err(err).Msg("write result buffer") } }) } func queryAction(w io.Writer, enc zettelEncoder, ml []*meta.Meta, actions []string) error { min, max := -1, -1 if len(actions) > 0 { acts := make([]string, 0, len(actions)) for _, act := range actions { if strings.HasPrefix(act, api.MinAction) { if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 { min = num continue } } if strings.HasPrefix(act, api.MaxAction) { if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 { max = num continue } } acts = append(acts, act) } for _, act := range acts { if act == api.KeysAction { return encodeKeysArrangement(w, enc, ml, act) } switch key := strings.ToLower(act); meta.Type(key) { case meta.TypeWord, meta.TypeTagSet: return encodeMetaKeyArrangement(w, enc, ml, key, min, max) } } } |
︙ | ︙ | |||
184 185 186 187 188 189 190 | return err } } return nil } type dataZettelEncoder struct { | < < | | | | | | < | | | | | | | < | | < < < | < | > > > > > > | | < | 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 | return err } } return nil } type dataZettelEncoder struct { sq *query.Query getRights func(*meta.Meta) api.ZettelRights } func (dze *dataZettelEncoder) writeMetaList(w io.Writer, ml []*meta.Meta) error { result := make(sx.Vector, len(ml)+1) result[0] = sx.SymbolList symID, symZettel := sx.MakeSymbol("id"), sx.MakeSymbol("zettel") for i, m := range ml { msz := sexp.EncodeMetaRights(api.MetaRights{ Meta: m.Map(), Rights: dze.getRights(m), }) msz = sx.Cons(sx.MakeList(symID, sx.Int64(m.Zid)), msz.Cdr()).Cons(symZettel) result[i+1] = msz } _, err := sx.Print(w, sx.MakeList( sx.MakeSymbol("meta-list"), sx.MakeList(sx.MakeSymbol("query"), sx.MakeString(dze.sq.String())), sx.MakeList(sx.MakeSymbol("human"), sx.MakeString(dze.sq.Human())), sx.MakeList(result...), )) return err } func (dze *dataZettelEncoder) writeArrangement(w io.Writer, act string, arr meta.Arrangement) error { result := sx.Nil() for aggKey, metaList := range arr { sxMeta := sx.Nil() for i := len(metaList) - 1; i >= 0; i-- { sxMeta = sxMeta.Cons(sx.Int64(metaList[i].Zid)) } sxMeta = sxMeta.Cons(sx.MakeString(aggKey)) result = result.Cons(sxMeta) } _, err := sx.Print(w, sx.MakeList( sx.MakeSymbol("aggregate"), sx.MakeString(act), sx.MakeList(sx.MakeSymbol("query"), sx.MakeString(dze.sq.String())), sx.MakeList(sx.MakeSymbol("human"), sx.MakeString(dze.sq.Human())), result.Cons(sx.SymbolList), )) return err } func (a *API) handleTagZettel(w http.ResponseWriter, r *http.Request, tagZettel *usecase.TagZettel, vals url.Values) bool { tag := vals.Get(api.QueryKeyTag) if tag == "" { return false } ctx := r.Context() z, err := tagZettel.Run(ctx, tag) if err != nil { a.reportUsecaseError(w, err) return true } zid := z.Meta.Zid newURL := a.NewURLBuilder('z').SetZid(zid.ZettelID()) for key, slVals := range vals { if key == api.QueryKeyTag { continue } for _, val := range slVals { newURL.AppendKVQuery(key, val) } } a.redirectFound(w, r, newURL, zid) return true } func (a *API) handleRoleZettel(w http.ResponseWriter, r *http.Request, roleZettel *usecase.RoleZettel, vals url.Values) bool { role := vals.Get(api.QueryKeyRole) if role == "" { return false } ctx := r.Context() z, err := roleZettel.Run(ctx, role) if err != nil { a.reportUsecaseError(w, err) return true } zid := z.Meta.Zid newURL := a.NewURLBuilder('z').SetZid(zid.ZettelID()) for key, slVals := range vals { if key == api.QueryKeyRole { continue } for _, val := range slVals { newURL.AppendKVQuery(key, val) } } a.redirectFound(w, r, newURL, zid) return true } func (a *API) redirectFound(w http.ResponseWriter, r *http.Request, ub *api.URLBuilder, zid id.Zid) { w.Header().Set(api.HeaderContentType, content.PlainText) http.Redirect(w, r, ub.String(), http.StatusFound) if _, err := io.WriteString(w, zid.String()); err != nil { a.log.Error().Err(err).Msg("redirect body") } } |
Deleted web/adapter/api/rename_zettel.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to web/adapter/api/request.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 api import ( "io" "net/http" "net/url" | > > > | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package api import ( "io" "net/http" "net/url" "t73f.de/r/sx/sxreader" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "t73f.de/r/zsc/sexp" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // getEncoding returns the data encoding selected by the caller. func getEncoding(r *http.Request, q url.Values) (api.EncodingEnum, string) { |
︙ | ︙ |
Changes to web/adapter/api/response.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 api import ( "bytes" "net/http" | > > > | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package api import ( "bytes" "net/http" "t73f.de/r/sx" "zettelstore.de/z/web/content" "zettelstore.de/z/zettel/id" ) func (a *API) writeObject(w http.ResponseWriter, zid id.Zid, obj sx.Object) error { var buf bytes.Buffer if _, err := sx.Print(&buf, obj); err != nil { msg := a.log.Error().Err(err) if msg != nil { if zid.IsValid() { msg = msg.Zid(zid) } msg.Msg("Unable to store object in buffer") } http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) |
︙ | ︙ |
Changes to web/adapter/api/update_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 api import ( "net/http" | > > > | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package api import ( "net/http" "t73f.de/r/zsc/api" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" ) // MakeUpdateZettelHandler creates a new HTTP handler to update a zettel. func (a *API) MakeUpdateZettelHandler(updateZettel *usecase.UpdateZettel) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } q := r.URL.Query() |
︙ | ︙ | |||
46 47 48 49 50 51 52 | return } if err = updateZettel.Run(r.Context(), zettel, true); err != nil { a.reportUsecaseError(w, err) return } w.WriteHeader(http.StatusNoContent) | | | 49 50 51 52 53 54 55 56 57 | return } if err = updateZettel.Run(r.Context(), zettel, true); err != nil { a.reportUsecaseError(w, err) return } w.WriteHeader(http.StatusNoContent) }) } |
Changes to web/adapter/errors.go.
1 2 3 4 5 6 7 8 9 10 | //----------------------------------------------------------------------------- // 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. //----------------------------------------------------------------------------- | > > > < < < < < < < < < < < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package adapter import "net/http" // BadRequest signals HTTP status code 400. func BadRequest(w http.ResponseWriter, text string) { http.Error(w, text, http.StatusBadRequest) } // ErrResourceNotFound is signalled when a web resource was not found. type ErrResourceNotFound struct{ Path string } func (ernf ErrResourceNotFound) Error() string { return "resource not found: " + ernf.Path } |
Changes to web/adapter/request.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 adapter import ( "net/http" "net/url" "strconv" "strings" | > > > | | 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package adapter import ( "net/http" "net/url" "strconv" "strings" "t73f.de/r/zsc/api" "zettelstore.de/z/kernel" "zettelstore.de/z/query" ) // GetCredentialsViaForm retrieves the authentication credentions from a form. func GetCredentialsViaForm(r *http.Request) (ident, cred string, ok bool) { err := r.ParseForm() |
︙ | ︙ |
Changes to web/adapter/response.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 adapter import ( "errors" "fmt" "net/http" "strings" | > > > | | 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package adapter import ( "errors" "fmt" "net/http" "strings" "t73f.de/r/zsc/api" "zettelstore.de/z/box" "zettelstore.de/z/usecase" ) // WriteData emits the given data to the response writer. func WriteData(w http.ResponseWriter, data []byte, contentType string) error { if len(data) == 0 { |
︙ | ︙ | |||
63 64 65 66 67 68 69 | msg := ena.Error() return http.StatusForbidden, strings.ToUpper(msg[:1]) + msg[1:] } var eiz box.ErrInvalidZid if errors.As(err, &eiz) { return http.StatusBadRequest, fmt.Sprintf("Zettel-ID %q not appropriate in this context", eiz.Zid) } | < < < < | 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | msg := ena.Error() return http.StatusForbidden, strings.ToUpper(msg[:1]) + msg[1:] } var eiz box.ErrInvalidZid if errors.As(err, &eiz) { return http.StatusBadRequest, fmt.Sprintf("Zettel-ID %q not appropriate in this context", eiz.Zid) } var etznf usecase.ErrTagZettelNotFound if errors.As(err, &etznf) { return http.StatusNotFound, "Tag zettel not found: " + etznf.Tag } var erznf usecase.ErrRoleZettelNotFound if errors.As(err, &erznf) { return http.StatusNotFound, "Role zettel not found: " + erznf.Role |
︙ | ︙ |
Changes to web/adapter/webui/const.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 webui // WebUI related constants. const queryKeyAction = "_action" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package webui // WebUI related constants. const queryKeyAction = "_action" |
︙ | ︙ |
Changes to web/adapter/webui/create_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 webui import ( "bytes" "context" "net/http" "strings" | > > > | | | > | > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "bytes" "context" "net/http" "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "zettelstore.de/z/box" "zettelstore.de/z/encoder/zmkenc" "zettelstore.de/z/evaluator" "zettelstore.de/z/parser" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeGetCreateZettelHandler creates a new HTTP handler to display the // HTML edit view for the various zettel creation methods. func (wui *WebUI) MakeGetCreateZettelHandler( getZettel usecase.GetZettel, createZettel *usecase.CreateZettel, ucListRoles usecase.ListRoles, ucListSyntax usecase.ListSyntax, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() op := getCreateAction(q.Get(queryKeyAction)) path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) |
︙ | ︙ | |||
62 63 64 65 66 67 68 | case actionNew: title := parser.NormalizedSpacedText(origZettel.Meta.GetTitle()) newTitle := parser.NormalizedSpacedText(q.Get(api.KeyTitle)) wui.renderZettelForm(ctx, w, createZettel.PrepareNew(origZettel, newTitle), title, "", roleData, syntaxData) case actionVersion: wui.renderZettelForm(ctx, w, createZettel.PrepareVersion(origZettel), "Version Zettel", "", roleData, syntaxData) } | | | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | case actionNew: title := parser.NormalizedSpacedText(origZettel.Meta.GetTitle()) newTitle := parser.NormalizedSpacedText(q.Get(api.KeyTitle)) wui.renderZettelForm(ctx, w, createZettel.PrepareNew(origZettel, newTitle), title, "", roleData, syntaxData) case actionVersion: wui.renderZettelForm(ctx, w, createZettel.PrepareVersion(origZettel), "Version Zettel", "", roleData, syntaxData) } }) } func retrieveDataLists(ctx context.Context, ucListRoles usecase.ListRoles, ucListSyntax usecase.ListSyntax) ([]string, []string) { roleData := dataListFromArrangement(ucListRoles.Run(ctx)) syntaxData := dataListFromArrangement(ucListSyntax.Run(ctx)) return roleData, syntaxData } |
︙ | ︙ | |||
100 101 102 103 104 105 106 | for _, p := range m.PairsRest() { sb.WriteString(p.Key) sb.WriteString(": ") sb.WriteString(p.Value) sb.WriteByte('\n') } env, rb := wui.createRenderEnv(ctx, "form", wui.rtConfig.Get(ctx, nil, api.KeyLang), title, user) | | | | | | | | | | | > | | > | > | | | 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 | for _, p := range m.PairsRest() { sb.WriteString(p.Key) sb.WriteString(": ") sb.WriteString(p.Value) sb.WriteByte('\n') } env, rb := wui.createRenderEnv(ctx, "form", wui.rtConfig.Get(ctx, nil, api.KeyLang), title, user) rb.bindString("heading", sx.MakeString(title)) rb.bindString("form-action-url", sx.MakeString(formActionURL)) rb.bindString("role-data", makeStringList(roleData)) rb.bindString("syntax-data", makeStringList(syntaxData)) rb.bindString("meta", sx.MakeString(sb.String())) if !ztl.Content.IsBinary() { rb.bindString("content", sx.MakeString(ztl.Content.AsString())) } wui.bindCommonZettelData(ctx, &rb, user, m, &ztl.Content) if rb.err == nil { rb.err = wui.renderSxnTemplate(ctx, w, id.FormTemplateZid, env) } if err := rb.err; err != nil { wui.reportError(ctx, w, err) } } // MakePostCreateZettelHandler creates a new HTTP handler to store content of // an existing zettel. func (wui *WebUI) MakePostCreateZettelHandler(createZettel *usecase.CreateZettel) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() reEdit, zettel, err := parseZettelForm(r, id.Invalid) if err == errMissingContent { wui.reportError(ctx, w, adapter.NewErrBadRequest("Content is missing")) return } if err != nil { const msg = "Unable to read form data" wui.log.Info().Err(err).Msg(msg) wui.reportError(ctx, w, adapter.NewErrBadRequest(msg)) return } newZid, err := createZettel.Run(ctx, zettel) if err != nil { wui.reportError(ctx, w, err) return } if reEdit { wui.redirectFound(w, r, wui.NewURLBuilder('e').SetZid(newZid.ZettelID())) } else { wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(newZid.ZettelID())) } }) } // MakeGetZettelFromListHandler creates a new HTTP handler to store content of // an existing zettel. func (wui *WebUI) MakeGetZettelFromListHandler( queryMeta *usecase.Query, evaluate *usecase.Evaluate, ucListRoles usecase.ListRoles, ucListSyntax usecase.ListSyntax, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { q := adapter.GetQuery(r.URL.Query()) ctx := r.Context() metaSeq, err := queryMeta.Run(box.NoEnrichQuery(ctx, q), q) if err != nil { wui.reportError(ctx, w, err) return } entries, _ := evaluator.QueryAction(ctx, q, metaSeq, wui.rtConfig) bns := evaluate.RunBlockNode(ctx, entries) enc := zmkenc.Create() var zmkContent bytes.Buffer _, err = enc.WriteBlocks(&zmkContent, &bns) if err != nil { wui.reportError(ctx, w, err) return } m := meta.New(id.Invalid) m.Set(api.KeyTitle, q.Human()) m.Set(api.KeySyntax, api.ValueSyntaxZmk) if qval := q.String(); qval != "" { m.Set(api.KeyQuery, qval) } zettel := zettel.Zettel{Meta: m, Content: zettel.NewContent(zmkContent.Bytes())} roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) wui.renderZettelForm(ctx, w, zettel, "Zettel from list", wui.createNewURL, roleData, syntaxData) }) } |
Changes to web/adapter/webui/delete_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 webui import ( "net/http" | > > > | | | | > > > | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "net/http" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/maps" "zettelstore.de/z/box" "zettelstore.de/z/strfun" "zettelstore.de/z/usecase" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeGetDeleteZettelHandler creates a new HTTP handler to display the // HTML delete view of a zettel. func (wui *WebUI) MakeGetDeleteZettelHandler( getZettel usecase.GetZettel, getAllZettel usecase.GetAllZettel, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) return } zs, err := getAllZettel.Run(ctx, zid) if err != nil { wui.reportError(ctx, w, err) return } m := zs[0].Meta user := server.GetUser(ctx) env, rb := wui.createRenderEnv( ctx, "delete", wui.rtConfig.Get(ctx, nil, api.KeyLang), "Delete Zettel "+m.Zid.String(), user) if len(zs) > 1 { rb.bindString("shadowed-box", sx.MakeString(zs[1].Meta.GetDefault(api.KeyBoxNumber, "???"))) rb.bindString("incoming", nil) } else { rb.bindString("shadowed-box", nil) rb.bindString("incoming", wui.encodeIncoming(m, wui.makeGetTextTitle(ctx, getZettel))) } wui.bindCommonZettelData(ctx, &rb, user, m, nil) if rb.err == nil { err = wui.renderSxnTemplate(ctx, w, id.DeleteTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } }) } func (wui *WebUI) encodeIncoming(m *meta.Meta, getTextTitle getTextTitleFunc) *sx.Pair { zidMap := make(strfun.Set) addListValues(zidMap, m, api.KeyBackward) for _, kd := range meta.GetSortedKeyDescriptions() { inverseKey := kd.Inverse |
︙ | ︙ | |||
93 94 95 96 97 98 99 | for _, val := range values { zidMap.Set(val) } } } // MakePostDeleteZettelHandler creates a new HTTP handler to delete a zettel. | | | | | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | for _, val := range values { zidMap.Set(val) } } } // MakePostDeleteZettelHandler creates a new HTTP handler to delete a zettel. func (wui *WebUI) MakePostDeleteZettelHandler(deleteZettel *usecase.DeleteZettel) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) return } if err = deleteZettel.Run(r.Context(), zid); err != nil { wui.reportError(ctx, w, err) return } wui.redirectFound(w, r, wui.NewURLBuilder('/')) }) } |
Changes to web/adapter/webui/edit_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 webui import ( "net/http" | > > > < | > > > > | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "net/http" "zettelstore.de/z/box" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel/id" ) // MakeEditGetZettelHandler creates a new HTTP handler to display the // HTML edit view of a zettel. func (wui *WebUI) MakeEditGetZettelHandler( getZettel usecase.GetZettel, ucListRoles usecase.ListRoles, ucListSyntax usecase.ListSyntax, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) return } zettel, err := getZettel.Run(box.NoEnrichContext(ctx), zid) if err != nil { wui.reportError(ctx, w, err) return } roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) wui.renderZettelForm(ctx, w, zettel, "Edit Zettel", "", roleData, syntaxData) }) } // MakeEditSetZettelHandler creates a new HTTP handler to store content of // an existing zettel. func (wui *WebUI) MakeEditSetZettelHandler(updateZettel *usecase.UpdateZettel) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) return } |
︙ | ︙ | |||
68 69 70 71 72 73 74 | } if err = updateZettel.Run(r.Context(), zettel, hasContent); err != nil { wui.reportError(ctx, w, err) return } if reEdit { | | | | | 74 75 76 77 78 79 80 81 82 83 84 85 86 | } if err = updateZettel.Run(r.Context(), zettel, hasContent); err != nil { wui.reportError(ctx, w, err) return } if reEdit { wui.redirectFound(w, r, wui.NewURLBuilder('e').SetZid(zid.ZettelID())) } else { wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(zid.ZettelID())) } }) } |
Changes to web/adapter/webui/favicon.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- // 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 webui import ( "io" "net/http" "os" "path/filepath" "zettelstore.de/z/web/adapter" ) | > > > | | | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "io" "net/http" "os" "path/filepath" "zettelstore.de/z/web/adapter" ) func (wui *WebUI) MakeFaviconHandler(baseDir string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { filename := filepath.Join(baseDir, "favicon.ico") f, err := os.Open(filename) if err != nil { wui.log.Debug().Err(err).Msg("Favicon not found") http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } defer f.Close() data, err := io.ReadAll(f) if err != nil { wui.log.Error().Err(err).Msg("Unable to read favicon data") http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } if err = adapter.WriteData(w, data, ""); err != nil { wui.log.Error().Err(err).Msg("Write favicon") } }) } |
Changes to web/adapter/webui/forms.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- // 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 webui import ( "bytes" "errors" "io" "net/http" "regexp" "strings" "unicode" | > > > | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "bytes" "errors" "io" "net/http" "regexp" "strings" "unicode" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" "zettelstore.de/z/web/content" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ |
Changes to web/adapter/webui/forms_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 webui import "testing" func TestRemoveEmptyLines(t *testing.T) { | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import "testing" func TestRemoveEmptyLines(t *testing.T) { |
︙ | ︙ |
Changes to web/adapter/webui/get_info.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 webui import ( "context" "net/http" | > > > | | | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "context" "net/http" "slices" "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/collect" "zettelstore.de/z/encoder" "zettelstore.de/z/evaluator" "zettelstore.de/z/parser" "zettelstore.de/z/query" "zettelstore.de/z/strfun" "zettelstore.de/z/usecase" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" ) // MakeGetInfoHandler creates a new HTTP handler for the use case "get zettel". func (wui *WebUI) MakeGetInfoHandler( ucParseZettel usecase.ParseZettel, ucEvaluate *usecase.Evaluate, ucGetZettel usecase.GetZettel, ucGetAllZettel usecase.GetAllZettel, ucQuery *usecase.Query, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) |
︙ | ︙ | |||
62 63 64 65 66 67 68 | return ucEvaluate.RunMetadata(ctx, val) } pairs := zn.Meta.ComputedPairs() metadata := sx.Nil() for i := len(pairs) - 1; i >= 0; i-- { key := pairs[i].Key sxval := wui.writeHTMLMetaValue(key, pairs[i].Value, getTextTitle, evalMeta, enc) | | > | | | | | | | > | | | | < | | | < < | 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 | return ucEvaluate.RunMetadata(ctx, val) } pairs := zn.Meta.ComputedPairs() metadata := sx.Nil() for i := len(pairs) - 1; i >= 0; i-- { key := pairs[i].Key sxval := wui.writeHTMLMetaValue(key, pairs[i].Value, getTextTitle, evalMeta, enc) metadata = metadata.Cons(sx.Cons(sx.MakeString(key), sxval)) } summary := collect.References(zn) locLinks, queryLinks, extLinks := wui.splitLocSeaExtLinks(append(summary.Links, summary.Embeds...)) title := parser.NormalizedSpacedText(zn.InhMeta.GetTitle()) phrase := q.Get(api.QueryKeyPhrase) if phrase == "" { phrase = title } unlinkedMeta, err := ucQuery.Run(ctx, createUnlinkedQuery(zid, phrase)) if err != nil { wui.reportError(ctx, w, err) return } entries, _ := evaluator.QueryAction(ctx, nil, unlinkedMeta, wui.rtConfig) bns := ucEvaluate.RunBlockNode(ctx, entries) unlinkedContent, _, err := enc.BlocksSxn(&bns) if err != nil { wui.reportError(ctx, w, err) return } encTexts := encodingTexts() shadowLinks := getShadowLinks(ctx, zid, ucGetAllZettel) user := server.GetUser(ctx) env, rb := wui.createRenderEnv(ctx, "info", wui.rtConfig.Get(ctx, nil, api.KeyLang), title, user) rb.bindString("metadata", metadata) rb.bindString("local-links", locLinks) rb.bindString("query-links", queryLinks) rb.bindString("ext-links", extLinks) rb.bindString("unlinked-content", unlinkedContent) rb.bindString("phrase", sx.MakeString(phrase)) rb.bindString("query-key-phrase", sx.MakeString(api.QueryKeyPhrase)) rb.bindString("enc-eval", wui.infoAPIMatrix(zid, false, encTexts)) rb.bindString("enc-parsed", wui.infoAPIMatrixParsed(zid, encTexts)) rb.bindString("shadow-links", shadowLinks) wui.bindCommonZettelData(ctx, &rb, user, zn.InhMeta, &zn.Content) if rb.err == nil { err = wui.renderSxnTemplate(ctx, w, id.InfoTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } }) } func (wui *WebUI) splitLocSeaExtLinks(links []*ast.Reference) (locLinks, queries, extLinks *sx.Pair) { for i := len(links) - 1; i >= 0; i-- { ref := links[i] switch ref.State { case ast.RefStateHosted, ast.RefStateBased: // Local locLinks = locLinks.Cons(sx.MakeString(ref.String())) case ast.RefStateQuery: queries = queries.Cons( sx.Cons( sx.MakeString(ref.Value), sx.MakeString(wui.NewURLBuilder('h').AppendQuery(ref.Value).String()))) case ast.RefStateExternal: extLinks = extLinks.Cons(sx.MakeString(ref.String())) } } return locLinks, queries, extLinks } func createUnlinkedQuery(zid id.Zid, phrase string) *query.Query { var sb strings.Builder sb.Write(zid.Bytes()) |
︙ | ︙ | |||
158 159 160 161 162 163 164 | func encodingTexts() []string { encodings := encoder.GetEncodings() encTexts := make([]string, 0, len(encodings)) for _, f := range encodings { encTexts = append(encTexts, f.String()) } | | | | | | | | | | 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 | func encodingTexts() []string { encodings := encoder.GetEncodings() encTexts := make([]string, 0, len(encodings)) for _, f := range encodings { encTexts = append(encTexts, f.String()) } slices.Sort(encTexts) return encTexts } var apiParts = []string{api.PartZettel, api.PartMeta, api.PartContent} func (wui *WebUI) infoAPIMatrix(zid id.Zid, parseOnly bool, encTexts []string) *sx.Pair { matrix := sx.Nil() u := wui.NewURLBuilder('z').SetZid(zid.ZettelID()) for ip := len(apiParts) - 1; ip >= 0; ip-- { part := apiParts[ip] row := sx.Nil() for je := len(encTexts) - 1; je >= 0; je-- { enc := encTexts[je] if parseOnly { u.AppendKVQuery(api.QueryKeyParseOnly, "") } u.AppendKVQuery(api.QueryKeyPart, part) u.AppendKVQuery(api.QueryKeyEncoding, enc) row = row.Cons(sx.Cons(sx.MakeString(enc), sx.MakeString(u.String()))) u.ClearQuery() } matrix = matrix.Cons(sx.Cons(sx.MakeString(part), row)) } return matrix } func (wui *WebUI) infoAPIMatrixParsed(zid id.Zid, encTexts []string) *sx.Pair { matrix := wui.infoAPIMatrix(zid, true, encTexts) u := wui.NewURLBuilder('z').SetZid(zid.ZettelID()) for i, row := 0, matrix; i < len(apiParts) && row != nil; row = row.Tail() { line, isLine := sx.GetPair(row.Car()) if !isLine || line == nil { continue } last := line.LastPair() part := apiParts[i] u.AppendKVQuery(api.QueryKeyPart, part) last = last.AppendBang(sx.Cons(sx.MakeString("plain"), sx.MakeString(u.String()))) u.ClearQuery() if i < 2 { u.AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) u.AppendKVQuery(api.QueryKeyPart, part) last.AppendBang(sx.Cons(sx.MakeString("data"), sx.MakeString(u.String()))) u.ClearQuery() } i++ } return matrix } func getShadowLinks(ctx context.Context, zid id.Zid, getAllZettel usecase.GetAllZettel) *sx.Pair { result := sx.Nil() if zl, err := getAllZettel.Run(ctx, zid); err == nil { for i := len(zl) - 1; i >= 1; i-- { if boxNo, ok := zl[i].Meta.Get(api.KeyBoxNumber); ok { result = result.Cons(sx.MakeString(boxNo)) } } } return result } |
Changes to web/adapter/webui/get_zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 webui import ( "context" "net/http" | > > > > | | > | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "context" "net/http" "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/shtml" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/parser" "zettelstore.de/z/usecase" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeGetHTMLZettelHandler creates a new HTTP handler for the use case "get zettel". func (wui *WebUI) MakeGetHTMLZettelHandler( evaluate *usecase.Evaluate, getZettel usecase.GetZettel, ) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() path := r.URL.Path[1:] zid, err := id.Parse(path) if err != nil { wui.reportError(ctx, w, box.ErrInvalidZid{Zid: path}) return } |
︙ | ︙ | |||
52 53 54 55 56 57 58 | } user := server.GetUser(ctx) getTextTitle := wui.makeGetTextTitle(ctx, getZettel) title := parser.NormalizedSpacedText(zn.InhMeta.GetTitle()) env, rb := wui.createRenderEnv(ctx, "zettel", wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang), title, user) | | | | | > | > > > > > > > > > > > > | | 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 | } user := server.GetUser(ctx) getTextTitle := wui.makeGetTextTitle(ctx, getZettel) title := parser.NormalizedSpacedText(zn.InhMeta.GetTitle()) env, rb := wui.createRenderEnv(ctx, "zettel", wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang), title, user) rb.bindSymbol(symMetaHeader, metaObj) rb.bindString("heading", sx.MakeString(title)) if role, found := zn.InhMeta.Get(api.KeyRole); found && role != "" { rb.bindString("role-url", sx.MakeString(wui.NewURLBuilder('h').AppendQuery(api.KeyRole+api.SearchOperatorHas+role).String())) } if folgeRole, found := zn.InhMeta.Get(api.KeyFolgeRole); found && folgeRole != "" { rb.bindString("folge-role-url", sx.MakeString(wui.NewURLBuilder('h').AppendQuery(api.KeyRole+api.SearchOperatorHas+folgeRole).String())) } rb.bindString("tag-refs", wui.transformTagSet(api.KeyTags, meta.ListFromValue(zn.InhMeta.GetDefault(api.KeyTags, "")))) rb.bindString("predecessor-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeyPredecessor, getTextTitle)) rb.bindString("precursor-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeyPrecursor, getTextTitle)) rb.bindString("superior-refs", wui.identifierSetAsLinks(zn.InhMeta, api.KeySuperior, getTextTitle)) rb.bindString("urls", metaURLAssoc(zn.InhMeta)) rb.bindString("content", content) rb.bindString("endnotes", endnotes) wui.bindLinks(ctx, &rb, "folge", zn.InhMeta, api.KeyFolge, config.KeyShowFolgeLinks, getTextTitle) wui.bindLinks(ctx, &rb, "subordinate", zn.InhMeta, api.KeySubordinates, config.KeyShowSubordinateLinks, getTextTitle) wui.bindLinks(ctx, &rb, "back", zn.InhMeta, api.KeyBack, config.KeyShowBackLinks, getTextTitle) wui.bindLinks(ctx, &rb, "successor", zn.InhMeta, api.KeySuccessors, config.KeyShowSuccessorLinks, getTextTitle) if role, found := zn.InhMeta.Get(api.KeyRole); found && role != "" { for _, part := range []string{"meta", "actions", "heading"} { rb.rebindResolved("ROLE-"+role+"-"+part, "ROLE-DEFAULT-"+part) } } wui.bindCommonZettelData(ctx, &rb, user, zn.InhMeta, &zn.Content) if rb.err == nil { err = wui.renderSxnTemplate(ctx, w, id.ZettelTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } }) } func (wui *WebUI) identifierSetAsLinks(m *meta.Meta, key string, getTextTitle getTextTitleFunc) *sx.Pair { if values, ok := m.GetList(key); ok { return wui.transformIdentifierSet(values, getTextTitle) } return nil } func metaURLAssoc(m *meta.Meta) *sx.Pair { var result sx.ListBuilder for _, p := range m.PairsRest() { if key := p.Key; strings.HasSuffix(key, meta.SuffixKeyURL) { if val := p.Value; val != "" { result.Add(sx.Cons(sx.MakeString(capitalizeMetaKey(key)), sx.MakeString(val))) } } } return result.List() } func (wui *WebUI) bindLinks(ctx context.Context, rb *renderBinder, varPrefix string, m *meta.Meta, key, configKey string, getTextTitle getTextTitleFunc) { varLinks := varPrefix + "-links" var symOpen *sx.Symbol switch wui.rtConfig.Get(ctx, m, configKey) { case "false": rb.bindString(varLinks, sx.Nil()) return case "close": default: symOpen = shtml.SymAttrOpen } lstLinks := wui.zettelLinksSxn(m, key, getTextTitle) rb.bindString(varLinks, lstLinks) if sx.IsNil(lstLinks) { return } rb.bindString(varPrefix+"-open", symOpen) |
︙ | ︙ | |||
129 130 131 132 133 134 135 | for i := len(values) - 1; i >= 0; i-- { val := values[i] zid, err := id.Parse(val) if err != nil { continue } if title, found := getTextTitle(zid); found > 0 { | | | | | 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | for i := len(values) - 1; i >= 0; i-- { val := values[i] zid, err := id.Parse(val) if err != nil { continue } if title, found := getTextTitle(zid); found > 0 { url := sx.MakeString(wui.NewURLBuilder('h').SetZid(zid.ZettelID()).String()) if title == "" { lst = lst.Cons(sx.Cons(sx.MakeString(val), url)) } else { lst = lst.Cons(sx.Cons(sx.MakeString(title), url)) } } } return lst } |
Changes to web/adapter/webui/goaction.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 webui import ( "net/http" "zettelstore.de/z/usecase" ) // MakeGetGoActionHandler creates a new HTTP handler to execute certain commands. | > > > | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "net/http" "zettelstore.de/z/usecase" ) // MakeGetGoActionHandler creates a new HTTP handler to execute certain commands. func (wui *WebUI) MakeGetGoActionHandler(ucRefresh *usecase.Refresh) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // Currently, command "refresh" is the only command to be executed. err := ucRefresh.Run(ctx) if err != nil { wui.reportError(ctx, w, err) return } wui.redirectFound(w, r, wui.NewURLBuilder('/')) }) } |
Changes to web/adapter/webui/home.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // 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 webui import ( "context" "errors" "net/http" | > > > < | | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "context" "errors" "net/http" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" ) type getRootPort interface { GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) } // MakeGetRootHandler creates a new HTTP handler to show the root URL. func (wui *WebUI) MakeGetRootHandler(s getRootPort) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if p := r.URL.Path; p != "/" { wui.reportError(ctx, w, adapter.ErrResourceNotFound{Path: p}) return } homeZid, _ := id.Parse(wui.rtConfig.Get(ctx, nil, config.KeyHomeZettel)) apiHomeZid := homeZid.ZettelID() if homeZid != id.DefaultHomeZid { if _, err := s.GetZettel(ctx, homeZid); err == nil { wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(apiHomeZid)) return } homeZid = id.DefaultHomeZid } _, err := s.GetZettel(ctx, homeZid) if err == nil { wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(apiHomeZid)) return } if errors.Is(err, &box.ErrNotAllowed{}) && wui.authz.WithAuth() && server.GetUser(ctx) == nil { wui.redirectFound(w, r, wui.NewURLBuilder('i')) return } wui.redirectFound(w, r, wui.NewURLBuilder('h')) }) } |
Changes to web/adapter/webui/htmlgen.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 webui import ( "net/url" "strings" | > > > | > | | | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "net/url" "strings" "t73f.de/r/sx" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/maps" "t73f.de/r/zsc/shtml" "t73f.de/r/zsc/sz" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/encoder/szenc" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/meta" ) |
︙ | ︙ | |||
37 38 39 40 41 42 43 | tx *szenc.Transformer th *shtml.Evaluator lang string symAt *sx.Symbol } func (wui *WebUI) createGenerator(builder urlBuilder, lang string) *htmlGenerator { | | < < < < < < < < | | | > > > > > > > > > > > > > > > > > > > > > > > > < | < < < | | | < < < | | < < < < < < < < < < < < < < < < < | | | | | | | | | | | | < | | | | | | | < | | | | 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 | tx *szenc.Transformer th *shtml.Evaluator lang string symAt *sx.Symbol } func (wui *WebUI) createGenerator(builder urlBuilder, lang string) *htmlGenerator { th := shtml.NewEvaluator(1) findA := func(obj sx.Object) (attr, assoc, rest *sx.Pair) { pair, isPair := sx.GetPair(obj) if !isPair || !shtml.SymA.IsEqual(pair.Car()) { return nil, nil, nil } rest = pair.Tail() if rest == nil { return nil, nil, nil } objA := rest.Car() attr, isPair = sx.GetPair(objA) if !isPair || !sxhtml.SymAttr.IsEqual(attr.Car()) { return nil, nil, nil } return attr, attr.Tail(), rest.Tail() } linkZettel := func(obj sx.Object) sx.Object { attr, assoc, rest := findA(obj) if attr == nil { return obj } hrefP := assoc.Assoc(shtml.SymAttrHref) if hrefP == nil { return obj } href, ok := sx.GetString(hrefP.Cdr()) if !ok { return obj } zid, fragment, hasFragment := strings.Cut(href.GetValue(), "#") u := builder.NewURLBuilder('h').SetZid(api.ZettelID(zid)) if hasFragment { u = u.SetFragment(fragment) } assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()))) return rest.Cons(assoc.Cons(sxhtml.SymAttr)).Cons(shtml.SymA) } rebind(th, sz.SymLinkZettel, linkZettel) rebind(th, sz.SymLinkFound, linkZettel) rebind(th, sz.SymLinkBased, func(obj sx.Object) sx.Object { attr, assoc, rest := findA(obj) if attr == nil { return obj } hrefP := assoc.Assoc(shtml.SymAttrHref) if hrefP == nil { return obj } href, ok := sx.GetString(hrefP.Cdr()) if !ok { return obj } u := builder.NewURLBuilder('/') assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()+href.GetValue()[1:]))) return rest.Cons(assoc.Cons(sxhtml.SymAttr)).Cons(shtml.SymA) }) rebind(th, sz.SymLinkQuery, func(obj sx.Object) sx.Object { attr, assoc, rest := findA(obj) if attr == nil { return obj } hrefP := assoc.Assoc(shtml.SymAttrHref) if hrefP == nil { return obj } href, ok := sx.GetString(hrefP.Cdr()) if !ok { return obj } ur, err := url.Parse(href.GetValue()) if err != nil { return obj } q := ur.Query().Get(api.QueryKeyQuery) if q == "" { return obj } u := builder.NewURLBuilder('h').AppendQuery(q) assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()))) return rest.Cons(assoc.Cons(sxhtml.SymAttr)).Cons(shtml.SymA) }) rebind(th, sz.SymLinkExternal, func(obj sx.Object) sx.Object { attr, _, rest := findA(obj) if attr == nil { return obj } a := sz.GetAttributes(attr) a = a.Set("target", "_blank") a = a.Add("rel", "external").Add("rel", "noreferrer") return rest.Cons(shtml.EvaluateAttrbute(a)).Cons(shtml.SymA) }) rebind(th, sz.SymEmbed, func(obj sx.Object) sx.Object { pair, isPair := sx.GetPair(obj) if !isPair || !shtml.SymIMG.IsEqual(pair.Car()) { return obj } attr, isPair := sx.GetPair(pair.Tail().Car()) if !isPair || !sxhtml.SymAttr.IsEqual(attr.Car()) { return obj } srcP := attr.Tail().Assoc(shtml.SymAttrSrc) if srcP == nil { return obj } src, isString := sx.GetString(srcP.Cdr()) if !isString { return obj } zid := api.ZettelID(src.GetValue()) if !zid.IsValid() { return obj } u := builder.NewURLBuilder('z').SetZid(zid) imgAttr := attr.Tail().Cons(sx.Cons(shtml.SymAttrSrc, sx.MakeString(u.String()))).Cons(sxhtml.SymAttr) return pair.Tail().Tail().Cons(imgAttr).Cons(shtml.SymIMG) }) return &htmlGenerator{ tx: szenc.NewTransformer(), th: th, lang: lang, } } func rebind(ev *shtml.Evaluator, sym *sx.Symbol, fn func(sx.Object) sx.Object) { prevFn := ev.ResolveBinding(sym) ev.Rebind(sym, func(args sx.Vector, env *shtml.Environment) sx.Object { obj := prevFn(args, env) if env.GetError() == nil { return fn(obj) } return sx.Nil() }) } |
︙ | ︙ | |||
229 230 231 232 233 234 235 | for aelem := att.Tail(); aelem != nil; aelem = aelem.Tail() { if p, ok := sx.GetPair(aelem.Car()); ok { key := p.Car() val := p.Cdr() if tail, isTail := sx.GetPair(val); isTail { val = tail.Car() } | | | 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | for aelem := att.Tail(); aelem != nil; aelem = aelem.Tail() { if p, ok := sx.GetPair(aelem.Car()); ok { key := p.Car() val := p.Cdr() if tail, isTail := sx.GetPair(val); isTail { val = tail.Car() } a = a.Set(sz.GoValue(key), sz.GoValue(val)) } } name, found := a.Get("name") if !found || ignore.Has(name) { continue } |
︙ | ︙ | |||
277 278 279 280 281 282 283 | } sx := g.tx.GetSz(bs) env := shtml.MakeEnvironment(g.lang) sh, err := g.th.Evaluate(sx, &env) if err != nil { return nil, nil, err } | | | 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | } sx := g.tx.GetSz(bs) env := shtml.MakeEnvironment(g.lang) sh, err := g.th.Evaluate(sx, &env) if err != nil { return nil, nil, err } return sh, shtml.Endnotes(&env), nil } // InlinesSxHTML returns an inline slice, encoded as a SxHTML object. func (g *htmlGenerator) InlinesSxHTML(is *ast.InlineSlice) *sx.Pair { if is == nil || len(*is) == 0 { return nil } |
︙ | ︙ |
Changes to web/adapter/webui/htmlmeta.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 webui import ( "context" "errors" | > > > | > | | | | | | | | | | | | < < | | | | | | | | | | | | | > > > | | < > | | | > | | < > | | | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "context" "errors" "t73f.de/r/sx" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/shtml" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/parser" "zettelstore.de/z/usecase" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func (wui *WebUI) writeHTMLMetaValue( key, value string, getTextTitle getTextTitleFunc, evalMetadata evalMetadataFunc, gen *htmlGenerator, ) sx.Object { switch kt := meta.Type(key); kt { case meta.TypeCredential: return sx.MakeString(value) case meta.TypeEmpty: return sx.MakeString(value) case meta.TypeID: return wui.transformIdentifier(value, getTextTitle) case meta.TypeIDSet: return wui.transformIdentifierSet(meta.ListFromValue(value), getTextTitle) case meta.TypeNumber: return wui.transformKeyValueText(key, value, value) case meta.TypeString: return sx.MakeString(value) case meta.TypeTagSet: return wui.transformTagSet(key, meta.ListFromValue(value)) case meta.TypeTimestamp: if ts, ok := meta.TimeValue(value); ok { return sx.MakeList( sx.MakeSymbol("time"), sx.MakeList( sxhtml.SymAttr, sx.Cons(sx.MakeSymbol("datetime"), sx.MakeString(ts.Format("2006-01-02T15:04:05"))), ), sx.MakeList(sxhtml.SymNoEscape, sx.MakeString(ts.Format("2006-01-02 15:04:05"))), ) } return sx.Nil() case meta.TypeURL: return wui.url2html(sx.MakeString(value)) case meta.TypeWord: return wui.transformKeyValueText(key, value, value) case meta.TypeZettelmarkup: return wui.transformZmkMetadata(value, evalMetadata, gen) default: return sx.MakeList(shtml.SymSTRONG, sx.MakeString("Unhandled type: "), sx.MakeString(kt.Name)) } } func (wui *WebUI) transformIdentifier(val string, getTextTitle getTextTitleFunc) sx.Object { text := sx.MakeString(val) zid, err := id.Parse(val) if err != nil { return text } title, found := getTextTitle(zid) switch { case found > 0: ub := wui.NewURLBuilder('h').SetZid(zid.ZettelID()) attrs := sx.Nil() if title != "" { attrs = attrs.Cons(sx.Cons(shtml.SymAttrTitle, sx.MakeString(title))) } attrs = attrs.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(ub.String()))).Cons(sxhtml.SymAttr) return sx.Nil().Cons(sx.MakeString(zid.String())).Cons(attrs).Cons(shtml.SymA) case found == 0: return sx.MakeList(sx.MakeSymbol("s"), text) default: // case found < 0: return text } } func (wui *WebUI) transformIdentifierSet(vals []string, getTextTitle getTextTitleFunc) *sx.Pair { if len(vals) == 0 { return nil } var space = sx.MakeString(" ") text := make(sx.Vector, 0, 2*len(vals)) for _, val := range vals { text = append(text, space, wui.transformIdentifier(val, getTextTitle)) } return sx.MakeList(text[1:]...).Cons(shtml.SymSPAN) } func (wui *WebUI) transformTagSet(key string, tags []string) *sx.Pair { if len(tags) == 0 { return nil } var space = sx.MakeString(" ") text := make(sx.Vector, 0, 2*len(tags)+2) for _, tag := range tags { text = append(text, space, wui.transformKeyValueText(key, tag, tag)) } if len(tags) > 1 { text = append(text, space, wui.transformKeyValuesText(key, tags, "(all)")) } return sx.MakeList(text[1:]...).Cons(shtml.SymSPAN) } func (wui *WebUI) transformKeyValueText(key, value, text string) *sx.Pair { ub := wui.NewURLBuilder('h').AppendQuery(key + api.SearchOperatorHas + value) return buildHref(ub, text) } func (wui *WebUI) transformKeyValuesText(key string, values []string, text string) *sx.Pair { ub := wui.NewURLBuilder('h') for _, val := range values { ub = ub.AppendQuery(key + api.SearchOperatorHas + val) } return buildHref(ub, text) } func buildHref(ub *api.URLBuilder, text string) *sx.Pair { return sx.MakeList( shtml.SymA, sx.MakeList( sxhtml.SymAttr, sx.Cons(shtml.SymAttrHref, sx.MakeString(ub.String())), ), sx.MakeString(text), ) } type evalMetadataFunc = func(string) ast.InlineSlice func createEvalMetadataFunc(ctx context.Context, evaluate *usecase.Evaluate) evalMetadataFunc { return func(value string) ast.InlineSlice { return evaluate.RunMetadata(ctx, value) } |
︙ | ︙ | |||
160 161 162 163 164 165 166 | } return parser.NormalizedSpacedText(z.Meta.GetTitle()), 1 } } func (wui *WebUI) transformZmkMetadata(value string, evalMetadata evalMetadataFunc, gen *htmlGenerator) sx.Object { is := evalMetadata(value) | | | 166 167 168 169 170 171 172 173 174 | } return parser.NormalizedSpacedText(z.Meta.GetTitle()), 1 } } func (wui *WebUI) transformZmkMetadata(value string, evalMetadata evalMetadataFunc, gen *htmlGenerator) sx.Object { is := evalMetadata(value) return gen.InlinesSxHTML(&is).Cons(shtml.SymSPAN) } |
Changes to web/adapter/webui/lists.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- // 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 webui import ( "context" "io" "net/http" "net/url" "slices" "strconv" "strings" | > > > | > | > | > > > > | | < < | | < < < < | | | | > > > > > > > < < < < | | | | | | | | | | > | > | | | | | > > > | | | | | > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "context" "io" "net/http" "net/url" "slices" "strconv" "strings" "t73f.de/r/sx" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/shtml" "zettelstore.de/z/ast" "zettelstore.de/z/encoding/atom" "zettelstore.de/z/encoding/rss" "zettelstore.de/z/encoding/xml" "zettelstore.de/z/evaluator" "zettelstore.de/z/query" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // MakeListHTMLMetaHandler creates a HTTP handler for rendering the list of zettel as HTML. func (wui *WebUI) MakeListHTMLMetaHandler( queryMeta *usecase.Query, tagZettel *usecase.TagZettel, roleZettel *usecase.RoleZettel, reIndex *usecase.ReIndex) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { urlQuery := r.URL.Query() if wui.handleTagZettel(w, r, tagZettel, urlQuery) || wui.handleRoleZettel(w, r, roleZettel, urlQuery) { return } q := adapter.GetQuery(urlQuery) q = q.SetDeterministic() ctx := r.Context() metaSeq, err := queryMeta.Run(ctx, q) if err != nil { wui.reportError(ctx, w, err) return } actions, err := adapter.TryReIndex(ctx, q.Actions(), metaSeq, reIndex) if err != nil { wui.reportError(ctx, w, err) return } if len(actions) > 0 { if len(metaSeq) > 0 { for _, act := range actions { if act == api.RedirectAction { ub := wui.NewURLBuilder('h').SetZid(metaSeq[0].Zid.ZettelID()) wui.redirectFound(w, r, ub) return } } } switch actions[0] { case api.AtomAction: wui.renderAtom(w, q, metaSeq) return case api.RSSAction: wui.renderRSS(ctx, w, q, metaSeq) return } } var content, endnotes *sx.Pair numEntries := 0 if bn, cnt := evaluator.QueryAction(ctx, q, metaSeq, wui.rtConfig); bn != nil { enc := wui.getSimpleHTMLEncoder(wui.rtConfig.Get(ctx, nil, api.KeyLang)) content, endnotes, err = enc.BlocksSxn(&ast.BlockSlice{bn}) if err != nil { wui.reportError(ctx, w, err) return } numEntries = cnt } user := server.GetUser(ctx) env, rb := wui.createRenderEnv( ctx, "list", wui.rtConfig.Get(ctx, nil, api.KeyLang), wui.rtConfig.GetSiteName(), user) if q == nil { rb.bindString("heading", sx.MakeString(wui.rtConfig.GetSiteName())) } else { var sb strings.Builder q.PrintHuman(&sb) rb.bindString("heading", sx.MakeString(sb.String())) } rb.bindString("query-value", sx.MakeString(q.String())) if tzl := q.GetMetaValues(api.KeyTags, false); len(tzl) > 0 { sxTzl, sxNoTzl := wui.transformTagZettelList(ctx, tagZettel, tzl) if !sx.IsNil(sxTzl) { rb.bindString("tag-zettel", sxTzl) } if !sx.IsNil(sxNoTzl) && wui.canCreate(ctx, user) { rb.bindString("create-tag-zettel", sxNoTzl) } } if rzl := q.GetMetaValues(api.KeyRole, false); len(rzl) > 0 { sxRzl, sxNoRzl := wui.transformRoleZettelList(ctx, roleZettel, rzl) if !sx.IsNil(sxRzl) { rb.bindString("role-zettel", sxRzl) } if !sx.IsNil(sxNoRzl) && wui.canCreate(ctx, user) { rb.bindString("create-role-zettel", sxNoRzl) } } rb.bindString("content", content) rb.bindString("endnotes", endnotes) rb.bindString("num-entries", sx.Int64(numEntries)) rb.bindString("num-meta", sx.Int64(len(metaSeq))) apiURL := wui.NewURLBuilder('z').AppendQuery(q.String()) seed, found := q.GetSeed() if found { apiURL = apiURL.AppendKVQuery(api.QueryKeySeed, strconv.Itoa(seed)) } else { seed = 0 } if len(metaSeq) > 0 { rb.bindString("plain-url", sx.MakeString(apiURL.String())) rb.bindString("data-url", sx.MakeString(apiURL.AppendKVQuery(api.QueryKeyEncoding, api.EncodingData).String())) if wui.canCreate(ctx, user) { rb.bindString("create-url", sx.MakeString(wui.createNewURL)) rb.bindString("seed", sx.Int64(seed)) } } if rb.err == nil { err = wui.renderSxnTemplate(ctx, w, id.ListTemplateZid, env) } else { err = rb.err } if err != nil { wui.reportError(ctx, w, err) } }) } func (wui *WebUI) transformTagZettelList(ctx context.Context, tagZettel *usecase.TagZettel, tags []string) (withZettel, withoutZettel *sx.Pair) { slices.Reverse(tags) for _, tag := range tags { tag = meta.NormalizeTag(tag) if _, err := tagZettel.Run(ctx, tag); err == nil { |
︙ | ︙ | |||
172 173 174 175 176 177 178 | } } return withZettel, withoutZettel } func (wui *WebUI) prependZettelLink(sxZtl *sx.Pair, name string, u *api.URLBuilder) *sx.Pair { link := sx.MakeList( | | | | | | | | | 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 | } } return withZettel, withoutZettel } func (wui *WebUI) prependZettelLink(sxZtl *sx.Pair, name string, u *api.URLBuilder) *sx.Pair { link := sx.MakeList( shtml.SymA, sx.MakeList( sxhtml.SymAttr, sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String())), ), sx.MakeString(name), ) if sxZtl != nil { sxZtl = sxZtl.Cons(sx.MakeString(", ")) } return sxZtl.Cons(link) } func (wui *WebUI) renderRSS(ctx context.Context, w http.ResponseWriter, q *query.Query, ml []*meta.Meta) { var rssConfig rss.Configuration rssConfig.Setup(ctx, wui.rtConfig) if actions := q.Actions(); len(actions) > 2 && actions[1] == api.TitleAction { rssConfig.Title = strings.Join(actions[2:], " ") } data := rssConfig.Marshal(q, ml) adapter.PrepareHeader(w, rss.ContentType) w.WriteHeader(http.StatusOK) var err error if _, err = io.WriteString(w, xml.Header); err == nil { _, err = w.Write(data) } if err != nil { wui.log.Error().Err(err).Msg("unable to write RSS data") } } func (wui *WebUI) renderAtom(w http.ResponseWriter, q *query.Query, ml []*meta.Meta) { var atomConfig atom.Configuration atomConfig.Setup(wui.rtConfig) if actions := q.Actions(); len(actions) > 2 && actions[1] == api.TitleAction { atomConfig.Title = strings.Join(actions[2:], " ") } data := atomConfig.Marshal(q, ml) adapter.PrepareHeader(w, atom.ContentType) w.WriteHeader(http.StatusOK) var err error |
︙ | ︙ | |||
234 235 236 237 238 239 240 | } ctx := r.Context() z, err := tagZettel.Run(ctx, tag) if err != nil { wui.reportError(ctx, w, err) return true } | | | | 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 | } ctx := r.Context() z, err := tagZettel.Run(ctx, tag) if err != nil { wui.reportError(ctx, w, err) return true } wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(z.Meta.Zid.ZettelID())) return true } func (wui *WebUI) handleRoleZettel(w http.ResponseWriter, r *http.Request, roleZettel *usecase.RoleZettel, vals url.Values) bool { role := vals.Get(api.QueryKeyRole) if role == "" { return false } ctx := r.Context() z, err := roleZettel.Run(ctx, role) if err != nil { wui.reportError(ctx, w, err) return true } wui.redirectFound(w, r, wui.NewURLBuilder('h').SetZid(z.Meta.Zid.ZettelID())) return true } |
Changes to web/adapter/webui/login.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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 webui import ( "context" "net/http" | > > > | | | | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "context" "net/http" "t73f.de/r/sx" "t73f.de/r/zsc/api" "zettelstore.de/z/auth" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/zettel/id" ) // MakeGetLoginOutHandler creates a new HTTP handler to display the HTML login view, // or to execute a logout. func (wui *WebUI) MakeGetLoginOutHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() if query.Has("logout") { wui.clearToken(r.Context(), w) wui.redirectFound(w, r, wui.NewURLBuilder('/')) return } wui.renderLoginForm(wui.clearToken(r.Context(), w), w, false) }) } func (wui *WebUI) renderLoginForm(ctx context.Context, w http.ResponseWriter, retry bool) { env, rb := wui.createRenderEnv(ctx, "login", wui.rtConfig.Get(ctx, nil, api.KeyLang), "Login", nil) rb.bindString("retry", sx.MakeBoolean(retry)) if rb.err == nil { rb.err = wui.renderSxnTemplate(ctx, w, id.LoginTemplateZid, env) } if err := rb.err; err != nil { wui.reportError(ctx, w, err) } } // MakePostLoginHandler creates a new HTTP handler to authenticate the given user. func (wui *WebUI) MakePostLoginHandler(ucAuth *usecase.Authenticate) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !wui.authz.WithAuth() { wui.redirectFound(w, r, wui.NewURLBuilder('/')) return } ctx := r.Context() ident, cred, ok := adapter.GetCredentialsViaForm(r) if !ok { |
︙ | ︙ | |||
68 69 70 71 72 73 74 | if token == nil { wui.renderLoginForm(wui.clearToken(ctx, w), w, true) return } wui.setToken(w, token) wui.redirectFound(w, r, wui.NewURLBuilder('/')) | | | 71 72 73 74 75 76 77 78 79 | if token == nil { wui.renderLoginForm(wui.clearToken(ctx, w), w, true) return } wui.setToken(w, token) wui.redirectFound(w, r, wui.NewURLBuilder('/')) }) } |
Added web/adapter/webui/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 | //----------------------------------------------------------------------------- // Copyright (c) 2024-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2024-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "strings" "unicode" "unicode/utf8" ) func capitalizeMetaKey(key string) string { var sb strings.Builder for i, word := range strings.Split(key, "-") { if i > 0 { sb.WriteByte(' ') } if newWord, isSpecial := specialWords[word]; isSpecial { if newWord == "" { sb.WriteString(strings.ToTitle(word)) } else { sb.WriteString(newWord) } continue } r, size := utf8.DecodeRuneInString(word) if r == utf8.RuneError { sb.WriteString(word) continue } sb.WriteRune(unicode.ToTitle(r)) sb.WriteString(word[size:]) } return sb.String() } var specialWords = map[string]string{ "css": "", "html": "", "github": "GitHub", "http": "", "https": "", "pdf": "", "svg": "", "url": "", } |
Added web/adapter/webui/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 | //----------------------------------------------------------------------------- // Copyright (c) 2024-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2024-present Detlef Stern //----------------------------------------------------------------------------- package webui import "testing" func TestCapitalizeMetaKey(t *testing.T) { var testcases = []struct { key string exp string }{ {"", ""}, {"alt-url", "Alt URL"}, {"author", "Author"}, {"back", "Back"}, {"box-number", "Box Number"}, {"cite-key", "Cite Key"}, {"fedi-url", "Fedi URL"}, {"github-url", "GitHub URL"}, {"hshn-bib", "Hshn Bib"}, {"job-url", "Job URL"}, {"new-user-id", "New User Id"}, {"origin-zid", "Origin Zid"}, {"site-url", "Site URL"}, } for _, tc := range testcases { t.Run(tc.key, func(t *testing.T) { got := capitalizeMetaKey(tc.key) if got != tc.exp { t.Errorf("capitalize(%q) == %q, but got %q", tc.key, tc.exp, got) } }) } } |
Deleted web/adapter/webui/rename_zettel.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to web/adapter/webui/response.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 webui import ( "net/http" | > > > | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "net/http" "t73f.de/r/zsc/api" ) func (wui *WebUI) redirectFound(w http.ResponseWriter, r *http.Request, ub *api.URLBuilder) { us := ub.String() wui.log.Debug().Str("uri", us).Msg("redirect") http.Redirect(w, r, us, http.StatusFound) } |
Changes to web/adapter/webui/sxn_code.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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 webui import ( "context" "fmt" "io" | > > > | | | | | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "context" "fmt" "io" "t73f.de/r/sx/sxeval" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func (wui *WebUI) loadAllSxnCodeZettel(ctx context.Context) (id.Digraph, *sxeval.Binding, error) { // getMeta MUST currently use GetZettel, because GetMeta just uses the // Index, which might not be current. getMeta := func(ctx context.Context, zid id.Zid) (*meta.Meta, error) { z, err := wui.box.GetZettel(ctx, zid) if err != nil { return nil, err } return z.Meta, nil } dg := buildSxnCodeDigraph(ctx, id.StartSxnZid, getMeta) if dg == nil { return nil, wui.rootBinding, nil } dg = dg.AddVertex(id.BaseSxnZid).AddEdge(id.StartSxnZid, id.BaseSxnZid) dg = dg.AddVertex(id.PreludeSxnZid).AddEdge(id.BaseSxnZid, id.PreludeSxnZid) dg = dg.TransitiveClosure(id.StartSxnZid) if zid, isDAG := dg.IsDAG(); !isDAG { return nil, nil, fmt.Errorf("zettel %v is part of a dependency cycle", zid) } bind := wui.rootBinding.MakeChildBinding("zettel", 128) for _, zid := range dg.SortReverse() { if err := wui.loadSxnCodeZettel(ctx, zid, bind); err != nil { return nil, nil, err } } return dg, bind, nil } type getMetaFunc func(context.Context, id.Zid) (*meta.Meta, error) func buildSxnCodeDigraph(ctx context.Context, startZid id.Zid, getMeta getMetaFunc) id.Digraph { m, err := getMeta(ctx, startZid) if err != nil { return nil } var marked *id.Set stack := []*meta.Meta{m} dg := id.Digraph(nil).AddVertex(startZid) for pos := len(stack) - 1; pos >= 0; pos = len(stack) - 1 { curr := stack[pos] stack = stack[:pos] if marked.Contains(curr.Zid) { continue |
︙ | ︙ | |||
81 82 83 84 85 86 87 | } } } } return dg } | | > | | | 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 | } } } } return dg } func (wui *WebUI) loadSxnCodeZettel(ctx context.Context, zid id.Zid, bind *sxeval.Binding) error { rdr, err := wui.makeZettelReader(ctx, zid) if err != nil { return err } env := sxeval.MakeExecutionEnvironment(bind) for { form, err2 := rdr.Read() if err2 != nil { if err2 == io.EOF { return nil } return err2 } wui.log.Debug().Zid(zid).Str("form", form.String()).Msg("Loaded sxn code") if _, err2 = env.Eval(form); err2 != nil { return err2 } } } |
Changes to web/adapter/webui/template.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 webui import ( "bytes" "context" "fmt" "net/http" "net/url" | > > > < | | | | > | > | | < | | > > | | | | | | | | | | | | | | | | | < > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < | | | < < < < < | | | < < | | < | < < < | > | | | | | | | < < < | | > | > > > | | | < < | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package webui import ( "bytes" "context" "fmt" "net/http" "net/url" "t73f.de/r/sx" "t73f.de/r/sx/sxbuiltins" "t73f.de/r/sx/sxeval" "t73f.de/r/sx/sxreader" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/shtml" "zettelstore.de/z/box" "zettelstore.de/z/collect" "zettelstore.de/z/config" "zettelstore.de/z/parser" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) func (wui *WebUI) createRenderBinding() *sxeval.Binding { root := sxeval.MakeRootBinding(len(specials) + len(builtins) + 3) for _, syntax := range specials { root.BindSpecial(syntax) } for _, b := range builtins { root.BindBuiltin(b) } _ = root.Bind(sx.MakeSymbol("NIL"), sx.Nil()) _ = root.Bind(sx.MakeSymbol("T"), sx.MakeSymbol("T")) root.BindBuiltin(&sxeval.Builtin{ Name: "url-to-html", MinArity: 1, MaxArity: 1, TestPure: sxeval.AssertPure, Fn1: func(_ *sxeval.Environment, arg sx.Object) (sx.Object, error) { text, err := sxbuiltins.GetString(arg, 0) if err != nil { return nil, err } return wui.url2html(text), nil }, }) root.BindBuiltin(&sxeval.Builtin{ Name: "zid-content-path", MinArity: 1, MaxArity: 1, TestPure: sxeval.AssertPure, Fn1: func(_ *sxeval.Environment, arg sx.Object) (sx.Object, error) { s, err := sxbuiltins.GetString(arg, 0) if err != nil { return nil, err } zid, err := id.Parse(s.GetValue()) if err != nil { return nil, fmt.Errorf("parsing zettel identifier %q: %w", s.GetValue(), err) } ub := wui.NewURLBuilder('z').SetZid(zid.ZettelID()) return sx.MakeString(ub.String()), nil }, }) root.BindBuiltin(&sxeval.Builtin{ Name: "query->url", MinArity: 1, MaxArity: 1, TestPure: sxeval.AssertPure, Fn1: func(_ *sxeval.Environment, arg sx.Object) (sx.Object, error) { qs, err := sxbuiltins.GetString(arg, 0) if err != nil { return nil, err } u := wui.NewURLBuilder('h').AppendQuery(qs.GetValue()) return sx.MakeString(u.String()), nil }, }) root.Freeze() return root } var ( specials = []*sxeval.Special{ &sxbuiltins.QuoteS, &sxbuiltins.QuasiquoteS, // quote, quasiquote &sxbuiltins.UnquoteS, &sxbuiltins.UnquoteSplicingS, // unquote, unquote-splicing &sxbuiltins.DefVarS, // defvar &sxbuiltins.DefunS, &sxbuiltins.LambdaS, // defun, lambda &sxbuiltins.SetXS, // set! &sxbuiltins.IfS, // if &sxbuiltins.BeginS, // begin &sxbuiltins.DefMacroS, // defmacro &sxbuiltins.LetS, // let } builtins = []*sxeval.Builtin{ &sxbuiltins.Equal, // = &sxbuiltins.NumGreater, // > &sxbuiltins.NullP, // null? &sxbuiltins.PairP, // pair? &sxbuiltins.Car, &sxbuiltins.Cdr, // car, cdr &sxbuiltins.Caar, &sxbuiltins.Cadr, &sxbuiltins.Cdar, &sxbuiltins.Cddr, &sxbuiltins.Caaar, &sxbuiltins.Caadr, &sxbuiltins.Cadar, &sxbuiltins.Caddr, &sxbuiltins.Cdaar, &sxbuiltins.Cdadr, &sxbuiltins.Cddar, &sxbuiltins.Cdddr, &sxbuiltins.List, // list &sxbuiltins.Append, // append &sxbuiltins.Assoc, // assoc &sxbuiltins.Map, // map &sxbuiltins.Apply, // apply &sxbuiltins.Concat, // concat &sxbuiltins.BoundP, // bound? &sxbuiltins.Defined, // defined? &sxbuiltins.CurrentBinding, // current-binding &sxbuiltins.BindingLookup, // binding-lookup } ) func (wui *WebUI) url2html(text sx.String) sx.Object { if u, errURL := url.Parse(text.GetValue()); errURL == nil { if us := u.String(); us != "" { return sx.MakeList( shtml.SymA, sx.MakeList( sxhtml.SymAttr, sx.Cons(shtml.SymAttrHref, sx.MakeString(us)), sx.Cons(shtml.SymAttrTarget, sx.MakeString("_blank")), sx.Cons(shtml.SymAttrRel, sx.MakeString("external noreferrer")), ), text) } } return text } func (wui *WebUI) getParentEnv(ctx context.Context) (*sxeval.Binding, error) { wui.mxZettelBinding.Lock() defer wui.mxZettelBinding.Unlock() if parentEnv := wui.zettelBinding; parentEnv != nil { return parentEnv, nil } dag, zettelEnv, err := wui.loadAllSxnCodeZettel(ctx) if err != nil { wui.log.Error().Err(err).Msg("loading zettel sxn") return nil, err } wui.dag = dag wui.zettelBinding = zettelEnv return zettelEnv, nil } // createRenderEnv creates a new environment and populates it with all relevant data for the base template. func (wui *WebUI) createRenderEnv(ctx context.Context, name, lang, title string, user *meta.Meta) (*sxeval.Binding, renderBinder) { userIsValid, userZettelURL, userIdent := wui.getUserRenderData(user) parentEnv, err := wui.getParentEnv(ctx) bind := parentEnv.MakeChildBinding(name, 128) rb := makeRenderBinder(bind, err) rb.bindString("lang", sx.MakeString(lang)) rb.bindString("css-base-url", sx.MakeString(wui.cssBaseURL)) rb.bindString("css-user-url", sx.MakeString(wui.cssUserURL)) rb.bindString("title", sx.MakeString(title)) rb.bindString("home-url", sx.MakeString(wui.homeURL)) rb.bindString("with-auth", sx.MakeBoolean(wui.withAuth)) rb.bindString("user-is-valid", sx.MakeBoolean(userIsValid)) rb.bindString("user-zettel-url", sx.MakeString(userZettelURL)) rb.bindString("user-ident", sx.MakeString(userIdent)) rb.bindString("login-url", sx.MakeString(wui.loginURL)) rb.bindString("logout-url", sx.MakeString(wui.logoutURL)) rb.bindString("list-zettel-url", sx.MakeString(wui.listZettelURL)) rb.bindString("list-roles-url", sx.MakeString(wui.listRolesURL)) rb.bindString("list-tags-url", sx.MakeString(wui.listTagsURL)) if wui.canRefresh(user) { rb.bindString("refresh-url", sx.MakeString(wui.refreshURL)) } rb.bindString("new-zettel-links", wui.fetchNewTemplatesSxn(ctx, user)) rb.bindString("search-url", sx.MakeString(wui.searchURL)) rb.bindString("query-key-query", sx.MakeString(api.QueryKeyQuery)) rb.bindString("query-key-seed", sx.MakeString(api.QueryKeySeed)) rb.bindString("FOOTER", wui.calculateFooterSxn(ctx)) // TODO: use real footer rb.bindString("debug-mode", sx.MakeBoolean(wui.debug)) rb.bindSymbol(symMetaHeader, sx.Nil()) rb.bindSymbol(symDetail, sx.Nil()) return bind, rb } func (wui *WebUI) getUserRenderData(user *meta.Meta) (bool, string, string) { if user == nil { return false, "", "" } return true, wui.NewURLBuilder('h').SetZid(user.Zid.ZettelID()).String(), user.GetDefault(api.KeyUserID, "") } type renderBinder struct { err error binding *sxeval.Binding } func makeRenderBinder(bind *sxeval.Binding, err error) renderBinder { return renderBinder{binding: bind, err: err} } func (rb *renderBinder) bindString(key string, obj sx.Object) { if rb.err == nil { rb.err = rb.binding.Bind(sx.MakeSymbol(key), obj) } } func (rb *renderBinder) bindSymbol(sym *sx.Symbol, obj sx.Object) { if rb.err == nil { rb.err = rb.binding.Bind(sym, obj) } } func (rb *renderBinder) bindKeyValue(key string, value string) { rb.bindString("meta-"+key, sx.MakeString(value)) if kt := meta.Type(key); kt.IsSet { rb.bindString("set-meta-"+key, makeStringList(meta.ListFromValue(value))) } } func (rb *renderBinder) rebindResolved(key, defKey string) { if rb.err == nil { if obj, found := rb.binding.Resolve(sx.MakeSymbol(key)); found { rb.bindString(defKey, obj) } } } func (wui *WebUI) bindCommonZettelData(ctx context.Context, rb *renderBinder, user, m *meta.Meta, content *zettel.Content) { strZid := m.Zid.String() apiZid := api.ZettelID(strZid) newURLBuilder := wui.NewURLBuilder rb.bindString("zid", sx.MakeString(strZid)) rb.bindString("zid-n", sx.MakeString(m.ZidN.String())) rb.bindString("web-url", sx.MakeString(newURLBuilder('h').SetZid(apiZid).String())) if content != nil && wui.canWrite(ctx, user, m, *content) { rb.bindString("edit-url", sx.MakeString(newURLBuilder('e').SetZid(apiZid).String())) } rb.bindString("info-url", sx.MakeString(newURLBuilder('i').SetZid(apiZid).String())) if wui.canCreate(ctx, user) { if content != nil && !content.IsBinary() { rb.bindString("copy-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionCopy).String())) } rb.bindString("version-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionVersion).String())) rb.bindString("child-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionChild).String())) rb.bindString("folge-url", sx.MakeString(newURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionFolge).String())) } if wui.canDelete(ctx, user, m) { rb.bindString("delete-url", sx.MakeString(newURLBuilder('d').SetZid(apiZid).String())) } if val, found := m.Get(api.KeyUselessFiles); found { rb.bindString("useless", sx.Cons(sx.MakeString(val), nil)) } queryContext := strZid + " " + api.ContextDirective rb.bindString("context-url", sx.MakeString(newURLBuilder('h').AppendQuery(queryContext).String())) queryContext += " " + api.FullDirective rb.bindString("context-full-url", sx.MakeString(newURLBuilder('h').AppendQuery(queryContext).String())) if wui.canRefresh(user) { rb.bindString("reindex-url", sx.MakeString(newURLBuilder('h').AppendQuery( strZid+" "+api.IdentDirective+api.ActionSeparator+api.ReIndexAction).String())) } // Ensure to have title, role, tags, and syntax included as "meta-*" rb.bindKeyValue(api.KeyTitle, m.GetDefault(api.KeyTitle, "")) rb.bindKeyValue(api.KeyRole, m.GetDefault(api.KeyRole, "")) rb.bindKeyValue(api.KeyTags, m.GetDefault(api.KeyTags, "")) rb.bindKeyValue(api.KeySyntax, m.GetDefault(api.KeySyntax, meta.DefaultSyntax)) var metaPairs sx.ListBuilder for _, p := range m.ComputedPairs() { key, value := p.Key, p.Value metaPairs.Add(sx.Cons(sx.MakeString(key), sx.MakeString(value))) rb.bindKeyValue(key, value) } rb.bindString("metapairs", metaPairs.List()) } func (wui *WebUI) fetchNewTemplatesSxn(ctx context.Context, user *meta.Meta) (lst *sx.Pair) { if !wui.canCreate(ctx, user) { return nil } ctx = box.NoEnrichContext(ctx) |
︙ | ︙ | |||
310 311 312 313 314 315 316 | z, err2 := wui.box.GetZettel(ctx, zid) if err2 != nil { continue } if !wui.policy.CanRead(user, z.Meta) { continue } | | | | 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | z, err2 := wui.box.GetZettel(ctx, zid) if err2 != nil { continue } if !wui.policy.CanRead(user, z.Meta) { continue } text := sx.MakeString(parser.NormalizedSpacedText(z.Meta.GetTitle())) link := sx.MakeString(wui.NewURLBuilder('c').SetZid(zid.ZettelID()). AppendKVQuery(queryKeyAction, valueActionNew).String()) lst = lst.Cons(sx.Cons(text, link)) } return lst } func (wui *WebUI) calculateFooterSxn(ctx context.Context) *sx.Pair { |
︙ | ︙ | |||
333 334 335 336 337 338 339 | return content } } } return nil } | | > | | | | | > | | | | | | | > > | | > | > | | | 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 424 425 426 427 428 429 430 | return content } } } return nil } func (wui *WebUI) getSxnTemplate(ctx context.Context, zid id.Zid, bind *sxeval.Binding) (sxeval.Expr, error) { if t := wui.getSxnCache(zid); t != nil { return t, nil } reader, err := wui.makeZettelReader(ctx, zid) if err != nil { return nil, err } objs, err := reader.ReadAll() if err != nil { wui.log.Error().Err(err).Zid(zid).Msg("reading sxn template") return nil, err } if len(objs) != 1 { return nil, fmt.Errorf("expected 1 expression in template, but got %d", len(objs)) } env := sxeval.MakeExecutionEnvironment(bind) t, err := env.Compile(objs[0]) if err != nil { return nil, err } wui.setSxnCache(zid, t) return t, nil } func (wui *WebUI) makeZettelReader(ctx context.Context, zid id.Zid) (*sxreader.Reader, error) { ztl, err := wui.box.GetZettel(ctx, zid) if err != nil { return nil, err } reader := sxreader.MakeReader(bytes.NewReader(ztl.Content.AsBytes())) return reader, nil } func (wui *WebUI) evalSxnTemplate(ctx context.Context, zid id.Zid, bind *sxeval.Binding) (sx.Object, error) { templateExpr, err := wui.getSxnTemplate(ctx, zid, bind) if err != nil { return nil, err } env := sxeval.MakeExecutionEnvironment(bind) return env.Run(templateExpr) } func (wui *WebUI) renderSxnTemplate(ctx context.Context, w http.ResponseWriter, templateID id.Zid, bind *sxeval.Binding) error { return wui.renderSxnTemplateStatus(ctx, w, http.StatusOK, templateID, bind) } func (wui *WebUI) renderSxnTemplateStatus(ctx context.Context, w http.ResponseWriter, code int, templateID id.Zid, bind *sxeval.Binding) error { detailObj, err := wui.evalSxnTemplate(ctx, templateID, bind) if err != nil { return err } bind.Bind(symDetail, detailObj) pageObj, err := wui.evalSxnTemplate(ctx, id.BaseTemplateZid, bind) if err != nil { return err } if msg := wui.log.Debug(); msg != nil { // pageObj.String() can be expensive to calculate. msg.Str("page", pageObj.String()).Msg("render") } gen := sxhtml.NewGenerator().SetNewline() var sb bytes.Buffer _, err = gen.WriteHTML(&sb, pageObj) if err != nil { return err } wui.prepareAndWriteHeader(w, code) if _, err = w.Write(sb.Bytes()); err != nil { wui.log.Error().Err(err).Msg("Unable to write HTML via template") } return nil // No error reporting, since we do not know what happended during write to client. } func (wui *WebUI) reportError(ctx context.Context, w http.ResponseWriter, err error) { ctx = context.WithoutCancel(ctx) // Ignore any cancel / timeouts to write an error message. code, text := adapter.CodeMessageFromError(err) if code == http.StatusInternalServerError { wui.log.Error().Msg(err.Error()) } else { wui.log.Debug().Err(err).Msg("reportError") } user := server.GetUser(ctx) env, rb := wui.createRenderEnv(ctx, "error", api.ValueLangEN, "Error", user) rb.bindString("heading", sx.MakeString(http.StatusText(code))) rb.bindString("message", sx.MakeString(text)) if rb.err == nil { rb.err = wui.renderSxnTemplateStatus(ctx, w, code, id.ErrorTemplateZid, env) } errSx := rb.err if errSx == nil { return } |
︙ | ︙ | |||
446 447 448 449 450 451 452 | func makeStringList(sl []string) *sx.Pair { if len(sl) == 0 { return nil } result := sx.Nil() for i := len(sl) - 1; i >= 0; i-- { | | | 446 447 448 449 450 451 452 453 454 455 456 | func makeStringList(sl []string) *sx.Pair { if len(sl) == 0 { return nil } result := sx.Nil() for i := len(sl) - 1; i >= 0; i-- { result = result.Cons(sx.MakeString(sl[i])) } return result } |
Changes to web/adapter/webui/webui.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //----------------------------------------------------------------------------- // 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 webui provides web-UI handlers for web requests. package webui import ( "context" "net/http" "sync" "time" | > > > | | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package webui provides web-UI handlers for web requests. package webui import ( "context" "net/http" "sync" "time" "t73f.de/r/sx" "t73f.de/r/sx/sxeval" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "zettelstore.de/z/auth" "zettelstore.de/z/box" "zettelstore.de/z/config" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" |
︙ | ︙ | |||
60 61 62 63 64 65 66 | refreshURL string withAuth bool loginURL string logoutURL string searchURL string createNewURL string | < | | | | | < < < < < < < < < | 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 | refreshURL string withAuth bool loginURL string logoutURL string searchURL string createNewURL string rootBinding *sxeval.Binding mxZettelBinding sync.Mutex zettelBinding *sxeval.Binding dag id.Digraph genHTML *sxhtml.Generator } // webuiBox contains all box methods that are needed for WebUI operation. // // Note: these function must not do auth checking. type webuiBox interface { CanCreateZettel(context.Context) bool GetZettel(context.Context, id.Zid) (zettel.Zettel, error) GetMeta(context.Context, id.Zid) (*meta.Meta, error) CanUpdateZettel(context.Context, zettel.Zettel) bool CanDeleteZettel(context.Context, id.Zid) bool } // New creates a new WebUI struct. func New(log *logger.Logger, ab server.AuthBuilder, authz auth.AuthzManager, rtConfig config.Config, token auth.TokenManager, mgr box.Manager, pol auth.Policy, evalZettel *usecase.Evaluate) *WebUI { loginoutBase := ab.NewURLBuilder('i') wui := &WebUI{ log: log, debug: kernel.Main.GetConfig(kernel.CoreService, kernel.CoreDebug).(bool), ab: ab, rtConfig: rtConfig, authz: authz, |
︙ | ︙ | |||
121 122 123 124 125 126 127 | refreshURL: ab.NewURLBuilder('g').AppendKVQuery("_c", "r").String(), withAuth: authz.WithAuth(), loginURL: loginoutBase.String(), logoutURL: loginoutBase.AppendKVQuery("logout", "").String(), searchURL: ab.NewURLBuilder('h').String(), createNewURL: ab.NewURLBuilder('c').String(), | < | | < < < < < < < | > > > > > | | | | 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 | refreshURL: ab.NewURLBuilder('g').AppendKVQuery("_c", "r").String(), withAuth: authz.WithAuth(), loginURL: loginoutBase.String(), logoutURL: loginoutBase.AppendKVQuery("logout", "").String(), searchURL: ab.NewURLBuilder('h').String(), createNewURL: ab.NewURLBuilder('c').String(), zettelBinding: nil, genHTML: sxhtml.NewGenerator().SetNewline(), } wui.rootBinding = wui.createRenderBinding() wui.observe(box.UpdateInfo{Box: mgr, Reason: box.OnReload, Zid: id.Invalid}) mgr.RegisterObserver(wui.observe) return wui } var ( symDetail = sx.MakeSymbol("DETAIL") symMetaHeader = sx.MakeSymbol("META-HEADER") ) func (wui *WebUI) observe(ci box.UpdateInfo) { wui.mxCache.Lock() if ci.Reason == box.OnReload { clear(wui.templateCache) } else { delete(wui.templateCache, ci.Zid) } wui.mxCache.Unlock() wui.mxZettelBinding.Lock() if ci.Reason == box.OnReload || wui.dag.HasVertex(ci.Zid) { wui.zettelBinding = nil wui.dag = nil } wui.mxZettelBinding.Unlock() } func (wui *WebUI) setSxnCache(zid id.Zid, expr sxeval.Expr) { wui.mxCache.Lock() wui.templateCache[zid] = expr wui.mxCache.Unlock() } |
︙ | ︙ | |||
181 182 183 184 185 186 187 | func (wui *WebUI) canWrite( ctx context.Context, user, meta *meta.Meta, content zettel.Content) bool { return wui.policy.CanWrite(user, meta, meta) && wui.box.CanUpdateZettel(ctx, zettel.Zettel{Meta: meta, Content: content}) } | < < < < | 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | func (wui *WebUI) canWrite( ctx context.Context, user, meta *meta.Meta, content zettel.Content) bool { return wui.policy.CanWrite(user, meta, meta) && wui.box.CanUpdateZettel(ctx, zettel.Zettel{Meta: meta, Content: content}) } func (wui *WebUI) canDelete(ctx context.Context, user, m *meta.Meta) bool { return wui.policy.CanDelete(user, m) && wui.box.CanDeleteZettel(ctx, m.Zid) } func (wui *WebUI) canRefresh(user *meta.Meta) bool { return wui.policy.CanRefresh(user) } |
︙ | ︙ |
Changes to web/content/content.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 content manages content handling within the web package. // It translates syntax values into content types, and vice versa. package content import ( "mime" "net/http" | > > > | | 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) 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- // Package content manages content handling within the web package. // It translates syntax values into content types, and vice versa. package content import ( "mime" "net/http" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel" "zettelstore.de/z/zettel/meta" ) const ( UnknownMIME = "application/octet-stream" mimeGIF = "image/gif" |
︙ | ︙ |
Changes to web/content/content_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 content_test import ( "testing" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package content_test import ( "testing" |
︙ | ︙ |
Changes to web/server/impl/http.go.
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 impl import ( "context" "net" "net/http" "time" ) // Server timeout values const ( shutdownTimeout = 5 * time.Second readTimeout = 5 * time.Second | > > > | | > | | | > | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "context" "net" "net/http" "time" ) // Server timeout values const ( shutdownTimeout = 5 * time.Second readTimeout = 5 * time.Second writeTimeout = 15 * time.Second idleTimeout = 120 * time.Second ) // httpServer is a HTTP server. type httpServer struct { http.Server origHandler http.Handler } // initializeHTTPServer creates a new HTTP server object. func (srv *httpServer) initializeHTTPServer(addr string, handler http.Handler) { if addr == "" { addr = ":http" } srv.Server = http.Server{ Addr: addr, Handler: http.TimeoutHandler(handler, writeTimeout, "Timeout"), // See: https://blog.cloudflare.com/exposing-go-on-the-internet/ ReadTimeout: readTimeout, WriteTimeout: writeTimeout + 200*time.Millisecond, // Give some time to detect timeout and to write an appropriate error message. IdleTimeout: idleTimeout, } srv.origHandler = handler } // SetDebug enables debugging goroutines that are started by the server. // Basically, just the timeout values are reset. This method should be called // before running the server. func (srv *httpServer) SetDebug() { srv.ReadTimeout = 0 srv.WriteTimeout = 0 srv.IdleTimeout = 0 srv.Handler = srv.origHandler } // Run starts the web server, but does not wait for its completion. func (srv *httpServer) Run() error { ln, err := net.Listen("tcp", srv.Addr) if err != nil { return err |
︙ | ︙ |
Changes to web/server/impl/impl.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 impl provides the Zettelstore web service. package impl import ( "context" "net/http" "time" | > > > | | 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) 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package impl provides the Zettelstore web service. package impl import ( "context" "net/http" "time" "t73f.de/r/zsc/api" "zettelstore.de/z/auth" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" "zettelstore.de/z/zettel/meta" ) type myServer struct { |
︙ | ︙ |
Changes to web/server/impl/router.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 impl import ( "io" "net/http" "regexp" "strings" | > > > < < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package impl import ( "io" "net/http" "regexp" "strings" "zettelstore.de/z/auth" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" "zettelstore.de/z/web/server" ) type ( methodHandler [server.MethodLAST]http.Handler routingTable [256]*methodHandler ) var mapMethod = map[string]server.Method{ http.MethodHead: server.MethodHead, http.MethodGet: server.MethodGet, http.MethodPost: server.MethodPost, http.MethodPut: server.MethodPut, http.MethodDelete: server.MethodDelete, } // httpRouter handles all routing for zettelstore. type httpRouter struct { log *logger.Logger urlPrefix string auth auth.TokenManager |
︙ | ︙ | |||
182 183 184 185 186 187 188 | } if len(t) == 0 { rt.log.Debug().Msg("no auth token found in request") // IP already logged: ServeHTTP return r } tokenData, err := rt.auth.CheckToken(t, k) if err != nil { | | | | 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | } if len(t) == 0 { rt.log.Debug().Msg("no auth token found in request") // IP already logged: ServeHTTP return r } tokenData, err := rt.auth.CheckToken(t, k) if err != nil { rt.log.Info().Err(err).HTTPIP(r).Msg("invalid auth token") return r } ctx := r.Context() user, err := rt.ur.GetUser(ctx, tokenData.Zid, tokenData.Ident) if err != nil { rt.log.Info().Zid(tokenData.Zid).Str("ident", tokenData.Ident).Err(err).HTTPIP(r).Msg("auth user not found") return r } return r.WithContext(updateContext(ctx, user, &tokenData)) } func getSessionToken(r *http.Request) []byte { cookie, err := r.Cookie(sessionName) |
︙ | ︙ |
Changes to web/server/server.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 server provides the Zettelstore web service. package server import ( "context" "net/http" "time" | > > > | < | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package server provides the Zettelstore web service. package server import ( "context" "net/http" "time" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) // UserRetriever allows to retrieve user data based on a given zettel identifier. type UserRetriever interface { GetUser(ctx context.Context, zid id.Zid, ident string) (*meta.Meta, error) } // Method enumerates the allowed HTTP methods. type Method uint8 // Values for method type const ( MethodGet Method = iota MethodHead MethodPost MethodPut MethodDelete MethodLAST // must always be the last one ) // Router allows to state routes for various URL paths. type Router interface { Handle(pattern string, handler http.Handler) |
︙ | ︙ |
Changes to www/build.md.
︙ | ︙ | |||
20 21 22 23 24 25 26 | Let's assume, you have created `$HOME/fossils`. 1. Clone the repository: `fossil clone https://zettelstore.de/ $HOME/fossils/zettelstore.fossil`. 1. Create a working directory. Let's assume, you have created `$HOME/zettelstore`. 1. Change into this directory: `cd $HOME/zettelstore`. 1. Open development: `fossil open $HOME/fossils/zettelstore.fossil`. | | | | | | < < < | | > > > > > > > > > > > > | | | | < > | > < | | | | | | < | | 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 | Let's assume, you have created `$HOME/fossils`. 1. Clone the repository: `fossil clone https://zettelstore.de/ $HOME/fossils/zettelstore.fossil`. 1. Create a working directory. Let's assume, you have created `$HOME/zettelstore`. 1. Change into this directory: `cd $HOME/zettelstore`. 1. Open development: `fossil open $HOME/fossils/zettelstore.fossil`. ## Tools to build, test, and manage In the directory `tools` there are some Go files to automate most aspects of building and testing, (hopefully) platform-independent. The build script is called as: ``` go run tools/build/build.go [-v] COMMAND ``` The flag `-v` enables the verbose mode. It outputs all commands called by the tool. Some important `COMMAND`s are: * `build`: builds the software with correct version information and puts it into a freshly created directory `bin`. * `check`: checks the current state of the working directory to be ready for release (or commit). * `version`: prints the current version information. Therefore, the easiest way to build your own version of the Zettelstore software is to execute the command ``` go run tools/build/build.go build ``` In case of errors, please send the output of the verbose execution: ``` go run tools/build/build.go -v build ``` Other tools are: * `go run tools/clean/clean.go` cleans your Go development worspace. * `go run tools/check/check.go` executes all linters and unit tests. If you add the option `-r` linters are more strict, to be used for a release version. * `go run tools/devtools/devtools.go` install all needed software (see above). * `go run tools/htmllint/htmllint.go [URL]` checks all generated HTML of a Zettelstore accessible at the given URL (default: http://localhost:23123). * `go run tools/testapi/testapi.go` tests the API against a running Zettelstore, which is started automatically. ## A note on the use of Fossil Zettelstore is managed by the Fossil version control system. Fossil is an alternative to the ubiquitous Git version control system. However, Go seems to prefer Git and popular platforms that just support Git. Some dependencies of Zettelstore, namely [Zettelstore client](https://t73f.de/r/zsc), [webs](https://t73f.de/r/webs), [sx](https://t73f.de/r/sx), and [sxwebs](https://t73f.de/r/sxwebs) are also managed by Fossil. Depending on your development setup, some error messages might occur. If the error message mentions an environment variable called `GOVCS` you should set it to the value `GOVCS=zettelstore.de:fossil` (alternatively more generous to `GOVCS=*:all`). Since the Go build system is coupled with Git and some special platforms, you allow ot to download a Fossil repository from the host `zettelstore.de`. The build tool set `GOVCS` to the right value, but you may use other `go` commands that try to download a Fossil repository. On some operating systems, namely Termux on Android, an error message might state that an user cannot be determined (`cannot determine user`). In this case, Fossil is allowed to download the repository, but cannot associate it with an user name. Set the environment variable `USER` to any user name, like: `USER=nobody go run tools/build.go build`. |
Changes to www/changes.wiki.
1 2 3 | <title>Change Log</title> <a id="0_17"></a> | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | | | | | | | | > | | | | | | | | | | | | | | | | | | | | | | | | 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 | <title>Change Log</title> <a id="0_19"></a> <h2>Changes for Version 0.19.0 (pending)</h2> * Remove support for renaming zettel, i.e. changing zettel identifier. Was announced as deprecated in version 0.18. (breaking: api, webui) * Zettel content for zettel with ID starting with 0000 is not indexed any more. If you search / query for zettel content, these zettel will not be returned. (breaking: api, webui) * Fix wrong quote translation for markdown encoder. (minor) * Generate <th> in table header (was: <td>). Also applies to SHTML encoder. (minor: webui, api) * External links are now generatd in shtml and html with attribute rel="external" (previously: class="external"). (minor: webui, api) * Show new format zettel identifier in zettel view, info view and delete view. (minor: webui) <a id="0_18"></a> <h2>Changes for Version 0.18.0 (2024-07-11)</h2> * Remove Sx macro <code>defunconst</code>. Use <code>defun</code> instead. (breaking: webui) * The sz encoding of zettel does not make use of <code>(SPACE)</code> elements any more. Instead, space characters are encoded within the <code>(TEXT "...")</code> element. This might affect any client that works with the sz encoding to produce some output. (breaking) * Format of zettel identifier will be changed in the future to a new format, instead of the current timestamp-based format. The usage of zettel identifier that are before 1970-01-01T00:00:00 is not allowed any more (with the exception of predefined identifier) (deprecation) * Due to the planned format of zettel identifier, the “rename” operation is deprecated. It will be removed in version 0.19 or later. If you have a significant use case for the rename operation, please contact the maintainer immediate. (deprecation) * New zettel are now created with the permission for others to read/write them. This is important especially for Unix-like systems. If you want the previous behaviour, set <code>umask</code> accordingly, for example <code>umask 066</code>. (major: dirbox) * Add expert-mode zettel “Zettelstore Warnings” to help identifying zettel to upgrade for future migration to planned new zettel identifier format. (minor: webui) * Add expert-mode zettel “Zettelstore Identifier Mapping” to show a possible mapping from the old identifier format to the new one. This should help users to possibly rename some zettel for a metter mapping. (minor: webui) * Add metadata key <code>created-missing</code> to list zettel without stored metadata key <code>created</code>. Needed for migration to planned new zettelstore identifier format, which is not based on timestamp of zettel creation date. (minor) * Add zettel “Zettelstore Application Directory”, which contains identifier for application specific zettel. Needed for planned new identifier format. (minor: webui) * Update Sx prelude: make macros more robust / more general. This might break your code in the future. (minor: webui) * Add computed expert-mode zettel “Zettelstore Memory” with zettel identifier <code>00000000000008</code>. It shows some statistics about memory usage. (minor: webui) * Add computed expert-mode zettel “Zettelstore Sx Engine” with zettel identifier <code>00000000000009</code>. It shows some statistics about the internal Sx engine. (Currently only the number of used symbols, but this will change in the future.) (minor: webui) * Zettelstore client is now Go package t73f.de/r/zsc. (minor) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_17"></a> <h2>Changes for Version 0.17.0 (2024-03-04)</h2> * Context search operates only on explicit references. Add the directive <code>FULL</code> to follow zettel tags additionally. (breaking) * Context cost calculation has been changed. Prepare to retrieve different result. (breaking) * Remove metadata type WordSet. It was never implemented completely, and nobody complained about this. (breaking) * Remove logging level “sense”, “warn”, “fatal”, and “panic”. (breaking) * Add query action <code>REDIRECT</code> which redirects to zettel that is the first in the query result list. (minor: api, webui) * Add link to <code>CONTEXT FULL</code> in the zettel info page. (minor: webui) * When generating HTML code to query set based metadata (esp. tags), also generate a query that matches all values. (minor: webui) * Show all metadata with key ending “-url” on zettel view. (minor: webui) * Make WebUI form elements a little bit more accessible by using HTML <code>search</code> tag and <code>inputmode</code> attribute. (minor: webui) * Add UI action for role zettel, similar to tag zettel. Obviously forgotten in release 0.16.0, but thanks to the bug fix v0.16.1 detected. (minor: webui) * If an action, which is written in uppercase letters, results in an empty list, the list of selected zettel is returned instead. This allows some backward compatibility if a new action is introduced. (minor) * Only when query list is not empty, allow to show data and plain encoding, an optionally show the “Save As Zettel” button. (minor: webui) * If query list is greater than three elements, show the number of elements at bottom (before other encodings). (minor: webui) * Zettel with syntax “sxn” are pretty-printed during evaluation. This allows to retrieve parsed zettel content, which checked for syntax, but is not pretty-printed. (minor) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_16"></a> <h2>Changes for Version 0.16.1 (2023-12-28)</h2> * Fix some Sxn definitions to allow role-based UI customizations. (minor: webui) <h2>Changes for Version 0.16.0 (2023-11-30)</h2> * Sx function <code>define</code> is removed, as announced for version 0.15.0. Use <code>defvar</code> (to define variables) or <code>defun</code> (to define functions) instead. In addition <code>defunconst</code> defines a constant function, which ensures a fixed binding of its name to its function body (performance optimization). (breaking: webui) * Allow to determine a role zettel for a given role. (major: api, webui) * Present user the option to create a (missing) role zettel (in list view). Results in a new predefined zettel with identifier 00000000090004, which is a template for new role zettel. (minor: webui) * Timestamp values can be abbreviated by omitting most of its components. Previously, such values that are not in the format YYYYMMDDhhmmss were ignored. Now the following formats are also allowed: YYYY, YYYYMM, YYYYMMDD, YYYYMMDDhh, YYYYMMDDhhmm. Querying and sorting work accordingly. Previously, only a sequences of zeroes were appended, resulting in illegal timestamps, e.g. for YYYY or YYYYMM. (minor) * SHTML encoder fixed w.r.t inline quoting. Previously, an <q> tag was used, which is inappropriate. Restored smart quotes from version 0.3, but with new SxHTML infrastructure. This affect the html encoder and the WebUI too. Now, an empty quote should not result in a warning by HTML linters. (minor: api, webui) * Add new zettelmarkup inline formatting: <code>##Text##</code> will mark / highlight the given Text. It is typically used to highlight some text, which is important for you, but not for the original author. When rendered as HTML, the <mark> tag is used. (minor: zettelmarkup) * Add configuration keys to show, not to show, or show the closed list of referencing zettel in the web user interface. You can set these configurations system-wide, per user, or per zettel. Often it is used to ensure a “clean” home zettel. Affects the list of incoming / back links, folge zettel, subordinate zettel, and successor zettel. (minor: webui) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_15"></a> <h2>Changes for Version 0.15.0 (2023-10-26)</h2> * Sx function <code>define</code> is now deprecated. It will be removed in version 0.16. Use <code>defvar</code> or <code>defun</code> instead. Otherwise the WebUI will not work in version 0.16. (major: webui, deprecated) * Zettel can be re-indexed via WebUI or API query action <code>REINDEX</code>. The info page of a zettel contains a link to re-index the zettel. In a query transclusion, this action is ignored. (major: api, webui). * Allow to determine a tag zettel for a given tag. (major: api, webui) * Present user the option to create a (missing) tag zettel (in list view). Results in a new predefined zettel with identifier 00000000090003, which is a template for new tag zettel. (minor: webui) * ZIP file with manual now contains a zettel 00001000000000 that contains its build date (metadata key <code>created</code>) and version (in the zettel content) (minor) * If an error page cannot be created due to template errors (or similar), a plain text error page is delivered instead. It shows the original error and the error that occurred during rendering the original error page. (minor: webui) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_14"></a> <h2>Changes for Version 0.14.0 (2023-09-22)</h2> * Remove support for JSON. This was marked deprecated in version 0.12.0. Use the <code>data</code> encoding instead, a form of symbolic expressions. (breaking: api; minor: webui) * Remove deprecated syntax for a context list: <code>CONTEXT zid</code>. Use <code>zid CONTEXT</code> instead. It was deprecated in version 0.13.0. (breaking: api, webui, zettelmarkup) * Replace CSS-role-map mechanism with a more general Sx-based one: user specific code may generates parts of resulting HTML document. (breaking: webui) * Allow meta-tags, i.e. zettel for a specific tag. Meta-tags have the tag name as a title and specify the role "tag". (major: webui) * Allow to load sx code from multiple zettel; dependencies are specified using <code>precursor</code> metadata. (major: webui) * Allow sx code to change WebUI for zettel with specified role. (major: webui) * Some minor usability improvements. (minor: webui) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_13"></a> <h2>Changes for Version 0.13.0 (2023-08-07)</h2> * There are for new search operators: less, not less, greater, not greater. These use the same syntax as the operators prefix, not prefix, suffix, not suffix. The latter are now denoted as <code>[</code>, <code>![</code>, <code>]</code>, and <code>!]</code>. The first may operate numerically for metadata like numbers, timestamps, and zettel identifier. They are not supported for full-text search. (breaking: api, webui) * The API endpoint <code>/o/{ID}</code> (order of zettel ID) is no longer available. Please use the query expression <code>{ID} ITEMS</code> instead. (breaking: api) * The API endpoint <code>/u/{ID}</code> (unlinked references of zettel ID) is no longer available. Please use the query expression <code>{ID} UNLINKED</code> instead. (breaking: api) * All API endpoints allow to encode zettel data with the <code>data</code> encodings, incl. creating, updating, retrieving, and querying zettel. (major: api) * Change syntax for context query to <code>zid ... CONTEXT</code>. This will allow to add more directives that operate on zettel identifier. Old syntax <code>CONTEXT zid</code> will be removed in 0.14. (major, deprecated) * Add query directive <code>ITEMS</code> that will produce a list of metadata of all zettel that are referenced by the originating zettel in a top-level list. It replaces the API endpoint <code>/o/{ID}</code> (and makes it more useful). (major: api, webui) * Add query directive <code>UNLINKED</code> that will produce a list of metadata of all zettel that are mentioning the originating zettel in a top-level, but do not mention them. It replaces the API endpoint <code>/u/{ID}</code> (and makes it more useful). (major: api, webui) * Add query directive <code>IDENT</code> to distinguish a search for a zettel identifier (“{ID}”), that will list all metadata of zettel containing that zettel identifier, and a request to just list the metadata of given zettel (“{ID} IDENT”). The latter could be filtered further. (minor: api, webui) * Add support for metadata key <code>folge-role</code>. (minor) * Allow to create a child from a given zettel. (minor: webui) * Make zettel entry/edit form a little friendlier: auto-prepend missing '#' to tags; ensure that role and syntax receive just a word. (minor: webui) * Use a zettel that defines builtins for evaluating WebUI templates. (minor: webui) * Add links to retrieve result of a query in other formats. (minor: webui) * Always log the found configuration file. (minor: server) * The use of the <code>json</code> zettel encoding is deprecated (since version 0.12.0). Support for this encoding will be removed in version 0.14.0. Please use the new <code>data</code> encoding instead. (deprecated: api) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_12"></a> <h2>Changes for Version 0.12.0 (2023-06-05)</h2> * Syntax of templates for the web user interface are changed from Mustache |
︙ | ︙ | |||
170 171 172 173 174 175 176 | that is the short form for "symbolic expression for HTML". (breaking) * Render footer zettel on all WebUI pages. (fix: webui) * Query search operator "=" now compares for equality, ":" compares depending on the value type. (minor: api, webui) | | | | | > | | | | > | > | | | | | < | | | | | | | | | | | | | > | | | | | | | > | | | | | | > | | | | | | | | | | | | | | 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 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 | that is the short form for "symbolic expression for HTML". (breaking) * Render footer zettel on all WebUI pages. (fix: webui) * Query search operator "=" now compares for equality, ":" compares depending on the value type. (minor: api, webui) * Search term <code>PICK</code> now respects the original sort order. This makes it more useful and orthogonal to <code>RANDOM</code> and <code>LIMIT</code>. As a side effect, zettel lists retrieved via the API are no longer sorted. In case you want a specific order, you must specify it explicit. (minor: api, webui) * New metadata key <code>expire</code> records a timestamp when a zettel should be treated as, well, expired. (minor) * New metadata keys <code>superior</code> and <code>subordinate</code> (calculated from <code>superior</code>) allow to specify a hierarchy between zettel. (minor) * Metadata keys with suffix <code>-date</code> and <code>-time</code> are treated as timestamp values. (minor) * <code>sexpr</code> zettel encoding is now documented in the manual. (minor: manual) * Build tool allows to install / update external Go tools needed to build the software. (minor) * Show only useful metadata on WebUI, not the internal metadata. (minor: webui) * The use of the <code>json</code> zettel encoding is deprecated. Support for this encoding may be removed in future versions. Please use the new <code>data</code> encoding instead. (deprecated: api) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_11"></a> <h2>Changes for Version 0.11.2 (2023-04-16)</h2> * Render footer zettel on all WebUI pages. Backported from 0.12.0. Many thanks to HK for reporting it! (fix: webui) <h2>Changes for Version 0.11.1 (2023-03-28)</h2> * Make <code>PICK</code> search term a little bit more deterministic so that the “Save As Zettel” button produces the same list. (fix: webui) <h2>Changes for Version 0.11.0 (2023-03-27)</h2> * Remove ZJSON encoding. It was announced in version 0.10.0. Use Sexpr encoding instead. (breaking) * Title of a zettel is no longer interpreted as Zettelmarkup text. Now it is just a plain string, possibly empty. Therefore, no inline formatting (like bold text), no links, no footnotes, no citations (the latter made rendering the title often questionable, in some contexts). If you used special entities, please use the Unicode characters directly. However, as a good practice, it is often the best to printable ASCII characters. (breaking) * Remove runtime configuration <code>marker-external</code>. It was added in version [#0_0_6|0.0.6] and updated in [#0_0_10|0.0.10]. If you want to change the marker for an external URL, you could modify zettel 00000000020001 (Zettelstore Base CSS) or zettel 00000000025001 (Zettelstore User CSS, preferred) by changing / adding a rule to add some content after an external <code><a ...></code> tag. (breaking: webui) * Add SHTML encoding. This allows to ensure the quality of generated HTML code. In addition, clients might use it, because it is easier to parse and manipulate than ordinary HTML. In the future, HTML template zettel will probably also use SHTML, deprecating the current Mustache syntax (which was added in [#0_0_9|0.0.9]). (major) * Search term <code>PICK n</code>, where <code>n</code> is an integer value greater zero, will pick randomly <code>n</code> elements from the search result list. Somehow similar (and faster) as <code>RANDOM LIMIT n</code>, but allows also later ordering of the resulting list. (minor) * Changed cost model for zettel context: a zettel with more outgoing/incoming references has higher cost than a zettel with less references. Also added support for traversing tags, with a similar cost model. As an effect, zettel hubs (in many cases your home zettel) will less likely add its references. Same for often used tags. The cost model might change in some details in the future, but the idea of a penalty applied to zettel / tags with many references will hold. (minor) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_10"></a> <h2>Changes for Version 0.10.1 (2023-01-30)</h2> * Show button to save a query into a zettel only when the current user has authorization to do it. (fix: webui) <h2>Changes for Version 0.10.0 (2023-01-24)</h2> * Remove support for endpoints <code>/j, /m, /q, /p, /v</code>. Their functions are merged into endpoint <code>/z</code>. This was announced in version 0.9.0. Please use only client library with at least version 0.10.0 too. (breaking: api) * Remove support for runtime configuration key <code>footer-html</code>. Use <code>footer-zettel</code> instead. Deprecated in version 0.9.0. (breaking: webui) * Save a query into a zettel to freeze it. (major: webui) * Allow to show all used metadata keys, linked with their occurrences and their values. (minor: webui) * Mark ZJSON encoding as deprecated for v0.11.0. Please use Sexpr encoding instead. (deprecated) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_9"></a> <h2>Changes for Version 0.9.0 (2022-12-12)</h2> * Remove support syntax <code>pikchr</code>. Although it was a nice idea to include it into Zettelstore, the implementation is too brittle (w.r.t. the expected long lifetime of Zettelstore). There should be other ways to support SVG front-ends. (breaking) * Allow to upload content when creating / updating a zettel. (major: webui) * Add syntax “draw” (again) (minor: zettelmarkup) * Allow to encode zettel in Markdown. Please note: not every aspect of a zettel can be encoded in Markdown. Those aspects will be ignored. (minor: api) * Enhance zettel context by raising the importance of folge zettel (and similar). (minor: api, webui) * Interpret zettel files with extension <code>.webp</code> as an binary image file format. (minor) * Allow to specify service specific log level via startup configuration and via command line. (minor) * Allow to specify a zettel to serve footer content via runtime configuration <code>footer-zettel</code>. Can be overwritten by user zettel. (minor: webui) * Footer data is automatically separated by a thematic break / horizontal rule. If you do not like it, you have to update the base template. (minor: webui) * Allow to set runtime configuration <code>home-zettel</code> in the user zettel to make it user-specific. (minor: webui) * Serve favicon.ico from the asset directory. (minor: webui) * Zettelmarkup cheat sheet (minor: manual) * Runtime configuration key <code>footer-html</code> will be removed in Version 0.10.0. Please use <code>footer-zettel</code> instead. (deprecated: webui) * In the next version 0.10.0, the API endpoints for a zettel (<code>/j</code>, <code>/p</code>, <code>/v</code>) will be merged with endpoint <code>/z</code>. Basically, the previous endpoint will be refactored as query parameter of endpoint <code>/z</code>. To reduce errors, there will be no version, where the previous endpoint are still available and the new functionality is still there. This is a warning to prepare for some breaking changes in v0.10.0. This also affects the API client implementation. (warning: api) * Some smaller bug fixes and improvements, to the software and to the documentation. <a id="0_8"></a> <h2>Changes for Version 0.8.0 (2022-10-20)</h2> * Remove support for tags within zettel content. Removes also property metadata keys <code>all-tags</code> and <code>computed-tags</code>. Deprecated in version 0.7.0. (breaking: zettelmarkup, api, webui) * Remove API endpoint <code>/m</code>, which retrieve aggregated (tags, roles) zettel identifier. Deprecated in version 0.7.0. (breaking: api) * Remove support for URL query parameter starting with an underscore. Deprecated in version 0.7.0. (breaking: api, webui) * Ignore HTML content by default, and allow HTML gradually by setting startup value <code>insecure-html</code>. (breaking: markup) * Endpoint <code>/q</code> returns list of full metadata, if no query action is specified. A HTTP call <code>GET /z</code> (retrieving metadata of all or some zettel) is now an alias for <code>GET /q</code>. (major: api) * Allow to create a zettel that acts as the new version of an existing zettel. Useful if you want to have access to older, outdated content. (minor: webui) * Allow transclusion to reference local image via URL. (minor: zettelmarkup, webui) * Add categories in RSS feed, based on zettel tags. |
︙ | ︙ | |||
372 373 374 375 376 377 378 | <a id="0_7"></a> <h2>Changes for Version 0.7.1 (2022-09-18)</h2> * Produce a RSS feed compatible to Miniflux. (minor) * Make sure to always produce a pubdata in RSS feed. (bug) * Prefix search for data that looks like a zettel identifier may end with a | | | | | | | | | > | | | | | | | | | | | | > | | | | | | | | | | | | | 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 | <a id="0_7"></a> <h2>Changes for Version 0.7.1 (2022-09-18)</h2> * Produce a RSS feed compatible to Miniflux. (minor) * Make sure to always produce a pubdata in RSS feed. (bug) * Prefix search for data that looks like a zettel identifier may end with a <code>0</code>. (bug) * Fix glitch on manual zettel. (bug) <h2>Changes for Version 0.7.0 (2022-09-17)</h2> * Removes support for URL query parameter to search for metadata values, sorting, offset, and limit a zettel list. Deprecated in version 0.6.0 (breaking: api, webui) * Allow to search for the existence / non-existence of a metadata key with the "?" operator: <code>key?</code> and <code>key!?</code>. Previously, the ":" operator was used for this by specifying an empty search value. Now you can use the ":" operator to find empty / non-empty metadata values. If you specify a search operator for metadata, the specified key is assumed to exist. (breaking: api, webui) * Rename “search expression” into “query expressions”. Similar, the reference prefix <code>search:</code> to specify a query link or a query transclusion is renamed to <code>query:</code> (breaking: zettelmarkup) * Rename query parameter for query expression from <code>_s</code> to <code>q</code>. (breaking: api, webui) * Cleanup names for HTTP query parameters in WebUI. Update your bookmarks if you used them. (For API: see below) (breaking: webui) * Allow search terms to be OR-ed. This allows to specify any search expression in disjunctive normal form. Therefore, the NEGATE term is not needed any more. (breaking: api, webui) * Replace runtime configuration <code>default-lang</code> with <code>lang</code>. Additionally, <code>lang</code> set at the zettel of the current user, will provide a default value for the current user, overwriting the global default value. (breaking) * Add new syntax <code>pikchr</code>, a markup language for diagrams in technical documentation. (major) * Add endpoint <code>/q</code> to query the zettelstore and aggregate resulting values. This is done by extending the query syntax. (major: api) * Add support for query actions. Actions may aggregate w.r.t. some metadata keys, or produce an RSS feed. (major: api, webui) * Query results can be ordered for more than one metadata key. Ordering by zettel identifier is an implicit last order expression to produce stable results. (minor: api, webui) * Add support for an asset directory, accessible via URL prefix <code>/assests/</code>. (minor: server) * Add support for metadata key <code>created</code>, a time stamp when the zettel was created. Since key <code>published</code> is now either <code>created</code> or <code>modified</code>, it will now always contains a valid time stamp. (minor) * Add support for metadata key <code>author</code>. It will be displayed on a zettel, if set. (minor: webui) * Remove CSS for lists. The browsers default value for <code>padding-left</code> will be used. (minor: webui) * Removed templates for rendering roles and tags lists. This is now done by query actions. (minor: webui) * Tags within zettel content are deprecated in version 0.8. This affects the computed metadata keys <code>content-tags</code> and <code>all-tags</code>. They will be removed. The number sign of a content tag introduces unintended tags, esp. in the English language; content tags may occur within links → links within links, when rendered as HTML; content tags may occur in the title of a zettel; naming of content tags, zettel tags, and their union is confusing for many. Migration: use zettel tags or replace content tag with a search. (deprecated: zettelmarkup) * Cleanup names for HTTP query parameter for API calls. Essentially, underscore characters in front are removed. Please use new names, old names will be deprecated in version 0.8. (deprecated: api) * Some smaller bug fixes and improvements, to the software and to the documentation. |
︙ | ︙ | |||
466 467 468 469 470 471 472 | (bug) <h2>Changes for Version 0.6.0 (2022-08-11)</h2> * Translating of "..." into horizontal ellipsis is no longer supported. Use &hellip; instead. (breaking: zettelmarkup) * Allow to specify search expressions, which allow to specify search | | | | 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 | (bug) <h2>Changes for Version 0.6.0 (2022-08-11)</h2> * Translating of "..." into horizontal ellipsis is no longer supported. Use &hellip; instead. (breaking: zettelmarkup) * Allow to specify search expressions, which allow to specify search criteria by using a simple syntax. Can be specified in WebUI's search box and via the API by using query parameter "_s". (major: api, webui) * A link reference is allowed to be a search expression. The WebUI will render this as a link to a list of zettel that satisfy the search expression. (major: zettelmarkup, webui) * A block transclusion is allowed to specify a search expression. When evaluated, the transclusion is replaced by a list of zettel that satisfy the search expression. (major: zettelmarkup) * When presenting a zettel list, allow to change the search expression. (minor: webui) * When evaluating a zettel, ignore transclusions if current user is not allowed to read transcluded zettel. (minor) * Added a small tutorial for Zettelmarkup. (minor: manual) * Using URL query parameter to search for metadata values, specify an ordering, an offset, and a limit for the resulting list, will be removed in version 0.7. Replace these with the more useable search expressions. Please be aware that the = search operator is also deprecated. It was only introduced to help the migration. (deprecated: api, webui) * Some smaller bug fixes and improvements, to the software and to the documentation. |
︙ | ︙ | |||
515 516 517 518 519 520 521 | * If authentication is enabled, a secret of at least 16 bytes must be set in the startup configuration. (breaking) * “Sexpr” encoding replaces “Native” encoding. Sexpr encoding is much easier to parse, compared with native and ZJSON encoding. In most cases it is smaller than ZJSON. (breaking: api) | | | | | | 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 | * If authentication is enabled, a secret of at least 16 bytes must be set in the startup configuration. (breaking) * “Sexpr” encoding replaces “Native” encoding. Sexpr encoding is much easier to parse, compared with native and ZJSON encoding. In most cases it is smaller than ZJSON. (breaking: api) * Endpoint <code>/r</code> is changed to <code>/m?_key=role</code> and returns now a map of role names to the list of zettel having this role. Endpoint <code>/t</code> is changed to <code>/m?_key=tags</code>. It already returned mapping described before. (breaking: api) * Remove support for a default value for metadata key title, role, and syntax. Title and role are now allowed to be empty, an empty syntax value defaults to “plain”. (breaking) * Add support for an “evaluation block” syntax in Zettelmarkup to allow interpretation of content by external software. |
︙ | ︙ | |||
546 547 548 549 550 551 552 | Zettelstore WebUI. (minor: webui) * A zettel can be saved while creating / editing it. There is no need to manually re-edit it by using the 'e' endpoint. (minor: webui) * Zettel role and zettel syntax are backed by a HTML5 data list element which lists supported and used values to help to enter a valid value. | | | | | | | 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 | Zettelstore WebUI. (minor: webui) * A zettel can be saved while creating / editing it. There is no need to manually re-edit it by using the 'e' endpoint. (minor: webui) * Zettel role and zettel syntax are backed by a HTML5 data list element which lists supported and used values to help to enter a valid value. (minor: webui) * Allow to use startup configuration, even if started in simple mode. (minor) * Log authentication issues in level "sense"; add caller IP address to some web server log messages. (minor: web server) * New startup configuration key <kbd>max-request-size</kbd> to limit a web request body to prevent client sending too large requests. (minor: web server) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_4"></a> <h2>Changes for Version 0.4 (2022-03-08)</h2> * Encoding “djson” renamed to “zjson” (<em>zettel json</em>). (breaking: api; minor: webui) * Remove inline quotation syntax <code><<...<<</code>. Now, <code>""...""</code> generates the equivalent code. Typographical quotes are generated by the browser, not by Zettelstore. (breaking: Zettelmarkup) * Remove inline formatting for mono space. Its syntax is now used by the similar syntax element of literal computer input. Mono space was just a visual element with no semantic association. Now, the syntax <kbd>++...++</kbd> is obsolete. (breaking: Zettelmarkup). * Remove API call to parse Zettelmarkup texts and encode it as text and HTML. Was call “POST /v”. It was needed to separately encode the titles of zettel. The same effect can be achieved by fetching the ZJSON representation and encode it using the function in the Zettelstore |
︙ | ︙ | |||
595 596 597 598 599 600 601 | interpreted as Zettelmarkup. Similar, the suffix <kbd>-set</kbd> denotes a set/list of words and the suffix <kbd>-zids</kbd> a set/list of zettel identifier. (minor: api, webui) * Change generated URLs for zettel-creation forms. If you have bookmarked them, e.g. to create a new zettel, you should update. (minor: webui) | | | | | | 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 | interpreted as Zettelmarkup. Similar, the suffix <kbd>-set</kbd> denotes a set/list of words and the suffix <kbd>-zids</kbd> a set/list of zettel identifier. (minor: api, webui) * Change generated URLs for zettel-creation forms. If you have bookmarked them, e.g. to create a new zettel, you should update. (minor: webui) * Remove support for metadata key <code>no-index</code> to suppress indexing selected zettel. It was introduced in <a href="#0_0_11">v0.0.11</a>, but disallows some future optimizations for searching zettel. (minor: api, webui) * Make some metadata-based searches a little bit faster by executing a (in-memory-based) full-text search first. Now only those zettel are loaded from file that contain the metadata value. (minor: api, webui) * Add an API call to retrieve the version of the Zettelstore. (minor: api) * Limit the amount of zettel and bytes to be stored in a memory box. Allows to use it with public access. (minor: box) * Disallow to cache the authentication cookie. Will remove most unexpected log-outs when using a mobile device. (minor: webui) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_3"></a> <h2>Changes for Version 0.3 (2022-02-09)</h2> * Zettel files with extension <code>.meta</code> are now treated as content files. Previously, they were interpreted as metadata files. The interpretation as metadata files was deprecated in version 0.2. (breaking: directory and file/zip box) * Add syntax “draw” to produce some graphical representations. (major) * Add Zettelmarkup syntax to specify full transclusion of other zettel. (major: Zettelmarkup) * Add Zettelmarkup syntax to specify inline-zettel, both for |
︙ | ︙ | |||
639 640 641 642 643 644 645 | (minor: directory and file/zip box) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_2"></a> <h2>Changes for Version 0.2 (2022-01-19)</h2> * v0.2.1 (2021-02-01) updates the license year in some documents | | | | | | | | | | | | > | | | | | | | | | | | > | | | | | | | | | < | | | > | | | | 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 | (minor: directory and file/zip box) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_2"></a> <h2>Changes for Version 0.2 (2022-01-19)</h2> * v0.2.1 (2021-02-01) updates the license year in some documents * Remove support for <code>;;small text;;</code> Zettelmarkup. (breaking: Zettelmarkup) * On macOS, the downloadable executable program is now called “zettelstore”, as on all other Unix-like platforms. (possibly breaking: macOS) * External metadata (e.g. for zettel with file extension other than <code>.zettel</code>) are stored in files without an extension. Metadata files with extension <code>.meta</code> are still recognized, but result in a warning message. In a future version (probably v0.3), <code>.meta</code> files will be treated as ordinary content files, possibly resulting in duplicate content. In other words: usage of <code>.meta</code> files for storing metadata is deprecated. (possibly breaking: directory and file box) * Show unlinked references in info page of each zettel. Unlinked references are phrases within zettel content that might reference another zettel with the same title as the phase. (major: webui) * Add endpoint <code>/u/{ID}</code> to retrieve unlinked references. (major: api) * Provide a logging facility. Log messages are written to standard output. Messages with level “information” are also written to a circular buffer (of length 8192) which can be retrieved via a computed zettel. There is a command line flag <code>-l LEVEL</code> to specify an application global logging level on startup (default: “information”). Logging level can also be changed via the administrator console, even for specific (sub-) services. (major) * The internal handling of zettel files is rewritten. This allows less reloads ands detects when the directory containing the zettel files is removed. The API, WebUI, and the admin console allow to manually refresh the internal state on demand. (major: box, webui) * <code>.zettel</code> files with YAML header are now correctly written. (bug) * Selecting zettel based on their metadata allows the same syntax as searching for zettel content. For example, you can list all zettel that have an identifier not ending with <code>00</code> by using the query <code>id=!<00</code>. (minor: api, webui) * Remove support for <code>//deprecated emphasized//</code> Zettelmarkup. (minor: Zettelmarkup) * Add options to profile the software. Profiling can be enabled at the command line or via the administrator console. (minor) * Add computed zettel that lists all supported parser / recognized zettel syntaxes. (minor) * Add API call to check for enabled authentication. (minor: api) * Renewing an API access token works even if authentication is not enabled. This corresponds to the behaviour of obtaining an access token. (minor: api) * If there is nothing to return, use HTTP status code 204, instead of 200 + <code>Content-Length: 0</code>. (minor: api) * Metadata key <code>duplicates</code> stores the duplicate file names, instead of just a boolean value that there were duplicate file names. (minor) * Document auto starting Zettelstore on Windows, macOS, and Linux. (minor) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_1"></a><a id="0_1_0"></a> <h2>Changes for Version 0.1 (2021-11-11)</h2> * v0.1.3 (2021-12-15) fixes a bug where the modification date could be set when a new zettel is created. * v0.1.2 (2021-11-18) fixes a bug when selecting zettel from a list when more than one comparison is negated. * v0.1.1 (2021-11-12) updates the documentation, mostly related to the deprecation of the <code>//</code> markup. * Remove visual Zettelmarkup (italic, underline). Semantic Zettelmarkup (emphasize, insert) is still allowed, but got a different syntax. The new syntax for <ins>inserted text</ins> is <code>>>inserted>></code>, while its previous syntax now denotes <em>emphasized text</em>: <code>__emphasized__</code>. The previous syntax for emphasized text is now deprecated: <code>//deprecated emphasized//</code>. Starting with Version 0.2.0, the deprecated syntax will not be supported. The reason is the collision with URLs that also contain the characters <code>//</code>. The ZMK encoding of a zettel may help with the transition (<code>/v/{ZettelID}?_part=zettel&_enc=zmk</code>, on the Info page of each zettel in the WebUI). Additionally, all deprecated uses of <code>//</code> will be rendered with a dashed box within the WebUI. (breaking: Zettelmarkup). * API client software is now a separate project. (breaking) * Initial support for HTTP security headers (Content-Security-Policy, Permissions-Policy, Referrer-Policy, X-Content-Type-Options, X-Frame-Options). Header values are currently some constant values. (possibly breaking: api, webui) * Remove visual Zettelmarkup (bold, strike through). Semantic Zettelmarkup (strong, delete) is still allowed and replaces the visual elements syntactically. The visual appearance should not change (depends on your changes / additions to CSS zettel). (possibly breaking: Zettelmarkup). * Add API endpoint <code>POST /v</code> to retrieve HTMl and text encoded strings from given ZettelMarkup encoded values. This will be used to render a HTML page from a given zettel: in many cases the title of a zettel must be treated separately. (minor: api) * Add API endpoint <code>/m</code> to retrieve only the metadata of a zettel. (minor: api) * New metadata value <code>content-tags</code> contains the tags that were given in the zettel content. To put it simply, <code>all-tags</code> = <code>tags</code> + <code>content-tags</code>. (minor) * Calculating the context of a zettel stops at the home zettel. (minor: api, webui) * When renaming or deleting a zettel, a warning will be given, if other zettel references the given zettel, or when “deleting” will uncover zettel in overlay box. (minor: webui) |
︙ | ︙ | |||
770 771 772 773 774 775 776 | (info) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_15"></a> <h2>Changes for Version 0.0.15 (2021-09-17)</h2> * Move again endpoint characters for authentication to make room for future | | > | | | | < | | | | | | | | | | | | | | | | | | | | | | | | | | > | | | | | | | | | | | | | | > | | | | | | | > | > | | | > | | | > | | | | | | | | | | > | | > | | | > | > | > | | > | > | > | | | | | | | | | | | | | | | | | > | | | | | | | | | | | | | | | | | | | | | | > | | | | | > | | | | | | 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 | (info) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_15"></a> <h2>Changes for Version 0.0.15 (2021-09-17)</h2> * Move again endpoint characters for authentication to make room for future features. WebUI authentication moves from <code>/a</code> to <code>/i</code> (login) and <code>/i?logout</code> (logout). API authentication moves from <code>/v</code> to </code>/a</code>. JSON-based basic zettel handling moves from <code>/z</code> to <code>/j</code> and <code>/z/{ID}</code> to <code>/j/{ID}</code>. Since the API client is updated too, this should not be a breaking change for most users. (minor: api, webui; possibly breaking) * Add API endpoint <code>/v/{ID}</code> to retrieve an evaluated zettel in various encodings. Mostly replaces endpoint <code>/z/{ID}</code> for other encodings except “json” and “raw”. Endpoint <code>/j/{ID}</code> now only returns JSON data, endpoint <code>/z/{ID}</code> is used to retrieve plain zettel data (previously called “raw”). See documentation for details. (major: api; breaking) * Metadata values of type <em>tag set</em> (the metadata with key <code>tags</code> is its most prominent example), are now compared in a case-insensitive manner. Tags that only differ in upper / lower case character are now treated identical. This might break your workflow, if you depend on case-sensitive comparison of tag values. Tag values are translated to their lower case equivalent before comparing them and when you edit a zettel through Zettelstore. If you just modify the zettel files, your tag values remain unchanged. (major; breaking) * Endpoint <code>/z/{ID}</code> allows the same methods as endpoint <code>/j/{ID}</code>: <code>GET</code> retrieves zettel (see above), <code>PUT</code> updates a zettel, <code>DELETE</code> deletes a zettel, <code>MOVE</code> renames a zettel. In addition, <code>POST /z</code> will create a new zettel. When zettel data must be given, the format is plain text, with metadata separated from content by an empty line. See documentation for more details. (major: api (plus WebUI for some details)) * Allows to transclude / expand the content of another zettel into a target zettel when the zettel is rendered. By using the syntax of embedding an image (which is some kind of expansion too), the first top-level paragraph of a zettel may be transcluded into the target zettel. Endless recursion is checked, as well as a possible “transclusion bomb ” (similar to a XML bomb). See manual for details. (major: zettelmarkup) * The endpoint <code>/z</code> allows to list zettel in a simpler format than endpoint <code>/j</code>: one line per zettel, and only zettel identifier plus zettel title. (minor: api) * Folgezettel are now displayed with full title at the bottom of a page. (minor: webui) * Add API endpoint <code>/p/{ID}</code> to retrieve a parsed, but not evaluated zettel in various encodings. (minor: api) * Fix: do not list a shadowed zettel that matches the select criteria. (minor) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_14"></a> <h2>Changes for Version 0.0.14 (2021-07-23)</h2> * Rename “place” into “box”. This also affects the configuration keys to specify boxes <code>box-uri<em>X</em></code> (previously <code>place-uri-<em>X</em></code>. Older changes documented here are renamed too. (breaking) * Add API for creating, updating, renaming, and deleting zettel. (major: api) * Initial API client for Go. (major: api) * Remove support for paging of WebUI list. Runtime configuration key <code>list-page-size</code> is removed. If you still specify it, it will be ignored. (major: webui) * Use endpoint <code>/v</code> for user authentication via API. Endpoint <code>/a</code> is now used for the web user interface only. Similar, endpoint <code>/y</code> (“zettel context”) is renamed to <code>/x</code>. (minor, possibly breaking) * Type of used-defined metadata is determined by suffix of key: <code>-number</code>, <code>-url</code>, <code>-zid</code> will result the values to be interpreted as a number, an URL, or a zettel identifier. (minor, but possibly breaking if you already used a metadata key with above suffixes, but as a string type) * New <code>user-role</code> “creator”, which is only allowed to create new zettel (except user zettel). This role may only read and update public zettel or its own user zettel. Added to support future client software (e.g. on a mobile device) that automatically creates new zettel but, in case of a password loss, should not allow to read existing zettel. (minor, possibly breaking, because new zettel template zettel must always prepend the string <code>new-</code> before metadata keys that should be transferred to the new zettel) * New supported metadata key <code>box-number</code>, which gives an indication from which box the zettel was loaded. (minor) * New supported syntax <code>html</code>. (minor) * New predefined zettel “User CSS” that can be used to redefine some predefined CSS (without modifying the base CSS zettel). (minor: webui) * When a user moves a zettel file with additional characters into the box directory, these characters are preserved when zettel is updated. (bug) * The phase “filtering a zettel list” is more precise “selecting zettel” (documentation) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_13"></a> <h2>Changes for Version 0.0.13 (2021-06-01)</h2> * Startup configuration <code>box-<em>X</em>-uri</code> (where <em>X</em> is a number greater than zero) has been renamed to <code>box-uri-<em>X</em></code>. (breaking) * Web server processes startup configuration <code>url-prefix</code>. There is no need for stripping the prefix by a front-end web server any more. (breaking: webui, api) * Administrator console (only optional accessible locally). Enable it only on systems with a single user or with trusted users. It is disabled by default. (major: core) * Remove visibility value “simple-expert” introduced in [#0_0_8|version 0.0.8]. It was too complicated, esp. authorization. There was a name collision with the “simple” directory box sub-type. (major) * For security reasons, HTML blocks are not encoded as HTML if they contain certain snippets, such as <code><script</code> or <code><iframe</code>. These may be caused by using CommonMark as a zettel syntax. (major) * Full-text search can be a prefix search or a search for equal words, in addition to the search whether a word just contains word of the search term. (minor: api, webui) * Full-text search for URLs, with above additional operators. (minor: api, webui) * Add system zettel about license, contributors, and dependencies (and their license). For a nicer layout of zettel identifier, the zettel about environment values and about runtime metrics got new zettel identifier. This affects only user that referenced those zettel. (minor) * Local images that cannot be read (not found or no access rights) are substituted with the new default image, a spinning emoji. See [/file?name=box/constbox/emoji_spin.gif]. (minor: webui) * Add zettelmarkup syntax for a table row that should be ignored: <code>|%</code>. This allows to paste output of the administrator console into a zettel. (minor: zmk) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_12"></a> <h2>Changes for Version 0.0.12 (2021-04-16)</h2> * Raise the per-process limit of open files on macOS to 1.048.576. This allows most macOS users to use at least 500.000 zettel. That should be enough for the near future. (major) * Mitigate the shortcomings of the macOS version by introducing types of directory boxes. The original directory box type is now called "notify" (the default value). There is a new type called "simple". This new type does not notify Zettelstore when some of the underlying Zettel files change. (major) * Add new startup configuration <code>default-dir-box-type</code>, which gives the default value for specifying a directory box type. The default value is “notify”. On macOS, the default value may be changed “simple” if some errors occur while raising the per-process limit of open files. (minor) <a id="0_0_11"></a> <h2>Changes for Version 0.0.11 (2021-04-05)</h2> * New box schema "file" allows to read zettel from a ZIP file. A zettel collection can now be packaged and distributed easier. (major: server) * Non-restricted search is a full-text search. The search string will be normalized according to Unicode NFKD. Every character that is not a letter or a number will be ignored for the search. It is sufficient if the words to be searched are part of words inside a zettel, both content and metadata. (major: api, webui) * A zettel can be excluded from being indexed (and excluded from being found in a search) if it contains the metadata <code>no-index: true</code>. (minor: api, webui) * Menu bar is shown when displaying error messages. (minor: webui) * When selecting zettel, it can be specified that a given value should <em>not</em> match. Previously, only the whole select criteria could be negated (which is still possible). (minor: api, webui) * You can select a zettel by specifying that specific metadata keys must (or must not) be present. (minor: api, webui) * Context of a zettel (introduced in version 0.0.10) does not take tags into account any more. Using some tags for determining the context resulted into erratic, non-deterministic context lists. (minor: api, webui) * Selecting zettel depending on tag values can be both by comparing only the prefix or the whole string. If a search value begins with '#', only zettel with the exact tag will be returned. Otherwise a zettel will be returned if the search string just matches the prefix of only one of its tags. (minor: api, webui) * Many smaller bug fixes and improvements, to the software and to the documentation. A note for users of macOS: in the current release and with macOS's default values, a zettel directory must not contain more than approx. 250 files. There are three options to mitigate this limitation temporarily: # You update the per-process limit of open files on macOS. # You setup a virtualisation environment to run Zettelstore on Linux or Windows. # You wait for version 0.0.12 which addresses this issue. <a id="0_0_10"></a> <h2>Changes for Version 0.0.10 (2021-02-26)</h2> * Menu item “Home” now redirects to a home zettel. Its default identifier is <code>000100000000</code>. The identifier can be changed with configuration key <code>home-zettel</code>, which supersedes key <code>start</code>. The default home zettel contains some welcoming information for the new user. (major: webui) * Show context of a zettel by following all backward and/or forward reference up to a defined depth and list the resulting zettel. Additionally, some zettel with similar tags as the initial zettel are also taken into account. (major: api, webui) * A zettel that references other zettel within first-level list items, can act as a “table of contents” zettel. The API endpoint <code>/o/{ID}</code> allows to retrieve the referenced zettel in the same order as they occur in the zettel. (major: api) * The zettel “New Menu” with identifier <code>00000000090000</code> contains a list of all zettel that should act as a template for new zettel. They are listed in the WebUIs ”New“ menu. This is an application of the previous item. It supersedes the usage of a role <code>new-template</code> introduced in [#0_0_6|version 0.0.6]. <b>Please update your zettel if you make use of the now deprecated feature.</b> (major: webui) * A reference that starts with two slash characters (“<code>//</code>”) it will be interpreted relative to the value of <code>url-prefix</code>. For example, if <code>url-prefix</code> has the value <code>/manual/</code>, the reference <code>[[Zettel list|//h]]</code> will render as <code><a href="/manual/h">Zettel list</a></code>. (minor: syntax) * Searching/selecting ignores the leading '#' character of tags. (minor: api, webui) * When result of selecting or searching is presented, the query is written as the page heading. (minor: webui) * A reference to a zettel that contains a URL fragment, will now be processed by the indexer. (bug: server) * Runtime configuration key <code>marker-external</code> now defaults to “&#10138;” (“➚”). It is more beautiful than the previous “&#8599;&#xfe0e;” (“↗︎”), which also needed the additional “&#xfe0e;” to disable the conversion to an emoji on iPadOS. (minor: webui) * A pre-build binary for macOS ARM64 (also known as Apple silicon) is available. (minor: infrastructure) * Many smaller bug fixes and improvements, to the software and to the documentation. <a id="0_0_9"></a> <h2>Changes for Version 0.0.9 (2021-01-29)</h2> This is the first version that is managed by [https://fossil-scm.org|Fossil] instead of GitHub. To access older versions, use the Git repository under [https://github.com/zettelstore/zettelstore-github|zettelstore-github]. <h3>Server / API</h3> * (major) Support for property metadata. Metadata key <code>published</code> is the first example of such a property. * (major) A background activity (called <i>indexer</i>) continuously monitors zettel changes to establish the reverse direction of found internal links. This affects the new metadata keys <code>precursor</code> and <code>folge</code>. A user specifies the precursor of a zettel and the indexer computes the property metadata for [https://forum.zettelkasten.de/discussion/996/definition-folgezettel|Folgezettel]. Metadata keys with type “Identifier” or “IdentifierSet” that have no inverse key (like <code>precursor</code> and <code>folge</code> with add to the key <code>forward</code> that also collects all internal links within the content. The computed inverse is <code>backward</code>, which provides all backlinks. The key <code>back</code> is computed as the value of <code>backward</code>, but without forward links. Therefore, <code>back</code> is something like the list of “smart backlinks”. * (minor) If Zettelstore is being stopped, an appropriate message is written in the console log. * (minor) New computed zettel with environmental data, the list of supported meta data keys, and statistics about all configured zettel boxes. Some other computed zettel got a new identifier (to make room for other variant). * (minor) Remove zettel <code>00000000000004</code>, which contained the Go version that produced the Zettelstore executable. It was too specific to the current implementation. This information is now included in zettel <code>00000000000006</code> (<i>Zettelstore Environment Values</i>). * (minor) Predefined templates for new zettel do not contain any value for attribute <code>visibility</code> any more. * (minor) Add a new metadata key type called “Zettelmarkup”. It is a non-empty string, that will be formatted with Zettelmarkup. <code>title</code> and <code>default-title</code> have this type. * (major) Rename zettel syntax “meta” to “none”. Please update the <i>Zettelstore Runtime Configuration</i> and all other zettel that previously used the value “meta”. Other zettel are typically user zettel, used for authentication. However, there is no real harm, if you do not update these zettel. In this case, the metadata is just not presented when rendered. Zettelstore will still work. * (minor) Login will take at least 500 milliseconds to mitigate login attacks. This affects both the API and the WebUI. * (minor) Add a sort option “_random” to produce a zettel list in random order. <code>_order</code> / <code>order</code> are now an aliases for the query parameters <code>_sort</code> / <code>sort</code>. <h3>WebUI</h3> * (major) HTML template zettel for WebUI now use [https://mustache.github.io/|Mustache] syntax instead of previously used [https://golang.org/pkg/html/template/|Go template] syntax. This allows these zettel to be used, even when there is another Zettelstore implementation, in another programming language. Mustache is available for approx. 48 programming languages, instead of only one for Go templates. <b>If you modified your templates, you <i>must</i> adapt them to the new syntax. Otherwise the WebUI will not work.</b> * (major) Show zettel identifier of folgezettel and precursor zettel in the header of a rendered zettel. If a zettel has real backlinks, they are shown at the botton of the page (“Additional links to this zettel”). * (minor) All property metadata, even computed metadata is shown in the info page of a zettel. * (minor) Rendering of metadata keys <code>title</code> and <code>default-title</code> in info page changed to a full HTML output for these Zettelmarkup encoded values. * (minor) Always show the zettel identifier on the zettel detail view. Previously, the identifier was not shown if the zettel was not editable. * (minor) Do not show computed metadata in edit forms anymore. <a id="0_0_8"></a> <h2>Changes for Version 0.0.8 (2020-12-23)</h2> <h3>Server / API</h3> * (bug) Zettel files with extension <code>.jpg</code> and without metadata will get a <code>syntax</code> value “jpg”. The internal data structure got the same value internally, instead of “jpeg”. This has been fixed for all possible alternative syntax values. * (bug) If a file, e.g. an image file like <code>20201130190200.jpg</code>, is added to the directory box, its metadata are just calculated from the information available. Updated metadata did not find its way into the zettel box, because the <code>.meta</code> file was not written. * (bug) If just the <code>.meta</code> file was deleted manually, the zettel was assumed to be missing. A workaround is to restart the software. If the <code>.meta</code> file is deleted, metadata is now calculated in the same way when the <code>.meta</code> file is non-existing at the start of the software. * (bug) A link to the current zettel, only using a fragment (e.g. <code>[[Title|#title]]</code>) is now handled correctly as a zettel link (and not as a link to external material). * (minor) Allow zettel to be marked as “read only”. This is done through the metadata key <code>read-only</code>. * (bug) When renaming a zettel, check all boxes for the new zettel identifier, not just the first one. Otherwise it will be possible to shadow a read-only zettel from a next box, effectively modifying it. * (minor) Add support for a configurable default value for metadata key <code>visibility</code>. * (bug) If <code>list-page-size</code> is set to a relatively small value and the authenticated user is <i>not</i> the owner, some zettel were not shown in the list of zettel or were not returned by the API. * (minor) Add support for new visibility “expert”. An owner becomes an expert, if the runtime configuration key <code>expert-mode</code> is set to true. * (major) Add support for computed zettel. These zettel have an identifier less than <code>0000000000100</code>. Most of them are only visible, if <code>expert-mode</code> is enabled. * (bug) Fixes a memory leak that results in too many open files after approx. 125 reload operations. * (major) Predefined templates for new zettel got an explicit value for visibility: “login”. Please update these zettel if you modified them. * (major) Rename key <code>readonly</code> of <i>Zettelstore Startup Configuration</i> to <code>read-only-mode</code>. This was done to avoid some confusion with the zettel metadata key <code>read-only</code>. <b>Please adapt your startup configuration. Otherwise your Zettelstore will be accidentally writable.</b> * (minor) References starting with “./” and “../” are treated as a local reference. Previously, only the prefix “/” was treated as a local reference. * (major) Metadata key <code>modified</code> will be set automatically to the current local time if a zettel is updated through Zettelstore. <b>If you used that key previously for your own, you should rename it before you upgrade.</b> * (minor) The new visibility value “simple-expert” ensures that many computed zettel are shown for new users. This is to enable them to send useful bug reports. * (minor) When a zettel is stored as a file, its identifier is additionally stored within the metadata. This helps for better robustness in case the file names were corrupted. In addition, there could be a tool that compares the identifier with the file name. <h3>WebUI</h3> * (minor) Remove list of tags in “List Zettel” and search results. There was some feedback that the additional tags were not helpful. * (minor) Move zettel field "role" above "tags" and move "syntax" more to "content". * (minor) Rename zettel operation “clone” to “copy”. * (major) All predefined HTML templates have now a visibility value “expert”. If you want to see them as an non-expert owner, you must temporary enable <code>expert-mode</code> and change the <code>visibility</code> metadata value. * (minor) Initial support for [https://zettelkasten.de/posts/tags/folgezettel/|Folgezettel]. If you click on “Folge” (detail view or info view), a new zettel is created with a reference (<code>precursor</code>) to the original zettel. Title, role, tags, and syntax are copied from the original zettel. * (major) Most predefined zettel have a title prefix of “Zettelstore”. * (minor) If started in simple mode, e.g. via double click or without any command, some information for the new user is presented. In the terminal, there is a hint about opening the web browser and use |
︙ | ︙ | |||
1199 1200 1201 1202 1203 1204 1205 | to stay with the old licenses (AGPLv3+, CC BY-SA 4.0), you are free to fork from the previous version. <a id="0_0_6"></a> <h2>Changes for Version 0.0.6 (2020-11-23)</h2> <h3>Server</h3> * (major) Rename identifier of <i>Zettelstore Runtime Configuration</i> to | | > | | | | | | | | | | 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 | to stay with the old licenses (AGPLv3+, CC BY-SA 4.0), you are free to fork from the previous version. <a id="0_0_6"></a> <h2>Changes for Version 0.0.6 (2020-11-23)</h2> <h3>Server</h3> * (major) Rename identifier of <i>Zettelstore Runtime Configuration</i> to <code>00000000000100</code> (previously <code>00000000000001</code>). This is done to gain some free identifier with smaller number to be used internally. <b>If you customized this zettel, please make sure to rename it to the new identifier.</b> * (major) Rename the two essential metadata keys of a user zettel to <code>credential</code> and <code>user-id</code>. The previous values were <code>cred</code> and <code>ident</code>. <b>If you enabled user authentication and added some user zettel, make sure to change them accordingly. Otherwise these users will not authenticated any more.</b> * (minor) Rename the scheme of the box URL where predefined zettel are stored to “const”. The previous value was “globals”. <h3>Zettelmarkup</h3> * (bug) Allow to specify a <i>fragment</i> in a reference to a zettel. Used to link to an internal position within a zettel. This applies to CommonMark too. <h3>API</h3> * (bug) Encoding binary content in format “json” now results in valid JSON content. * (bug) All query parameters of selecting zettel must be true, regardless if a specific key occurs more than one or not. * (minor) Encode all inherited meta values in all formats except “raw”. A meta value is called <i>inherited</i> if there is a key starting with <code>default-</code> in the <i>Zettelstore Runtime Configuration</i>. Applies to WebUI also. * (minor) Automatic calculated identifier for headings (only for “html”, “djson”, “native” format and for the Web user interface). You can use this to provide a zettel reference that links to the heading, without specifying an explicit mark (<code>[!mark]</code>). * (major) Allow to retrieve all references of a given zettel. |
︙ | ︙ | |||
1250 1251 1252 1253 1254 1255 1256 | contrast to “zettel references” and “external references”). When a local reference is displayed as an URL on the WebUI, it will not opened in a new window/tab. They will receive a <i>local</i> marker, when encoded as “djson” or “native”. Local references are listed on the <i>Info page</i> of each zettel. * (minor) Change the default value for some visual sugar put after an | | | | > | | | | | | | | | > | | | | | > | 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 | contrast to “zettel references” and “external references”). When a local reference is displayed as an URL on the WebUI, it will not opened in a new window/tab. They will receive a <i>local</i> marker, when encoded as “djson” or “native”. Local references are listed on the <i>Info page</i> of each zettel. * (minor) Change the default value for some visual sugar put after an external URL to <code>&\#8599;&\#xfe0e;</code> (“↗︎”). This affects the former key <code>icon-material</code> of the <i>Zettelstore Runtime Configuration</i>, which is renamed to <code>marker-external</code>. * (major) Allow multiple zettel to act as templates for creating new zettel. All zettel with a role value “new-template” act as a template to create a new zettel. The WebUI menu item “New” changed to a drop-down list with all those zettel, ordered by their identifier. All metadata keys with the prefix <code>new-</code> will be translated to a new or updated keys/value without that prefix. You can use this mechanism to specify a role for the new zettel, or a different title. The title of the template zettel is used in the drop-down list. The initial template zettel “New Zettel” has now a different zettel identifier (now: <code>00000000091001</code>, was: <code>00000000040001</code>). <b>Please update it, if you changed that zettel.</b> <br>Note: this feature was superseded in [#0_0_10|version 0.0.10] by the “New Menu” zettel. * (minor) When a page should be opened in a new windows (e.g. for external references), the web browser is instructed to decouple the new page from the previous one for privacy and security reasons. In detail, the web browser is instructed to omit referrer information and to omit a JS object linking to the page that contained the external link. * (minor) If the value of the <i>Zettelstore Runtime Configuration</i> key <code>list-page-size</code> is greater than zero, the number of WebUI list elements will be restricted and it is possible to change to the next/previous page to list more elements. * (minor) Change CSS to enhance reading: make <code>line-height</code> a little smaller (previous: 1.6, now 1.4) and move list items to the left. <a id="0_0_5"></a> <h2>Changes for Version 0.0.5 (2020-10-22)</h2> * Application Programming Interface (API) to allow external software to retrieve zettel data from the Zettelstore. * Specify boxes, where zettel are stored, via an URL. * Add support for a custom footer. <a id="0_0_4"></a> <h2>Changes for Version 0.0.4 (2020-09-11)</h2> * Optional user authentication/authorization. * New sub-commands <code>file</code> (use Zettelstore as a command line filter), <code>password</code> (for authentication), and <code>config</code>. <a id="0_0_3"></a> <h2>Changes for Version 0.0.3 (2020-08-31)</h2> * Starting Zettelstore has been changed by introducing sub-commands. This change is also reflected on the server installation procedures. * Limitations on renaming zettel has been relaxed. <a id="0_0_2"></a> <h2>Changes for Version 0.0.2 (2020-08-28)</h2> * Configuration zettel now has ID <code>00000000000001</code> (previously: <code>00000000000000</code>). * The zettel with ID <code>00000000000000</code> is no longer shown in any zettel list. If you changed the configuration zettel, you should rename it manually in its file directory. * Creating a new zettel is now done by cloning an existing zettel. To mimic the previous behaviour, a zettel with ID <code>00000000040001</code> is introduced. You can change it if you need a different template zettel. <a id="0_0_1"></a> <h2>Changes for Version 0.0.1 (2020-08-21)</h2> * Initial public release. |
Changes to www/download.wiki.
1 2 3 4 5 6 7 8 9 10 11 | <title>Download</title> <h1>Download of Zettelstore Software</h1> <h2>Foreword</h2> * Zettelstore is free/libre open source software, licensed under EUPL-1.2-or-later. * The software is provided as-is. * There is no guarantee that it will not damage your system. * However, it is in use by the main developer since March 2020 without any damage. * It may be useful for you. It is useful for me. * Take a look at the [https://zettelstore.de/manual/|manual] to know how to start and use it. <h2>ZIP-ped Executables</h2> | | | | | | | | | 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 | <title>Download</title> <h1>Download of Zettelstore Software</h1> <h2>Foreword</h2> * Zettelstore is free/libre open source software, licensed under EUPL-1.2-or-later. * The software is provided as-is. * There is no guarantee that it will not damage your system. * However, it is in use by the main developer since March 2020 without any damage. * It may be useful for you. It is useful for me. * Take a look at the [https://zettelstore.de/manual/|manual] to know how to start and use it. <h2>ZIP-ped Executables</h2> Build: <code>v0.18.0</code> (2024-07-11). * [/uv/zettelstore-0.18.0-linux-amd64.zip|Linux] (amd64) * [/uv/zettelstore-0.18.0-linux-arm.zip|Linux] (arm6, e.g. Raspberry Pi) * [/uv/zettelstore-0.18.0-darwin-arm64.zip|macOS] (arm64) * [/uv/zettelstore-0.18.0-darwin-amd64.zip|macOS] (amd64) * [/uv/zettelstore-0.18.0-windows-amd64.zip|Windows] (amd64) Unzip the appropriate file, install and execute Zettelstore according to the manual. <h2>Zettel for the manual</h2> As a starter, you can download the zettel for the manual [/uv/manual-0.18.0.zip|here]. Just unzip the contained files and put them into your zettel folder or configure a file box to read the zettel directly from the ZIP file. |
Changes to www/index.wiki.
︙ | ︙ | |||
12 13 14 15 16 17 18 | To get an initial impression, take a look at the [https://zettelstore.de/manual/|manual]. It is a live example of the zettelstore software, running in read-only mode. The software, including the manual, is licensed under the [/file?name=LICENSE.txt&ci=trunk|European Union Public License 1.2 (or later)]. | | | | | | | | | | | | 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 | To get an initial impression, take a look at the [https://zettelstore.de/manual/|manual]. It is a live example of the zettelstore software, running in read-only mode. The software, including the manual, is licensed under the [/file?name=LICENSE.txt&ci=trunk|European Union Public License 1.2 (or later)]. * [https://t73f.de/r/zsc|Zettelstore Client] provides client software to access Zettelstore via its API more easily. * [https://zettelstore.de/contrib|Zettelstore Contrib] contains contributed software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. * [https://t73f.de/r/sx|Sx] provides an evaluator for symbolic expressions, which is used for HTML templates and more. [https://mastodon.social/tags/Zettelstore|Stay tuned] … <hr> <h3>Latest Release: 0.18.0 (2024-07-11)</h3> * [./download.wiki|Download] * [./changes.wiki#0_18|Change summary] * [/timeline?p=v0.18.0&bt=v0.17.0&y=ci|Check-ins for version 0.18.0], [/vdiff?to=v0.18.0&from=v0.17.0|content diff] * [/timeline?df=v0.18.0&y=ci|Check-ins derived from the 0.18.0 release], [/vdiff?from=v0.18.0&to=trunk|content diff] * [./plan.wiki|Limitations and planned improvements] * [/timeline?t=release|Timeline of all past releases] <hr> <h2>Build instructions</h2> Just install [https://go.dev/dl/|Go] and some Go-based tools. Please read the [./build.md|instructions] for details. |
︙ | ︙ |
Changes to zettel/content.go.
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 zettel import ( "bytes" "encoding/base64" "errors" "io" "unicode" "unicode/utf8" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package zettel import ( "bytes" "encoding/base64" "errors" "io" "unicode" "unicode/utf8" "t73f.de/r/zsc/input" ) // Content is just the content of a zettel. type Content struct { data []byte isBinary bool } |
︙ | ︙ | |||
70 71 72 73 74 75 76 | pos := inp.Pos for inp.Ch != input.EOS { if input.IsEOLEOS(inp.Ch) { inp.Next() pos = inp.Pos continue } | | | 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | pos := inp.Pos for inp.Ch != input.EOS { if input.IsEOLEOS(inp.Ch) { inp.Next() pos = inp.Pos continue } if !inp.IsSpace() { break } inp.Next() } zc.data = bytes.TrimRightFunc(inp.Src[pos:], unicode.IsSpace) } |
︙ | ︙ | |||
109 110 111 112 113 114 115 | } // IsBinary returns true if the given data appears to be non-text data. func IsBinary(data []byte) bool { if !utf8.Valid(data) { return true } | | < | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | } // IsBinary returns true if the given data appears to be non-text data. func IsBinary(data []byte) bool { if !utf8.Valid(data) { return true } for i := range len(data) { if data[i] == 0 { return true } } return false } |
Changes to zettel/content_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 zettel_test import ( "testing" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package zettel_test import ( "testing" |
︙ | ︙ |
Changes to zettel/id/digraph.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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 ( "maps" "slices" ) // Digraph relates zettel identifier in a directional way. | > > > | | 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) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package id import ( "maps" "slices" ) // Digraph relates zettel identifier in a directional way. type Digraph map[Zid]*Set // AddVertex adds an edge / vertex to the digraph. func (dg Digraph) AddVertex(zid Zid) Digraph { if dg == nil { return Digraph{zid: nil} } if _, found := dg[zid]; !found { |
︙ | ︙ | |||
39 40 41 42 43 44 45 | } } // AddEdge adds a connection from `zid1` to `zid2`. // Both vertices must be added before. Otherwise the function may panic. func (dg Digraph) AddEdge(fromZid, toZid Zid) Digraph { if dg == nil { | | | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | } } // AddEdge adds a connection from `zid1` to `zid2`. // Both vertices must be added before. Otherwise the function may panic. func (dg Digraph) AddEdge(fromZid, toZid Zid) Digraph { if dg == nil { return Digraph{fromZid: (*Set)(nil).Add(toZid), toZid: nil} } dg[fromZid] = dg[fromZid].Add(toZid) return dg } // AddEgdes adds all given `Edge`s to the digraph. // |
︙ | ︙ | |||
65 66 67 68 69 70 71 | dg = dg.AddEdge(edge.From, edge.To) } return dg } // Equal returns true if both digraphs have the same vertices and edges. func (dg Digraph) Equal(other Digraph) bool { | | | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | dg = dg.AddEdge(edge.From, edge.To) } return dg } // Equal returns true if both digraphs have the same vertices and edges. func (dg Digraph) Equal(other Digraph) bool { return maps.EqualFunc(dg, other, func(cg, co *Set) bool { return cg.Equal(co) }) } // Clone a digraph. func (dg Digraph) Clone() Digraph { if len(dg) == 0 { return nil } |
︙ | ︙ | |||
90 91 92 93 94 95 96 | return false } _, found := dg[zid] return found } // Vertices returns the set of all vertices. | | | | | | | | | | | | | | | | | | | | | | 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 | return false } _, found := dg[zid] return found } // Vertices returns the set of all vertices. func (dg Digraph) Vertices() *Set { if len(dg) == 0 { return nil } verts := NewSetCap(len(dg)) for vert := range dg { verts.Add(vert) } return verts } // Edges returns an unsorted slice of the edges of the digraph. func (dg Digraph) Edges() (es EdgeSlice) { for vert, closure := range dg { closure.ForEach(func(next Zid) { es = append(es, Edge{From: vert, To: next}) }) } return es } // Originators will return the set of all vertices that are not referenced // a the to-part of an edge. func (dg Digraph) Originators() *Set { if len(dg) == 0 { return nil } origs := dg.Vertices() for _, closure := range dg { origs.ISubstract(closure) } return origs } // Terminators returns the set of all vertices that does not reference // other vertices. func (dg Digraph) Terminators() (terms *Set) { for vert, closure := range dg { if closure.IsEmpty() { terms = terms.Add(vert) } } return terms } // TransitiveClosure calculates the sub-graph that is reachable from `zid`. func (dg Digraph) TransitiveClosure(zid Zid) (tc Digraph) { if len(dg) == 0 { return nil } var marked *Set stack := Slice{zid} for pos := len(stack) - 1; pos >= 0; pos = len(stack) - 1 { curr := stack[pos] stack = stack[:pos] if marked.Contains(curr) { continue } tc = tc.AddVertex(curr) dg[curr].ForEach(func(next Zid) { tc = tc.AddVertex(next) tc = tc.AddEdge(curr, next) stack = append(stack, next) }) marked = marked.Add(curr) } return tc } // ReachableVertices calculates the set of all vertices that are reachable // from the given `zid`. func (dg Digraph) ReachableVertices(zid Zid) (tc *Set) { if len(dg) == 0 { return nil } stack := dg[zid].SafeSorted() for last := len(stack) - 1; last >= 0; last = len(stack) - 1 { curr := stack[last] stack = stack[:last] if tc.Contains(curr) { continue } closure, found := dg[curr] if !found { continue } tc = tc.Add(curr) closure.ForEach(func(next Zid) { stack = append(stack, next) }) } return tc } // IsDAG returns a vertex and false, if the graph has a cycle containing the vertex. func (dg Digraph) IsDAG() (Zid, bool) { for vertex := range dg { if dg.ReachableVertices(vertex).Contains(vertex) { return vertex, false } } return Invalid, true } // Reverse returns a graph with reversed edges. func (dg Digraph) Reverse() (revDg Digraph) { for vertex, closure := range dg { revDg = revDg.AddVertex(vertex) closure.ForEach(func(next Zid) { revDg = revDg.AddVertex(next) revDg = revDg.AddEdge(next, vertex) }) } return revDg } // SortReverse returns a deterministic, topological, reverse sort of the // digraph. // // Works only if digraph is a DAG. Otherwise the algorithm will not terminate // or returns an arbitrary value. func (dg Digraph) SortReverse() (sl Slice) { if len(dg) == 0 { return nil } tempDg := dg.Clone() for len(tempDg) > 0 { terms := tempDg.Terminators() if terms.IsEmpty() { break } termSlice := terms.SafeSorted() slices.Reverse(termSlice) sl = append(sl, termSlice...) terms.ForEach(func(t Zid) { tempDg.RemoveVertex(t) }) } return sl } |
Changes to zettel/id/digraph_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package id_test import ( "testing" |
︙ | ︙ | |||
23 24 25 26 27 28 29 | } func TestDigraphOriginators(t *testing.T) { t.Parallel() testcases := []struct { name string dg id.EdgeSlice | | | | 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | } func TestDigraphOriginators(t *testing.T) { t.Parallel() testcases := []struct { name string dg id.EdgeSlice orig *id.Set term *id.Set }{ {"empty", nil, nil, nil}, {"single", zps{{0, 1}}, id.NewSet(0), id.NewSet(1)}, {"chain", zps{{0, 1}, {1, 2}, {2, 3}}, id.NewSet(0), id.NewSet(3)}, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { |
︙ | ︙ | |||
49 50 51 52 53 54 55 | func TestDigraphReachableVertices(t *testing.T) { t.Parallel() testcases := []struct { name string pairs id.EdgeSlice start id.Zid | | | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | func TestDigraphReachableVertices(t *testing.T) { t.Parallel() testcases := []struct { name string pairs id.EdgeSlice start id.Zid exp *id.Set }{ {"nil", nil, 0, nil}, {"0-2", zps{{1, 2}, {2, 3}}, 1, id.NewSet(2, 3)}, {"1,2", zps{{1, 2}, {2, 3}}, 2, id.NewSet(3)}, {"0-2,1-2", zps{{1, 2}, {2, 3}, {1, 3}}, 1, id.NewSet(2, 3)}, {"0-2,1-2/1", zps{{1, 2}, {2, 3}, {1, 3}}, 2, id.NewSet(3)}, {"0-2,1-2/2", zps{{1, 2}, {2, 3}, {1, 3}}, 3, nil}, |
︙ | ︙ |
Changes to zettel/id/edge.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // Copyright (c) 2023-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 "slices" // Edge is a pair of to vertices. | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2023-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2023-present Detlef Stern //----------------------------------------------------------------------------- package id import "slices" // Edge is a pair of to vertices. |
︙ | ︙ |
Changes to zettel/id/id.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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 zettel specific types, constants, and functions about // zettel identifier. package id import ( "strconv" "time" | > > > | | 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package id provides zettel specific types, constants, and functions about // zettel identifier. package id import ( "strconv" "time" "t73f.de/r/zsc/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 |
︙ | ︙ | |||
39 40 41 42 43 44 45 | 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) | < > | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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) DeleteTemplateZid = MustParse(api.ZidDeleteTemplate) ErrorTemplateZid = MustParse(api.ZidErrorTemplate) StartSxnZid = MustParse(api.ZidSxnStart) BaseSxnZid = MustParse(api.ZidSxnBase) PreludeSxnZid = MustParse(api.ZidSxnPrelude) EmojiZid = MustParse(api.ZidEmoji) TOCNewTemplateZid = MustParse(api.ZidTOCNewTemplate) MappingZid = MustParse(api.ZidMapping) DefaultHomeZid = MustParse(api.ZidDefaultHome) ) const maxZid = 99999999999999 // ParseUint interprets a string as a possible zettel identifier // and returns its integer value. |
︙ | ︙ | |||
96 97 98 99 100 101 102 103 104 105 106 107 108 109 | // 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[:] } | > > > | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | // Only defined for valid ids. func (zid Zid) String() string { var result [14]byte zid.toByteArray(&result) return string(result[:]) } // ZettelID return the zettel identification as a api.ZettelID. func (zid Zid) ZettelID() api.ZettelID { return api.ZettelID(zid.String()) } // Bytes converts the zettel identification to a byte slice of 14 digits. // Only defined for valid ids. func (zid Zid) Bytes() []byte { var result [14]byte zid.toByteArray(&result) return result[:] } |
︙ | ︙ | |||
155 156 157 158 159 160 161 | } res, err := Parse(s) if err != nil { panic(err) } return res } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | } res, err := Parse(s) if err != nil { panic(err) } return res } // ----- Base36 zettel identifier. // ZidN is the internal identifier of a zettel. It is a number in the range // 1..36^4-1 (1..1679615), as it is externally represented by four alphanumeric // characters. type ZidN uint32 // Some important ZettelIDs. const ( InvalidN = ZidN(0) // Invalid is a Zid that will never be valid ) const maxZidN = 36*36*36*36 - 1 // ParseUintN interprets a string as a possible zettel identifier // and returns its integer value. func ParseUintN(s string) (uint64, error) { res, err := strconv.ParseUint(s, 36, 21) if err != nil { return 0, err } if res == 0 || res > maxZidN { return res, strconv.ErrRange } return res, nil } // ParseN interprets a string as a zettel identification and // returns its value. func ParseN(s string) (ZidN, error) { if len(s) != 4 { return InvalidN, strconv.ErrSyntax } res, err := ParseUintN(s) if err != nil { return InvalidN, err } return ZidN(res), nil } // MustParseN tries to interpret a string as a zettel identifier and returns // its value or panics otherwise. func MustParseN(s api.ZettelID) ZidN { zid, err := ParseN(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 ZidN) String() string { var result [4]byte zid.toByteArray(&result) return string(result[:]) } // ZettelID return the zettel identification as a api.ZettelID. func (zid ZidN) ZettelID() api.ZettelID { return api.ZettelID(zid.String()) } // Bytes converts the zettel identification to a byte slice of 14 digits. // Only defined for valid ids. func (zid ZidN) Bytes() []byte { var result [4]byte zid.toByteArray(&result) return result[:] } // toByteArray converts the Zid into a fixed byte array, usable for printing. // // Based on idea by Daniel Lemire: "Converting integers to fix-digit representations quickly" // https://lemire.me/blog/2021/11/18/converting-integers-to-fix-digit-representations-quickly/ func (zid ZidN) toByteArray(result *[4]byte) { d12 := uint32(zid) / (36 * 36) d1 := d12 / 36 d2 := d12 % 36 d34 := uint32(zid) % (36 * 36) d3 := d34 / 36 d4 := d34 % 36 const digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" result[0] = digits[d1] result[1] = digits[d2] result[2] = digits[d3] result[3] = digits[d4] } // IsValid determines if zettel id is a valid one, e.g. consists of max. 14 digits. func (zid ZidN) IsValid() bool { return 0 < zid && zid <= maxZidN } |
Changes to zettel/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 | //----------------------------------------------------------------------------- // 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/zettel/id" ) func TestIsValid(t *testing.T) { t.Parallel() | > > > > | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package id_test provides unit tests for testing zettel id specific functions. package id_test import ( "strings" "testing" "zettelstore.de/z/zettel/id" ) func TestIsValid(t *testing.T) { t.Parallel() |
︙ | ︙ | |||
68 69 70 71 72 73 74 | } } var sResult string // to disable compiler optimization in loop below func BenchmarkString(b *testing.B) { var s string | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | } } var sResult string // to disable compiler optimization in loop below func BenchmarkString(b *testing.B) { var s string for range b.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 range b.N { bs = id.Zid(12345678901200).Bytes() } bResult = bs } // ----- Base-36 identifier func TestIsValidN(t *testing.T) { t.Parallel() validIDs := []string{ "0001", "0020", "0300", "4000", "zzzz", "ZZZZ", "Cafe", "bAbE", } for i, sid := range validIDs { zid, err := id.ParseN(sid) if err != nil { t.Errorf("i=%d: sid=%q is not valid, but should be. err=%v", i, sid, err) } if s := zid.String(); !strings.EqualFold(s, sid) { t.Errorf("i=%d: zid=%v does not format to %q, but to %q", i, zid, sid, s) } } invalidIDs := []string{ "", "0", "a", "de", "dfg", "abcde", "012.", "+1234", "+123", } for i, sid := range invalidIDs { if zid, err := id.ParseN(sid); err == nil { t.Errorf("i=%d: sid=%q is valid (zid=%s), but should not be", i, sid, zid) } } } |
Changes to zettel/id/set.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // 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 ( | > > > | | > | > | | > > > > > | | < | < | | | > | < > > | | | | | > | > | < | | | > | > | | > > > | > | | | | | | | < < < | < < < | < < < | < < < < < < < < < < < < < < < | | | > | | | < < | < < < | | | | > | | > > > | | > > > | > | > > > | | | > > > | | > > | > | | | > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > | > > > > > > | > > > > > > > > > > > > > > > | | | > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package id import ( "slices" "strings" ) // Set is a set of zettel identifier type Set struct { seq []Zid } // String returns a string representation of the set. func (s *Set) String() string { return "{" + s.MetaString() + "}" } // MetaString returns a string representation of the set to be stored as metadata. func (s *Set) MetaString() string { if s == nil || len(s.seq) == 0 { return "" } var sb strings.Builder for i, zid := range s.seq { if i > 0 { sb.WriteByte(' ') } sb.Write(zid.Bytes()) } return sb.String() } // NewSet returns a new set of identifier with the given initial values. func NewSet(zids ...Zid) *Set { switch l := len(zids); l { case 0: return &Set{seq: nil} case 1: return &Set{seq: []Zid{zids[0]}} default: result := Set{seq: make(Slice, 0, 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 { result := Set{seq: make(Slice, 0, max(c, len(zids)))} result.AddSlice(zids) return &result } // IsEmpty returns true, if the set conains no element. func (s *Set) IsEmpty() bool { return s == nil || len(s.seq) == 0 } // Length returns the number of elements in this set. func (s *Set) Length() int { if s == nil { return 0 } return len(s.seq) } // Clone returns a copy of the given set. func (s *Set) Clone() *Set { if s == nil || len(s.seq) == 0 { return nil } return &Set{seq: slices.Clone(s.seq)} } // Add adds a Add to the set. func (s *Set) Add(zid Zid) *Set { if s == nil { return NewSet(zid) } s.add(zid) return s } // Contains return true if the set is non-nil and the set contains the given Zettel identifier. func (s *Set) Contains(zid Zid) bool { return s != nil && s.contains(zid) } // ContainsOrNil return true if the set is nil or if the set contains the given Zettel identifier. func (s *Set) ContainsOrNil(zid Zid) bool { return s == nil || s.contains(zid) } // AddSlice adds all identifier of the given slice to the set. func (s *Set) AddSlice(sl Slice) *Set { if s == nil { return NewSet(sl...) } s.seq = slices.Grow(s.seq, len(sl)) for _, zid := range sl { s.add(zid) } return s } // SafeSorted returns the set as a new sorted slice of zettel identifier. func (s *Set) SafeSorted() Slice { if s == nil { return nil } return slices.Clone(s.seq) } // 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 || other == nil { return other } topos, spos, opos := 0, 0, 0 for spos < len(s.seq) && opos < len(other.seq) { sz, oz := s.seq[spos], other.seq[opos] if sz < oz { spos++ continue } if sz > oz { opos++ continue } s.seq[topos] = sz topos++ spos++ opos++ } s.seq = s.seq[:topos] return s } // IUnion adds the elements of set other to s. func (s *Set) IUnion(other *Set) *Set { if other == nil || len(other.seq) == 0 { return s } // TODO: if other is large enough (and s is not too small) -> optimize by swapping and/or loop through both return s.AddSlice(other.seq) } // ISubstract removes all zettel identifier from 's' that are in the set 'other'. func (s *Set) ISubstract(other *Set) { if s == nil || len(s.seq) == 0 || other == nil || len(other.seq) == 0 { return } topos, spos, opos := 0, 0, 0 for spos < len(s.seq) && opos < len(other.seq) { sz, oz := s.seq[spos], other.seq[opos] if sz < oz { s.seq[topos] = sz topos++ spos++ continue } if sz == oz { spos++ } opos++ } for spos < len(s.seq) { s.seq[topos] = s.seq[spos] topos++ spos++ } s.seq = s.seq[:topos] } // Diff returns the difference sets between the two sets: the first difference // set is the set of elements that are in other, but not in s; the second // difference set is the set of element that are in s but not in other. // // in other words: the first result is the set of elements from other that must // be added to s; the second result is the set of elements that must be removed // from s, so that s would have the same elemest as other. func (s *Set) Diff(other *Set) (newS, remS *Set) { if s == nil || len(s.seq) == 0 { return other.Clone(), nil } if other == nil || len(other.seq) == 0 { return nil, s.Clone() } seqS, seqO := s.seq, other.seq var newRefs, remRefs Slice npos, opos := 0, 0 for npos < len(seqO) && opos < len(seqS) { rn, ro := seqO[npos], seqS[opos] if rn == ro { npos++ opos++ continue } if rn < ro { newRefs = append(newRefs, rn) npos++ continue } remRefs = append(remRefs, ro) opos++ } if npos < len(seqO) { newRefs = append(newRefs, seqO[npos:]...) } if opos < len(seqS) { remRefs = append(remRefs, seqS[opos:]...) } return newFromSlice(newRefs), newFromSlice(remRefs) } // Remove the identifier from the set. func (s *Set) Remove(zid Zid) *Set { if s == nil || len(s.seq) == 0 { return nil } if pos, found := s.find(zid); found { copy(s.seq[pos:], s.seq[pos+1:]) s.seq = s.seq[:len(s.seq)-1] } if len(s.seq) == 0 { return nil } return s } // Equal returns true if the other set is equal to the given set. func (s *Set) Equal(other *Set) bool { if s == nil { return other == nil } if other == nil { return false } return slices.Equal(s.seq, other.seq) } // ForEach calls the given function for each element of the set. // // Every element is bigger than the previous one. func (s *Set) ForEach(fn func(zid Zid)) { if s != nil { for _, zid := range s.seq { fn(zid) } } } // Pop return one arbitrary element of the set. func (s *Set) Pop() (Zid, bool) { if s != nil { if l := len(s.seq); l > 0 { zid := s.seq[l-1] s.seq = s.seq[:l-1] return zid, true } } return Invalid, false } // Optimize the amount of memory to store the set. func (s *Set) Optimize() { if s != nil { s.seq = slices.Clip(s.seq) } } // ----- unchecked base operations func newFromSlice(seq Slice) *Set { if l := len(seq); l == 0 { return nil } else { return &Set{seq: seq} } } func (s *Set) add(zid Zid) { if pos, found := s.find(zid); !found { s.seq = slices.Insert(s.seq, pos, zid) } } func (s *Set) contains(zid Zid) bool { _, found := s.find(zid) return found } func (s *Set) find(zid Zid) (int, bool) { hi := len(s.seq) for lo := 0; lo < hi; { m := lo + (hi-lo)/2 if z := s.seq[m]; z == zid { return m, true } else if z < zid { lo = m + 1 } else { hi = m } } return hi, false } |
Changes to zettel/id/set_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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/zettel/id" ) | > > > | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | < < < < < < | | | | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package id_test import ( "testing" "zettelstore.de/z/zettel/id" ) func TestSetContainsOrNil(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.ContainsOrNil(tc.zid) if got != tc.exp { t.Errorf("%d: %v.ContainsOrNil(%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.SafeSorted() sl2 := tc.s2.SafeSorted() got := tc.s1.IUnion(tc.s2).SafeSorted() if !got.Equal(tc.exp) { t.Errorf("%d: %v.Add(%v) should be %v, but got %v", i, sl1, sl2, tc.exp, got) } } } func TestSetSafeSorted(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.SafeSorted() if !got.Equal(tc.exp) { t.Errorf("%d: %v.SafeSorted() 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.SafeSorted() sl2 := tc.s2.SafeSorted() got := tc.s1.IntersectOrSet(tc.s2).SafeSorted() if !got.Equal(tc.exp) { t.Errorf("%d: %v.IntersectOrSet(%v) should be %v, but got %v", i, sl1, sl2, tc.exp, got) } } } func TestSetIUnion(t *testing.T) { t.Parallel() testcases := []struct { s1, s2 *id.Set exp *id.Set }{ {nil, nil, nil}, {id.NewSet(), nil, nil}, {nil, id.NewSet(), nil}, {id.NewSet(), id.NewSet(), nil}, {id.NewSet(1), nil, id.NewSet(1)}, {nil, id.NewSet(1), id.NewSet(1)}, {id.NewSet(1), id.NewSet(), id.NewSet(1)}, {id.NewSet(), id.NewSet(1), id.NewSet(1)}, {id.NewSet(1), id.NewSet(2), id.NewSet(1, 2)}, {id.NewSet(2), id.NewSet(1), id.NewSet(2, 1)}, {id.NewSet(1), id.NewSet(1), id.NewSet(1)}, {id.NewSet(1, 2, 3), id.NewSet(2, 3, 4), id.NewSet(1, 2, 3, 4)}, } for i, tc := range testcases { s1 := tc.s1.Clone() sl1 := s1.SafeSorted() sl2 := tc.s2.SafeSorted() got := s1.IUnion(tc.s2) if !got.Equal(tc.exp) { t.Errorf("%d: %v.IUnion(%v) should be %v, but got %v", i, sl1, sl2, tc.exp, got) } } } func TestSetISubtract(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, id.Slice{1}}, {nil, id.NewSet(1), nil}, {id.NewSet(1), id.NewSet(), id.Slice{1}}, {id.NewSet(), id.NewSet(1), nil}, {id.NewSet(1), id.NewSet(2), id.Slice{1}}, {id.NewSet(2), id.NewSet(1), id.Slice{2}}, {id.NewSet(1), id.NewSet(1), nil}, {id.NewSet(1, 2, 3), id.NewSet(1), id.Slice{2, 3}}, {id.NewSet(1, 2, 3), id.NewSet(2), id.Slice{1, 3}}, {id.NewSet(1, 2, 3), id.NewSet(3), id.Slice{1, 2}}, {id.NewSet(1, 2, 3), id.NewSet(1, 2), id.Slice{3}}, {id.NewSet(1, 2, 3), id.NewSet(1, 3), id.Slice{2}}, {id.NewSet(1, 2, 3), id.NewSet(2, 3), id.Slice{1}}, } for i, tc := range testcases { s1 := tc.s1.Clone() sl1 := s1.SafeSorted() sl2 := tc.s2.SafeSorted() s1.ISubstract(tc.s2) got := s1.SafeSorted() if !got.Equal(tc.exp) { t.Errorf("%d: %v.ISubstract(%v) should be %v, but got %v", i, sl1, sl2, tc.exp, got) } } } func TestSetDiff(t *testing.T) { t.Parallel() testcases := []struct { in1, in2 *id.Set exp1, exp2 *id.Set }{ {nil, nil, nil, nil}, {id.NewSet(1), nil, nil, id.NewSet(1)}, {nil, id.NewSet(1), id.NewSet(1), nil}, {id.NewSet(1), id.NewSet(1), nil, nil}, {id.NewSet(1, 2), id.NewSet(1), nil, id.NewSet(2)}, {id.NewSet(1), id.NewSet(1, 2), id.NewSet(2), nil}, {id.NewSet(1, 2), id.NewSet(1, 3), id.NewSet(3), id.NewSet(2)}, {id.NewSet(1, 2, 3), id.NewSet(2, 3, 4), id.NewSet(4), id.NewSet(1)}, {id.NewSet(2, 3, 4), id.NewSet(1, 2, 3), id.NewSet(1), id.NewSet(4)}, } for i, tc := range testcases { gotN, gotO := tc.in1.Diff(tc.in2) if !tc.exp1.Equal(gotN) { t.Errorf("%d: expected %v, but got: %v", i, tc.exp1, gotN) } if !tc.exp2.Equal(gotO) { t.Errorf("%d: expected %v, but got: %v", i, tc.exp2, gotO) } } } 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.SafeSorted() sl2 := tc.s2.SafeSorted() newS1 := id.NewSet(sl1...) newS1.ISubstract(tc.s2) got := newS1.SafeSorted() 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.NewSetCap(b.N) for i := range b.N { s.Add(id.Zid(i)) } } |
Changes to zettel/id/slice.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 ( "slices" "strings" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package id import ( "slices" "strings" |
︙ | ︙ | |||
24 25 26 27 28 29 30 | // Clone a zettel identifier slice func (zs Slice) Clone() Slice { return slices.Clone(zs) } // 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 { return slices.Equal(zs, other) } | > | | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | // Clone a zettel identifier slice func (zs Slice) Clone() Slice { return slices.Clone(zs) } // 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 { return slices.Equal(zs, other) } // MetaString returns the slice as a string to be store in metadata. func (zs Slice) MetaString() 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() } |
Changes to zettel/id/slice_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package id_test import ( "testing" |
︙ | ︙ | |||
62 63 64 65 66 67 68 | 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) } } } | | | | 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 | 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 TestSliceMetaString(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.MetaString() if got != tc.exp { t.Errorf("%d/%v: expected %q, but got %q", i, tc.in, tc.exp, got) } } } |
Changes to zettel/meta/collection.go.
1 2 3 4 5 6 7 8 9 10 11 12 | //----------------------------------------------------------------------------- // 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 | > > > | > > > | 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 | //----------------------------------------------------------------------------- // 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2022-present Detlef Stern //----------------------------------------------------------------------------- package meta import ( "slices" "strings" ) // 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. |
︙ | ︙ | |||
78 79 80 81 82 83 84 | // 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() { | | | | | | | | 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 | // 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() { slices.SortFunc(ccs, func(i, j CountedCategory) int { return strings.Compare(i.Name, 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() { slices.SortFunc(ccs, func(i, j CountedCategory) int { iCount, jCount := i.Count, j.Count if iCount > jCount { return -1 } if iCount == jCount { return strings.Compare(i.Name, j.Name) } return 1 }) } // 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 } |
Changes to zettel/meta/meta.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 zettel specific type 'meta'. package meta import ( "regexp" | > > > | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package meta provides the zettel specific type 'meta'. package meta import ( "regexp" "slices" "strings" "unicode" "unicode/utf8" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "t73f.de/r/zsc/maps" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" ) type keyUsage int const ( |
︙ | ︙ | |||
44 45 46 47 48 49 50 | // 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 } | < < < | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | // 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 } 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 != "" { |
︙ | ︙ | |||
113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 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, "") | > > > > | 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | result := make([]*DescriptionKey, 0, len(keys)) for _, n := range keys { result = append(result, registeredKeys[n]) } return result } // KeyCreatedMissing is temporary until migration to B36 has ended. // It is not an "official" key to be designed to last long. const KeyCreatedMissing = "created-missing" // 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, "") |
︙ | ︙ | |||
134 135 136 137 138 139 140 141 142 143 144 145 146 147 | 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.KeyExpire, TypeTimestamp, usageUser, "") registerKey(api.KeyFolgeRole, TypeWord, usageUser, "") registerKey(api.KeyForward, TypeIDSet, usageProperty, "") registerKey(api.KeyLang, TypeWord, usageUser, "") registerKey(api.KeyLicense, TypeEmpty, usageUser, "") registerKey(api.KeyModified, TypeTimestamp, usageComputed, "") | > | 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | 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(KeyCreatedMissing, TypeWord, usageProperty, "") registerKey(api.KeyDead, TypeIDSet, usageProperty, "") registerKey(api.KeyExpire, TypeTimestamp, usageUser, "") registerKey(api.KeyFolgeRole, TypeWord, usageUser, "") registerKey(api.KeyForward, TypeIDSet, usageProperty, "") registerKey(api.KeyLang, TypeWord, usageUser, "") registerKey(api.KeyLicense, TypeEmpty, usageUser, "") registerKey(api.KeyModified, TypeTimestamp, usageComputed, "") |
︙ | ︙ | |||
161 162 163 164 165 166 167 168 169 170 171 172 173 174 | // 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)} | > | 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | // 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 ZidN id.ZidN 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)} |
︙ | ︙ | |||
195 196 197 198 199 200 201 202 203 204 205 206 207 208 | 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 { | > | 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | return result } // Clone returns a new copy of the metadata. func (m *Meta) Clone() *Meta { return &Meta{ Zid: m.Zid, ZidN: m.ZidN, 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 { |
︙ | ︙ | |||
335 336 337 338 339 340 341 | 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) } } | | | 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 | 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) } } slices.Sort(keys) return keys } // Delete removes a key from the data. func (m *Meta) Delete(key string) { if key != api.KeyID { delete(m.pairs, key) |
︙ | ︙ |
Changes to zettel/meta/meta_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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" "testing" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package meta import ( "strings" "testing" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" ) const testID = id.Zid(98765432101234) func TestKeyIsValid(t *testing.T) { t.Parallel() |
︙ | ︙ | |||
227 228 229 230 231 232 233 | if m2.Equal(m1, true) { t.Error("Different ID should differentiate") } } func pairs2meta(pairs []string) *Meta { m := New(testID) | | | 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | 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 += 2 { m.Set(pairs[i], pairs[i+1]) } return m } func TestRemoveNonGraphic(t *testing.T) { testCases := []struct { |
︙ | ︙ |
Changes to zettel/meta/parse.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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" | > > > | | | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package meta import ( "strings" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "t73f.de/r/zsc/maps" "zettelstore.de/z/strfun" "zettelstore.de/z/zettel/id" ) // 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 { inp.SkipSpace() switch inp.Ch { case '\r': if inp.Peek() == '\n' { inp.Next() } fallthrough case '\n': |
︙ | ︙ | |||
57 58 59 60 61 62 63 | func parseHeader(m *Meta, inp *input.Input) { pos := inp.Pos for isHeader(inp.Ch) { inp.Next() } key := inp.Src[pos:inp.Pos] | | | | < < < < < < | 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 | func parseHeader(m *Meta, inp *input.Input) { pos := inp.Pos for isHeader(inp.Ch) { inp.Next() } key := inp.Src[pos:inp.Pos] inp.SkipSpace() if inp.Ch == ':' { inp.Next() } var val []byte for { inp.SkipSpace() pos = inp.Pos skipToEOL(inp) val = append(val, inp.Src[pos:inp.Pos]...) inp.EatEOL() if !inp.IsSpace() { break } val = append(val, ' ') } addToMeta(m, string(key), string(val)) } func skipToEOL(inp *input.Input) { for { switch inp.Ch { case '\n', '\r', input.EOS: return } inp.Next() |
︙ | ︙ | |||
152 153 154 155 156 157 158 | } 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)) | < < | 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | } 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 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) |
︙ | ︙ |
Changes to zettel/meta/parse_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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" | > > > | | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package meta_test import ( "strings" "testing" "t73f.de/r/zsc/api" "t73f.de/r/zsc/input" "zettelstore.de/z/zettel/meta" ) func parseMetaStr(src string) *meta.Meta { return meta.NewFromInput(testID, input.NewInput([]byte(src))) } |
︙ | ︙ | |||
133 134 135 136 137 138 139 | } } func equalPairs(one, two []meta.Pair) bool { if len(one) != len(two) { return false } | | | 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | } } func equalPairs(one, two []meta.Pair) bool { if len(one) != len(two) { return false } for i := range len(one) { if one[i].Key != two[i].Key || one[i].Value != two[i].Value { return false } } return true } |
︙ | ︙ |
Changes to zettel/meta/type.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // 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" | > > > | | 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. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package meta import ( "strconv" "strings" "sync" "time" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" ) // DescriptionType is a description of a specific key type. type DescriptionType struct { Name string IsSet bool |
︙ | ︙ | |||
48 49 50 51 52 53 54 | 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) | < > > > > > > | | | < | | | | | | | | > > > | > | 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 | TypeIDSet = registerType(api.MetaIDSet, true) TypeNumber = registerType(api.MetaNumber, false) TypeString = registerType(api.MetaString, false) TypeTagSet = registerType(api.MetaTagSet, true) TypeTimestamp = registerType(api.MetaTimestamp, false) TypeURL = registerType(api.MetaURL, false) TypeWord = registerType(api.MetaWord, false) TypeZettelmarkup = registerType(api.MetaZettelmarkup, false) ) // Type returns a type hint for the given key. If no type hint is specified, // TypeUnknown is returned. func (*Meta) Type(key string) *DescriptionType { return Type(key) } // Some constants for key suffixes that determine a type. const ( SuffixKeyRole = "-role" SuffixKeyURL = "-url" ) var ( cachedTypedKeys = make(map[string]*DescriptionType) mxTypedKey sync.RWMutex suffixTypes = map[string]*DescriptionType{ "-date": TypeTimestamp, "-number": TypeNumber, SuffixKeyRole: TypeWord, "-time": TypeTimestamp, "-title": TypeZettelmarkup, SuffixKeyURL: TypeURL, "-zettel": TypeID, "-zid": TypeID, "-zids": TypeIDSet, } ) // Type returns a type hint for the given key. If no type hint is specified, // TypeEmpty is returned. func Type(key string) *DescriptionType { if k, ok := registeredKeys[key]; ok { return k.Type } mxTypedKey.RLock() k, found := cachedTypedKeys[key] mxTypedKey.RUnlock() if found { return k } for suffix, t := range suffixTypes { if strings.HasSuffix(key, suffix) { mxTypedKey.Lock() defer mxTypedKey.Unlock() // Double check to avoid races if _, found = cachedTypedKeys[key]; !found { cachedTypedKeys[key] = t } return t } } return TypeEmpty } // SetList stores the given string list value under the given key. |
︙ | ︙ |
Changes to zettel/meta/type_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package meta_test import ( "strconv" "testing" |
︙ | ︙ |
Changes to zettel/meta/values.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package meta import ( "fmt" "t73f.de/r/zsc/api" ) // Supported syntax values. const ( SyntaxCSS = api.ValueSyntaxCSS SyntaxDraw = api.ValueSyntaxDraw SyntaxGif = api.ValueSyntaxGif |
︙ | ︙ | |||
31 32 33 34 35 36 37 38 39 40 41 42 43 44 | SyntaxPNG = "png" SyntaxSVG = api.ValueSyntaxSVG SyntaxSxn = api.ValueSyntaxSxn 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 ( | > > | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | SyntaxPNG = "png" SyntaxSVG = api.ValueSyntaxSVG SyntaxSxn = api.ValueSyntaxSxn SyntaxText = api.ValueSyntaxText SyntaxTxt = "txt" SyntaxWebp = "webp" SyntaxZmk = api.ValueSyntaxZmk DefaultSyntax = SyntaxPlain ) // Visibility enumerates the variations of the 'visibility' meta key. type Visibility int // Supported values for visibility. const ( |
︙ | ︙ |
Changes to zettel/meta/write.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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. | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package meta import "io" // Write writes metadata to a writer, excluding computed and propery values. |
︙ | ︙ |
Changes to zettel/meta/write_test.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //----------------------------------------------------------------------------- // 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" | > > > | | 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package meta_test import ( "strings" "testing" "t73f.de/r/zsc/api" "zettelstore.de/z/zettel/id" "zettelstore.de/z/zettel/meta" ) const testID = id.Zid(98765432101234) func newMeta(title string, tags []string, syntax string) *meta.Meta { |
︙ | ︙ |
Changes to zettel/zettel.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- // 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 zettel provides specific types, constants, and functions for zettel. package zettel import "zettelstore.de/z/zettel/meta" | > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- // Copyright (c) 2020-present Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package zettel provides specific types, constants, and functions for zettel. package zettel import "zettelstore.de/z/zettel/meta" |
︙ | ︙ |