Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -1,1 +1,1 @@ -0.19.0 +0.21.0-dev DELETED ast/ast.go Index: ast/ast.go ================================================================== --- ast/ast.go +++ /dev/null @@ -1,92 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - - "zettelstore.de/z/zettel" - "zettelstore.de/z/zettel/id" - "zettelstore.de/z/zettel/meta" -) - -// ZettelNode is the root node of the abstract syntax tree. -// It is *not* part of the visitor pattern. -type ZettelNode struct { - Meta *meta.Meta // Original metadata - Content zettel.Content // Original content - Zid id.Zid // Zettel identification. - InhMeta *meta.Meta // Metadata of the zettel, with inherited values. - Ast BlockSlice // Zettel abstract syntax tree is a sequence of block nodes. - Syntax string // Syntax / parser that produced the Ast -} - -// Node is the interface, all nodes must implement. -type Node interface { - WalkChildren(v Visitor) -} - -// BlockNode is the interface that all block nodes must implement. -type BlockNode interface { - Node - blockNode() -} - -// ItemNode is a node that can occur as a list item. -type ItemNode interface { - BlockNode - itemNode() -} - -// ItemSlice is a slice of ItemNodes. -type ItemSlice []ItemNode - -// DescriptionNode is a node that contains just textual description. -type DescriptionNode interface { - ItemNode - descriptionNode() -} - -// DescriptionSlice is a slice of DescriptionNodes. -type DescriptionSlice []DescriptionNode - -// InlineNode is the interface that all inline nodes must implement. -type InlineNode interface { - Node - inlineNode() -} - -// Reference is a reference to external or internal material. -type Reference struct { - URL *url.URL - Value string - State RefState -} - -// RefState indicates the state of the reference. -type RefState int - -// Constants for RefState -const ( - RefStateInvalid RefState = iota // Invalid Reference - RefStateZettel // Reference to an internal zettel - RefStateSelf // Reference to same zettel with a fragment - RefStateFound // Reference to an existing internal zettel, URL is ajusted - RefStateBroken // Reference to a non-existing internal zettel - RefStateHosted // Reference to local hosted non-Zettel, without URL change - RefStateBased // Reference to local non-Zettel, to be prefixed - RefStateQuery // Reference to a zettel query - RefStateExternal // Reference to external material -) DELETED ast/block.go Index: ast/block.go ================================================================== --- ast/block.go +++ /dev/null @@ -1,295 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 */ } - -// WalkChildren walks down to the descriptions. -func (bs *BlockSlice) WalkChildren(v Visitor) { - if bs != nil { - for _, bn := range *bs { - Walk(v, bn) - } - } -} - -// FirstParagraphInlines returns the inline list of the first paragraph that -// contains a inline list. -func (bs BlockSlice) FirstParagraphInlines() InlineSlice { - for _, bn := range bs { - pn, ok := bn.(*ParaNode) - if !ok { - continue - } - if inl := pn.Inlines; len(inl) > 0 { - return inl - } - } - return nil -} - -//-------------------------------------------------------------------------- - -// ParaNode contains just a sequence of inline elements. -// Another name is "paragraph". -type ParaNode struct { - Inlines InlineSlice -} - -func (*ParaNode) blockNode() { /* Just a marker */ } -func (*ParaNode) itemNode() { /* Just a marker */ } -func (*ParaNode) descriptionNode() { /* Just a marker */ } - -// CreateParaNode creates a parameter block from inline nodes. -func CreateParaNode(nodes ...InlineNode) *ParaNode { return &ParaNode{Inlines: nodes} } - -// WalkChildren walks down the inline elements. -func (pn *ParaNode) WalkChildren(v Visitor) { Walk(v, &pn.Inlines) } - -//-------------------------------------------------------------------------- - -// VerbatimNode contains uninterpreted text -type VerbatimNode struct { - Kind VerbatimKind - Attrs attrs.Attributes - Content []byte -} - -// VerbatimKind specifies the format that is applied to code inline nodes. -type VerbatimKind int - -// Constants for VerbatimCode -const ( - _ VerbatimKind = iota - VerbatimZettel // Zettel content - VerbatimProg // Program code - VerbatimEval // Code to be externally interpreted. Syntax is stored in default attribute. - VerbatimComment // Block comment - VerbatimHTML // Block HTML, e.g. for Markdown - VerbatimMath // Block math mode -) - -func (*VerbatimNode) blockNode() { /* Just a marker */ } -func (*VerbatimNode) itemNode() { /* Just a marker */ } - -// WalkChildren does nothing. -func (*VerbatimNode) WalkChildren(Visitor) { /* No children*/ } - -//-------------------------------------------------------------------------- - -// RegionNode encapsulates a region of block nodes. -type RegionNode struct { - Kind RegionKind - Attrs attrs.Attributes - Blocks BlockSlice - Inlines InlineSlice // Optional text at the end of the region -} - -// RegionKind specifies the actual region type. -type RegionKind int - -// Values for RegionCode -const ( - _ RegionKind = iota - RegionSpan // Just a span of blocks - RegionQuote // A longer quotation - RegionVerse // Line breaks matter -) - -func (*RegionNode) blockNode() { /* Just a marker */ } -func (*RegionNode) itemNode() { /* Just a marker */ } - -// WalkChildren walks down the blocks and the text. -func (rn *RegionNode) WalkChildren(v Visitor) { - Walk(v, &rn.Blocks) - Walk(v, &rn.Inlines) -} - -//-------------------------------------------------------------------------- - -// HeadingNode stores the heading text and level. -type HeadingNode struct { - Level int - Attrs attrs.Attributes - Slug string // Heading text, normalized - Fragment string // Heading text, suitable to be used as an unique URL fragment - Inlines InlineSlice // Heading text, possibly formatted -} - -func (*HeadingNode) blockNode() { /* Just a marker */ } -func (*HeadingNode) itemNode() { /* Just a marker */ } - -// WalkChildren walks the heading text. -func (hn *HeadingNode) WalkChildren(v Visitor) { Walk(v, &hn.Inlines) } - -//-------------------------------------------------------------------------- - -// HRuleNode specifies a horizontal rule. -type HRuleNode struct { - Attrs attrs.Attributes -} - -func (*HRuleNode) blockNode() { /* Just a marker */ } -func (*HRuleNode) itemNode() { /* Just a marker */ } - -// WalkChildren does nothing. -func (*HRuleNode) WalkChildren(Visitor) { /* No children*/ } - -//-------------------------------------------------------------------------- - -// NestedListNode specifies a nestable list, either ordered or unordered. -type NestedListNode struct { - Kind NestedListKind - Items []ItemSlice - Attrs attrs.Attributes -} - -// NestedListKind specifies the actual list type. -type NestedListKind uint8 - -// Values for ListCode -const ( - _ NestedListKind = iota - NestedListOrdered // Ordered list. - NestedListUnordered // Unordered list. - NestedListQuote // Quote list. -) - -func (*NestedListNode) blockNode() { /* Just a marker */ } -func (*NestedListNode) itemNode() { /* Just a marker */ } - -// WalkChildren walks down the items. -func (ln *NestedListNode) WalkChildren(v Visitor) { - if items := ln.Items; items != nil { - for _, item := range items { - WalkItemSlice(v, item) - } - } -} - -//-------------------------------------------------------------------------- - -// DescriptionListNode specifies a description list. -type DescriptionListNode struct { - Descriptions []Description -} - -// Description is one element of a description list. -type Description struct { - Term InlineSlice - Descriptions []DescriptionSlice -} - -func (*DescriptionListNode) blockNode() { /* Just a marker */ } - -// WalkChildren walks down to the descriptions. -func (dn *DescriptionListNode) WalkChildren(v Visitor) { - if descrs := dn.Descriptions; descrs != nil { - for i, desc := range descrs { - if len(desc.Term) > 0 { - Walk(v, &descrs[i].Term) // Otherwise, changes in desc.Term will not go back into AST - } - if dss := desc.Descriptions; dss != nil { - for _, dns := range dss { - WalkDescriptionSlice(v, dns) - } - } - } - } -} - -//-------------------------------------------------------------------------- - -// TableNode specifies a full table -type TableNode struct { - Header TableRow // The header row - Align []Alignment // Default column alignment - Rows []TableRow // The slice of cell rows -} - -// TableCell contains the data for one table cell -type TableCell struct { - Align Alignment // Cell alignment - Inlines InlineSlice // Cell content -} - -// TableRow is a slice of cells. -type TableRow []*TableCell - -// Alignment specifies text alignment. -// Currently only for tables. -type Alignment int - -// Constants for Alignment. -const ( - _ Alignment = iota - AlignDefault // Default alignment, inherited - AlignLeft // Left alignment - AlignCenter // Center the content - AlignRight // Right alignment -) - -func (*TableNode) blockNode() { /* Just a marker */ } - -// WalkChildren walks down to the cells. -func (tn *TableNode) WalkChildren(v Visitor) { - if header := tn.Header; header != nil { - for i := range header { - Walk(v, &header[i].Inlines) // Otherwise changes will not go back - } - } - if rows := tn.Rows; rows != nil { - for _, row := range rows { - for i := range row { - Walk(v, &row[i].Inlines) // Otherwise changes will not go back - } - } - } -} - -//-------------------------------------------------------------------------- - -// TranscludeNode specifies block content from other zettel to embedded in -// current zettel -type TranscludeNode struct { - Attrs attrs.Attributes - Ref *Reference -} - -func (*TranscludeNode) blockNode() { /* Just a marker */ } - -// WalkChildren does nothing. -func (*TranscludeNode) WalkChildren(Visitor) { /* No children*/ } - -//-------------------------------------------------------------------------- - -// BLOBNode contains just binary data that must be interpreted according to -// a syntax. -type BLOBNode struct { - Description InlineSlice - Syntax string - Blob []byte -} - -func (*BLOBNode) blockNode() { /* Just a marker */ } - -// WalkChildren does nothing. -func (*BLOBNode) WalkChildren(Visitor) { /* No children*/ } DELETED ast/inline.go Index: ast/inline.go ================================================================== --- ast/inline.go +++ /dev/null @@ -1,212 +0,0 @@ -//----------------------------------------------------------------------------- -// 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? -} - -func (*BreakNode) inlineNode() { /* Just a marker */ } - -// WalkChildren does nothing. -func (*BreakNode) WalkChildren(Visitor) { /* No children*/ } - -// -------------------------------------------------------------------------- - -// LinkNode contains the specified link. -type LinkNode struct { - Attrs attrs.Attributes // Optional attributes - Ref *Reference - Inlines InlineSlice // The text associated with the link. -} - -func (*LinkNode) inlineNode() { /* Just a marker */ } - -// WalkChildren walks to the link text. -func (ln *LinkNode) WalkChildren(v Visitor) { - if len(ln.Inlines) > 0 { - Walk(v, &ln.Inlines) - } -} - -// -------------------------------------------------------------------------- - -// EmbedRefNode contains the specified embedded reference material. -type EmbedRefNode struct { - Attrs attrs.Attributes // Optional attributes - Ref *Reference // The reference to be embedded. - Syntax string // Syntax of referenced material, if known - Inlines InlineSlice // Optional text associated with the image. -} - -func (*EmbedRefNode) inlineNode() { /* Just a marker */ } - -// WalkChildren walks to the text that describes the embedded material. -func (en *EmbedRefNode) WalkChildren(v Visitor) { Walk(v, &en.Inlines) } - -// -------------------------------------------------------------------------- - -// EmbedBLOBNode contains the specified embedded BLOB material. -type EmbedBLOBNode struct { - Attrs attrs.Attributes // Optional attributes - Syntax string // Syntax of Blob - Blob []byte // BLOB data itself. - Inlines InlineSlice // Optional text associated with the image. -} - -func (*EmbedBLOBNode) inlineNode() { /* Just a marker */ } - -// WalkChildren walks to the text that describes the embedded material. -func (en *EmbedBLOBNode) WalkChildren(v Visitor) { Walk(v, &en.Inlines) } - -// -------------------------------------------------------------------------- - -// CiteNode contains the specified citation. -type CiteNode struct { - Attrs attrs.Attributes // Optional attributes - Key string // The citation key - Inlines InlineSlice // Optional text associated with the citation. -} - -func (*CiteNode) inlineNode() { /* Just a marker */ } - -// WalkChildren walks to the cite text. -func (cn *CiteNode) WalkChildren(v Visitor) { Walk(v, &cn.Inlines) } - -// -------------------------------------------------------------------------- - -// MarkNode contains the specified merked position. -// It is a BlockNode too, because although it is typically parsed during inline -// mode, it is moved into block mode afterwards. -type MarkNode struct { - Mark string // The mark text itself - Slug string // Slugified form of Mark - Fragment string // Unique form of Slug - Inlines InlineSlice // Marked inline content -} - -func (*MarkNode) inlineNode() { /* Just a marker */ } - -// WalkChildren does nothing. -func (mn *MarkNode) WalkChildren(v Visitor) { - if len(mn.Inlines) > 0 { - Walk(v, &mn.Inlines) - } -} - -// -------------------------------------------------------------------------- - -// FootnoteNode contains the specified footnote. -type FootnoteNode struct { - Attrs attrs.Attributes // Optional attributes - Inlines InlineSlice // The footnote text. -} - -func (*FootnoteNode) inlineNode() { /* Just a marker */ } - -// WalkChildren walks to the footnote text. -func (fn *FootnoteNode) WalkChildren(v Visitor) { Walk(v, &fn.Inlines) } - -// -------------------------------------------------------------------------- - -// FormatNode specifies some inline formatting. -type FormatNode struct { - Kind FormatKind - Attrs attrs.Attributes // Optional attributes. - Inlines InlineSlice -} - -// FormatKind specifies the format that is applied to the inline nodes. -type FormatKind int - -// Constants for FormatCode -const ( - _ FormatKind = iota - FormatEmph // Emphasized text - FormatStrong // Strongly emphasized text - FormatInsert // Inserted text - FormatDelete // Deleted text - FormatSuper // Superscripted text - FormatSub // SubscriptedText - FormatQuote // Quoted text - FormatMark // Marked text - FormatSpan // Generic inline container -) - -func (*FormatNode) inlineNode() { /* Just a marker */ } - -// WalkChildren walks to the formatted text. -func (fn *FormatNode) WalkChildren(v Visitor) { Walk(v, &fn.Inlines) } - -// -------------------------------------------------------------------------- - -// LiteralNode specifies some uninterpreted text. -type LiteralNode struct { - Kind LiteralKind - Attrs attrs.Attributes // Optional attributes. - Content []byte -} - -// LiteralKind specifies the format that is applied to code inline nodes. -type LiteralKind int - -// Constants for LiteralCode -const ( - _ LiteralKind = iota - LiteralZettel // Zettel content - LiteralProg // Inline program code - LiteralInput // Computer input, e.g. Keyboard strokes - LiteralOutput // Computer output - LiteralComment // Inline comment - LiteralHTML // Inline HTML, e.g. for Markdown - LiteralMath // Inline math mode -) - -func (*LiteralNode) inlineNode() { /* Just a marker */ } - -// WalkChildren does nothing. -func (*LiteralNode) WalkChildren(Visitor) { /* No children*/ } DELETED ast/ref.go Index: ast/ref.go ================================================================== --- ast/ref.go +++ /dev/null @@ -1,109 +0,0 @@ -//----------------------------------------------------------------------------- -// 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) { - return &Reference{URL: nil, Value: s[len(QueryPrefix):], State: RefStateQuery} - } - if state, ok := localState(s); ok { - if state == RefStateBased { - s = s[1:] - } - u, err := url.Parse(s) - if err == nil { - return &Reference{URL: u, Value: s, State: state} - } - } - u, err := url.Parse(s) - if err != nil { - return &Reference{URL: nil, Value: s, State: RefStateInvalid} - } - if !externalURL(u) { - if _, err = id.Parse(u.Path); err == nil { - return &Reference{URL: u, Value: s, State: RefStateZettel} - } - if u.Path == "" && u.Fragment != "" { - return &Reference{URL: u, Value: s, State: RefStateSelf} - } - } - return &Reference{URL: u, Value: s, State: RefStateExternal} -} - -func invalidReference(s string) bool { return s == "" || s == "00000000000000" } -func externalURL(u *url.URL) bool { - return u.Scheme != "" || u.Opaque != "" || u.Host != "" || u.User != nil -} - -func localState(path string) (RefState, bool) { - if len(path) > 0 && path[0] == '/' { - if len(path) > 1 && path[1] == '/' { - return RefStateBased, true - } - return RefStateHosted, true - } - if len(path) > 1 && path[0] == '.' { - if len(path) > 2 && path[1] == '.' && path[2] == '/' { - return RefStateHosted, true - } - return RefStateHosted, path[1] == '/' - } - return RefStateInvalid, false -} - -// String returns the string representation of a reference. -func (r Reference) String() string { - if r.URL != nil { - return r.URL.String() - } - if r.State == RefStateQuery { - return QueryPrefix + r.Value - } - return r.Value -} - -// IsValid returns true if reference is valid -func (r *Reference) IsValid() bool { return r.State != RefStateInvalid } - -// IsZettel returns true if it is a referencen to a local zettel. -func (r *Reference) IsZettel() bool { - switch r.State { - case RefStateZettel, RefStateSelf, RefStateFound, RefStateBroken: - return true - } - return false -} - -// IsLocal returns true if reference is local -func (r *Reference) IsLocal() bool { - return r.State == RefStateHosted || r.State == RefStateBased -} - -// IsExternal returns true if it is a referencen to external material. -func (r *Reference) IsExternal() bool { return r.State == RefStateExternal } DELETED ast/ref_test.go Index: ast/ref_test.go ================================================================== --- ast/ref_test.go +++ /dev/null @@ -1,98 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - - "zettelstore.de/z/ast" -) - -func TestParseReference(t *testing.T) { - t.Parallel() - testcases := []struct { - link string - err bool - exp string - }{ - {"", true, ""}, - {"123", false, "123"}, - {",://", true, ""}, - } - - for i, tc := range testcases { - got := ast.ParseReference(tc.link) - if got.IsValid() == tc.err { - t.Errorf( - "TC=%d, expected parse error of %q: %v, but got %q", i, tc.link, tc.err, got) - } - if got.IsValid() && got.String() != tc.exp { - t.Errorf("TC=%d, Reference of %q is %q, but got %q", i, tc.link, tc.exp, got) - } - } -} - -func TestReferenceIsZettelMaterial(t *testing.T) { - t.Parallel() - testcases := []struct { - link string - isZettel bool - isExternal bool - isLocal bool - }{ - {"", false, false, false}, - {"00000000000000", false, false, false}, - {"http://zettelstore.de/z/ast", false, true, false}, - {"12345678901234", true, false, false}, - {"12345678901234#local", true, false, false}, - {"http://12345678901234", false, true, false}, - {"http://zettelstore.de/z/12345678901234", false, true, false}, - {"http://zettelstore.de/12345678901234", false, true, false}, - {"/12345678901234", false, false, true}, - {"//12345678901234", false, false, true}, - {"./12345678901234", false, false, true}, - {"../12345678901234", false, false, true}, - {".../12345678901234", false, true, false}, - } - - for i, tc := range testcases { - ref := ast.ParseReference(tc.link) - isZettel := ref.IsZettel() - if isZettel != tc.isZettel { - t.Errorf( - "TC=%d, Reference %q isZettel=%v expected, but got %v", - i, - tc.link, - tc.isZettel, - isZettel) - } - isLocal := ref.IsLocal() - if isLocal != tc.isLocal { - t.Errorf( - "TC=%d, Reference %q isLocal=%v expected, but got %v", - i, - tc.link, - tc.isLocal, isLocal) - } - isExternal := ref.IsExternal() - if isExternal != tc.isExternal { - t.Errorf( - "TC=%d, Reference %q isExternal=%v expected, but got %v", - i, - tc.link, - tc.isExternal, - isExternal) - } - } -} DELETED ast/walk.go Index: ast/walk.go ================================================================== --- ast/walk.go +++ /dev/null @@ -1,48 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 -} - -// Walk traverses the AST. -func Walk(v Visitor, node Node) { - if v = v.Visit(node); v == nil { - return - } - - // Implementation note: - // It is much faster to use interface dispatching than to use a switch statement. - // On my "cpu: Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz", a switch statement - // implementation tooks approx 940-980 ns/op. Interface dispatching is in the - // range of 900-930 ns/op. - node.WalkChildren(v) - v.Visit(nil) -} - -// WalkItemSlice traverses an item slice. -func WalkItemSlice(v Visitor, ins ItemSlice) { - for _, in := range ins { - Walk(v, in) - } -} - -// WalkDescriptionSlice traverses an item slice. -func WalkDescriptionSlice(v Visitor, dns DescriptionSlice) { - for _, dn := range dns { - Walk(v, dn) - } -} DELETED ast/walk_test.go Index: ast/walk_test.go ================================================================== --- ast/walk_test.go +++ /dev/null @@ -1,74 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 } DELETED auth/auth.go Index: auth/auth.go ================================================================== --- auth/auth.go +++ /dev/null @@ -1,103 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - - "zettelstore.de/z/box" - "zettelstore.de/z/config" - "zettelstore.de/z/zettel/id" - "zettelstore.de/z/zettel/meta" -) - -// BaseManager allows to check some base auth modes. -type BaseManager interface { - // IsReadonly returns true, if the systems is configured to run in read-only-mode. - IsReadonly() bool -} - -// TokenManager provides methods to create authentication -type TokenManager interface { - - // GetToken produces a authentication token. - GetToken(ident *meta.Meta, d time.Duration, kind TokenKind) ([]byte, error) - - // CheckToken checks the validity of the token and returns relevant data. - CheckToken(token []byte, k TokenKind) (TokenData, error) -} - -// TokenKind specifies for which application / usage a token is/was requested. -type TokenKind int - -// Allowed values of token kind -const ( - _ TokenKind = iota - KindAPI - KindwebUI -) - -// TokenData contains some important elements from a token. -type TokenData struct { - Token []byte - Now time.Time - Issued time.Time - Expires time.Time - Ident string - Zid id.Zid -} - -// AuthzManager provides methods for authorization. -type AuthzManager interface { - BaseManager - - // Owner returns the zettel identifier of the owner. - Owner() id.Zid - - // IsOwner returns true, if the given zettel identifier is that of the owner. - IsOwner(zid id.Zid) bool - - // Returns true if authentication is enabled. - WithAuth() bool - - // GetUserRole role returns the user role of the given user zettel. - GetUserRole(user *meta.Meta) meta.UserRole -} - -// Manager is the main interface for providing the service. -type Manager interface { - TokenManager - AuthzManager - - BoxWithPolicy(unprotectedBox box.Box, rtConfig config.Config) (box.Box, Policy) -} - -// Policy is an interface for checking access authorization. -type Policy interface { - // User is allowed to create a new zettel. - CanCreate(user, newMeta *meta.Meta) bool - - // 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 -} DELETED auth/cred/cred.go Index: auth/cred/cred.go ================================================================== --- auth/cred/cred.go +++ /dev/null @@ -1,56 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - - "golang.org/x/crypto/bcrypt" - "zettelstore.de/z/zettel/id" -) - -// HashCredential returns a hashed vesion of the given credential -func HashCredential(zid id.Zid, ident, credential string) (string, error) { - fullCredential := createFullCredential(zid, ident, credential) - res, err := bcrypt.GenerateFromPassword(fullCredential, bcrypt.DefaultCost) - if err != nil { - return "", err - } - return string(res), nil -} - -// CompareHashAndCredential checks, whether the hashed credential is a possible -// value when hashing the credential. -func CompareHashAndCredential(hashed string, zid id.Zid, ident, credential string) (bool, error) { - fullCredential := createFullCredential(zid, ident, credential) - err := bcrypt.CompareHashAndPassword([]byte(hashed), fullCredential) - if err == nil { - return true, nil - } - if err == bcrypt.ErrMismatchedHashAndPassword { - return false, nil - } - return false, err -} - -func createFullCredential(zid id.Zid, ident, credential string) []byte { - var buf bytes.Buffer - buf.Write(zid.Bytes()) - buf.WriteByte(' ') - buf.WriteString(ident) - buf.WriteByte(' ') - buf.WriteString(credential) - return buf.Bytes() -} DELETED auth/impl/digest.go Index: auth/impl/digest.go ================================================================== --- auth/impl/digest.go +++ /dev/null @@ -1,89 +0,0 @@ -//----------------------------------------------------------------------------- -// 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) - - token = append(token, '.') - token = append(token, encDig...) - return token, nil -} - -func check(token []byte, secret []byte) (sx.Object, error) { - i := bytes.IndexByte(token, '.') - if i <= 0 || 1024 < i { - return nil, ErrMalformedToken - } - buf := make([]byte, len(token)) - n, err := encoding.Decode(buf, token[:i]) - if err != nil { - return nil, err - } - rdr := sxreader.MakeReader(bytes.NewReader(buf[:n])) - obj, err := rdr.Read() - if err != nil { - return nil, err - } - - var objBuf bytes.Buffer - _, err = sx.Print(&objBuf, obj) - if err != nil { - return nil, err - } - - digest := hmac.New(digestAlg.New, secret) - _, err = digest.Write(objBuf.Bytes()) - if err != nil { - return nil, err - } - - n, err = encoding.Decode(buf, token[i+1:]) - if err != nil { - return nil, err - } - if !hmac.Equal(buf[:n], digest.Sum(nil)) { - return nil, ErrMalformedToken - } - return obj, nil -} DELETED auth/impl/impl.go Index: auth/impl/impl.go ================================================================== --- auth/impl/impl.go +++ /dev/null @@ -1,180 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" -) - -type myAuth struct { - readonly bool - owner id.Zid - secret []byte -} - -// New creates a new auth object. -func New(readonly bool, owner id.Zid, extSecret string) auth.Manager { - return &myAuth{ - readonly: readonly, - owner: owner, - secret: calcSecret(extSecret), - } -} - -var configKeys = []string{ - kernel.CoreProgname, - kernel.CoreGoVersion, - kernel.CoreHostname, - kernel.CoreGoOS, - kernel.CoreGoArch, - kernel.CoreVersion, -} - -func calcSecret(extSecret string) []byte { - h := fnv.New128() - if extSecret != "" { - io.WriteString(h, extSecret) - } - for _, key := range configKeys { - io.WriteString(h, kernel.Main.GetConfig(kernel.CoreService, key).(string)) - } - return h.Sum(nil) -} - -// IsReadonly returns true, if the systems is configured to run in read-only-mode. -func (a *myAuth) IsReadonly() bool { return a.readonly } - -// ErrMalformedToken signals a broken token. -var ErrMalformedToken = errors.New("auth: malformed token") - -// ErrNoIdent signals that the 'ident' key is missing. -var ErrNoIdent = errors.New("auth: missing ident") - -// ErrOtherKind signals that the token was defined for another token kind. -var ErrOtherKind = errors.New("auth: wrong token kind") - -// ErrNoZid signals that the 'zid' key is missing. -var ErrNoZid = errors.New("auth: missing zettel id") - -// GetToken returns a token to be used for authentification. -func (a *myAuth) GetToken(ident *meta.Meta, d time.Duration, kind auth.TokenKind) ([]byte, error) { - subject, ok := ident.Get(api.KeyUserID) - if !ok || subject == "" { - return nil, ErrNoIdent - } - - now := time.Now().Round(time.Second) - 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) -} - -// ErrTokenExpired signals an exired token -var ErrTokenExpired = errors.New("auth: token expired") - -// CheckToken checks the validity of the token and returns relevant data. -func (a *myAuth) CheckToken(tok []byte, k auth.TokenKind) (auth.TokenData, error) { - var tokenData auth.TokenData - - obj, err := check(tok, a.secret) - if err != nil { - return tokenData, err - } - - tokenData.Token = tok - err = setupTokenData(obj, k, &tokenData) - return tokenData, err -} - -func setupTokenData(obj sx.Object, k auth.TokenKind, tokenData *auth.TokenData) error { - 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 -} - -func (a *myAuth) Owner() id.Zid { return a.owner } - -func (a *myAuth) IsOwner(zid id.Zid) bool { - return zid.IsValid() && zid == a.owner -} - -func (a *myAuth) WithAuth() bool { return a.owner != id.Invalid } - -// GetUserRole role returns the user role of the given user zettel. -func (a *myAuth) GetUserRole(user *meta.Meta) meta.UserRole { - if user == nil { - if a.WithAuth() { - return meta.UserRoleUnknown - } - return meta.UserRoleOwner - } - if a.IsOwner(user.Zid) { - return meta.UserRoleOwner - } - if val, ok := user.Get(api.KeyUserRole); ok { - if ur := meta.GetUserRole(val); ur != meta.UserRoleUnknown { - return ur - } - } - return meta.UserRoleReader -} - -func (a *myAuth) BoxWithPolicy(unprotectedBox box.Box, rtConfig config.Config) (box.Box, auth.Policy) { - return policy.BoxWithPolicy(a, unprotectedBox, rtConfig) -} DELETED auth/policy/anon.go Index: auth/policy/anon.go ================================================================== --- auth/policy/anon.go +++ /dev/null @@ -1,55 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - "zettelstore.de/z/zettel/meta" -) - -type anonPolicy struct { - authConfig config.AuthConfig - pre auth.Policy -} - -func (ap *anonPolicy) CanCreate(user, newMeta *meta.Meta) bool { - return ap.pre.CanCreate(user, newMeta) -} - -func (ap *anonPolicy) CanRead(user, m *meta.Meta) bool { - 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 - } - return ap.pre.CanRefresh(user) -} - -func (ap *anonPolicy) checkVisibility(m *meta.Meta) bool { - if ap.authConfig.GetVisibility(m) == meta.VisibilityExpert { - return ap.authConfig.GetExpertMode() - } - return true -} DELETED auth/policy/box.go Index: auth/policy/box.go ================================================================== --- auth/policy/box.go +++ /dev/null @@ -1,155 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - - "zettelstore.de/z/auth" - "zettelstore.de/z/box" - "zettelstore.de/z/config" - "zettelstore.de/z/query" - "zettelstore.de/z/web/server" - "zettelstore.de/z/zettel" - "zettelstore.de/z/zettel/id" - "zettelstore.de/z/zettel/meta" -) - -// BoxWithPolicy wraps the given box inside a policy box. -func BoxWithPolicy(manager auth.AuthzManager, box box.Box, authConfig config.AuthConfig) (box.Box, auth.Policy) { - pol := newPolicy(manager, authConfig) - return newBox(box, pol), pol -} - -// polBox implements a policy box. -type polBox struct { - box box.Box - policy auth.Policy -} - -// newBox creates a new policy box. -func newBox(box box.Box, policy auth.Policy) box.Box { - return &polBox{ - box: box, - policy: policy, - } -} - -func (pp *polBox) Location() string { - return pp.box.Location() -} - -func (pp *polBox) CanCreateZettel(ctx context.Context) bool { - return pp.box.CanCreateZettel(ctx) -} - -func (pp *polBox) CreateZettel(ctx context.Context, zettel zettel.Zettel) (id.Zid, error) { - user := server.GetUser(ctx) - if pp.policy.CanCreate(user, zettel.Meta) { - return pp.box.CreateZettel(ctx, zettel) - } - return id.Invalid, box.NewErrNotAllowed("Create", user, id.Invalid) -} - -func (pp *polBox) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) { - z, err := pp.box.GetZettel(ctx, zid) - if err != nil { - return zettel.Zettel{}, err - } - user := server.GetUser(ctx) - if pp.policy.CanRead(user, z.Meta) { - return z, nil - } - 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 - } - user := server.GetUser(ctx) - if pp.policy.CanRead(user, m) { - return m, nil - } - return nil, box.NewErrNotAllowed("GetMeta", user, zid) -} - -func (pp *polBox) SelectMeta(ctx context.Context, metaSeq []*meta.Meta, q *query.Query) ([]*meta.Meta, error) { - user := server.GetUser(ctx) - canRead := pp.policy.CanRead - q = q.SetPreMatch(func(m *meta.Meta) bool { return canRead(user, m) }) - return pp.box.SelectMeta(ctx, metaSeq, q) -} - -func (pp *polBox) CanUpdateZettel(ctx context.Context, zettel zettel.Zettel) bool { - return pp.box.CanUpdateZettel(ctx, zettel) -} - -func (pp *polBox) UpdateZettel(ctx context.Context, zettel zettel.Zettel) error { - zid := zettel.Meta.Zid - user := server.GetUser(ctx) - if !zid.IsValid() { - return box.ErrInvalidZid{Zid: zid.String()} - } - // Write existing zettel - oldZettel, err := pp.box.GetZettel(ctx, zid) - if err != nil { - return err - } - 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 { - return err - } - user := server.GetUser(ctx) - if pp.policy.CanDelete(user, z.Meta) { - return pp.box.DeleteZettel(ctx, zid) - } - return box.NewErrNotAllowed("Delete", user, zid) -} - -func (pp *polBox) Refresh(ctx context.Context) error { - user := server.GetUser(ctx) - if pp.policy.CanRefresh(user) { - return pp.box.Refresh(ctx) - } - return box.NewErrNotAllowed("Refresh", user, id.Invalid) -} -func (pp *polBox) ReIndex(ctx context.Context, zid id.Zid) error { - user := server.GetUser(ctx) - if pp.policy.CanRefresh(user) { - // If a user is allowed to refresh all data, it it also allowed to re-index a zettel. - return pp.box.ReIndex(ctx, zid) - } - return box.NewErrNotAllowed("ReIndex", user, zid) -} DELETED auth/policy/default.go Index: auth/policy/default.go ================================================================== --- auth/policy/default.go +++ /dev/null @@ -1,59 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 { - return true - } - if user == nil { - // If we are here, there is no authentication. - // See owner.go:CanWrite. - - // No authentication: check for owner-like restriction, because the user - // acts as an owner - return metaRo != api.ValueUserRoleOwner && !meta.BoolValue(metaRo) - } - - userRole := d.manager.GetUserRole(user) - switch metaRo { - case api.ValueUserRoleReader: - return userRole > meta.UserRoleReader - case api.ValueUserRoleWriter: - return userRole > meta.UserRoleWriter - case api.ValueUserRoleOwner: - return userRole > meta.UserRoleOwner - } - return !meta.BoolValue(metaRo) -} DELETED auth/policy/owner.go Index: auth/policy/owner.go ================================================================== --- auth/policy/owner.go +++ /dev/null @@ -1,156 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 - authConfig config.AuthConfig - pre auth.Policy -} - -func (o *ownerPolicy) CanCreate(user, newMeta *meta.Meta) bool { - if user == nil || !o.pre.CanCreate(user, newMeta) { - return false - } - return o.userIsOwner(user) || o.userCanCreate(user, newMeta) -} - -func (o *ownerPolicy) userCanCreate(user, newMeta *meta.Meta) bool { - if o.manager.GetUserRole(user) == meta.UserRoleReader { - return false - } - if _, ok := newMeta.Get(api.KeyUserID); ok { - return false - } - return true -} - -func (o *ownerPolicy) CanRead(user, m *meta.Meta) bool { - // No need to call o.pre.CanRead(user, meta), because it will always return true. - // Both the default and the readonly policy allow to read a zettel. - vis := o.authConfig.GetVisibility(m) - if res, ok := o.checkVisibility(user, vis); ok { - return res - } - return o.userIsOwner(user) || o.userCanRead(user, m, vis) -} - -func (o *ownerPolicy) userCanRead(user, m *meta.Meta, vis meta.Visibility) bool { - switch vis { - case meta.VisibilityOwner, meta.VisibilityExpert: - return false - case meta.VisibilityPublic: - return true - } - if user == nil { - return false - } - if _, ok := m.Get(api.KeyUserID); ok { - // Only the user can read its own zettel - return user.Zid == m.Zid - } - switch o.manager.GetUserRole(user) { - case meta.UserRoleReader, meta.UserRoleWriter, meta.UserRoleOwner: - return true - case meta.UserRoleCreator: - return vis == meta.VisibilityCreator - default: - return false - } -} - -var noChangeUser = []string{ - api.KeyID, - api.KeyRole, - api.KeyUserID, - api.KeyUserRole, -} - -func (o *ownerPolicy) CanWrite(user, oldMeta, newMeta *meta.Meta) bool { - if user == nil || !o.pre.CanWrite(user, oldMeta, newMeta) { - return false - } - vis := o.authConfig.GetVisibility(oldMeta) - if res, ok := o.checkVisibility(user, vis); ok { - return res - } - if o.userIsOwner(user) { - return true - } - if !o.userCanRead(user, oldMeta, vis) { - return false - } - if _, ok := oldMeta.Get(api.KeyUserID); ok { - // Here we know, that user.Zid == newMeta.Zid (because of userCanRead) and - // user.Zid == newMeta.Zid (because oldMeta.Zid == newMeta.Zid) - for _, key := range noChangeUser { - if oldMeta.GetDefault(key, "") != newMeta.GetDefault(key, "") { - return false - } - } - return true - } - 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 - } - return o.userIsOwner(user) -} - -func (o *ownerPolicy) CanRefresh(user *meta.Meta) bool { - switch userRole := o.manager.GetUserRole(user); userRole { - case meta.UserRoleUnknown: - return o.authConfig.GetSimpleMode() - case meta.UserRoleCreator: - return o.authConfig.GetExpertMode() || o.authConfig.GetSimpleMode() - } - return true -} - -func (o *ownerPolicy) checkVisibility(user *meta.Meta, vis meta.Visibility) (bool, bool) { - if vis == meta.VisibilityExpert { - return o.userIsOwner(user) && o.authConfig.GetExpertMode(), true - } - return false, false -} - -func (o *ownerPolicy) userIsOwner(user *meta.Meta) bool { - if user == nil { - return false - } - if o.manager.IsOwner(user.Zid) { - return true - } - if val, ok := user.Get(api.KeyUserRole); ok && val == api.ValueUserRoleOwner { - return true - } - return false -} DELETED auth/policy/policy.go Index: auth/policy/policy.go ================================================================== --- auth/policy/policy.go +++ /dev/null @@ -1,69 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - "zettelstore.de/z/config" - "zettelstore.de/z/zettel/meta" -) - -// newPolicy creates a policy based on given constraints. -func newPolicy(manager auth.AuthzManager, authConfig config.AuthConfig) auth.Policy { - var pol auth.Policy - if manager.IsReadonly() { - pol = &roPolicy{} - } else { - pol = &defaultPolicy{manager} - } - if manager.WithAuth() { - pol = &ownerPolicy{ - manager: manager, - authConfig: authConfig, - pre: pol, - } - } else { - pol = &anonPolicy{ - authConfig: authConfig, - pre: pol, - } - } - return &prePolicy{pol} -} - -type prePolicy struct { - post auth.Policy -} - -func (p *prePolicy) CanCreate(user, newMeta *meta.Meta) bool { - return newMeta != nil && p.post.CanCreate(user, newMeta) -} - -func (p *prePolicy) CanRead(user, m *meta.Meta) bool { - return m != nil && p.post.CanRead(user, m) -} - -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) -} DELETED auth/policy/policy_test.go Index: auth/policy/policy_test.go ================================================================== --- auth/policy/policy_test.go +++ /dev/null @@ -1,631 +0,0 @@ -//----------------------------------------------------------------------------- -// 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() - testScene := []struct { - readonly bool - withAuth bool - expert bool - simple bool - }{ - {true, true, true, true}, - {true, true, true, false}, - {true, true, false, true}, - {true, true, false, false}, - {true, false, true, true}, - {true, false, true, false}, - {true, false, false, true}, - {true, false, false, false}, - {false, true, true, true}, - {false, true, true, false}, - {false, true, false, true}, - {false, true, false, false}, - {false, false, true, true}, - {false, false, true, false}, - {false, false, false, true}, - {false, false, false, false}, - } - for _, ts := range testScene { - pol := newPolicy( - &testAuthzManager{readOnly: ts.readonly, withAuth: ts.withAuth}, - &authConfig{simple: ts.simple, expert: ts.expert}, - ) - 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 { - readOnly bool - withAuth bool -} - -func (a *testAuthzManager) IsReadonly() bool { return a.readOnly } -func (*testAuthzManager) Owner() id.Zid { return ownerZid } -func (*testAuthzManager) IsOwner(zid id.Zid) bool { return zid == ownerZid } - -func (a *testAuthzManager) WithAuth() bool { return a.withAuth } - -func (a *testAuthzManager) GetUserRole(user *meta.Meta) meta.UserRole { - if user == nil { - if a.WithAuth() { - return meta.UserRoleUnknown - } - return meta.UserRoleOwner - } - if a.IsOwner(user.Zid) { - return meta.UserRoleOwner - } - if val, ok := user.Get(api.KeyUserRole); ok { - if ur := meta.GetUserRole(val); ur != meta.UserRoleUnknown { - return ur - } - } - return meta.UserRoleReader -} - -type authConfig struct{ simple, expert bool } - -func (ac *authConfig) GetSimpleMode() bool { return ac.simple } -func (ac *authConfig) GetExpertMode() bool { return ac.expert } - -func (*authConfig) GetVisibility(m *meta.Meta) meta.Visibility { - if vis, ok := m.Get(api.KeyVisibility); ok { - return meta.GetVisibility(vis) - } - return meta.VisibilityLogin -} - -func testCreate(t *testing.T, pol auth.Policy, withAuth, readonly bool) { - t.Helper() - anonUser := newAnon() - creator := newCreator() - reader := newReader() - writer := newWriter() - owner := newOwner() - owner2 := newOwner2() - zettel := newZettel() - userZettel := newUserZettel() - testCases := []struct { - user *meta.Meta - meta *meta.Meta - exp bool - }{ - // No meta - {anonUser, nil, false}, - {creator, nil, false}, - {reader, nil, false}, - {writer, nil, false}, - {owner, nil, false}, - {owner2, nil, false}, - // Ordinary zettel - {anonUser, zettel, !withAuth && !readonly}, - {creator, zettel, !readonly}, - {reader, zettel, !withAuth && !readonly}, - {writer, zettel, !readonly}, - {owner, zettel, !readonly}, - {owner2, zettel, !readonly}, - // User zettel - {anonUser, userZettel, !withAuth && !readonly}, - {creator, userZettel, !withAuth && !readonly}, - {reader, userZettel, !withAuth && !readonly}, - {writer, userZettel, !withAuth && !readonly}, - {owner, userZettel, !readonly}, - {owner2, userZettel, !readonly}, - } - for _, tc := range testCases { - t.Run("Create", func(tt *testing.T) { - got := pol.CanCreate(tc.user, tc.meta) - if tc.exp != got { - tt.Errorf("exp=%v, but got=%v", tc.exp, got) - } - }) - } -} - -func testRead(t *testing.T, pol auth.Policy, withAuth, expert bool) { - t.Helper() - anonUser := newAnon() - creator := newCreator() - reader := newReader() - writer := newWriter() - owner := newOwner() - owner2 := newOwner2() - zettel := newZettel() - publicZettel := newPublicZettel() - creatorZettel := newCreatorZettel() - loginZettel := newLoginZettel() - ownerZettel := newOwnerZettel() - expertZettel := newExpertZettel() - userZettel := newUserZettel() - testCases := []struct { - user *meta.Meta - meta *meta.Meta - exp bool - }{ - // No meta - {anonUser, nil, false}, - {creator, nil, false}, - {reader, nil, false}, - {writer, nil, false}, - {owner, nil, false}, - {owner2, nil, false}, - // Ordinary zettel - {anonUser, zettel, !withAuth}, - {creator, zettel, !withAuth}, - {reader, zettel, true}, - {writer, zettel, true}, - {owner, zettel, true}, - {owner2, zettel, true}, - // Public zettel - {anonUser, publicZettel, true}, - {creator, publicZettel, true}, - {reader, publicZettel, true}, - {writer, publicZettel, true}, - {owner, publicZettel, true}, - {owner2, publicZettel, true}, - // Creator zettel - {anonUser, creatorZettel, !withAuth}, - {creator, creatorZettel, true}, - {reader, creatorZettel, true}, - {writer, creatorZettel, true}, - {owner, creatorZettel, true}, - {owner2, creatorZettel, true}, - // Login zettel - {anonUser, loginZettel, !withAuth}, - {creator, loginZettel, !withAuth}, - {reader, loginZettel, true}, - {writer, loginZettel, true}, - {owner, loginZettel, true}, - {owner2, loginZettel, true}, - // Owner zettel - {anonUser, ownerZettel, !withAuth}, - {creator, ownerZettel, !withAuth}, - {reader, ownerZettel, !withAuth}, - {writer, ownerZettel, !withAuth}, - {owner, ownerZettel, true}, - {owner2, ownerZettel, true}, - // Expert zettel - {anonUser, expertZettel, !withAuth && expert}, - {creator, expertZettel, !withAuth && expert}, - {reader, expertZettel, !withAuth && expert}, - {writer, expertZettel, !withAuth && expert}, - {owner, expertZettel, expert}, - {owner2, expertZettel, expert}, - // Other user zettel - {anonUser, userZettel, !withAuth}, - {creator, userZettel, !withAuth}, - {reader, userZettel, !withAuth}, - {writer, userZettel, !withAuth}, - {owner, userZettel, true}, - {owner2, userZettel, true}, - // Own user zettel - {creator, creator, true}, - {reader, reader, true}, - {writer, writer, true}, - {owner, owner, true}, - {owner, owner2, true}, - {owner2, owner, true}, - {owner2, owner2, true}, - } - for _, tc := range testCases { - t.Run("Read", func(tt *testing.T) { - got := pol.CanRead(tc.user, tc.meta) - if tc.exp != got { - tt.Errorf("exp=%v, but got=%v", tc.exp, got) - } - }) - } -} - -func testWrite(t *testing.T, pol auth.Policy, withAuth, readonly, expert bool) { - t.Helper() - anonUser := newAnon() - creator := newCreator() - reader := newReader() - writer := newWriter() - owner := newOwner() - owner2 := newOwner2() - zettel := newZettel() - publicZettel := newPublicZettel() - loginZettel := newLoginZettel() - ownerZettel := newOwnerZettel() - expertZettel := newExpertZettel() - userZettel := newUserZettel() - writerNew := writer.Clone() - writerNew.Set(api.KeyUserRole, owner.GetDefault(api.KeyUserRole, "")) - roFalse := newRoFalseZettel() - roTrue := newRoTrueZettel() - roReader := newRoReaderZettel() - roWriter := newRoWriterZettel() - roOwner := newRoOwnerZettel() - notAuthNotReadonly := !withAuth && !readonly - testCases := []struct { - user *meta.Meta - old *meta.Meta - new *meta.Meta - exp bool - }{ - // No old and new meta - {anonUser, nil, nil, false}, - {creator, nil, nil, false}, - {reader, nil, nil, false}, - {writer, nil, nil, false}, - {owner, nil, nil, false}, - {owner2, nil, nil, false}, - // No old meta - {anonUser, nil, zettel, false}, - {creator, nil, zettel, false}, - {reader, nil, zettel, false}, - {writer, nil, zettel, false}, - {owner, nil, zettel, false}, - {owner2, nil, zettel, false}, - // No new meta - {anonUser, zettel, nil, false}, - {creator, zettel, nil, false}, - {reader, zettel, nil, false}, - {writer, zettel, nil, false}, - {owner, zettel, nil, false}, - {owner2, zettel, nil, false}, - // Old an new zettel have different zettel identifier - {anonUser, zettel, publicZettel, false}, - {creator, zettel, publicZettel, false}, - {reader, zettel, publicZettel, false}, - {writer, zettel, publicZettel, false}, - {owner, zettel, publicZettel, false}, - {owner2, zettel, publicZettel, false}, - // Overwrite a normal zettel - {anonUser, zettel, zettel, notAuthNotReadonly}, - {creator, zettel, zettel, notAuthNotReadonly}, - {reader, zettel, zettel, notAuthNotReadonly}, - {writer, zettel, zettel, !readonly}, - {owner, zettel, zettel, !readonly}, - {owner2, zettel, zettel, !readonly}, - // Public zettel - {anonUser, publicZettel, publicZettel, notAuthNotReadonly}, - {creator, publicZettel, publicZettel, notAuthNotReadonly}, - {reader, publicZettel, publicZettel, notAuthNotReadonly}, - {writer, publicZettel, publicZettel, !readonly}, - {owner, publicZettel, publicZettel, !readonly}, - {owner2, publicZettel, publicZettel, !readonly}, - // Login zettel - {anonUser, loginZettel, loginZettel, notAuthNotReadonly}, - {creator, loginZettel, loginZettel, notAuthNotReadonly}, - {reader, loginZettel, loginZettel, notAuthNotReadonly}, - {writer, loginZettel, loginZettel, !readonly}, - {owner, loginZettel, loginZettel, !readonly}, - {owner2, loginZettel, loginZettel, !readonly}, - // Owner zettel - {anonUser, ownerZettel, ownerZettel, notAuthNotReadonly}, - {creator, ownerZettel, ownerZettel, notAuthNotReadonly}, - {reader, ownerZettel, ownerZettel, notAuthNotReadonly}, - {writer, ownerZettel, ownerZettel, notAuthNotReadonly}, - {owner, ownerZettel, ownerZettel, !readonly}, - {owner2, ownerZettel, ownerZettel, !readonly}, - // Expert zettel - {anonUser, expertZettel, expertZettel, notAuthNotReadonly && expert}, - {creator, expertZettel, expertZettel, notAuthNotReadonly && expert}, - {reader, expertZettel, expertZettel, notAuthNotReadonly && expert}, - {writer, expertZettel, expertZettel, notAuthNotReadonly && expert}, - {owner, expertZettel, expertZettel, !readonly && expert}, - {owner2, expertZettel, expertZettel, !readonly && expert}, - // Other user zettel - {anonUser, userZettel, userZettel, notAuthNotReadonly}, - {creator, userZettel, userZettel, notAuthNotReadonly}, - {reader, userZettel, userZettel, notAuthNotReadonly}, - {writer, userZettel, userZettel, notAuthNotReadonly}, - {owner, userZettel, userZettel, !readonly}, - {owner2, userZettel, userZettel, !readonly}, - // Own user zettel - {creator, creator, creator, !readonly}, - {reader, reader, reader, !readonly}, - {writer, writer, writer, !readonly}, - {owner, owner, owner, !readonly}, - {owner2, owner2, owner2, !readonly}, - // Writer cannot change importand metadata of its own user zettel - {writer, writer, writerNew, notAuthNotReadonly}, - // No r/o zettel - {anonUser, roFalse, roFalse, notAuthNotReadonly}, - {creator, roFalse, roFalse, notAuthNotReadonly}, - {reader, roFalse, roFalse, notAuthNotReadonly}, - {writer, roFalse, roFalse, !readonly}, - {owner, roFalse, roFalse, !readonly}, - {owner2, roFalse, roFalse, !readonly}, - // Reader r/o zettel - {anonUser, roReader, roReader, false}, - {creator, roReader, roReader, false}, - {reader, roReader, roReader, false}, - {writer, roReader, roReader, !readonly}, - {owner, roReader, roReader, !readonly}, - {owner2, roReader, roReader, !readonly}, - // Writer r/o zettel - {anonUser, roWriter, roWriter, false}, - {creator, roWriter, roWriter, false}, - {reader, roWriter, roWriter, false}, - {writer, roWriter, roWriter, false}, - {owner, roWriter, roWriter, !readonly}, - {owner2, roWriter, roWriter, !readonly}, - // Owner r/o zettel - {anonUser, roOwner, roOwner, false}, - {creator, roOwner, roOwner, false}, - {reader, roOwner, roOwner, false}, - {writer, roOwner, roOwner, false}, - {owner, roOwner, roOwner, false}, - {owner2, roOwner, roOwner, false}, - // r/o = true zettel - {anonUser, roTrue, roTrue, false}, - {creator, roTrue, roTrue, false}, - {reader, roTrue, roTrue, false}, - {writer, roTrue, roTrue, false}, - {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) { - t.Helper() - anonUser := newAnon() - creator := newCreator() - reader := newReader() - writer := newWriter() - owner := newOwner() - owner2 := newOwner2() - zettel := newZettel() - expertZettel := newExpertZettel() - roFalse := newRoFalseZettel() - roTrue := newRoTrueZettel() - roReader := newRoReaderZettel() - roWriter := newRoWriterZettel() - roOwner := newRoOwnerZettel() - notAuthNotReadonly := !withAuth && !readonly - testCases := []struct { - user *meta.Meta - meta *meta.Meta - exp bool - }{ - // No meta - {anonUser, nil, false}, - {creator, nil, false}, - {reader, nil, false}, - {writer, nil, false}, - {owner, nil, false}, - {owner2, nil, false}, - // Any zettel - {anonUser, zettel, notAuthNotReadonly}, - {creator, zettel, notAuthNotReadonly}, - {reader, zettel, notAuthNotReadonly}, - {writer, zettel, notAuthNotReadonly}, - {owner, zettel, !readonly}, - {owner2, zettel, !readonly}, - // Expert zettel - {anonUser, expertZettel, notAuthNotReadonly && expert}, - {creator, expertZettel, notAuthNotReadonly && expert}, - {reader, expertZettel, notAuthNotReadonly && expert}, - {writer, expertZettel, notAuthNotReadonly && expert}, - {owner, expertZettel, !readonly && expert}, - {owner2, expertZettel, !readonly && expert}, - // No r/o zettel - {anonUser, roFalse, notAuthNotReadonly}, - {creator, roFalse, notAuthNotReadonly}, - {reader, roFalse, notAuthNotReadonly}, - {writer, roFalse, notAuthNotReadonly}, - {owner, roFalse, !readonly}, - {owner2, roFalse, !readonly}, - // Reader r/o zettel - {anonUser, roReader, false}, - {creator, roReader, false}, - {reader, roReader, false}, - {writer, roReader, notAuthNotReadonly}, - {owner, roReader, !readonly}, - {owner2, roReader, !readonly}, - // Writer r/o zettel - {anonUser, roWriter, false}, - {creator, roWriter, false}, - {reader, roWriter, false}, - {writer, roWriter, false}, - {owner, roWriter, !readonly}, - {owner2, roWriter, !readonly}, - // Owner r/o zettel - {anonUser, roOwner, false}, - {creator, roOwner, false}, - {reader, roOwner, false}, - {writer, roOwner, false}, - {owner, roOwner, false}, - {owner2, roOwner, false}, - // r/o = true zettel - {anonUser, roTrue, false}, - {creator, roTrue, false}, - {reader, roTrue, false}, - {writer, roTrue, false}, - {owner, roTrue, false}, - {owner2, roTrue, false}, - } - for _, tc := range testCases { - t.Run("Delete", func(tt *testing.T) { - got := pol.CanDelete(tc.user, tc.meta) - if tc.exp != got { - tt.Errorf("exp=%v, but got=%v", tc.exp, got) - } - }) - } -} - -func testRefresh(t *testing.T, pol auth.Policy, withAuth, expert, simple bool) { - t.Helper() - testCases := []struct { - user *meta.Meta - exp bool - }{ - {newAnon(), (!withAuth && expert) || simple}, - {newCreator(), !withAuth || expert || simple}, - {newReader(), true}, - {newWriter(), true}, - {newOwner(), true}, - {newOwner2(), true}, - } - for _, tc := range testCases { - t.Run("Refresh", func(tt *testing.T) { - got := pol.CanRefresh(tc.user) - if tc.exp != got { - tt.Errorf("exp=%v, but got=%v", tc.exp, got) - } - }) - } -} - -const ( - creatorZid = id.Zid(1013) - readerZid = id.Zid(1013) - writerZid = id.Zid(1015) - ownerZid = id.Zid(1017) - owner2Zid = id.Zid(1019) - zettelZid = id.Zid(1021) - visZid = id.Zid(1023) - userZid = id.Zid(1025) -) - -func newAnon() *meta.Meta { return nil } -func newCreator() *meta.Meta { - user := meta.New(creatorZid) - user.Set(api.KeyTitle, "Creator") - user.Set(api.KeyUserID, "ceator") - user.Set(api.KeyUserRole, api.ValueUserRoleCreator) - return user -} -func newReader() *meta.Meta { - user := meta.New(readerZid) - user.Set(api.KeyTitle, "Reader") - user.Set(api.KeyUserID, "reader") - user.Set(api.KeyUserRole, api.ValueUserRoleReader) - return user -} -func newWriter() *meta.Meta { - user := meta.New(writerZid) - user.Set(api.KeyTitle, "Writer") - user.Set(api.KeyUserID, "writer") - user.Set(api.KeyUserRole, api.ValueUserRoleWriter) - return user -} -func newOwner() *meta.Meta { - user := meta.New(ownerZid) - user.Set(api.KeyTitle, "Owner") - user.Set(api.KeyUserID, "owner") - user.Set(api.KeyUserRole, api.ValueUserRoleOwner) - return user -} -func newOwner2() *meta.Meta { - user := meta.New(owner2Zid) - user.Set(api.KeyTitle, "Owner 2") - user.Set(api.KeyUserID, "owner-2") - user.Set(api.KeyUserRole, api.ValueUserRoleOwner) - return user -} -func newZettel() *meta.Meta { - m := meta.New(zettelZid) - m.Set(api.KeyTitle, "Any Zettel") - return m -} -func newPublicZettel() *meta.Meta { - m := meta.New(visZid) - m.Set(api.KeyTitle, "Public Zettel") - m.Set(api.KeyVisibility, api.ValueVisibilityPublic) - return m -} -func newCreatorZettel() *meta.Meta { - m := meta.New(visZid) - m.Set(api.KeyTitle, "Creator Zettel") - m.Set(api.KeyVisibility, api.ValueVisibilityCreator) - return m -} -func newLoginZettel() *meta.Meta { - m := meta.New(visZid) - m.Set(api.KeyTitle, "Login Zettel") - m.Set(api.KeyVisibility, api.ValueVisibilityLogin) - return m -} -func newOwnerZettel() *meta.Meta { - m := meta.New(visZid) - m.Set(api.KeyTitle, "Owner Zettel") - m.Set(api.KeyVisibility, api.ValueVisibilityOwner) - return m -} -func newExpertZettel() *meta.Meta { - m := meta.New(visZid) - m.Set(api.KeyTitle, "Expert Zettel") - m.Set(api.KeyVisibility, api.ValueVisibilityExpert) - return m -} -func newRoFalseZettel() *meta.Meta { - m := meta.New(zettelZid) - m.Set(api.KeyTitle, "No r/o Zettel") - m.Set(api.KeyReadOnly, api.ValueFalse) - return m -} -func newRoTrueZettel() *meta.Meta { - m := meta.New(zettelZid) - m.Set(api.KeyTitle, "A r/o Zettel") - m.Set(api.KeyReadOnly, api.ValueTrue) - return m -} -func newRoReaderZettel() *meta.Meta { - m := meta.New(zettelZid) - m.Set(api.KeyTitle, "Reader r/o Zettel") - m.Set(api.KeyReadOnly, api.ValueUserRoleReader) - return m -} -func newRoWriterZettel() *meta.Meta { - m := meta.New(zettelZid) - m.Set(api.KeyTitle, "Writer r/o Zettel") - m.Set(api.KeyReadOnly, api.ValueUserRoleWriter) - return m -} -func newRoOwnerZettel() *meta.Meta { - m := meta.New(zettelZid) - m.Set(api.KeyTitle, "Owner r/o Zettel") - m.Set(api.KeyReadOnly, api.ValueUserRoleOwner) - return m -} -func newUserZettel() *meta.Meta { - m := meta.New(userZid) - m.Set(api.KeyTitle, "Any User") - m.Set(api.KeyUserID, "any") - return m -} DELETED auth/policy/readonly.go Index: auth/policy/readonly.go ================================================================== --- auth/policy/readonly.go +++ /dev/null @@ -1,24 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 } DELETED box/box.go Index: box/box.go ================================================================== --- box/box.go +++ /dev/null @@ -1,327 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 -} - -// WriteBox is a box that can create / update zettel content. -type WriteBox interface { - // CanCreateZettel returns true, if box could possibly create a new zettel. - CanCreateZettel(ctx context.Context) bool - - // CreateZettel creates a new zettel. - // Returns the new zettel id (and an error indication). - CreateZettel(ctx context.Context, zettel zettel.Zettel) (id.Zid, error) - - // CanUpdateZettel returns true, if box could possibly update the given zettel. - CanUpdateZettel(ctx context.Context, zettel zettel.Zettel) bool - - // UpdateZettel updates an existing zettel. - UpdateZettel(ctx context.Context, zettel zettel.Zettel) error -} - -// ZidFunc is a function that processes identifier of a zettel. -type ZidFunc func(id.Zid) - -// MetaFunc is a function that processes metadata of a zettel. -type MetaFunc func(*meta.Meta) - -// ManagedBox is the interface of managed boxes. -type ManagedBox interface { - BaseBox - - // HasZettel returns true, if box conains zettel with given identifier. - HasZettel(context.Context, id.Zid) bool - - // Apply identifier of every zettel to the given function, if predicate returns true. - ApplyZid(context.Context, ZidFunc, query.RetrievePredicate) error - - // Apply metadata of every zettel to the given function, if predicate returns true. - ApplyMeta(context.Context, MetaFunc, query.RetrievePredicate) error - - // ReadStats populates st with box statistics - ReadStats(st *ManagedBoxStats) -} - -// ManagedBoxStats records statistics about the box. -type ManagedBoxStats struct { - // ReadOnly indicates that the content of a box cannot change. - ReadOnly bool - - // Zettel is the number of zettel managed by the box. - Zettel int -} - -// StartState enumerates the possible states of starting and stopping a box. -// -// StartStateStopped -> StartStateStarting -> StartStateStarted -> StateStateStopping -> StartStateStopped. -// -// Other transitions are also possible. -type StartState uint8 - -// Constant values of StartState -const ( - StartStateStopped StartState = iota - StartStateStarting - StartStateStarted - StartStateStopping -) - -// StartStopper performs simple lifecycle management. -type StartStopper interface { - // State the current status of the box. - State() StartState - - // Start the box. Now all other functions of the box are allowed. - // Starting a box, which is not in state StartStateStopped is not allowed. - Start(ctx context.Context) error - - // Stop the started box. Now only the Start() function is allowed. - Stop(ctx context.Context) -} - -// Refresher allow to refresh their internal data. -type Refresher interface { - // Refresh the box data. - Refresh(context.Context) -} - -// 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) - - // GetAllZettel retrieves a specific zettel from all managed boxes. - GetAllZettel(ctx context.Context, zid id.Zid) ([]zettel.Zettel, error) - - // Refresh the data from the box and from its managed sub-boxes. - Refresh(context.Context) error - - // ReIndex one zettel to update its index data. - ReIndex(context.Context, id.Zid) error -} - -// Stats record stattistics about a box. -type Stats struct { - // ReadOnly indicates that boxes cannot be modified. - ReadOnly bool - - // NumManagedBoxes is the number of boxes managed. - NumManagedBoxes int - - // Zettel is the number of zettel managed by the box, including - // duplicates across managed boxes. - ZettelTotal int - - // LastReload stores the timestamp when a full re-index was done. - LastReload time.Time - - // DurLastReload is the duration of the last full re-index run. - DurLastReload time.Duration - - // IndexesSinceReload counts indexing a zettel since the full re-index. - IndexesSinceReload uint64 - - // ZettelIndexed is the number of zettel managed by the indexer. - ZettelIndexed int - - // IndexUpdates count the number of metadata updates. - IndexUpdates uint64 - - // IndexedWords count the different words indexed. - IndexedWords uint64 - - // IndexedUrls count the different URLs indexed. - IndexedUrls uint64 -} - -// Manager is a box-managing box. -type Manager interface { - Box - StartStopper - Subject - - // ReadStats populates st with box statistics - ReadStats(st *Stats) - - // Dump internal data to a Writer. - Dump(w io.Writer) -} - -// UpdateReason gives an indication, why the ObserverFunc was called. -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 -} - -// UpdateFunc is a function to be called when a change is detected. -type UpdateFunc func(UpdateInfo) - -// UpdateNotifier is an UpdateFunc, but with separate values. -type UpdateNotifier func(BaseBox, id.Zid, UpdateReason) - -// Subject is a box that notifies observers about changes. -type Subject interface { - // RegisterObserver registers an observer that will be notified - // if one or all zettel are found to be changed. - RegisterObserver(UpdateFunc) -} - -// Enricher is used to update metadata by adding new properties. -type Enricher interface { - // Enrich computes additional properties and updates the given metadata. - // It is typically called by zettel reading methods. - Enrich(ctx context.Context, m *meta.Meta, boxNumber int) -} - -// NoEnrichContext will signal an enricher that nothing has to be done. -// This is useful for an Indexer, but also for some box.Box calls, when -// just the plain metadata is needed. -func NoEnrichContext(ctx context.Context) context.Context { - 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 - } - return NoEnrichContext(ctx) -} - -// ErrNotAllowed is returned if the caller is not allowed to perform the operation. -type ErrNotAllowed struct { - Op string - User *meta.Meta - Zid id.Zid -} - -// NewErrNotAllowed creates an new authorization error. -func NewErrNotAllowed(op string, user *meta.Meta, zid id.Zid) error { - return &ErrNotAllowed{ - Op: op, - User: user, - Zid: zid, - } -} - -func (err *ErrNotAllowed) Error() string { - if err.User == nil { - if err.Zid.IsValid() { - return fmt.Sprintf( - "operation %q on zettel %v not allowed for not authorized user", - err.Op, err.Zid) - } - return fmt.Sprintf("operation %q not allowed for not authorized user", err.Op) - } - if err.Zid.IsValid() { - return fmt.Sprintf( - "operation %q on zettel %v not allowed for user %v/%v", - err.Op, err.Zid, err.User.GetDefault(api.KeyUserID, "?"), err.User.Zid) - } - return fmt.Sprintf( - "operation %q not allowed for user %v/%v", - err.Op, err.User.GetDefault(api.KeyUserID, "?"), err.User.Zid) -} - -// Is return true, if the error is of type ErrNotAllowed. -func (*ErrNotAllowed) Is(error) bool { return true } - -// ErrStarted is returned when trying to start an already started box. -var ErrStarted = errors.New("box is already started") - -// ErrStopped is returned if calling methods on a box that was not started. -var ErrStopped = errors.New("box is stopped") - -// ErrReadOnly is returned if there is an attepmt to write to a read-only box. -var ErrReadOnly = errors.New("read-only box") - -// ErrZettelNotFound is returned if a zettel was not found in the box. -type ErrZettelNotFound struct{ Zid id.Zid } - -func (eznf ErrZettelNotFound) Error() string { return "zettel not found: " + eznf.Zid.String() } - -// ErrConflict is returned if a box operation detected a conflict.. -// One example: if calculating a new zettel identifier takes too long. -var ErrConflict = errors.New("conflict") - -// ErrCapacity is returned if a box has reached its capacity. -var ErrCapacity = errors.New("capacity exceeded") - -// ErrInvalidZid is returned if the zettel id is not appropriate for the box operation. -type ErrInvalidZid struct{ Zid string } - -func (err ErrInvalidZid) Error() string { return "invalid Zettel id: " + err.Zid } DELETED box/compbox/compbox.go Index: box/compbox/compbox.go ================================================================== --- box/compbox/compbox.go +++ /dev/null @@ -1,178 +0,0 @@ -//----------------------------------------------------------------------------- -// 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(_ *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { - return getCompBox(cdata.Number, cdata.Enricher), nil - }) -} - -type compBox struct { - log *logger.Logger - number int - enricher box.Enricher -} - -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}, -} - -// Get returns the one program box. -func getCompBox(boxNumber int, mf box.Enricher) *compBox { - return &compBox{ - log: kernel.Main.GetLogger(kernel.BoxService).Clone(). - Str("box", "comp").Int("boxnum", int64(boxNumber)).Child(), - number: boxNumber, - enricher: mf, - } -} - -// 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} - cb.log.Trace().Err(err).Msg("GetZettel/Err") - return zettel.Zettel{}, err -} - -func (*compBox) HasZettel(_ context.Context, zid id.Zid) bool { - _, found := myZettel[zid] - return found -} - -func (cb *compBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { - cb.log.Trace().Int("entries", int64(len(myZettel))).Msg("ApplyZid") - for zid, gen := range myZettel { - if !constraint(zid) { - continue - } - if genMeta := gen.meta; genMeta != nil { - if genMeta(zid) != nil { - handle(zid) - } - } - } - return nil -} - -func (cb *compBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint query.RetrievePredicate) error { - cb.log.Trace().Int("entries", int64(len(myZettel))).Msg("ApplyMeta") - for zid, gen := range myZettel { - if !constraint(zid) { - continue - } - if genMeta := gen.meta; genMeta != nil { - if m := genMeta(zid); m != nil { - updateMeta(m) - cb.enricher.Enrich(ctx, m, cb.number) - 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) - } -} DELETED box/compbox/config.go Index: box/compbox/config.go ================================================================== --- box/compbox/config.go +++ /dev/null @@ -1,52 +0,0 @@ -//----------------------------------------------------------------------------- -// 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) - buf.WriteString("''") - if p.Value != "" { - buf.WriteString("\n: ``") - for _, r := range p.Value { - if r == '`' { - buf.WriteByte('\\') - } - buf.WriteRune(r) - } - buf.WriteString("``") - } - } - return buf.Bytes() -} DELETED box/compbox/keys.go Index: box/compbox/keys.go ================================================================== --- box/compbox/keys.go +++ /dev/null @@ -1,43 +0,0 @@ -//----------------------------------------------------------------------------- -// 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()) - } - return buf.Bytes() -} DELETED box/compbox/log.go Index: box/compbox/log.go ================================================================== --- box/compbox/log.go +++ /dev/null @@ -1,52 +0,0 @@ -//----------------------------------------------------------------------------- -// 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++ { - buf.WriteByte('0') - } - buf.WriteByte(' ') - buf.WriteString(entry.Level.Format()) - buf.WriteByte(' ') - buf.WriteString(entry.Prefix) - buf.WriteByte(' ') - buf.WriteString(entry.Message) - buf.WriteByte('\n') - } - return buf.Bytes() -} DELETED box/compbox/manager.go Index: box/compbox/manager.go ================================================================== --- box/compbox/manager.go +++ /dev/null @@ -1,41 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 { - fmt.Fprintf(&buf, "| %v | %v\n", kv.Key, kv.Value) - } - return buf.Bytes() -} DELETED box/compbox/memory.go Index: box/compbox/memory.go ================================================================== --- box/compbox/memory.go +++ /dev/null @@ -1,55 +0,0 @@ -//----------------------------------------------------------------------------- -// 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) - fmt.Fprintf(&buf, "|CPUs|%d\n", runtime.NumCPU()) - fmt.Fprintf(&buf, "|Threads|%d\n", runtime.NumGoroutine()) - 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() -} DELETED box/compbox/parser.go Index: box/compbox/parser.go ================================================================== --- box/compbox/parser.go +++ /dev/null @@ -1,54 +0,0 @@ -//----------------------------------------------------------------------------- -// 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() -} DELETED box/compbox/sx.go Index: box/compbox/sx.go ================================================================== --- box/compbox/sx.go +++ /dev/null @@ -1,35 +0,0 @@ -//----------------------------------------------------------------------------- -// 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() -} DELETED box/compbox/version.go Index: box/compbox/version.go ================================================================== --- box/compbox/version.go +++ /dev/null @@ -1,52 +0,0 @@ -//----------------------------------------------------------------------------- -// 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...) -} DELETED box/constbox/base.css Index: box/constbox/base.css ================================================================== --- box/constbox/base.css +++ /dev/null @@ -1,250 +0,0 @@ -/*----------------------------------------------------------------------------- - * 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; - line-height: 1.4; - background-color: #f8f8f8 ; - height: 100%; - } - nav.zs-menu { - background-color: hsl(210, 28%, 90%); - overflow: auto; - white-space: nowrap; - font-family: sans-serif; - padding-left: .5rem; - } - nav.zs-menu > a { - float:left; - display: block; - text-align: center; - padding:.41rem .5rem; - text-decoration: none; - color:black; - } - nav.zs-menu > a:hover, .zs-dropdown:hover button { background-color: hsl(210, 28%, 80%) } - nav.zs-menu form { float: right } - nav.zs-menu form input[type=text] { - padding: .12rem; - border: none; - margin-top: .25rem; - margin-right: .5rem; - } - .zs-dropdown { - float: left; - overflow: hidden; - } - .zs-dropdown > button { - font-size: 16px; - border: none; - outline: none; - color: black; - padding:.41rem .5rem; - background-color: inherit; - font-family: inherit; - margin: 0; - } - .zs-dropdown-content { - display: none; - position: absolute; - background-color: #f9f9f9; - min-width: 160px; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - z-index: 1; - } - .zs-dropdown-content > a { - float: none; - color: black; - padding:.41rem .5rem; - text-decoration: none; - display: block; - text-align: left; - } - .zs-dropdown-content > a:hover { background-color: hsl(210, 28%, 75%) } - .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; - height: 0; - clear: both; - visibility: hidden; - } - main form div { margin: .5em 0 0 0 } - input { font-family: monospace } - input[type="submit"],button,select { font: inherit } - label { font-family: sans-serif; font-size:.9rem } - textarea { - font-family: monospace; - resize: vertical; - width: 100%; - } - .zs-input { - padding: .5em; - display:block; - border:none; - border-bottom:1px solid #ccc; - width:100%; - } - input.zs-primary { float:right } - 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; - } - } DELETED box/constbox/base.sxn Index: box/constbox/base.sxn ================================================================== --- box/constbox/base.sxn +++ /dev/null @@ -1,63 +0,0 @@ -;;;---------------------------------------------------------------------------- -;;; 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") - (nav (@ (class "zs-dropdown-content")) - ,@(if user-is-valid - `((a (@ (href ,user-zettel-url)) ,user-ident) - (a (@ (href ,logout-url)) "Logout")) - `((a (@ (href ,login-url)) "Login")) - ) - ))) - ) - (div (@ (class "zs-dropdown")) - (button "Lists") - (nav (@ (class "zs-dropdown-content")) - (a (@ (href ,list-zettel-url)) "List Zettel") - (a (@ (href ,list-roles-url)) "List Roles") - (a (@ (href ,list-tags-url)) "List Tags") - ,@(if (bound? 'refresh-url) `((a (@ (href ,refresh-url)) "Refresh"))) - )) - ,@(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!")))) -))) DELETED box/constbox/constbox.go Index: box/constbox/constbox.go ================================================================== --- box/constbox/constbox.go +++ /dev/null @@ -1,487 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - "zettelstore.de/z/zettel/meta" -) - -func init() { - manager.Register( - " const", - func(_ *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { - return &constBox{ - log: kernel.Main.GetLogger(kernel.BoxService).Clone(). - Str("box", "const").Int("boxnum", int64(cdata.Number)).Child(), - number: cdata.Number, - zettel: constZettelMap, - enricher: cdata.Enricher, - }, nil - }) -} - -type constHeader map[string]string - -type constZettel struct { - header constHeader - content zettel.Content -} - -type constBox struct { - log *logger.Logger - number int - zettel map[id.Zid]constZettel - enricher box.Enricher -} - -func (*constBox) Location() string { return "const:" } - -func (cb *constBox) GetZettel(_ context.Context, zid id.Zid) (zettel.Zettel, error) { - if z, ok := cb.zettel[zid]; ok { - cb.log.Trace().Msg("GetZettel") - return zettel.Zettel{Meta: meta.NewWithData(zid, z.header), Content: z.content}, nil - } - err := box.ErrZettelNotFound{Zid: zid} - cb.log.Trace().Err(err).Msg("GetZettel/Err") - return zettel.Zettel{}, err -} - -func (cb *constBox) HasZettel(_ context.Context, zid id.Zid) bool { - _, found := cb.zettel[zid] - return found -} - -func (cb *constBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { - cb.log.Trace().Int("entries", int64(len(cb.zettel))).Msg("ApplyZid") - for zid := range cb.zettel { - if constraint(zid) { - handle(zid) - } - } - return nil -} - -func (cb *constBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint query.RetrievePredicate) error { - cb.log.Trace().Int("entries", int64(len(cb.zettel))).Msg("ApplyMeta") - for zid, zettel := range cb.zettel { - if constraint(zid) { - m := meta.NewWithData(zid, zettel.header) - 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} - } - cb.log.Trace().Err(err).Msg("DeleteZettel") - return err -} - -func (cb *constBox) ReadStats(st *box.ManagedBoxStats) { - st.ReadOnly = true - st.Zettel = len(cb.zettel) - cb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") -} - -var constZettelMap = map[id.Zid]constZettel{ - id.ConfigurationZid: { - constHeader{ - api.KeyTitle: "Zettelstore Runtime Configuration", - api.KeyRole: api.ValueRoleConfiguration, - api.KeySyntax: meta.SyntaxNone, - api.KeyCreated: "20200804111624", - api.KeyVisibility: api.ValueVisibilityOwner, - }, - zettel.NewContent(nil)}, - id.MustParse(api.ZidLicense): { - constHeader{ - api.KeyTitle: "Zettelstore License", - api.KeyRole: api.ValueRoleConfiguration, - api.KeySyntax: meta.SyntaxText, - api.KeyCreated: "20210504135842", - api.KeyLang: api.ValueLangEN, - api.KeyModified: "20220131153422", - api.KeyReadOnly: api.ValueTrue, - api.KeyVisibility: api.ValueVisibilityPublic, - }, - zettel.NewContent(contentLicense)}, - id.MustParse(api.ZidAuthors): { - constHeader{ - api.KeyTitle: "Zettelstore Contributors", - api.KeyRole: api.ValueRoleConfiguration, - api.KeySyntax: meta.SyntaxZmk, - api.KeyCreated: "20210504135842", - api.KeyLang: api.ValueLangEN, - api.KeyReadOnly: api.ValueTrue, - api.KeyVisibility: api.ValueVisibilityLogin, - }, - zettel.NewContent(contentContributors)}, - id.MustParse(api.ZidDependencies): { - constHeader{ - api.KeyTitle: "Zettelstore Dependencies", - api.KeyRole: api.ValueRoleConfiguration, - api.KeySyntax: meta.SyntaxZmk, - api.KeyLang: api.ValueLangEN, - api.KeyReadOnly: api.ValueTrue, - api.KeyVisibility: api.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: "20241127170400", - 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: "20241127170500", - 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: "20241127170530", - 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: "20241118173500", - 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, - api.KeySyntax: meta.SyntaxCSS, - api.KeyCreated: "20210622110143", - api.KeyVisibility: api.ValueVisibilityPublic, - }, - zettel.NewContent([]byte("/* User-defined CSS */"))}, - id.EmojiZid: { - constHeader{ - api.KeyTitle: "Zettelstore Generic Emoji", - api.KeyRole: api.ValueRoleConfiguration, - api.KeySyntax: meta.SyntaxGif, - api.KeyReadOnly: api.ValueTrue, - api.KeyCreated: "20210504175807", - api.KeyVisibility: api.ValueVisibilityPublic, - }, - zettel.NewContent(contentEmoji)}, - id.TOCNewTemplateZid: { - constHeader{ - api.KeyTitle: "New Menu", - api.KeyRole: api.ValueRoleConfiguration, - api.KeySyntax: meta.SyntaxZmk, - api.KeyLang: api.ValueLangEN, - api.KeyCreated: "20210217161829", - api.KeyModified: "20231129111800", - api.KeyVisibility: api.ValueVisibilityCreator, - }, - zettel.NewContent(contentNewTOCZettel)}, - id.MustParse(api.ZidTemplateNewZettel): { - constHeader{ - api.KeyTitle: "New Zettel", - api.KeyRole: api.ValueRoleConfiguration, - api.KeySyntax: meta.SyntaxZmk, - api.KeyCreated: "20201028185209", - api.KeyModified: "20230929132900", - meta.NewPrefix + api.KeyRole: api.ValueRoleZettel, - api.KeyVisibility: api.ValueVisibilityCreator, - }, - zettel.NewContent(nil)}, - id.MustParse(api.ZidTemplateNewRole): { - constHeader{ - api.KeyTitle: "New Role", - api.KeyRole: api.ValueRoleConfiguration, - api.KeySyntax: meta.SyntaxZmk, - api.KeyCreated: "20231129110800", - meta.NewPrefix + api.KeyRole: api.ValueRoleRole, - meta.NewPrefix + api.KeyTitle: "", - api.KeyVisibility: api.ValueVisibilityCreator, - }, - zettel.NewContent(nil)}, - id.MustParse(api.ZidTemplateNewTag): { - constHeader{ - api.KeyTitle: "New Tag", - api.KeyRole: api.ValueRoleConfiguration, - api.KeySyntax: meta.SyntaxZmk, - api.KeyCreated: "20230929132400", - meta.NewPrefix + api.KeyRole: api.ValueRoleTag, - meta.NewPrefix + api.KeyTitle: "#", - api.KeyVisibility: api.ValueVisibilityCreator, - }, - zettel.NewContent(nil)}, - id.MustParse(api.ZidTemplateNewUser): { - constHeader{ - api.KeyTitle: "New User", - api.KeyRole: api.ValueRoleConfiguration, - api.KeySyntax: meta.SyntaxNone, - api.KeyCreated: "20201028185209", - meta.NewPrefix + api.KeyCredential: "", - meta.NewPrefix + api.KeyUserID: "", - meta.NewPrefix + api.KeyUserRole: api.ValueUserRoleReader, - api.KeyVisibility: api.ValueVisibilityOwner, - }, - zettel.NewContent(nil)}, - id.MustParse(api.ZidRoleZettelZettel): { - constHeader{ - api.KeyTitle: api.ValueRoleZettel, - api.KeyRole: api.ValueRoleRole, - api.KeySyntax: meta.SyntaxZmk, - api.KeyCreated: "20231129161400", - api.KeyLang: api.ValueLangEN, - api.KeyVisibility: api.ValueVisibilityLogin, - }, - zettel.NewContent(contentRoleZettel)}, - id.MustParse(api.ZidRoleConfigurationZettel): { - constHeader{ - api.KeyTitle: api.ValueRoleConfiguration, - api.KeyRole: api.ValueRoleRole, - api.KeySyntax: meta.SyntaxZmk, - api.KeyCreated: "20241213103100", - api.KeyLang: api.ValueLangEN, - api.KeyVisibility: api.ValueVisibilityLogin, - }, - zettel.NewContent(contentRoleConfiguration)}, - id.MustParse(api.ZidRoleRoleZettel): { - constHeader{ - api.KeyTitle: api.ValueRoleRole, - api.KeyRole: api.ValueRoleRole, - api.KeySyntax: meta.SyntaxZmk, - api.KeyCreated: "20231129162900", - api.KeyLang: api.ValueLangEN, - api.KeyVisibility: api.ValueVisibilityLogin, - }, - zettel.NewContent(contentRoleRole)}, - id.MustParse(api.ZidRoleTagZettel): { - constHeader{ - api.KeyTitle: api.ValueRoleTag, - 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.DefaultHomeZid: { - constHeader{ - api.KeyTitle: "Home", - api.KeyRole: api.ValueRoleZettel, - api.KeySyntax: meta.SyntaxZmk, - api.KeyLang: api.ValueLangEN, - api.KeyCreated: "20210210190757", - }, - zettel.NewContent(contentHomeZettel)}, -} - -//go:embed license.txt -var contentLicense []byte - -//go:embed contributors.zettel -var contentContributors []byte - -//go:embed dependencies.zettel -var contentDependencies []byte - -//go:embed base.sxn -var contentBaseSxn []byte - -//go:embed login.sxn -var contentLoginSxn []byte - -//go:embed zettel.sxn -var contentZettelSxn []byte - -//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 -var contentErrorSxn []byte - -//go:embed start.sxn -var contentStartCodeSxn []byte - -//go:embed wuicode.sxn -var contentBaseCodeSxn []byte - -//go:embed prelude.sxn -var contentPreludeSxn []byte - -//go:embed base.css -var contentBaseCSS []byte - -//go:embed emoji_spin.gif -var contentEmoji []byte - -//go:embed newtoc.zettel -var contentNewTOCZettel []byte - -//go:embed rolezettel.zettel -var contentRoleZettel []byte - -//go:embed roleconfiguration.zettel -var contentRoleConfiguration []byte - -//go:embed rolerole.zettel -var contentRoleRole []byte - -//go:embed roletag.zettel -var contentRoleTag []byte - -//go:embed home.zettel -var contentHomeZettel []byte DELETED box/constbox/contributors.zettel Index: box/constbox/contributors.zettel ================================================================== --- box/constbox/contributors.zettel +++ /dev/null @@ -1,8 +0,0 @@ -Zettelstore is a software for humans made from humans. - -=== Licensor(s) -* Detlef Stern [[mailto:ds@zettelstore.de]] -** Main author -** Maintainer - -=== Contributors DELETED box/constbox/delete.sxn Index: box/constbox/delete.sxn ================================================================== --- box/constbox/delete.sxn +++ /dev/null @@ -1,39 +0,0 @@ -;;;---------------------------------------------------------------------------- -;;; 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)) - (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.") - )) - ) - ,@(if incoming - `((div (@ (class "zs-warning")) - (h2 "Warning!") - (p "If you delete this zettel, incoming references from the following zettel will become invalid.") - (ul ,@(map wui-item-link incoming)) - )) - ) - ,@(if (and (bound? 'useless) useless) - `((div (@ (class "zs-warning")) - (h2 "Warning!") - (p "Deleting this zettel will also delete the following files, so that they will not be interpreted as content for this zettel.") - (ul ,@(map wui-item useless)) - )) - ) - ,(wui-meta-desc metapairs) - (form (@ (method "POST")) (input (@ (class "zs-primary") (type "submit") (value "Delete")))) -) DELETED box/constbox/dependencies.zettel Index: box/constbox/dependencies.zettel ================================================================== --- box/constbox/dependencies.zettel +++ /dev/null @@ -1,146 +0,0 @@ -Zettelstore is made with the help of other software and other artifacts. -Thank you very much! - -This zettel lists all of them, together with their licenses. - -=== Go runtime and associated libraries -; License -: BSD 3-Clause "New" or "Revised" License -``` -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -``` - -=== ASCIIToSVG -; URL -: [[https://github.com/asciitosvg/asciitosvg]] -; License -: MIT -; Remarks -: ASCIIToSVG was incorporated into the source code of Zettelstore, moving it into package ''zettelstore.de/z/parser/draw''. - Later, the source code was changed substantially to adapt it to the needs of Zettelstore. -``` -Copyright (c) 2015 The ASCIIToSVG Contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` - -=== Fsnotify -; URL -: [[https://fsnotify.org/]] -; License -: BSD 3-Clause "New" or "Revised" License -; Source -: [[https://github.com/fsnotify/fsnotify]] -``` -Copyright © 2012 The Go Authors. All rights reserved. -Copyright © fsnotify Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. -* Neither the name of Google Inc. nor the names of its contributors may be used - to endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -``` - -=== yuin/goldmark -; URL & Source -: [[https://github.com/yuin/goldmark]] -; License -: MIT License -``` -MIT License - -Copyright (c) 2019 Yusuke Inuzuka - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` - -=== 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. DELETED box/constbox/emoji_spin.gif Index: box/constbox/emoji_spin.gif ================================================================== --- box/constbox/emoji_spin.gif +++ /dev/null cannot compute difference between binary files DELETED box/constbox/error.sxn Index: box/constbox/error.sxn ================================================================== --- box/constbox/error.sxn +++ /dev/null @@ -1,17 +0,0 @@ -;;;---------------------------------------------------------------------------- -;;; 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 -) DELETED box/constbox/form.sxn Index: box/constbox/form.sxn ================================================================== --- box/constbox/form.sxn +++ /dev/null @@ -1,63 +0,0 @@ -;;;---------------------------------------------------------------------------- -;;; 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"))) - )) -) DELETED box/constbox/home.zettel Index: box/constbox/home.zettel ================================================================== --- box/constbox/home.zettel +++ /dev/null @@ -1,43 +0,0 @@ -=== Thank you for using Zettelstore! - -You will find the lastest information about Zettelstore at [[https://zettelstore.de]]. -Check that website regulary for [[upgrades|https://zettelstore.de/home/doc/trunk/www/download.wiki]] to the latest version. -You should consult the [[change log|https://zettelstore.de/home/doc/trunk/www/changes.wiki]] before upgrading. -Sometimes, you have to edit some of your Zettelstore-related zettel before upgrading. -Since Zettelstore is currently in a development state, every upgrade might fix some of your problems. - -If you have problems concerning Zettelstore, -do not hesitate to get in [[contact with the main developer|mailto:ds@zettelstore.de]]. - -=== Reporting errors -If you have encountered an error, please include the content of the following zettel in your mail (if possible): -* [[Zettelstore Version|00000000000001]]: {{00000000000001}} -* [[Zettelstore Operating System|00000000000003]] -* [[Zettelstore Startup Configuration|00000000000096]] -* [[Zettelstore Runtime Configuration|00000000000100]] - -Additionally, you have to describe, what you have done before that error occurs -and what you have expected instead. -Please do not forget to include the error message, if there is one. - -Some of above Zettelstore zettel can only be retrieved if you enabled ""expert mode"". -Otherwise, only some zettel are linked. -To enable expert mode, edit the zettel [[Zettelstore Runtime Configuration|00000000000100]]: -please set the metadata value of the key ''expert-mode'' to true. -To do you, enter the string ''expert-mode:true'' inside the edit view of the metadata. - -=== Information about this zettel -This zettel is your home zettel. -It is part of the Zettelstore software itself. -Every time you click on the [[Home|//]] link in the menu bar, you will be redirected to this zettel. - -You can change the content of this zettel by clicking on ""Edit"" above. -This allows you to customize your home zettel. - -Alternatively, you can designate another zettel as your home zettel. -Edit the [[Zettelstore Runtime Configuration|00000000000100]] by adding the metadata key ''home-zettel''. -Its value is the identifier of the zettel that should act as the new home zettel. -You will find the identifier of each zettel between their ""Edit"" and the ""Info"" link above. -The identifier of this zettel is ''00010000000000''. -If you provide a wrong identifier, this zettel will be shown as the home zettel. -Take a look inside the manual for further details. DELETED box/constbox/info.sxn Index: box/constbox/info.sxn ================================================================== --- box/constbox/info.sxn +++ /dev/null @@ -1,47 +0,0 @@ -;;;---------------------------------------------------------------------------- -;;; 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) - (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))) - ) - (h2 "Parts and encodings") - ,(wui-enc-matrix enc-eval) - (h3 "Parsed (not evaluated)") - ,(wui-enc-matrix enc-parsed) - ,@(if shadow-links - `((h2 "Shadowed Boxes") - (ul ,@(map wui-item shadow-links)) - ) - ) -) DELETED box/constbox/license.txt Index: box/constbox/license.txt ================================================================== --- box/constbox/license.txt +++ /dev/null @@ -1,295 +0,0 @@ -Copyright (c) 2020-present Detlef Stern - - Licensed under the EUPL - -Zettelstore is licensed under the European Union Public License, version 1.2 or -later (EUPL v. 1.2). The license is available in the official languages of the -EU. The English version is included here. Please see -https://joinup.ec.europa.eu/community/eupl/og_page/eupl for official -translations of the other languages. - - -------------------------------------------------------------------------------- - - -EUROPEAN UNION PUBLIC LICENCE v. 1.2 -EUPL © the European Union 2007, 2016 - -This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined -below) which is provided under the terms of this Licence. Any use of the Work, -other than as authorised under this Licence is prohibited (to the extent such -use is covered by a right of the copyright holder of the Work). - -The Work is provided under the terms of this Licence when the Licensor (as -defined below) has placed the following notice immediately following the -copyright notice for the Work: - - Licensed under the EUPL - -or has expressed by any other means his willingness to license under the EUPL. - -1. Definitions - -In this Licence, the following terms have the following meaning: - -— ‘The Licence’: this Licence. -— ‘The Original Work’: the work or software distributed or communicated by the - Licensor under this Licence, available as Source Code and also as Executable - Code as the case may be. -— ‘Derivative Works’: the works or software that could be created by the - Licensee, based upon the Original Work or modifications thereof. This Licence - does not define the extent of modification or dependence on the Original Work - required in order to classify a work as a Derivative Work; this extent is - determined by copyright law applicable in the country mentioned in Article - 15. -— ‘The Work’: the Original Work or its Derivative Works. -— ‘The Source Code’: the human-readable form of the Work which is the most - convenient for people to study and modify. -— ‘The Executable Code’: any code which has generally been compiled and which - is meant to be interpreted by a computer as a program. -— ‘The Licensor’: the natural or legal person that distributes or communicates - the Work under the Licence. -— ‘Contributor(s)’: any natural or legal person who modifies the Work under the - Licence, or otherwise contributes to the creation of a Derivative Work. -— ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of - the Work under the terms of the Licence. -— ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, - renting, distributing, communicating, transmitting, or otherwise making - available, online or offline, copies of the Work or providing access to its - essential functionalities at the disposal of any other natural or legal - person. - -2. Scope of the rights granted by the Licence - -The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, -sublicensable licence to do the following, for the duration of copyright vested -in the Original Work: - -— use the Work in any circumstance and for all usage, -— reproduce the Work, -— modify the Work, and make Derivative Works based upon the Work, -— communicate to the public, including the right to make available or display - the Work or copies thereof to the public and perform publicly, as the case - may be, the Work, -— distribute the Work or copies thereof, -— lend and rent the Work or copies thereof, -— sublicense rights in the Work or copies thereof. - -Those rights can be exercised on any media, supports and formats, whether now -known or later invented, as far as the applicable law permits so. - -In the countries where moral rights apply, the Licensor waives his right to -exercise his moral right to the extent allowed by law in order to make -effective the licence of the economic rights here above listed. - -The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to -any patents held by the Licensor, to the extent necessary to make use of the -rights granted on the Work under this Licence. - -3. Communication of the Source Code - -The Licensor may provide the Work either in its Source Code form, or as -Executable Code. If the Work is provided as Executable Code, the Licensor -provides in addition a machine-readable copy of the Source Code of the Work -along with each copy of the Work that the Licensor distributes or indicates, in -a notice following the copyright notice attached to the Work, a repository -where the Source Code is easily and freely accessible for as long as the -Licensor continues to distribute or communicate the Work. - -4. Limitations on copyright - -Nothing in this Licence is intended to deprive the Licensee of the benefits -from any exception or limitation to the exclusive rights of the rights owners -in the Work, of the exhaustion of those rights or of other applicable -limitations thereto. - -5. Obligations of the Licensee - -The grant of the rights mentioned above is subject to some restrictions and -obligations imposed on the Licensee. Those obligations are the following: - -Attribution right: The Licensee shall keep intact all copyright, patent or -trademarks notices and all notices that refer to the Licence and to the -disclaimer of warranties. The Licensee must include a copy of such notices and -a copy of the Licence with every copy of the Work he/she distributes or -communicates. The Licensee must cause any Derivative Work to carry prominent -notices stating that the Work has been modified and the date of modification. - -Copyleft clause: If the Licensee distributes or communicates copies of the -Original Works or Derivative Works, this Distribution or Communication will be -done under the terms of this Licence or of a later version of this Licence -unless the Original Work is expressly distributed only under this version of -the Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee -(becoming Licensor) cannot offer or impose any additional terms or conditions -on the Work or Derivative Work that alter or restrict the terms of the Licence. - -Compatibility clause: If the Licensee Distributes or Communicates Derivative -Works or copies thereof based upon both the Work and another work licensed -under a Compatible Licence, this Distribution or Communication can be done -under the terms of this Compatible Licence. For the sake of this clause, -‘Compatible Licence’ refers to the licences listed in the appendix attached to -this Licence. Should the Licensee's obligations under the Compatible Licence -conflict with his/her obligations under this Licence, the obligations of the -Compatible Licence shall prevail. - -Provision of Source Code: When distributing or communicating copies of the -Work, the Licensee will provide a machine-readable copy of the Source Code or -indicate a repository where this Source will be easily and freely available for -as long as the Licensee continues to distribute or communicate the Work. - -Legal Protection: This Licence does not grant permission to use the trade -names, trademarks, service marks, or names of the Licensor, except as required -for reasonable and customary use in describing the origin of the Work and -reproducing the content of the copyright notice. - -6. Chain of Authorship - -The original Licensor warrants that the copyright in the Original Work granted -hereunder is owned by him/her or licensed to him/her and that he/she has the -power and authority to grant the Licence. - -Each Contributor warrants that the copyright in the modifications he/she brings -to the Work are owned by him/her or licensed to him/her and that he/she has the -power and authority to grant the Licence. - -Each time You accept the Licence, the original Licensor and subsequent -Contributors grant You a licence to their contributions to the Work, under the -terms of this Licence. - -7. Disclaimer of Warranty - -The Work is a work in progress, which is continuously improved by numerous -Contributors. It is not a finished work and may therefore contain defects or -‘bugs’ inherent to this type of development. - -For the above reason, the Work is provided under the Licence on an ‘as is’ -basis and without warranties of any kind concerning the Work, including without -limitation merchantability, fitness for a particular purpose, absence of -defects or errors, accuracy, non-infringement of intellectual property rights -other than copyright as stated in Article 6 of this Licence. - -This disclaimer of warranty is an essential part of the Licence and a condition -for the grant of any rights to the Work. - -8. Disclaimer of Liability - -Except in the cases of wilful misconduct or damages directly caused to natural -persons, the Licensor will in no event be liable for any direct or indirect, -material or moral, damages of any kind, arising out of the Licence or of the -use of the Work, including without limitation, damages for loss of goodwill, -work stoppage, computer failure or malfunction, loss of data or any commercial -damage, even if the Licensor has been advised of the possibility of such -damage. However, the Licensor will be liable under statutory product liability -laws as far such laws apply to the Work. - -9. Additional agreements - -While distributing the Work, You may choose to conclude an additional -agreement, defining obligations or services consistent with this Licence. -However, if accepting obligations, You may act only on your own behalf and on -your sole responsibility, not on behalf of the original Licensor or any other -Contributor, and only if You agree to indemnify, defend, and hold each -Contributor harmless for any liability incurred by, or claims asserted against -such Contributor by the fact You have accepted any warranty or additional -liability. - -10. Acceptance of the Licence - -The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ -placed under the bottom of a window displaying the text of this Licence or by -affirming consent in any other similar way, in accordance with the rules of -applicable law. Clicking on that icon indicates your clear and irrevocable -acceptance of this Licence and all of its terms and conditions. - -Similarly, you irrevocably accept this Licence and all of its terms and -conditions by exercising any rights granted to You by Article 2 of this -Licence, such as the use of the Work, the creation by You of a Derivative Work -or the Distribution or Communication by You of the Work or copies thereof. - -11. Information to the public - -In case of any Distribution or Communication of the Work by means of electronic -communication by You (for example, by offering to download the Work from -a remote location) the distribution channel or media (for example, a website) -must at least provide to the public the information requested by the applicable -law regarding the Licensor, the Licence and the way it may be accessible, -concluded, stored and reproduced by the Licensee. - -12. Termination of the Licence - -The Licence and the rights granted hereunder will terminate automatically upon -any breach by the Licensee of the terms of the Licence. - -Such a termination will not terminate the licences of any person who has -received the Work from the Licensee under the Licence, provided such persons -remain in full compliance with the Licence. - -13. Miscellaneous - -Without prejudice of Article 9 above, the Licence represents the complete -agreement between the Parties as to the Work. - -If any provision of the Licence is invalid or unenforceable under applicable -law, this will not affect the validity or enforceability of the Licence as -a whole. Such provision will be construed or reformed so as necessary to make -it valid and enforceable. - -The European Commission may publish other linguistic versions or new versions -of this Licence or updated versions of the Appendix, so far this is required -and reasonable, without reducing the scope of the rights granted by the -Licence. New versions of the Licence will be published with a unique version -number. - -All linguistic versions of this Licence, approved by the European Commission, -have identical value. Parties can take advantage of the linguistic version of -their choice. - -14. Jurisdiction - -Without prejudice to specific agreement between parties, - -— any litigation resulting from the interpretation of this License, arising - between the European Union institutions, bodies, offices or agencies, as - a Licensor, and any Licensee, will be subject to the jurisdiction of the - Court of Justice of the European Union, as laid down in article 272 of the - Treaty on the Functioning of the European Union, -— any litigation arising between other parties and resulting from the - interpretation of this License, will be subject to the exclusive jurisdiction - of the competent court where the Licensor resides or conducts its primary - business. - -15. Applicable Law - -Without prejudice to specific agreement between parties, - -— this Licence shall be governed by the law of the European Union Member State - where the Licensor has his seat, resides or has his registered office, -— this licence shall be governed by Belgian law if the Licensor has no seat, - residence or registered office inside a European Union Member State. - - - Appendix - - -‘Compatible Licences’ according to Article 5 EUPL are: - -— GNU General Public License (GPL) v. 2, v. 3 -— GNU Affero General Public License (AGPL) v. 3 -— Open Software License (OSL) v. 2.1, v. 3.0 -— Eclipse Public License (EPL) v. 1.0 -— CeCILL v. 2.0, v. 2.1 -— Mozilla Public Licence (MPL) v. 2 -— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 -— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for - works other than software -— European Union Public Licence (EUPL) v. 1.1, v. 1.2 -— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong - Reciprocity (LiLiQ-R+) - -The European Commission may update this Appendix to later versions of the above -licences without producing a new version of the EUPL, as long as they provide -the rights granted in Article 2 of this Licence and protect the covered Source -Code from exclusive appropriation. - -All other changes or additions to this Appendix require the production of a new -EUPL version. DELETED box/constbox/listzettel.sxn Index: box/constbox/listzettel.sxn ================================================================== --- box/constbox/listzettel.sxn +++ /dev/null @@ -1,50 +0,0 @@ -;;;---------------------------------------------------------------------------- -;;; 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"))) - ) - ) - ) -) DELETED box/constbox/login.sxn Index: box/constbox/login.sxn ================================================================== --- box/constbox/login.sxn +++ /dev/null @@ -1,27 +0,0 @@ -;;;---------------------------------------------------------------------------- -;;; 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)))) - (div - (label (@ (for "password")) "Password:") - (input (@ (class "zs-input") (type "password") (id "password") (name "password") (placeholder "Your password..")))) - (div - (input (@ (class "zs-primary") (type "submit") (value "Login")))) - ) -) DELETED box/constbox/newtoc.zettel Index: box/constbox/newtoc.zettel ================================================================== --- box/constbox/newtoc.zettel +++ /dev/null @@ -1,6 +0,0 @@ -This zettel lists all zettel that should act as a template for new zettel. -These zettel will be included in the ""New"" menu of the WebUI. -* [[New Zettel|00000000090001]] -* [[New Role|00000000090004]] -* [[New Tag|00000000090003]] -* [[New User|00000000090002]] DELETED box/constbox/prelude.sxn Index: box/constbox/prelude.sxn ================================================================== --- box/constbox/prelude.sxn +++ /dev/null @@ -1,62 +0,0 @@ -;;;---------------------------------------------------------------------------- -;;; 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)) - (T `(if ,(car args) (and ,@(cdr args)))))) - - -;; or macro -;; -;; (or EXPR ...) -(defmacro or args - (cond ((null? args) NIL) - ((null? (cdr args)) (car args)) - (T `(if ,(car args) T (or ,@(cdr args)))))) DELETED box/constbox/roleconfiguration.zettel Index: box/constbox/roleconfiguration.zettel ================================================================== --- box/constbox/roleconfiguration.zettel +++ /dev/null @@ -1,22 +0,0 @@ -Zettel with role ""configuration"" are used within Zettelstore to manage and to show the current configuration of the software. - -Typically, there are some public zettel that show the license of this software, its dependencies. -There is some CSS code to make the default web user interface a litte bit nicer. -The default image to signal a broken image can be configured too. - -Other zettel are only visible if an user has authenticated itself, or if there is no authentication enabled. -In this case, one additional configuration zettel is the zettel containing the version number of this software. -Other zettel are showing the supported metadata keys and supported syntax values. -Zettel that allow to configure the menu of template to create new zettel are also using the role ""configuration"". - -Most important is the zettel that contains the runtime configuration. -You may change its metadata value to change the behaviour of the software. - -One configuration is the ""expert mode"". -If enabled, and if you are authorized to see them, you will discover some more zettel. -For example, HTML templates to customize the default web user interface, to show the application log, to see statistics about zettel boxes, to show the host name and it operating system, and many more. - -You are allowed to add your own configuration zettel, for example if you want to customize the look and feel of zettel by placing relevant data into your own zettel. - -By default, user zettel (for authentification) use also the role ""configuration"". -However, you are allowed to change this. DELETED box/constbox/rolerole.zettel Index: box/constbox/rolerole.zettel ================================================================== --- box/constbox/rolerole.zettel +++ /dev/null @@ -1,10 +0,0 @@ -A zettel with the role ""role"" describes a specific role. -The described role must be the title of such a zettel. - -This zettel is such a zettel, as it describes the meaning of the role ""role"". -Therefore it has the title ""role"" too. -If you like, this zettel is a meta-role. - -You are free to create your own role-describing zettel. -For example, you want to document the intended meaning of the role. -You might also be interested to describe needed metadata so that some software is enabled to analyse or to process your zettel. DELETED box/constbox/roletag.zettel Index: box/constbox/roletag.zettel ================================================================== --- box/constbox/roletag.zettel +++ /dev/null @@ -1,6 +0,0 @@ -A zettel with role ""tag"" is a zettel that describes specific tag. -The tag name must be the title of such a zettel. - -Such zettel are similar to this specific zettel: this zettel describes zettel with a role ""tag"". -These zettel with the role ""tag"" describe specific tags. -These might form a hierarchy of meta-tags (and meta-roles). DELETED box/constbox/rolezettel.zettel Index: box/constbox/rolezettel.zettel ================================================================== --- box/constbox/rolezettel.zettel +++ /dev/null @@ -1,7 +0,0 @@ -A zettel with the role ""zettel"" is typically used to document your own thoughts. -Such zettel are the main reason to use the software Zettelstore. - -The only predefined zettel with the role ""zettel"" is the [[default home zettel|00010000000000]], which contains some welcome information. - -You are free to change this. -In this case you should modify this zettel too, so that it reflects your own use of zettel with the role ""zettel"". DELETED box/constbox/start.sxn Index: box/constbox/start.sxn ================================================================== --- box/constbox/start.sxn +++ /dev/null @@ -1,17 +0,0 @@ -;;;---------------------------------------------------------------------------- -;;; 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. DELETED box/constbox/wuicode.sxn Index: box/constbox/wuicode.sxn ================================================================== --- box/constbox/wuicode.sxn +++ /dev/null @@ -1,138 +0,0 @@ -;;;---------------------------------------------------------------------------- -;;; 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 ((sequel-url (binding-lookup 'sequel-url binding))) - (if (defined? sequel-url) `((@H " · ") (a (@ (href ,sequel-url)) "Sequel")))) - ,@(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))) - ) -) DELETED box/constbox/zettel.sxn Index: box/constbox/zettel.sxn ================================================================== --- box/constbox/zettel.sxn +++ /dev/null @@ -1,43 +0,0 @@ -;;;---------------------------------------------------------------------------- -;;; 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 (@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 prequel-refs `((br) "Prequel: " ,prequel-refs)) - ,@(ROLE-DEFAULT-heading (current-binding)) - ) - ) - ,@content - ,endnotes - ,@(if (or folge-links sequel-links back-links successor-links) - `((nav - ,@(if folge-links `((details (@ (,folge-open)) (summary "Folgezettel") (ul ,@(map wui-item-link folge-links))))) - ,@(if sequel-links `((details (@ (,sequel-open)) (summary "Sequel") (ul ,@(map wui-item-link sequel-links))))) - ,@(if back-links `((details (@ (,back-open)) (summary "Incoming") (ul ,@(map wui-item-link back-links))))) - ,@(if successor-links `((details (@ (,successor-open)) (summary "Successors") (ul ,@(map wui-item-link successor-links))))) - )) - ) -) DELETED box/dirbox/dirbox.go Index: box/dirbox/dirbox.go ================================================================== --- box/dirbox/dirbox.go +++ /dev/null @@ -1,362 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - "errors" - "net/url" - "os" - "path/filepath" - "sync" - - "zettelstore.de/z/box" - "zettelstore.de/z/box/manager" - "zettelstore.de/z/box/notify" - "zettelstore.de/z/kernel" - "zettelstore.de/z/logger" - "zettelstore.de/z/query" - "zettelstore.de/z/zettel" - "zettelstore.de/z/zettel/id" - "zettelstore.de/z/zettel/meta" -) - -func init() { - manager.Register("dir", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { - var log *logger.Logger - if krnl := kernel.Main; krnl != nil { - log = krnl.GetLogger(kernel.BoxService).Clone().Str("box", "dir").Int("boxnum", int64(cdata.Number)).Child() - } - path := getDirPath(u) - if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { - return nil, err - } - dp := dirBox{ - log: log, - number: cdata.Number, - location: u.String(), - readonly: box.GetQueryBool(u, "readonly"), - cdata: *cdata, - dir: path, - notifySpec: getDirSrvInfo(log, u.Query().Get("type")), - fSrvs: makePrime(uint32(box.GetQueryInt(u, "worker", 1, 7, 1499))), - } - return &dp, nil - }) -} - -func makePrime(n uint32) uint32 { - for !isPrime(n) { - n++ - } - return n -} - -func isPrime(n uint32) bool { - if n == 0 { - return false - } - if n <= 3 { - return true - } - if n%2 == 0 { - return false - } - for i := uint32(3); i*i <= n; i += 2 { - if n%i == 0 { - return false - } - } - return true -} - -type notifyTypeSpec int - -const ( - _ 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) - } - } - log.Error().Str("notifyType", notifyType).Msg("Unable to set notify type, using a default") - return dirNotifySimple -} - -func getDirPath(u *url.URL) string { - if u.Opaque != "" { - return filepath.Clean(u.Opaque) - } - return filepath.Clean(u.Path) -} - -// dirBox uses a directory to store zettel as files. -type dirBox struct { - log *logger.Logger - number int - location string - readonly bool - cdata manager.ConnectData - dir string - notifySpec notifyTypeSpec - dirSrv *notify.DirService - fSrvs uint32 - fCmds []chan fileCmd - mxCmds sync.RWMutex -} - -func (dp *dirBox) Location() string { - return dp.location -} - -func (dp *dirBox) State() box.StartState { - if ds := dp.dirSrv; ds != nil { - switch ds.State() { - case notify.DsCreated: - return box.StartStateStopped - case notify.DsStarting: - return box.StartStateStarting - case notify.DsWorking: - return box.StartStateStarted - case notify.DsMissing: - return box.StartStateStarted - case notify.DsStopping: - return box.StartStateStopping - } - } - 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, - dp.cdata.Notify, - ) - dp.dirSrv.Start() - return nil -} - -func (dp *dirBox) Refresh(_ context.Context) { - dp.dirSrv.Refresh() - dp.log.Trace().Msg("Refresh") -} - -func (dp *dirBox) Stop(_ context.Context) { - dirSrv := dp.dirSrv - dp.dirSrv = nil - if dirSrv != nil { - dirSrv.Stop() - } - dp.stopFileServices() -} - -func (dp *dirBox) stopFileServices() { - for _, c := range dp.fCmds { - close(c) - } -} - -func (dp *dirBox) notifyChanged(zid id.Zid, reason box.UpdateReason) { - if notify := dp.cdata.Notify; notify != nil { - dp.log.Trace().Zid(zid).Uint("reason", uint64(reason)).Msg("notifyChanged") - notify(dp, zid, reason) - } -} - -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 - sum ^= uint32(zid >> 32) - sum *= 16777619 - - dp.mxCmds.RLock() - defer dp.mxCmds.RUnlock() - return dp.fCmds[sum%dp.fSrvs] -} - -func (dp *dirBox) CanCreateZettel(_ context.Context) bool { - return !dp.readonly -} - -func (dp *dirBox) CreateZettel(ctx context.Context, zettel zettel.Zettel) (id.Zid, error) { - if dp.readonly { - return id.Invalid, box.ErrReadOnly - } - - newZid, err := dp.dirSrv.SetNewDirEntry() - if err != nil { - return id.Invalid, err - } - meta := zettel.Meta - meta.Zid = newZid - 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() { - return zettel.Zettel{}, box.ErrZettelNotFound{Zid: zid} - } - m, c, err := dp.srvGetMetaContent(ctx, entry, zid) - if err != nil { - return zettel.Zettel{}, err - } - zettel := zettel.Zettel{Meta: m, Content: zettel.NewContent(c)} - dp.log.Trace().Zid(zid).Msg("GetZettel") - return zettel, nil -} - -func (dp *dirBox) HasZettel(_ context.Context, zid id.Zid) bool { - return dp.dirSrv.GetDirEntry(zid).IsValid() -} - -func (dp *dirBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { - entries := dp.dirSrv.GetDirEntries(constraint) - dp.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyZid") - for _, entry := range entries { - handle(entry.Zid) - } - return nil -} - -func (dp *dirBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint query.RetrievePredicate) error { - entries := dp.dirSrv.GetDirEntries(constraint) - dp.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyMeta") - - // The following loop could be parallelized if needed for performance. - for _, entry := range entries { - m, err := dp.srvGetMeta(ctx, entry, entry.Zid) - if err != nil { - dp.log.Trace().Err(err).Msg("ApplyMeta/getMeta") - return err - } - dp.cdata.Enricher.Enrich(ctx, m, dp.number) - handle(m) - } - return nil -} - -func (dp *dirBox) CanUpdateZettel(context.Context, zettel.Zettel) bool { - return !dp.readonly -} - -func (dp *dirBox) UpdateZettel(ctx context.Context, zettel zettel.Zettel) error { - if dp.readonly { - return box.ErrReadOnly - } - - meta := zettel.Meta - zid := meta.Zid - if !zid.IsValid() { - return box.ErrInvalidZid{Zid: zid.String()} - } - entry := dp.dirSrv.GetDirEntry(zid) - if !entry.IsValid() { - // Existing zettel, but new in this box. - entry = ¬ify.DirEntry{Zid: zid} - } - dp.updateEntryFromMetaContent(entry, meta, zettel.Content) - dp.dirSrv.UpdateDirEntry(entry) - err := dp.srvSetZettel(ctx, entry, zettel) - if err == nil { - dp.notifyChanged(zid, 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() -} - -func (dp *dirBox) DeleteZettel(ctx context.Context, zid id.Zid) error { - if dp.readonly { - return box.ErrReadOnly - } - - entry := dp.dirSrv.GetDirEntry(zid) - if !entry.IsValid() { - return box.ErrZettelNotFound{Zid: zid} - } - 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") -} DELETED box/dirbox/dirbox_test.go Index: box/dirbox/dirbox_test.go ================================================================== --- box/dirbox/dirbox_test.go +++ /dev/null @@ -1,53 +0,0 @@ -//----------------------------------------------------------------------------- -// 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) { - testcases := []struct { - n uint32 - exp bool - }{ - {0, false}, {1, true}, {2, true}, {3, true}, {4, false}, {5, true}, - {6, false}, {7, true}, {8, false}, {9, false}, {10, false}, - {11, true}, {12, false}, {13, true}, {14, false}, {15, false}, - {17, true}, {19, true}, {21, false}, {23, true}, {25, false}, - {27, false}, {29, true}, {31, true}, {33, false}, {35, false}, - } - for _, tc := range testcases { - got := isPrime(tc.n) - 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) - continue - } - if isPrime(i) && i != np { - t.Errorf("%d is already prime, but got %d as next prime", i, np) - continue - } - } -} DELETED box/dirbox/service.go Index: box/dirbox/service.go ================================================================== --- box/dirbox/service.go +++ /dev/null @@ -1,396 +0,0 @@ -//----------------------------------------------------------------------------- -// 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. - -func (dp *dirBox) srvGetMeta(ctx context.Context, entry *notify.DirEntry, zid id.Zid) (*meta.Meta, error) { - rc := make(chan resGetMeta, 1) - dp.getFileChan(zid) <- &fileGetMeta{entry, rc} - ctx, cancel := context.WithTimeout(ctx, serviceTimeout) - defer cancel() - select { - case res := <-rc: - return res.meta, res.err - case <-ctx.Done(): - return nil, ctx.Err() - } -} - -type fileGetMeta struct { - entry *notify.DirEntry - 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)) - } - if err == nil { - cmdCleanupMeta(m, entry) - } - cmd.rc <- resGetMeta{m, err} -} - -// COMMAND: srvGetMetaContent ---------------------------------------- -// -// Retrieves the meta data and the content of a zettel. - -func (dp *dirBox) srvGetMetaContent(ctx context.Context, entry *notify.DirEntry, zid id.Zid) (*meta.Meta, []byte, error) { - rc := make(chan resGetMetaContent, 1) - dp.getFileChan(zid) <- &fileGetMetaContent{entry, rc} - ctx, cancel := context.WithTimeout(ctx, serviceTimeout) - defer cancel() - select { - case res := <-rc: - return res.meta, res.content, res.err - case <-ctx.Done(): - return nil, nil, ctx.Err() - } -} - -type fileGetMetaContent struct { - entry *notify.DirEntry - rc chan<- resGetMetaContent -} -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)) - if contentName != "" { - var err1 error - content, err1 = os.ReadFile(contentPath) - if err == nil { - err = err1 - } - } - } - if err == nil { - cmdCleanupMeta(m, entry) - } - cmd.rc <- resGetMetaContent{m, content, err} -} - -// COMMAND: srvSetZettel ---------------------------------------- -// -// Writes a new or exsting zettel. - -func (dp *dirBox) srvSetZettel(ctx context.Context, entry *notify.DirEntry, zettel zettel.Zettel) error { - rc := make(chan resSetZettel, 1) - dp.getFileChan(zettel.Meta.Zid) <- &fileSetZettel{entry, zettel, rc} - ctx, cancel := context.WithTimeout(ctx, serviceTimeout) - defer cancel() - select { - case err := <-rc: - return err - case <-ctx.Done(): - return ctx.Err() - } -} - -type fileSetZettel struct { - entry *notify.DirEntry - zettel 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 { - metaFile, err := openFileWrite(metaPath) - if err != nil { - return err - } - err = writeFileZid(metaFile, m.Zid) - if err == nil { - _, err = m.WriteComputed(metaFile) - } - if err1 := metaFile.Close(); err == nil { - err = err1 - } - return err -} - -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 -} - -var ( - newline = []byte{'\n'} - yamlSep = []byte{'-', '-', '-', '\n'} -) - -func writeMetaHeader(w io.Writer, m *meta.Meta) (err error) { - if m.YamlSep { - _, err = w.Write(yamlSep) - if err != nil { - return err - } - } - err = writeFileZid(w, m.Zid) - if err != nil { - return err - } - _, err = m.WriteComputed(w) - if err != nil { - return err - } - if m.YamlSep { - _, err = w.Write(yamlSep) - } else { - _, err = w.Write(newline) - } - return err -} - -// COMMAND: srvDeleteZettel ---------------------------------------- -// -// Deletes an existing zettel. - -func (dp *dirBox) srvDeleteZettel(ctx context.Context, entry *notify.DirEntry, zid id.Zid) error { - rc := make(chan resDeleteZettel, 1) - dp.getFileChan(zid) <- &fileDeleteZettel{entry, rc} - ctx, cancel := context.WithTimeout(ctx, serviceTimeout) - defer cancel() - select { - case err := <-rc: - return err - case <-ctx.Done(): - return ctx.Err() - } -} - -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 - } - } - for _, dupName := range entry.UselessFiles { - err1 := os.Remove(filepath.Join(dirPath, dupName)) - if err == nil { - err = err1 - } - } - cmd.rc <- err -} - -// Utility functions ---------------------------------------- - -func parseMetaFile(zid id.Zid, path string) (*meta.Meta, error) { - src, err := os.ReadFile(path) - if err != nil { - return nil, err - } - inp := input.NewInput(src) - return meta.NewFromInput(zid, inp), nil -} - -func parseMetaContentFile(zid id.Zid, path string) (*meta.Meta, []byte, error) { - src, err := os.ReadFile(path) - if err != nil { - return nil, nil, err - } - inp := input.NewInput(src) - meta := meta.NewFromInput(zid, inp) - return meta, src[inp.Pos:], nil -} - -func cmdCleanupMeta(m *meta.Meta, entry *notify.DirEntry) { - filebox.CleanupMeta( - 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 { - _, err = io.WriteString(w, "\n") - } - } - return err -} - -func writeFileContent(path string, content []byte) error { - f, err := openFileWrite(path) - if err == nil { - _, err = f.Write(content) - if err1 := f.Close(); err == nil { - err = err1 - } - } - return err -} DELETED box/filebox/filebox.go Index: box/filebox/filebox.go ================================================================== --- box/filebox/filebox.go +++ /dev/null @@ -1,97 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" -) - -func init() { - manager.Register("file", func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { - path := getFilepathFromURL(u) - ext := strings.ToLower(filepath.Ext(path)) - if ext != ".zip" { - return nil, errors.New("unknown extension '" + ext + "' in box URL: " + u.String()) - } - return &zipBox{ - log: kernel.Main.GetLogger(kernel.BoxService).Clone(). - Str("box", "zip").Int("boxnum", int64(cdata.Number)).Child(), - number: cdata.Number, - name: path, - enricher: cdata.Enricher, - notify: cdata.Notify, - }, nil - }) -} - -func getFilepathFromURL(u *url.URL) string { - name := u.Opaque - if name == "" { - name = u.Path - } - components := strings.Split(name, "/") - fileName := filepath.Join(components...) - if len(components) > 0 && components[0] == "" { - return "/" + fileName - } - return fileName -} - -var alternativeSyntax = map[string]string{ - "htm": "html", -} - -func calculateSyntax(ext string) string { - ext = strings.ToLower(ext) - if syntax, ok := alternativeSyntax[ext]; ok { - return syntax - } - return ext -} - -// CalcDefaultMeta returns metadata with default values for the given entry. -func CalcDefaultMeta(zid id.Zid, ext string) *meta.Meta { - m := meta.New(zid) - m.Set(api.KeySyntax, calculateSyntax(ext)) - return m -} - -// CleanupMeta enhances the given metadata. -func CleanupMeta(m *meta.Meta, zid id.Zid, ext string, inMeta bool, uselessFiles []string) { - if inMeta { - if syntax, ok := m.Get(api.KeySyntax); !ok || syntax == "" { - dm := CalcDefaultMeta(zid, ext) - syntax, ok = dm.Get(api.KeySyntax) - if !ok { - panic("Default meta must contain syntax") - } - m.Set(api.KeySyntax, syntax) - } - } - - if len(uselessFiles) > 0 { - m.Set(api.KeyUselessFiles, strings.Join(uselessFiles, " ")) - } -} DELETED box/filebox/zipbox.go Index: box/filebox/zipbox.go ================================================================== --- box/filebox/zipbox.go +++ /dev/null @@ -1,230 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" -) - -type zipBox struct { - log *logger.Logger - number int - name string - enricher box.Enricher - notify box.UpdateNotifier - dirSrv *notify.DirService -} - -func (zb *zipBox) Location() string { - if strings.HasPrefix(zb.name, "/") { - return "file://" + zb.name - } - return "file:" + zb.name -} - -func (zb *zipBox) State() box.StartState { - if ds := zb.dirSrv; ds != nil { - switch ds.State() { - case notify.DsCreated: - return box.StartStateStopped - case notify.DsStarting: - return box.StartStateStarting - case notify.DsWorking: - return box.StartStateStarted - case notify.DsMissing: - return box.StartStateStarted - case notify.DsStopping: - return box.StartStateStopping - } - } - return box.StartStateStopped -} - -func (zb *zipBox) Start(context.Context) error { - reader, err := zip.OpenReader(zb.name) - if err != nil { - return err - } - reader.Close() - zipNotifier := notify.NewSimpleZipNotifier(zb.log, zb.name) - zb.dirSrv = notify.NewDirService(zb, zb.log, zipNotifier, zb.notify) - zb.dirSrv.Start() - return nil -} - -func (zb *zipBox) Refresh(_ context.Context) { - zb.dirSrv.Refresh() - zb.log.Trace().Msg("Refresh") -} - -func (zb *zipBox) Stop(context.Context) { - zb.dirSrv.Stop() - zb.dirSrv = nil -} - -func (zb *zipBox) GetZettel(_ context.Context, zid id.Zid) (zettel.Zettel, error) { - entry := zb.dirSrv.GetDirEntry(zid) - if !entry.IsValid() { - return zettel.Zettel{}, box.ErrZettelNotFound{Zid: zid} - } - reader, err := zip.OpenReader(zb.name) - if err != nil { - return zettel.Zettel{}, err - } - defer reader.Close() - - var m *meta.Meta - var src []byte - var inMeta bool - - contentName := entry.ContentName - if metaName := entry.MetaName; metaName == "" { - if contentName == "" { - 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) - m = meta.NewFromInput(zid, inp) - src = src[inp.Pos:] - } else { - m = CalcDefaultMeta(zid, entry.ContentExt) - } - } else { - m, err = readZipMetaFile(reader, zid, metaName) - if err != nil { - return zettel.Zettel{}, err - } - inMeta = true - if contentName != "" { - src, err = readZipFileContent(reader, entry.ContentName) - if err != nil { - return zettel.Zettel{}, err - } - } - } - - CleanupMeta(m, zid, entry.ContentExt, inMeta, entry.UselessFiles) - zb.log.Trace().Zid(zid).Msg("GetZettel") - return zettel.Zettel{Meta: m, Content: zettel.NewContent(src)}, nil -} - -func (zb *zipBox) HasZettel(_ context.Context, zid id.Zid) bool { - return zb.dirSrv.GetDirEntry(zid).IsValid() -} - -func (zb *zipBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { - entries := zb.dirSrv.GetDirEntries(constraint) - zb.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyZid") - for _, entry := range entries { - handle(entry.Zid) - } - return nil -} - -func (zb *zipBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint query.RetrievePredicate) error { - reader, err := zip.OpenReader(zb.name) - if err != nil { - return err - } - defer reader.Close() - entries := zb.dirSrv.GetDirEntries(constraint) - zb.log.Trace().Int("entries", int64(len(entries))).Msg("ApplyMeta") - for _, entry := range entries { - if !constraint(entry.Zid) { - continue - } - m, err2 := zb.readZipMeta(reader, entry.Zid, entry) - if err2 != nil { - continue - } - 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} - } - zb.log.Trace().Err(err).Msg("DeleteZettel") - return err -} - -func (zb *zipBox) ReadStats(st *box.ManagedBoxStats) { - st.ReadOnly = true - st.Zettel = zb.dirSrv.NumDirEntries() - zb.log.Trace().Int("zettel", int64(st.Zettel)).Msg("ReadStats") -} - -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) - } - if err == nil { - CleanupMeta(m, zid, entry.ContentExt, inMeta, entry.UselessFiles) - } - return m, err -} - -func readZipMetaFile(reader *zip.ReadCloser, zid id.Zid, name string) (*meta.Meta, error) { - src, err := readZipFileContent(reader, name) - if err != nil { - return nil, err - } - inp := input.NewInput(src) - return meta.NewFromInput(zid, inp), nil -} - -func readZipFileContent(reader *zip.ReadCloser, name string) ([]byte, error) { - f, err := reader.Open(name) - if err != nil { - return nil, err - } - defer f.Close() - return io.ReadAll(f) -} DELETED box/helper.go Index: box/helper.go ================================================================== --- box/helper.go +++ /dev/null @@ -1,66 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 - } - // TODO: do not wait here unconditionally. - time.Sleep(100 * time.Millisecond) - withSeconds = true - } - return id.Invalid, ErrConflict -} - -// GetQueryBool is a helper function to extract bool values from a box URI. -func GetQueryBool(u *url.URL, key string) bool { - _, ok := u.Query()[key] - return ok -} - -// GetQueryInt is a helper function to extract int values of a specified range from a box URI. -func GetQueryInt(u *url.URL, key string, minVal, defVal, maxVal int) int { - sVal := u.Query().Get(key) - if sVal == "" { - return defVal - } - iVal, err := strconv.Atoi(sVal) - if err != nil { - return defVal - } - if iVal < minVal { - return minVal - } - if iVal > maxVal { - return maxVal - } - return iVal -} DELETED box/manager/anteroom.go Index: box/manager/anteroom.go ================================================================== --- box/manager/anteroom.go +++ /dev/null @@ -1,143 +0,0 @@ -//----------------------------------------------------------------------------- -// 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() { - 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++ - return - } - room := ar.makeAnteroom(zid) - ar.last.next = room - ar.last = room -} - -func (ar *anteroomQueue) makeAnteroom(zid id.Zid) *anteroom { - if zid == id.Invalid { - panic(zid) - } - waiting := id.NewSetCap(max(ar.maxLoad, 100), zid) - return &anteroom{next: nil, waiting: waiting, curLoad: 1, reload: false} -} - -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 - } -} - -func (ar *anteroomQueue) deleteReloadedRooms() { - room := ar.first - for room != nil && room.reload { - room = room.next - } - ar.first = room - if room == nil { - ar.last = nil - } -} - -func (ar *anteroomQueue) Dequeue() (arAction, id.Zid, bool) { - ar.mx.Lock() - 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 -} - -func (ar *anteroomQueue) removeFirst() { - ar.first = ar.first.next - if ar.first == nil { - ar.last = nil - } -} DELETED box/manager/anteroom_test.go Index: box/manager/anteroom_test.go ================================================================== --- box/manager/anteroom_test.go +++ /dev/null @@ -1,109 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - - "zettelstore.de/z/zettel/id" -) - -func TestSimple(t *testing.T) { - t.Parallel() - ar := newAnteroomQueue(2) - ar.EnqueueZettel(id.Zid(1)) - action, zid, lastReload := ar.Dequeue() - if zid != id.Zid(1) || action != arZettel || lastReload { - t.Errorf("Expected arZettel/1/false, but got %v/%v/%v", action, zid, lastReload) - } - _, zid, _ = ar.Dequeue() - if zid != id.Invalid { - t.Errorf("Expected invalid Zid, but got %v", zid) - } - ar.EnqueueZettel(id.Zid(1)) - ar.EnqueueZettel(id.Zid(2)) - if ar.first != ar.last { - t.Errorf("Expected one room, but got more") - } - ar.EnqueueZettel(id.Zid(3)) - if ar.first == ar.last { - t.Errorf("Expected more than one room, but got only one") - } - - count := 0 - for ; count < 1000; count++ { - action, _, _ = ar.Dequeue() - if action == arNothing { - break - } - } - if count != 3 { - t.Errorf("Expected 3 dequeues, but got %v", count) - } -} - -func TestReset(t *testing.T) { - t.Parallel() - ar := newAnteroomQueue(1) - ar.EnqueueZettel(id.Zid(1)) - ar.Reset() - action, zid, _ := ar.Dequeue() - if action != arReload || zid != id.Invalid { - t.Errorf("Expected reload & invalid Zid, but got %v/%v", action, zid) - } - ar.Reload(id.NewSet(3, 4)) - ar.EnqueueZettel(id.Zid(5)) - ar.EnqueueZettel(id.Zid(5)) - if ar.first == ar.last || ar.first.next != ar.last /*|| ar.first.next.next != ar.last*/ { - t.Errorf("Expected 2 rooms") - } - action, zid1, _ := ar.Dequeue() - if action != arZettel { - t.Errorf("Expected arZettel, but got %v", action) - } - action, zid2, _ := ar.Dequeue() - if action != arZettel { - t.Errorf("Expected arZettel, but got %v", action) - } - if !(zid1 == id.Zid(3) && zid2 == id.Zid(4) || zid1 == id.Zid(4) && zid2 == id.Zid(3)) { - t.Errorf("Zids must be 3 or 4, but got %v/%v", zid1, zid2) - } - action, zid, _ = ar.Dequeue() - if zid != id.Zid(5) || action != arZettel { - t.Errorf("Expected 5/arZettel, but got %v/%v", zid, action) - } - action, zid, _ = ar.Dequeue() - if action != arNothing || zid != id.Invalid { - t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) - } - - ar = newAnteroomQueue(1) - ar.Reload(id.NewSet(id.Zid(6))) - action, zid, _ = ar.Dequeue() - if zid != id.Zid(6) || action != arZettel { - t.Errorf("Expected 6/arZettel, but got %v/%v", zid, action) - } - action, zid, _ = ar.Dequeue() - if action != arNothing || zid != id.Invalid { - t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) - } - - ar = newAnteroomQueue(1) - ar.EnqueueZettel(id.Zid(8)) - ar.Reload(nil) - action, zid, _ = ar.Dequeue() - if action != arNothing || zid != id.Invalid { - t.Errorf("Expected nothing & invalid Zid, but got %v/%v", action, zid) - } -} DELETED box/manager/box.go Index: box/manager/box.go ================================================================== --- box/manager/box.go +++ /dev/null @@ -1,315 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - "strings" - - "zettelstore.de/z/box" - "zettelstore.de/z/query" - "zettelstore.de/z/zettel" - "zettelstore.de/z/zettel/id" - "zettelstore.de/z/zettel/meta" -) - -// Conatains all box.Box related functions - -// Location returns some information where the box is located. -func (mgr *Manager) Location() string { - if len(mgr.boxes) <= 2 { - 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) - } - return zidO, err - } - return id.Invalid, box.ErrReadOnly -} - -// 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 -} - -// FetchZidsO returns the set of all old-style zettel identifer managed by the box. -func (mgr *Manager) FetchZidsO(ctx context.Context) (*id.Set, error) { - mgr.mgrLog.Debug().Msg("FetchZidsO") - return mgr.fetchZids(ctx) -} - -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 -} - -// GetMeta returns just the metadata of the zettel with the given identifier. -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 { - // TODO: Call GetZettel and return just metadata, in case the index is not complete. - 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 - } - if compSearch.PreMatch(m) && term.Match(m) { - selected[zid] = m - mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/match") - } else { - rejected.Add(zid) - mgr.mgrLog.Trace().Zid(zid).Msg("SelectMeta/reject") - } - } - for _, p := range mgr.boxes { - if err2 := p.ApplyMeta(ctx, handleMeta, term.Retrieve); err2 != nil { - return nil, err2 - } - } - } - result := make([]*meta.Meta, 0, len(selected)) - for _, m := range selected { - result = append(result, m) - } - 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 - } - return mgr.updateZettel(ctx, zettel) -} -func (mgr *Manager) updateZettel(ctx context.Context, zettel zettel.Zettel) error { - 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) - return err - } - var errZNF box.ErrZettelNotFound - if !errors.As(err, &errZNF) && !errors.Is(err, box.ErrReadOnly) { - return err - } - } - return box.ErrZettelNotFound{Zid: zidO} -} - -// 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 -} DELETED box/manager/collect.go Index: box/manager/collect.go ================================================================== --- box/manager/collect.go +++ /dev/null @@ -1,84 +0,0 @@ -//----------------------------------------------------------------------------- -// 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() - data.urls = store.NewWordSet() -} - -func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) { - ast.Walk(data, &zn.Ast) -} - -func collectInlineIndexData(is *ast.InlineSlice, data *collectData) { - ast.Walk(data, is) -} - -func (data *collectData) Visit(node ast.Node) ast.Visitor { - switch n := node.(type) { - case *ast.VerbatimNode: - data.addText(string(n.Content)) - case *ast.TranscludeNode: - data.addRef(n.Ref) - case *ast.TextNode: - data.addText(n.Text) - case *ast.LinkNode: - data.addRef(n.Ref) - case *ast.EmbedRefNode: - data.addRef(n.Ref) - case *ast.CiteNode: - data.addText(n.Key) - case *ast.LiteralNode: - data.addText(string(n.Content)) - } - return data -} - -func (data *collectData) addText(s string) { - for _, word := range strfun.NormalizeWords(s) { - data.words.Add(word) - } -} - -func (data *collectData) addRef(ref *ast.Reference) { - if ref == nil { - return - } - if ref.IsExternal() { - data.urls.Add(strings.ToLower(ref.Value)) - } - if !ref.IsZettel() { - return - } - if zid, err := id.Parse(ref.URL.Path); err == nil { - data.refs.Add(zid) - } -} DELETED box/manager/enrich.go Index: box/manager/enrich.go ================================================================== --- box/manager/enrich.go +++ /dev/null @@ -1,136 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 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 - } - zid /= 100 - minutes := zid % 100 - if minutes > 59 { - minutes = 59 - } - zid /= 100 - hours := zid % 100 - if hours > 23 { - hours = 23 - } - zid /= 100 - day := zid % 100 - zid /= 100 - month := zid % 100 - year := zid / 100 - month, day = sanitizeMonthDay(year, month, day) - created := ((((year*100+month)*100+day)*100+hours)*100+minutes)*100 + seconds - return created.String() -} - -func sanitizeMonthDay(year, month, day id.Zid) (id.Zid, id.Zid) { - if day < 1 { - day = 1 - } - if month < 1 { - month = 1 - } - if month > 12 { - month = 12 - } - - switch month { - case 1, 3, 5, 7, 8, 10, 12: - if day > 31 { - day = 31 - } - case 4, 6, 9, 11: - if day > 30 { - day = 30 - } - case 2: - if year%4 != 0 || (year%100 == 0 && year%400 != 0) { - if day > 28 { - day = 28 - } - } else { - if day > 29 { - day = 29 - } - } - } - return month, day -} - -func computePublished(m *meta.Meta) { - if _, ok := m.Get(api.KeyPublished); ok { - return - } - if modified, ok := m.Get(api.KeyModified); ok { - if _, ok = meta.TimeValue(modified); ok { - m.Set(api.KeyPublished, modified) - return - } - } - if created, ok := m.Get(api.KeyCreated); ok { - if _, ok = meta.TimeValue(created); ok { - m.Set(api.KeyPublished, created) - return - } - } - zid := m.Zid.String() - if _, ok := meta.TimeValue(zid); ok { - m.Set(api.KeyPublished, zid) - return - } - - // Neither the zettel was modified nor the zettel identifer contains a valid - // timestamp. In this case do not set the "published" property. -} DELETED box/manager/indexer.go Index: box/manager/indexer.go ================================================================== --- box/manager/indexer.go +++ /dev/null @@ -1,255 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - "net/url" - "time" - - "zettelstore.de/z/box" - "zettelstore.de/z/box/manager/store" - "zettelstore.de/z/kernel" - "zettelstore.de/z/parser" - "zettelstore.de/z/strfun" - "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. -// This is the main service of the idxIndexer. -func (mgr *Manager) idxIndexer() { - // Something may panic. Ensure a running indexer. - defer func() { - if ri := recover(); ri != nil { - kernel.Main.LogRecover("Indexer", ri) - go mgr.idxIndexer() - } - }() - - timerDuration := 15 * time.Second - timer := time.NewTimer(timerDuration) - ctx := box.NoEnrichContext(context.Background()) - for { - mgr.idxWorkService(ctx) - if !mgr.idxSleepService(timer, timerDuration) { - return - } - } -} - -func (mgr *Manager) idxWorkService(ctx context.Context) { - var start time.Time - for { - switch action, zid, lastReload := mgr.idxAr.Dequeue(); action { - case arNothing: - return - case arReload: - mgr.idxLog.Debug().Msg("reload") - zids, err := mgr.FetchZids(ctx) - if err == nil { - start = time.Now() - mgr.idxAr.Reload(zids) - mgr.idxMx.Lock() - mgr.idxLastReload = time.Now().Local() - mgr.idxSinceReload = 0 - mgr.idxMx.Unlock() - } - case arZettel: - mgr.idxLog.Debug().Zid(zid).Msg("zettel") - zettel, err := mgr.GetZettel(ctx, zid) - if err != nil { - // Zettel was deleted or is not accessible b/c of other reasons - mgr.idxLog.Trace().Zid(zid).Msg("delete") - mgr.idxDeleteZettel(ctx, zid) - continue - } - mgr.idxLog.Trace().Zid(zid).Msg("update") - mgr.idxUpdateZettel(ctx, zettel) - mgr.idxMx.Lock() - if lastReload { - mgr.idxDurReload = time.Since(start) - } - mgr.idxSinceReload++ - mgr.idxMx.Unlock() - } - } -} - -func (mgr *Manager) idxSleepService(timer *time.Timer, timerDuration time.Duration) bool { - select { - case _, ok := <-mgr.idxReady: - 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.Zid(999999900) -} - -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 - } - switch descr.Type { - case meta.TypeID: - mgr.idxUpdateValue(ctx, descr.Inverse, pair.Value, zi) - case meta.TypeIDSet: - for _, val := range meta.ListFromValue(pair.Value) { - mgr.idxUpdateValue(ctx, descr.Inverse, val, zi) - } - case meta.TypeZettelmarkup: - is := parser.ParseMetadata(pair.Value) - collectInlineIndexData(&is, cData) - case meta.TypeURL: - if _, err := url.Parse(pair.Value); err == nil { - cData.urls.Add(pair.Value) - } - default: - if descr.Type.IsSet { - for _, val := range meta.ListFromValue(pair.Value) { - idxCollectMetaValue(cData.words, val) - } - } else { - idxCollectMetaValue(cData.words, pair.Value) - } - } - } -} - -func idxCollectMetaValue(stWords store.WordSet, value string) { - if words := strfun.NormalizeWords(value); len(words) > 0 { - for _, word := range words { - stWords.Add(word) - } - } 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) - }) -} DELETED box/manager/manager.go Index: box/manager/manager.go ================================================================== --- box/manager/manager.go +++ /dev/null @@ -1,440 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 ( - "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/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 box.UpdateNotifier -} - -// 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: - // 1. fragment is set - if q := u.Query(); len(q) == 0 { - rawURL += "?readonly" - } else if _, ok := q["readonly"]; !ok { - rawURL += "&readonly" - } - var err error - if u, err = url.Parse(rawURL); err != nil { - return nil, err - } - } - - if create, ok := registry[u.Scheme]; ok { - return create(u, cdata) - } - return nil, &ErrInvalidScheme{u.Scheme} -} - -// ErrInvalidScheme is returned if there is no box with the given scheme. -type ErrInvalidScheme struct{ Scheme string } - -func (err *ErrInvalidScheme) Error() string { return "Invalid scheme: " + err.Scheme } - -type createFunc func(*url.URL, *ConnectData) (box.ManagedBox, error) - -var registry = map[string]createFunc{} - -// Register the encoder for later retrieval. -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 - - // Indexer data - idxLog *logger.Logger - idxStore store.Store - idxAr *anteroomQueue - idxReady chan struct{} // Signal a non-empty anteroom to background task - - // Indexer stats data - idxMx sync.RWMutex - idxLastReload time.Time - idxDurReload time.Duration - idxSinceReload uint64 -} - -func (mgr *Manager) setState(newState box.StartState) { - mgr.stateMx.Lock() - mgr.state = newState - mgr.stateMx.Unlock() -} - -// State returns the box.StartState of the manager. -func (mgr *Manager) State() box.StartState { - mgr.stateMx.RLock() - state := mgr.state - mgr.stateMx.RUnlock() - return state -} - -// New creates a new managing box. -func New(boxURIs []*url.URL, authManager auth.BaseManager, rtConfig config.Config) (*Manager, error) { - descrs := meta.GetSortedKeyDescriptions() - propertyKeys := make(strfun.Set, len(descrs)) - for _, kd := range descrs { - if kd.IsProperty() { - propertyKeys.Set(kd.Name) - } - } - boxLog := kernel.Main.GetLogger(kernel.BoxService) - 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), - } - - cdata := ConnectData{Number: 1, Config: rtConfig, Enricher: mgr, Notify: mgr.notifyChanged} - 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 { - boxes = append(boxes, p) - cdata.Number++ - } - } - constbox, err := registry[" const"](nil, &cdata) - if err != nil { - return nil, err - } - cdata.Number++ - compbox, err := registry[" comp"](nil, &cdata) - if err != nil { - 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) - mgr.mxObserver.Unlock() - } -} - -func (mgr *Manager) notifier() { - // The call to notify may panic. Ensure a running notifier. - defer func() { - if ri := recover(); ri != nil { - kernel.Main.LogRecover("Notifier", ri) - go mgr.notifier() - } - }() - - tsLastEvent := time.Now() - cache := destutterCache{} - for { - select { - case ci, ok := <-mgr.infos: - if ok { - now := time.Now() - if len(cache) > 1 && tsLastEvent.Add(10*time.Second).Before(now) { - // Cache contains entries and is definitely outdated - mgr.mgrLog.Trace().Msg("clean destutter cache") - cache = destutterCache{} - } - tsLastEvent = now - - 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) - if ci.Box == nil { - ci.Box = mgr - } - if isStarted { - mgr.notifyObserver(&ci) - } - } - case <-mgr.done: - return - } - } -} - -type destutterData struct { - deadAt time.Time - reason box.UpdateReason -} -type destutterCache = map[id.Zid]destutterData - -func ignoreUpdate(cache destutterCache, now time.Time, reason box.UpdateReason, zid id.Zid) bool { - if dsd, found := cache[zid]; found { - if dsd.reason == reason && dsd.deadAt.After(now) { - return true - } - } - cache[zid] = destutterData{ - deadAt: now.Add(500 * time.Millisecond), - reason: reason, - } - return false -} - -func (mgr *Manager) idxEnqueue(reason box.UpdateReason, zidO id.Zid) { - switch reason { - case box.OnReady: - return - case box.OnReload: - mgr.idxAr.Reset() - case box.OnZettel: - mgr.idxAr.EnqueueZettel(zidO) - case box.OnDelete: - 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: - } -} - -func (mgr *Manager) notifyObserver(ci *box.UpdateInfo) { - mgr.mxObserver.RLock() - observers := mgr.observers - mgr.mxObserver.RUnlock() - for _, ob := range observers { - ob(*ci) - } -} - -// Start the box. Now all other functions of the box are allowed. -// Starting an already started box is not allowed. -func (mgr *Manager) Start(ctx context.Context) error { - mgr.mgrMx.Lock() - defer mgr.mgrMx.Unlock() - if mgr.State() != box.StartStateStopped { - return box.ErrStarted - } - mgr.setState(box.StartStateStarting) - for i := len(mgr.boxes) - 1; i >= 0; i-- { - ssi, ok := mgr.boxes[i].(box.StartStopper) - if !ok { - continue - } - err := ssi.Start(ctx) - if err == nil { - continue - } - mgr.setState(box.StartStateStopping) - for j := i + 1; j < len(mgr.boxes); j++ { - if ssj, ok2 := mgr.boxes[j].(box.StartStopper); ok2 { - ssj.Stop(ctx) - } - } - mgr.setState(box.StartStateStopped) - 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 { - 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 -} - -// 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() - defer mgr.mgrMx.RUnlock() - subStats := make([]box.ManagedBoxStats, len(mgr.boxes)) - for i, p := range mgr.boxes { - p.ReadStats(&subStats[i]) - } - - st.ReadOnly = true - sumZettel := 0 - for _, sst := range subStats { - if !sst.ReadOnly { - st.ReadOnly = false - } - sumZettel += sst.Zettel - } - st.NumManagedBoxes = len(mgr.boxes) - st.ZettelTotal = sumZettel - - var storeSt store.Stats - mgr.idxMx.RLock() - defer mgr.idxMx.RUnlock() - mgr.idxStore.ReadStats(&storeSt) - - st.LastReload = mgr.idxLastReload - st.IndexesSinceReload = mgr.idxSinceReload - st.DurLastReload = mgr.idxDurReload - st.ZettelIndexed = storeSt.Zettel - st.IndexUpdates = storeSt.Updates - st.IndexedWords = storeSt.Words - 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() -} - -func (mgr *Manager) notifyChanged(bbox box.BaseBox, zid id.Zid, reason box.UpdateReason) { - if infos := mgr.infos; infos != nil { - mgr.infos <- box.UpdateInfo{Box: bbox, Reason: reason, Zid: zid} - } -} DELETED box/manager/mapstore/mapstore.go Index: box/manager/mapstore/mapstore.go ================================================================== --- box/manager/mapstore/mapstore.go +++ /dev/null @@ -1,673 +0,0 @@ -//----------------------------------------------------------------------------- -// 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/store/store.go Index: box/manager/store/store.go ================================================================== --- box/manager/store/store.go +++ /dev/null @@ -1,68 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - "io" - - "zettelstore.de/z/query" - "zettelstore.de/z/zettel/id" - "zettelstore.de/z/zettel/meta" -) - -// Stats records statistics about the store. -type Stats struct { - // Zettel is the number of zettel managed by the indexer. - Zettel int - - // Updates count the number of metadata updates. - Updates uint64 - - // Words count the different words stored in the store. - Words uint64 - - // Urls count the different URLs stored in the store. - Urls uint64 -} - -// Store all relevant zettel data. There may be multiple implementations, i.e. -// memory-based, file-based, based on SQLite, ... -type Store interface { - query.Searcher - - // GetMeta returns the metadata of the zettel with the given identifier. - 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) -} DELETED box/manager/store/wordset.go Index: box/manager/store/wordset.go ================================================================== --- box/manager/store/wordset.go +++ /dev/null @@ -1,63 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 - -// NewWordSet returns a new WordSet. -func NewWordSet() WordSet { return make(WordSet) } - -// Add one word to the set -func (ws WordSet) Add(s string) { - ws[s] = ws[s] + 1 -} - -// Words gives the slice of all words in the set. -func (ws WordSet) Words() []string { - if len(ws) == 0 { - return nil - } - words := make([]string, 0, len(ws)) - for w := range ws { - words = append(words, w) - } - return words -} - -// Diff calculates the word slice to be added and to be removed from oldWords -// to get the given word set. -func (ws WordSet) Diff(oldWords []string) (newWords, removeWords []string) { - if len(ws) == 0 { - return nil, oldWords - } - if len(oldWords) == 0 { - return ws.Words(), nil - } - oldSet := make(WordSet, len(oldWords)) - for _, ow := range oldWords { - if _, ok := ws[ow]; ok { - oldSet[ow] = 1 - continue - } - removeWords = append(removeWords, ow) - } - for w := range ws { - if _, ok := oldSet[w]; ok { - continue - } - newWords = append(newWords, w) - } - return newWords, removeWords -} DELETED box/manager/store/wordset_test.go Index: box/manager/store/wordset_test.go ================================================================== --- box/manager/store/wordset_test.go +++ /dev/null @@ -1,80 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 -} - -func TestWordsWords(t *testing.T) { - t.Parallel() - testcases := []struct { - words store.WordSet - exp []string - }{ - {nil, nil}, - {store.WordSet{}, nil}, - {store.WordSet{"a": 1, "b": 2}, []string{"a", "b"}}, - } - for i, tc := range testcases { - got := tc.words.Words() - if !equalWordList(tc.exp, got) { - t.Errorf("%d: %v.Words() == %v, but got %v", i, tc.words, tc.exp, got) - } - } -} - -func TestWordsDiff(t *testing.T) { - t.Parallel() - testcases := []struct { - cur store.WordSet - old []string - expN, expR []string - }{ - {nil, nil, nil, nil}, - {store.WordSet{}, []string{}, nil, nil}, - {store.WordSet{"a": 1}, []string{}, []string{"a"}, nil}, - {store.WordSet{"a": 1}, []string{"b"}, []string{"a"}, []string{"b"}}, - {store.WordSet{}, []string{"b"}, nil, []string{"b"}}, - {store.WordSet{"a": 1}, []string{"a"}, nil, nil}, - } - for i, tc := range testcases { - gotN, gotR := tc.cur.Diff(tc.old) - if !equalWordList(tc.expN, gotN) { - t.Errorf("%d: %v.Diff(%v)->new %v, but got %v", i, tc.cur, tc.old, tc.expN, gotN) - } - if !equalWordList(tc.expR, gotR) { - t.Errorf("%d: %v.Diff(%v)->rem %v, but got %v", i, tc.cur, tc.old, tc.expR, gotR) - } - } -} DELETED box/manager/store/zettel.go Index: box/manager/store/zettel.go ================================================================== --- box/manager/store/zettel.go +++ /dev/null @@ -1,93 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 - } - zi.inverseRefs[key] = id.NewSet(zid) -} - -// AddDeadRef adds a dead reference to a zettel. -func (zi *ZettelIndex) AddDeadRef(zid id.Zid) { - zi.deadrefs.Add(zid) -} - -// SetWords sets the words to the given value. -func (zi *ZettelIndex) SetWords(words WordSet) { zi.words = words } - -// SetUrls sets the words to the given value. -func (zi *ZettelIndex) SetUrls(urls WordSet) { zi.urls = urls } - -// GetDeadRefs returns all dead references as a sorted list. -func (zi *ZettelIndex) GetDeadRefs() *id.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 } DELETED box/membox/membox.go Index: box/membox/membox.go ================================================================== --- box/membox/membox.go +++ /dev/null @@ -1,237 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - "net/url" - "sync" - - "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" -) - -func init() { - manager.Register( - "mem", - func(u *url.URL, cdata *manager.ConnectData) (box.ManagedBox, error) { - return &memBox{ - log: kernel.Main.GetLogger(kernel.BoxService).Clone(). - Str("box", "mem").Int("boxnum", int64(cdata.Number)).Child(), - u: u, - cdata: *cdata, - maxZettel: box.GetQueryInt(u, "max-zettel", 0, 127, 65535), - maxBytes: box.GetQueryInt(u, "max-bytes", 0, 65535, (1024*1024*1024)-1), - }, nil - }) -} - -type memBox struct { - log *logger.Logger - u *url.URL - cdata manager.ConnectData - maxZettel int - maxBytes int - mx sync.RWMutex // Protects the following fields - zettel map[id.Zid]zettel.Zettel - curBytes int -} - -func (mb *memBox) notifyChanged(zid id.Zid, reason box.UpdateReason) { - if notify := mb.cdata.Notify; notify != nil { - notify(mb, zid, reason) - } -} - -func (mb *memBox) Location() string { - return mb.u.String() -} - -func (mb *memBox) State() box.StartState { - mb.mx.RLock() - defer mb.mx.RUnlock() - if mb.zettel == nil { - return box.StartStateStopped - } - return box.StartStateStarted -} - -func (mb *memBox) Start(context.Context) error { - mb.mx.Lock() - mb.zettel = make(map[id.Zid]zettel.Zettel) - mb.curBytes = 0 - mb.mx.Unlock() - mb.log.Trace().Int("max-zettel", int64(mb.maxZettel)).Int("max-bytes", int64(mb.maxBytes)).Msg("Start Box") - return nil -} - -func (mb *memBox) Stop(context.Context) { - mb.mx.Lock() - mb.zettel = nil - mb.mx.Unlock() -} - -func (mb *memBox) CanCreateZettel(context.Context) bool { - mb.mx.RLock() - defer mb.mx.RUnlock() - return len(mb.zettel) < mb.maxZettel -} - -func (mb *memBox) CreateZettel(_ context.Context, zettel zettel.Zettel) (id.Zid, error) { - mb.mx.Lock() - newBytes := mb.curBytes + zettel.Length() - if mb.maxZettel < len(mb.zettel) || mb.maxBytes < newBytes { - mb.mx.Unlock() - return id.Invalid, box.ErrCapacity - } - zid, err := box.GetNewZid(func(zid id.Zid) (bool, error) { - _, ok := mb.zettel[zid] - return !ok, nil - }) - if err != nil { - mb.mx.Unlock() - return id.Invalid, err - } - 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] - mb.mx.RUnlock() - if !ok { - return zettel.Zettel{}, box.ErrZettelNotFound{Zid: zid} - } - z.Meta = z.Meta.Clone() - mb.log.Trace().Msg("GetZettel") - return z, nil -} - -func (mb *memBox) HasZettel(_ context.Context, zid id.Zid) bool { - mb.mx.RLock() - _, found := mb.zettel[zid] - mb.mx.RUnlock() - return found -} - -func (mb *memBox) ApplyZid(_ context.Context, handle box.ZidFunc, constraint query.RetrievePredicate) error { - mb.mx.RLock() - defer mb.mx.RUnlock() - mb.log.Trace().Int("entries", int64(len(mb.zettel))).Msg("ApplyZid") - for zid := range mb.zettel { - if constraint(zid) { - handle(zid) - } - } - return nil -} - -func (mb *memBox) ApplyMeta(ctx context.Context, handle box.MetaFunc, constraint query.RetrievePredicate) error { - mb.mx.RLock() - defer mb.mx.RUnlock() - mb.log.Trace().Int("entries", int64(len(mb.zettel))).Msg("ApplyMeta") - for zid, zettel := range mb.zettel { - if constraint(zid) { - m := zettel.Meta.Clone() - mb.cdata.Enricher.Enrich(ctx, m, mb.cdata.Number) - handle(m) - } - } - return nil -} - -func (mb *memBox) CanUpdateZettel(_ context.Context, zettel zettel.Zettel) bool { - mb.mx.RLock() - defer mb.mx.RUnlock() - zid := zettel.Meta.Zid - if !zid.IsValid() { - return false - } - - newBytes := mb.curBytes + zettel.Length() - if prevZettel, found := mb.zettel[zid]; found { - newBytes -= prevZettel.Length() - } - return newBytes < mb.maxBytes -} - -func (mb *memBox) UpdateZettel(_ context.Context, zettel zettel.Zettel) error { - m := zettel.Meta.Clone() - if !m.Zid.IsValid() { - return box.ErrInvalidZid{Zid: m.Zid.String()} - } - - mb.mx.Lock() - newBytes := mb.curBytes + zettel.Length() - if prevZettel, found := mb.zettel[m.Zid]; found { - newBytes -= prevZettel.Length() - } - if mb.maxBytes < newBytes { - mb.mx.Unlock() - 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") -} DELETED box/notify/directory.go Index: box/notify/directory.go ================================================================== --- box/notify/directory.go +++ /dev/null @@ -1,583 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - "zettelstore.de/z/strfun" - "zettelstore.de/z/zettel/id" -) - -type entrySet map[id.Zid]*DirEntry - -// DirServiceState signal the internal state of the service. -// -// The following state transitions are possible: -// --newDirService--> dsCreated -// dsCreated --Start--> dsStarting -// dsStarting --last list notification--> dsWorking -// dsWorking --directory missing--> dsMissing -// dsMissing --last list notification--> dsWorking -// --Stop--> dsStopping -type DirServiceState uint8 - -// Constants for DirServiceState -const ( - DsCreated DirServiceState = iota - DsStarting // Reading inital scan - DsWorking // Initial scan complete, fully operational - DsMissing // Directory is missing - DsStopping // Service is shut down -) - -// DirService specifies a directory service for file based zettel. -type DirService struct { - box box.ManagedBox - log *logger.Logger - dirPath string - notifier Notifier - infos box.UpdateNotifier - mx sync.RWMutex // protects status, entries - state DirServiceState - entries entrySet -} - -// ErrNoDirectory signals missing directory data. -var ErrNoDirectory = errors.New("unable to retrieve zettel directory information") - -// NewDirService creates a new directory service. -func NewDirService(box box.ManagedBox, log *logger.Logger, notifier Notifier, notify box.UpdateNotifier) *DirService { - return &DirService{ - box: box, - log: log, - notifier: notifier, - infos: notify, - state: DsCreated, - } -} - -// State the current service state. -func (ds *DirService) State() DirServiceState { - ds.mx.RLock() - state := ds.state - ds.mx.RUnlock() - return state -} - -// Start the directory service. -func (ds *DirService) Start() { - ds.mx.Lock() - ds.state = DsStarting - ds.mx.Unlock() - var newEntries entrySet - go ds.updateEvents(newEntries) -} - -// Refresh the directory entries. -func (ds *DirService) Refresh() { - ds.notifier.Refresh() -} - -// Stop the directory service. -func (ds *DirService) Stop() { - ds.mx.Lock() - ds.state = DsStopping - ds.mx.Unlock() - ds.notifier.Close() -} - -func (ds *DirService) logMissingEntry(action string) error { - err := ErrNoDirectory - ds.log.Info().Err(err).Str("action", action).Msg("Unable to get directory information") - return err -} - -// NumDirEntries returns the number of entries in the directory. -func (ds *DirService) NumDirEntries() int { - ds.mx.RLock() - defer ds.mx.RUnlock() - if ds.entries == nil { - return 0 - } - return len(ds.entries) -} - -// GetDirEntries returns a list of directory entries, which satisfy the given constraint. -func (ds *DirService) GetDirEntries(constraint query.RetrievePredicate) []*DirEntry { - ds.mx.RLock() - defer ds.mx.RUnlock() - if ds.entries == nil { - return nil - } - result := make([]*DirEntry, 0, len(ds.entries)) - for zid, entry := range ds.entries { - if constraint(zid) { - copiedEntry := *entry - result = append(result, &copiedEntry) - } - } - return result -} - -// GetDirEntry returns a directory entry with the given zid, or nil if not found. -func (ds *DirService) GetDirEntry(zid id.Zid) *DirEntry { - ds.mx.RLock() - defer ds.mx.RUnlock() - if ds.entries == nil { - return nil - } - foundEntry := ds.entries[zid] - if foundEntry == nil { - return nil - } - result := *foundEntry - return &result -} - -// SetNewDirEntry calculates an empty directory entry with an unused identifier and -// stores it in the directory. -func (ds *DirService) SetNewDirEntry() (id.Zid, error) { - ds.mx.Lock() - defer ds.mx.Unlock() - if ds.entries == nil { - return id.Invalid, ds.logMissingEntry("new") - } - zid, err := box.GetNewZid(func(zid id.Zid) (bool, error) { - _, found := ds.entries[zid] - return !found, nil - }) - if err != nil { - return id.Invalid, err - } - ds.entries[zid] = &DirEntry{Zid: zid} - return zid, nil -} - -// UpdateDirEntry updates an directory entry in place. -func (ds *DirService) UpdateDirEntry(updatedEntry *DirEntry) error { - entry := *updatedEntry - ds.mx.Lock() - defer ds.mx.Unlock() - 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") - } - delete(ds.entries, zid) - return nil -} - -func (ds *DirService) updateEvents(newEntries entrySet) { - // Something may panic. Ensure a running service. - defer func() { - if ri := recover(); ri != nil { - kernel.Main.LogRecover("DirectoryService", ri) - go ds.updateEvents(newEntries) - } - }() - - for ev := range ds.notifier.Events() { - e, ok := ds.handleEvent(ev, newEntries) - if !ok { - break - } - newEntries = e - } -} -func (ds *DirService) handleEvent(ev Event, newEntries entrySet) (entrySet, bool) { - ds.mx.RLock() - state := ds.state - ds.mx.RUnlock() - - if msg := ds.log.Trace(); msg.Enabled() { - msg.Uint("state", uint64(state)).Str("op", ev.Op.String()).Str("name", ev.Name).Msg("notifyEvent") - } - if state == DsStopping { - 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() - fromMissing := ds.state == DsMissing - prevEntries := ds.entries - ds.entries = newEntries - ds.state = DsWorking - ds.mx.Unlock() - ds.onCreateDirectory(zids, prevEntries) - if fromMissing { - ds.log.Info().Str("path", ds.dirPath).Msg("Zettel directory found") - } - return nil, true - } - if newEntries != nil { - ds.onUpdateFileEvent(newEntries, ev.Name) - } - case Destroy: - ds.onDestroyDirectory() - 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) -} - -func seekZid(name string) id.Zid { - match := matchValidFileName(name) - if len(match) == 0 { - return id.Invalid - } - zid, err := id.Parse(match[1]) - if err != nil { - return id.Invalid - } - return zid -} - -func fetchdirEntry(entries entrySet, zid id.Zid) *DirEntry { - if entry, found := entries[zid]; found { - return entry - } - entry := &DirEntry{Zid: zid} - entries[zid] = entry - return entry -} - -func (ds *DirService) onUpdateFileEvent(entries entrySet, name string) id.Zid { - if entries == nil { - return id.Invalid - } - 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 { - if entries == nil { - return id.Invalid - } - zid := seekZid(name) - if zid == id.Invalid { - return id.Invalid - } - entry, found := entries[zid] - if !found { - return zid - } - for i, dupName := range entry.UselessFiles { - if dupName == name { - removeDuplicate(entry, i) - return zid - } - } - if name == entry.ContentName { - entry.ContentName = "" - entry.ContentExt = "" - ds.replayUpdateUselessFiles(entry) - } else if name == entry.MetaName { - entry.MetaName = "" - ds.replayUpdateUselessFiles(entry) - } - if entry.ContentName == "" && entry.MetaName == "" { - delete(entries, zid) - } - return zid -} - -func removeDuplicate(entry *DirEntry, i int) { - if len(entry.UselessFiles) == 1 { - entry.UselessFiles = nil - return - } - entry.UselessFiles = entry.UselessFiles[:i+copy(entry.UselessFiles[i:], entry.UselessFiles[i+1:])] -} - -func (ds *DirService) replayUpdateUselessFiles(entry *DirEntry) { - uselessFiles := entry.UselessFiles - if len(uselessFiles) == 0 { - return - } - entry.UselessFiles = make([]string, 0, len(uselessFiles)) - for _, name := range uselessFiles { - ds.updateEntry(entry, name) - } - if len(uselessFiles) == len(entry.UselessFiles) { - return - } -loop: - for _, prevName := range uselessFiles { - for _, newName := range entry.UselessFiles { - if prevName == newName { - continue loop - } - } - ds.log.Info().Str("name", prevName).Msg("Previous duplicate file becomes useful") - } -} - -func (ds *DirService) updateEntry(entry *DirEntry, name string) (string, string) { - ext := onlyExt(name) - if !extIsMetaAndContent(entry.ContentExt) { - if ext == "" { - return updateEntryMeta(entry, name), "" - } - if entry.MetaName == "" { - if nameWithoutExt(name, ext) == entry.ContentName { - // We have marked a file as content file, but it is a metadata file, - // because it is the same as the new file without extension. - entry.MetaName = entry.ContentName - entry.ContentName = "" - entry.ContentExt = "" - ds.replayUpdateUselessFiles(entry) - } else if entry.ContentName != "" && nameWithoutExt(entry.ContentName, entry.ContentExt) == name { - // We have already a valid content file, and new file should serve as metadata file, - // because it is the same as the content file without extension. - entry.MetaName = name - return "", "" - } - } - } - return updateEntryContent(entry, name, ext) -} - -func nameWithoutExt(name, ext string) string { - return name[0 : len(name)-len(ext)-1] -} - -func updateEntryMeta(entry *DirEntry, name string) string { - metaName := entry.MetaName - if metaName == "" { - entry.MetaName = name - return "" - } - if metaName == name { - return "" - } - if newNameIsBetter(metaName, name) { - entry.MetaName = name - return addUselessFile(entry, metaName) - } - return addUselessFile(entry, name) -} - -func updateEntryContent(entry *DirEntry, name, ext string) (string, string) { - contentName := entry.ContentName - if contentName == "" { - entry.ContentName = name - entry.ContentExt = ext - return "", "" - } - if contentName == name { - return "", "" - } - contentExt := entry.ContentExt - if contentExt == ext { - if newNameIsBetter(contentName, name) { - entry.ContentName = name - return addUselessFile(entry, contentName), "" - } - return addUselessFile(entry, name), "" - } - if contentExt == extZettel { - return addUselessFile(entry, name), "" - } - if ext == extZettel { - entry.ContentName = name - entry.ContentExt = ext - contentName = addUselessFile(entry, contentName) - if metaName := entry.MetaName; metaName != "" { - metaName = addUselessFile(entry, metaName) - entry.MetaName = "" - return contentName, metaName - } - return contentName, "" - } - if newExtIsBetter(contentExt, ext) { - entry.ContentName = name - entry.ContentExt = ext - return addUselessFile(entry, contentName), "" - } - return addUselessFile(entry, name), "" -} -func addUselessFile(entry *DirEntry, name string) string { - for _, dupName := range entry.UselessFiles { - if name == dupName { - return "" - } - } - entry.UselessFiles = append(entry.UselessFiles, name) - return name -} - -func onlyExt(name string) string { - ext := filepath.Ext(name) - if ext == "" || ext[0] != '.' { - return ext - } - return ext[1:] -} - -func newNameIsBetter(oldName, newName string) bool { - if len(oldName) < len(newName) { - return false - } - return oldName > newName -} - -var supportedSyntax, primarySyntax strfun.Set - -func init() { - syntaxList := parser.GetSyntaxes() - supportedSyntax = strfun.NewSet(syntaxList...) - primarySyntax = make(map[string]struct{}, len(syntaxList)) - for _, syntax := range syntaxList { - if parser.Get(syntax).Name == syntax { - primarySyntax.Set(syntax) - } - } -} -func newExtIsBetter(oldExt, newExt string) bool { - oldSyntax := supportedSyntax.Has(oldExt) - if oldSyntax != supportedSyntax.Has(newExt) { - return !oldSyntax - } - if oldSyntax { - if oldExt == "zmk" { - return false - } - if newExt == "zmk" { - return true - } - oldInfo := parser.Get(oldExt) - newInfo := parser.Get(newExt) - if oldASTParser := oldInfo.IsASTParser; oldASTParser != newInfo.IsASTParser { - return !oldASTParser - } - if oldTextFormat := oldInfo.IsTextFormat; oldTextFormat != newInfo.IsTextFormat { - return !oldTextFormat - } - if oldImageFormat := oldInfo.IsImageFormat; oldImageFormat != newInfo.IsImageFormat { - return oldImageFormat - } - if oldPrimary := primarySyntax.Has(oldExt); oldPrimary != primarySyntax.Has(newExt) { - return !oldPrimary - } - } - - oldLen := len(oldExt) - newLen := len(newExt) - if oldLen != newLen { - return newLen < oldLen - } - return newExt < oldExt -} - -func (ds *DirService) notifyChange(zid id.Zid, reason box.UpdateReason) { - if notify := ds.infos; notify != nil { - ds.log.Trace().Zid(zid).Uint("reason", uint64(reason)).Msg("notifyChange") - notify(ds.box, zid, reason) - } -} DELETED box/notify/directory_test.go Index: box/notify/directory_test.go ================================================================== --- box/notify/directory_test.go +++ /dev/null @@ -1,81 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - - _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. - _ "zettelstore.de/z/parser/draw" // Allow to use draw parser. - _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. - _ "zettelstore.de/z/parser/none" // Allow to use none parser. - _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. - _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. - "zettelstore.de/z/zettel/id" - "zettelstore.de/z/zettel/meta" -) - -func TestSeekZid(t *testing.T) { - testcases := []struct { - name string - zid id.Zid - }{ - {"", id.Invalid}, - {"1", id.Invalid}, - {"1234567890123", id.Invalid}, - {" 12345678901234", id.Invalid}, - {"12345678901234", id.Zid(12345678901234)}, - {"12345678901234.ext", id.Zid(12345678901234)}, - {"12345678901234 abc.ext", id.Zid(12345678901234)}, - {"12345678901234.abc.ext", id.Zid(12345678901234)}, - {"12345678901234 def", id.Zid(12345678901234)}, - } - for _, tc := range testcases { - gotZid := seekZid(tc.name) - if gotZid != tc.zid { - t.Errorf("seekZid(%q) == %v, but got %v", tc.name, tc.zid, gotZid) - } - } -} - -func TestNewExtIsBetter(t *testing.T) { - extVals := []string{ - // Main Formats - meta.SyntaxZmk, meta.SyntaxDraw, meta.SyntaxMarkdown, meta.SyntaxMD, - // Other supported text formats - meta.SyntaxCSS, meta.SyntaxSxn, meta.SyntaxTxt, meta.SyntaxHTML, - meta.SyntaxText, meta.SyntaxPlain, - // Supported text graphics formats - meta.SyntaxSVG, - meta.SyntaxNone, - // Supported binary graphic formats - meta.SyntaxGif, meta.SyntaxPNG, meta.SyntaxJPEG, meta.SyntaxWebp, meta.SyntaxJPG, - - // Unsupported syntax values - "gz", "cpp", "tar", "cppc", - } - for oldI, oldExt := range extVals { - for newI, newExt := range extVals { - if oldI <= newI { - continue - } - if !newExtIsBetter(oldExt, newExt) { - t.Errorf("newExtIsBetter(%q, %q) == true, but got false", oldExt, newExt) - } - if newExtIsBetter(newExt, oldExt) { - t.Errorf("newExtIsBetter(%q, %q) == false, but got true", newExt, oldExt) - } - } - } -} DELETED box/notify/entry.go Index: box/notify/entry.go ================================================================== --- box/notify/entry.go +++ /dev/null @@ -1,123 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 ( - extZettel = "zettel" // file contains metadata and content - extBin = "bin" // file contains binary content - extTxt = "txt" // file contains non-binary content -) - -func extIsMetaAndContent(ext string) bool { return ext == extZettel } - -// DirEntry stores everything for a directory entry. -type DirEntry struct { - Zid id.Zid - MetaName string // file name of meta information - ContentName string // file name of zettel content - ContentExt string // (normalized) file extension of zettel content - UselessFiles []string // list of other content files -} - -// IsValid checks whether the entry is valid. -func (e *DirEntry) IsValid() bool { - return e != nil && e.Zid.IsValid() -} - -// HasMetaInContent returns true, if metadata will be stored in the content file. -func (e *DirEntry) HasMetaInContent() bool { - return e.IsValid() && extIsMetaAndContent(e.ContentExt) -} - -// SetupFromMetaContent fills entry data based on metadata and zettel content. -func (e *DirEntry) SetupFromMetaContent(m *meta.Meta, content zettel.Content, getZettelFileSyntax func() []string) { - if e.Zid != m.Zid { - panic("Zid differ") - } - if contentName := e.ContentName; contentName != "" { - if !extIsMetaAndContent(e.ContentExt) && e.MetaName == "" { - e.MetaName = e.calcBaseName(contentName) - } - 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) - } - e.ContentName = e.calcBaseName(metaName) + "." + ext - e.ContentExt = ext - } else { - if len(content.AsBytes()) > 0 { - e.ContentName = e.calcBaseName(metaName) + "." + ext - e.ContentExt = ext - } - if metaName == "" { - e.MetaName = e.calcBaseName(e.ContentName) - } - } -} - -func contentExtWithMeta(syntax string, content zettel.Content) string { - p := parser.Get(syntax) - if content.IsBinary() { - if p.IsImageFormat { - return syntax - } - return extBin - } - if p.IsImageFormat { - return extTxt - } - return syntax -} - -func calcContentExt(syntax string, yamlSep bool, getZettelFileSyntax func() []string) string { - if yamlSep { - return extZettel - } - switch syntax { - case meta.SyntaxNone, meta.SyntaxZmk: - return extZettel - } - for _, s := range getZettelFileSyntax() { - if s == syntax { - return extZettel - } - } - return syntax - -} - -func (e *DirEntry) calcBaseName(name string) string { - if name == "" { - return e.Zid.String() - } - return name[0 : len(name)-len(filepath.Ext(name))] - -} DELETED box/notify/fsdir.go Index: box/notify/fsdir.go ================================================================== --- box/notify/fsdir.go +++ /dev/null @@ -1,234 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - "strings" - - "github.com/fsnotify/fsnotify" - "zettelstore.de/z/logger" -) - -type fsdirNotifier struct { - log *logger.Logger - events chan Event - done chan struct{} - refresh chan struct{} - base *fsnotify.Watcher - path string - fetcher EntryFetcher - parent string -} - -// NewFSDirNotifier creates a directory based notifier that receives notifications -// from the file system. -func NewFSDirNotifier(log *logger.Logger, path string) (Notifier, error) { - absPath, err := filepath.Abs(path) - if err != nil { - log.Debug().Err(err).Str("path", path).Msg("Unable to create absolute path") - return nil, err - } - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Debug().Err(err).Str("absPath", absPath).Msg("Unable to create watcher") - return nil, err - } - absParentDir := filepath.Dir(absPath) - errParent := watcher.Add(absParentDir) - err = watcher.Add(absPath) - if errParent != nil { - if err != nil { - 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{}), - base: watcher, - path: absPath, - fetcher: newDirPathFetcher(absPath), - parent: absParentDir, - } - go fsdn.eventLoop() - return fsdn, nil -} - -func (fsdn *fsdirNotifier) Events() <-chan Event { - return fsdn.events -} - -func (fsdn *fsdirNotifier) Refresh() { - fsdn.refresh <- struct{}{} -} - -func (fsdn *fsdirNotifier) eventLoop() { - defer fsdn.base.Close() - defer close(fsdn.events) - defer close(fsdn.refresh) - if !listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) { - return - } - - for fsdn.readAndProcessEvent() { - } -} - -func (fsdn *fsdirNotifier) readAndProcessEvent() bool { - select { - case <-fsdn.done: - fsdn.traceDone(1) - return false - default: - } - select { - case <-fsdn.done: - fsdn.traceDone(2) - return false - case <-fsdn.refresh: - fsdn.log.Trace().Msg("refresh") - listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) - case err, ok := <-fsdn.base.Errors: - fsdn.log.Trace().Err(err).Bool("ok", ok).Msg("got errors") - if !ok { - return false - } - select { - case fsdn.events <- Event{Op: Error, Err: err}: - case <-fsdn.done: - fsdn.traceDone(3) - return false - } - case ev, ok := <-fsdn.base.Events: - fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Bool("ok", ok).Msg("file event") - if !ok { - return false - } - if !fsdn.processEvent(&ev) { - return false - } - } - return true -} - -func (fsdn *fsdirNotifier) traceDone(pos int64) { - fsdn.log.Trace().Int("i", pos).Msg("done with read and process events") -} - -func (fsdn *fsdirNotifier) processEvent(ev *fsnotify.Event) bool { - if strings.HasPrefix(ev.Name, fsdn.path) { - if len(ev.Name) == len(fsdn.path) { - return fsdn.processDirEvent(ev) - } - return fsdn.processFileEvent(ev) - } - fsdn.log.Trace().Str("path", fsdn.path).Str("name", ev.Name).Str("op", ev.Op.String()).Msg("event does not match") - return true -} - -func (fsdn *fsdirNotifier) processDirEvent(ev *fsnotify.Event) bool { - if ev.Has(fsnotify.Remove) || ev.Has(fsnotify.Rename) { - fsdn.log.Debug().Str("name", fsdn.path).Msg("Directory removed") - fsdn.base.Remove(fsdn.path) - select { - case fsdn.events <- Event{Op: Destroy}: - case <-fsdn.done: - fsdn.log.Trace().Int("i", 1).Msg("done dir event processing") - return false - } - return true - } - - if ev.Has(fsnotify.Create) { - err := fsdn.base.Add(fsdn.path) - if err != nil { - fsdn.log.Error().Err(err).Str("name", fsdn.path).Msg("Unable to add directory") - select { - case fsdn.events <- Event{Op: Error, Err: err}: - case <-fsdn.done: - fsdn.log.Trace().Int("i", 2).Msg("done dir event processing") - return false - } - } - fsdn.log.Debug().Str("name", fsdn.path).Msg("Directory added") - return listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) - } - - fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("Directory processed") - return true -} - -func (fsdn *fsdirNotifier) processFileEvent(ev *fsnotify.Event) bool { - if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Write) { - if fi, err := os.Lstat(ev.Name); err != nil || !fi.Mode().IsRegular() { - regular := err == nil && fi.Mode().IsRegular() - fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Err(err).Bool("regular", regular).Msg("error with file") - return true - } - fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File updated") - return fsdn.sendEvent(Update, filepath.Base(ev.Name)) - } - - if ev.Has(fsnotify.Rename) { - fi, err := os.Lstat(ev.Name) - if err != nil { - fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File deleted") - return fsdn.sendEvent(Delete, filepath.Base(ev.Name)) - } - if fi.Mode().IsRegular() { - fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File updated") - return fsdn.sendEvent(Update, filepath.Base(ev.Name)) - } - fsdn.log.Trace().Str("name", ev.Name).Msg("File not regular") - return true - } - - if ev.Has(fsnotify.Remove) { - fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File deleted") - return fsdn.sendEvent(Delete, filepath.Base(ev.Name)) - } - - fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File processed") - return true -} - -func (fsdn *fsdirNotifier) sendEvent(op EventOp, filename string) bool { - select { - case fsdn.events <- Event{Op: op, Name: filename}: - case <-fsdn.done: - fsdn.log.Trace().Msg("done file event processing") - return false - } - return true -} - -func (fsdn *fsdirNotifier) Close() { - close(fsdn.done) -} DELETED box/notify/helper.go Index: box/notify/helper.go ================================================================== --- box/notify/helper.go +++ /dev/null @@ -1,98 +0,0 @@ -//----------------------------------------------------------------------------- -// 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 -} - -func newDirPathFetcher(dirPath string) EntryFetcher { return &dirPathFetcher{dirPath} } - -func (dpf *dirPathFetcher) Fetch() ([]string, error) { - entries, err := os.ReadDir(dpf.dirPath) - if err != nil { - return nil, err - } - result := make([]string, 0, len(entries)) - for _, entry := range entries { - if info, err1 := entry.Info(); err1 != nil || !info.Mode().IsRegular() { - continue - } - result = append(result, entry.Name()) - } - return result, nil -} - -type zipPathFetcher struct { - zipPath string -} - -func newZipPathFetcher(zipPath string) EntryFetcher { return &zipPathFetcher{zipPath} } - -func (zpf *zipPathFetcher) Fetch() ([]string, error) { - reader, err := zip.OpenReader(zpf.zipPath) - if err != nil { - return nil, err - } - defer reader.Close() - result := make([]string, 0, len(reader.File)) - for _, f := range reader.File { - result = append(result, f.Name) - } - return result, nil -} - -// listDirElements write all files within the directory path as events. -func listDirElements(log *logger.Logger, fetcher EntryFetcher, events chan<- Event, done <-chan struct{}) bool { - select { - case events <- Event{Op: Make}: - case <-done: - return false - } - entries, err := fetcher.Fetch() - if err != nil { - select { - case events <- Event{Op: Error, Err: err}: - case <-done: - return false - } - } - for _, name := range entries { - log.Trace().Str("name", name).Msg("File listed") - select { - case events <- Event{Op: List, Name: name}: - case <-done: - return false - } - } - - select { - case events <- Event{Op: List}: - case <-done: - return false - } - return true -} DELETED box/notify/notify.go Index: box/notify/notify.go ================================================================== --- box/notify/notify.go +++ /dev/null @@ -1,85 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - -// Notifier send events about their container and content. -type Notifier interface { - // Return the channel - Events() <-chan Event - - // Signal a refresh of the container. This will result in some events. - Refresh() - - // Close the notifier (and eventually the channel) - Close() -} - -// EventOp describe a notification operation. -type EventOp uint8 - -// Valid constants for event operations. -// -// Error signals a detected error. Details are in Event.Err. -// -// Make signals that the container is detected. List events will follow. -// -// List signals a found file, if Event.Name is not empty. Otherwise it signals -// the end of files within the container. -// -// Destroy signals that the container is not there any more. It might me Make later again. -// -// Update signals that file Event.Name was created/updated. -// File name is relative to the container. -// -// Delete signals that file Event.Name was removed. -// File name is relative to the container's name. -const ( - _ EventOp = iota - Error // Error while operating - Make // Make container - List // List container - Destroy // Destroy container - Update // Update element - Delete // Delete element -) - -// String representation of operation code. -func (c EventOp) String() string { - switch c { - case Error: - return "ERROR" - case Make: - return "MAKE" - case List: - return "LIST" - case Destroy: - return "DESTROY" - case Update: - return "UPDATE" - case Delete: - return "DELETE" - default: - return fmt.Sprintf("UNKNOWN(%d)", c) - } -} - -// Event represents a single container / element event. -type Event struct { - Op EventOp - Name string - Err error // Valid iff Op == Error -} DELETED box/notify/simpledir.go Index: box/notify/simpledir.go ================================================================== --- box/notify/simpledir.go +++ /dev/null @@ -1,88 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - - "zettelstore.de/z/logger" -) - -type simpleDirNotifier struct { - log *logger.Logger - events chan Event - done chan struct{} - refresh chan struct{} - fetcher EntryFetcher -} - -// NewSimpleDirNotifier creates a directory based notifier that will not receive -// any notifications from the operating system. -func NewSimpleDirNotifier(log *logger.Logger, path string) (Notifier, error) { - absPath, err := filepath.Abs(path) - if err != nil { - return nil, err - } - sdn := &simpleDirNotifier{ - log: log, - events: make(chan Event), - done: make(chan struct{}), - refresh: make(chan struct{}), - fetcher: newDirPathFetcher(absPath), - } - go sdn.eventLoop() - return sdn, nil -} - -// NewSimpleZipNotifier creates a zip-file based notifier that will not receive -// any notifications from the operating system. -func NewSimpleZipNotifier(log *logger.Logger, zipPath string) Notifier { - sdn := &simpleDirNotifier{ - log: log, - events: make(chan Event), - done: make(chan struct{}), - refresh: make(chan struct{}), - fetcher: newZipPathFetcher(zipPath), - } - go sdn.eventLoop() - return sdn -} - -func (sdn *simpleDirNotifier) Events() <-chan Event { - return sdn.events -} - -func (sdn *simpleDirNotifier) Refresh() { - sdn.refresh <- struct{}{} -} - -func (sdn *simpleDirNotifier) eventLoop() { - defer close(sdn.events) - defer close(sdn.refresh) - if !listDirElements(sdn.log, sdn.fetcher, sdn.events, sdn.done) { - return - } - for { - select { - case <-sdn.done: - return - case <-sdn.refresh: - listDirElements(sdn.log, sdn.fetcher, sdn.events, sdn.done) - } - } -} - -func (sdn *simpleDirNotifier) Close() { - close(sdn.done) -} Index: cmd/cmd_file.go ================================================================== --- cmd/cmd_file.go +++ cmd/cmd_file.go @@ -19,16 +19,17 @@ "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" + "t73f.de/r/zsc/domain/id" + "t73f.de/r/zsc/domain/meta" + "t73f.de/r/zsx/input" + + "zettelstore.de/z/internal/encoder" + "zettelstore.de/z/internal/parser" + "zettelstore.de/z/internal/zettel" ) // ---------- Subcommand: file ----------------------------------------------- func cmdFile(fs *flag.FlagSet) (int, error) { @@ -41,19 +42,21 @@ context.Background(), zettel.Zettel{ Meta: m, Content: zettel.NewContent(inp.Src[inp.Pos:]), }, - m.GetDefault(api.KeySyntax, meta.DefaultSyntax), + string(m.GetDefault(meta.KeySyntax, meta.DefaultSyntax)), nil, ) - encdr := encoder.Create(api.Encoder(enc), &encoder.CreateParameter{Lang: m.GetDefault(api.KeyLang, api.ValueLangEN)}) + encdr := encoder.Create( + api.Encoder(enc), + &encoder.CreateParameter{Lang: string(m.GetDefault(meta.KeyLang, meta.ValueLangEN))}) if encdr == nil { fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc) return 2, nil } - _, err = encdr.WriteZettel(os.Stdout, z, parser.ParseMetadata) + _, err = encdr.WriteZettel(os.Stdout, z) if err != nil { return 2, err } fmt.Println() Index: cmd/cmd_password.go ================================================================== --- cmd/cmd_password.go +++ cmd/cmd_password.go @@ -18,13 +18,14 @@ "fmt" "os" "golang.org/x/term" - "t73f.de/r/zsc/api" - "zettelstore.de/z/auth/cred" - "zettelstore.de/z/zettel/id" + "t73f.de/r/zsc/domain/id" + "t73f.de/r/zsc/domain/meta" + + "zettelstore.de/z/internal/auth/cred" ) // ---------- Subcommand: password ------------------------------------------- func cmdPassword(fs *flag.FlagSet) (int, error) { @@ -61,12 +62,12 @@ hashedPassword, err := cred.HashCredential(zid, ident, password) if err != nil { return 2, err } fmt.Printf("%v: %s\n%v: %s\n", - api.KeyCredential, hashedPassword, - api.KeyUserID, ident, + meta.KeyCredential, hashedPassword, + meta.KeyUserID, ident, ) return 0, nil } func getPassword(prompt string) (string, error) { Index: cmd/cmd_run.go ================================================================== --- cmd/cmd_run.go +++ cmd/cmd_run.go @@ -16,19 +16,20 @@ import ( "context" "flag" "net/http" - "zettelstore.de/z/auth" - "zettelstore.de/z/box" - "zettelstore.de/z/config" - "zettelstore.de/z/kernel" - "zettelstore.de/z/usecase" - "zettelstore.de/z/web/adapter/api" - "zettelstore.de/z/web/adapter/webui" - "zettelstore.de/z/web/server" - "zettelstore.de/z/zettel/meta" + "t73f.de/r/zsc/domain/meta" + + "zettelstore.de/z/internal/auth" + "zettelstore.de/z/internal/box" + "zettelstore.de/z/internal/config" + "zettelstore.de/z/internal/kernel" + "zettelstore.de/z/internal/usecase" + "zettelstore.de/z/internal/web/adapter/api" + "zettelstore.de/z/internal/web/adapter/webui" + "zettelstore.de/z/internal/web/server" ) // ---------- Subcommand: run ------------------------------------------------ func flgRun(fs *flag.FlagSet) { Index: cmd/command.go ================================================================== --- cmd/command.go +++ cmd/command.go @@ -13,13 +13,14 @@ package cmd import ( "flag" + "maps" + "slices" - "t73f.de/r/zsc/maps" - "zettelstore.de/z/logger" + "zettelstore.de/z/internal/logger" ) // Command stores information about commands / sub-commands. type Command struct { Name string // command name as it appears on the command line @@ -64,6 +65,6 @@ cmd, ok := commands[name] return cmd, ok } // List returns a sorted list of all registered command names. -func List() []string { return maps.Keys(commands) } +func List() []string { return slices.Sorted(maps.Keys(commands)) } Index: cmd/main.go ================================================================== --- cmd/main.go +++ cmd/main.go @@ -9,10 +9,11 @@ // // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- +// Package cmd provides the commands to call Zettelstore from the command line. package cmd import ( "crypto/sha256" "flag" @@ -24,22 +25,23 @@ "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" + "t73f.de/r/zsc/domain/id" + "t73f.de/r/zsc/domain/meta" + "t73f.de/r/zsx/input" + + "zettelstore.de/z/internal/auth" + "zettelstore.de/z/internal/auth/impl" + "zettelstore.de/z/internal/box" + "zettelstore.de/z/internal/box/compbox" + "zettelstore.de/z/internal/box/manager" + "zettelstore.de/z/internal/config" + "zettelstore.de/z/internal/kernel" + "zettelstore.de/z/internal/logger" + "zettelstore.de/z/internal/web/server" ) const strRunSimple = "run-simple" func init() { @@ -123,38 +125,38 @@ func getConfig(fs *flag.FlagSet) (string, *meta.Meta) { filename, cfg := fetchStartupConfiguration(fs) fs.Visit(func(flg *flag.Flag) { switch flg.Name { case "p": - cfg.Set(keyListenAddr, net.JoinHostPort("127.0.0.1", flg.Value.String())) + cfg.Set(keyListenAddr, meta.Value(net.JoinHostPort("127.0.0.1", flg.Value.String()))) case "a": - cfg.Set(keyAdminPort, flg.Value.String()) + cfg.Set(keyAdminPort, meta.Value(flg.Value.String())) case "d": val := flg.Value.String() if strings.HasPrefix(val, "/") { val = "dir://" + val } else { val = "dir:" + val } deleteConfiguredBoxes(cfg) - cfg.Set(keyBoxOneURI, val) + cfg.Set(keyBoxOneURI, meta.Value(val)) case "l": - cfg.Set(keyLogLevel, flg.Value.String()) + cfg.Set(keyLogLevel, meta.Value(flg.Value.String())) case "debug": - cfg.Set(keyDebug, flg.Value.String()) + cfg.Set(keyDebug, meta.Value(flg.Value.String())) case "r": - cfg.Set(keyReadOnly, flg.Value.String()) + cfg.Set(keyReadOnly, meta.Value(flg.Value.String())) case "v": - cfg.Set(keyVerbose, flg.Value.String()) + cfg.Set(keyVerbose, meta.Value(flg.Value.String())) } }) return filename, cfg } func deleteConfiguredBoxes(cfg *meta.Meta) { - for _, p := range cfg.PairsRest() { - if key := p.Key; strings.HasPrefix(key, kernel.BoxURIs) { + for key := range cfg.Rest() { + if strings.HasPrefix(key, kernel.BoxURIs) { cfg.Delete(key) } } } @@ -184,11 +186,11 @@ debugMode := cfg.GetBool(keyDebug) if debugMode && kernel.Main.GetKernelLogger().Level() > logger.DebugLevel { kernel.Main.SetLogLevel(logger.DebugLevel.String()) } if logLevel, found := cfg.Get(keyLogLevel); found { - kernel.Main.SetLogLevel(logLevel) + kernel.Main.SetLogLevel(string(logLevel)) } err := setConfigValue(nil, kernel.CoreService, kernel.CoreDebug, debugMode) err = setConfigValue(err, kernel.CoreService, kernel.CoreVerbose, cfg.GetBool(keyVerbose)) if val, found := cfg.Get(keyAdminPort); found { err = setConfigValue(err, kernel.CoreService, kernel.CorePort, val) @@ -279,15 +281,15 @@ if len(secret) < 16 && cfg.GetDefault(keyOwner, "") != "" { fmt.Fprintf(os.Stderr, "secret must have at least length 16 when authentication is enabled, but is %q\n", secret) return 2 } cfg.Delete("secret") - secret = fmt.Sprintf("%x", sha256.Sum256([]byte(secret))) + secretHash := fmt.Sprintf("%x", sha256.Sum256([]byte(string(secret)))) kern.SetCreators( func(readonly bool, owner id.Zid) (auth.Manager, error) { - return impl.New(readonly, owner, secret), nil + return impl.New(readonly, owner, secretHash), nil }, createManager, func(srv server.Server, plMgr box.Manager, authMgr auth.Manager, rtConfig config.Config) error { setupRouting(srv, plMgr, authMgr, rtConfig) return nil Index: cmd/register.go ================================================================== --- cmd/register.go +++ cmd/register.go @@ -9,29 +9,15 @@ // // 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. +// Mention all needed boxes, encoders, and parsers to have them registered. import ( - _ "zettelstore.de/z/box/compbox" // Allow to use computed box. - _ "zettelstore.de/z/box/constbox" // Allow to use global internal box. - _ "zettelstore.de/z/box/dirbox" // Allow to use directory box. - _ "zettelstore.de/z/box/filebox" // Allow to use file box. - _ "zettelstore.de/z/box/membox" // Allow to use in-memory box. - _ "zettelstore.de/z/encoder/htmlenc" // Allow to use HTML encoder. - _ "zettelstore.de/z/encoder/mdenc" // Allow to use markdown encoder. - _ "zettelstore.de/z/encoder/shtmlenc" // Allow to use SHTML encoder. - _ "zettelstore.de/z/encoder/szenc" // Allow to use Sz encoder. - _ "zettelstore.de/z/encoder/textenc" // Allow to use text encoder. - _ "zettelstore.de/z/encoder/zmkenc" // Allow to use zmk encoder. - _ "zettelstore.de/z/kernel/impl" // Allow kernel implementation to create itself - _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. - _ "zettelstore.de/z/parser/draw" // Allow to use draw parser. - _ "zettelstore.de/z/parser/markdown" // Allow to use markdown parser. - _ "zettelstore.de/z/parser/none" // Allow to use none parser. - _ "zettelstore.de/z/parser/plain" // Allow to use plain parser. - _ "zettelstore.de/z/parser/zettelmark" // Allow to use zettelmark parser. + _ "zettelstore.de/z/internal/box/compbox" // Allow to use computed box. + _ "zettelstore.de/z/internal/box/constbox" // Allow to use global internal box. + _ "zettelstore.de/z/internal/box/dirbox" // Allow to use directory box. + _ "zettelstore.de/z/internal/box/filebox" // Allow to use file box. + _ "zettelstore.de/z/internal/box/membox" // Allow to use in-memory box. ) DELETED collect/collect.go Index: collect/collect.go ================================================================== --- collect/collect.go +++ /dev/null @@ -1,46 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - -// Summary stores the relevant parts of the syntax tree -type Summary struct { - Links []*ast.Reference // list of all linked material - Embeds []*ast.Reference // list of all embedded material - Cites []*ast.CiteNode // list of all referenced citations -} - -// References returns all references mentioned in the given zettel. This also -// includes references to images. -func References(zn *ast.ZettelNode) (s Summary) { - ast.Walk(&s, &zn.Ast) - return s -} - -// Visit all node to collect data for the summary. -func (s *Summary) Visit(node ast.Node) ast.Visitor { - switch n := node.(type) { - case *ast.TranscludeNode: - s.Embeds = append(s.Embeds, n.Ref) - case *ast.LinkNode: - s.Links = append(s.Links, n.Ref) - case *ast.EmbedRefNode: - s.Embeds = append(s.Embeds, n.Ref) - case *ast.CiteNode: - s.Cites = append(s.Cites, n) - } - return s -} DELETED collect/collect_test.go Index: collect/collect_test.go ================================================================== --- collect/collect_test.go +++ /dev/null @@ -1,64 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - - "zettelstore.de/z/ast" - "zettelstore.de/z/collect" -) - -func parseRef(s string) *ast.Reference { - r := ast.ParseReference(s) - if !r.IsValid() { - panic(s) - } - return r -} - -func TestLinks(t *testing.T) { - t.Parallel() - zn := &ast.ZettelNode{} - summary := collect.References(zn) - if summary.Links != nil || summary.Embeds != nil { - t.Error("No links/images expected, but got:", summary.Links, "and", summary.Embeds) - } - - intNode := &ast.LinkNode{Ref: parseRef("01234567890123")} - para := ast.CreateParaNode(intNode, &ast.LinkNode{Ref: parseRef("https://zettelstore.de/z")}) - zn.Ast = ast.BlockSlice{para} - summary = collect.References(zn) - if summary.Links == nil || summary.Embeds != nil { - t.Error("Links expected, and no images, but got:", summary.Links, "and", summary.Embeds) - } - - para.Inlines = append(para.Inlines, intNode) - summary = collect.References(zn) - if cnt := len(summary.Links); cnt != 3 { - t.Error("Link count does not work. Expected: 3, got", summary.Links) - } -} - -func TestEmbed(t *testing.T) { - t.Parallel() - zn := &ast.ZettelNode{ - Ast: ast.BlockSlice{ast.CreateParaNode(&ast.EmbedRefNode{Ref: parseRef("12345678901234")})}, - } - summary := collect.References(zn) - if summary.Embeds == nil { - t.Error("Only image expected, but got: ", summary.Embeds) - } -} DELETED collect/order.go Index: collect/order.go ================================================================== --- collect/order.go +++ /dev/null @@ -1,76 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - -// Order of internal reference within the given zettel. -func Order(zn *ast.ZettelNode) (result []*ast.Reference) { - for _, bn := range zn.Ast { - ln, ok := bn.(*ast.NestedListNode) - if !ok { - continue - } - switch ln.Kind { - case ast.NestedListOrdered, ast.NestedListUnordered: - for _, is := range ln.Items { - if ref := firstItemZettelReference(is); ref != nil { - result = append(result, ref) - } - } - } - } - return result -} - -func firstItemZettelReference(is ast.ItemSlice) *ast.Reference { - for _, in := range is { - if pn, ok := in.(*ast.ParaNode); ok { - if ref := firstInlineZettelReference(pn.Inlines); ref != nil { - return ref - } - } - } - return nil -} - -func firstInlineZettelReference(is ast.InlineSlice) (result *ast.Reference) { - for _, inl := range is { - switch in := inl.(type) { - case *ast.LinkNode: - if ref := in.Ref; ref.IsZettel() { - return ref - } - result = firstInlineZettelReference(in.Inlines) - case *ast.EmbedRefNode: - result = firstInlineZettelReference(in.Inlines) - case *ast.EmbedBLOBNode: - result = firstInlineZettelReference(in.Inlines) - case *ast.CiteNode: - result = firstInlineZettelReference(in.Inlines) - case *ast.FootnoteNode: - // Ignore references in footnotes - continue - case *ast.FormatNode: - result = firstInlineZettelReference(in.Inlines) - default: - continue - } - if result != nil { - return result - } - } - return nil -} DELETED config/config.go Index: config/config.go ================================================================== --- config/config.go +++ /dev/null @@ -1,109 +0,0 @@ -//----------------------------------------------------------------------------- -// 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" - - "zettelstore.de/z/zettel/meta" -) - -// Key values that are supported by Config.Get -const ( - KeyFooterZettel = "footer-zettel" - KeyHomeZettel = "home-zettel" - KeyShowBackLinks = "show-back-links" - KeyShowFolgeLinks = "show-folge-links" - KeyShowSequelLinks = "show-sequel-links" - KeyShowSuccessorLinks = "show-successor-links" - // api.KeyLang -) - -// Config allows to retrieve all defined configuration values that can be changed during runtime. -type Config interface { - AuthConfig - - // Get returns the value of the given key. It searches first in the given metadata, - // then in the data of the current user, and at last in the system-wide data. - Get(ctx context.Context, m *meta.Meta, key string) string - - // AddDefaultValues enriches the given meta data with its default values. - AddDefaultValues(context.Context, *meta.Meta) *meta.Meta - - // GetSiteName returns the current value of the "site-name" key. - GetSiteName() string - - // GetHTMLInsecurity returns the current - GetHTMLInsecurity() HTMLInsecurity - - // GetMaxTransclusions returns the maximum number of indirect transclusions. - GetMaxTransclusions() int - - // GetYAMLHeader returns the current value of the "yaml-header" key. - GetYAMLHeader() bool - - // GetZettelFileSyntax returns the current value of the "zettel-file-syntax" key. - GetZettelFileSyntax() []string -} - -// AuthConfig are relevant configuration values for authentication. -type AuthConfig interface { - // GetSimpleMode returns true if system tuns in simple-mode. - GetSimpleMode() bool - - // GetExpertMode returns the current value of the "expert-mode" key. - GetExpertMode() bool - - // GetVisibility returns the visibility value of the metadata. - GetVisibility(m *meta.Meta) meta.Visibility -} - -// HTMLInsecurity states what kind of insecure HTML is allowed. -// The lowest value is the most secure one (disallowing any HTML) -type HTMLInsecurity uint8 - -// Constant values for HTMLInsecurity: -const ( - NoHTML HTMLInsecurity = iota - SyntaxHTML - MarkdownHTML - ZettelmarkupHTML -) - -func (hi HTMLInsecurity) String() string { - switch hi { - case SyntaxHTML: - return "html" - case MarkdownHTML: - return "markdown" - case ZettelmarkupHTML: - return "zettelmarkup" - } - return "secure" -} - -// AllowHTML returns true, if the given HTML insecurity level matches the given syntax value. -func (hi HTMLInsecurity) AllowHTML(syntax string) bool { - switch hi { - case SyntaxHTML: - return syntax == meta.SyntaxHTML - case MarkdownHTML: - return syntax == meta.SyntaxHTML || syntax == meta.SyntaxMarkdown || syntax == meta.SyntaxMD - case ZettelmarkupHTML: - return syntax == meta.SyntaxZmk || syntax == meta.SyntaxHTML || - syntax == meta.SyntaxMarkdown || syntax == meta.SyntaxMD - } - return false -} Index: docs/manual/00001001000000.zettel ================================================================== --- docs/manual/00001001000000.zettel +++ docs/manual/00001001000000.zettel @@ -2,16 +2,16 @@ title: Introduction to the Zettelstore role: manual tags: #introduction #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20240710184612 +modified: 20250102181246 [[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. +The method is based on creating many individual notes, each containing one idea or piece of information, which are 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"". Index: docs/manual/00001002000000.zettel ================================================================== --- docs/manual/00001002000000.zettel +++ docs/manual/00001002000000.zettel @@ -2,20 +2,20 @@ title: Design goals for the Zettelstore role: manual tags: #design #goal #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20230624171152 +modified: 20250102191434 Zettelstore supports the following design goals: ; Longevity of stored notes / zettel : Every zettel you create should be readable without the help of any tool, even without Zettelstore. -: It should be not hard to write other software that works with your zettel. +: It should not hard to write other software that works with your zettel. : Normal zettel should be stored in a single file. If this is not possible: at most in two files: one for the metadata, one for the content. - The only exception are [[predefined zettel|00001005090000]] stored in the Zettelstore executable. + The only exceptions are [[predefined zettel|00001005090000]] stored in the Zettelstore executable. : There is no additional database. ; Single user : All zettel belong to you, only to you. Zettelstore provides its services only to one person: you. If the computer running Zettelstore is securely configured, there should be no risk that others are able to read or update your zettel. @@ -26,18 +26,18 @@ ; Ease of operation : There is only one executable for Zettelstore and one directory, where your zettel are stored. : If you decide to use multiple directories, you are free to configure Zettelstore appropriately. ; Multiple modes of operation : You can use Zettelstore as a standalone software on your device, but you are not restricted to it. -: You can install the software on a central server, or you can install it on all your devices with no restrictions how to synchronize your zettel. +: You can install the software on a central server, or you can install it on all your devices with no restrictions on how to synchronize your zettel. ; Multiple user interfaces : Zettelstore provides a default [[web-based user interface|00001014000000]]. - Anybody can provide alternative user interfaces, e.g. for special purposes. + Anyone can provide alternative user interfaces, e.g. for special purposes. ; Simple service : The purpose of Zettelstore is to safely store your zettel and to provide some initial relations between them. : External software can be written to deeply analyze your zettel and the structures they form. ; Security by default : Without any customization, Zettelstore provides its services in a safe and secure manner and does not expose you (or other users) to security risks. -: If you know what use are doing, Zettelstore allows you to relax some security-related preferences. +: If you know what you are doing, Zettelstore allows you to relax some security-related preferences. However, even in this case, the more secure way is chosen. : The Zettelstore software uses a minimal design and uses other software dependencies only is essential needed. : There will be no plugin mechanism, which allows external software to control the inner workings of the Zettelstore software. Index: docs/manual/00001003000000.zettel ================================================================== --- docs/manual/00001003000000.zettel +++ docs/manual/00001003000000.zettel @@ -2,28 +2,28 @@ title: Installation of the Zettelstore software role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20241213101917 +modified: 20250102185359 === The curious user You just want to check out the Zettelstore software -* Grab the appropriate executable and copy it into any directory +* Grab the appropriate executable and copy it to 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 a problem, please take a look on the [[Troubleshooting|00001018000000]] page.] + If you encounter a problem, please refer to the [[Troubleshooting|00001018000000]] page.] * A sub-directory ""zettel"" will be created in the directory where you put the executable. It will contain your future zettel. * Open the URI [[http://localhost:23123]] with your web browser. - It will present you a mostly empty Zettelstore. + A mostly empty Zettelstore is presented. There will be a zettel titled ""[[Home|00010000000000]]"" that contains some helpful information. * Please read the instructions for the [[web-based user interface|00001014000000]] and learn about the various ways to write zettel. * If you restart your device, please make sure to start your Zettelstore again. === The intermediate user -You already tried the Zettelstore software and now you want to use it permanently. +You have already tried the Zettelstore software and now you want to use it permanently. Zettelstore should start automatically when you log into your computer. Please follow [[these instructions|00001003300000]]. === The server administrator Index: docs/manual/00001003300000.zettel ================================================================== --- docs/manual/00001003300000.zettel +++ docs/manual/00001003300000.zettel @@ -2,13 +2,13 @@ title: Zettelstore installation for the intermediate user role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20211125191727 -modified: 20220114175754 +modified: 20250227220050 -You already tried the Zettelstore software and now you want to use it permanently. +You have 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]]. * If you created a startup configuration file, you need to test it: @@ -17,11 +17,17 @@ In most cases, this is done by the command ``cd DIR``, where ''DIR'' denotes the directory, where you placed the executable. ** Start the Zettelstore: *** On Windows execute the command ``zettelstore.exe run -c CONFIG_FILE`` *** On macOS execute the command ``./zettelstore run -c CONFIG_FILE`` *** On Linux execute the command ``./zettelstore run -c CONFIG_FILE`` -** In all cases ''CONFIG_FILE'' must be substituted by file name where you wrote the startup configuration. +** In all cases ''CONFIG_FILE'' must be replaced with the file name where you wrote the startup configuration. ** If you encounter some error messages, update the startup configuration, and try again. * Depending on your operating system, there are different ways to register Zettelstore to start automatically: ** [[Windows|00001003305000]] ** [[macOS|00001003310000]] ** [[Linux|00001003315000]] + +A word of caution: Never expose Zettelstore directly to the Internet. +As a personal service, Zettelstore is not designed to handle all aspects of the open web. +For instance, it lacks support for certificate handling, which is necessary for encrypted HTTP connections. +To ensure security, [[install Zettelstore on a server|00001003600000]] and place it behind a proxy server designed for Internet exposure. +For more details, see: [[External server to encrypt message transport|00001010090100]]. Index: docs/manual/00001003315000.zettel ================================================================== --- docs/manual/00001003315000.zettel +++ docs/manual/00001003315000.zettel @@ -2,11 +2,11 @@ title: Enable Zettelstore to start automatically on Linux role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20220114181521 -modified: 20220307104944 +modified: 20250102221716 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. @@ -17,11 +17,11 @@ * [[LXDE|https://www.lxde.org/]] uses [[LXSession Edit|https://wiki.lxde.org/en/LXSession_Edit]] to allow users to specify autostart applications. If you use a different desktop environment, it often helps to to provide its name and the string ""autostart"" to google for it with the search engine of your choice. Yet another way is to make use of the middleware that is provided. -Many Linux distributions make use of [[systemd|https://systemd.io/]], which allows to start processes on behalf of an user. +Many Linux distributions make use of [[systemd|https://systemd.io/]], which allows to start processes on behalf of a user. On the command line, adapt the following script to your own needs and execute it: ``` # mkdir -p "$HOME/.config/systemd/user" # cd "$HOME/.config/systemd/user" # cat <<__EOF__ > zettelstore.service Index: docs/manual/00001003600000.zettel ================================================================== --- docs/manual/00001003600000.zettel +++ docs/manual/00001003600000.zettel @@ -2,11 +2,11 @@ title: Installation of Zettelstore on a server role: manual tags: #installation #manual #zettelstore syntax: zmk created: 20211125191727 -modified: 20211125185833 +modified: 20250227220033 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: @@ -50,5 +50,11 @@ Use the commands ``systemctl``{=sh} and ``journalctl``{=sh} to manage the service, e.g.: ```sh # sudo systemctl status zettelstore # verify that it is running # sudo journalctl -u zettelstore # obtain the output of the running zettelstore ``` + +A word of caution: Never expose Zettelstore directly to the Internet. +As a personal service, Zettelstore is not designed to handle all aspects of the open web. +For instance, it lacks support for certificate handling, which is necessary for encrypted HTTP connections. +To ensure security, place Zettelstore behind a proxy server designed for Internet exposure. +For more details, see: [[External server to encrypt message transport|00001010090100]]. Index: docs/manual/00001004000000.zettel ================================================================== --- docs/manual/00001004000000.zettel +++ docs/manual/00001004000000.zettel @@ -2,13 +2,13 @@ title: Configuration of Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20210510153233 +modified: 20250102181034 -There are some levels to change the behavior and/or the appearance of Zettelstore. +There are several 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. This may include the URI under which your Zettelstore is accessible, or the directories in which your Zettel are stored. Index: docs/manual/00001004010000.zettel ================================================================== --- docs/manual/00001004010000.zettel +++ docs/manual/00001004010000.zettel @@ -2,11 +2,11 @@ title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20240926144803 +modified: 20250102180346 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. @@ -24,11 +24,11 @@ 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. +: Allows to specify a directory whose files are allowed to 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]].] Index: docs/manual/00001004011200.zettel ================================================================== --- docs/manual/00001004011200.zettel +++ docs/manual/00001004011200.zettel @@ -2,17 +2,17 @@ title: Zettelstore boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20220307121547 +modified: 20250102185551 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. +An example is the [[predefined zettel|00001005090000]] that come with a Zettelstore. They are stored within the software itself. In another situation you may want to store your zettel volatile, e.g. if you want to provide a sandbox for experimenting. To cope with these (and more) situations, you configure Zettelstore to use one or more __boxes__. This is done via the ''box-uri-X'' keys of the [[startup configuration|00001004010000#box-uri-X]] (X is a number). Index: docs/manual/00001004011400.zettel ================================================================== --- docs/manual/00001004011400.zettel +++ docs/manual/00001004011400.zettel @@ -2,20 +2,20 @@ title: Configure file directory boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20240710180215 +modified: 20250102180416 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:| |type|(Sub-) Type of the directory service|(value of ""[[default-dir-box-type|00001004010000#default-dir-box-type]]"") -|worker|Number of worker that can access the directory in parallel|7 +|worker|Number of workers that can access the directory in parallel|7 |readonly|Allow only operations that do not create or change zettel|n/a === Type On some operating systems, Zettelstore tries to detect changes to zettel files outside of Zettelstore's control[^This includes Linux, Windows, and macOS.]. On other operating systems, this may be not possible, due to technical limitations. Index: docs/manual/00001004011600.zettel ================================================================== --- docs/manual/00001004011600.zettel +++ docs/manual/00001004011600.zettel @@ -2,11 +2,11 @@ title: Configure memory boxes role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20220307112918 -modified: 20220307122554 +modified: 20250102222236 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: @@ -15,12 +15,12 @@ |max-bytes|Maximum number of bytes the box will store|65535|1073741824 (1 GiB) |max-zettel|Maximum number of zettel|127|65535 The default values are somehow arbitrarily, but applicable for many use cases. -While the number of zettel should be easily calculable by an user, the number of bytes might be a little more difficult. +While the number of zettel should be easily calculable by a user, the number of bytes might be a little more difficult. Metadata consumes 6 bytes for the zettel identifier and for each metadata value one byte for the separator, plus the length of key and data. Then size of the content is its size in bytes. For text content, its the number of bytes for its UTF-8 encoding. If one of the limits are exceeded, Zettelstore will give an error indication, based on the HTTP status code 507. Index: docs/manual/00001004020000.zettel ================================================================== --- docs/manual/00001004020000.zettel +++ docs/manual/00001004020000.zettel @@ -1,12 +1,12 @@ id: 00001004020000 -title: Configure the running Zettelstore +title: Configure a running Zettelstore role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20241118175216 +modified: 20250131151530 show-back-links: false You can configure a running Zettelstore by modifying the special zettel with the ID [[00000000000100]]. This zettel is called __configuration zettel__. The following metadata keys change the appearance / behavior of Zettelstore. @@ -39,32 +39,40 @@ May be [[overwritten|00001004020200]] in a user zettel. Default: (an invalid zettel identifier) ; [!home-zettel|''home-zettel''] -: Specifies the identifier of the zettel, that should be presented for the default view / home view. +: Specifies the identifier of the zettel that should be presented for the default view / home view. If not given or if the identifier does not identify a zettel, the zettel with the identifier ''00010000000000'' is shown. May be [[overwritten|00001004020200]] in a user zettel. - ; [!lang|''lang''] : Language to be used when displaying content. Default: ""en"". - This value is used as a default value, if it is not set in an user's zettel or in a zettel. + This value is used as a default value, if it is not set in a user's zettel or in a zettel. It is also used to specify the language for all non-zettel content, e.g. lists or search results. Use values according to the language definition of [[RFC-5646|https://tools.ietf.org/html/rfc5646]]. +; [!lists-menu-zettel|''lists-menu-zettel''] +: Identifier of the zettel that specifies entries of the ""Lists"" menu (in the [[Web user interface|00001014000000]]). + Every list item with a [[link|00001007040310]] is translated into a menu entry. + + If not given or if the identifier does not identify a zettel, or the zettel is not accessible for the current user, the zettel with the identifier ''00000000080001'' is used. + + May be [[overwritten|00001004020200]] in a user zettel. + + Default: ""00000000080001"". ; [!max-transclusions|''max-transclusions''] : Maximum number of indirect transclusion. This is used to avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]]. Default: ""1024"". -; [!show-back-links|''show-back-links''], [!show-folge-links|''show-folge-links''], [!show-sequel-links|''show-sequel-links''], [!show-successor-links|''show-successor-links''] +; [!show-back-links|''show-back-links''], [!show-folge-links|''show-folge-links''], [!show-sequel-links|''show-sequel-links''], [!show-subordinate-links|''show-subordinate-links''], [!show-successor-links|''show-successor-links''] : When displaying a zettel in the web user interface, references to other zettel are normally shown below the content of the zettel. - This affects the metadata keys [[''back''|00001006020000#back]], [[''folge''|00001006020000#folge]], [[''sequel''|00001006020000#sequel]], and [[''prequel''|00001006020000#prequel]]. + This affects the metadata keys [[''back''|00001006020000#back]], [[''folge''|00001006020000#folge]], [[''sequel''|00001006020000#sequel]], [[''subordinates''|00001006020000#subordinates]], and [[''successors''|00001006020000#successors]]. These configuration keys may be used to show, not to show, or to close the list of referenced zettel. Allowed values are: ""false"" (will not show the list), ""close"" (will show the list closed), and ""open"" / """" (will show the list). Index: docs/manual/00001004020200.zettel ================================================================== --- docs/manual/00001004020200.zettel +++ docs/manual/00001004020200.zettel @@ -2,21 +2,23 @@ title: Runtime configuration data that may be user specific or zettel specific role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20221205155521 -modified: 20241118175124 +modified: 20250131151259 Some metadata of the [[runtime configuration|00001004020000]] may be overwritten in an [[user zettel|00001010040200]]. -A subset of those may be overwritten in zettel that is currently used. +A subset of those may be overwritten in the zettel that is currently used. This allows to specify user specific or zettel specific behavior. The following metadata keys are supported to provide a more specific behavior: |=Key|User:|Zettel:|Remarks |[[''footer-zettel''|00001004020000#footer-zettel]]|Y|N| |[[''home-zettel''|00001004020000#home-zettel]]|Y|N| |[[''lang''|00001004020000#lang]]|Y|Y|Making it user-specific could make zettel for other user less useful +|[[''lists-menu-zettel''|00001004020000#lists-menu-zettel]]|Y|N| |[[''show-back-links''|00001004020000#show-back-links]]|Y|Y| |[[''show-folge-links''|00001004020000#show-folge-links]]|Y|Y| |[[''show-sequel-links''|00001004020000#show-sequel-links]]|Y|Y| +|[[''show-subordinate-links''|00001004020000#show-subordinate-links]]|Y|Y| |[[''show-successor-links''|00001004020000#show-successor-links]]|Y|Y| Index: docs/manual/00001004050000.zettel ================================================================== --- docs/manual/00001004050000.zettel +++ docs/manual/00001004050000.zettel @@ -2,14 +2,14 @@ title: Command line parameters role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20221128161932 +modified: 20250102174436 Zettelstore is not just a service that provides services of a zettelkasten. -It allows to some tasks to be executed at the command line. +It allows some tasks to be executed at the command line. Typically, the task (""sub-command"") will be given at the command line as the first parameter. If no parameter is given, the Zettelstore is called as ``` zettelstore @@ -17,16 +17,16 @@ This is equivalent to call it this way: ```sh mkdir -p ./zettel zettelstore run -d ./zettel -c ./.zscfg ``` -Typically this is done by starting Zettelstore via a graphical user interface by double-clicking to its file icon. +Typically this is done by starting Zettelstore via a graphical user interface by double-clicking its file icon. === Sub-commands * [[``zettelstore help``|00001004050200]] lists all available sub-commands. * [[``zettelstore version``|00001004050400]] to display version information of Zettelstore. * [[``zettelstore run``|00001004051000]] to start the Zettelstore service. -* [[``zettelstore run-simple``|00001004051100]] is typically called, when you start Zettelstore by a double.click in your GUI. +* [[``zettelstore run-simple``|00001004051100]] is typically called, when you start Zettelstore by double-clicking in your GUI. * [[``zettelstore file``|00001004051200]] to render files manually without activated/running Zettelstore services. * [[``zettelstore password``|00001004051400]] to calculate data for [[user authentication|00001010040200]]. Every sub-command allows the following command line options: ; [!h|''-h''] (or ''--help'') Index: docs/manual/00001004051100.zettel ================================================================== --- docs/manual/00001004051100.zettel +++ docs/manual/00001004051100.zettel @@ -2,23 +2,23 @@ title: The ''run-simple'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20221128161922 +modified: 20250102221633 === ``zettelstore run-simple`` -This sub-command is implicitly called, when an user starts Zettelstore by double-clicking on its GUI icon. -It is s simplified variant of the [[''run'' sub-command|00001004051000]]. +This sub-command is implicitly called, when a user starts Zettelstore by double-clicking on its GUI icon. +It is a simplified variant of the [[''run'' sub-command|00001004051000]]. First, this sub-command checks if it can read a [[Zettelstore startup configuration|00001004010000]] file by trying the [[default values|00001004051000#c]]. If this is the case, ''run-simple'' just continues as the [[''run'' sub-command|00001004051000]], but ignores any command line options (including ''-d DIR'').[^This allows a [[curious user|00001003000000]] to become an intermediate user.] If no startup configuration was found, the sub-command allows only to specify a zettel directory. The directory will be created automatically, if it does not exist. -This is a difference to the ''run'' sub-command, where the directory must exists. +This is a difference to the ''run'' sub-command, where the directory must exist. In contrast to the ''run'' sub-command, other command line parameter are not allowed. ``` zettelstore run-simple [-d DIR] ``` Index: docs/manual/00001004051400.zettel ================================================================== --- docs/manual/00001004051400.zettel +++ docs/manual/00001004051400.zettel @@ -2,13 +2,13 @@ title: The ''password'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20210712234305 +modified: 20250102221851 -This sub-command is used to create a hashed password for to be authenticated users. +This sub-command is used to create a hashed password for users to be authenticated. 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: ``` @@ -16,11 +16,11 @@ ``` ``IDENT`` is the identification for the user that should be authenticated. ``ZETTEL-ID`` is the [[identifier of the zettel|00001006050000]] that later acts as a user zettel. -See [[Creating an user zettel|00001010040200]] for some background information. +See [[Creating a user zettel|00001010040200]] for some background information. An example: ``` # zettelstore password bob 20200911115600 @@ -33,7 +33,7 @@ This will produce a hashed password (""credential"") for the new user ""bob"" to be stored in zettel ""20200911115600"". You should copy the relevant output to the zettel of the user to be secured, especially by setting the meta keys ''credential'' and ''user-id'' to the copied values. Please note that the generated hashed password is tied to the given user identification (''user-id'') and to the identifier of its zettel. -Changing one of those will stop authenticating the user with the given password. +Changing one of these will prevent the user from being authenticated with the given password. In this case you have to re-run this sub-command. Index: docs/manual/00001004100000.zettel ================================================================== --- docs/manual/00001004100000.zettel +++ docs/manual/00001004100000.zettel @@ -2,13 +2,13 @@ title: Zettelstore Administrator Console role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210510141304 -modified: 20211103162926 +modified: 20250102212543 -The administrator console is a service accessible only on the same computer on which Zettelstore is running. +The administrator console is a service that is 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]]. After you enable the administrator console, you can use tools such as [[PuTTY|https://www.chiark.greenend.org.uk/~sgtatham/putty/]] or other telnet software to connect to the administrator console. @@ -17,10 +17,10 @@ Therefore, you can also use tools like [[netcat|https://nc110.sourceforge.io/]], [[socat|http://www.dest-unreach.org/socat/]], etc. After connecting to the administrator console, there is no further authentication. It is not needed because you must be logged in on the same computer where Zettelstore is running. You cannot connect to the administrator console if you are on a different computer. -Of course, on multi-user systems with encrusted users, you should not enable the administrator console. +Of course, on multi-user systems with untrusted users, you should not enable the administrator console. * Enable via [[command line|00001004051000#a]] * Enable via [[configuration file|00001004010000#admin-port]] * [[List of supported commands|00001004101000]] Index: docs/manual/00001004101000.zettel ================================================================== --- docs/manual/00001004101000.zettel +++ docs/manual/00001004101000.zettel @@ -2,11 +2,11 @@ title: List of supported commands of the administrator console role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210510141304 -modified: 20220823194553 +modified: 20250102190201 ; [!bye|''bye''] : Closes the connection to the administrator console. ; [!config|''config SERVICE''] : Displays all valid configuration keys for the given service. @@ -38,11 +38,11 @@ ``get-config SERVICE`` shows only the current configuration data of the given service. ``get-config SERVICE KEY`` shows the current configuration data for the given service and key. ; [!header|''header''] -: Toggles the header mode, where each table is show with a header nor not. +: Toggles the header mode, where each table is shown with a header nor not. ; [!log-level|''log-level''] : Displays or sets the [[logging level|00001004059700]] for the kernel or a service. ``log-level`` shows all known log level. @@ -77,11 +77,11 @@ ; [!refresh|''refresh''] : Refresh all internal data about zettel. ; [!restart|''restart SERVICE''] : Restart the given service and all other that depend on this. ; [!services|''services''] -: Displays s list of all available services and their current status. +: Displays a list of all available services and their current status. ; [!set-config|''set-config SERVICE KEY VALUE''] : Sets a single configuration value for the next configuration of a given service. It will become effective if the service is restarted. If the key specifies a list value, all other list values with a number greater than the given key are deleted. Index: docs/manual/00001005000000.zettel ================================================================== --- docs/manual/00001005000000.zettel +++ docs/manual/00001005000000.zettel @@ -2,19 +2,19 @@ title: Structure of Zettelstore role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20241213101751 +modified: 20250102191502 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. +Since every zettel must be readable without any special tool, most zettel have to be stored as ordinary files within specific directories. +Typically, file names and file content must comply with 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. -Via the builtin [[web user interface|00001014000000]] you can work with zettel in various ways. +Via the built-in [[web user interface|00001014000000]] you can work with zettel in various ways. For example, you are able to list zettel, to create new zettel, to edit them, or to delete them. You can view zettel details and relations between zettel. In addition, Zettelstore provides an ""application programming interface"" ([[API|00001012000000]]) that allows other software to communicate with the Zettelstore. Zettelstore becomes extensible by external software. @@ -39,28 +39,28 @@ 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; - in this case, the filename just the 14 digits of the zettel identifier, and optional characters except the period ''"."''. + in this case, the filename contains just the 14 digits of the zettel identifier, and optional characters except the period ''"."''. Other filename extensions are used to determine the ""syntax"" of a zettel. This allows to use other content within the Zettelstore, e.g. images or HTML templates. For example, you want to store an important figure in the Zettelstore that is encoded as a ''.png'' file. -Since each zettel contains some metadata, e.g. the title of the figure, the question arises where these data should be stores. +Since each zettel contains some metadata, e.g. the title of the figure, the question arises where these data should be stored. The solution is a meta-file with the same zettel identifier, but without a filename extension. Zettelstore recognizes this situation and reads in both files for the one zettel containing the figure. -It maintains this relationship as long as these files exists. +It maintains this relationship as long as these files exist. In case of some textual zettel content you do not want to store the metadata and the zettel content in two different files. -Here the ''.zettel'' extension will signal that the metadata and the zettel content will be put in the same file, separated by an empty line or a line with three dashes (""''-\-\-''"", also known as ""YAML separator""). +Here the ''.zettel'' extension will signal that the metadata and the zettel content will be stored in the same file, separated by an empty line or a line with three dashes (""''-\-\-''"", also known as ""YAML separator""). === Predefined zettel Zettelstore contains some [[predefined zettel|00001005090000]] to work properly. The [[configuration zettel|00001004020000]] is one example. -To render the builtin [[web user interface|00001014000000]], some templates are used, as well as a [[layout specification in CSS|00000000020001]]. +To render the built-in [[web user interface|00001014000000]], some templates are used, as well as a [[layout specification in CSS|00000000020001]]. The icon that visualizes a broken image is a [[predefined GIF image|00000000040001]]. All of these are visible to the Zettelstore as zettel. One reason for this is to allow you to modify these zettel to adapt Zettelstore to your needs and visual preferences. @@ -67,11 +67,11 @@ Where are these zettel stored? They are stored within the Zettelstore software itself, because one [[design goal|00001002000000]] was to have just one executable file to use Zettelstore. But data stored within an executable program cannot be changed later[^Well, it can, but it is a very bad idea to allow this. Mostly for security reasons.]. 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 you change a zettel, it will be 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). @@ -78,15 +78,15 @@ 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. +As described above, a zettel may be stored either as a file inside a directory or within 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.] A file directory which stores zettel is called a ""directory box"". But zettel may be also stored in a ZIP file, which is called ""file box"". For testing purposes, zettel may be stored in volatile memory (called __RAM__). This way is called ""memory box"". Other types of boxes could be added to Zettelstore. What about a ""remote Zettelstore box""? Index: docs/manual/00001005090000.zettel ================================================================== --- docs/manual/00001005090000.zettel +++ docs/manual/00001005090000.zettel @@ -2,11 +2,11 @@ title: List of predefined zettel role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20210126175322 -modified: 20241202102358 +modified: 20250131151733 The following table lists all predefined zettel with their purpose. The content of most[^To be more exact: zettel with an identifier greater or equal ''00000999999900'' will have their content indexed.] of these zettel will not be indexed by Zettelstore. You will not find zettel when searched for some content, e.g. ""[[query:european]]"" will not find the [[Zettelstore License|00000000000004]]. @@ -20,11 +20,11 @@ | [[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 +| [[00000000000020]] | Zettelstore Box Manager | Contains some statistics about zettel boxes and 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]] | [[00000000010100]] | Zettelstore Base HTML Template | Contains the general layout of the HTML view @@ -39,13 +39,14 @@ | [[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]]"" +| [[00000000060020]] | configuration | [[Role zettel|00001012051800]] for the role ""[[confguration|00001006020100#configuration]]"" | [[00000000060030]] | role | [[Role zettel|00001012051800]] for the role ""[[role|00001006020100#role]]"" | [[00000000060040]] | tag | [[Role zettel|00001012051800]] for the role ""[[tag|00001006020100#tag]]"" +| [[00000000080001]] | Lists Menu | Default items of the ""Lists"" menu; see [[lists-menu-zettel|00001004020000#lists-menu-zettel]] for customization options | [[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]] @@ -53,6 +54,6 @@ | [[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. +**Important:** All identifiers may change until a stable version of the software is released. Index: docs/manual/00001006000000.zettel ================================================================== --- docs/manual/00001006000000.zettel +++ docs/manual/00001006000000.zettel @@ -2,11 +2,11 @@ title: Layout of a Zettel role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20230403123541 +modified: 20250102190828 A zettel consists of two parts: the metadata and the zettel content. Metadata gives some information mostly about the zettel content, how it should be interpreted, how it is sorted within Zettelstore. The zettel content is, well, the actual content. In many cases, the content is in plain text form. @@ -40,10 +40,10 @@ It can be presented in various [[encodings|00001012920500]].[^The [[zmk encoding|00001012920522]] allows you to compare the plain, the parsed, and the evaluated form of a zettel.] However, a zettel such as this one you are currently reading, is a ""[[evaluated zettel|00001012053500]]"", also retrieved with the [[endpoint|00001012920000]] ''/z/{ID}'' and specifying an encoding. The biggest difference to a parsed zettel is the inclusion of [[block transclusions|00001007031100]] or [[inline transclusions|00001007040324]] for an evaluated zettel. It can also be presented in various encoding, including the ""zmk"" encoding. -Evaluations also applies to metadata of a zettel, if appropriate. +Evaluations also applies to metadata of a zettel, when appropriate. Please note, that searching for content is based on parsed zettel. Transcluded content will only be found in transcluded zettel, but not in the zettel that transcluded the content. -However, you will easily pick up that zettel by follow the [[backward|00001006020000#backward]] metadata key of the transcluded zettel. +However, you will easily pick up that zettel by following the [[backward|00001006020000#backward]] metadata key of the transcluded zettel. Index: docs/manual/00001006020000.zettel ================================================================== --- docs/manual/00001006020000.zettel +++ docs/manual/00001006020000.zettel @@ -2,11 +2,11 @@ title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 -modified: 20241118175033 +modified: 20250115163835 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]]. @@ -31,30 +31,24 @@ If you create a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value. 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. + 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 a 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. ; [!expire|''expire''] : A user-entered time stamp that document the point in time when the zettel should expire. - When a zettel is expires, Zettelstore does nothing. + When a zettel expires, Zettelstore does nothing. It is up to you to define required actions. ''expire'' is just a documentation. You could define a query and execute it regularly, for example [[query:expire? ORDER expire]]. Alternatively, a Zettelstore client software could define some actions when it detects expired zettel. ; [!folge|''folge''] @@ -89,13 +83,13 @@ : References the zettel that contains a previous version of the content. In contrast to [[''precursor''|#precurso]] / [[''folge''|#folge]], this is a reference because of technical reasons, not because of content-related reasons. Basically the inverse of key [[''successors''|#successors]]. ; [!prequel|''prequel''] : Specifies a zettel that is conceptually a prequel zettel. - This is a zettel that occured somehow before the current zettel. + This is a zettel that occurred somehow before the current zettel. ; [!published|''published''] -: This property contains the timestamp of the mast modification / creation of the zettel. +: This property contains the timestamp of the last modification / creation of the zettel. If [[''modified''|#modified]] is set with a valid timestamp, it contains the its value. Otherwise, if [[''created''|#created]] is set with a valid timestamp, it contains the its value. Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used. In all other cases, this property is not set. @@ -114,19 +108,23 @@ Can be used for selecting zettel. See [[supported zettel roles|00001006020100]]. If not given, it is ignored. ; [!sequel|''sequel''] : Is a property that contains identifier of all zettel that reference this zettel through the [[''prequel''|#prequel]] value. +; [!subordinates|''subordinates''] +: Is a property that contains identifier of all zettel that reference this zettel through the [[''superior''|#superior]] value. ; [!successors|''successors''] : Is a property that contains identifier of all zettel that reference this zettel through the [[''predecessor''|#predecessor]] value. Therefore, it references all zettel that contain a new version of the content and/or metadata. In contrast to [[''folge''|#folge]], these are references because of technical reasons, not because of content-related reasons. In most cases, zettel referencing the current zettel should be updated to reference a successor zettel. The [[query reference|00001007040310]] [[query:backward? successors?]] lists all such zettel. ; [!summary|''summary''] -: Summarizes the content of the zettel. - You may use all [[inline-structued elements|00001007040000]] of Zettelmarkup. +: Summarizes the content of the zettel using plain text. +; [!superior|''superior''] +: Specifies a zettel that is conceptually a superior zettel. + This might be a more abstract zettel, or a zettel that should be higher in a hierarchy. ; [!syntax|''syntax''] : Specifies the syntax that should be used for interpreting the zettel. The zettel about [[other markup languages|00001008000000]] defines supported values. If it is not given, it defaults to ''plain''. ; [!tags|''tags''] @@ -134,11 +132,11 @@ Each Tag must begin with the number sign character (""''#''"", U+0023). ; [!title|''title''] : Specifies the title of the zettel. If not given, the value of [[''id''|#id]] will be used. ; [!url|''url''] -: Defines an URL / URI for this zettel that possibly references external material. +: Defines a 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]]. Index: docs/manual/00001006020100.zettel ================================================================== --- docs/manual/00001006020100.zettel +++ docs/manual/00001006020100.zettel @@ -2,11 +2,11 @@ title: Supported Zettel Roles role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 -modified: 20231129173620 +modified: 20250102175032 The [[''role'' key|00001006020000#role]] defines what kind of zettel you are writing. You are free to define your own roles. It is allowed to set an empty value or to omit the role. @@ -18,14 +18,14 @@ ; [!manual|''manual''] : All zettel that document the inner workings of the Zettelstore software. This role is only used in this specific Zettelstore. ; [!role|''role''] : A zettel with the role ""role"" and a title, which names a [[role|00001006020000#role]], is treated as a __role zettel__. - Basically, role zettel describe the role, and form a hierarchiy of meta-roles. + Basically, role zettel describe the role, and form a hierarchy of meta-roles. ; [!tag|''tag''] : A zettel with the role ""tag"" and a title, which names a [[tag|00001006020000#tags]], is treated as a __tag zettel__. - Basically, tag zettel describe the tag, and form a hierarchiy of meta-tags. + Basically, tag zettel describe the tag, and form a hierarchy of meta-tags. ; [!zettel|''zettel''] : A zettel that contains your own thoughts. The real reason to use this software. If you adhere to the process outlined by Niklas Luhmann, a zettel could have one of the following three roles: Index: docs/manual/00001006020400.zettel ================================================================== --- docs/manual/00001006020400.zettel +++ docs/manual/00001006020400.zettel @@ -2,41 +2,33 @@ title: Supported values for metadata key ''read-only'' role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 -modified: 20211124132040 +modified: 20250102205707 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]]. +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. === No authentication -If there is no metadata value for key ''read-only'' or if its [[boolean value|00001006030500]] -is interpreted as ""false"", anybody can modify the zettel. +If there is no metadata value for key ''read-only'' or if its [[boolean value|00001006030500]] is interpreted as ""false"", anybody can modify the zettel. -If the metadata value is something else (the value ""true"" is recommended), -the user cannot modify the zettel through the [[web user interface|00001014000000]]. -However, if the zettel is stored as a file in a [[directory box|00001004011400]], -the zettel could be modified using an external editor. +If the metadata value is something else (the value ""true"" is recommended), the user cannot modify the zettel through the [[web user interface|00001014000000]]. +However, if the zettel is stored as a file in a [[directory box|00001004011400]], the zettel could be modified using an external editor. === Authentication enabled -If there is no metadata value for key ''read-only'' or if its [[boolean value|00001006030500]] -is interpreted as ""false"", anybody can modify the zettel. +If there is no metadata value for key ''read-only'' or if its [[boolean value|00001006030500]] is interpreted as ""false"", anybody can modify the zettel. -If the metadata value is the same as an explicit [[user role|00001010070300]], -users with that role (or a role with lower rights) are not allowed to modify the zettel. +If the metadata value is the same as an explicit [[user role|00001010070300]], users with that role (or a role with lower rights) are not allowed to modify the zettel. ; ""reader"" : Neither an unauthenticated user nor a user with role ""reader"" is allowed to modify the zettel. Users with role ""writer"" or the owner itself still can modify the zettel. ; ""writer"" : Neither an unauthenticated user, nor users with roles ""reader"" or ""writer"" are allowed to modify the zettel. Only the owner of the Zettelstore can modify the zettel. -If the metadata value is something else (one of the values ""true"" or ""owner"" is recommended), -no user is allowed modify the zettel through the [[web user interface|00001014000000]]. -However, if the zettel is accessible as a file in a [[directory box|00001004011400]], -the zettel could be modified using an external editor. -Typically the owner of a Zettelstore have such an access. +If the metadata value is something else (one of the values ""true"" or ""owner"" is recommended), no user is allowed to modify the zettel through the [[web user interface|00001014000000]]. +However, if the zettel is accessible as a file in a [[directory box|00001004011400]], the zettel could be modified using an external editor. +Typically the owner of a Zettelstore has such access. Index: docs/manual/00001006030000.zettel ================================================================== --- docs/manual/00001006030000.zettel +++ docs/manual/00001006030000.zettel @@ -2,11 +2,11 @@ title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 -modified: 20240219161909 +modified: 20250115172354 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. @@ -13,11 +13,10 @@ |=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]] @@ -36,6 +35,5 @@ * [[String|00001006033500]] * [[TagSet|00001006034000]] * [[Timestamp|00001006034500]] * [[URL|00001006035000]] * [[Word|00001006035500]] -* [[Zettelmarkup|00001006036500]] Index: docs/manual/00001006031500.zettel ================================================================== --- docs/manual/00001006031500.zettel +++ docs/manual/00001006031500.zettel @@ -2,13 +2,13 @@ title: EString Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 -modified: 20230419175525 +modified: 20250102164729 -Values of this type are just a sequence of character, possibly an empty sequence. +Values of this type are just a sequence of characters, possibly an empty sequence. An EString is the most general metadata key type, as it places no restrictions to the character sequence.[^Well, there are some minor restrictions that follow from the [[metadata syntax|00001006010000]].] === Allowed values All printable characters are allowed. Index: docs/manual/00001006033000.zettel ================================================================== --- docs/manual/00001006033000.zettel +++ docs/manual/00001006033000.zettel @@ -2,21 +2,21 @@ title: Number Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 -modified: 20230612183900 +modified: 20250102220057 Values of this type denote a numeric integer value. === Allowed values Must be a sequence of digits (""0""--""9""), optionally prefixed with a ""-"" or a ""+"" character. === Query comparison [[Search operators|00001007705000]] for equality (""equal"" or ""not equal"", ""has"" or ""not has""), for lesser values (""less"" or ""not less""), or for greater values (""greater"" or ""not greater"") are executed by converting both the [[search value|00001007706000]] and the metadata value into integer values and then comparing them numerically. Integer values must be in the range -9223372036854775808 … 9223372036854775807. -Comparisons with metadata values outside this range always returns a negative match. +Comparisons with metadata values outside this range always return a negative match. Comparisons with search values outside this range will be executed as a comparison of the string representation values. All other comparisons (""match"", ""not match"", ""prefix"", ""not prefix"", ""suffix"", and ""not suffix"") are done on the given string representation of the number. In this case, the number ""+12"" will be treated as different to the number ""12"". Index: docs/manual/00001006034000.zettel ================================================================== --- docs/manual/00001006034000.zettel +++ docs/manual/00001006034000.zettel @@ -2,18 +2,18 @@ title: TagSet Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 -modified: 20230419175642 +modified: 20250102205826 Values of this type denote a (sorted) set of tags. A set is different to a list, as no duplicate values are allowed. === Allowed values -Every tag must must begin with the number sign character (""''#''"", U+0023), followed by at least one printable character. +Every tag must begin with the number sign character (""''#''"", U+0023), followed by at least one printable character. Tags are separated by space characters. All characters are mapped to their lower case values. === Query comparison Index: docs/manual/00001006035000.zettel ================================================================== --- docs/manual/00001006035000.zettel +++ docs/manual/00001006035000.zettel @@ -2,16 +2,16 @@ title: URL Key Type role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210212135017 -modified: 20230419175725 +modified: 20250102205855 -Values of this type denote an URL. +Values of this type denote a URL. === Allowed values -All characters of an URL / URI are allowed. +All characters of a URL / URI are allowed. === Query comparison All comparisons are done case-insensitive. For example, ""hello"" is the suffix of ""http://example.com/Hello"". DELETED docs/manual/00001006036500.zettel Index: docs/manual/00001006036500.zettel ================================================================== --- docs/manual/00001006036500.zettel +++ /dev/null @@ -1,27 +0,0 @@ -id: 00001006036500 -title: Zettelmarkup Key Type -role: manual -tags: #manual #meta #reference #zettel #zettelstore -syntax: zmk -created: 20210212135017 -modified: 20230419175441 - -Values of this type are [[String|00001006033500]] values, interpreted as [[Zettelmarkup|00001007000000]]. - -=== Allowed values -All printable characters are allowed. -There must be at least one such character. - -=== Query comparison -Comparison is done similar to the full-text search: both the value to compare and the metadata value are normalized according to Unicode NKFD, ignoring everything except letters and numbers. -Letters are mapped to the corresponding lower-case value. - -For example, ""Brücke"" will be the prefix of ""(Bruckenpfeiler,"". - -=== Sorting -To sort two values, the underlying encoding is used to determine which value is less than the other. - -Uppercase letters are typically interpreted as less than their corresponding lowercase letters, i.e. ``A < a``. - -Comparison is done character-wise by finding the first difference in the respective character sequence. -For example, ``abc > aBc``. Index: docs/manual/00001006050000.zettel ================================================================== --- docs/manual/00001006050000.zettel +++ docs/manual/00001006050000.zettel @@ -2,29 +2,26 @@ title: Zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20241128141443 +modified: 20250102165749 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. 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. +They resemble a timestamp: the first four digits could represent the year, the next two represent the month, followed by day, hour, minute, and second. 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. +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"". +Zettel identifier of this manual have been 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. +A zettel can have any identifier that contains 14 digits and that is not in use by another zettel managed by the same Zettelstore. Index: docs/manual/00001006055000.zettel ================================================================== --- docs/manual/00001006055000.zettel +++ docs/manual/00001006055000.zettel @@ -2,18 +2,18 @@ title: Reserved zettel identifier role: manual tags: #design #manual #zettelstore syntax: zmk created: 20210721105704 -modified: 20241202100917 +modified: 20250102222416 -[[Zettel identifier|00001006050000]] are typically created by examine the current date and time. +[[Zettel identifier|00001006050000]] are typically created by examining the current date and time. By renaming the name of the underlying zettel file, you are able to provide any sequence of 14 digits. 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.]. +All zettel provided by an empty zettelstore begin with six zeroes[^Exception: the predefined home zettel is ''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|00000999999999]] (''00000999999999'') 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''. @@ -28,6 +28,6 @@ | 00001100000000 | 00008999999999 | Reserved, do not use | 00009000000000 | 00009999999999 | Reserved for applications ==== External Applications |= From | To | Description -| 00009000001000 | 00009000001999 | [[Zettel Presenter|https://zettelstore.de/contrib]], an application to display zettel as a HTML-based slideshow +| 00009000001000 | 00009000001999 | [[Zettel Presenter|https://zettelstore.de/contrib]], an application to display zettel as an HTML-based slideshow Index: docs/manual/00001007010000.zettel ================================================================== --- docs/manual/00001007010000.zettel +++ docs/manual/00001007010000.zettel @@ -2,31 +2,31 @@ title: Zettelmarkup: General Principles role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 -modified: 20241213101524 +modified: 20250106174703 -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. +Any document can be thought of 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. List blocks also begin at the first position of a line, but may need one or more identical character, plus a space character. [[Table blocks|00001007031000]] begin at the first position of a line with the character ""``|``"". Non-list blocks are either fully specified on that line or they span multiple lines and are delimited with the same three or more character. It depends on the block kind, whether blocks are specified on one line or on at least two lines. -If a line does not begin with an explicit block element. the line is treated as a (implicit) paragraph block element that contains inline elements. +If a line does not begin with an explicit block element, the line is treated as a (implicit) paragraph block element that contains inline elements. This paragraph ends when a block element is detected at the beginning of a next line or when an empty line occurs. Some blocks may also contain inline elements, e.g. a heading. -Inline elements mostly begins with two non-space, often identical characters. +Inline elements mostly begin with two non-space, often identical characters. With some exceptions, two identical non-space characters begins a formatting range that is ended with the same two characters. Exceptions are: links, images, edits, comments, and both the ""en-dash"" and the ""horizontal ellipsis"". -A link is given with ``[[...]]``{=zmk}, an images with ``{{...}}``{=zmk}, and an edit formatting with ``((...))``{=zmk}. +A link is given with ``[[...]]``{=zmk}, an image with ``{{...}}``{=zmk}, and an edit formatting with ``((...))``{=zmk}. An inline comment, beginning with the sequence ``%%``{=zmk}, always ends at the end of the line where it begins. The ""en-dash"" (""--"") is specified as ``--``{=zmk}, the ""horizontal ellipsis"" (""..."") as ``...``{=zmk}[^If put at the end of non-space text.]. Some inline elements do not follow the rule of two identical character, especially to specify [[footnotes|00001007040330]], [[citation keys|00001007040340]], and local marks. These elements begin with one opening square bracket (""``[``""), use a character for specifying the kind of the inline, typically allow to specify some content, and end with one closing square bracket (""``]``""). @@ -37,11 +37,11 @@ For example, an ""n-dash"" could also be specified as ``–``{==zmk}. The backslash character (""``\\``"") possibly gives the next character a special meaning. This allows to resolve some left ambiguities. For example, a list of depth 2 will begin a line with ``** Item 2.2``{=zmk}. -An inline element to strongly emphasize some text begin with a space will be specified as ``** Text**``{=zmk}. +An inline element to strongly emphasize some text that begins with a space will be specified as ``** Text**``{=zmk}. To force the inline element formatting at the beginning of a line, ``**\\ Text**``{=zmk} should better be specified. Many block and inline elements can be refined by additional [[attributes|00001007050000]]. Attributes resemble roughly HTML attributes and are put near the corresponding elements by using the syntax ``{...}``{=zmk}. One example is to make space characters visible inside a inline literal element: ``1 + 2 = 3``{-} was specified by using the default attribute: ``\`\`1 + 2 = 3\`\`{-}``. @@ -49,13 +49,13 @@ To summarize: * With some exceptions, block-structural elements begins at the for position of a line with three identical characters. * The most important exception to this rule is the specification of lists. * If no block element is found, a paragraph with inline elements is assumed. -* With some exceptions, inline-structural elements begins with two characters, quite often the same two characters. +* With some exceptions, inline-structural elements begin with two characters, quite often the same two characters. * The most important exceptions are links. * The backslash character can help to resolve possible ambiguities. * Attributes refine some block and inline elements. * Block elements have a higher priority than inline elements. -These principles makes automatic recognizing zettelmarkup an (relatively) easy task. -By looking at the reference implementation, a moderately skilled software developer should be able to create a appropriate software in a different programming language. +These principles make automatic recognizing zettelmarkup an (relatively) easy task. +By looking at the reference implementation, a moderately skilled software developer should be able to create an appropriate software in a different programming language. Index: docs/manual/00001007030100.zettel ================================================================== --- docs/manual/00001007030100.zettel +++ docs/manual/00001007030100.zettel @@ -2,14 +2,14 @@ title: Zettelmarkup: Description Lists role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 -modified: 20220218131155 +modified: 20250102180137 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. +Every term can be 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. The description of a term is given with one colon (""'':''"", U+003A) at the first position, followed by a space character and the description itself, specified as a sequence of [[inline elements|00001007040000]]. Index: docs/manual/00001007030300.zettel ================================================================== --- docs/manual/00001007030300.zettel +++ docs/manual/00001007030300.zettel @@ -2,11 +2,11 @@ title: Zettelmarkup: Headings role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 -modified: 20220218133755 +modified: 20250102210039 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]]. @@ -28,11 +28,11 @@ ======== Level 5 Heading ::: === Notes -The heading level is translated to a HTML heading by adding 1 to the level, e.g. ``=== Level 1 Heading``{=zmk} translates to ==

Level 1 Heading

=={=html}. +The heading level is translated to an HTML heading by adding 1 to the level, e.g. ``=== Level 1 Heading``{=zmk} translates to ==

Level 1 Heading

=={=html}. The ==

=={=html} tag is rendered for the zettel title. This syntax is often used in a similar way in wiki implementation. However, trailing equal signs are __not__ removed, they are part of the heading text. Index: docs/manual/00001007030400.zettel ================================================================== --- docs/manual/00001007030400.zettel +++ docs/manual/00001007030400.zettel @@ -2,18 +2,18 @@ title: Zettelmarkup: Horizontal Rules / Thematic Break role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 -modified: 20220825185533 +modified: 20250102220220 To signal a thematic break, you can specify a horizontal rule. This is done by entering at least three hyphen-minus characters (""''-''"", U+002D) at the first position of a line. You can add some [[attributes|00001007050000]], although the horizontal rule does not support the default attribute. Any other characters in this line will be ignored. -If you do not enter the three hyphen-minus character at the very first position of a line, the are interpreted as [[inline elements|00001007040000]], typically as an ""en-dash" followed by a hyphen-minus. +If you do not enter the three hyphen-minus character at the very first position of a line, they are interpreted as [[inline elements|00001007040000]], typically as an ""en-dash"" followed by a hyphen-minus. Example: ```zmk --- Index: docs/manual/00001007030800.zettel ================================================================== --- docs/manual/00001007030800.zettel +++ docs/manual/00001007030800.zettel @@ -2,13 +2,13 @@ title: Zettelmarkup: Region Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 -modified: 20220323190829 +modified: 20250102180106 -Region blocks does not directly have a visual representation. +Region blocks do 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. This kind of line-range block begins with at least three colon characters (""'':''"", U+003A) at the first position of a line[^Since a [[description text|00001007030100]] only use exactly one colon character at the first position of a line, there is no possible ambiguity between these elements.]. Index: docs/manual/00001007030900.zettel ================================================================== --- docs/manual/00001007030900.zettel +++ docs/manual/00001007030900.zettel @@ -2,11 +2,11 @@ title: Zettelmarkup: Comment Blocks role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 -modified: 20230807170858 +modified: 20250102222357 Comment blocks are quite similar to [[verbatim blocks|00001007030500]]: both are used to enter text that should not be interpreted. While the text entered inside a verbatim block will be processed somehow, text inside a comment block will be ignored[^Well, not completely ignored: text is read, but it will typically not rendered visible.]. Comment blocks are typically used to give some internal comments, e.g. the license of a text or some internal remarks. @@ -32,6 +32,6 @@ ```zmk %%%{-} Will be rendered %%% ``` -will be rendered as some kind of comment[^This cannot be shown here, because a HTML comment will not be rendered visible; it will be in the HTML text.]. +will be rendered as some kind of comment[^This cannot be shown here, because an HTML comment will not be rendered visible; it will be in the HTML text.]. Index: docs/manual/00001007031000.zettel ================================================================== --- docs/manual/00001007031000.zettel +++ docs/manual/00001007031000.zettel @@ -2,11 +2,11 @@ title: Zettelmarkup: Tables role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 -modified: 20241212153641 +modified: 20250102210107 Tables are used to show some data in a two-dimensional fashion. In zettelmarkup, tables 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 but a sequence of __table cells__. @@ -31,11 +31,11 @@ | b1 | b2 | b3 | c1 | c2 ::: === Header row -If any cell in the first row of a table contains an equal sing character (""''=''"", U+003D) as the very first character, then this first row will be interpreted as a __table header__ row. +If any cell in the first row of a table contains an equal sign character (""''=''"", U+003D) as the very first character, then this first row will be interpreted as a __table header__ row. For example: ```zmk | a1 | a2 |= a3| | b1 | b2 | b3 Index: docs/manual/00001007031110.zettel ================================================================== --- docs/manual/00001007031110.zettel +++ docs/manual/00001007031110.zettel @@ -2,11 +2,11 @@ title: Zettelmarkup: Zettel Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 -modified: 20220926183331 +modified: 20250102165258 A zettel transclusion is specified by the following sequence, starting at the first position in a line: ''{{{zettel-identifier}}}''. When evaluated, the referenced zettel is read. If it contains some transclusions itself, these will be expanded, recursively. @@ -16,11 +16,11 @@ An error message is also given, if the zettel cannot be read or if too many transclusions are made. The maximum number of transclusion can be controlled by setting the value [[''max-transclusions''|00001004020000#max-transclusions]] of the runtime configuration zettel. If everything went well, the referenced, expanded zettel will replace the transclusion element. -For example, to include the text of the Zettel titled ""Zettel identifier"", just specify its identifier [[''00001006050000''|00001006050000]] in the transclude element: +For example, to include the text of the Zettel titled ""Zettel identifier"", just specify its identifier [[''00001006050000''|00001006050000]] in the transclusion element: ```zmk {{{00001006050000}}} ``` This will result in: :::example @@ -35,10 +35,10 @@ To the current user, it seems that there was no transclusion in zettel __z__. This allows to create a zettel with content that seems to be changed, depending on the authorization of the current user. --- Any [[attributes|00001007050000]] added to the transclusion will set/overwrite the appropriate metadata of the included zettel. -Of course, this applies only to thoes attribtues, which have a valid name for a metadata key. +Of course, this applies only to those attributes, which have a valid name for a metadata key. This allows to control the evaluation of the included zettel, especially for zettel containing a diagram description. === See also [[Inline-mode transclusion|00001007040324]] does not work at the paragraph / block level, but is used for [[inline-structured elements|00001007040000]]. Index: docs/manual/00001007031140.zettel ================================================================== --- docs/manual/00001007031140.zettel +++ docs/manual/00001007031140.zettel @@ -2,11 +2,11 @@ title: Zettelmarkup: Query Transclusion role: manual tags: #manual #search #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 -modified: 20240219161800 +modified: 20241213153229 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]]. @@ -36,19 +36,10 @@ : 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. -; ''TITLE'' (parameter) -: All words following ''TITLE'' are joined together to form a title. - It is used for the ''ATOM'' and ''RSS'' action. -; ''ATOM'' (aggregate) -: 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). Index: docs/manual/00001007031200.zettel ================================================================== --- docs/manual/00001007031200.zettel +++ docs/manual/00001007031200.zettel @@ -2,20 +2,20 @@ title: Zettelmarkup: Inline-Zettel Block role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20220201142439 -modified: 20221018121251 +modified: 20250102183744 An inline-zettel block allows to specify some content with another syntax without creating a new zettel. This is useful, for example, if you want to embed some [[Markdown|00001008010500]] content, because you are too lazy to translate Markdown into Zettelmarkup. Another example is to specify HTML code to use it for some kind of web front-end framework. -As all other [[line-range blocks|00001007030000#line-range-blocks]], an inline-zettel block begins with at least three identical characters, starting at the first position of a line. +Like all other [[line-range blocks|00001007030000#line-range-blocks]], an inline-zettel block begins with at least three identical characters, starting at the first position of a line. For inline-zettel blocks, the at-sign character (""''@''"", U+0040) is used. -You can add some [[attributes|00001007050000]] on the beginning line of a verbatim block, following the initiating characters. +You can add some [[attributes|00001007050000]] to the beginning line of a verbatim block, following the initiating characters. The inline-zettel block uses the attribute key ""syntax"" to specify the [[syntax|00001008000000]] of the inline-zettel. Alternatively, you can use the generic attribute to specify the syntax value. If no value is provided, ""[[text|00001008000000#text]]"" is assumed. Any other character in this first line will be ignored. Index: docs/manual/00001007040000.zettel ================================================================== --- docs/manual/00001007040000.zettel +++ docs/manual/00001007040000.zettel @@ -2,24 +2,24 @@ title: Zettelmarkup: Inline-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 -modified: 20220920143243 +modified: 20250102182659 -Most characters you type is concerned with inline-structured elements. -The content of a zettel contains is many cases just ordinary text, lightly formatted. +Most characters you type are concerned with inline-structured elements. +The content of a zettel contains in many cases just ordinary text, lightly formatted. Inline-structured elements allow to format your text and add some helpful links or images. Sometimes, you want to enter characters that have no representation on your keyboard. ; Text formatting : Every [[text formatting|00001007040100]] element begins with two same characters at the beginning. It lasts until the same two characters occurred the second time. Some of these elements explicitly support [[attributes|00001007050000]]. ; Literal-like formatting -: Sometime you want to enter the text as it is. +: Sometimes, you want to enter the text as it is. : This is the core motivation of [[literal-like formatting|00001007040200]]. ; Reference-like text : You can reference other zettel and (external) material within one zettel. This kind of reference may be a link, or an images that is display inline when the zettel is rendered. @@ -33,21 +33,21 @@ A comment begins with two consecutive percent sign characters (""''%''"", U+0025). It ends at the end of the line where it begins. ==== Backslash The backslash character (""''\\''"", U+005C) gives the next character another meaning. -* If a space character follows, it is converted in a non-breaking space (U+00A0). +* If a space character follows, it is converted into a non-breaking space (U+00A0). * If a line ending follows the backslash character, the line break is converted from a __soft break__ into a __hard break__. * Every other character is taken as itself, but without the interpretation of a Zettelmarkup element. For example, if you want to enter a ""'']''"" into a [[footnote text|00001007040330]], you should escape it with a backslash. ==== Entities & more Sometimes it is not easy to enter special characters. If you know the Unicode code point of that character, or its name according to the [[HTML standard|https://html.spec.whatwg.org/multipage/named-characters.html]], you can enter it by number or by name. Regardless which method you use, an entity always begins with an ampersand character (""''&''"", U+0026) and ends with a semicolon character (""'';''"", U+003B). -If you know the HTML name of the character you want to enter, put it between these two character. +If you know the HTML name of the character you want to enter, put it between these two characters. Example: ``&`` is rendered as ::&::{=example}. If you want to enter its numeric code point, a number sign character must follow the ampersand character, followed by digits to base 10. Example: ``&`` is rendered in HTML as ::&::{=example}. @@ -55,11 +55,11 @@ Example: ``&`` is rendered in HTML as ::&::{=example}. According to the [[HTML Standard|https://html.spec.whatwg.org/multipage/syntax.html#character-references]], some numeric code points are not allowed. These are all code point below the numeric value 32 (decimal) or 0x20 (hex) and all code points for [[noncharacter|https://infra.spec.whatwg.org/#noncharacter]] values. -Since some Unicode character are used quite often, a special notation is introduced for them: +Since some Unicode characters are used quite often, a special notation is introduced for them: * Two consecutive hyphen-minus characters result in an __en-dash__ character. It is typically used in numeric ranges. ``pages 4--7`` will be rendered in HTML as: ::pages 4--7::{=example}. Alternative specifications are: ``–``, ``&x8211``, and ``–``. Index: docs/manual/00001007040100.zettel ================================================================== --- docs/manual/00001007040100.zettel +++ docs/manual/00001007040100.zettel @@ -2,14 +2,14 @@ title: Zettelmarkup: Text Formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 -modified: 20231113191353 +modified: 20250106174436 Text formatting is the way to make your text visually different. -Every text formatting element begins with two same characters. +Every text formatting element begins with two identical characters. It ends when these two same characters occur the second time. It is possible that some [[attributes|00001007050000]] follow immediately, without any separating character. Text formatting can be nested, up to a reasonable limit. @@ -19,11 +19,11 @@ ** Example: ``abc __def__ ghi`` is rendered in HTML as: ::abc __def__ ghi::{=example}. * The asterisk character (""''*''"", U+002A) strongly emphasized its enclosed text. ** Example: ``abc **def** ghi`` is rendered in HTML as: ::abc **def** ghi::{=example}. * The greater-than sign character (""''>''"", U+003E) marks text as inserted. ** Example: ``abc >>def>> ghi`` is rendered in HTML as: ::abc >>def>> ghi::{=example}. -* Similar, the tilde character (""''~''"", U+007E) marks deleted text. +* Similarly, the tilde character (""''~''"", U+007E) marks deleted text. ** Example: ``abc ~~def~~ ghi`` is rendered in HTML as: ::abc ~~def~~ ghi::{=example}. * The circumflex accent character (""''^''"", U+005E) allows to enter super-scripted text. ** Example: ``e=mc^^2^^`` is rendered in HTML as: ::e=mc^^2^^::{=example}. * The comma character (""'',''"", U+002C) produces sub-scripted text. ** Example: ``H,,2,,O`` is rendered in HTML as: ::H,,2,,O::{=example}. @@ -31,7 +31,7 @@ ** Example: ``""To be or not""`` is rendered in HTML as: ::""To be or not""::{=example}. ** Example: ``""Sein oder nicht""{lang=de}`` is rendered in HTML as: ::""Sein oder nicht""{lang=de}::{=example}. * The number sign (""''#''"", U+0023) marks the text visually, where the mark does not belong to the text itself. It is typically used to highlight some text that is important for you, but was not important for the original author. ** Example: ``abc ##def## ghi`` is rendered in HTML as: ::abc ##def## ghi::{=example}. -* The colon character (""'':''"", U+003A) mark some text that should belong together. It fills a similar role as [[region blocks|00001007030800]], but just for inline elements. +* The colon character (""'':''"", U+003A) marks some text that should belong together. It fills a similar role as [[region blocks|00001007030800]], but just for inline elements. ** Example: ``abc ::def::{=example} ghi`` is rendered in HTML as: abc ::def::{=example} ghi. Index: docs/manual/00001007040200.zettel ================================================================== --- docs/manual/00001007040200.zettel +++ docs/manual/00001007040200.zettel @@ -2,11 +2,11 @@ title: Zettelmarkup: Literal-like formatting role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 -modified: 20220311185110 +modified: 20250115180425 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. @@ -45,28 +45,14 @@ * ``==The result is: 42==`` renders in HTML as ::==The result is: 42==::{=example}. * ``==The result is: 42=={-}`` renders in HTML as ::==The result is: 42=={-}::{=example}. Attributes can be specified, the default attribute has the same semantic as for literal text. -=== Inline-zettel snippet -To specify an inline snippet in a different [[syntax|00001008000000]], delimit your text with two at-sign characters (""''@''"", U+0040) on each side. - -You can add some [[attributes|00001007050000]] immediate after the two closing at-sign characters to specify the syntax to use. -Either use the attribute key ""syntax"" or use the generic attribute to specify the syntax value. -If no value is provided, ""[[text|00001008000000#text]]"" is assumed. - -Examples: -* ``A @@-->@@ B`` renders in HTML as ::A @@-->@@ B::{=example}. -* ``@@@@{=html}Small@@@@{=html}`` renders in HTML as ::@@@@{=html}Small@@@@{=html}::{=example}. - -To some degree, an inline-zettel snippet is the @@@@{=html}smaller@@@@{=html} sibling of the [[inline-zettel block|00001007031200]]. -For HTML syntax, the same rules apply. - === Math mode / $$\TeX$$ input This allows to enter text, that is typically interpreted by $$\TeX$$ or similar software. The main difference to all other literal-like formatting above is that the backslash character (""''\\''"", U+005C) has no special meaning. -Therefore it is well suited the enter text with a lot of backslash characters. +Therefore it is well suited to enter text with a lot of backslash characters. Math mode text is delimited with two dollar signs (""''$''"", U+0024) on each side. You can add some [[attributes|00001007050000]] immediate after the two closing at-sign characters to specify the syntax to use. Either use the attribute key ""syntax"" or use the generic attribute to specify the syntax value. Index: docs/manual/00001007040310.zettel ================================================================== --- docs/manual/00001007040310.zettel +++ docs/manual/00001007040310.zettel @@ -2,14 +2,14 @@ title: Zettelmarkup: Links role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210810155955 -modified: 20221024173849 +modified: 20250313140836 There are two kinds of links, regardless of links to (internal) other zettel or to (external) material. -Both kinds begin with two consecutive left square bracket characters (""''[''"", U+005B) and ends with two consecutive right square bracket characters (""'']''"", U+005D). +Both kinds begin with two consecutive left square bracket characters (""''[''"", U+005B) and end with two consecutive right square bracket characters (""'']''"", U+005D). If the content starts with more than two left square bracket characters, all but the last two will be treated as text. The first form provides some text plus the link specification, delimited by a vertical bar character (""''|''"", U+007C): ``[[text|linkspecification]]``. The text is a sequence of [[inline elements|00001007040000]]. However, it should not contain links itself. @@ -22,17 +22,20 @@ To reference some content within a zettel, you can append a number sign character (""''#''"", U+0023) and the name of the mark to the zettel identifier. The resulting reference is called ""zettel reference"". If the link specification begins with the string ''query:'', the text following this string will be interpreted as a [[query expression|00001007700000]]. The resulting reference is called ""query reference"". -When this type of references is rendered, it will typically reference a list of all zettel that fulfills the query expression. +When this type of reference is rendered, it will typically reference a list of all zettel that fulfills the query expression. A link specification starting with one slash character (""''/''"", U+002F), or one or two full stop characters (""''.''"", U+002E) followed by a slash character, will be interpreted as a local reference, called __hosted reference__. Such references will be interpreted relative to the web server hosting the Zettelstore. If a link specification begins with two slash characters (called __based reference__), it will be interpreted relative to the value of [[''url-prefix''|00001004010000#url-prefix]]. -To specify some material outside the Zettelstore, just use an normal Uniform Resource Identifier (URI) as defined by [[RFC\ 3986|https://tools.ietf.org/html/rfc3986]]. +To specify some material outside the Zettelstore, just use a normal Uniform Resource Identifier (URI) as defined by [[RFC\ 3986|https://tools.ietf.org/html/rfc3986]]. === Other topics -If the link references another zettel, and this zettel is not readable for the current user, because of a missing access rights, then only the associated text is presented. +If the link references another zettel, and this zettel is not readable for the current user, e.g. because of missing access rights, then only the associated text is presented. + +A zettel reference to a non-existing zettel, i.e. using a legal zettel identifier that does not identify an existing zettel, the link text will be shown strikethrough. +For example, ''[[Missing zettel|99999999999999]]'' will be rendered in HTML as ::[[Missing zettel|99999999999999]]::{=example}. Index: docs/manual/00001007040320.zettel ================================================================== --- docs/manual/00001007040320.zettel +++ docs/manual/00001007040320.zettel @@ -2,22 +2,22 @@ title: Zettelmarkup: Inline Embedding / Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210810155955 -modified: 20221024173926 +modified: 20250102210158 -To some degree, an specification for embedded material is conceptually not too far away from a specification for [[linked material|00001007040310]]. +To some degree, a specification for embedded material is conceptually not too far away from a specification for [[linked material|00001007040310]]. Both contain a reference specification and optionally some text. In contrast to a link, the specification of embedded material must currently resolve to some kind of real content. This content replaces the embed specification. An embed specification begins with two consecutive left curly bracket characters (""''{''"", U+007B) and ends with two consecutive right curly bracket characters (""''}''"", U+007D). The curly brackets delimits either a reference specification or some text, a vertical bar character and the link specification, similar to a link. If the content starts with more than two left curly bracket characters, all but the last two will be treated as text. -One difference to a link: if the text was not given, an empty string is assumed. +One difference to a link: if the text is not given, an empty string is assumed. The reference must point to some content, either zettel content or URL-referenced content. If the current user is not allowed to read the referenced zettel, the inline transclusion / embedding is ignored. If the referenced zettel does not exist, or is not readable because of other reasons, a [[spinning emoji|00000000040001]] is presented as a visual hint: Index: docs/manual/00001007040322.zettel ================================================================== --- docs/manual/00001007040322.zettel +++ docs/manual/00001007040322.zettel @@ -2,13 +2,13 @@ title: Zettelmarkup: Image Embedding role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210811154251 -modified: 20241202101206 +modified: 20250102222115 -Image content is assumed, if an URL is used or if the referenced zettel contains an image. +Image content is assumed, if a URL is used or if the referenced zettel contains an image. Supported formats are: * Portable Network Graphics (""PNG""), as defined by [[RFC\ 2083|https://tools.ietf.org/html/rfc2083]]. * Graphics Interchange Format (""GIF"), as defined by [[https://www.w3.org/Graphics/GIF/spec-gif89a.txt]]. Index: docs/manual/00001007040324.zettel ================================================================== --- docs/manual/00001007040324.zettel +++ docs/manual/00001007040324.zettel @@ -2,11 +2,11 @@ title: Zettelmarkup: Inline-mode Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210811154251 -modified: 20231222164501 +modified: 20250102183508 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. @@ -16,11 +16,11 @@ When an endless recursion is detected, expansion does not take place. Instead an error message replaces the transclude specification. The result of this (indirect) transclusion is searched for inline-structured elements. -* If only an [[zettel identifier|00001006050000]] was specified, the first top-level [[paragraph|00001007030000#paragraphs]] is used. +* If only a [[zettel identifier|00001006050000]] was specified, the first top-level [[paragraph|00001007030000#paragraphs]] is used. Since a paragraph is basically a sequence of inline-structured elements, these elements will replace the transclude specification. Example: ``{{00010000000000}}`` (see [[00010000000000]]) is rendered as ::{{00010000000000}}::{=example}. * If a fragment identifier was additionally specified, the element with the given fragment is searched: @@ -31,19 +31,19 @@ ** In case the fragment names a [[mark|00001007040350]], the inline-structured elements after the mark are used. Initial spaces and line breaks are ignored in this case. Example: ``{{00001007040322#spin}}`` is rendered as ::{{00001007040322#spin}}::{=example}. -** Just specifying the fragment identifier will reference something in the current page. +** Just specifying the fragment identifier will reference something on 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. +To avoid an exploding ""transclusion bomb"", a form of [[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]]. Index: docs/manual/00001007040340.zettel ================================================================== --- docs/manual/00001007040340.zettel +++ docs/manual/00001007040340.zettel @@ -2,15 +2,15 @@ title: Zettelmarkup: Citation Key role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210810155955 -modified: 20220218133447 +modified: 20250102210258 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. +However, the syntax is: beginning with a left square bracket and followed by an at sign character (""''@''"", U+0040), the citation key is given. The key is typically a sequence of letters and digits. If a comma character (""'',''"", U+002C) or a vertical bar character is given, the following is interpreted as [[inline elements|00001007040000]]. A right square bracket ends the text and the citation key element. Index: docs/manual/00001007701000.zettel ================================================================== --- docs/manual/00001007701000.zettel +++ docs/manual/00001007701000.zettel @@ -2,13 +2,13 @@ title: Query: Search Expression role: manual tags: #manual #search #zettelstore syntax: zmk created: 20230707205043 -modified: 20230707210039 +modified: 20250102210324 -In its simplest form, a search expression just contains a string to be search for with the help of a full-text search. +In its simplest form, a search expression just contains a string to be searched for with the help of a full-text search. For example, the string ''syntax'' will search for all zettel containing the word ""syntax"". If you want to search for all zettel with a title containing the word ""syntax"", you must specify ''title:syntax''. ""title"" denotes the [[metadata key|00001006010000]], in this case the [[supported metadata key ""title""|00001006020000#title]]. The colon character (""'':''"") is a [[search operator|00001007705000]], in this example to specify a match. Index: docs/manual/00001007702000.zettel ================================================================== --- docs/manual/00001007702000.zettel +++ docs/manual/00001007702000.zettel @@ -2,19 +2,19 @@ title: Search term role: manual tags: #manual #search #zettelstore syntax: zmk created: 20220805150154 -modified: 20230925173539 +modified: 20250102210348 A search term allows you to specify one search restriction. The result [[search expression|00001007700000]], which contains more than one search term, will be the applications of all restrictions. A search term can be one of the following (the first three term are collectively called __search literals__): * A metadata-based search, by specifying the name of a [[metadata key|00001006010000]], followed by a [[search operator|00001007705000]], followed by an optional [[search value|00001007706000]]. - All zettel containing the given metadata key with a allowed value (depending on the search operator) are selected. + All zettel containing the given metadata key with an allowed value (depending on the search operator) are selected. If no search value is given, then all zettel containing the given metadata key are selected (or ignored, for a negated search operator). * An optional [[search operator|00001007705000]], followed by a [[search value|00001007706000]]. This specifies a full-text search for the given search value. Index: docs/manual/00001007705000.zettel ================================================================== --- docs/manual/00001007705000.zettel +++ docs/manual/00001007705000.zettel @@ -2,11 +2,11 @@ title: Search operator role: manual tags: #manual #search #zettelstore syntax: zmk created: 20220805150154 -modified: 20230612180539 +modified: 20250102210427 A search operator specifies how the comparison of a search value and a zettel should be executed. Every comparison is done case-insensitive, treating all uppercase letters the same as lowercase letters. The following are allowed search operator characters: @@ -15,12 +15,12 @@ * The tilde character (""''~''"", U+007E) compares on matching (""match operator""). * The left square bracket character (""''[''"", U+005B) matches if there is some prefix (""prefix operator""). * The right square bracket character (""'']''"", U+005D) compares a suffix relationship (""suffix operator""). * The colon character (""'':''"", U+003A) compares depending on the on the actual [[key type|00001006030000]] (""has operator""). In most cases, it acts as a equals operator, but for some type it acts as the match operator. -* The less-than sign character (""''<''"", U+003C) matches if the search value is somehow less then the metadata value (""less operator""). -* The greater-than sign character (""''>''"", U+003E) matches if the search value is somehow greater then the metadata value (""greater operator""). +* The less-than sign character (""''<''"", U+003C) matches if the search value is somehow less than the metadata value (""less operator""). +* The greater-than sign character (""''>''"", U+003E) matches if the search value is somehow greater than the metadata value (""greater operator""). * The question mark (""''?''"", U+003F) checks for an existing metadata key (""exist operator""). In this case no [[search value|00001007706000]] must be given. Since the exclamation mark character can be combined with the other, there are 18 possible combinations: # ""''!''"": is an abbreviation of the ""''!~''"" operator. Index: docs/manual/00001007720300.zettel ================================================================== --- docs/manual/00001007720300.zettel +++ docs/manual/00001007720300.zettel @@ -2,11 +2,11 @@ title: Query: Context Directive role: manual tags: #manual #search #zettelstore syntax: zmk created: 20230707204706 -modified: 20241118174741 +modified: 20250202172633 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. @@ -14,10 +14,12 @@ * ''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). +* ''MIN'': one or more space characters, and a positive integer: set the minimum number of context zettel (default: 0). + Takes precedence over ''COST'' and ''MAX''. 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. Index: docs/manual/00001007780000.zettel ================================================================== --- docs/manual/00001007780000.zettel +++ docs/manual/00001007780000.zettel @@ -2,11 +2,11 @@ title: Formal syntax of query expressions role: manual tags: #manual #reference #search #zettelstore syntax: zmk created: 20220810144539 -modified: 20240219155949 +modified: 20250202164337 ``` QueryExpression := ZettelList? QueryDirective* SearchExpression ActionExpression? ZettelList := (ZID (SPACE+ ZID)*). ZID := '0'+ ('1' .. '9'') DIGIT* @@ -18,11 +18,12 @@ ContextDirective := "CONTEXT" (SPACE+ ContextDetail)*. ContextDetail := "FULL" | "BACKWARD" | "FORWARD" | "COST" SPACE+ PosInt - | "MAX" SPACE+ PosInt. + | "MAX" SPACE+ PosInt + | "MIN" SPACE+ PosInt. IdentDirective := IDENT. ItemsDirective := ITEMS. UnlinkedDirective := UNLINKED (SPACE+ PHRASE SPACE+ Word)*. SearchExpression := SearchTerm (SPACE+ SearchTerm)*. SearchTerm := SearchOperator? SearchValue @@ -42,16 +43,13 @@ | '!' '?'. 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)* . + | 'REINDEX'. Word := NO-SPACE NO-SPACE* ``` Index: docs/manual/00001007800000.zettel ================================================================== --- docs/manual/00001007800000.zettel +++ docs/manual/00001007800000.zettel @@ -2,11 +2,11 @@ title: Zettelmarkup: Summary of Formatting Characters role: manual tags: #manual #reference #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 -modified: 20241125182149 +modified: 20250115180517 The following table gives an overview about the use of all characters that begin a markup element. |= Character :|= [[Blocks|00001007030000]] <|= [[Inlines|00001007040000]] < | ''!'' | (free) | (free) @@ -28,11 +28,11 @@ | '';'' | [[Description term|00001007030100]] | (free) | ''<'' | [[Quotation block|00001007030600]] | (free) | ''='' | [[Headings|00001007030300]] | [[Computer output|00001007040200]] | ''>'' | [[Quotation lists|00001007030200]] | [[Inserted text|00001007040100]] | ''?'' | (free) | (free) -| ''@'' | [[Inline-Zettel block|00001007031200]] | [[Inline-zettel snippet|00001007040200#inline-zettel-snippet]] +| ''@'' | [[Inline-Zettel block|00001007031200]] | (reserved) | ''['' | (reserved) | [[Linked material|00001007040300]], [[citation key|00001007040300]], [[footnote|00001007040300]], [[mark|00001007040300]] | ''\\'' | (blocked by inline meaning) | [[Escape character|00001007040000]] | '']'' | (reserved) | End of [[link|00001007040300]], [[citation key|00001007040300]], [[footnote|00001007040300]], [[mark|00001007040300]] | ''^'' | (free) | [[Super-scripted text|00001007040100]] | ''_'' | (free) | [[Emphasized text|00001007040100]] Index: docs/manual/00001007906000.zettel ================================================================== --- docs/manual/00001007906000.zettel +++ docs/manual/00001007906000.zettel @@ -2,11 +2,11 @@ title: Zettelmarkup: Second Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk created: 20220811115501 -modified: 20220926183427 +modified: 20250102221931 After you have [[learned|00001007903000]] the basic concepts and markup of Zettelmarkup (paragraphs, emphasized text, and lists), this zettel introduces you into the concepts of links, thematic breaks, and headings. === Links A Zettelstore is much more useful, if you connect related zettel. @@ -18,11 +18,11 @@ * Within these character sequences you specify the [[zettel identifier|00001006050000]] of the zettel you want to reference: ''[[00001007903000]]'' will connect to zettel containing the first steps into Zettelmarkup. * In addition, you should give the link a more readable description. This is done by prepending the description before the reference and use the vertical bar character to separate both: ''[[First Steps|00001007903000]]''. You are not restricted to reference your zettel. -Alternatively, you might specify an URL of an external website: ''[[Zettelstore|https://zettelstore.de]]''. +Alternatively, you might specify a URL of an external website: ''[[Zettelstore|https://zettelstore.de]]''. Of course, if you just want to specify the URL, you are allowed to omit the description: ''[[https://zettelstore.de]]'' |= Zettelmarkup | Rendered output | Remark | ''[[00001007903000]]'' | [[00001007903000]] | If no description is given, the zettel identifier acts as a description | ''[[First Steps|00001007903000]]'' | [[First Steps|00001007903000]] | The description should be chosen so that you are not confused later Index: docs/manual/00001008010500.zettel ================================================================== --- docs/manual/00001008010500.zettel +++ docs/manual/00001008010500.zettel @@ -2,11 +2,11 @@ title: CommonMark role: manual tags: #manual #markdown #zettelstore syntax: zmk created: 20220113183435 -modified: 20221018123145 +modified: 20250115200458 url: https://commonmark.org/ [[CommonMark|https://commonmark.org/]] is a Markdown dialect, an [[attempt|https://xkcd.com/927/]] to unify all the different, divergent dialects of Markdown by providing an unambiguous syntax specification for Markdown, together with a suite of comprehensive tests to validate implementation. Time will show, if this attempt is successful. @@ -14,16 +14,17 @@ However, CommonMark is a well specified Markdown dialect, in contrast to most (if not all) other dialects. Other software adopts CommonMark somehow, notably [[GitHub Flavored Markdown|https://github.github.com/gfm/]] (GFM). But they provide proprietary extensions, which makes it harder to change to another CommonMark implementation if needed. Plus, they sometimes build on an older specification of CommonMark. -Zettelstore supports the latest CommonMark [[specification version 0.30 (2021-06-19)|https://spec.commonmark.org/0.30/]]. +Zettelstore supports the latest CommonMark [[specification version 0.31.2 (2024-01-28)|https://spec.commonmark.org/0.31.2/]]. If possible, Zettelstore will adapt to newer versions when they are available. To provide CommonMark support, Zettelstore uses currently the [[Goldmark|https://github.com/yuin/goldmark]] implementation, which passes all validation tests of CommonMark. -Internally, CommonMark is translated into some kind of super-set of [[Zettelmarkup|00001007000000]], which additionally allows to use HTML code.[^Effectively, Markdown and CommonMark are itself super-sets of HTML.] -This Zettelmarkup super-set is later [[encoded|00001012920500]], often into [[HTML|00001012920510]]. -Because Zettelstore HTML encoding philosophy differs a little bit to that of CommonMark, Zettelstore itself will not pass the CommonMark test suite fully. -However, no CommonMark language element will fail to be encoded as HTML. -In most cases, the differences are not visible for an user, but only by comparing the generated HTML code. +Effectively, Markdown and CommonMark are super-sets of HTML. +Internally, CommonMark is translated into [[Zettelmarkup|00001007000000]]. +Since Zettelmarkup supports HTML content only at the block level via [[Inline Zettel|00001007031200]], most uses of HTML within a CommonMark zettel are not translated as expected. +Instead, inline level HTML is translated into [[literal text|00001007040200#literal-text]] with a [[generic attribute|00001007050000]] set to ``html``. -Be aware, depending on the value of the startup configuration key [[''insecure-html''|00001004010000#insecure-html]], HTML code found within a CommonMark document or within the mentioned kind of super-set of Zettelmarkup will typically be ignored for security-related reasons. +Therefore, Zettelstore itself will not pass the CommonMark test suite fully. +However, no CommonMark language element except inline HTML will fail to be encoded as HTML. +In most cases, the differences are not visible for a user, but only by comparing the generated HTML code. Index: docs/manual/00001010000000.zettel ================================================================== --- docs/manual/00001010000000.zettel +++ docs/manual/00001010000000.zettel @@ -2,11 +2,11 @@ title: Security role: manual tags: #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 -modified: 20241213102811 +modified: 20250102212014 Your zettel may contain sensitive content. You probably want to ensure that only authorized persons can read and/or modify them. Zettelstore ensures this in various ways. @@ -42,27 +42,27 @@ If someone is authenticated as the owner of the Zettelstore (hopefully you), no restrictions apply. But as an owner, you can create ""user zettel"" to allow others to access your Zettelstore in various ways. Even if you do not want to share your Zettelstore with other persons, creating user zettel can be useful if you plan to access your Zettelstore via the [[API|00001012000000]]. Additionally, you can specify that a zettel is publicly visible. -In this case no one has to authenticate itself to see the content of the zettel. +In this case, nobody has to authenticate themselves to see the contents of the note. Or you can specify that a zettel is visible only to the owner. In this case, no authenticated user will be able to read and change that protected zettel. * [[Visibility rules for zettel|00001010070200]] -* [[User roles|00001010070300]] define basic rights of an user +* [[User roles|00001010070300]] define basic rights of a user * [[Authorization and read-only mode|00001010070400]] * [[Access rules|00001010070600]] define the policy which user is allowed to do what operation. === Encryption When Zettelstore is accessed remotely, the messages that are sent between Zettelstore and the client must be encrypted. -Otherwise, an eavesdropper could fetch sensible data, such as passwords or precious content that is not for the public. +Otherwise, an eavesdropper could fetch sensitive data, such as passwords or precious content that is not for the public. The Zettelstore itself does not encrypt messages. But you can put a server in front of it, which is able to handle encryption. -Most generic web server software do allow this. +Most generic web server software allow this. To enforce encryption, [[authenticated sessions|00001010040700]] are marked as secure by default. If you still want to access the Zettelstore remotely without encryption, you must change the startup configuration. Otherwise, authentication will not work. * [[Use a server for encryption|00001010090100]] Index: docs/manual/00001010040200.zettel ================================================================== --- docs/manual/00001010040200.zettel +++ docs/manual/00001010040200.zettel @@ -1,14 +1,14 @@ id: 00001010040200 -title: Creating an user zettel +title: Creating a user zettel role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 -modified: 20221205160251 +modified: 20250102221859 -All data to be used for authenticating a user is store in a special zettel called ""user zettel"". +All data used for authenticating a user is stored in a special zettel called ""user zettel"". A user zettel must have set the following two metadata fields: ; ''user-id'' (""user identification"") : The unique identification to be specified for authentication. ; ''credential'' @@ -26,12 +26,12 @@ A user zettel can only be created by the owner of the Zettelstore. The owner should execute the following steps to create a new user zettel: # Create a new zettel. -# Save the zettel to get a [[identifier|00001006050000]] for this zettel. +# Save the zettel to get an [[identifier|00001006050000]] for this zettel. # Choose a unique identification for the user. #* If the identifier is not unique, authentication will not work for this user. # Execute the [[``zettelstore password``|00001004051400]] command. #* You have to specify the user identification and the zettel identifier #* If you should not know the password of the new user, send her/him the user identification and the user zettel identifier, so that the person can create the hashed password herself. # Edit the user zettel and add the hashed password under the meta key ''credential'' and the user identification under the key ''user-id''. Index: docs/manual/00001010040400.zettel ================================================================== --- docs/manual/00001010040400.zettel +++ docs/manual/00001010040400.zettel @@ -2,13 +2,13 @@ title: Authentication process role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 -modified: 20211127174943 +modified: 20250102222012 -When someone tries to authenticate itself with an user identifier / ""user name"" and a password, the following process is executed: +When someone tries to authenticate itself with a 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. Or in other words: the oldest zettel is selected[^This is done to prevent an attacker from creating a new note with the same user identification]. Index: docs/manual/00001010040700.zettel ================================================================== --- docs/manual/00001010040700.zettel +++ docs/manual/00001010040700.zettel @@ -2,26 +2,26 @@ title: Access token role: manual tags: #authentication #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 -modified: 20241213101607 +modified: 20250102215422 -If an user is authenticated, an ""access token"" is created that must be sent with every request to prove the identity of the caller. +If a 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, these cookies are not saved. -If you want web browser to store the cookie as long as lifetime of that token, the owner must set ''persistent-cookie'' of the [[startup configuration|00001004010000]] to ''true''. +If you want the web browser to store the cookie for the lifetime of that token, the owner must set ''persistent-cookie'' of the [[startup configuration|00001004010000]] to ''true''. If the web browser remains inactive for a period, the user will be automatically logged off, because each access token has a limited lifetime. The maximum length of this period is specified by the ''token-lifetime-html'' value of the startup configuration. Every time a web page is displayed, a fresh token is created and stored inside the cookie. If the user was authenticated via the API, the access token will be returned as the content of the response. -Typically, the lifetime of this token is more short term, e.g. 10 minutes. +Typically, the lifetime of this token is shorter, e.g. 10 minutes. It is specified by the ''token-lifetime-api'' value of the startup configuration. If you need more time, you can either [[re-authenticate|00001012050200]] the user or use an API call to [[renew the access token|00001012050400]]. -If you remotely access your Zettelstore via HTTP (not via HTTPS, which allows encrypted communication), you must set the ''insecure-cookie'' value of the startup configuration to ''true''. +If you remotely access your Zettelstore via HTTP (not via HTTPS, which allows encrypted communication), you must set the ''insecure-cookie'' value in the startup configuration to ''true''. In most cases, such a scenario is not recommended, because user name and password will be transferred as plain text. -You could make use of such scenario if you know all parties that access the local network where you access the Zettelstore. +You could use such a scenario if you know all parties that access the local network where you access the Zettelstore. Index: docs/manual/00001010070200.zettel ================================================================== --- docs/manual/00001010070200.zettel +++ docs/manual/00001010070200.zettel @@ -2,11 +2,11 @@ title: Visibility rules for zettel role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 -modified: 20220923104643 +modified: 20250102170611 For every zettel you can specify under which condition the zettel is visible to others. This is controlled with the metadata key [[''visibility''|00001006020000#visibility]]. The following values are supported: @@ -30,11 +30,11 @@ When you install a Zettelstore, only [[some zettel|query:visibility:public]] have visibility ""public"". One is the zettel that contains [[CSS|00000000020001]] for displaying the [[web user interface|00001014000000]]. This is to ensure that the web interface looks nice even for not authenticated users. Another is the zettel containing the Zettelstore [[license|00000000000004]]. -The [[default image|00000000040001]], used if an image reference is invalid, is also public visible. +The [[default image|00000000040001]], used if an image reference is invalid, is also publicly visible. Please note: if [[authentication is not enabled|00001010040100]], every user has the same rights as the owner of a Zettelstore. This is also true, if the Zettelstore runs additionally in [[read-only mode|00001004010000#read-only-mode]]. In this case, the [[runtime configuration zettel|00001004020000]] is shown (its visibility is ""owner""). The [[startup configuration|00001004010000]] is not shown, because the associated computed zettel with identifier ''00000000000096'' is stored with the visibility ""expert"". Index: docs/manual/00001010070400.zettel ================================================================== --- docs/manual/00001010070400.zettel +++ docs/manual/00001010070400.zettel @@ -2,20 +2,20 @@ title: Authorization and read-only mode role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 -modified: 20211103164251 +modified: 20250102212150 It is possible to enable both the read-only mode of the Zettelstore __and__ authentication/authorization. -Both modes are independent from each other. +Both modes are independent of each other. This gives four use cases: ; Not read-only, no authorization : Zettelstore runs on your local computer and you only work with it. ; Not read-only, with authorization : Zettelstore is accessed remotely. You need authentication to ensure that only valid users access your Zettelstore. ; With read-only, no authorization -: Zettelstore present publicly its full content to everybody. +: Zettelstore presents its full content publicly to everyone. ; With read-only, with authorization -: Nobody is allowed to change the content of the Zettelstore, but only specific zettel should be presented to the public. +: Nobody is allowed to change the content of the Zettelstore, and only specific zettel should be presented to the public. Index: docs/manual/00001012000000.zettel ================================================================== --- docs/manual/00001012000000.zettel +++ docs/manual/00001012000000.zettel @@ -2,11 +2,11 @@ title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20240711183736 +modified: 20250102222042 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. @@ -15,11 +15,11 @@ There is an [[overview zettel|00001012920000]] that shows the structure of the endpoints used by the API and gives an indication about its use. === Authentication If [[authentication is enabled|00001010040100]], most API calls must include an [[access token|00001010040700]] that proves the identity of the caller. -* [[Authenticate an user|00001012050200]] to obtain an access token +* [[Authenticate a user|00001012050200]] to obtain an access token * [[Renew an access token|00001012050400]] without costly re-authentication * [[Provide an access token|00001012050600]] when doing an API call === Zettel lists * [[List all zettel|00001012051200]] Index: docs/manual/00001012050200.zettel ================================================================== --- docs/manual/00001012050200.zettel +++ docs/manual/00001012050200.zettel @@ -2,11 +2,11 @@ title: API: Authenticate a client role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20230412150544 +modified: 20250102215926 Authentication for future API calls is done by sending a [[user identification|00001010040200]] and a password to the Zettelstore to obtain an [[access token|00001010040700]]. This token has to be used for other API calls. It is valid for a relatively short amount of time, as configured with the key ''token-lifetime-api'' of the [[startup configuration|00001004010000#token-lifetime-api]] (typically 10 minutes). @@ -26,17 +26,17 @@ ```sh # curl -X POST -d 'username=IDENT&password=PASSWORD' http://127.0.0.1:23123/a ("Bearer" "eyJhbGciOiJIUzUxMiJ9.eyJfdGsiOjEsImV4cCI6MTY4MTMwNDA4OCwiaWF0IjoxNjgxMzA0MDI4LCJzdWIiOiJvd25lciIsInppZCI6IjIwMjEwNjI5MTYzMzAwIn0.qIEyOMFXykCApWtBaqbSESwTL96stWl2LRICiRNAXUjcY-mwx_SSl9L5Fj2FvmrI1K1RBvWehjoq8KZUNjhJ9Q" 600) ``` -In all cases, you will receive a list with three elements that will contain all [[relevant data|00001012921000]] to be used for further API calls. +In all cases, you will receive a list containing three elements with all [[relevant data|00001012921000]] needed for further API calls. **Important:** obtaining a token is a time-intensive process. Zettelstore will delay every request to obtain a token for a certain amount of time. Please take into account that this request will take approximately 500 milliseconds, under certain circumstances more. -However, if [[authentication is not enabled|00001010040100]] and you send an authentication request, no user identification/password checking is done and you receive an artificial token immediate, without any delay: +However, if [[authentication is not enabled|00001010040100]] and you send an authentication request, no user identification/password checking is done and you receive an artificial token immediately, without any delay: ```sh # curl -X POST -u IDENT:PASSWORD http://127.0.0.1:23123/a ("Bearer" "freeaccess" 316224000) ``` @@ -45,14 +45,14 @@ === HTTP Status codes In all cases of successful authentication, a list is returned, which contains the token as the second element. A successful authentication is signaled with the HTTP status code 200, as usual. -Other status codes possibly send by the Zettelstore: +Other status codes possibly sent by the Zettelstore: ; ''400'' : Unable to process the request. In most cases the form data was invalid. ; ''401'' : Authentication failed. Either the user identification is invalid or you provided the wrong password. ; ''403'' : Authentication is not active. Index: docs/manual/00001012051200.zettel ================================================================== --- docs/manual/00001012051200.zettel +++ docs/manual/00001012051200.zettel @@ -2,13 +2,13 @@ title: API: List all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 -modified: 20230807170810 +modified: 20250224120441 -To list all zettel just send a HTTP GET request to the [[endpoint|00001012920000]] ''/z''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. +To list all zettel just send a HTTP GET request to the [[endpoint|00001012920000]] ''/z''[^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header]. Always use the endpoint ''/z'' to work with a list of zettel. Without further specifications, a plain text document is returned, with one line per zettel. Each line contains in the first 14 characters the [[zettel identifier|00001006050000]]. Separated by a space character, the title of the zettel follows: @@ -31,37 +31,38 @@ Alternatively, you may retrieve the zettel list as a parseable object / a [[symbolic expression|00001012930500]] by providing the query parameter ''enc=data'': ```sh # curl 'http://127.0.0.1:23123/z?enc=data' -(meta-list (query "") (human "") (list (zettel (id "00001012921200") (meta (title "API: Encoding of Zettel Access Rights") (role "manual") (tags "#api #manual #reference #zettelstore") (syntax "zmk") (back "00001012051200 00001012051400 00001012053300 00001012053400 00001012053900 00001012054000") (backward "00001012051200 00001012051400 00001012053300 00001012053400 00001012053900 00001012054000") (box-number "1") (created "00010101000000") (forward "00001003000000 00001006020400 00001010000000 00001010040100 00001010040200 00001010070200 00001010070300") (modified "20220201171959") (published "20220201171959")) (rights 62)) (zettel (id "00001007030100") ... +(meta-list (query "") (human "") (zettel (id "00001012921200") (meta (title "API: Encoding of Zettel Access Rights") (role "manual") (tags "#api #manual #reference #zettelstore") (syntax "zmk") (back "00001012051200 00001012051400 00001012053300 00001012053400 00001012053900 00001012054000") (backward "00001012051200 00001012051400 00001012053300 00001012053400 00001012053900 00001012054000") (box-number "1") (created "00010101000000") (forward "00001003000000 00001006020400 00001010000000 00001010040100 00001010040200 00001010070200 00001010070300") (modified "20220201171959") (published "20220201171959")) (rights 62)) (zettel (id "00001007030100") ... ``` Pretty-printed, this results in: ``` (meta-list (query "") (human "") - (list (zettel (id "00001012921200") - (meta (title "API: Encoding of Zettel Access Rights") - (role "manual") - (tags "#api #manual #reference #zettelstore") - (syntax "zmk") - (back "00001012051200 00001012051400 00001012053300 00001012053400 00001012053900 00001012054000") - (backward "00001012051200 00001012051400 00001012053300 00001012053400 00001012053900 00001012054000") - (box-number "1") - (created "00010101000000") - (forward "00001003000000 00001006020400 00001010000000 00001010040100 00001010040200 00001010070200 00001010070300") - (modified "20220201171959") - (published "20220201171959")) - (rights 62)) - (zettel (id "00001007030100") + (zettel (id "00001012921200") + (meta (title "API: Encoding of Zettel Access Rights") + (role "manual") + (tags "#api #manual #reference #zettelstore") + (syntax "zmk") + (back "00001012051200 00001012051400 00001012053300 00001012053400 00001012053900 00001012054000") + (backward "00001012051200 00001012051400 00001012053300 00001012053400 00001012053900 00001012054000") + (box-number "1") + (created "00010101000000") + (forward "00001003000000 00001006020400 00001010000000 00001010040100 00001010040200 00001010070200 00001010070300") + (modified "20220201171959") + (published "20220201171959")) + (rights 62)) + (zettel (id "00001007030100") + ... ``` * The result is a list, starting with the symbol ''meta-list''. * Then, some key/value pairs are following, also nested. * Keys ''query'' and ''human'' will be explained [[later in this manual|00001012051400]]. -* ''list'' starts a list of zettel. +* Then comes the list of zettel. * ''zettel'' itself start, well, a zettel. * ''id'' denotes the zettel identifier, encoded as a string. * Nested in ''meta'' are the metadata, each as a key/value pair. * ''rights'' specifies the [[access rights|00001012921200]] the user has for this zettel. Index: docs/manual/00001012051400.zettel ================================================================== --- docs/manual/00001012051400.zettel +++ docs/manual/00001012051400.zettel @@ -2,26 +2,26 @@ title: API: Query the list of all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20220912111111 -modified: 20240711161320 +modified: 20250224120055 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. +The [[endpoint|00001012920000]] ''/z'' also allows you to filter the list of all zettel[^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header] and optionally specify 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. +A [[query|00001007700000]] consists of an optional [[search expression|00001007700000#search-expression]] and an optional [[list of actions|00001007700000#action-list]] (described below). +If no search expression is provided, all zettel are selected. +Similarly, if no valid action is specified, or the action list is empty, the list of all selected zettel metadata is returned. -Search expression and action list are separated by a vertical bar character (""''|''"", U+007C), and must be given with the query parameter ''q''. +Search expression and action list are separated by a vertical bar character (""''|''"", U+007C), and must be given with the ''q'' query parameter. The query parameter ""''q''"" allows you to specify [[query expressions|00001007700000]] for a full-text search of all zettel content and/or restricting the search according to specific metadata. -It is allowed to specify this query parameter more than once. +The query parameter can be provided multiple times. -This parameter loosely resembles the search form of the [[web user interface|00001014000000]] or those of [[Zettelmarkup's Query Transclusion|00001007031140]]. +It loosely resembles the search form of the [[web user interface|00001014000000]] or those of [[Zettelmarkup's Query Transclusion|00001007031140]]. For example, if you want to retrieve all zettel that contain the string ""API"" in its title, your request will be: ```sh # curl 'http://127.0.0.1:23123/z?q=title%3AAPI+ORDER+REVERSE+id+OFFSET+1' 00001012921000 API: Structure of an access token @@ -32,20 +32,20 @@ If you want to retrieve a data document, as a [[symbolic expression|00001012930500]]: ```sh # curl 'http://127.0.0.1:23123/z?q=title%3AAPI+ORDER+REVERSE+id+OFFSET+1&enc=data' -(meta-list (query "title:API ORDER REVERSE id OFFSET 1") (human "title HAS API ORDER REVERSE id OFFSET 1") (list (zettel (id 1012921000) (meta (title "API: Structure of an access token") (role "manual") (tags "#api #manual #reference #zettelstore") (syntax "zmk") (back "00001012050600 00001012051200") (backward "00001012050200 00001012050400 00001012050600 00001012051200") (box-number "1") (created "20210126175322") (forward "00001012050200 00001012050400 00001012930000") (modified "20230412155303") (published "20230412155303")) (rights 62)) (zettel (id 1012920500) (meta (title "Encodings available via the API") (role "manual") (tags "#api #manual #reference #zettelstore") (syntax "zmk") (back "00001006000000 00001008010000 00001008010500 00001012053500 00001012053600") (backward "00001006000000 00001008010000 00001008010500 00001012053500 00001012053600") (box-number "1") (created "20210126175322") (forward "00001012000000 00001012920510 00001012920513 00001012920516 00001012920519 00001012920522 00001012920525") (modified "20230403123653") (published "20230403123653")) (rights 62)) (zettel (id 1012920000) (meta (title "Endpoints used by the API") ... +(meta-list (query "title:API ORDER REVERSE id OFFSET 1") (human "title HAS API ORDER REVERSE id OFFSET 1") (zettel (id 1012921000) (meta (title "API: Structure of an access token") (role "manual") (tags "#api #manual #reference #zettelstore") (syntax "zmk") (back "00001012050600 00001012051200") (backward "00001012050200 00001012050400 00001012050600 00001012051200") (box-number "1") (created "20210126175322") (forward "00001012050200 00001012050400 00001012930000") (modified "20230412155303") (published "20230412155303")) (rights 62)) (zettel (id 1012920500) (meta (title "Encodings available via the API") (role "manual") (tags "#api #manual #reference #zettelstore") (syntax "zmk") (back "00001006000000 00001008010000 00001008010500 00001012053500 00001012053600") (backward "00001006000000 00001008010000 00001008010500 00001012053500 00001012053600") (box-number "1") (created "20210126175322") (forward "00001012000000 00001012920510 00001012920513 00001012920516 00001012920519 00001012920522 00001012920525") (modified "20230403123653") (published "20230403123653")) (rights 62)) (zettel (id 1012920000) (meta (title "Endpoints used by the API") ... ``` The data object contains a key ''"meta-list"'' to signal that it contains a list of metadata values (and some more). It contains the keys ''"query"'' and ''"human"'' with a string value. Both will contain a textual description of the underlying query if you select only some zettel with a [[query expression|00001007700000]]. Without a selection, the values are the empty string. ''"query"'' returns the normalized query expression itself, while ''"human"'' is the normalized query expression to be read by humans. -The symbol ''list'' starts the list of zettel data. +Then comes the list of zettel data. Data of a zettel is indicated by the symbol ''zettel'', followed by ''(id ID)'' that describes the zettel identifier as a numeric value. Leading zeroes are removed. Metadata starts with the symbol ''meta'', and each metadatum itself is a list of metadata key / metadata value. Metadata keys are encoded as a symbol, metadata values as a string. ''"rights"'' encodes the [[access rights|00001012921200]] for the given zettel. @@ -77,25 +77,25 @@ Of course, this list can also be returned as a data object: ```sh # curl 'http://127.0.0.1:23123/z?q=|role&enc=data' -(aggregate "role" (query "| role") (human "| role") (list ("zettel" 10000000000 90001) ("configuration" 6 100 1000000100 20001 90 25001 92 4 40001 1 90000 5 90002) ("manual" 1008050000 1007031110 1008000000 1012920513 1005000000 1012931800 1010040700 1012931000 1012053600 1006050000 1012050200 1012000000 1012070500 1012920522 1006032500 1006020100 1007906000 1007030300 1012051400 1007040350 1007040324 1007706000 1012931900 1006030500 1004050200 1012054400 1007700000 1004050000 1006020000 1007030400 1012080100 1012920510 1007790000 1010070400 1005090000 1004011400 1006033000 1012930500 1001000000 1007010000 1006020400 1007040300 1010070300 1008010000 1003305000 1006030000 1006034000 1012054200 1012080200 1004010000 1003300000 1006032000 1003310000 1004059700 1007031000 1003600000 1004000000 1007030700 1007000000 1006055000 1007050200 1006036000 1012050600 1006000000 1012053900 1012920500 1004050400 1007031100 1007040340 1007020000 1017000000 1012053200 1007030600 1007040320 1003315000 1012054000 1014000000 1007030800 1010000000 1007903000 1010070200 1004051200 1007040330 1004051100 1004051000 1007050100 1012080500 1012053400 1006035500 1012054600 1004100000 1010040200 1012920000 1012920525 1004051400 1006031500 1012921200 1008010500 1012921000 1018000000 1012051200 1010040100 1012931200 1012920516 1007040310 1007780000 1007030200 1004101000 1012920800 1007030100 1007040200 1012053500 1007040000 1007040322 1007031300 1007031140 1012931600 1012931400 1004059900 1003000000 1006036500 1004020200 1010040400 1006033500 1000000000 1012053300 1007990000 1010090100 1007900000 1007030500 1004011600 1012930000 1007030900 1004020000 1007030000 1010070600 1007040100 1007800000 1012050400 1006010000 1007705000 1007702000 1007050000 1002000000 1007031200 1006035000 1006031000 1006034500 1004011200 1007031400 1012920519))) +(aggregate "role" (query "| role") (human "| role") ("zettel" 10000000000 90001) ("configuration" 6 100 1000000100 20001 90 25001 92 4 40001 1 90000 5 90002) ("manual" 1008050000 1007031110 1008000000 1012920513 1005000000 1012931800 1010040700 1012931000 1012053600 1006050000 1012050200 1012000000 1012070500 1012920522 1006032500 1006020100 1007906000 1007030300 1012051400 1007040350 1007040324 1007706000 1012931900 1006030500 1004050200 1012054400 1007700000 1004050000 1006020000 1007030400 1012080100 1012920510 1007790000 1010070400 1005090000 1004011400 1006033000 1012930500 1001000000 1007010000 1006020400 1007040300 1010070300 1008010000 1003305000 1006030000 1006034000 1012054200 1012080200 1004010000 1003300000 1006032000 1003310000 1004059700 1007031000 1003600000 1004000000 1007030700 1007000000 1006055000 1007050200 1006036000 1012050600 1006000000 1012053900 1012920500 1004050400 1007031100 1007040340 1007020000 1017000000 1012053200 1007030600 1007040320 1003315000 1012054000 1014000000 1007030800 1010000000 1007903000 1010070200 1004051200 1007040330 1004051100 1004051000 1007050100 1012080500 1012053400 1006035500 1012054600 1004100000 1010040200 1012920000 1012920525 1004051400 1006031500 1012921200 1008010500 1012921000 1018000000 1012051200 1010040100 1012931200 1012920516 1007040310 1007780000 1007030200 1004101000 1012920800 1007030100 1007040200 1012053500 1007040000 1007040322 1007031300 1007031140 1012931600 1012931400 1004059900 1003000000 1006036500 1004020200 1010040400 1006033500 1000000000 1012053300 1007990000 1010090100 1007900000 1007030500 1004011600 1012930000 1007030900 1004020000 1007030000 1010070600 1007040100 1007800000 1012050400 1006010000 1007705000 1007702000 1007050000 1002000000 1007031200 1006035000 1006031000 1006034500 1004011200 1007031400 1012920519)) ``` The data object starts with the symbol ''aggregate'' to signal a different format compared to ''meta-list'' above. Then a string follows, which specifies the key on which the aggregate was performed. ''query'' and ''human'' have the same meaning as above. -The ''symbol'' list starts the result list of aggregates. +Then comes the result list of aggregates. Each aggregate starts with a string of the aggregate value, in this case the role value, followed by a list of zettel identifier, denoting zettel which have the given role value. Similar, to list all tags used in the Zettelstore, send a HTTP GET request to the endpoint ''/z?q=|tags''. If successful, the output is a data object: ```sh # curl 'http://127.0.0.1:23123/z?q=|tags&enc=data' -(aggregate "tags" (query "| tags") (human "| tags") (list ("#zettel" 1006034500 1006034000 1006031000 1006020400 1006033500 1006036500 1006032500 1006020100 1006031500 1006030500 1006035500 1006033000 1006020000 1006036000 1006030000 1006032000 1006035000) ("#reference" 1006034500 1006034000 1007800000 1012920500 1006031000 1012931000 1006020400 1012930000 1006033500 1012920513 1007050100 1012920800 1007780000 1012921000 1012920510 1007990000 1006036500 1006032500 1006020100 1012931400 1012931800 1012920516 1012931600 1012920525 1012931200 1006031500 1012931900 1012920000 1005090000 1012920522 1006030500 1007050200 1012921200 1006035500 1012920519 1006033000 1006020000 1006036000 1006030000 1006032000 1012930500 1006035000) ("#graphic" 1008050000) ("#search" 1007700000 1007705000 1007790000 1007780000 1007702000 1007706000 1007031140) ("#installation" 1003315000 1003310000 1003000000 1003305000 1003300000 1003600000) ("#zettelmarkup" 1007900000 1007030700 1007031300 1007030600 1007800000 1007000000 1007031400 1007040100 1007030300 1007031200 1007040350 1007030400 1007030900 1007050100 1007040000 1007030500 1007903000 1007040200 1007040330 1007990000 1007040320 1007050000 1007040310 1007031100 1007040340 1007020000 1007031110 1007031140 1007040324 1007030800 1007031000 1007030000 1007010000 1007906000 1007050200 1007030100 1007030200 1007040300 1007040322) ("#design" 1005000000 1006000000 1002000000 1006050000 1006055000) ("#markdown" 1008010000 1008010500) ("#goal" 1002000000) ("#syntax" 1006010000) ... +(aggregate "tags" (query "| tags") (human "| tags") ("#zettel" 1006034500 1006034000 1006031000 1006020400 1006033500 1006036500 1006032500 1006020100 1006031500 1006030500 1006035500 1006033000 1006020000 1006036000 1006030000 1006032000 1006035000) ("#reference" 1006034500 1006034000 1007800000 1012920500 1006031000 1012931000 1006020400 1012930000 1006033500 1012920513 1007050100 1012920800 1007780000 1012921000 1012920510 1007990000 1006036500 1006032500 1006020100 1012931400 1012931800 1012920516 1012931600 1012920525 1012931200 1006031500 1012931900 1012920000 1005090000 1012920522 1006030500 1007050200 1012921200 1006035500 1012920519 1006033000 1006020000 1006036000 1006030000 1006032000 1012930500 1006035000) ("#graphic" 1008050000) ("#search" 1007700000 1007705000 1007790000 1007780000 1007702000 1007706000 1007031140) ("#installation" 1003315000 1003310000 1003000000 1003305000 1003300000 1003600000) ("#zettelmarkup" 1007900000 1007030700 1007031300 1007030600 1007800000 1007000000 1007031400 1007040100 1007030300 1007031200 1007040350 1007030400 1007030900 1007050100 1007040000 1007030500 1007903000 1007040200 1007040330 1007990000 1007040320 1007050000 1007040310 1007031100 1007040340 1007020000 1007031110 1007031140 1007040324 1007030800 1007031000 1007030000 1007010000 1007906000 1007050200 1007030100 1007030200 1007040300 1007040322) ("#design" 1005000000 1006000000 1002000000 1006050000 1006055000) ("#markdown" 1008010000 1008010500) ("#goal" 1002000000) ("#syntax" 1006010000) ... ``` If you want only those tags that occur at least 100 times, use the endpoint ''/z?q=|MIN100+tags''. You see from this that actions are separated by space characters. Index: docs/manual/00001012053300.zettel ================================================================== --- docs/manual/00001012053300.zettel +++ docs/manual/00001012053300.zettel @@ -2,21 +2,21 @@ title: API: Retrieve metadata and content of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20211004093206 -modified: 20230807170259 +modified: 20241216104429 The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. -For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. +For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header]. ````sh # curl 'http://127.0.0.1:23123/z/00001012053300' The [[endpoint|00001012920000]] to work with metadata and content of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. -For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. +For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/z/00001012053300''[^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header]. ```sh ... ```` Index: docs/manual/00001012053400.zettel ================================================================== --- docs/manual/00001012053400.zettel +++ docs/manual/00001012053400.zettel @@ -2,13 +2,13 @@ title: API: Retrieve metadata of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210726174524 -modified: 20230807170155 +modified: 20241216104120 -The [[endpoint|00001012920000]] to work with metadata of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]][^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. +The [[endpoint|00001012920000]] to work with metadata of a specific zettel is ''/z/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]][^If [[authentication is enabled|00001010040100]], you must include a valid [[access token|00001012050200]] in the ''Authorization'' header]. To retrieve the plain metadata of a zettel, use the query parameter ''part=meta'' ````sh # curl 'http://127.0.0.1:23123/z/00001012053400?part=meta' Index: docs/manual/00001012053500.zettel ================================================================== --- docs/manual/00001012053500.zettel +++ docs/manual/00001012053500.zettel @@ -2,15 +2,15 @@ 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 +modified: 20241216104145 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''. +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 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 ") ... ``` Index: docs/manual/00001012053600.zettel ================================================================== --- docs/manual/00001012053600.zettel +++ docs/manual/00001012053600.zettel @@ -2,18 +2,18 @@ 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 +modified: 20241216104306 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, 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 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 ") ... ``` Index: docs/manual/00001012080200.zettel ================================================================== --- docs/manual/00001012080200.zettel +++ docs/manual/00001012080200.zettel @@ -2,13 +2,13 @@ title: API: Check for authentication role: manual tags: #api #manual #zettelstore syntax: zmk created: 20220103224858 -modified: 20220908163156 +modified: 20241216104549 -API clients typically wants to know, whether [[authentication is enabled|00001010040100]] or not. +API clients typically want to know, whether [[authentication is enabled|00001010040100]] or not. If authentication is enabled, they present some form of user interface to get user name and password for the actual authentication. Then they try to [[obtain an access token|00001012050200]]. If authentication is disabled, these steps are not needed. To check for enabled authentication, you must send a HTTP POST request to the [[endpoint|00001012920000]] ''/x'' and you must specify the query parameter ''cmd=authenticated''. Index: docs/manual/00001012080500.zettel ================================================================== --- docs/manual/00001012080500.zettel +++ docs/manual/00001012080500.zettel @@ -2,22 +2,22 @@ title: API: Refresh internal data role: manual tags: #api #manual #zettelstore syntax: zmk created: 20211230230441 -modified: 20220923104836 +modified: 20250102212703 Zettelstore maintains some internal data to allow faster operations. One example is the [[content search|00001012051400]] for a term: Zettelstore does not need to scan all zettel to find all occurrences for the term. -Instead, all word are stored internally, with a list of zettel where they occur. +Instead, all words are stored internally, with a list of zettel where they occur. Another example is the way to determine which zettel are stored in a [[ZIP file|00001004011200]]. Scanning a ZIP file is a lengthy operation, therefore Zettelstore maintains a directory of zettel for each ZIP file. All these internal data may become stale. -This should not happen, but when it comes e.g. to file handling, every operating systems behaves differently in very subtle ways. +This should not happen, but when it comes e.g. to file handling, every operating system behaves differently in very subtle ways. To avoid stopping and re-starting Zettelstore, you can use the API to force Zettelstore to refresh its internal data if you think it is needed. To do this, you must send a HTTP POST request to the [[endpoint|00001012920000]] ''/x'' and you must specify the query parameter ''cmd=refresh''. ```sh Index: docs/manual/00001012920516.zettel ================================================================== --- docs/manual/00001012920516.zettel +++ docs/manual/00001012920516.zettel @@ -2,19 +2,19 @@ title: Sz Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20220422181104 -modified: 20230403161458 +modified: 20250102214350 A zettel representation that is a [[s-expression|00001012930000]] (also known as symbolic expression). -It is (relatively) easy to parse and contain all relevant information of a zettel, metadata and content. -For example, take a look at the Sz encoding of this page, which is available via the ""Info"" sub-page of this zettel: +It is (relatively) easy to parse and contains all relevant information of metadata, content or the whole zettel. +For example, take a look at the Sz encoding of this page, which is available on the ""Info"" sub-page of this zettel: * [[//z/00001012920516?enc=sz&part=zettel]], * [[//z/00001012920516?enc=sz&part=meta]], * [[//z/00001012920516?enc=sz&part=content]]. -Some zettel describe the [[Sz encoding|00001012931000]] in a more detailed way. +Some zettel provide a more detailed description of the [[Sz encoding|00001012931000]]. If transferred via HTTP, the content type will be ''text/plain''. Index: docs/manual/00001012920525.zettel ================================================================== --- docs/manual/00001012920525.zettel +++ docs/manual/00001012920525.zettel @@ -2,11 +2,11 @@ title: SHTML Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230316181044 -modified: 20230403150657 +modified: 20250102180003 A zettel representation that is a [[s-expression|00001012930000]], syntactically similar to the [[Sz encoding|00001012920516]], but denotes [[HTML|00001012920510]] semantics. It is derived from a XML encoding in s-expressions, called [[SXML|https://en.wikipedia.org/wiki/SXML]]. It is (relatively) easy to parse and contains everything to transform it into real HTML. @@ -24,13 +24,13 @@ === Syntax of SHTML There are only two types of elements: atoms and lists, similar to the Sz encoding. A list always starts with the left parenthesis (""''(''"", U+0028) and ends with a right parenthesis (""'')''"", U+0029). A list may contain a possibly empty sequence of elements, i.e. lists and / or atoms. -Before the last element of a list of at least to elements, a full stop character (""''.''"", U+002E) signal a pair as the last two elements. +Before the last element of a list of at least two elements, a full stop character (""''.''"", U+002E) signal a pair as the last two elements. This allows a more space economic storage of data. An HTML tag like ``< a href="link">Text`` is encoded in SHTML with a list, where the first element is a symbol named a the tag. The second element is an optional encoding of the tag's attributes. Further elements are either other tag encodings or a string. The above tag is encoded as ``(a (@ (href . "link")) "Text")``. Also possible is to encode the attribute without pairs: ``(a (@ (href "link")) "Text")`` (note the missing full stop character). Index: docs/manual/00001012930500.zettel ================================================================== --- docs/manual/00001012930500.zettel +++ docs/manual/00001012930500.zettel @@ -2,11 +2,11 @@ title: Syntax of Symbolic Expressions role: manual tags: #manual #reference #zettelstore syntax: zmk created: 20230403151127 -modified: 20240413160345 +modified: 20250102175559 === 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. @@ -55,23 +55,23 @@ A number is a non-empty sequence of digits (""0"" ... ""9""). The smallest number is ``0``, there are no negative numbers. === Syntax of symbols (atom) A symbol is a non-empty sequence of printable characters, except left or right parenthesis. -Unicode characters of the following categories contains printable characters in the above sense: letter (L), number (N), punctuation (P), symbol (S). +Unicode characters of the following categories contain printable characters in the above sense: letter (L), number (N), punctuation (P), symbol (S). Symbols are case-sensitive, i.e. ""''ZETTEL''"" and ""''zettel''"" denote different symbols. -=== Syntax of string (atom) +=== Syntax of strings (atom) A string starts with a quotation mark (""''"''"", U+0022), contains a possibly empty sequence of Unicode characters, and ends with a quotation mark. -To allow a string to contain a quotations mark, it must be prefixed by one backslash (""''\\''"", U+005C). +To allow a string to contain a quotation mark, it must be prefixed by one backslash (""''\\''"", U+005C). To allow a string to contain a backslash, it also must be prefixed by one backslash. -Unicode characters with a code less than U+FF are encoded by by the sequence ""''\\xNM''"", where ''NM'' is the hex encoding of the character. -Unicode characters with a code less than U+FFFF are encoded by by the sequence ""''\\uNMOP''"", where ''NMOP'' is the hex encoding of the character. -Unicode characters with a code less than U+FFFFFF are encoded by by the sequence ""''\\UNMOPQR''"", where ''NMOPQR'' is the hex encoding of the character. +Unicode characters with a code less than U+FF are encoded 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 the sequence ""''\\uNMOP''"", where ''NMOP'' is the hex encoding of the character. +Unicode characters with a code less than U+FFFFFF are encoded 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. + Zettelstore only uses lists, numbers, strings, and symbols to represent zettel. Index: docs/manual/00001012931000.zettel ================================================================== --- docs/manual/00001012931000.zettel +++ docs/manual/00001012931000.zettel @@ -2,17 +2,17 @@ title: Encoding of Sz role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403153903 -modified: 20240123120319 +modified: 20250102213403 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 first element of that list is always a unique symbol, which denotes that part. The meaning / semantic of all other elements depend on that symbol. === Zettel A full zettel is represented by a list of two elements. The first elements represents the metadata, the second element represents the zettel content. Index: docs/manual/00001012931200.zettel ================================================================== --- docs/manual/00001012931200.zettel +++ docs/manual/00001012931200.zettel @@ -2,11 +2,11 @@ title: Encoding of Sz Metadata role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161618 -modified: 20240219161848 +modified: 20250115172406 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. @@ -22,14 +22,13 @@ | [[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"))'' Index: docs/manual/00001012931400.zettel ================================================================== --- docs/manual/00001012931400.zettel +++ docs/manual/00001012931400.zettel @@ -2,11 +2,11 @@ title: Encoding of Sz Block Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161803 -modified: 20240123120132 +modified: 20250317143910 === ''PARA'' :::syntax __Paragraph__ **=** ''(PARA'' [[__InlineElement__|00001012931600]] … '')''. ::: @@ -29,27 +29,27 @@ === ''ORDERED'', ''UNORDERED'', ''QUOTATION'' These three symbols are specifying different kinds of lists / enumerations: an ordered list, an unordered list, and a quotation list. Their structure is the same. :::syntax -__OrderedList__ **=** ''(ORDERED'' __ListElement__ … '')''. +__OrderedList__ **=** ''(ORDERED'' [[__Attributes__|00001012931000#attribute]] __ListElement__ … '')''. -__UnorderedList__ **=** ''(UNORDERED'' __ListElement__ … '')''. +__UnorderedList__ **=** ''(UNORDERED'' [[__Attributes__|00001012931000#attribute]] __ListElement__ … '')''. -__QuotationList__ **=** ''(QUOTATION'' __ListElement__ … '')''. +__QuotationList__ **=** ''(QUOTATION'' [[__Attributes__|00001012931000#attribute]] __ListElement__ … '')''. ::: :::syntax __ListElement__ **=** [[__Block__|00001012931000#block]] **|** [[__Inline__|00001012931000#inline]]. ::: A list element is either a block or an inline. If it is a block, it may contain a nested list. === ''DESCRIPTION'' :::syntax -__Description__ **=** ''(DESCRIPTION'' __DescriptionTerm__ __DescriptionValues__ __DescriptionTerm__ __DescriptionValues__ … '')''. +__Description__ **=** ''(DESCRIPTION'' [[__Attributes__|00001012931000#attribute]] __DescriptionTerm__ __DescriptionValues__ __DescriptionTerm__ __DescriptionValues__ … '')''. ::: -A description is a sequence of one ore more terms and values. +A description is a sequence of one or more terms and values. :::syntax __DescriptionTerm__ **=** ''('' [[__InlineElement__|00001012931600]] … '')''. ::: A description term is just an inline-structured value. @@ -59,11 +59,11 @@ ::: Description values are sequences of blocks. === ''TABLE'' :::syntax -__Table__ **=** ''(TABLE'' __TableHeader__ __TableRow__ … '')''. +__Table__ **=** ''(TABLE'' [[__Attributes__|00001012931000#attribute]] __TableHeader__ __TableRow__ … '')''. ::: A table is a table header and a sequence of table rows. :::syntax __TableHeader__ **=** ''()'' **|** ''('' __TableCell__ … '')''. @@ -73,37 +73,21 @@ :::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__. -::: - -:::syntax -__DefaultCell__ **=** ''(CELL'' [[__InlineElement__|00001012931600]] … '')''. -::: -The cell content, specified by the sequence of inline elements, used the default alignment. - -:::syntax -__CenterCell__ **=** ''(CELL-CENTER'' [[__InlineElement__|00001012931600]] … '')''. -::: -The cell content, specified by the sequence of inline elements, is centered aligned. - -:::syntax -__LeftCell__ **=** ''(CELL-LEFT'' [[__InlineElement__|00001012931600]] … '')''. -::: -The cell content, specified by the sequence of inline elements, is left aligned. - -:::syntax -__RightCell__ **=** ''(CELL-RIGHT'' [[__InlineElement__|00001012931600]] … '')''. -::: -The cell content, specified by the sequence of inline elements, is right aligned. +=== ''CELL'' +:::syntax +__TableCell__ **=** ''(CELL'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''. +::: +A table cell contains some attributes and cell content, which is a sequence of inline elements. + +The attribute ''align'' specifies the cell alignment. +An attribute value ''"center"'' specifies centered aligned cell content. +The attribute value ''"left"'' specifies left aligned cell content. +The attribute value ''"right"'' specifies right aligned cell content. +In all other cases, a default alignment is assumed. === ''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. @@ -124,11 +108,11 @@ :::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. +Soft line breaks 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-*'' The following lists specifies some literal text of more than one line. @@ -165,11 +149,11 @@ ::: The string contains text that should be treated as (nested) zettel content. === ''BLOB'' :::syntax -__BLOB__ **=** ''(BLOB'' ''('' [[__InlineElement__|00001012931600]] … '')'' String,,1,, String,,2,, '')''. +__BLOB__ **=** ''(BLOB'' [[__Attributes__|00001012931000#attribute]] ''('' [[__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. @@ -176,11 +160,11 @@ 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]] '')''. +__Transclude__ **=** ''(TRANSCLUDE'' [[__Attributes__|00001012931000#attribute]] [[__Reference__|00001012931900]] [[__InlineElement__|00001012931600]] … '')''. ::: 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. Index: docs/manual/00001012931600.zettel ================================================================== --- docs/manual/00001012931600.zettel +++ docs/manual/00001012931600.zettel @@ -2,11 +2,11 @@ title: Encoding of Sz Inline Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161845 -modified: 20240620170546 +modified: 20250313130428 === ''TEXT'' :::syntax __Text__ **=** ''(TEXT'' String '')''. ::: @@ -23,59 +23,17 @@ :::syntax __Hard__ **=** ''(HARD)''. ::: Specifies a hard line break, i.e. the user wants to have a line break here. -=== ''LINK-*'' -The following lists specify various links, based on the full reference. -They all have the same structure, with a trailing sequence of __InlineElements__ that contain the linked text. - -:::syntax -__InvalidLink__ **=** ''(LINK-INVALID'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. -::: -The string contains the invalid link specification. - -:::syntax -__ZettelLink__ **=** ''(LINK-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. -::: -The string contains the zettel identifier, a zettel reference. - -:::syntax -__SelfLink__ **=** ''(LINK-SELF'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. -::: -The string contains the number sign character and the name of a zettel mark. -It reference the same zettel where it occurs. - -:::syntax -__FoundLink__ **=** ''(LINK-FOUND'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. -::: -The string contains a zettel identifier, a zettel reference, of a zettel known to be included in the Zettelstore. - -:::syntax -__BrokenLink__ **=** ''(LINK-BROKEN'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. -::: -The string contains a zettel identifier, a zettel reference, of a zettel known to be __not__ included in the Zettelstore. - -:::syntax -__HostedLink__ **=** ''(LINK-HOSTED'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. -::: -The string contains a link starting with one slash character, denoting an absolute local reference. - -:::syntax -__BasedLink__ **=** ''(LINK-BASED'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. -::: -The string contains a link starting with two slash characters, denoting a local reference interpreted relative to the Zettelstore base URL. - -:::syntax -__QueryLink__ **=** ''(LINK-BASED'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. -::: -The string contains a [[query expression|00001007700000]]. - -:::syntax -__ExternalLink__ **=** ''(LINK-EXTERNAL'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. -::: -The string contains a full URL, referencing a resource outside of the Zettelstore server. +=== ''LINK'' +:::syntax +__Link__ **=** ''(LINK'' [[__Attributes__|00001012931000#attribute]] [[__Reference__|00001012931900]] [[__InlineElement__|00001012931600]] … '')''. +::: +Specifies a link to some other material. +The link type is based on the full reference. +The trailing sequence of __InlineElements__ contains the linked text. === ''EMBED'' :::syntax __Embed__ **=** ''(EMBED'' [[__Attributes__|00001012931000#attribute]] [[__Reference__|00001012931900]] String [[__InlineElement__|00001012931600]] … '')''. ::: @@ -94,11 +52,11 @@ __EmbedBLOB__ **=** ''(EMBED-BLOB'' [[__Attributes__|00001012931000#attribute]] String,,1,, String,,2,, '')''. ::: If used if some processed image has to be embedded inside some inline material. The first string specifies the syntax of the image content. The second string contains the image content. -If the syntax is ""SVG"", the image content is not further encoded. +If the syntax is ""SVG"", the image content is not encoded further. Otherwise a base64 encoding is used. === ''CITE'' :::syntax __CiteBLOB__ **=** ''(CITE'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. @@ -140,11 +98,11 @@ The inline text should be treated as inserted. :::syntax __MarkFormat__ **=** ''(FORMAT-MARK'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''. ::: -The inline text should be treated as highlighted for the reader (but was not important fto the original author). +The inline text should be treated as highlighted for the reader (but was not important to the original author). :::syntax __QuoteFormat__ **=** ''(FORMAT-QUOTE'' [[__Attributes__|00001012931000#attribute]] [[__InlineElement__|00001012931600]] … '')''. ::: The inline text should be treated as quoted text. @@ -189,21 +147,21 @@ The string contains text that should be treated as HTML code. :::syntax __InputLiteral__ **=** ''(LITERAL-INPUT'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: -The string contains text that should be treated as input entered by an user. +The string contains text that should be treated as input entered by a user. :::syntax __MathLiteral__ **=** ''(LITERAL-MATH'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as special code to be interpreted as mathematical formulas. :::syntax __OutputLiteral__ **=** ''(LITERAL-OUTPUT'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: -The string contains text that should be treated as computer output to be read by an user. +The string contains text that should be treated as computer output to be read by a user. :::syntax __ZettelLiteral__ **=** ''(LITERAL-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as (nested) zettel content. Index: docs/manual/00001012931900.zettel ================================================================== --- docs/manual/00001012931900.zettel +++ docs/manual/00001012931900.zettel @@ -2,11 +2,11 @@ title: Encoding of Sz Reference Values role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230405123046 -modified: 20240122094720 +modified: 20250313130616 A reference is encoded as the actual reference value, and a symbol describing the state of that actual reference value. :::syntax __Reference__ **=** ''('' __ReferenceState__ String '')''. @@ -15,12 +15,10 @@ :::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]]. - ; ''INVALID'' : The reference value is invalid. ; ''ZETTEL'' : The reference value is a reference to a zettel. This value is only possible before evaluating the zettel. @@ -28,15 +26,15 @@ : The reference value is a reference to the same zettel, to a specific mark. ; ''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. +: The reference value is a valid reference to a 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. Index: docs/manual/00001017000000.zettel ================================================================== --- docs/manual/00001017000000.zettel +++ docs/manual/00001017000000.zettel @@ -2,22 +2,22 @@ title: Tips and Tricks role: manual tags: #manual #zettelstore syntax: zmk created: 20220803170112 -modified: 20231012154803 +modified: 20250102190553 === Welcome Zettel * **Problem:** You want to put your Zettelstore into the public and need a starting zettel for your users. In addition, you still want a ""home zettel"", with all your references to internal, non-public zettel. - Zettelstore only allows to specify one [[''home-zettel''|00001004020000#home-zettel]]. + Zettelstore only allows specifying one [[''home-zettel''|00001004020000#home-zettel]]. * **Solution 1:** *# Create a new zettel with all your references to internal, non-public zettel. Let's assume this zettel receives the zettel identifier ''20220803182600''. *# Create the zettel that should serve as the starting zettel for your users. It must have syntax [[Zettelmarkup|00001008000000#zmk]], i.e. the syntax metadata must be set to ''zmk''. - If needed, set the runtime configuration [[''home-zettel|00001004020000#home-zettel]] to the value of the identifier of this zettel. + If needed, set the runtime configuration [[''home-zettel''|00001004020000#home-zettel]] to the value of the identifier of this zettel. *# At the beginning of the start zettel, add the following [[Zettelmarkup|00001007000000]] text in a separate paragraph: ``{{{20220803182600}}}`` (you have to adapt to the actual value of the zettel identifier for your non-public home zettel). * **Discussion:** As stated in the description for a [[transclusion|00001007031100]], a transclusion will be ignored, if the transcluded zettel is not visible to the current user. In effect, the transclusion statement (above paragraph that contained ''{{{...}}}'') is ignored when rendering the zettel. * **Solution 2:** Set a user-specific value by adding metadata ''home-zettel'' to the [[user zettel|00001010040200]]. * **Discussion:** A value for ''home-zettel'' is first searched in the user zettel of the current authenticated user. @@ -46,20 +46,20 @@ For example, if you also want the role ""configuration"" to be rendered using that CSS, the code should be something like ``(set! CSS-ROLE-map '(("zettel" . "20220825200100") ("configuration" . "20220825200100")))``. * **Discussion:** you have to ensure that the CSS zettel is allowed to be read by the intended audience of the zettel with that given role. For example, if you made zettel with a specific role public visible, the CSS zettel must also have a [[''visibility: public''|00001010070200]] metadata. === Zettel synchronization with iCloud (Apple) -* **Problem:** You use Zettelstore on various macOS computers and you want to use the sameset of zettel across all computers. +* **Problem:** You use Zettelstore on various macOS computers and you want to use the same set of zettel across all computers. * **Solution:** Place your zettel in an iCloud folder. - To configure Zettelstore to use the folder, you must specify its location within you directory structure as [[''box-uri-X''|00001004010000#box-uri-x]] (replace ''X'' with an appropriate number). + To configure Zettelstore to use the folder, you must specify its location within your directory structure as [[''box-uri-X''|00001004010000#box-uri-x]] (replace ''X'' with an appropriate number). Your iCloud folder is typically placed in the folder ''~/Library/Mobile Documents/com~apple~CloudDocs''. The ""''~''"" is a shortcut and specifies your home folder. Unfortunately, Zettelstore does not yet support this shortcut. Therefore you must replace it with the absolute name of your home folder. - In addition, a space character is not allowed in an URI. + In addition, a space character is not allowed in a URI. You have to replace it with the sequence ""''%20''"". Let us assume, that you stored your zettel box inside the folder ""zettel"", which is located top-level in your iCloud folder. In this case, you must specify the following box URI within the startup configuration: ''box-uri-1: dir:///Users/USERNAME/Library/Mobile%20Documents/com~apple~CloudDocs/zettel'', replacing ''USERNAME'' with the username of that specific computer (and assuming you want to use it as the first box). * **Solution 2:** If you typically start your Zettelstore on the command line, you could use the ''-d DIR'' option for the [[''run''|00001004051000#d]] sub-command. @@ -67,14 +67,14 @@ ''zettelstore run -d ~/Library/Mobile\\ Documents/com\\~apple\\~CloudDocs/zettel'' (The ""''\\''"" is needed by the command line processor to mask the following character to be processed in unintended ways.) * **Discussion:** Zettel files are synchronized between your computers via iCloud. - Is does not matter, if one of your computer is offline / switched off. + It does not matter, if one of your computers is offline or switched off. iCloud will synchronize the zettel files if it later comes online. However, if you use more than one computer simultaneously, you must be aware that synchronization takes some time. - It might take several seconds, maybe longer, that new new version of a zettel appears on the other computer. + It might take several seconds, maybe longer, that the new version of a zettel appears on the other computer. If you update the same zettel on multiple computers at nearly the same time, iCloud will not be able to synchronize the different versions in a safe manner. Zettelstore is intentionally not aware of any synchronization within its zettel boxes. If Zettelstore behaves strangely after a synchronization took place, the page about [[Troubleshooting|00001018000000#working-with-files]] might contain some useful information. Index: docs/manual/00001018000000.zettel ================================================================== --- docs/manual/00001018000000.zettel +++ docs/manual/00001018000000.zettel @@ -2,38 +2,38 @@ title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk created: 20211027105921 -modified: 20241212153148 +modified: 20250106180049 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. A dialog is then opened where you can acknowledge that you understand the possible risks when you start Zettelstore. - This dialog is only resented once for a given Zettelstore executable. + This dialog is only presented once for a given Zettelstore executable. * **Problem:** When you double-click on the Zettelstore executable icon, Windows complains that Zettelstore is an application from an unknown developer. ** **Solution:** Windows displays a dialog where you can acknowledge possible risks and allow to start Zettelstore. === Authentication * **Problem:** [[Authentication is enabled|00001010040100]] for a local running Zettelstore and there is a valid [[user zettel|00001010040200]] for the owner. But entering user name and password at the [[web user interface|00001014000000]] seems to be ignored, while entering a wrong password will result in an error message. ** **Explanation:** A local running Zettelstore typically means, that you are accessing the Zettelstore using an URL with schema ''http://'', and not ''https://'', for example ''http://localhost:23123''. 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 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 your [[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. +* **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 does not detect the 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 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 can find 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]]. === HTML content is not shown DELETED encoder/encoder.go Index: encoder/encoder.go ================================================================== --- encoder/encoder.go +++ /dev/null @@ -1,83 +0,0 @@ -//----------------------------------------------------------------------------- -// 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) - WriteMeta(io.Writer, *meta.Meta, EvalMetaFunc) (int, error) - WriteContent(io.Writer, *ast.ZettelNode) (int, error) - WriteBlocks(io.Writer, *ast.BlockSlice) (int, error) - WriteInlines(io.Writer, *ast.InlineSlice) (int, error) -} - -// EvalMetaFunc is a function that takes a string of metadata and returns -// a list of syntax elements. -type EvalMetaFunc func(string) ast.InlineSlice - -// Some errors to signal when encoder methods are not implemented. -var ( - ErrNoWriteZettel = errors.New("method WriteZettel is not implemented") - ErrNoWriteMeta = errors.New("method WriteMeta is not implemented") - ErrNoWriteContent = errors.New("method WriteContent is not implemented") - ErrNoWriteBlocks = errors.New("method WriteBlocks is not implemented") - ErrNoWriteInlines = errors.New("method WriteInlines is not implemented") -) - -// Create builds a new encoder with the given options. -func Create(enc api.EncodingEnum, params *CreateParameter) Encoder { - if create, ok := registry[enc]; ok { - return create(params) - } - return nil -} - -// CreateFunc produces a new encoder. -type CreateFunc func(*CreateParameter) Encoder - -// CreateParameter contains values that are needed to create an encoder. -type CreateParameter struct { - Lang string // default language -} - -var registry = map[api.EncodingEnum]CreateFunc{} - -// Register the encoder for later retrieval. -func Register(enc api.EncodingEnum, create CreateFunc) { - if _, ok := registry[enc]; ok { - panic(fmt.Sprintf("Encoder %q already registered", enc)) - } - registry[enc] = create -} - -// GetEncodings returns all registered encodings, ordered by encoding value. -func GetEncodings() []api.EncodingEnum { - result := make([]api.EncodingEnum, 0, len(registry)) - for enc := range registry { - result = append(result, enc) - } - return result -} DELETED encoder/encoder_blob_test.go Index: encoder/encoder_blob_test.go ================================================================== --- encoder/encoder_blob_test.go +++ /dev/null @@ -1,63 +0,0 @@ -//----------------------------------------------------------------------------- -// 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. -) - -type blobTestCase struct { - descr string - blob []byte - expect expectMap -} - -var pngTestCases = []blobTestCase{ - { - descr: "Minimal PNG", - blob: []byte{ - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, - 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: `

PNG

`, - 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'.`, - }, - }, -} - -func TestBlob(t *testing.T) { - m := meta.New(id.Invalid) - m.Set(api.KeyTitle, "PNG") - for testNum, tc := range pngTestCases { - inp := input.NewInput(tc.blob) - pe := &peBlocks{bs: parser.ParseBlocks(inp, m, "png", config.NoHTML)} - checkEncodings(t, testNum, pe, tc.descr, tc.expect, "???") - } -} DELETED encoder/encoder_block_test.go Index: encoder/encoder_block_test.go ================================================================== --- encoder/encoder_block_test.go +++ /dev/null @@ -1,408 +0,0 @@ -//----------------------------------------------------------------------------- -// 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", - zmk: "", - expect: expectMap{ - encoderHTML: "", - encoderMD: "", - encoderSz: `(BLOCK)`, - encoderSHTML: `()`, - encoderText: "", - encoderZmk: useZmk, - }, - }, - { - descr: "Simple text: Hello, world", - zmk: "Hello, world", - expect: expectMap{ - encoderHTML: "

Hello, world

", - 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%%%", - expect: expectMap{ - encoderHTML: ``, - encoderMD: "", - encoderSz: `(BLOCK (VERBATIM-COMMENT () "No\nrender"))`, - encoderSHTML: `(())`, - encoderText: ``, - encoderZmk: useZmk, - }, - }, - { - descr: "Rendered block comment", - zmk: "%%%{-}\nRender\n%%%", - expect: expectMap{ - encoderHTML: "\n", - encoderMD: "", - encoderSz: `(BLOCK (VERBATIM-COMMENT (("-" . "")) "Render"))`, - encoderSHTML: "((@@@ \"Render\"))", - encoderText: ``, - encoderZmk: useZmk, - }, - }, - { - descr: "Simple Heading", - zmk: `=== Top Job`, - expect: expectMap{ - encoderHTML: "

Top Job

", - 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{ - encoderHTML: "", - encoderMD: "* A\n* B\n* C", - encoderSz: `(BLOCK (UNORDERED (INLINE (TEXT "A")) (INLINE (TEXT "B")) (INLINE (TEXT "C"))))`, - encoderSHTML: `((ul (li "A") (li "B") (li "C")))`, - encoderText: "A\nB\nC", - encoderZmk: useZmk, - }, - }, - { - descr: "Nested List", - zmk: "* T1\n** T2\n* T3\n** T4\n** T5\n* T6", - expect: expectMap{ - encoderHTML: ``, - encoderMD: "* T1\n * T2\n* T3\n * T4\n * T5\n* T6", - encoderSz: `(BLOCK (UNORDERED (BLOCK (PARA (TEXT "T1")) (UNORDERED (INLINE (TEXT "T2")))) (BLOCK (PARA (TEXT "T3")) (UNORDERED (INLINE (TEXT "T4")) (INLINE (TEXT "T5")))) (BLOCK (PARA (TEXT "T6")))))`, - encoderSHTML: `((ul (li (p "T1") (ul (li "T2"))) (li (p "T3") (ul (li "T4") (li "T5"))) (li (p "T6"))))`, - encoderText: "T1\nT2\nT3\nT4\nT5\nT6", - encoderZmk: useZmk, - }, - }, - { - descr: "Sequence of two lists", - zmk: "* Item1.1\n* Item1.2\n* Item1.3\n\n* Item2.1\n* Item2.2", - expect: expectMap{ - encoderHTML: "", - encoderMD: "* Item1.1\n* Item1.2\n* Item1.3\n* Item2.1\n* Item2.2", - encoderSz: `(BLOCK (UNORDERED (INLINE (TEXT "Item1.1")) (INLINE (TEXT "Item1.2")) (INLINE (TEXT "Item1.3")) (INLINE (TEXT "Item2.1")) (INLINE (TEXT "Item2.2"))))`, - encoderSHTML: `((ul (li "Item1.1") (li "Item1.2") (li "Item1.3") (li "Item2.1") (li "Item2.2")))`, - encoderText: "Item1.1\nItem1.2\nItem1.3\nItem2.1\nItem2.2", - encoderZmk: "* Item1.1\n* Item1.2\n* Item1.3\n* Item2.1\n* Item2.2", - }, - }, - { - descr: "Simple horizontal rule", - zmk: `---`, - expect: expectMap{ - encoderHTML: "
", - encoderMD: "---", - encoderSz: `(BLOCK (THEMATIC ()))`, - encoderSHTML: `((hr))`, - encoderText: ``, - encoderZmk: useZmk, - }, - }, - { - descr: "Thematic break with attribute", - zmk: `---{lang="zmk"}`, - expect: expectMap{ - encoderHTML: `
`, - encoderMD: "---", - encoderSz: `(BLOCK (THEMATIC (("lang" . "zmk"))))`, - encoderSHTML: `((hr (@ (lang . "zmk"))))`, - encoderText: ``, - encoderZmk: useZmk, - }, - }, - { - descr: "No list after paragraph", - zmk: "Text\n*abc", - expect: expectMap{ - encoderHTML: "

Text *abc

", - encoderMD: "Text\n*abc", - encoderSz: `(BLOCK (PARA (TEXT "Text") (SOFT) (TEXT "*abc")))`, - encoderSHTML: `((p "Text" " " "*abc"))`, - encoderText: `Text *abc`, - encoderZmk: useZmk, - }, - }, - { - descr: "A list after paragraph", - zmk: "Text\n# abc", - expect: expectMap{ - encoderHTML: "

Text

  1. abc
", - encoderMD: "Text\n\n1. abc", - encoderSz: `(BLOCK (PARA (TEXT "Text")) (ORDERED (INLINE (TEXT "abc"))))`, - encoderSHTML: `((p "Text") (ol (li "abc")))`, - encoderText: "Text\nabc", - encoderZmk: useZmk, - }, - }, - { - descr: "Simple List Quote", - zmk: "> ToBeOrNotToBe", - expect: expectMap{ - encoderHTML: "
ToBeOrNotToBe
", - 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: "

ToBeOrNotToBe

Romeo Julia
", - 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: "

ToBeOr

NotToBe

Romeo
", - 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: "

A\u00a0line
\u00a0\u00a0another\u00a0line
Back

Paragraph

\u00a0\u00a0\u00a0\u00a0Spacy\u00a0\u00a0Para

Author
", - 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: "

A simple span and much more

", - 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```", - expect: expectMap{ - encoderHTML: "
Hello\nWorld
", - encoderMD: " Hello\n World", - encoderSz: `(BLOCK (VERBATIM-CODE () "Hello\nWorld"))`, - encoderSHTML: `((pre (code "Hello\nWorld")))`, - encoderText: "Hello\nWorld", - encoderZmk: useZmk, - }, - }, - { - descr: "Simple Verbatim Code with visible spaces", - zmk: "```{-}\nHello World\n```", - expect: expectMap{ - encoderHTML: "
Hello\u2423World
", - encoderMD: " Hello World", - encoderSz: `(BLOCK (VERBATIM-CODE (("-" . "")) "Hello World"))`, - encoderSHTML: "((pre (code \"Hello\u2423World\")))", - encoderText: "Hello World", - encoderZmk: useZmk, - }, - }, - { - descr: "Simple Verbatim Eval", - zmk: "~~~\nHello\nWorld\n~~~", - expect: expectMap{ - encoderHTML: "
Hello\nWorld
", - encoderMD: "", - encoderSz: `(BLOCK (VERBATIM-EVAL () "Hello\nWorld"))`, - encoderSHTML: "((pre (code (@ (class . \"zs-eval\")) \"Hello\\nWorld\")))", - encoderText: "Hello\nWorld", - encoderZmk: useZmk, - }, - }, - { - descr: "Simple Verbatim Math", - zmk: "$$$\nHello\n\\LaTeX\n$$$", - expect: expectMap{ - encoderHTML: "
Hello\n\\LaTeX
", - encoderMD: "", - encoderSz: `(BLOCK (VERBATIM-MATH () "Hello\n\\LaTeX"))`, - encoderSHTML: "((pre (code (@ (class . \"zs-math\")) \"Hello\\n\\\\LaTeX\")))", - encoderText: "Hello\n\\LaTeX", - encoderZmk: useZmk, - }, - }, - { - descr: "Simple Description List", - zmk: "; Zettel\n: Paper\n: Note\n; Zettelkasten\n: Slip box", - expect: expectMap{ - encoderHTML: "
Zettel

Paper

Note

Zettelkasten

Slip box

", - 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: "
Zettel

Paper

Note

Zettelkasten

Slip box

", - 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: "
K1

D11

D12

K2
K3

D31

", - 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: `
c1c2c3
d1d3
`, - 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:| -|%--+---+---+ -|h1h2h3c1c2c3f1f2=f3`, - 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: -|Text1

  1. Endnote \u21a9\ufe0e
", - 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: "

Text1

  1. Endnote2 \u21a9\ufe0e
  2. Nested \u21a9\ufe0e
", - 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: `

`, - 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", - zmk: `%% Comment`, - expect: expectMap{ - // encoderHTML: ``, - encoderSz: `(BLOCK (PARA (LITERAL-COMMENT () "Comment")))`, - // encoderSHTML: ``, - encoderText: "", - encoderZmk: useZmk, - }, - }, - { - descr: "", - zmk: ``, - expect: expectMap{ - encoderHTML: ``, - encoderSz: `(BLOCK)`, - encoderSHTML: `()`, - encoderText: "", - encoderZmk: useZmk, - }, - }, -} - -// func TestEncoderBlock(t *testing.T) { -// executeTestCases(t, tcsBlock) -// } DELETED encoder/encoder_inline_test.go Index: encoder/encoder_inline_test.go ================================================================== --- encoder/encoder_inline_test.go +++ /dev/null @@ -1,676 +0,0 @@ -//----------------------------------------------------------------------------- -// 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)", - zmk: "", - expect: expectMap{ - encoderHTML: "", - encoderMD: "", - encoderSz: `(INLINE)`, - encoderSHTML: `()`, - encoderText: "", - encoderZmk: useZmk, - }, - }, - { - 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", - expect: expectMap{ - encoderHTML: "soft break", - encoderMD: "soft\nbreak", - encoderSz: `(INLINE (TEXT "soft") (SOFT) (TEXT "break"))`, - encoderSHTML: `("soft" " " "break")`, - encoderText: "soft break", - encoderZmk: useZmk, - }, - }, - { - descr: "Hard Break", - zmk: "hard\\\nbreak", - expect: expectMap{ - encoderHTML: "hard
break", - encoderMD: "hard\\\nbreak", - encoderSz: `(INLINE (TEXT "hard") (HARD) (TEXT "break"))`, - encoderSHTML: `("hard" (br) "break")`, - encoderText: "hard\nbreak", - encoderZmk: useZmk, - }, - }, - { - descr: "Emphasized formatting", - zmk: "__emph__", - expect: expectMap{ - encoderHTML: "emph", - encoderMD: "*emph*", - encoderSz: `(INLINE (FORMAT-EMPH () (TEXT "emph")))`, - encoderSHTML: `((em "emph"))`, - encoderText: "emph", - encoderZmk: useZmk, - }, - }, - { - descr: "Strong formatting", - zmk: "**strong**", - expect: expectMap{ - encoderHTML: "strong", - encoderMD: "__strong__", - encoderSz: `(INLINE (FORMAT-STRONG () (TEXT "strong")))`, - encoderSHTML: `((strong "strong"))`, - encoderText: "strong", - encoderZmk: useZmk, - }, - }, - { - descr: "Insert formatting", - zmk: ">>insert>>", - expect: expectMap{ - encoderHTML: "insert", - encoderMD: "insert", - encoderSz: `(INLINE (FORMAT-INSERT () (TEXT "insert")))`, - encoderSHTML: `((ins "insert"))`, - encoderText: "insert", - encoderZmk: useZmk, - }, - }, - { - descr: "Delete formatting", - zmk: "~~delete~~", - expect: expectMap{ - encoderHTML: "delete", - encoderMD: "delete", - encoderSz: `(INLINE (FORMAT-DELETE () (TEXT "delete")))`, - encoderSHTML: `((del "delete"))`, - encoderText: "delete", - encoderZmk: useZmk, - }, - }, - { - descr: "Update formatting", - zmk: "~~old~~>>new>>", - expect: expectMap{ - encoderHTML: "oldnew", - encoderMD: "oldnew", - encoderSz: `(INLINE (FORMAT-DELETE () (TEXT "old")) (FORMAT-INSERT () (TEXT "new")))`, - encoderSHTML: `((del "old") (ins "new"))`, - encoderText: "oldnew", - encoderZmk: useZmk, - }, - }, - { - descr: "Superscript formatting", - zmk: "^^superscript^^", - expect: expectMap{ - encoderHTML: `superscript`, - encoderMD: "superscript", - encoderSz: `(INLINE (FORMAT-SUPER () (TEXT "superscript")))`, - encoderSHTML: `((sup "superscript"))`, - encoderText: `superscript`, - encoderZmk: useZmk, - }, - }, - { - descr: "Subscript formatting", - zmk: ",,subscript,,", - expect: expectMap{ - encoderHTML: `subscript`, - encoderMD: "subscript", - encoderSz: `(INLINE (FORMAT-SUB () (TEXT "subscript")))`, - encoderSHTML: `((sub "subscript"))`, - encoderText: `subscript`, - encoderZmk: useZmk, - }, - }, - { - 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: `„quotes“`, - 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: `""`, - 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: ‘yes, “or?””`, - 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##`, - expect: expectMap{ - encoderHTML: `marked`, - encoderMD: "marked", - encoderSz: `(INLINE (FORMAT-MARK () (TEXT "marked")))`, - encoderSHTML: `((mark "marked"))`, - encoderText: `marked`, - encoderZmk: useZmk, - }, - }, - { - descr: "Span formatting", - zmk: `::span::`, - expect: expectMap{ - encoderHTML: `span`, - encoderMD: "span", - encoderSz: `(INLINE (FORMAT-SPAN () (TEXT "span")))`, - encoderSHTML: `((span "span"))`, - encoderText: `span`, - encoderZmk: useZmk, - }, - }, - { - descr: "Code formatting", - zmk: "``code``", - expect: expectMap{ - encoderHTML: `code`, - encoderMD: "`code`", - encoderSz: `(INLINE (LITERAL-CODE () "code"))`, - encoderSHTML: `((code "code"))`, - encoderText: `code`, - encoderZmk: useZmk, - }, - }, - { - descr: "Code formatting with visible space", - zmk: "``x y``{-}", - expect: expectMap{ - encoderHTML: "x\u2423y", - encoderMD: "`x y`", - encoderSz: `(INLINE (LITERAL-CODE (("-" . "")) "x y"))`, - encoderSHTML: "((code \"x\u2423y\"))", - encoderText: `x y`, - encoderZmk: useZmk, - }, - }, - { - descr: "HTML in Code formatting", - zmk: "``