Index: api/const.go ================================================================== --- api/const.go +++ api/const.go @@ -117,27 +117,31 @@ CommandRefresh = Command("refresh") ) // Supported search operator representations. const ( - BackwardDirective = "BACKWARD" // Backward-only context + BackwardDirective = "BACKWARD" // Backward-only context / thread ContextDirective = "CONTEXT" // Context directive CostDirective = "COST" // Maximum cost of a context operation - ForwardDirective = "FORWARD" // Forward-only context + DirectedDirective = "DIRECTED" // Context/thread collection can have general directions + FolgeDirective = "FOLGE" // Folge thread + ForwardDirective = "FORWARD" // Forward-only context / thread FullDirective = "FULL" // Include tags in context IdentDirective = "IDENT" // Use only specified zettel ItemsDirective = "ITEMS" // Select list elements in a zettel - MaxDirective = "MAX" // Maximum number of context results + MaxDirective = "MAX" // Maximum number of context / thread results MinDirective = "MIN" // Minimum number of context results LimitDirective = "LIMIT" // Maximum number of zettel OffsetDirective = "OFFSET" // Offset to start returned zettel list OrDirective = "OR" // Combine several search expression with an "or" OrderDirective = "ORDER" // Specify metadata keys for the order of returned list PhraseDirective = "PHRASE" // Only unlinked zettel with given phrase PickDirective = "PICK" // Pick some random zettel RandomDirective = "RANDOM" // Order zettel list randomly ReverseDirective = "REVERSE" // Reverse the order of a zettel list + SequelDirective = "SEQUEL" // Sequel / branching thread + ThreadDirective = "THREAD" // Both folge and Sequel thread UnlinkedDirective = "UNLINKED" // Search for zettel that contain a phase(s) but do not link ActionSeparator = "|" // Separates action list of previous elements of query expression KeysAction = "KEYS" // Provide metadata key used Index: client/client_test.go ================================================================== --- client/client_test.go +++ client/client_test.go @@ -25,27 +25,21 @@ "t73f.de/r/zsc/domain/id" ) func TestZettelList(t *testing.T) { c := getClient() - _, err := c.QueryZettel(context.Background(), "") - if err != nil { + if _, err := c.QueryZettel(context.Background(), ""); err != nil { t.Error(err) - return } } func TestGetProtectedZettel(t *testing.T) { c := getClient() - _, err := c.GetZettel(context.Background(), id.ZidStartupConfiguration, api.PartZettel) - if err != nil { - if cErr, ok := err.(*client.Error); ok && cErr.StatusCode == http.StatusForbidden { - return - } else { + if _, err := c.GetZettel(context.Background(), id.ZidStartupConfiguration, api.PartZettel); err != nil { + if cErr, ok := err.(*client.Error); !ok || cErr.StatusCode != http.StatusForbidden { t.Error(err) } - return } } func TestGetSzZettel(t *testing.T) { c := getClient() Index: domain/id/id.go ================================================================== --- domain/id/id.go +++ domain/id/id.go @@ -87,11 +87,10 @@ // WebUI image zettel are in the range 40000..49999 ZidEmoji = Zid(40001) // Other sxn code zettel are in the range 50000..59999 - ZidSxnPrelude = Zid(59900) // Predefined Zettelmarkup zettel are in the range 60000..69999 ZidRoleZettelZettel = Zid(60010) ZidRoleConfigurationZettel = Zid(60020) ZidRoleRoleZettel = Zid(60030) Index: domain/meta/meta.go ================================================================== --- domain/meta/meta.go +++ domain/meta/meta.go @@ -144,18 +144,16 @@ KeyForward = "forward" KeyLang = "lang" KeyLicense = "license" KeyModified = "modified" KeyPrecursor = "precursor" - KeyPredecessor = "predecessor" KeyPrequel = "prequel" KeyPublished = "published" KeyQuery = "query" KeyReadOnly = "read-only" KeySequel = "sequel" KeySubordinates = "subordinates" - KeySuccessors = "successors" KeySummary = "summary" KeySuperior = "superior" KeyURL = "url" KeyUselessFiles = "useless-files" KeyUserID = "user-id" @@ -172,11 +170,10 @@ registerKey(KeySyntax, TypeWord, usageUser, "") // Properties that are inverse keys registerKey(KeyFolge, TypeIDSet, usageProperty, "") registerKey(KeySequel, TypeIDSet, usageProperty, "") - registerKey(KeySuccessors, TypeIDSet, usageProperty, "") registerKey(KeySubordinates, TypeIDSet, usageProperty, "") // Non-inverse keys registerKey(KeyAuthor, TypeString, usageUser, "") registerKey(KeyBack, TypeIDSet, usageProperty, "") @@ -191,11 +188,10 @@ registerKey(KeyForward, TypeIDSet, usageProperty, "") registerKey(KeyLang, TypeWord, usageUser, "") registerKey(KeyLicense, TypeEmpty, usageUser, "") registerKey(KeyModified, TypeTimestamp, usageComputed, "") registerKey(KeyPrecursor, TypeIDSet, usageUser, KeyFolge) - registerKey(KeyPredecessor, TypeID, usageUser, KeySuccessors) registerKey(KeyPrequel, TypeIDSet, usageUser, KeySequel) registerKey(KeyPublished, TypeTimestamp, usageProperty, "") registerKey(KeyQuery, TypeEmpty, usageUser, "") registerKey(KeyReadOnly, TypeWord, usageUser, "") registerKey(KeySummary, TypeString, usageUser, "") @@ -277,27 +273,40 @@ // SetNonEmpty stores the given value under the given key, if the value is non-empty. // An empty value will delete the previous association. func (m *Meta) SetNonEmpty(key string, value Value) { if value == "" { - delete(m.pairs, key) // TODO: key != KeyID + if key != KeyID { + delete(m.pairs, key) + } } else { m.Set(key, value.TrimSpace()) } } + +// Has returns true, if the given key is used in the metadata. +func (m *Meta) Has(key string) bool { + if m != nil { + if _, found := m.pairs[key]; found || key == KeyID { + return true + } + } + return false +} // Get retrieves the string value of a given key. The bool value signals, // whether there was a value stored or not. func (m *Meta) Get(key string) (Value, bool) { - if m == nil { - return "", false - } - if key == KeyID { - return Value(m.Zid.String()), true - } - value, ok := m.pairs[key] - return value, ok + if m != nil { + if value, found := m.pairs[key]; found { + return value, true + } + if key == KeyID { + return Value(m.Zid.String()), true + } + } + return "", false } // GetDefault retrieves the string value of the given key. If no value was // stored, the given default value is returned. func (m *Meta) GetDefault(key string, def Value) Value { Index: domain/meta/type.go ================================================================== --- domain/meta/type.go +++ domain/meta/type.go @@ -92,10 +92,12 @@ cachedTypedKeys = make(map[string]*DescriptionType) mxTypedKey sync.RWMutex suffixTypes = map[string]*DescriptionType{ "-date": TypeTimestamp, "-number": TypeNumber, + "-ref": TypeID, + "-refs": TypeIDSet, SuffixKeyRole: TypeWord, "-time": TypeTimestamp, SuffixKeyURL: TypeURL, "-zettel": TypeID, "-zid": TypeID, Index: go.mod ================================================================== --- go.mod +++ go.mod @@ -1,11 +1,11 @@ module t73f.de/r/zsc go 1.24 require ( - t73f.de/r/sx v0.0.0-20250415161954-42ed9c4d6abc - t73f.de/r/sxwebs v0.0.0-20250415162443-110f49c5a1ae - t73f.de/r/webs v0.0.0-20250311182734-f263a38b32d5 - t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7 - t73f.de/r/zsx v0.0.0-20250415162540-fc13b286b6ce + t73f.de/r/sx v0.0.0-20250620141036-553aa22c59dc + t73f.de/r/sxwebs v0.0.0-20250621125212-c25706b6e4b3 + t73f.de/r/webs v0.0.0-20250604132257-c12dbd1f7046 + t73f.de/r/zero v0.0.0-20250604143210-ce1230735c4c + t73f.de/r/zsx v0.0.0-20250526093914-c34f0bae8fd2 ) Index: go.sum ================================================================== --- go.sum +++ go.sum @@ -1,10 +1,10 @@ -t73f.de/r/sx v0.0.0-20250415161954-42ed9c4d6abc h1:tlsP+47Rf8i9Zv1TqRnwfbQx3nN/F/92RkT6iCA6SVA= -t73f.de/r/sx v0.0.0-20250415161954-42ed9c4d6abc/go.mod h1:hzg05uSCMk3D/DWaL0pdlowfL2aWQeGIfD1S04vV+Xg= -t73f.de/r/sxwebs v0.0.0-20250415162443-110f49c5a1ae h1:K6nxN/bb0BCSiDffwNPGTF2uf5WcTdxcQXzByXNuJ7M= -t73f.de/r/sxwebs v0.0.0-20250415162443-110f49c5a1ae/go.mod h1:0LQ9T1svSg9ADY/6vQLKNUu6LqpPi8FGr7fd2qDT5H8= -t73f.de/r/webs v0.0.0-20250311182734-f263a38b32d5 h1:nnKfs/2i9n3S5VjbSj98odcwZKGcL96qPSIUATT/2P8= -t73f.de/r/webs v0.0.0-20250311182734-f263a38b32d5/go.mod h1:zk92hSKB4iWyT290+163seNzu350TA9XLATC9kOldqo= -t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7 h1:OuzHSfniY8UzLmo5zp1w23Kd9h7x9CSXP2jQ+kppeqU= -t73f.de/r/zero v0.0.0-20250226205915-c4194684acb7/go.mod h1:T1vFcHoymUQcr7+vENBkS1yryZRZ3YB8uRtnMy8yRBA= -t73f.de/r/zsx v0.0.0-20250415162540-fc13b286b6ce h1:R9rtg4ecx4YYixsMmsh+wdcqLdY9GxoC5HZ9mMS33to= -t73f.de/r/zsx v0.0.0-20250415162540-fc13b286b6ce/go.mod h1:tXOlmsQBoY4mY7Plu0LCCMZNSJZJbng98fFarZXAWvM= +t73f.de/r/sx v0.0.0-20250620141036-553aa22c59dc h1:5s02C7lwQKjOXyY4ghR6oLo+0SagSBiEiC26ju3VG40= +t73f.de/r/sx v0.0.0-20250620141036-553aa22c59dc/go.mod h1:Ow4Btc5PCykSct4TrS1kbEB386msl/tmfmLSsEz6OAw= +t73f.de/r/sxwebs v0.0.0-20250621125212-c25706b6e4b3 h1:+tqWPX3z5BgsRZJDpMtReHmGUioUFP+LsPpXieZ2ZsY= +t73f.de/r/sxwebs v0.0.0-20250621125212-c25706b6e4b3/go.mod h1:zZBXrGeTfUqElkSMJhGUCuDDWNOUaZE0EH3IZwkW+RA= +t73f.de/r/webs v0.0.0-20250604132257-c12dbd1f7046 h1:BZWNT/wYlX5sHmEtClRG0rHzZnoh8J35NcRnTvXlqy0= +t73f.de/r/webs v0.0.0-20250604132257-c12dbd1f7046/go.mod h1:EVohQwCAlRK0kuVBEw5Gw+S44vj+6f6NU8eNJdAIK6s= +t73f.de/r/zero v0.0.0-20250604143210-ce1230735c4c h1:Zy7GaPv/uVSjKQY7t2c0OOIdSue36x+/0sXt+xoxlpQ= +t73f.de/r/zero v0.0.0-20250604143210-ce1230735c4c/go.mod h1:T1vFcHoymUQcr7+vENBkS1yryZRZ3YB8uRtnMy8yRBA= +t73f.de/r/zsx v0.0.0-20250526093914-c34f0bae8fd2 h1:GWLCd3n8mN6AGhiv8O7bhdjK0BqXQS5EExRlBdx3OPU= +t73f.de/r/zsx v0.0.0-20250526093914-c34f0bae8fd2/go.mod h1:IQdyC9JP1i6RK55+LJVGjP3hSA9H766yCyUt1AkOU9c= Index: sexp/sexp.go ================================================================== --- sexp/sexp.go +++ sexp/sexp.go @@ -19,10 +19,11 @@ "errors" "fmt" "sort" "t73f.de/r/sx" + "t73f.de/r/sx/sxbuiltins" "t73f.de/r/zsc/api" ) // EncodeZettel transforms zettel data into a sx object. func EncodeZettel(zettel api.ZettelData) sx.Object { @@ -80,11 +81,11 @@ } // EncodeMetaRights translates metadata/rights into a sx object. func EncodeMetaRights(mr api.MetaRights) *sx.Pair { return sx.MakeList( - sx.SymbolList, + sx.MakeSymbol(sxbuiltins.List.Name), meta2sz(mr.Meta), sx.MakeList(sx.MakeSymbol("rights"), sx.Int64(int64(mr.Rights))), ) } Index: sz/zmk/zmk_test.go ================================================================== --- sz/zmk/zmk_test.go +++ sz/zmk/zmk_test.go @@ -66,12 +66,12 @@ } } type astWalker struct{} -func (astWalker) VisitBefore(node *sx.Pair, env *sx.Pair) (sx.Object, bool) { return sx.Nil(), false } -func (astWalker) VisitAfter(node *sx.Pair, env *sx.Pair) sx.Object { return node } +func (astWalker) VisitBefore(*sx.Pair, *sx.Pair) (sx.Object, bool) { return sx.Nil(), false } +func (astWalker) VisitAfter(node *sx.Pair, _ *sx.Pair) sx.Object { return node } func TestEdges(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"\"\"\"\n; \n0{{0}}{0}\n\"\"\"", "(BLOCK (REGION-VERSE () ((DESCRIPTION () ()) (PARA (TEXT \"0\") (EMBED ((\"0\" . \"\")) (HOSTED \"0\") \"\")))))"},