DELETED attrs/attrs.go Index: attrs/attrs.go ================================================================== --- attrs/attrs.go +++ /dev/null @@ -1,116 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -//----------------------------------------------------------------------------- - -// Package attrs stores attributes of zettel parts. -package attrs - -import ( - "maps" - "slices" - "strings" -) - -// Attributes store additional information about some node types. -type Attributes map[string]string - -// IsEmpty returns true if there are no attributes. -func (a Attributes) IsEmpty() bool { return len(a) == 0 } - -// DefaultAttribute is the value of the key of the default attribute -const DefaultAttribute = "-" - -// HasDefault returns true, if the default attribute "-" has been set. -func (a Attributes) HasDefault() bool { - if a != nil { - _, ok := a[DefaultAttribute] - return ok - } - return false -} - -// RemoveDefault removes the default attribute -func (a Attributes) RemoveDefault() Attributes { - if a != nil { - a.Remove(DefaultAttribute) - } - return a -} - -// Keys returns the sorted list of keys. -func (a Attributes) Keys() []string { return slices.Sorted(maps.Keys(a)) } - -// Get returns the attribute value of the given key and a succes value. -func (a Attributes) Get(key string) (string, bool) { - if a != nil { - value, ok := a[key] - return value, ok - } - return "", false -} - -// Clone returns a duplicate of the attribute. -func (a Attributes) Clone() Attributes { return maps.Clone(a) } - -// Set changes the attribute that a given key has now a given value. -func (a Attributes) Set(key, value string) Attributes { - if a == nil { - return map[string]string{key: value} - } - a[key] = value - return a -} - -// Remove the key from the attributes. -func (a Attributes) Remove(key string) Attributes { - if a != nil { - delete(a, key) - } - return a -} - -// Add a value to an attribute key. -func (a Attributes) Add(key, value string) Attributes { - if a == nil { - return map[string]string{key: value} - } - values := a.Values(key) - if !slices.Contains(values, value) { - values = append(values, value) - a[key] = strings.Join(values, " ") - } - return a -} - -// Values are the space separated values of an attribute. -func (a Attributes) Values(key string) []string { - if a != nil { - if value, ok := a[key]; ok { - return strings.Fields(value) - } - } - return nil -} - -// Has the attribute key a value? -func (a Attributes) Has(key, value string) bool { - return slices.Contains(a.Values(key), value) -} - -// AddClass adds a value to the class attribute. -func (a Attributes) AddClass(class string) Attributes { return a.Add("class", class) } - -// GetClasses returns the class values as a string slice -func (a Attributes) GetClasses() []string { return a.Values("class") } - -// HasClass returns true, if attributes contains the given class. -func (a Attributes) HasClass(s string) bool { return a.Has("class", s) } DELETED attrs/attrs_test.go Index: attrs/attrs_test.go ================================================================== --- attrs/attrs_test.go +++ /dev/null @@ -1,78 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -//----------------------------------------------------------------------------- - -package attrs_test - -import ( - "testing" - - "t73f.de/r/zsc/attrs" -) - -func TestHasDefault(t *testing.T) { - t.Parallel() - attr := attrs.Attributes{} - if attr.HasDefault() { - t.Error("Should not have default attr") - } - attr = attrs.Attributes(map[string]string{"-": "value"}) - if !attr.HasDefault() { - t.Error("Should have default attr") - } -} - -func TestAttrClone(t *testing.T) { - t.Parallel() - orig := attrs.Attributes{} - clone := orig.Clone() - if !clone.IsEmpty() { - t.Error("Attrs must be empty") - } - - orig = attrs.Attributes(map[string]string{"": "0", "-": "1", "a": "b"}) - clone = orig.Clone() - if clone[""] != "0" || clone["-"] != "1" || clone["a"] != "b" || len(clone) != len(orig) { - t.Error("Wrong cloned map") - } - clone["a"] = "c" - if orig["a"] != "b" { - t.Error("Aliased map") - } -} - -func TestHasClass(t *testing.T) { - t.Parallel() - testcases := []struct { - classes string - class string - exp bool - }{ - {"", "", false}, - {"x", "", false}, - {"x", "x", true}, - {"x", "y", false}, - {"abc def ghi", "abc", true}, - {"abc def ghi", "def", true}, - {"abc def ghi", "ghi", true}, - {"ab de gi", "b", false}, - {"ab de gi", "d", false}, - } - for _, tc := range testcases { - var a attrs.Attributes - a = a.Set("class", tc.classes) - got := a.HasClass(tc.class) - if tc.exp != got { - t.Errorf("%q.HasClass(%q)=%v, but got %v", tc.classes, tc.class, tc.exp, got) - } - } -} Index: client/retrieve.go ================================================================== --- client/retrieve.go +++ client/retrieve.go @@ -24,11 +24,11 @@ "t73f.de/r/sx" "t73f.de/r/sx/sxreader" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/sexp" - "t73f.de/r/zsc/sz" + "t73f.de/r/zsx" ) var bsLF = []byte{'\n'} // QueryZettel returns a list of all Zettel based on the given query. @@ -103,11 +103,11 @@ hVals, err := sexp.ParseList(vals[2], "ys") if err != nil { return "", "", nil, err } metaList, err := parseMetaList(vals[3].(*sx.Pair)) - return sz.GoValue(qVals[1]), sz.GoValue(hVals[1]), metaList, err + return zsx.GoValue(qVals[1]), zsx.GoValue(hVals[1]), metaList, err } func parseMetaList(metaPair *sx.Pair) ([]api.ZidMetaRights, error) { var result []api.ZidMetaRights for node := metaPair; !sx.IsNil(node); { @@ -401,10 +401,44 @@ return api.MetaRights{ Meta: meta, Rights: rights, }, nil } + +// GetReferences returns all references / URIs of a given zettel. +// +// part must be one of "meta", "content", or "zettel". +func (c *Client) GetReferences(ctx context.Context, zid id.Zid, part string) (urls []string, err error) { + ub := c.NewURLBuilder('r').SetZid(zid) + if part != "" { + ub.AppendKVQuery(api.QueryKeyPart, part) + } + ub.AppendKVQuery(api.QueryKeyEncoding, api.EncodingData) // data encoding is more robust. + resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, ub, nil) + if err != nil { + return nil, err + } + defer func() { _ = resp.Body.Close() }() + rdr := sxreader.MakeReader(resp.Body) + obj, err := rdr.Read() + if resp.StatusCode != http.StatusOK { + return nil, statusToError(resp) + } + if err != nil { + return nil, err + } + seq, isSeq := sx.GetSequence(obj) + if !isSeq { + return nil, fmt.Errorf("not a sequence: %T/%v", obj, obj) + } + for val := range seq.Values() { + if s, isString := sx.GetString(val); isString { + urls = append(urls, s.GetValue()) + } + } + return urls, nil +} // GetVersionInfo returns version information of the Zettelstore that is used. func (c *Client) GetVersionInfo(ctx context.Context) (VersionInfo, error) { resp, err := c.buildAndExecuteRequest(ctx, http.MethodGet, c.NewURLBuilder('x'), nil) if err != nil { ADDED docs/fuzz.txt Index: docs/fuzz.txt ================================================================== --- /dev/null +++ docs/fuzz.txt @@ -0,0 +1,4 @@ +The source code contains some simple fuzzing tests. You should call them +regulary to make sure the the software will cope with unusual input. + +go test -fuzz=FuzzParseBlocks t73f.de/r/zsc/sz/zmk Index: domain/id/id.go ================================================================== --- domain/id/id.go +++ domain/id/id.go @@ -45,17 +45,18 @@ ZidHost = Zid(2) ZidOperatingSystem = Zid(3) ZidLicense = Zid(4) ZidAuthors = Zid(5) ZidDependencies = Zid(6) - ZidLog = Zid(7) - ZidMemory = Zid(8) - ZidSx = Zid(9) - ZidHTTP = Zid(10) - ZidAPI = Zid(11) - ZidWebUI = Zid(12) - ZidConsole = Zid(13) + ZidModules = Zid(7) + ZidLog = Zid(9) + ZidMemory = Zid(10) + ZidSx = Zid(11) + ZidHTTP = Zid(12) + ZidAPI = Zid(13) + ZidWebUI = Zid(14) + ZidConsole = Zid(15) ZidBoxManager = Zid(20) ZidZettel = Zid(21) ZidIndex = Zid(22) ZidQuery = Zid(23) ZidMetadataKey = Zid(90) DELETED domain/id/idgraph/digraph.go Index: domain/id/idgraph/digraph.go ================================================================== --- domain/id/idgraph/digraph.go +++ /dev/null @@ -1,243 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2023-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore Client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2023-present Detlef Stern -//----------------------------------------------------------------------------- - -// Package idgraph implements a graph of zettel identifier. -package idgraph - -import ( - "maps" - "slices" - - "t73f.de/r/zsc/domain/id" - "t73f.de/r/zsc/domain/id/idset" -) - -// Digraph relates zettel identifier in a directional way. -type Digraph map[id.Zid]*idset.Set - -// AddVertex adds an edge / vertex to the digraph. -func (dg Digraph) AddVertex(zid id.Zid) Digraph { - if dg == nil { - return Digraph{zid: nil} - } - if _, found := dg[zid]; !found { - dg[zid] = nil - } - return dg -} - -// RemoveVertex removes a vertex and all its edges from the digraph. -func (dg Digraph) RemoveVertex(zid id.Zid) { - if len(dg) > 0 { - delete(dg, zid) - for vertex, closure := range dg { - dg[vertex] = closure.Remove(zid) - } - } -} - -// AddEdge adds a connection from `zid1` to `zid2`. -// Both vertices must be added before. Otherwise the function may panic. -func (dg Digraph) AddEdge(fromZid, toZid id.Zid) Digraph { - if dg == nil { - return Digraph{fromZid: (*idset.Set)(nil).Add(toZid), toZid: nil} - } - dg[fromZid] = dg[fromZid].Add(toZid) - return dg -} - -// AddEgdes adds all given `Edge`s to the digraph. -// -// In contrast to `AddEdge` the vertices must not exist before. -func (dg Digraph) AddEgdes(edges EdgeSlice) Digraph { - if dg == nil { - if len(edges) == 0 { - return nil - } - dg = make(Digraph, len(edges)) - } - for _, edge := range edges { - dg = dg.AddVertex(edge.From) - dg = dg.AddVertex(edge.To) - dg = dg.AddEdge(edge.From, edge.To) - } - return dg -} - -// Equal returns true if both digraphs have the same vertices and edges. -func (dg Digraph) Equal(other Digraph) bool { - return maps.EqualFunc(dg, other, func(cg, co *idset.Set) bool { return cg.Equal(co) }) -} - -// Clone a digraph. -func (dg Digraph) Clone() Digraph { - if len(dg) == 0 { - return nil - } - copyDG := make(Digraph, len(dg)) - for vertex, closure := range dg { - copyDG[vertex] = closure.Clone() - } - return copyDG -} - -// HasVertex returns true, if `zid` is a vertex of the digraph. -func (dg Digraph) HasVertex(zid id.Zid) bool { - if len(dg) == 0 { - return false - } - _, found := dg[zid] - return found -} - -// Vertices returns the set of all vertices. -func (dg Digraph) Vertices() *idset.Set { - if len(dg) == 0 { - return nil - } - verts := idset.NewCap(len(dg)) - for vert := range dg { - verts.Add(vert) - } - return verts -} - -// Edges returns an unsorted slice of the edges of the digraph. -func (dg Digraph) Edges() (es EdgeSlice) { - for vert, closure := range dg { - closure.ForEach(func(next id.Zid) { - es = append(es, Edge{From: vert, To: next}) - }) - } - return es -} - -// Originators will return the set of all vertices that are not referenced -// a the to-part of an edge. -func (dg Digraph) Originators() *idset.Set { - if len(dg) == 0 { - return nil - } - origs := dg.Vertices() - for _, closure := range dg { - origs.ISubstract(closure) - } - return origs -} - -// Terminators returns the set of all vertices that does not reference -// other vertices. -func (dg Digraph) Terminators() (terms *idset.Set) { - for vert, closure := range dg { - if closure.IsEmpty() { - terms = terms.Add(vert) - } - } - return terms -} - -// TransitiveClosure calculates the sub-graph that is reachable from `zid`. -func (dg Digraph) TransitiveClosure(zid id.Zid) (tc Digraph) { - if len(dg) == 0 { - return nil - } - var marked *idset.Set - stack := []id.Zid{zid} - for pos := len(stack) - 1; pos >= 0; pos = len(stack) - 1 { - curr := stack[pos] - stack = stack[:pos] - if marked.Contains(curr) { - continue - } - tc = tc.AddVertex(curr) - dg[curr].ForEach(func(next id.Zid) { - tc = tc.AddVertex(next) - tc = tc.AddEdge(curr, next) - stack = append(stack, next) - }) - marked = marked.Add(curr) - } - return tc -} - -// ReachableVertices calculates the set of all vertices that are reachable -// from the given `zid`. -func (dg Digraph) ReachableVertices(zid id.Zid) (tc *idset.Set) { - if len(dg) == 0 { - return nil - } - stack := dg[zid].SafeSorted() - for last := len(stack) - 1; last >= 0; last = len(stack) - 1 { - curr := stack[last] - stack = stack[:last] - if tc.Contains(curr) { - continue - } - closure, found := dg[curr] - if !found { - continue - } - tc = tc.Add(curr) - closure.ForEach(func(next id.Zid) { - stack = append(stack, next) - }) - } - return tc -} - -// IsDAG returns a vertex and false, if the graph has a cycle containing the vertex. -func (dg Digraph) IsDAG() (id.Zid, bool) { - for vertex := range dg { - if dg.ReachableVertices(vertex).Contains(vertex) { - return vertex, false - } - } - return id.Invalid, true -} - -// Reverse returns a graph with reversed edges. -func (dg Digraph) Reverse() (revDg Digraph) { - for vertex, closure := range dg { - revDg = revDg.AddVertex(vertex) - closure.ForEach(func(next id.Zid) { - revDg = revDg.AddVertex(next) - revDg = revDg.AddEdge(next, vertex) - }) - } - return revDg -} - -// SortReverse returns a deterministic, topological, reverse sort of the -// digraph. -// -// Works only if digraph is a DAG. Otherwise the algorithm will not terminate -// or returns an arbitrary value. -func (dg Digraph) SortReverse() (sl []id.Zid) { - if len(dg) == 0 { - return nil - } - tempDg := dg.Clone() - for len(tempDg) > 0 { - terms := tempDg.Terminators() - if terms.IsEmpty() { - break - } - termSlice := terms.SafeSorted() - slices.Reverse(termSlice) - sl = append(sl, termSlice...) - terms.ForEach(func(t id.Zid) { - tempDg.RemoveVertex(t) - }) - } - return sl -} DELETED domain/id/idgraph/digraph_test.go Index: domain/id/idgraph/digraph_test.go ================================================================== --- domain/id/idgraph/digraph_test.go +++ /dev/null @@ -1,181 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2023-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore Client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2023-present Detlef Stern -//----------------------------------------------------------------------------- - -package idgraph_test - -import ( - "slices" - "testing" - - "t73f.de/r/zsc/domain/id" - "t73f.de/r/zsc/domain/id/idgraph" - "t73f.de/r/zsc/domain/id/idset" -) - -type zps = idgraph.EdgeSlice - -func createDigraph(pairs zps) (dg idgraph.Digraph) { - return dg.AddEgdes(pairs) -} - -func TestDigraphOriginators(t *testing.T) { - t.Parallel() - testcases := []struct { - name string - dg idgraph.EdgeSlice - orig *idset.Set - term *idset.Set - }{ - {"empty", nil, nil, nil}, - {"single", zps{{0, 1}}, idset.New(0), idset.New(1)}, - {"chain", zps{{0, 1}, {1, 2}, {2, 3}}, idset.New(0), idset.New(3)}, - } - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - dg := createDigraph(tc.dg) - if got := dg.Originators(); !tc.orig.Equal(got) { - t.Errorf("Originators: expected:\n%v, but got:\n%v", tc.orig, got) - } - if got := dg.Terminators(); !tc.term.Equal(got) { - t.Errorf("Termintors: expected:\n%v, but got:\n%v", tc.orig, got) - } - }) - } -} - -func TestDigraphReachableVertices(t *testing.T) { - t.Parallel() - testcases := []struct { - name string - pairs idgraph.EdgeSlice - start id.Zid - exp *idset.Set - }{ - {"nil", nil, 0, nil}, - {"0-2", zps{{1, 2}, {2, 3}}, 1, idset.New(2, 3)}, - {"1,2", zps{{1, 2}, {2, 3}}, 2, idset.New(3)}, - {"0-2,1-2", zps{{1, 2}, {2, 3}, {1, 3}}, 1, idset.New(2, 3)}, - {"0-2,1-2/1", zps{{1, 2}, {2, 3}, {1, 3}}, 2, idset.New(3)}, - {"0-2,1-2/2", zps{{1, 2}, {2, 3}, {1, 3}}, 3, nil}, - {"0-2,1-2,3*", zps{{1, 2}, {2, 3}, {1, 3}, {4, 4}}, 1, idset.New(2, 3)}, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - dg := createDigraph(tc.pairs) - if got := dg.ReachableVertices(tc.start); !got.Equal(tc.exp) { - t.Errorf("\n%v, but got:\n%v", tc.exp, got) - } - - }) - } -} - -func TestDigraphTransitiveClosure(t *testing.T) { - t.Parallel() - testcases := []struct { - name string - pairs idgraph.EdgeSlice - start id.Zid - exp idgraph.EdgeSlice - }{ - {"nil", nil, 0, nil}, - {"1-3", zps{{1, 2}, {2, 3}}, 1, zps{{1, 2}, {2, 3}}}, - {"1,2", zps{{1, 1}, {2, 3}}, 2, zps{{2, 3}}}, - {"0-2,1-2", zps{{1, 2}, {2, 3}, {1, 3}}, 1, zps{{1, 2}, {1, 3}, {2, 3}}}, - {"0-2,1-2/1", zps{{1, 2}, {2, 3}, {1, 3}}, 1, zps{{1, 2}, {1, 3}, {2, 3}}}, - {"0-2,1-2/2", zps{{1, 2}, {2, 3}, {1, 3}}, 2, zps{{2, 3}}}, - {"0-2,1-2,3*", zps{{1, 2}, {2, 3}, {1, 3}, {4, 4}}, 1, zps{{1, 2}, {1, 3}, {2, 3}}}, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - dg := createDigraph(tc.pairs) - if got := dg.TransitiveClosure(tc.start).Edges().Sort(); !got.Equal(tc.exp) { - t.Errorf("\n%v, but got:\n%v", tc.exp, got) - } - }) - } -} - -func TestIsDAG(t *testing.T) { - t.Parallel() - testcases := []struct { - name string - dg idgraph.EdgeSlice - exp bool - }{ - {"empty", nil, true}, - {"single-edge", zps{{1, 2}}, true}, - {"single-loop", zps{{1, 1}}, false}, - {"long-loop", zps{{1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 2}}, false}, - } - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - if zid, got := createDigraph(tc.dg).IsDAG(); got != tc.exp { - t.Errorf("expected %v, but got %v (%v)", tc.exp, got, zid) - } - }) - } -} - -func TestDigraphReverse(t *testing.T) { - t.Parallel() - testcases := []struct { - name string - dg idgraph.EdgeSlice - exp idgraph.EdgeSlice - }{ - {"empty", nil, nil}, - {"single-edge", zps{{1, 2}}, zps{{2, 1}}}, - {"single-loop", zps{{1, 1}}, zps{{1, 1}}}, - {"end-loop", zps{{1, 2}, {2, 2}}, zps{{2, 1}, {2, 2}}}, - {"long-loop", zps{{1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 2}}, zps{{2, 1}, {2, 5}, {3, 2}, {4, 3}, {5, 4}}}, - {"sect-loop", zps{{1, 2}, {2, 3}, {3, 4}, {4, 5}, {4, 2}}, zps{{2, 1}, {2, 4}, {3, 2}, {4, 3}, {5, 4}}}, - {"two-islands", zps{{1, 2}, {2, 3}, {4, 5}}, zps{{2, 1}, {3, 2}, {5, 4}}}, - {"direct-indirect", zps{{1, 2}, {1, 3}, {3, 2}}, zps{{2, 1}, {2, 3}, {3, 1}}}, - } - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - dg := createDigraph(tc.dg) - if got := dg.Reverse().Edges().Sort(); !got.Equal(tc.exp) { - t.Errorf("\n%v, but got:\n%v", tc.exp, got) - } - }) - } -} - -func TestDigraphSortReverse(t *testing.T) { - t.Parallel() - testcases := []struct { - name string - dg idgraph.EdgeSlice - exp []id.Zid - }{ - {"empty", nil, nil}, - {"single-edge", zps{{1, 2}}, []id.Zid{2, 1}}, - {"single-loop", zps{{1, 1}}, nil}, - {"end-loop", zps{{1, 2}, {2, 2}}, []id.Zid{}}, - {"long-loop", zps{{1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 2}}, []id.Zid{}}, - {"sect-loop", zps{{1, 2}, {2, 3}, {3, 4}, {4, 5}, {4, 2}}, []id.Zid{5}}, - {"two-islands", zps{{1, 2}, {2, 3}, {4, 5}}, []id.Zid{5, 3, 4, 2, 1}}, - {"direct-indirect", zps{{1, 2}, {1, 3}, {3, 2}}, []id.Zid{2, 3, 1}}, - } - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - if got := createDigraph(tc.dg).SortReverse(); !slices.Equal(got, tc.exp) { - t.Errorf("expected:\n%v, but got:\n%v", tc.exp, got) - } - }) - } -} DELETED domain/id/idgraph/edge.go Index: domain/id/idgraph/edge.go ================================================================== --- domain/id/idgraph/edge.go +++ /dev/null @@ -1,53 +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 idgraph - -import ( - "slices" - - "t73f.de/r/zsc/domain/id" -) - -// Edge is a pair of to vertices. -type Edge struct { - From, To id.Zid -} - -// EdgeSlice is a slice of Edges -type EdgeSlice []Edge - -// Equal return true if both slices are the same. -func (es EdgeSlice) Equal(other EdgeSlice) bool { - return slices.Equal(es, other) -} - -// Sort the slice. -func (es EdgeSlice) Sort() EdgeSlice { - slices.SortFunc(es, func(e1, e2 Edge) int { - if e1.From < e2.From { - return -1 - } - if e1.From > e2.From { - return 1 - } - if e1.To < e2.To { - return -1 - } - if e1.To > e2.To { - return 1 - } - return 0 - }) - return es -} Index: domain/meta/parse.go ================================================================== --- domain/meta/parse.go +++ domain/meta/parse.go @@ -18,11 +18,11 @@ "slices" "strings" "t73f.de/r/zero/set" "t73f.de/r/zsc/domain/id" - "t73f.de/r/zsc/input" + "t73f.de/r/zsx/input" ) // NewFromInput parses the meta data of a zettel. func NewFromInput(zid id.Zid, inp *input.Input) *Meta { if inp.Ch == '-' && inp.PeekN(0) == '-' && inp.PeekN(1) == '-' { Index: domain/meta/parse_test.go ================================================================== --- domain/meta/parse_test.go +++ domain/meta/parse_test.go @@ -18,11 +18,11 @@ "slices" "strings" "testing" "t73f.de/r/zsc/domain/meta" - "t73f.de/r/zsc/input" + "t73f.de/r/zsx/input" ) func parseMetaStr(src string) *meta.Meta { return meta.NewFromInput(testID, input.NewInput([]byte(src))) } Index: domain/meta/values.go ================================================================== --- domain/meta/values.go +++ domain/meta/values.go @@ -20,11 +20,11 @@ "strings" "time" zeroiter "t73f.de/r/zero/iter" "t73f.de/r/zsc/domain/id" - "t73f.de/r/zsc/input" + "t73f.de/r/zsx/input" ) // Value ist a single metadata value. type Value string Index: go.mod ================================================================== --- go.mod +++ go.mod @@ -1,10 +1,11 @@ module t73f.de/r/zsc go 1.24 require ( - t73f.de/r/sx v0.0.0-20250226205800-c12af029b6d3 - t73f.de/r/sxwebs v0.0.0-20250226210617-7bc3145c269b - t73f.de/r/webs v0.0.0-20250226210341-4a531b8bfb18 + 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 ) Index: go.sum ================================================================== --- go.sum +++ go.sum @@ -1,8 +1,10 @@ -t73f.de/r/sx v0.0.0-20250226205800-c12af029b6d3 h1:Jek4x1Qp59SWXI1enWVTeP1wxcVO96FuBpJBnnwOY98= -t73f.de/r/sx v0.0.0-20250226205800-c12af029b6d3/go.mod h1:hzg05uSCMk3D/DWaL0pdlowfL2aWQeGIfD1S04vV+Xg= -t73f.de/r/sxwebs v0.0.0-20250226210617-7bc3145c269b h1:X+9mMDd3fKML5SPcQk4n28oDGFUwqjDiSmQrH2LHZwI= -t73f.de/r/sxwebs v0.0.0-20250226210617-7bc3145c269b/go.mod h1:p+3JCSzNm9e+Yyub0ODRiLDeKaGVYWvBKYANZaAWYIA= -t73f.de/r/webs v0.0.0-20250226210341-4a531b8bfb18 h1:p7rOFBzP6FE/aYN5MUfmGDrKP1H1IFs6v19T7hm7rXI= -t73f.de/r/webs v0.0.0-20250226210341-4a531b8bfb18/go.mod h1:zk92hSKB4iWyT290+163seNzu350TA9XLATC9kOldqo= +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= DELETED input/entity.go Index: input/entity.go ================================================================== --- input/entity.go +++ /dev/null @@ -1,162 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2022-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2022-present Detlef Stern -//----------------------------------------------------------------------------- - -package input - -import ( - "html" - "unicode" -) - -// ScanEntity scans either a named or a numbered entity and returns it as a string. -// -// For numbered entities (like { or ģ) html.UnescapeString returns -// sometimes other values as expected, if the number is not well-formed. This -// may happen because of some strange HTML parsing rules. But these do not -// apply to Zettelmarkup. Therefore, I parse the number here in the code. -func (inp *Input) ScanEntity() (res string, success bool) { - if inp.Ch != '&' { - return "", false - } - pos := inp.Pos - inp.Next() - if inp.Ch == '#' { - inp.Next() - if inp.Ch == 'x' || inp.Ch == 'X' { - return inp.scanEntityBase16() - } - return inp.scanEntityBase10() - } - return inp.scanEntityNamed(pos) -} - -func (inp *Input) scanEntityBase16() (string, bool) { - inp.Next() - if inp.Ch == ';' { - return "", false - } - code := 0 - for { - switch ch := inp.Ch; ch { - case ';': - inp.Next() - if r := rune(code); isValidEntity(r) { - return string(r), true - } - return "", false - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - code = 16*code + int(ch-'0') - case 'a', 'b', 'c', 'd', 'e', 'f': - code = 16*code + int(ch-'a'+10) - case 'A', 'B', 'C', 'D', 'E', 'F': - code = 16*code + int(ch-'A'+10) - default: - return "", false - } - if code > unicode.MaxRune { - return "", false - } - inp.Next() - } -} - -func (inp *Input) scanEntityBase10() (string, bool) { - // Base 10 code - if inp.Ch == ';' { - return "", false - } - code := 0 - for { - switch ch := inp.Ch; ch { - case ';': - inp.Next() - if r := rune(code); isValidEntity(r) { - return string(r), true - } - return "", false - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - code = 10*code + int(ch-'0') - default: - return "", false - } - if code > unicode.MaxRune { - return "", false - } - inp.Next() - } -} -func (inp *Input) scanEntityNamed(pos int) (string, bool) { - for { - switch inp.Ch { - case EOS, '\n', '\r', '&': - return "", false - case ';': - inp.Next() - es := string(inp.Src[pos:inp.Pos]) - ues := html.UnescapeString(es) - if es == ues { - return "", false - } - return ues, true - default: - inp.Next() - } - } -} - -// isValidEntity checks if the given code is valid for an entity. -// -// According to https://html.spec.whatwg.org/multipage/syntax.html#character-references -// ""The numeric character reference forms described above are allowed to reference any code point -// excluding U+000D CR, noncharacters, and controls other than ASCII whitespace."" -func isValidEntity(r rune) bool { - // No C0 control and no "code point in the range U+007F DELETE to U+009F APPLICATION PROGRAM COMMAND, inclusive." - if r < ' ' || ('\u007f' <= r && r <= '\u009f') { - return false - } - - // If below any noncharacter code point, return true - // - // See: https://infra.spec.whatwg.org/#noncharacter - if r < '\ufdd0' { - return true - } - - // First range of noncharacter code points: "(...) in the range U+FDD0 to U+FDEF, inclusive" - if r <= '\ufdef' { - return false - } - - // Other noncharacter code points: - switch r { - case '\uFFFE', '\uFFFF', - '\U0001FFFE', '\U0001FFFF', - '\U0002FFFE', '\U0002FFFF', - '\U0003FFFE', '\U0003FFFF', - '\U0004FFFE', '\U0004FFFF', - '\U0005FFFE', '\U0005FFFF', - '\U0006FFFE', '\U0006FFFF', - '\U0007FFFE', '\U0007FFFF', - '\U0008FFFE', '\U0008FFFF', - '\U0009FFFE', '\U0009FFFF', - '\U000AFFFE', '\U000AFFFF', - '\U000BFFFE', '\U000BFFFF', - '\U000CFFFE', '\U000CFFFF', - '\U000DFFFE', '\U000DFFFF', - '\U000EFFFE', '\U000EFFFF', - '\U000FFFFE', '\U000FFFFF', - '\U0010FFFE', '\U0010FFFF': - return false - } - return true -} DELETED input/entity_test.go Index: input/entity_test.go ================================================================== --- input/entity_test.go +++ /dev/null @@ -1,64 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -//----------------------------------------------------------------------------- - -package input_test - -import ( - "testing" - - "t73f.de/r/zsc/input" -) - -func TestScanEntity(t *testing.T) { - t.Parallel() - var testcases = []struct { - text string - exp string - }{ - {"", ""}, - {"a", ""}, - {"&", "&"}, - {"!", "!"}, - {"3", "3"}, - {""", "\""}, - } - for id, tc := range testcases { - inp := input.NewInput([]byte(tc.text)) - got, ok := inp.ScanEntity() - if !ok { - if tc.exp != "" { - t.Errorf("ID=%d, text=%q: expected error, but got %q", id, tc.text, got) - } - if inp.Pos != 0 { - t.Errorf("ID=%d, text=%q: input position advances to %d", id, tc.text, inp.Pos) - } - continue - } - if tc.exp != got { - t.Errorf("ID=%d, text=%q: expected %q, but got %q", id, tc.text, tc.exp, got) - } - } -} - -func TestScanIllegalEntity(t *testing.T) { - t.Parallel() - testcases := []string{"", "a", "& Input →", " ", ""} - for i, tc := range testcases { - inp := input.NewInput([]byte(tc)) - got, ok := inp.ScanEntity() - if ok { - t.Errorf("%d: scanning %q was unexpected successful, got %q", i, tc, got) - continue - } - } -} DELETED input/input.go Index: input/input.go ================================================================== --- input/input.go +++ /dev/null @@ -1,157 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -//----------------------------------------------------------------------------- - -// Package input provides an abstraction for data to be read. -package input - -import "unicode/utf8" - -// Input is an abstract input source -type Input struct { - // Read-only, will never change - Src []byte // The source string - - // Read-only, will change - Ch rune // current character - Pos int // character position in src - readPos int // reading position (position after current character) -} - -// NewInput creates a new input source. -func NewInput(src []byte) *Input { - inp := &Input{Src: src} - inp.Next() - return inp -} - -// EOS = End of source -const EOS = rune(-1) - -// Next reads the next rune into inp.Ch and returns it too. -func (inp *Input) Next() rune { - if inp.readPos >= len(inp.Src) { - inp.Pos = len(inp.Src) - inp.Ch = EOS - return EOS - } - inp.Pos = inp.readPos - r, w := rune(inp.Src[inp.readPos]), 1 - if r >= utf8.RuneSelf { - r, w = utf8.DecodeRune(inp.Src[inp.readPos:]) - } - inp.readPos += w - inp.Ch = r - return r -} - -// Peek returns the rune following the most recently read rune without -// advancing. If end-of-source was already found peek returns EOS. -func (inp *Input) Peek() rune { - return inp.PeekN(0) -} - -// PeekN returns the n-th rune after the most recently read rune without -// advancing. If end-of-source was already found peek returns EOS. -func (inp *Input) PeekN(n int) rune { - pos := inp.readPos + n - if pos < len(inp.Src) { - r := rune(inp.Src[pos]) - if r >= utf8.RuneSelf { - r, _ = utf8.DecodeRune(inp.Src[pos:]) - } - if r == '\t' { - return ' ' - } - return r - } - return EOS -} - -// Accept checks if the given string is a prefix of the text to be parsed. -// If successful, advance position and current character. -// String must only contain bytes < 128. -// If not successful, everything remains as it is. -func (inp *Input) Accept(s string) bool { - pos := inp.Pos - remaining := len(inp.Src) - pos - if s == "" || len(s) > remaining { - return false - } - // According to internal documentation of bytes.Equal, the string() will not allocate any memory. - if readPos := pos + len(s); s == string(inp.Src[pos:readPos]) { - inp.readPos = readPos - inp.Next() - return true - } - return false -} - -// IsEOLEOS returns true if char is either EOS or EOL. -func IsEOLEOS(ch rune) bool { return ch == EOS || ch == '\n' || ch == '\r' } - -// EatEOL transforms both "\r" and "\r\n" into "\n". -func (inp *Input) EatEOL() { - switch inp.Ch { - case '\r': - if inp.Peek() == '\n' { - inp.Next() - } - inp.Ch = '\n' - inp.Next() - case '\n': - inp.Next() - } -} - -// SetPos allows to reset the read position. -func (inp *Input) SetPos(pos int) { - if inp.Pos != pos { - inp.readPos = pos - inp.Next() - } -} - -// SkipSpace reads while the current character is not a space character. -func (inp *Input) SkipSpace() { - for ch := inp.Ch; IsSpace(ch); { - ch = inp.Next() - } -} - -// SkipToEOL reads until the next end-of-line. -func (inp *Input) SkipToEOL() { - for { - switch inp.Ch { - case EOS, '\n', '\r': - return - } - inp.Next() - } -} - -// ScanLineContent reads the reaining input stream and interprets it as lines of text. -func (inp *Input) ScanLineContent() []byte { - result := make([]byte, 0, len(inp.Src)-inp.Pos+1) - for { - inp.EatEOL() - posL := inp.Pos - if inp.Ch == EOS { - return result - } - inp.SkipToEOL() - if len(result) > 0 { - result = append(result, '\n') - } - result = append(result, inp.Src[posL:inp.Pos]...) - } -} DELETED input/input_test.go Index: input/input_test.go ================================================================== --- input/input_test.go +++ /dev/null @@ -1,68 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -//----------------------------------------------------------------------------- - -// Package input_test provides some unit-tests for reading data. -package input_test - -import ( - "testing" - - "t73f.de/r/zsc/input" -) - -func TestEatEOL(t *testing.T) { - t.Parallel() - inp := input.NewInput(nil) - inp.EatEOL() - if inp.Ch != input.EOS { - t.Errorf("No EOS found: %q", inp.Ch) - } - if inp.Pos != 0 { - t.Errorf("Pos != 0: %d", inp.Pos) - } - - inp = input.NewInput([]byte("ABC")) - if inp.Ch != 'A' { - t.Errorf("First ch != 'A', got %q", inp.Ch) - } - inp.EatEOL() - if inp.Ch != 'A' { - t.Errorf("First ch != 'A', got %q", inp.Ch) - } -} - -func TestAccept(t *testing.T) { - t.Parallel() - testcases := []struct { - accept string - src string - acc bool - exp rune - }{ - {"", "", false, input.EOS}, - {"AB", "abc", false, 'a'}, - {"AB", "ABC", true, 'C'}, - {"AB", "AB", true, input.EOS}, - {"AB", "A", false, 'A'}, - } - for i, tc := range testcases { - inp := input.NewInput([]byte(tc.src)) - acc := inp.Accept(tc.accept) - if acc != tc.acc { - t.Errorf("%d: %q.Accept(%q) == %v, but got %v", i, tc.src, tc.accept, tc.acc, acc) - } - if got := inp.Ch; tc.exp != got { - t.Errorf("%d: %q.Accept(%q) should result in run %v, but got %v", i, tc.src, tc.accept, tc.exp, got) - } - } -} DELETED input/runes.go Index: input/runes.go ================================================================== --- input/runes.go +++ /dev/null @@ -1,30 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -//----------------------------------------------------------------------------- - -package input - -import "unicode" - -// IsSpace returns true if rune is a whitespace. -func IsSpace(ch rune) bool { - switch ch { - case ' ', '\t': - return true - case '\n', '\r', EOS: - return false - } - return unicode.IsSpace(ch) -} - -// IsSpace returns true if current character is a whitespace. -func (inp *Input) IsSpace() bool { return IsSpace(inp.Ch) } Index: shtml/const.go ================================================================== --- shtml/const.go +++ shtml/const.go @@ -66,11 +66,10 @@ SymUL = sx.MakeSymbol("ul") ) // Symbols for HTML attribute keys var ( - symAttrAlt = sx.MakeSymbol("alt") SymAttrClass = sx.MakeSymbol("class") SymAttrHref = sx.MakeSymbol("href") SymAttrID = sx.MakeSymbol("id") SymAttrLang = sx.MakeSymbol("lang") SymAttrOpen = sx.MakeSymbol("open") Index: shtml/shtml.go ================================================================== --- shtml/shtml.go +++ shtml/shtml.go @@ -21,13 +21,13 @@ "strings" "t73f.de/r/sx" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" - "t73f.de/r/zsc/attrs" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/sz" + "t73f.de/r/zsx" ) // Evaluator will transform a s-expression that encodes the zettel AST into an s-expression // that represents HTML. type Evaluator struct { @@ -57,20 +57,20 @@ func (ev *Evaluator) SetUnique(s string) { ev.unique = s } // IsValidName returns true, if name is a valid symbol name. func isValidName(s string) bool { return s != "" } -// EvaluateAttrbute transforms the given attributes into a HTML s-expression. -func EvaluateAttrbute(a attrs.Attributes) *sx.Pair { +// EvaluateAttributes transforms the given attributes into a HTML s-expression. +func EvaluateAttributes(a zsx.Attributes) *sx.Pair { if len(a) == 0 { return nil } plist := sx.Nil() keys := a.Keys() for i := len(keys) - 1; i >= 0; i-- { key := keys[i] - if key != attrs.DefaultAttribute && isValidName(key) { + if key != zsx.DefaultAttribute && isValidName(key) { plist = plist.Cons(sx.Cons(sx.MakeSymbol(key), sx.MakeString(a[key]))) } } if plist == nil { return nil @@ -185,11 +185,11 @@ env.endnotes = nil env.quoteNesting = 0 } // pushAttribute adds the current attributes to the environment. -func (env *Environment) pushAttributes(a attrs.Attributes) { +func (env *Environment) pushAttributes(a zsx.Attributes) { if value, ok := a.Get("lang"); ok { env.langStack.Push(value) } else { env.langStack.Dup() } @@ -236,11 +236,11 @@ } func (ev *Evaluator) bindMetadata() { ev.bind(sz.SymMeta, 0, ev.evalList) evalMetaString := func(args sx.Vector, env *Environment) sx.Object { - a := make(attrs.Attributes, 2). + a := make(zsx.Attributes, 2). Set("name", getSymbol(args[0], env).GetValue()). Set("content", getString(args[1], env).GetValue()) return ev.EvaluateMeta(a) } ev.bind(sz.SymTypeCredential, 2, evalMetaString) @@ -260,30 +260,30 @@ } s := sb.String() if len(s) > 0 { s = s[1:] } - a := make(attrs.Attributes, 2). + a := make(zsx.Attributes, 2). Set("name", getSymbol(args[0], env).GetValue()). Set("content", s) return ev.EvaluateMeta(a) } ev.bind(sz.SymTypeIDSet, 2, evalMetaSet) ev.bind(sz.SymTypeTagSet, 2, evalMetaSet) } // EvaluateMeta returns HTML meta object for an attribute. -func (ev *Evaluator) EvaluateMeta(a attrs.Attributes) *sx.Pair { - return sx.Nil().Cons(EvaluateAttrbute(a)).Cons(SymMeta) +func (ev *Evaluator) EvaluateMeta(a zsx.Attributes) *sx.Pair { + return sx.Nil().Cons(EvaluateAttributes(a)).Cons(SymMeta) } func (ev *Evaluator) bindBlocks() { - ev.bind(sz.SymBlock, 0, ev.evalList) - ev.bind(sz.SymPara, 0, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymBlock, 0, ev.evalList) + ev.bind(zsx.SymPara, 0, func(args sx.Vector, env *Environment) sx.Object { return ev.evalSlice(args, env).Cons(SymP) }) - ev.bind(sz.SymHeading, 5, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymHeading, 5, func(args sx.Vector, env *Environment) sx.Object { nLevel := getInt64(args[0], env) if nLevel <= 0 { env.err = fmt.Errorf("%v is a negative heading level", nLevel) return sx.Nil() } @@ -297,37 +297,57 @@ a = a.Set("id", ev.unique+fragment) } if result, _ := ev.EvaluateList(args[4:], env); result != nil { if len(a) > 0 { - result = result.Cons(EvaluateAttrbute(a)) + result = result.Cons(EvaluateAttributes(a)) } return result.Cons(headingSymbol) } return sx.MakeList(headingSymbol, sx.MakeString("")) }) - ev.bind(sz.SymThematic, 0, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymThematic, 0, func(args sx.Vector, env *Environment) sx.Object { result := sx.Nil() if len(args) > 0 { if attrList := getList(args[0], env); attrList != nil { - result = result.Cons(EvaluateAttrbute(sz.GetAttributes(attrList))) + result = result.Cons(EvaluateAttributes(zsx.GetAttributes(attrList))) } } return result.Cons(SymHR) }) - ev.bind(sz.SymListOrdered, 0, ev.makeListFn(SymOL)) - ev.bind(sz.SymListUnordered, 0, ev.makeListFn(SymUL)) - ev.bind(sz.SymDescription, 0, func(args sx.Vector, env *Environment) sx.Object { - if len(args) == 0 { + ev.bind(zsx.SymListOrdered, 1, ev.makeListFn(SymOL)) + ev.bind(zsx.SymListUnordered, 1, ev.makeListFn(SymUL)) + ev.bind(zsx.SymListQuote, 1, func(args sx.Vector, env *Environment) sx.Object { + if len(args) == 1 { + return sx.Nil() + } + var result sx.ListBuilder + result.Add(symBLOCKQUOTE) + if attrs := EvaluateAttributes(GetAttributes(args[0], env)); attrs != nil { + result.Add(attrs) + } + for _, elem := range args[1:] { + if quote, isPair := sx.GetPair(ev.Eval(elem, env)); isPair { + result.Add(quote.Cons(sxhtml.SymListSplice)) + } + } + return result.List() + }) + + ev.bind(zsx.SymDescription, 1, func(args sx.Vector, env *Environment) sx.Object { + if len(args) == 1 { return sx.Nil() } - var items sx.ListBuilder - items.Add(symDL) - for pos := 0; pos < len(args); pos++ { + var result sx.ListBuilder + result.Add(symDL) + if attrs := EvaluateAttributes(GetAttributes(args[0], env)); attrs != nil { + result.Add(attrs) + } + for pos := 1; pos < len(args); pos++ { term := ev.evalDescriptionTerm(getList(args[pos], env), env) - items.Add(term.Cons(symDT)) + result.Add(term.Cons(symDT)) pos++ if pos >= len(args) { break } ddBlock := getList(ev.Eval(args[pos], env), env) @@ -334,30 +354,17 @@ if ddBlock == nil { continue } for ddlst := range ddBlock.Values() { dditem := getList(ddlst, env) - items.Add(dditem.Cons(symDD)) - } - } - return items.List() - }) - ev.bind(sz.SymListQuote, 0, func(args sx.Vector, env *Environment) sx.Object { - if args == nil { - return sx.Nil() - } - var result sx.ListBuilder - result.Add(symBLOCKQUOTE) - for _, elem := range args { - if quote, isPair := sx.GetPair(ev.Eval(elem, env)); isPair { - result.Add(quote.Cons(sxhtml.SymListSplice)) + result.Add(dditem.Cons(symDD)) } } return result.List() }) - ev.bind(sz.SymTable, 1, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymTable, 1, func(args sx.Vector, env *Environment) sx.Object { thead := sx.Nil() if header := getList(args[0], env); !sx.IsNil(header) { thead = sx.Nil().Cons(ev.evalTableRow(symTH, header, env)).Cons(symTHEAD) } @@ -379,69 +386,71 @@ if table == nil { return sx.Nil() } return table.Cons(symTABLE) }) - ev.bind(sz.SymCell, 0, ev.makeCellFn("")) - ev.bind(sz.SymCellCenter, 0, ev.makeCellFn("center")) - ev.bind(sz.SymCellLeft, 0, ev.makeCellFn("left")) - ev.bind(sz.SymCellRight, 0, ev.makeCellFn("right")) - - ev.bind(sz.SymRegionBlock, 2, ev.makeRegionFn(SymDIV, true)) - ev.bind(sz.SymRegionQuote, 2, ev.makeRegionFn(symBLOCKQUOTE, false)) - ev.bind(sz.SymRegionVerse, 2, ev.makeRegionFn(SymDIV, false)) - - ev.bind(sz.SymVerbatimComment, 1, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymCell, 1, func(args sx.Vector, env *Environment) sx.Object { + tdata := ev.evalSlice(args[1:], env) + pattrs := getList(args[0], env) + if alignPairs := pattrs.Assoc(zsx.SymAttrAlign); alignPairs != nil { + if salign, isString := sx.GetString(alignPairs.Cdr()); isString { + a := zsx.GetAttributes(pattrs.RemoveAssoc(zsx.SymAttrAlign)) + // Since in Sz there are attributes of align:center|left|right, we can reuse the values. + a = a.AddClass(salign.GetValue()) + tdata = tdata.Cons(EvaluateAttributes(a)) + } + } + return tdata + }) + + ev.bind(zsx.SymRegionBlock, 2, ev.makeRegionFn(SymDIV, true)) + ev.bind(zsx.SymRegionQuote, 2, ev.makeRegionFn(symBLOCKQUOTE, false)) + ev.bind(zsx.SymRegionVerse, 2, ev.makeRegionFn(SymDIV, false)) + + ev.bind(zsx.SymVerbatimComment, 1, func(args sx.Vector, env *Environment) sx.Object { if GetAttributes(args[0], env).HasDefault() { if len(args) > 1 { if s := getString(args[1], env); s.GetValue() != "" { return sx.Nil().Cons(s).Cons(sxhtml.SymBlockComment) } } } return nil }) - ev.bind(sz.SymVerbatimEval, 2, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymVerbatimEval, 2, func(args sx.Vector, env *Environment) sx.Object { return evalVerbatim(GetAttributes(args[0], env).AddClass("zs-eval"), getString(args[1], env)) }) - ev.bind(sz.SymVerbatimHTML, 2, ev.evalHTML) - ev.bind(sz.SymVerbatimMath, 2, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymVerbatimHTML, 2, ev.evalHTML) + ev.bind(zsx.SymVerbatimMath, 2, func(args sx.Vector, env *Environment) sx.Object { return evalVerbatim(GetAttributes(args[0], env).AddClass("zs-math"), getString(args[1], env)) }) - ev.bind(sz.SymVerbatimCode, 2, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymVerbatimCode, 2, func(args sx.Vector, env *Environment) sx.Object { a := GetAttributes(args[0], env) content := getString(args[1], env) if a.HasDefault() { content = sx.MakeString(visibleReplacer.Replace(content.GetValue())) } return evalVerbatim(a, content) }) - ev.bind(sz.SymVerbatimZettel, 0, nilFn) - ev.bind(sz.SymBLOB, 3, func(args sx.Vector, env *Environment) sx.Object { - return evalBLOB(getList(args[0], env), getString(args[1], env), getString(args[2], env)) + ev.bind(zsx.SymVerbatimZettel, 0, nilFn) + ev.bind(zsx.SymBLOB, 4, func(args sx.Vector, env *Environment) sx.Object { + a := GetAttributes(args[0], env) + return evalBLOB(a, getList(args[1], env), getString(args[2], env), getString(args[3], env)) }) - ev.bind(sz.SymTransclude, 2, func(args sx.Vector, env *Environment) sx.Object { - ref, isPair := sx.GetPair(args[1]) - if !isPair { - return sx.Nil() - } - refKind := ref.Car() - if sx.IsNil(refKind) { - return sx.Nil() - } - if refValue := getString(ref.Tail().Car(), env); refValue.GetValue() != "" { - if refSym, isRefSym := sx.GetSymbol(refKind); isRefSym && refSym.IsEqualSymbol(sz.SymRefStateExternal) { - a := GetAttributes(args[0], env).Set("src", refValue.GetValue()).AddClass("external") + ev.bind(zsx.SymTransclude, 2, func(args sx.Vector, env *Environment) sx.Object { + if refSym, refValue := GetReference(args[1], env); refSym != nil { + if refSym.IsEqualSymbol(zsx.SymRefStateExternal) { + a := GetAttributes(args[0], env).Set("src", refValue).AddClass("external") // TODO: if len(args) > 2, add "alt" attr based on args[2:], as in SymEmbed - return sx.Nil().Cons(sx.Nil().Cons(EvaluateAttrbute(a)).Cons(SymIMG)).Cons(SymP) + return sx.Nil().Cons(sx.Nil().Cons(EvaluateAttributes(a)).Cons(SymIMG)).Cons(SymP) } return sx.MakeList( sxhtml.SymInlineComment, sx.MakeString("transclude"), - refKind, + refSym, sx.MakeString("->"), - refValue, + sx.MakeString(refValue), ) } return ev.evalSlice(args, env) }) } @@ -448,16 +457,21 @@ func (ev *Evaluator) makeListFn(sym *sx.Symbol) EvalFn { return func(args sx.Vector, env *Environment) sx.Object { var result sx.ListBuilder result.Add(sym) - for _, elem := range args { - item := sx.Nil().Cons(SymLI) - if res, isPair := sx.GetPair(ev.Eval(elem, env)); isPair { - item.ExtendBang(res) + if attrs := EvaluateAttributes(GetAttributes(args[0], env)); attrs != nil { + result.Add(attrs) + } + if len(args) > 1 { + for _, elem := range args[1:] { + item := sx.Nil().Cons(SymLI) + if res, isPair := sx.GetPair(ev.Eval(elem, env)); isPair { + item.ExtendBang(res) + } + result.Add(item) } - result.Add(item) } return result.List() } } @@ -479,19 +493,10 @@ for obj := range pairs.Values() { row.Add(sx.Cons(sym, ev.Eval(obj, env))) } return row.List() } -func (ev *Evaluator) makeCellFn(align string) EvalFn { - return func(args sx.Vector, env *Environment) sx.Object { - tdata := ev.evalSlice(args, env) - if align != "" { - tdata = tdata.Cons(EvaluateAttrbute(attrs.Attributes{"class": align})) - } - return tdata - } -} func (ev *Evaluator) makeRegionFn(sym *sx.Symbol, genericToClass bool) EvalFn { return func(args sx.Vector, env *Environment) sx.Object { a := GetAttributes(args[0], env) env.pushAttributes(a) @@ -502,11 +507,11 @@ } } var result sx.ListBuilder result.Add(sym) if len(a) > 0 { - result.Add(EvaluateAttrbute(a)) + result.Add(EvaluateAttributes(a)) } if region, isPair := sx.GetPair(args[1]); isPair { if evalRegion := ev.EvalPairList(region, env); evalRegion != nil { result.ExtendBang(evalRegion) } @@ -518,101 +523,84 @@ } return result.List() } } -func evalVerbatim(a attrs.Attributes, s sx.String) sx.Object { +func evalVerbatim(a zsx.Attributes, s sx.String) sx.Object { a = setProgLang(a) code := sx.Nil().Cons(s) - if al := EvaluateAttrbute(a); al != nil { + if al := EvaluateAttributes(a); al != nil { code = code.Cons(al) } code = code.Cons(symCODE) return sx.Nil().Cons(code).Cons(symPRE) } func (ev *Evaluator) bindInlines() { - ev.bind(sz.SymInline, 0, ev.evalList) - ev.bind(sz.SymText, 1, func(args sx.Vector, env *Environment) sx.Object { return getString(args[0], env) }) - ev.bind(sz.SymSoft, 0, func(sx.Vector, *Environment) sx.Object { return sx.MakeString(" ") }) - ev.bind(sz.SymHard, 0, func(sx.Vector, *Environment) sx.Object { return sx.Nil().Cons(symBR) }) + ev.bind(zsx.SymInline, 0, ev.evalList) + ev.bind(zsx.SymText, 1, func(args sx.Vector, env *Environment) sx.Object { return getString(args[0], env) }) + ev.bind(zsx.SymSoft, 0, func(sx.Vector, *Environment) sx.Object { return sx.MakeString(" ") }) + ev.bind(zsx.SymHard, 0, func(sx.Vector, *Environment) sx.Object { return sx.Nil().Cons(symBR) }) - ev.bind(sz.SymLinkInvalid, 2, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymLink, 2, func(args sx.Vector, env *Environment) sx.Object { a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() + refSym, refValue := GetReference(args[1], env) + switch refSym { + case sz.SymRefStateZettel, zsx.SymRefStateSelf, sz.SymRefStateFound, zsx.SymRefStateHosted, sz.SymRefStateBased: + return ev.evalLink(a.Set("href", refValue), refValue, args[2:], env) + + case zsx.SymRefStateExternal: + return ev.evalLink(a.Set("href", refValue).Add("rel", "external"), refValue, args[2:], env) + + case sz.SymRefStateQuery: + query := "?" + api.QueryKeyQuery + "=" + url.QueryEscape(refValue) + return ev.evalLink(a.Set("href", query), refValue, args[2:], env) + + case sz.SymRefStateBroken: + return ev.evalLink(a.AddClass("broken"), refValue, args[2:], env) + } + + // sz.SymRefStateInvalid or unknown var inline *sx.Pair if len(args) > 2 { inline = ev.evalSlice(args[2:], env) } if inline == nil { - inline = sx.Nil().Cons(ev.Eval(args[1], env)) + inline = sx.Nil().Cons(sx.MakeString(refValue)) } return inline.Cons(SymSPAN) }) - evalHREF := func(args sx.Vector, env *Environment) sx.Object { - a := GetAttributes(args[0], env) - env.pushAttributes(a) - defer env.popAttributes() - refValue := getString(args[1], env) - return ev.evalLink(a.Set("href", refValue.GetValue()), refValue, args[2:], env) - } - ev.bind(sz.SymLinkZettel, 2, evalHREF) - ev.bind(sz.SymLinkSelf, 2, evalHREF) - ev.bind(sz.SymLinkFound, 2, evalHREF) - ev.bind(sz.SymLinkBroken, 2, func(args sx.Vector, env *Environment) sx.Object { - a := GetAttributes(args[0], env) - env.pushAttributes(a) - defer env.popAttributes() - refValue := getString(args[1], env) - return ev.evalLink(a.AddClass("broken"), refValue, args[2:], env) - }) - ev.bind(sz.SymLinkHosted, 2, evalHREF) - ev.bind(sz.SymLinkBased, 2, evalHREF) - ev.bind(sz.SymLinkQuery, 2, func(args sx.Vector, env *Environment) sx.Object { - a := GetAttributes(args[0], env) - env.pushAttributes(a) - defer env.popAttributes() - refValue := getString(args[1], env) - query := "?" + api.QueryKeyQuery + "=" + url.QueryEscape(refValue.GetValue()) - return ev.evalLink(a.Set("href", query), refValue, args[2:], env) - }) - ev.bind(sz.SymLinkExternal, 2, func(args sx.Vector, env *Environment) sx.Object { - a := GetAttributes(args[0], env) - env.pushAttributes(a) - defer env.popAttributes() - refValue := getString(args[1], env) - return ev.evalLink(a.Set("href", refValue.GetValue()).Add("rel", "external"), refValue, args[2:], env) - }) - - ev.bind(sz.SymEmbed, 3, func(args sx.Vector, env *Environment) sx.Object { - ref := getList(args[1], env) - a := GetAttributes(args[0], env) - a = a.Set("src", getString(ref.Tail().Car(), env).GetValue()) + + ev.bind(zsx.SymEmbed, 3, func(args sx.Vector, env *Environment) sx.Object { + _, refValue := GetReference(args[1], env) + a := GetAttributes(args[0], env).Set("src", refValue) if len(args) > 3 { var sb strings.Builder flattenText(&sb, sx.MakeList(args[3:]...)) if d := sb.String(); d != "" { a = a.Set("alt", d) } } - return sx.MakeList(SymIMG, EvaluateAttrbute(a)) + return sx.MakeList(SymIMG, EvaluateAttributes(a)) }) - ev.bind(sz.SymEmbedBLOB, 3, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymEmbedBLOB, 3, func(args sx.Vector, env *Environment) sx.Object { a, syntax, data := GetAttributes(args[0], env), getString(args[1], env), getString(args[2], env) summary, hasSummary := a.Get(meta.KeySummary) if !hasSummary { summary = "" } return evalBLOB( + a, sx.MakeList(sxhtml.SymListSplice, sx.MakeString(summary)), syntax, data, ) }) - ev.bind(sz.SymCite, 2, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymCite, 2, func(args sx.Vector, env *Environment) sx.Object { a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() result := sx.Nil() if key := getString(args[1], env); key.GetValue() != "" { @@ -620,34 +608,34 @@ result = ev.evalSlice(args[2:], env).Cons(sx.MakeString(", ")) } result = result.Cons(key) } if len(a) > 0 { - result = result.Cons(EvaluateAttrbute(a)) + result = result.Cons(EvaluateAttributes(a)) } if result == nil { return nil } return result.Cons(SymSPAN) }) - ev.bind(sz.SymMark, 3, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymMark, 3, func(args sx.Vector, env *Environment) sx.Object { result := ev.evalSlice(args[3:], env) if !ev.noLinks { if fragment := getString(args[2], env).GetValue(); fragment != "" { - a := attrs.Attributes{"id": fragment + ev.unique} - return result.Cons(EvaluateAttrbute(a)).Cons(SymA) + a := zsx.Attributes{"id": fragment + ev.unique} + return result.Cons(EvaluateAttributes(a)).Cons(SymA) } } return result.Cons(SymSPAN) }) - ev.bind(sz.SymEndnote, 1, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymEndnote, 1, func(args sx.Vector, env *Environment) sx.Object { a := GetAttributes(args[0], env) env.pushAttributes(a) defer env.popAttributes() attrPlist := sx.Nil() if len(a) > 0 { - if attrs := EvaluateAttrbute(a); attrs != nil { + if attrs := EvaluateAttributes(a); attrs != nil { attrPlist = attrs.Tail() } } noteNum := strconv.Itoa(len(env.endnotes) + 1) @@ -661,41 +649,41 @@ href := sx.Nil().Cons(sx.MakeString(noteNum)).Cons(hrefAttr).Cons(SymA) supAttr := sx.Nil().Cons(sx.Cons(SymAttrID, sx.MakeString("fnref:"+noteID))).Cons(sxhtml.SymAttr) return sx.Nil().Cons(href).Cons(supAttr).Cons(symSUP) }) - ev.bind(sz.SymFormatDelete, 1, ev.makeFormatFn(symDEL)) - ev.bind(sz.SymFormatEmph, 1, ev.makeFormatFn(symEM)) - ev.bind(sz.SymFormatInsert, 1, ev.makeFormatFn(symINS)) - ev.bind(sz.SymFormatMark, 1, ev.makeFormatFn(symMARK)) - ev.bind(sz.SymFormatQuote, 1, ev.evalQuote) - ev.bind(sz.SymFormatSpan, 1, ev.makeFormatFn(SymSPAN)) - ev.bind(sz.SymFormatStrong, 1, ev.makeFormatFn(SymSTRONG)) - ev.bind(sz.SymFormatSub, 1, ev.makeFormatFn(symSUB)) - ev.bind(sz.SymFormatSuper, 1, ev.makeFormatFn(symSUP)) - - ev.bind(sz.SymLiteralComment, 1, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymFormatDelete, 1, ev.makeFormatFn(symDEL)) + ev.bind(zsx.SymFormatEmph, 1, ev.makeFormatFn(symEM)) + ev.bind(zsx.SymFormatInsert, 1, ev.makeFormatFn(symINS)) + ev.bind(zsx.SymFormatMark, 1, ev.makeFormatFn(symMARK)) + ev.bind(zsx.SymFormatQuote, 1, ev.evalQuote) + ev.bind(zsx.SymFormatSpan, 1, ev.makeFormatFn(SymSPAN)) + ev.bind(zsx.SymFormatStrong, 1, ev.makeFormatFn(SymSTRONG)) + ev.bind(zsx.SymFormatSub, 1, ev.makeFormatFn(symSUB)) + ev.bind(zsx.SymFormatSuper, 1, ev.makeFormatFn(symSUP)) + + ev.bind(zsx.SymLiteralComment, 1, func(args sx.Vector, env *Environment) sx.Object { if GetAttributes(args[0], env).HasDefault() { if len(args) > 1 { if s := getString(ev.Eval(args[1], env), env); s.GetValue() != "" { return sx.Nil().Cons(s).Cons(sxhtml.SymInlineComment) } } } return sx.Nil() }) - ev.bind(sz.SymLiteralInput, 2, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymLiteralInput, 2, func(args sx.Vector, env *Environment) sx.Object { return evalLiteral(args, nil, symKBD, env) }) - ev.bind(sz.SymLiteralMath, 2, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymLiteralMath, 2, func(args sx.Vector, env *Environment) sx.Object { a := GetAttributes(args[0], env).AddClass("zs-math") return evalLiteral(args, a, symCODE, env) }) - ev.bind(sz.SymLiteralOutput, 2, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymLiteralOutput, 2, func(args sx.Vector, env *Environment) sx.Object { return evalLiteral(args, nil, symSAMP, env) }) - ev.bind(sz.SymLiteralCode, 2, func(args sx.Vector, env *Environment) sx.Object { + ev.bind(zsx.SymLiteralCode, 2, func(args sx.Vector, env *Environment) sx.Object { return evalLiteral(args, nil, symCODE, env) }) } func (ev *Evaluator) makeFormatFn(sym *sx.Symbol) EvalFn { @@ -706,11 +694,11 @@ if val, hasClass := a.Get(""); hasClass { a = a.Remove("").AddClass(val) } res := ev.evalSlice(args[1:], env) if len(a) > 0 { - res = res.Cons(EvaluateAttrbute(a)) + res = res.Cons(EvaluateAttributes(a)) } return res.Cons(sym) } } @@ -739,19 +727,19 @@ lastPair.AppendBang(sx.MakeList(sxhtml.SymNoEscape, sx.MakeString(rightQ))) res = res.Cons(sx.MakeList(sxhtml.SymNoEscape, sx.MakeString(leftQ))) } } if len(a) > 0 { - res = res.Cons(EvaluateAttrbute(a)) + res = res.Cons(EvaluateAttributes(a)) return res.Cons(SymSPAN) } return res.Cons(sxhtml.SymListSplice) } var visibleReplacer = strings.NewReplacer(" ", "\u2423") -func evalLiteral(args sx.Vector, a attrs.Attributes, sym *sx.Symbol, env *Environment) sx.Object { +func evalLiteral(args sx.Vector, a zsx.Attributes, sym *sx.Symbol, env *Environment) sx.Object { if a == nil { a = GetAttributes(args[0], env) } a = setProgLang(a) literal := getString(args[1], env).GetValue() @@ -759,15 +747,15 @@ a = a.RemoveDefault() literal = visibleReplacer.Replace(literal) } res := sx.Nil().Cons(sx.MakeString(literal)) if len(a) > 0 { - res = res.Cons(EvaluateAttrbute(a)) + res = res.Cons(EvaluateAttributes(a)) } return res.Cons(sym) } -func setProgLang(a attrs.Attributes) attrs.Attributes { +func setProgLang(a zsx.Attributes) zsx.Attributes { if val, found := a.Get(""); found { a = a.AddClass("language-" + val).Remove("") } return a } @@ -777,27 +765,27 @@ return sx.Nil().Cons(s).Cons(sxhtml.SymNoEscape) } return nil } -func evalBLOB(description *sx.Pair, syntax, data sx.String) sx.Object { +func evalBLOB(a zsx.Attributes, description *sx.Pair, syntax, data sx.String) sx.Object { if data.GetValue() == "" { return sx.Nil() } switch syntax.GetValue() { case "": return sx.Nil() case meta.ValueSyntaxSVG: return sx.Nil().Cons(sx.Nil().Cons(data).Cons(sxhtml.SymNoEscape)).Cons(SymP) default: - imgAttr := sx.Nil().Cons(sx.Cons(SymAttrSrc, sx.MakeString("data:image/"+syntax.GetValue()+";base64,"+data.GetValue()))) + a = a.Add("src", "data:image/"+syntax.GetValue()+";base64,"+data.GetValue()) var sb strings.Builder flattenText(&sb, description) if d := sb.String(); d != "" { - imgAttr = imgAttr.Cons(sx.Cons(symAttrAlt, sx.MakeString(d))) + a = a.Add("alt", d) } - return sx.Nil().Cons(sx.Nil().Cons(imgAttr.Cons(sxhtml.SymAttr)).Cons(SymIMG)).Cons(SymP) + return sx.Nil().Cons(sx.Nil().Cons(EvaluateAttributes(a)).Cons(SymIMG)).Cons(SymP) } } func flattenText(sb *strings.Builder, lst *sx.Pair) { for elem := range lst.Values() { @@ -883,19 +871,19 @@ return result.List() } return nil } -func (ev *Evaluator) evalLink(a attrs.Attributes, refValue sx.String, inline sx.Vector, env *Environment) sx.Object { +func (ev *Evaluator) evalLink(a zsx.Attributes, refValue string, inline sx.Vector, env *Environment) sx.Object { result := ev.evalSlice(inline, env) if len(inline) == 0 { - result = sx.Nil().Cons(refValue) + result = sx.Nil().Cons(sx.MakeString(refValue)) } if ev.noLinks { return result.Cons(SymSPAN) } - return result.Cons(EvaluateAttrbute(a)).Cons(SymA) + return result.Cons(EvaluateAttributes(a)).Cons(SymA) } func getSymbol(obj sx.Object, env *Environment) *sx.Symbol { if env.err == nil { if sym, ok := sx.GetSymbol(obj); ok { @@ -904,18 +892,17 @@ env.err = fmt.Errorf("%v/%T is not a symbol", obj, obj) } return sx.MakeSymbol("???") } func getString(val sx.Object, env *Environment) sx.String { - if env.err != nil { - return sx.String{} - } - if s, ok := sx.GetString(val); ok { - return s - } - env.err = fmt.Errorf("%v/%T is not a string", val, val) - return sx.String{} + if env.err == nil { + if s, ok := sx.GetString(val); ok { + return s + } + env.err = fmt.Errorf("%v/%T is not a string", val, val) + } + return sx.MakeString("") } func getList(val sx.Object, env *Environment) *sx.Pair { if env.err == nil { if res, isPair := sx.GetPair(val); isPair { return res @@ -935,12 +922,26 @@ return -1017 } // GetAttributes evaluates the given arg in the given environment and returns // the contained attributes. -func GetAttributes(arg sx.Object, env *Environment) attrs.Attributes { - return sz.GetAttributes(getList(arg, env)) +func GetAttributes(arg sx.Object, env *Environment) zsx.Attributes { + return zsx.GetAttributes(getList(arg, env)) +} + +// GetReference returns the reference symbol and the reference value of a reference pair. +func GetReference(val sx.Object, env *Environment) (*sx.Symbol, string) { + if env.err == nil { + if p := getList(val, env); env.err == nil { + sym, val := sz.GetReference(p) + if sym != nil { + return sym, val + } + env.err = fmt.Errorf("%v/%T is not a reference", val, val) + } + } + return nil, "" } var unsafeSnippets = []string{ " id.LengthZid && s[id.LengthZid] == '#' { + zidPart := s[:id.LengthZid] + if _, err := id.Parse(zidPart); err == nil { + if u, err2 := url.Parse(s); err2 != nil || u.String() != s { + return MakeReference(zsx.SymRefStateInvalid, s) + } + return MakeReference(SymRefStateZettel, s) + } + if zidPart == "00000000000000" { + return MakeReference(zsx.SymRefStateInvalid, s) + } + } + if strings.HasPrefix(s, api.QueryPrefix) { + return MakeReference(SymRefStateQuery, s[len(api.QueryPrefix):]) + } + if strings.HasPrefix(s, "//") { + if u, err := url.Parse(s[1:]); err == nil { + if u.Scheme == "" && u.Opaque == "" && u.Host == "" && u.User == nil { + if u.String() == s[1:] { + return MakeReference(SymRefStateBased, s[1:]) + } + return MakeReference(zsx.SymRefStateInvalid, s) + } + } + } + + if s == "" { + return MakeReference(zsx.SymRefStateInvalid, s) + } + u, err := url.Parse(s) + if err != nil || u.String() != s { + return MakeReference(zsx.SymRefStateInvalid, s) + } + sym := zsx.SymRefStateExternal + if u.Scheme == "" && u.Opaque == "" && u.Host == "" && u.User == nil { + if s[0] == '#' { + sym = zsx.SymRefStateSelf + } else { + sym = zsx.SymRefStateHosted + } + } + return MakeReference(sym, s) +} ADDED sz/ref_test.go Index: sz/ref_test.go ================================================================== --- /dev/null +++ sz/ref_test.go @@ -0,0 +1,76 @@ +// ----------------------------------------------------------------------------- +// Copyright (c) 2020-present Detlef Stern +// +// This file is part of zettelstore-client. +// +// Zettelstore client is licensed under the latest version of the EUPL +// (European Union Public License). Please see file LICENSE.txt for your rights +// and obligations under this license. +// +// SPDX-License-Identifier: EUPL-1.2 +// SPDX-FileCopyrightText: 2020-present Detlef Stern +// ----------------------------------------------------------------------------- + +package sz_test + +import ( + "testing" + + "t73f.de/r/zsc/sz" +) + +func TestParseReference(t *testing.T) { + t.Parallel() + testcases := []struct { + s string + exp string + }{ + {"", `(INVALID "")`}, + {"abc", `(HOSTED "abc")`}, + {"abc def", `(INVALID "abc def")`}, + {"/hosted", `(HOSTED "/hosted")`}, + {"/hosted ref", `(INVALID "/hosted ref")`}, + {"./", `(HOSTED "./")`}, + {"./12345678901234", `(HOSTED "./12345678901234")`}, + {"../", `(HOSTED "../")`}, + {"../12345678901234", `(HOSTED "../12345678901234")`}, + {"abc#frag", `(HOSTED "abc#frag")`}, + {"abc#frag space", `(INVALID "abc#frag space")`}, + {"abc#", `(INVALID "abc#")`}, + {"abc# ", `(INVALID "abc# ")`}, + {"/hosted#frag", `(HOSTED "/hosted#frag")`}, + {"./#frag", `(HOSTED "./#frag")`}, + {"./12345678901234#frag", `(HOSTED "./12345678901234#frag")`}, + {"../#frag", `(HOSTED "../#frag")`}, + {"../12345678901234#frag", `(HOSTED "../12345678901234#frag")`}, + {"#frag", `(SELF "#frag")`}, + {"#", `(INVALID "#")`}, + {"# ", `(INVALID "# ")`}, + {"https://t73f.de", `(EXTERNAL "https://t73f.de")`}, + {"https://t73f.de/12345678901234", `(EXTERNAL "https://t73f.de/12345678901234")`}, + {"http://t73f.de/1234567890", `(EXTERNAL "http://t73f.de/1234567890")`}, + {"mailto:ds@zettelstore.de", `(EXTERNAL "mailto:ds@zettelstore.de")`}, + {",://", `(INVALID ",://")`}, + + // ZS specific + {"00000000000000", `(INVALID "00000000000000")`}, + {"00000000000000#frag", `(INVALID "00000000000000#frag")`}, + {"12345678901234", `(ZETTEL "12345678901234")`}, + {"12345678901234#frag", `(ZETTEL "12345678901234#frag")`}, + {"12345678901234#", `(INVALID "12345678901234#")`}, + {"12345678901234# space", `(INVALID "12345678901234# space")`}, + {"12345678901234#frag ", `(INVALID "12345678901234#frag ")`}, + {"12345678901234#frag space", `(INVALID "12345678901234#frag space")`}, + {"query:role:zettel LIMIT 13", `(QUERY "role:zettel LIMIT 13")`}, + {"//based", `(BASED "/based")`}, + {"//based#frag", `(BASED "/based#frag")`}, + {"//based#", `(INVALID "//based#")`}, + } + for _, tc := range testcases { + t.Run(tc.s, func(t *testing.T) { + if got := sz.ScanReference(tc.s); got.String() != tc.exp { + t.Errorf("%q should be %q, but got %q", tc.s, tc.exp, got) + } + }) + } +} Index: sz/sz.go ================================================================== --- sz/sz.go +++ sz/sz.go @@ -14,46 +14,12 @@ // Package sz contains zettel data handling as sx expressions. package sz import ( "t73f.de/r/sx" - "t73f.de/r/zsc/attrs" -) - -// GetAttributes traverses a s-expression list and returns an attribute structure. -func GetAttributes(seq *sx.Pair) (result attrs.Attributes) { - for obj := range seq.Values() { - pair, isPair := sx.GetPair(obj) - if !isPair || pair == nil { - continue - } - key := pair.Car() - if !key.IsAtom() { - continue - } - val := pair.Cdr() - if tail, isTailPair := sx.GetPair(val); isTailPair { - val = tail.Car() - } - if !val.IsAtom() { - continue - } - result = result.Set(GoValue(key), GoValue(val)) - } - return result -} - -// GoValue returns the string value of the sx.Object suitable for Go processing. -func GoValue(obj sx.Object) string { - switch o := obj.(type) { - case sx.String: - return o.GetValue() - case *sx.Symbol: - return o.GetValue() - } - return obj.String() -} + "t73f.de/r/zsx" +) // GetMetaContent returns the metadata and the content of a sz encoded zettel. func GetMetaContent(zettel sx.Object) (Meta, *sx.Pair) { if pair, isPair := sx.GetPair(zettel); isPair { m := pair.Car() @@ -120,11 +86,11 @@ } // GetString return the metadata string value associated with the given key. func (m Meta) GetString(key string) string { if v, found := m[key]; found { - return GoValue(v.Value) + return zsx.GoValue(v.Value) } return "" } // GetPair return the metadata value associated with the given key, @@ -135,30 +101,5 @@ return pair } } return nil } - -// MapRefStateToLink maps a reference state symbol to a link symbol. -func MapRefStateToLink(symRefState *sx.Symbol) *sx.Symbol { - if sym, found := mapRefStateLink[symRefState]; found { - return sym - } - return SymLinkInvalid -} - -var mapRefStateLink = map[*sx.Symbol]*sx.Symbol{ - SymRefStateInvalid: SymLinkInvalid, - SymRefStateZettel: SymLinkZettel, - SymRefStateSelf: SymLinkSelf, - SymRefStateFound: SymLinkFound, - SymRefStateBroken: SymLinkBroken, - SymRefStateHosted: SymLinkHosted, - SymRefStateBased: SymLinkBased, - SymRefStateQuery: SymLinkQuery, - SymRefStateExternal: SymLinkExternal, -} - -// IsBreakSym return true if the object is either a soft or a hard break symbol. -func IsBreakSym(obj sx.Object) bool { - return SymSoft.IsEqual(obj) || SymHard.IsEqual(obj) -} DELETED sz/walk.go Index: sz/walk.go ================================================================== --- sz/walk.go +++ /dev/null @@ -1,222 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2024-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2024-present Detlef Stern -//----------------------------------------------------------------------------- - -package sz - -import ( - "t73f.de/r/sx" -) - -// Visitor is walking the sx-based AST. -type Visitor interface { - VisitBefore(node *sx.Pair, env *sx.Pair) (sx.Object, bool) - VisitAfter(node *sx.Pair, env *sx.Pair) sx.Object -} - -// Walk a sx-based AST through a Visitor. -func Walk(v Visitor, node *sx.Pair, env *sx.Pair) sx.Object { - if node == nil { - return nil - } - if result, ok := v.VisitBefore(node, env); ok { - return result - } - - if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol { - if fn, found := mapChildrenWalk[sym]; found { - node = fn(v, node, env) - } - } - return v.VisitAfter(node, env) -} - -var mapChildrenWalk map[*sx.Symbol]func(Visitor, *sx.Pair, *sx.Pair) *sx.Pair - -func init() { - mapChildrenWalk = map[*sx.Symbol]func(Visitor, *sx.Pair, *sx.Pair) *sx.Pair{ - SymBlock: walkChildrenTail, - SymPara: walkChildrenTail, - SymRegionBlock: walkChildrenRegion, - SymRegionQuote: walkChildrenRegion, - SymRegionVerse: walkChildrenRegion, - SymHeading: walkChildrenHeading, - SymListOrdered: walkChildrenTail, - SymListUnordered: walkChildrenTail, - SymListQuote: walkChildrenTail, - SymDescription: walkChildrenDescription, - SymTable: walkChildrenTable, - SymCell: walkChildrenTail, - SymCellCenter: walkChildrenTail, - SymCellLeft: walkChildrenTail, - SymCellRight: walkChildrenTail, - SymTransclude: walkChildrenInlines4, - - SymInline: walkChildrenTail, - SymEndnote: walkChildrenInlines3, - SymMark: walkChildrenMark, - SymLinkBased: walkChildrenInlines4, - SymLinkBroken: walkChildrenInlines4, - SymLinkExternal: walkChildrenInlines4, - SymLinkFound: walkChildrenInlines4, - SymLinkHosted: walkChildrenInlines4, - SymLinkInvalid: walkChildrenInlines4, - SymLinkQuery: walkChildrenInlines4, - SymLinkSelf: walkChildrenInlines4, - SymLinkZettel: walkChildrenInlines4, - SymEmbed: walkChildrenEmbed, - SymCite: walkChildrenInlines4, - SymFormatDelete: walkChildrenInlines3, - SymFormatEmph: walkChildrenInlines3, - SymFormatInsert: walkChildrenInlines3, - SymFormatMark: walkChildrenInlines3, - SymFormatQuote: walkChildrenInlines3, - SymFormatStrong: walkChildrenInlines3, - SymFormatSpan: walkChildrenInlines3, - SymFormatSub: walkChildrenInlines3, - SymFormatSuper: walkChildrenInlines3, - } -} - -func walkChildrenTail(v Visitor, node *sx.Pair, env *sx.Pair) *sx.Pair { - hasNil := false - for n := range node.Tail().Pairs() { - obj := Walk(v, n.Head(), env) - if sx.IsNil(obj) { - hasNil = true - } - n.SetCar(obj) - } - if !hasNil { - return node - } - for n := node; ; { - next := n.Tail() - if next == nil { - break - } - if sx.IsNil(next.Car()) { - n.SetCdr(next.Cdr()) - continue - } - n = next - } - return node -} - -func walkChildrenList(v Visitor, lst *sx.Pair, env *sx.Pair) *sx.Pair { - hasNil := false - for n := range lst.Pairs() { - obj := Walk(v, n.Head(), env) - if sx.IsNil(obj) { - hasNil = true - } - n.SetCar(obj) - } - if !hasNil { - return lst - } - var result sx.ListBuilder - for obj := range lst.Values() { - if !sx.IsNil(obj) { - result.Add(obj) - } - } - return result.List() -} - -func walkChildrenRegion(v Visitor, node *sx.Pair, env *sx.Pair) *sx.Pair { - // sym := node.Car() - next := node.Tail() - // attrs := next.Car() - next = next.Tail() - next.SetCar(walkChildrenList(v, next.Head(), env)) - next.SetCdr(walkChildrenList(v, next.Tail(), env)) - return node -} - -func walkChildrenHeading(v Visitor, node *sx.Pair, env *sx.Pair) *sx.Pair { - // sym := node.Car() - next := node.Tail() - // level := next.Car() - next = next.Tail() - // attrs := next.Car() - next = next.Tail() - // slug := next.Car() - next = next.Tail() - // fragment := next.Car() - next.SetCdr(walkChildrenList(v, next.Tail(), env)) - return node -} - -func walkChildrenDescription(v Visitor, dn *sx.Pair, env *sx.Pair) *sx.Pair { - for n := dn.Tail(); n != nil; n = n.Tail() { - n.SetCar(walkChildrenList(v, n.Head(), env)) - n = n.Tail() - if n == nil { - break - } - n.SetCar(Walk(v, n.Head(), env)) - } - return dn -} - -func walkChildrenTable(v Visitor, tn *sx.Pair, env *sx.Pair) *sx.Pair { - for row := range tn.Tail().Pairs() { - row.SetCar(walkChildrenList(v, row.Head(), env)) - } - return tn -} - -func walkChildrenMark(v Visitor, mn *sx.Pair, env *sx.Pair) *sx.Pair { - // sym := mn.Car() - next := mn.Tail() - // mark := next.Car() - next = next.Tail() - // slug := next.Car() - next = next.Tail() - // fragment := next.Car() - next.SetCdr(walkChildrenList(v, next.Tail(), env)) - return mn -} - -func walkChildrenEmbed(v Visitor, en *sx.Pair, env *sx.Pair) *sx.Pair { - // sym := en.Car() - next := en.Tail() - // attr := next.Car() - next = next.Tail() - // ref := next.Car() - next = next.Tail() - // syntax := next.Car() - - // text-list := next.Tail() - next.SetCdr(walkChildrenList(v, next.Tail(), env)) - return en -} - -func walkChildrenInlines4(v Visitor, ln *sx.Pair, env *sx.Pair) *sx.Pair { - // sym := ln.Car() - next := ln.Tail() - // attrs := next.Car() - next = next.Tail() - // val3 := next.Car() - next.SetCdr(walkChildrenList(v, next.Tail(), env)) - return ln -} - -func walkChildrenInlines3(v Visitor, node *sx.Pair, env *sx.Pair) *sx.Pair { - // sym := node.Car() - next := node.Tail() // Attrs - // attrs := next.Car() - next.SetCdr(walkChildrenList(v, next.Tail(), env)) - return node -} Index: sz/zmk/block.go ================================================================== --- sz/zmk/block.go +++ sz/zmk/block.go @@ -15,16 +15,30 @@ import ( "fmt" "t73f.de/r/sx" - "t73f.de/r/zsc/input" - "t73f.de/r/zsc/sz" + "t73f.de/r/zsx" + "t73f.de/r/zsx/input" ) // parseBlock parses one block. -func (cp *zmkP) parseBlock(lastPara *sx.Pair) (res *sx.Pair, cont bool) { +func (cp *Parser) parseBlock(blocksBuilder *sx.ListBuilder, lastPara *sx.Pair) *sx.Pair { + bn, cont := cp.parseBlock0(lastPara) + if bn != nil { + blocksBuilder.Add(bn) + } + if cont { + return lastPara + } + if bn.Car().IsEqual(zsx.SymPara) { + return bn + } + return nil +} + +func (cp *Parser) parseBlock0(lastPara *sx.Pair) (res *sx.Pair, cont bool) { inp := cp.inp pos := inp.Pos if cp.nestingLevel <= maxNestingLevel { cp.nestingLevel++ defer func() { cp.nestingLevel-- }() @@ -68,11 +82,11 @@ cp.lists = nil cp.descrl = nil bn, success = cp.parseRow(), true case '{': cp.clearStacked() - bn, success = parseTransclusion(inp) + bn, success = cp.parseTransclusion() } if success { return bn, false } @@ -85,11 +99,11 @@ } else if lastPara != nil { lastPair := lastPara.LastPair() lastPair.ExtendBang(ins) return nil, true } - return sz.MakePara(ins), false + return zsx.MakeParaList(ins), false } func startsWithSpaceSoftBreak(ins *sx.Pair) bool { if ins == nil { return false @@ -105,11 +119,11 @@ pair1, isPair1 := sx.GetPair(next.Car()) if pair1 == nil || !isPair1 { return false } - if pair0.Car().IsEqual(sz.SymText) && sz.IsBreakSym(pair1.Car()) { + if pair0.Car().IsEqual(zsx.SymText) && isBreakSym(pair1.Car()) { if args := pair0.Tail(); args != nil { if val, isString := sx.GetString(args.Car()); isString { for _, ch := range val.GetValue() { if !input.IsSpace(ch) { return false @@ -122,41 +136,41 @@ return false } var symSeparator = sx.MakeSymbol("sEpArAtOr") -func (cp *zmkP) cleanupListsAfterEOL() { +func (cp *Parser) cleanupListsAfterEOL() { for _, l := range cp.lists { l.LastPair().Head().LastPair().AppendBang(sx.Cons(symSeparator, nil)) } if descrl := cp.descrl; descrl != nil { - if lastPair, pos := lastPairPos(descrl); pos > 1 && pos%2 == 0 { + if lastPair, pos := lastPairPos(descrl); pos > 2 && pos%2 != 0 { lastPair.Head().LastPair().AppendBang(sx.Cons(symSeparator, nil)) } } } // parseColon determines which element should be parsed. -func (cp *zmkP) parseColon() (*sx.Pair, bool) { +func (cp *Parser) parseColon() (*sx.Pair, bool) { inp := cp.inp if inp.PeekN(1) == ':' { cp.clearStacked() return cp.parseRegion() } return cp.parseDefDescr() } // parsePara parses paragraphed inline material as a sx List. -func (cp *zmkP) parsePara() *sx.Pair { +func (cp *Parser) parsePara() *sx.Pair { var lb sx.ListBuilder for { in := cp.parseInline() if in == nil { return lb.List() } lb.Add(in) - if sz.IsBreakSym(in.Car()) { + if isBreakSym(in.Car()) { ch := cp.inp.Ch switch ch { // Must contain all cases from above switch in parseBlock. case input.EOS, '\n', '\r', '@', '`', runeModGrave, '%', '~', '$', '"', '<', '=', '-', '*', '#', '>', ';', ':', ' ', '|', '{': return lb.List() @@ -188,19 +202,19 @@ return nil, false } var sym *sx.Symbol switch fch { case '@': - sym = sz.SymVerbatimZettel + sym = zsx.SymVerbatimZettel case '`', runeModGrave: - sym = sz.SymVerbatimCode + sym = zsx.SymVerbatimCode case '%': - sym = sz.SymVerbatimComment + sym = zsx.SymVerbatimComment case '~': - sym = sz.SymVerbatimEval + sym = zsx.SymVerbatimEval case '$': - sym = sz.SymVerbatimMath + sym = zsx.SymVerbatimMath default: panic(fmt.Sprintf("%q is not a verbatim char", fch)) } content := make([]byte, 0, 512) for { @@ -208,11 +222,11 @@ posL := inp.Pos switch inp.Ch { case fch: if countDelim(inp, fch) >= cnt { inp.SkipToEOL() - return sz.MakeVerbatim(sym, attrs, string(content)), true + return zsx.MakeVerbatim(sym, attrs, string(content)), true } inp.SetPos(posL) case input.EOS: return nil, false } @@ -223,11 +237,11 @@ content = append(content, inp.Src[posL:inp.Pos]...) } } // parseRegion parses a block region. -func (cp *zmkP) parseRegion() (*sx.Pair, bool) { +func (cp *Parser) parseRegion() (*sx.Pair, bool) { inp := cp.inp fch := inp.Ch cnt := countDelim(inp, fch) if cnt < 3 { return nil, false @@ -234,15 +248,15 @@ } var sym *sx.Symbol switch fch { case ':': - sym = sz.SymRegionBlock + sym = zsx.SymRegionBlock case '<': - sym = sz.SymRegionQuote + sym = zsx.SymRegionQuote case '"': - sym = sz.SymRegionVerse + sym = zsx.SymRegionVerse default: panic(fmt.Sprintf("%q is not a region char", fch)) } attrs := parseBlockAttributes(inp) inp.SkipToEOL() @@ -256,28 +270,23 @@ posL := inp.Pos switch inp.Ch { case fch: if countDelim(inp, fch) >= cnt { ins := cp.parseRegionLastLine() - return sz.MakeRegion(sym, attrs, blocksBuilder.List(), ins), true + return zsx.MakeRegion(sym, attrs, blocksBuilder.List(), ins), true } inp.SetPos(posL) case input.EOS: return nil, false } - bn, cont := cp.parseBlock(lastPara) - if bn != nil { - blocksBuilder.Add(bn) - } - if !cont { - lastPara = bn - } + + lastPara = cp.parseBlock(&blocksBuilder, lastPara) } } // parseRegionLastLine parses the last line of a region and returns its inline text. -func (cp *zmkP) parseRegionLastLine() *sx.Pair { +func (cp *Parser) parseRegionLastLine() *sx.Pair { inp := cp.inp cp.clearStacked() // remove any lists defined in the region inp.SkipSpace() var region sx.ListBuilder for { @@ -292,11 +301,11 @@ region.Add(in) } } // parseHeading parses a head line. -func (cp *zmkP) parseHeading() (*sx.Pair, bool) { +func (cp *Parser) parseHeading() (*sx.Pair, bool) { inp := cp.inp delims := countDelim(inp, inp.Ch) if delims < 3 { return nil, false } @@ -311,21 +320,21 @@ level := delims - 2 var attrs *sx.Pair var text sx.ListBuilder for { if input.IsEOLEOS(inp.Ch) { - return sz.MakeHeading(level, attrs, text.List(), "", ""), true + return zsx.MakeHeading(level, attrs, text.List(), "", ""), true } in := cp.parseInline() if in == nil { - return sz.MakeHeading(level, attrs, text.List(), "", ""), true + return zsx.MakeHeading(level, attrs, text.List(), "", ""), true } text.Add(in) if inp.Ch == '{' && inp.Peek() != '{' { attrs = parseBlockAttributes(inp) inp.SkipToEOL() - return sz.MakeHeading(level, attrs, text.List(), "", ""), true + return zsx.MakeHeading(level, attrs, text.List(), "", ""), true } } } // parseHRule parses a horizontal rule. @@ -334,33 +343,33 @@ return nil, false } attrs := parseBlockAttributes(inp) inp.SkipToEOL() - return sz.MakeThematic(attrs), true + return zsx.MakeThematic(attrs), true } // parseNestedList parses a list. -func (cp *zmkP) parseNestedList() (*sx.Pair, bool) { +func (cp *Parser) parseNestedList() (*sx.Pair, bool) { inp := cp.inp kinds := parseNestedListKinds(inp) if len(kinds) == 0 { return nil, false } inp.SkipSpace() - if !kinds[len(kinds)-1].IsEqual(sz.SymListQuote) && input.IsEOLEOS(inp.Ch) { + if !kinds[len(kinds)-1].IsEqual(zsx.SymListQuote) && input.IsEOLEOS(inp.Ch) { return nil, false } if len(kinds) < len(cp.lists) { cp.lists = cp.lists[:len(kinds)] } ln, newLnCount := cp.buildNestedList(kinds) pv := cp.parseLinePara() - bn := sz.MakeBlock() + bn := zsx.MakeBlock() if pv != nil { - bn.AppendBang(sz.MakePara(pv)) + bn.AppendBang(zsx.MakeParaList(pv)) } lastItemPair := ln.LastPair() lastItemPair.AppendBang(bn) return cp.cleanupParsedNestedList(newLnCount) } @@ -369,15 +378,15 @@ result := make([]*sx.Symbol, 0, 8) for { var sym *sx.Symbol switch inp.Ch { case '*': - sym = sz.SymListUnordered + sym = zsx.SymListUnordered case '#': - sym = sz.SymListOrdered + sym = zsx.SymListOrdered case '>': - sym = sz.SymListQuote + sym = zsx.SymListQuote default: panic(fmt.Sprintf("%q is not a region char", inp.Ch)) } result = append(result, sym) switch inp.Next() { @@ -388,100 +397,100 @@ return nil } } } -func (cp *zmkP) buildNestedList(kinds []*sx.Symbol) (ln *sx.Pair, newLnCount int) { +func (cp *Parser) buildNestedList(kinds []*sx.Symbol) (ln *sx.Pair, newLnCount int) { for i, kind := range kinds { if i < len(cp.lists) { if !cp.lists[i].Car().IsEqual(kind) { - ln = sx.Cons(kind, nil) + ln = sx.Cons(kind, sx.Cons(sx.Nil(), sx.Nil())) newLnCount++ cp.lists[i] = ln cp.lists = cp.lists[:i+1] } else { ln = cp.lists[i] } } else { - ln = sx.Cons(kind, nil) + ln = sx.Cons(kind, sx.Cons(sx.Nil(), sx.Nil())) newLnCount++ cp.lists = append(cp.lists, ln) } } return ln, newLnCount } -func (cp *zmkP) cleanupParsedNestedList(newLnCount int) (*sx.Pair, bool) { +func (cp *Parser) cleanupParsedNestedList(newLnCount int) (*sx.Pair, bool) { childPos := len(cp.lists) - 1 parentPos := childPos - 1 for range newLnCount { if parentPos < 0 { return cp.lists[0], true } parentLn := cp.lists[parentPos] childLn := cp.lists[childPos] - if firstParent := parentLn.Tail(); firstParent != nil { + if firstParent := parentLn.Tail().Tail(); firstParent != nil { // Add list to last item of the parent list lastParent := firstParent.LastPair() lastParent.Head().LastPair().AppendBang(childLn) } else { // Set list to first child of parent. - parentLn.LastPair().AppendBang(sz.MakeBlock(cp.lists[childPos])) + parentLn.LastPair().AppendBang(zsx.MakeBlock(cp.lists[childPos])) } childPos-- parentPos-- } return nil, true } // parseDefTerm parses a term of a definition list. -func (cp *zmkP) parseDefTerm() (res *sx.Pair, success bool) { +func (cp *Parser) parseDefTerm() (res *sx.Pair, success bool) { inp := cp.inp if inp.Next() != ' ' { return nil, false } inp.Next() inp.SkipSpace() descrl := cp.descrl if descrl == nil { - descrl = sx.Cons(sz.SymDescription, nil) + descrl = sx.Cons(zsx.SymDescription, sx.Cons(sx.Nil(), sx.Nil())) cp.descrl = descrl res = descrl } lastPair, pos := lastPairPos(descrl) for first := true; ; first = false { in := cp.parseInline() if in == nil { - if pos%2 == 0 { + if pos%2 != 0 { // lastPair is either the empty description list or the last block of definitions return nil, false } // lastPair is the definition term return res, true } - if pos%2 == 0 { + if pos%2 != 0 { // lastPair is either the empty description list or the last block of definitions lastPair = lastPair.AppendBang(sx.Cons(in, nil)) pos++ } else if first { // Previous term had no description lastPair = lastPair. - AppendBang(sz.MakeBlock()). + AppendBang(zsx.MakeBlock()). AppendBang(sx.Cons(in, nil)) pos += 2 } else { // lastPair is the term part and we need to append the inline list just read lastPair.Head().LastPair().AppendBang(in) } - if sz.IsBreakSym(in.Car()) { + if isBreakSym(in.Car()) { return res, true } } } // parseDefDescr parses a description of a definition list. -func (cp *zmkP) parseDefDescr() (res *sx.Pair, success bool) { +func (cp *Parser) parseDefDescr() (res *sx.Pair, success bool) { inp := cp.inp if inp.Next() != ' ' { return nil, false } inp.Next() @@ -496,14 +505,14 @@ pn := cp.parseLinePara() if pn == nil { return nil, false } - newDef := sz.MakeBlock(sz.MakePara(pn)) - if lpPos%2 == 1 { + newDef := zsx.MakeBlock(zsx.MakeParaList(pn)) + if lpPos%2 == 0 { // Just a term, but no definitions - lastPair.AppendBang(sz.MakeBlock(newDef)) + lastPair.AppendBang(zsx.MakeBlock(newDef)) } else { // lastPara points a the last definition lastPair.Head().LastPair().AppendBang(newDef) } return nil, true @@ -521,11 +530,11 @@ } return nil, -1 } // parseIndent parses initial spaces to continue a list. -func (cp *zmkP) parseIndent() bool { +func (cp *Parser) parseIndent() bool { inp := cp.inp cnt := 0 for { if inp.Next() != ' ' { break @@ -539,11 +548,11 @@ return cp.parseIndentForDescription(cnt) } return false } -func (cp *zmkP) parseIndentForList(cnt int) bool { +func (cp *Parser) parseIndentForList(cnt int) bool { if len(cp.lists) < cnt { cnt = len(cp.lists) } cp.lists = cp.lists[:cnt] if cnt == 0 { @@ -554,33 +563,33 @@ return false } ln := cp.lists[cnt-1] lbn := ln.LastPair().Head() lpn := lbn.LastPair().Head() - if lpn.Car().IsEqual(sz.SymPara) { + if lpn.Car().IsEqual(zsx.SymPara) { lpn.LastPair().SetCdr(pv) } else { - lbn.LastPair().AppendBang(sz.MakePara(pv)) + lbn.LastPair().AppendBang(zsx.MakeParaList(pv)) } return true } -func (cp *zmkP) parseIndentForDescription(cnt int) bool { +func (cp *Parser) parseIndentForDescription(cnt int) bool { descrl := cp.descrl lastPair, pos := lastPairPos(descrl) - if cnt < 1 || pos < 1 { + if cnt < 1 || pos < 2 { return false } - if pos%2 == 1 { + if pos%2 == 0 { // Continuation of a definition term for { in := cp.parseInline() if in == nil { return true } lastPair.Head().LastPair().AppendBang(in) - if sz.IsBreakSym(in.Car()) { + if isBreakSym(in.Car()) { return true } } } @@ -603,43 +612,43 @@ if next == nil { break } if symSeparator.IsEqual(next.Head().Car()) { // It is a new paragraph! - obj.LastPair().AppendBang(sz.MakePara(pn)) + obj.LastPair().AppendBang(zsx.MakeParaList(pn)) return true } curr = next } // Continuation of existing paragraph para := bn.LastPair().Head().LastPair().Head() - if para.Car().IsEqual(sz.SymPara) { + if para.Car().IsEqual(zsx.SymPara) { para.LastPair().SetCdr(pn) } else { - bn.LastPair().AppendBang(sz.MakePara(pn)) + bn.LastPair().AppendBang(zsx.MakeParaList(pn)) } return true } // parseLinePara parses one paragraph of inline material. -func (cp *zmkP) parseLinePara() *sx.Pair { +func (cp *Parser) parseLinePara() *sx.Pair { var lb sx.ListBuilder for { in := cp.parseInline() if in == nil { return lb.List() } lb.Add(in) - if sz.IsBreakSym(in.Car()) { + if isBreakSym(in.Car()) { return lb.List() } } } // parseRow parse one table row. -func (cp *zmkP) parseRow() *sx.Pair { +func (cp *Parser) parseRow() *sx.Pair { inp := cp.inp if inp.Peek() == '%' { inp.SkipToEOL() return nil } @@ -660,41 +669,42 @@ if cp.lastRow == nil { if row.IsEmpty() { return nil } cp.lastRow = sx.Cons(row.List(), nil) - return cp.lastRow.Cons(nil).Cons(sz.SymTable) + return cp.lastRow.Cons(nil).Cons(zsx.SymTable) } cp.lastRow = cp.lastRow.AppendBang(row.List()) return nil } // inp.Ch must be '|' } } // parseCell parses one single cell of a table row. -func (cp *zmkP) parseCell() *sx.Pair { +func (cp *Parser) parseCell() *sx.Pair { inp := cp.inp var cell sx.ListBuilder for { if input.IsEOLEOS(inp.Ch) { if cell.IsEmpty() { return nil } - return sz.MakeCell(sz.SymCell, cell.List()) + return zsx.MakeCell(nil, cell.List()) } if inp.Ch == '|' { - return sz.MakeCell(sz.SymCell, cell.List()) + return zsx.MakeCell(nil, cell.List()) } in := cp.parseInline() cell.Add(in) } } // parseTransclusion parses '{' '{' '{' ZID '}' '}' '}' -func parseTransclusion(inp *input.Input) (*sx.Pair, bool) { +func (cp *Parser) parseTransclusion() (*sx.Pair, bool) { + inp := cp.inp if countDelim(inp, '{') != 3 { return nil, false } posA, posE := inp.Pos, 0 @@ -703,11 +713,11 @@ for { switch inp.Ch { case input.EOS: return nil, false case '\n', '\r', ' ', '\t': - if !hasQueryPrefix(inp.Src[posA:]) { + if !cp.isSpaceReference(inp.Src[posA:]) { return nil, false } case '\\': switch inp.Next() { case input.EOS, '\n', '\r': @@ -730,8 +740,8 @@ } inp.Next() // consume last '}' attrs := parseBlockAttributes(inp) inp.SkipToEOL() refText := string(inp.Src[posA:posE]) - ref := ParseReference(refText) - return sz.MakeTransclusion(attrs, ref, sx.Nil()), true + ref := cp.scanReference(refText) + return zsx.MakeTransclusion(attrs, ref, sx.Nil()), true } Index: sz/zmk/inline.go ================================================================== --- sz/zmk/inline.go +++ sz/zmk/inline.go @@ -17,16 +17,15 @@ "fmt" "slices" "strings" "t73f.de/r/sx" - "t73f.de/r/zsc/api" - "t73f.de/r/zsc/input" - "t73f.de/r/zsc/sz" + "t73f.de/r/zsx" + "t73f.de/r/zsx/input" ) -func (cp *zmkP) parseInline() *sx.Pair { +func (cp *Parser) parseInline() *sx.Pair { inp := cp.inp pos := inp.Pos if cp.nestingLevel <= maxNestingLevel { cp.nestingLevel++ defer func() { cp.nestingLevel-- }() @@ -74,11 +73,11 @@ } inp.SetPos(pos) return parseText(inp) } -func parseText(inp *input.Input) *sx.Pair { return sz.MakeText(parseString(inp)) } +func parseText(inp *input.Input) *sx.Pair { return zsx.MakeText(parseString(inp)) } func parseString(inp *input.Input) string { pos := inp.Pos if inp.Ch == '\\' { inp.Next() @@ -96,13 +95,13 @@ func parseBackslash(inp *input.Input) *sx.Pair { switch inp.Next() { case '\n', '\r': inp.EatEOL() - return sz.MakeHard() + return zsx.MakeHard() default: - return sz.MakeText(parseBackslashRest(inp)) + return zsx.MakeText(parseBackslashRest(inp)) } } func parseBackslashRest(inp *input.Input) string { if input.IsEOLEOS(inp.Ch) { @@ -117,50 +116,44 @@ return string(inp.Src[pos:inp.Pos]) } func parseSoftBreak(inp *input.Input) *sx.Pair { inp.EatEOL() - return sz.MakeSoft() + return zsx.MakeSoft() } -func (cp *zmkP) parseLink(openCh, closeCh rune) (*sx.Pair, bool) { +func (cp *Parser) parseLink(openCh, closeCh rune) (*sx.Pair, bool) { if refString, text, ok := cp.parseReference(openCh, closeCh); ok { attrs := parseInlineAttributes(cp.inp) if len(refString) > 0 { - ref := ParseReference(refString) - refSym, _ := sx.GetSymbol(ref.Car()) - sym := sz.MapRefStateToLink(refSym) - return sz.MakeLink(sym, attrs, ref.Tail().Car().(sx.String).GetValue(), text), true + ref := cp.scanReference(refString) + return zsx.MakeLink(attrs, ref, text), true } } return nil, false } -func (cp *zmkP) parseEmbed(openCh, closeCh rune) (*sx.Pair, bool) { +func (cp *Parser) parseEmbed(openCh, closeCh rune) (*sx.Pair, bool) { if refString, text, ok := cp.parseReference(openCh, closeCh); ok { attrs := parseInlineAttributes(cp.inp) if len(refString) > 0 { - return sz.MakeEmbed(attrs, ParseReference(refString), "", text), true + return zsx.MakeEmbed(attrs, cp.scanReference(refString), "", text), true } } return nil, false } -func hasQueryPrefix(src []byte) bool { - return len(src) > len(api.QueryPrefix) && string(src[:len(api.QueryPrefix)]) == api.QueryPrefix -} - -func (cp *zmkP) parseReference(openCh, closeCh rune) (string, *sx.Pair, bool) { +func (cp *Parser) parseReference(openCh, closeCh rune) (string, *sx.Pair, bool) { inp := cp.inp inp.Next() inp.SkipSpace() if inp.Ch == openCh { // Additional opening chars result in a fail return "", nil, false } var lb sx.ListBuilder pos := inp.Pos - if !hasQueryPrefix(inp.Src[pos:]) { + if !cp.isSpaceReference(inp.Src[pos:]) { hasSpace, ok := readReferenceToSep(inp, closeCh) if !ok { return "", nil, false } if inp.Ch == '|' { // First part must be inline text @@ -185,11 +178,11 @@ } } inp.SkipSpace() pos = inp.Pos - if !readReferenceToClose(inp, closeCh) { + if !cp.readReferenceToClose(closeCh) { return "", nil, false } ref := strings.TrimSpace(string(inp.Src[pos:inp.Pos])) if inp.Next() != closeCh { return "", nil, false @@ -228,18 +221,19 @@ } inp.Next() } } -func readReferenceToClose(inp *input.Input, closeCh rune) bool { +func (cp *Parser) readReferenceToClose(closeCh rune) bool { + inp := cp.inp pos := inp.Pos for { switch inp.Ch { case input.EOS: return false case '\t', '\r', '\n', ' ': - if !hasQueryPrefix(inp.Src[pos:]) { + if !cp.isSpaceReference(inp.Src[pos:]) { return false } case '\\': switch inp.Next() { case input.EOS, '\n', '\r': @@ -250,11 +244,11 @@ } inp.Next() } } -func (cp *zmkP) parseCite() (*sx.Pair, bool) { +func (cp *Parser) parseCite() (*sx.Pair, bool) { inp := cp.inp switch inp.Next() { case ' ', ',', '|', ']', '\n', '\r': return nil, false } @@ -277,24 +271,24 @@ ins, ok := cp.parseLinkLikeRest() if !ok { return nil, false } attrs := parseInlineAttributes(inp) - return sz.MakeCite(attrs, string(inp.Src[pos:posL]), ins), true + return zsx.MakeCite(attrs, string(inp.Src[pos:posL]), ins), true } -func (cp *zmkP) parseEndnote() (*sx.Pair, bool) { +func (cp *Parser) parseEndnote() (*sx.Pair, bool) { cp.inp.Next() ins, ok := cp.parseLinkLikeRest() if !ok { return nil, false } attrs := parseInlineAttributes(cp.inp) - return sz.MakeEndnote(attrs, ins), true + return zsx.MakeEndnote(attrs, ins), true } -func (cp *zmkP) parseMark() (*sx.Pair, bool) { +func (cp *Parser) parseMark() (*sx.Pair, bool) { inp := cp.inp inp.Next() pos := inp.Pos for inp.Ch != '|' && inp.Ch != ']' { if !isNameRune(inp.Ch) { @@ -312,26 +306,26 @@ return nil, false } } else { inp.Next() } - return sz.MakeMark(mark, "", "", ins), true + return zsx.MakeMark(mark, "", "", ins), true // Problematisch ist, dass hier noch nicht mn.Fragment und mn.Slug gesetzt werden. // Evtl. muss es ein PreMark-Symbol geben } -func (cp *zmkP) parseLinkLikeRest() (*sx.Pair, bool) { +func (cp *Parser) parseLinkLikeRest() (*sx.Pair, bool) { var ins sx.ListBuilder inp := cp.inp inp.SkipSpace() for inp.Ch != ']' { in := cp.parseInline() if in == nil { return nil, false } ins.Add(in) - if input.IsEOLEOS(inp.Ch) && sz.IsBreakSym(in.Car()) { + if input.IsEOLEOS(inp.Ch) && isBreakSym(in.Car()) { return nil, false } } inp.Next() return ins.List(), true @@ -347,29 +341,29 @@ attrs := parseInlineAttributes(inp) inp.SkipSpace() pos := inp.Pos for { if input.IsEOLEOS(inp.Ch) { - return sz.MakeLiteral(sz.SymLiteralComment, attrs, string(inp.Src[pos:inp.Pos])), true + return zsx.MakeLiteral(zsx.SymLiteralComment, attrs, string(inp.Src[pos:inp.Pos])), true } inp.Next() } } var mapRuneFormat = map[rune]*sx.Symbol{ - '_': sz.SymFormatEmph, - '*': sz.SymFormatStrong, - '>': sz.SymFormatInsert, - '~': sz.SymFormatDelete, - '^': sz.SymFormatSuper, - ',': sz.SymFormatSub, - '"': sz.SymFormatQuote, - '#': sz.SymFormatMark, - ':': sz.SymFormatSpan, + '_': zsx.SymFormatEmph, + '*': zsx.SymFormatStrong, + '>': zsx.SymFormatInsert, + '~': zsx.SymFormatDelete, + '^': zsx.SymFormatSuper, + ',': zsx.SymFormatSub, + '"': zsx.SymFormatQuote, + '#': zsx.SymFormatMark, + ':': zsx.SymFormatSpan, } -func (cp *zmkP) parseFormat() (*sx.Pair, bool) { +func (cp *Parser) parseFormat() (*sx.Pair, bool) { inp := cp.inp fch := inp.Ch symFormat, ok := mapRuneFormat[fch] if !ok { panic(fmt.Sprintf("%q is not a formatting char", fch)) @@ -386,27 +380,27 @@ } if inp.Ch == fch { if inp.Next() == fch { inp.Next() attrs := parseInlineAttributes(inp) - return sz.MakeFormat(symFormat, attrs, inlines.List()), true + return zsx.MakeFormat(symFormat, attrs, inlines.List()), true } - inlines.Add(sz.MakeText(string(fch))) + inlines.Add(zsx.MakeText(string(fch))) } else if in := cp.parseInline(); in != nil { - if input.IsEOLEOS(inp.Ch) && sz.IsBreakSym(in.Car()) { + if input.IsEOLEOS(inp.Ch) && isBreakSym(in.Car()) { return nil, false } inlines.Add(in) } } } var mapRuneLiteral = map[rune]*sx.Symbol{ - '`': sz.SymLiteralCode, - runeModGrave: sz.SymLiteralCode, - '\'': sz.SymLiteralInput, - '=': sz.SymLiteralOutput, + '`': zsx.SymLiteralCode, + runeModGrave: zsx.SymLiteralCode, + '\'': zsx.SymLiteralInput, + '=': zsx.SymLiteralOutput, // No '$': sz.SymLiteralMath, because pairing literal math is a little different } func parseLiteral(inp *input.Input) (*sx.Pair, bool) { fch := inp.Ch @@ -426,11 +420,11 @@ } if inp.Ch == fch { if inp.Peek() == fch { inp.Next() inp.Next() - return sz.MakeLiteral(symLiteral, parseInlineAttributes(inp), sb.String()), true + return zsx.MakeLiteral(symLiteral, parseInlineAttributes(inp), sb.String()), true } sb.WriteRune(fch) inp.Next() } else { s := parseString(inp) @@ -452,11 +446,11 @@ } if inp.Ch == '$' && inp.Peek() == '$' { content := slices.Clone(inp.Src[pos:inp.Pos]) inp.Next() inp.Next() - return sz.MakeLiteral(sz.SymLiteralMath, parseInlineAttributes(inp), string(content)), true + return zsx.MakeLiteral(zsx.SymLiteralMath, parseInlineAttributes(inp), string(content)), true } inp.Next() } } @@ -464,14 +458,14 @@ if inp.Peek() != inp.Ch { return nil, false } inp.Next() inp.Next() - return sz.MakeText("\u2013"), true + return zsx.MakeText("\u2013"), true } func parseEntity(inp *input.Input) (*sx.Pair, bool) { - if text, ok := inp.ScanEntity(); ok { - return sz.MakeText(text), true + if text, ok := zsx.ScanEntity(inp); ok { + return zsx.MakeText(text), true } return nil, false } Index: sz/zmk/post-processor.go ================================================================== --- sz/zmk/post-processor.go +++ sz/zmk/post-processor.go @@ -15,11 +15,11 @@ import ( "strings" "t73f.de/r/sx" - "t73f.de/r/zsc/sz" + "t73f.de/r/zsx" ) var symInVerse = sx.MakeSymbol("in-verse") var symNoBlock = sx.MakeSymbol("no-block") @@ -42,11 +42,11 @@ func (pp *postProcessor) VisitAfter(lst *sx.Pair, _ *sx.Pair) sx.Object { return lst } func (pp *postProcessor) visitPairList(lst *sx.Pair, env *sx.Pair) *sx.Pair { var pList sx.ListBuilder for node := range lst.Pairs() { - if elem, isPair := sx.GetPair(sz.Walk(pp, node.Head(), env)); isPair && elem != nil { + if elem, isPair := sx.GetPair(zsx.Walk(pp, node.Head(), env)); isPair && elem != nil { pList.Add(elem) } } return pList.List() } @@ -53,52 +53,44 @@ var symMap map[*sx.Symbol]func(*postProcessor, *sx.Pair, *sx.Pair) *sx.Pair func init() { symMap = map[*sx.Symbol]func(*postProcessor, *sx.Pair, *sx.Pair) *sx.Pair{ - sz.SymBlock: postProcessBlockList, - sz.SymPara: postProcessInlineList, - sz.SymRegionBlock: postProcessRegion, - sz.SymRegionQuote: postProcessRegion, - sz.SymRegionVerse: postProcessRegionVerse, - sz.SymVerbatimComment: postProcessVerbatim, - sz.SymVerbatimEval: postProcessVerbatim, - sz.SymVerbatimMath: postProcessVerbatim, - sz.SymVerbatimCode: postProcessVerbatim, - sz.SymVerbatimZettel: postProcessVerbatim, - sz.SymHeading: postProcessHeading, - sz.SymListOrdered: postProcessItemList, - sz.SymListUnordered: postProcessItemList, - sz.SymListQuote: postProcessQuoteList, - sz.SymDescription: postProcessDescription, - sz.SymTable: postProcessTable, - - sz.SymInline: postProcessInlineList, - sz.SymText: postProcessText, - sz.SymSoft: postProcessSoft, - sz.SymEndnote: postProcessEndnote, - sz.SymMark: postProcessMark, - sz.SymLinkBased: postProcessInlines4, - sz.SymLinkBroken: postProcessInlines4, - sz.SymLinkExternal: postProcessInlines4, - sz.SymLinkFound: postProcessInlines4, - sz.SymLinkHosted: postProcessInlines4, - sz.SymLinkInvalid: postProcessInlines4, - sz.SymLinkQuery: postProcessInlines4, - sz.SymLinkSelf: postProcessInlines4, - sz.SymLinkZettel: postProcessInlines4, - sz.SymEmbed: postProcessEmbed, - sz.SymCite: postProcessInlines4, - sz.SymFormatDelete: postProcessFormat, - sz.SymFormatEmph: postProcessFormat, - sz.SymFormatInsert: postProcessFormat, - sz.SymFormatMark: postProcessFormat, - sz.SymFormatQuote: postProcessFormat, - sz.SymFormatStrong: postProcessFormat, - sz.SymFormatSpan: postProcessFormat, - sz.SymFormatSub: postProcessFormat, - sz.SymFormatSuper: postProcessFormat, + zsx.SymBlock: postProcessBlockList, + zsx.SymPara: postProcessInlineList, + zsx.SymRegionBlock: postProcessRegion, + zsx.SymRegionQuote: postProcessRegion, + zsx.SymRegionVerse: postProcessRegionVerse, + zsx.SymVerbatimComment: postProcessVerbatim, + zsx.SymVerbatimEval: postProcessVerbatim, + zsx.SymVerbatimMath: postProcessVerbatim, + zsx.SymVerbatimCode: postProcessVerbatim, + zsx.SymVerbatimZettel: postProcessVerbatim, + zsx.SymHeading: postProcessHeading, + zsx.SymListOrdered: postProcessItemList, + zsx.SymListUnordered: postProcessItemList, + zsx.SymListQuote: postProcessQuoteList, + zsx.SymDescription: postProcessDescription, + zsx.SymTable: postProcessTable, + + zsx.SymInline: postProcessInlineList, + zsx.SymText: postProcessText, + zsx.SymSoft: postProcessSoft, + zsx.SymEndnote: postProcessEndnote, + zsx.SymMark: postProcessMark, + zsx.SymLink: postProcessInlines4, + zsx.SymEmbed: postProcessEmbed, + zsx.SymCite: postProcessInlines4, + zsx.SymFormatDelete: postProcessFormat, + zsx.SymFormatEmph: postProcessFormat, + zsx.SymFormatInsert: postProcessFormat, + zsx.SymFormatMark: postProcessFormat, + zsx.SymFormatQuote: postProcessFormat, + zsx.SymFormatStrong: postProcessFormat, + zsx.SymFormatSpan: postProcessFormat, + zsx.SymFormatSub: postProcessFormat, + zsx.SymFormatSuper: postProcessFormat, symSeparator: ignoreProcess, } } @@ -138,11 +130,11 @@ blocks := pp.visitPairList(next.Head(), envBlock) text := pp.visitInlines(next.Tail(), envInline) if blocks == nil && text == nil { return nil } - return sz.MakeRegion(sym, attrs, blocks, text) + return zsx.MakeRegion(sym, attrs, blocks, text) } func postProcessVerbatim(_ *postProcessor, verb *sx.Pair, _ *sx.Pair) *sx.Pair { if content, isString := sx.GetString(verb.Tail().Tail().Car()); isString && content.GetValue() != "" { return verb @@ -158,84 +150,87 @@ next = next.Tail() slug := next.Car().(sx.String) next = next.Tail() fragment := next.Car().(sx.String) if text := pp.visitInlines(next.Tail(), env); text != nil { - return sz.MakeHeading(int(level), attrs, text, slug.GetValue(), fragment.GetValue()) + return zsx.MakeHeading(int(level), attrs, text, slug.GetValue(), fragment.GetValue()) } return nil } func postProcessItemList(pp *postProcessor, ln *sx.Pair, env *sx.Pair) *sx.Pair { - elems := pp.visitListElems(ln, env) + attrs := ln.Tail().Head() + elems := pp.visitListElems(ln.Tail(), env) if elems == nil { return nil } - return elems.Cons(ln.Car()) + return zsx.MakeList(ln.Car().(*sx.Symbol), attrs, elems) } func postProcessQuoteList(pp *postProcessor, ln *sx.Pair, env *sx.Pair) *sx.Pair { - elems := pp.visitListElems(ln, env.Cons(sx.Cons(symNoBlock, nil))) + attrs := ln.Tail().Head() + elems := pp.visitListElems(ln.Tail(), env.Cons(sx.Cons(symNoBlock, nil))) // Collect multiple paragraph items into one item. var newElems sx.ListBuilder var newPara sx.ListBuilder addtoParagraph := func() { if !newPara.IsEmpty() { - newElems.Add(sx.MakeList(sz.SymBlock, newPara.List().Cons(sz.SymPara))) + newElems.Add(sx.MakeList(zsx.SymBlock, newPara.List().Cons(zsx.SymPara))) newPara.Reset() } } for node := range elems.Pairs() { item := node.Head() - if !item.Car().IsEqual(sz.SymBlock) { + if !item.Car().IsEqual(zsx.SymBlock) { continue } itemTail := item.Tail() if itemTail == nil || itemTail.Tail() != nil { addtoParagraph() newElems.Add(item) continue } - if pn := itemTail.Head(); pn.Car().IsEqual(sz.SymPara) { + if pn := itemTail.Head(); pn.Car().IsEqual(zsx.SymPara) { if !newPara.IsEmpty() { - newPara.Add(sx.Cons(sz.SymSoft, nil)) + newPara.Add(sx.Cons(zsx.SymSoft, nil)) } newPara.ExtendBang(pn.Tail()) continue } addtoParagraph() newElems.Add(item) } addtoParagraph() - return newElems.List().Cons(ln.Car()) + return zsx.MakeList(ln.Car().(*sx.Symbol), attrs, newElems.List()) } func (pp *postProcessor) visitListElems(ln *sx.Pair, env *sx.Pair) *sx.Pair { var pList sx.ListBuilder for node := range ln.Tail().Pairs() { - if elem := sz.Walk(pp, node.Head(), env); elem != nil { + if elem := zsx.Walk(pp, node.Head(), env); elem != nil { pList.Add(elem) } } return pList.List() } func postProcessDescription(pp *postProcessor, dl *sx.Pair, env *sx.Pair) *sx.Pair { + attrs := dl.Tail().Head() var dList sx.ListBuilder isTerm := false - for node := range dl.Tail().Pairs() { + for node := range dl.Tail().Tail().Pairs() { isTerm = !isTerm if isTerm { dList.Add(pp.visitInlines(node.Head(), env)) } else { - dList.Add(sz.Walk(pp, node.Head(), env)) + dList.Add(zsx.Walk(pp, node.Head(), env)) } } - return dList.List().Cons(dl.Car()) + return dList.List().Cons(attrs).Cons(dl.Car()) } func postProcessTable(pp *postProcessor, tbl *sx.Pair, env *sx.Pair) *sx.Pair { sym := tbl.Car() next := tbl.Tail() @@ -274,36 +269,38 @@ func (pp *postProcessor) visitCells(cells *sx.Pair, env *sx.Pair) (*sx.Pair, int) { width := 0 var pCells sx.ListBuilder for node := range cells.Pairs() { cell := node.Head() - ins := pp.visitInlines(cell.Tail(), env) - newCell := ins.Cons(cell.Car()) - pCells.Add(newCell) + rest := cell.Tail() + attrs := rest.Head() + ins := pp.visitInlines(rest.Tail(), env) + pCells.Add(zsx.MakeCell(attrs, ins)) width++ } return pCells.List(), width } -func splitTableHeader(rows *sx.Pair, width int) (header, realRows *sx.Pair, align []*sx.Symbol) { - align = make([]*sx.Symbol, width) +func splitTableHeader(rows *sx.Pair, width int) (header, realRows *sx.Pair, align []byte) { + align = make([]byte, width) foundHeader := false cellCount := 0 // assert: rows != nil (checked in postProcessTable) for node := range rows.Head().Pairs() { cell := node.Head() cellCount++ - cellTail := cell.Tail() - if cellTail == nil { + rest := cell.Tail() // attrs := rest.Head() + cellInlines := rest.Tail() + if cellInlines == nil { continue } // elem is first cell inline element - elem := cellTail.Head() - if elem.Car().IsEqual(sz.SymText) { + elem := cellInlines.Head() + if elem.Car().IsEqual(zsx.SymText) { if s, isString := sx.GetString(elem.Tail().Car()); isString && s.GetValue() != "" { str := s.GetValue() if str[0] == '=' { foundHeader = true elem.SetCdr(sx.Cons(sx.MakeString(str[1:]), nil)) @@ -311,92 +308,95 @@ } } // move to the last cell inline element for { - next := cellTail.Tail() + next := cellInlines.Tail() if next == nil { break } - cellTail = next + cellInlines = next } - elem = cellTail.Head() - if elem.Car().IsEqual(sz.SymText) { + elem = cellInlines.Head() + if elem.Car().IsEqual(zsx.SymText) { if s, isString := sx.GetString(elem.Tail().Car()); isString && s.GetValue() != "" { str := s.GetValue() - cellAlign := getCellAlignment(str[len(str)-1]) - if !cellAlign.IsEqualSymbol(sz.SymCell) { + lastByte := str[len(str)-1] + if cellAlign, isValid := getCellAlignment(lastByte); isValid { elem.SetCdr(sx.Cons(sx.MakeString(str[0:len(str)-1]), nil)) + rest.SetCar(makeCellAttrs(cellAlign)) } - align[cellCount-1] = cellAlign - cell.SetCar(cellAlign) + align[cellCount-1] = lastByte } } } if !foundHeader { - for i := range width { - align[i] = sz.SymCell // Default alignment - } - return nil, rows, align - } - - for i := range width { - if align[i] == nil { - align[i] = sz.SymCell // Default alignment - } - } - return rows.Head(), rows.Tail(), align -} - -func alignRow(row *sx.Pair, align []*sx.Symbol) { - if row == nil { - return - } - var lastCellNode *sx.Pair - cellCount := 0 - for node := range row.Pairs() { - lastCellNode = node - cell := node.Head() - cell.SetCar(align[cellCount]) - cellCount++ - cellTail := cell.Tail() - if cellTail == nil { - continue - } - - // elem is first cell inline element - elem := cellTail.Head() - if elem.Car().IsEqual(sz.SymText) { - if s, isString := sx.GetString(elem.Tail().Car()); isString && s.GetValue() != "" { - str := s.GetValue() - cellAlign := getCellAlignment(str[0]) - if !cellAlign.IsEqualSymbol(sz.SymCell) { - elem.SetCdr(sx.Cons(sx.MakeString(str[1:]), nil)) - cell.SetCar(cellAlign) - } - } - } - } - - for cellCount < len(align) { - lastCellNode = lastCellNode.AppendBang(sx.Cons(align[cellCount], nil)) - cellCount++ - } -} - -func getCellAlignment(ch byte) *sx.Symbol { - switch ch { - case ':': - return sz.SymCellCenter - case '<': - return sz.SymCellLeft - case '>': - return sz.SymCellRight - default: - return sz.SymCell + return nil, rows, align + } + + return rows.Head(), rows.Tail(), align +} + +func alignRow(row *sx.Pair, defaultAlign []byte) { + if row == nil { + return + } + var lastCellNode *sx.Pair + cellColumnNo := 0 + for node := range row.Pairs() { + lastCellNode = node + cell := node.Head() + cellColumnNo++ + rest := cell.Tail() // attrs := rest.Head() + if cellAlign, isValid := getCellAlignment(defaultAlign[cellColumnNo-1]); isValid { + rest.SetCar(makeCellAttrs(cellAlign)) + } + cellInlines := rest.Tail() + if cellInlines == nil { + continue + } + + // elem is first cell inline element + elem := cellInlines.Head() + if elem.Car().IsEqual(zsx.SymText) { + if s, isString := sx.GetString(elem.Tail().Car()); isString && s.GetValue() != "" { + str := s.GetValue() + cellAlign, isValid := getCellAlignment(str[0]) + if isValid { + elem.SetCdr(sx.Cons(sx.MakeString(str[1:]), nil)) + rest.SetCar(makeCellAttrs(cellAlign)) + } + } + } + } + + for cellColumnNo < len(defaultAlign) { + var attrs *sx.Pair + if cellAlign, isValid := getCellAlignment(defaultAlign[cellColumnNo]); isValid { + attrs = makeCellAttrs(cellAlign) + } + lastCellNode = lastCellNode.AppendBang(zsx.MakeCell(attrs, nil)) + cellColumnNo++ + } +} + +func makeCellAttrs(align sx.String) *sx.Pair { + return sx.Cons(sx.Cons(zsx.SymAttrAlign, align), sx.Nil()) +} + +func getCellAlignment(ch byte) (sx.String, bool) { + switch ch { + case ':': + return zsx.AttrAlignCenter, true + case '<': + return zsx.AttrAlignLeft, true + case '>': + return zsx.AttrAlignRight, true + default: + return sx.MakeString(""), false } } func (pp *postProcessor) visitInlines(lst *sx.Pair, env *sx.Pair) *sx.Pair { length := lst.Length() @@ -405,28 +405,28 @@ } inVerse := env.Assoc(symInVerse) != nil vector := make([]*sx.Pair, 0, length) // 1st phase: process all childs, ignore ' ' / '\t' at start, and merge some elements for node := range lst.Pairs() { - elem, isPair := sx.GetPair(sz.Walk(pp, node.Head(), env)) + elem, isPair := sx.GetPair(zsx.Walk(pp, node.Head(), env)) if !isPair || elem == nil { continue } elemSym := elem.Car() elemTail := elem.Tail() - if inVerse && elemSym.IsEqual(sz.SymText) { + if inVerse && elemSym.IsEqual(zsx.SymText) { if s, isString := sx.GetString(elemTail.Car()); isString { verseText := s.GetValue() verseText = strings.ReplaceAll(verseText, " ", "\u00a0") elemTail.SetCar(sx.MakeString(verseText)) } } if len(vector) == 0 { // If the 1st element is a TEXT, remove all ' ', '\t' at the beginning, if outside a verse block. - if !elemSym.IsEqual(sz.SymText) { + if !elemSym.IsEqual(zsx.SymText) { vector = append(vector, elem) continue } elemText := elemTail.Car().(sx.String).GetValue() @@ -445,29 +445,29 @@ continue } last := vector[len(vector)-1] lastSym := last.Car() - if lastSym.IsEqual(sz.SymText) && elemSym.IsEqual(sz.SymText) { + if lastSym.IsEqual(zsx.SymText) && elemSym.IsEqual(zsx.SymText) { // Merge two TEXT elements into one lastText := last.Tail().Car().(sx.String).GetValue() elemText := elem.Tail().Car().(sx.String).GetValue() last.SetCdr(sx.Cons(sx.MakeString(lastText+elemText), sx.Nil())) continue } - if lastSym.IsEqual(sz.SymText) && elemSym.IsEqual(sz.SymSoft) { + if lastSym.IsEqual(zsx.SymText) && elemSym.IsEqual(zsx.SymSoft) { // Merge (TEXT "... ") (SOFT) to (TEXT "...") (HARD) lastTail := last.Tail() if lastText := lastTail.Car().(sx.String).GetValue(); strings.HasSuffix(lastText, " ") { newText := removeTrailingSpaces(lastText) if newText == "" { - vector[len(vector)-1] = sx.Cons(sz.SymHard, sx.Nil()) + vector[len(vector)-1] = sx.Cons(zsx.SymHard, sx.Nil()) continue } lastTail.SetCar(sx.MakeString(newText)) - elemSym = sz.SymHard + elemSym = zsx.SymHard elem.SetCar(elemSym) } } vector = append(vector, elem) @@ -479,20 +479,20 @@ // 2nd phase: remove (SOFT), (HARD) at the end, remove trailing spaces in (TEXT "...") lastPos := len(vector) - 1 for lastPos >= 0 { elem := vector[lastPos] elemSym := elem.Car() - if elemSym.IsEqual(sz.SymText) { + if elemSym.IsEqual(zsx.SymText) { elemTail := elem.Tail() elemText := elemTail.Car().(sx.String).GetValue() newText := removeTrailingSpaces(elemText) if newText != "" { elemTail.SetCar(sx.MakeString(newText)) break } lastPos-- - } else if sz.IsBreakSym(elemSym) { + } else if isBreakSym(elemSym) { lastPos-- } else { break } } @@ -529,20 +529,20 @@ func postProcessSoft(_ *postProcessor, sn *sx.Pair, env *sx.Pair) *sx.Pair { if env.Assoc(symInVerse) == nil { return sn } - return sx.Cons(sz.SymHard, nil) + return sx.Cons(zsx.SymHard, nil) } func postProcessEndnote(pp *postProcessor, en *sx.Pair, env *sx.Pair) *sx.Pair { next := en.Tail() attrs := next.Car().(*sx.Pair) if text := pp.visitInlines(next.Tail(), env); text != nil { - return sz.MakeEndnote(attrs, text) + return zsx.MakeEndnote(attrs, text) } - return sz.MakeEndnote(attrs, sx.Nil()) + return zsx.MakeEndnote(attrs, sx.Nil()) } func postProcessMark(pp *postProcessor, en *sx.Pair, env *sx.Pair) *sx.Pair { next := en.Tail() mark := next.Car().(sx.String) @@ -549,11 +549,11 @@ next = next.Tail() slug := next.Car().(sx.String) next = next.Tail() fragment := next.Car().(sx.String) text := pp.visitInlines(next.Tail(), env) - return sz.MakeMark(mark.GetValue(), slug.GetValue(), fragment.GetValue(), text) + return zsx.MakeMark(mark.GetValue(), slug.GetValue(), fragment.GetValue(), text) } func postProcessInlines4(pp *postProcessor, ln *sx.Pair, env *sx.Pair) *sx.Pair { sym := ln.Car() next := ln.Tail() @@ -570,11 +570,11 @@ next = next.Tail() ref := next.Car() next = next.Tail() syntax := next.Car().(sx.String) text := pp.visitInlines(next.Tail(), env) - return sz.MakeEmbed(attrs, ref, syntax.GetValue(), text) + return zsx.MakeEmbed(attrs, ref, syntax.GetValue(), text) } func postProcessFormat(pp *postProcessor, fn *sx.Pair, env *sx.Pair) *sx.Pair { symFormat := fn.Car().(*sx.Symbol) next := fn.Tail() // Attrs @@ -582,7 +582,7 @@ next = next.Tail() // Possible inlines if next == nil { return fn } inlines := pp.visitInlines(next, env) - return sz.MakeFormat(symFormat, attrs, inlines) + return zsx.MakeFormat(symFormat, attrs, inlines) } DELETED sz/zmk/ref.go Index: sz/zmk/ref.go ================================================================== --- sz/zmk/ref.go +++ /dev/null @@ -1,107 +0,0 @@ -// ----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -// ----------------------------------------------------------------------------- - -package zmk - -import ( - "net/url" - "strings" - - "t73f.de/r/sx" - "t73f.de/r/zsc/api" - "t73f.de/r/zsc/domain/id" - "t73f.de/r/zsc/sz" -) - -// ParseReference parses a string and returns a reference. -func ParseReference(s string) *sx.Pair { - if invalidReference(s) { - return makePairRef(sz.SymRefStateInvalid, s) - } - if strings.HasPrefix(s, api.QueryPrefix) { - return makePairRef(sz.SymRefStateQuery, s[len(api.QueryPrefix):]) - } - if state, ok := localState(s); ok { - if state.IsEqualSymbol(sz.SymRefStateBased) { - s = s[1:] - } - _, err := url.Parse(s) - if err == nil { - return makePairRef(state, s) - } - } - u, err := url.Parse(s) - if err != nil { - return makePairRef(sz.SymRefStateInvalid, s) - } - if !externalURL(u) { - if _, err = id.Parse(u.Path); err == nil { - return makePairRef(sz.SymRefStateZettel, s) - } - if u.Path == "" && u.Fragment != "" { - return makePairRef(sz.SymRefStateSelf, s) - } - } - return makePairRef(sz.SymRefStateExternal, s) -} -func makePairRef(sym *sx.Symbol, val string) *sx.Pair { - return sx.MakeList(sym, sx.MakeString(val)) -} - -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) (*sx.Symbol, bool) { - if len(path) > 0 && path[0] == '/' { - if len(path) > 1 && path[1] == '/' { - return sz.SymRefStateBased, true - } - return sz.SymRefStateHosted, true - } - if len(path) > 1 && path[0] == '.' { - if len(path) > 2 && path[1] == '.' && path[2] == '/' { - return sz.SymRefStateHosted, true - } - return sz.SymRefStateHosted, path[1] == '/' - } - return sz.SymRefStateInvalid, false -} - -// ReferenceIsValid returns true if reference is valid -func ReferenceIsValid(ref *sx.Pair) bool { - return !ref.Car().IsEqual(sz.SymRefStateInvalid) -} - -// ReferenceIsZettel returns true if it is a reference to a local zettel. -func ReferenceIsZettel(ref *sx.Pair) bool { - state := ref.Car() - return state.IsEqual(sz.SymRefStateZettel) || - state.IsEqual(sz.SymRefStateSelf) || - state.IsEqual(sz.SymRefStateFound) || - state.IsEqual(sz.SymRefStateBroken) -} - -// ReferenceIsLocal returns true if reference is local -func ReferenceIsLocal(ref *sx.Pair) bool { - state := ref.Car() - return state.IsEqual(sz.SymRefStateHosted) || - state.IsEqual(sz.SymRefStateBased) -} - -// ReferenceIsExternal returns true if it is a reference to external material. -func ReferenceIsExternal(ref *sx.Pair) bool { - return ref.Car().IsEqual(sz.SymRefStateExternal) -} DELETED sz/zmk/ref_test.go Index: sz/zmk/ref_test.go ================================================================== --- sz/zmk/ref_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// ----------------------------------------------------------------------------- -// Copyright (c) 2020-present Detlef Stern -// -// This file is part of zettelstore-client. -// -// Zettelstore client is licensed under the latest version of the EUPL -// (European Union Public License). Please see file LICENSE.txt for your rights -// and obligations under this license. -// -// SPDX-License-Identifier: EUPL-1.2 -// SPDX-FileCopyrightText: 2020-present Detlef Stern -// ----------------------------------------------------------------------------- -package zmk_test - -import ( - "testing" - - "t73f.de/r/zsc/sz/zmk" -) - -func TestParseReference(t *testing.T) { - t.Parallel() - testcases := []struct { - link string - err bool - exp string - }{ - {"", true, ""}, - {"12345678901234", false, "(ZETTEL \"12345678901234\")"}, - {"123", false, "(EXTERNAL \"123\")"}, - {",://", true, ""}, - } - - for i, tc := range testcases { - got := zmk.ParseReference(tc.link) - gotIsValid := zmk.ReferenceIsValid(got) - if gotIsValid == tc.err { - t.Errorf( - "TC=%d, expected parse error of %q: %v, but got %q", i, tc.link, tc.err, got) - } - if gotIsValid && 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 := zmk.ParseReference(tc.link) - isZettel := zmk.ReferenceIsZettel(ref) - if isZettel != tc.isZettel { - t.Errorf( - "TC=%d, Reference %q isZettel=%v expected, but got %v", - i, - tc.link, - tc.isZettel, - isZettel) - } - isLocal := zmk.ReferenceIsLocal(ref) - if isLocal != tc.isLocal { - t.Errorf( - "TC=%d, Reference %q isLocal=%v expected, but got %v", - i, - tc.link, - tc.isLocal, isLocal) - } - isExternal := zmk.ReferenceIsExternal(ref) - if isExternal != tc.isExternal { - t.Errorf( - "TC=%d, Reference %q isExternal=%v expected, but got %v", - i, - tc.link, - tc.isExternal, - isExternal) - } - } -} Index: sz/zmk/zmk.go ================================================================== --- sz/zmk/zmk.go +++ sz/zmk/zmk.go @@ -19,51 +19,58 @@ "slices" "strings" "unicode" "t73f.de/r/sx" - "t73f.de/r/zsc/input" - "t73f.de/r/zsc/sz" -) - -// Parse tries to parse the input as a block element. -func Parse(inp *input.Input) *sx.Pair { - parser := zmkP{inp: inp} - - var lastPara *sx.Pair - var blkBuild sx.ListBuilder - for inp.Ch != input.EOS { - bn, cont := parser.parseBlock(lastPara) - if bn != nil { - blkBuild.Add(bn) - } - if !cont { - if bn.Car().IsEqual(sz.SymPara) { - lastPara = bn - } else { - lastPara = nil - } - } - } - if parser.nestingLevel != 0 { - panic("Nesting level was not decremented") - } - - bnl := blkBuild.List() - var pp postProcessor - if bs := pp.visitPairList(bnl, nil); bs != nil { - return bs.Cons(sz.SymBlock) - } - return nil -} - -type zmkP struct { + "t73f.de/r/zsc/api" + "t73f.de/r/zsc/sz" + "t73f.de/r/zsx" + "t73f.de/r/zsx/input" +) + +// Parser allows to parse its plain text input into Zettelmarkup. +type Parser struct { inp *input.Input // Input stream lists []*sx.Pair // Stack of lists lastRow *sx.Pair // Last row of table, or nil if not in table. descrl *sx.Pair // Current description list nestingLevel int // Count nesting of block and inline elements + + scanReference func(string) *sx.Pair // Builds a reference node from a given string reference + isSpaceReference func([]byte) bool // Returns true, if src starts with a reference that allows white space +} + +// Initialize the parser with the input stream and a reference scanner. +func (cp *Parser) Initialize(inp *input.Input) { + var zeroParser Parser + *cp = zeroParser + cp.inp = inp + cp.scanReference = sz.ScanReference + cp.isSpaceReference = withQueryPrefix +} + +// Parse tries to parse the input as a block element. +func (cp *Parser) Parse() *sx.Pair { + + var lastPara *sx.Pair + var blkBuild sx.ListBuilder + for cp.inp.Ch != input.EOS { + lastPara = cp.parseBlock(&blkBuild, lastPara) + } + if cp.nestingLevel != 0 { + panic("Nesting level was not decremented") + } + + var pp postProcessor + if bs := pp.visitPairList(blkBuild.List(), nil); bs != nil { + return bs.Cons(zsx.SymBlock) + } + return nil +} + +func withQueryPrefix(src []byte) bool { + return len(src) > len(api.QueryPrefix) && string(src[:len(api.QueryPrefix)]) == api.QueryPrefix } // runeModGrave is Unicode code point U+02CB (715) called "MODIFIER LETTER // GRAVE ACCENT". On the iPad it is much more easier to type in this code point // than U+0060 (96) "Grave accent" (aka backtick). Therefore, U+02CB will be @@ -71,11 +78,11 @@ const runeModGrave = 'ˋ' // This is NOT '`'! const maxNestingLevel = 50 // clearStacked removes all multi-line nodes from parser. -func (cp *zmkP) clearStacked() { +func (cp *Parser) clearStacked() { cp.lists = nil cp.lastRow = nil cp.descrl = nil } @@ -247,5 +254,10 @@ } func isNameRune(ch rune) bool { return unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '-' || ch == '_' } + +// isBreakSym return true if the object is either a soft or a hard break symbol. +func isBreakSym(obj sx.Object) bool { + return zsx.SymSoft.IsEqual(obj) || zsx.SymHard.IsEqual(obj) +} Index: sz/zmk/zmk_fuzz_test.go ================================================================== --- sz/zmk/zmk_fuzz_test.go +++ sz/zmk/zmk_fuzz_test.go @@ -14,16 +14,18 @@ package zmk_test import ( "testing" - "t73f.de/r/zsc/input" "t73f.de/r/zsc/sz/zmk" + "t73f.de/r/zsx/input" ) func FuzzParseBlocks(f *testing.F) { + var parser zmk.Parser f.Fuzz(func(t *testing.T, src []byte) { t.Parallel() inp := input.NewInput(src) - zmk.Parse(inp) + parser.Initialize(inp) + parser.Parse() }) } Index: sz/zmk/zmk_test.go ================================================================== --- sz/zmk/zmk_test.go +++ sz/zmk/zmk_test.go @@ -18,13 +18,13 @@ "fmt" "strings" "testing" "t73f.de/r/sx" - "t73f.de/r/zsc/input" - "t73f.de/r/zsc/sz" "t73f.de/r/zsc/sz/zmk" + "t73f.de/r/zsx" + "t73f.de/r/zsx/input" ) type TestCase struct{ source, want string } type TestCases []TestCase type symbolMap map[string]*sx.Symbol @@ -48,16 +48,18 @@ } func checkTcs(t *testing.T, tcs TestCases) { t.Helper() + var parser zmk.Parser for tcn, tc := range tcs { t.Run(fmt.Sprintf("TC=%02d,src=%q", tcn, tc.source), func(st *testing.T) { st.Helper() inp := input.NewInput([]byte(tc.source)) - ast := zmk.Parse(inp) - sz.Walk(astWalker{}, ast, nil) + parser.Initialize(inp) + ast := parser.Parse() + zsx.Walk(astWalker{}, ast, nil) got := ast.String() if tc.want != got { st.Errorf("\nwant=%q\n got=%q", tc.want, got) } }) @@ -66,10 +68,17 @@ 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 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\") \"\")))))"}, + }) +} func TestEOL(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"", "()"}, @@ -142,49 +151,49 @@ {"[[|]", "(BLOCK (PARA (TEXT \"[[|]\")))"}, {"[[]]", "(BLOCK (PARA (TEXT \"[[]]\")))"}, {"[[|]]", "(BLOCK (PARA (TEXT \"[[|]]\")))"}, {"[[ ]]", "(BLOCK (PARA (TEXT \"[[ ]]\")))"}, {"[[\n]]", "(BLOCK (PARA (TEXT \"[[\") (SOFT) (TEXT \"]]\")))"}, - {"[[ a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\")))"}, + {"[[ a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\"))))"}, {"[[a ]]", "(BLOCK (PARA (TEXT \"[[a ]]\")))"}, {"[[a\n]]", "(BLOCK (PARA (TEXT \"[[a\") (SOFT) (TEXT \"]]\")))"}, - {"[[a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\")))"}, - {"[[12345678901234]]", "(BLOCK (PARA (LINK-ZETTEL () \"12345678901234\")))"}, + {"[[a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\"))))"}, + {"[[12345678901234]]", "(BLOCK (PARA (LINK () (ZETTEL \"12345678901234\"))))"}, {"[[a]", "(BLOCK (PARA (TEXT \"[[a]\")))"}, {"[[|a]]", "(BLOCK (PARA (TEXT \"[[|a]]\")))"}, {"[[b|]]", "(BLOCK (PARA (TEXT \"[[b|]]\")))"}, - {"[[b|a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\" (TEXT \"b\"))))"}, - {"[[b| a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\" (TEXT \"b\"))))"}, - {"[[b%c|a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\" (TEXT \"b%c\"))))"}, + {"[[b|a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\") (TEXT \"b\"))))"}, + {"[[b| a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\") (TEXT \"b\"))))"}, + {"[[b%c|a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\") (TEXT \"b%c\"))))"}, {"[[b%%c|a]]", "(BLOCK (PARA (TEXT \"[[b\") (LITERAL-COMMENT () \"c|a]]\")))"}, {"[[b|a]", "(BLOCK (PARA (TEXT \"[[b|a]\")))"}, - {"[[b\nc|a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\" (TEXT \"b\") (SOFT) (TEXT \"c\"))))"}, - {"[[b c|a#n]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a#n\" (TEXT \"b c\"))))"}, - {"[[a]]go", "(BLOCK (PARA (LINK-EXTERNAL () \"a\") (TEXT \"go\")))"}, - {"[[b|a]]{go}", "(BLOCK (PARA (LINK-EXTERNAL ((\"go\" . \"\")) \"a\" (TEXT \"b\"))))"}, - {"[[[[a]]|b]]", "(BLOCK (PARA (TEXT \"[[\") (LINK-EXTERNAL () \"a\") (TEXT \"|b]]\")))"}, - {"[[a[b]c|d]]", "(BLOCK (PARA (LINK-EXTERNAL () \"d\" (TEXT \"a[b]c\"))))"}, - {"[[[b]c|d]]", "(BLOCK (PARA (TEXT \"[\") (LINK-EXTERNAL () \"d\" (TEXT \"b]c\"))))"}, - {"[[a[]c|d]]", "(BLOCK (PARA (LINK-EXTERNAL () \"d\" (TEXT \"a[]c\"))))"}, - {"[[a[b]|d]]", "(BLOCK (PARA (LINK-EXTERNAL () \"d\" (TEXT \"a[b]\"))))"}, - {"[[\\|]]", "(BLOCK (PARA (LINK-EXTERNAL () \"\\\\|\")))"}, - {"[[\\||a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\" (TEXT \"|\"))))"}, - {"[[b\\||a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\" (TEXT \"b|\"))))"}, - {"[[b\\|c|a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\" (TEXT \"b|c\"))))"}, - {"[[\\]]]", "(BLOCK (PARA (LINK-EXTERNAL () \"\\\\]\")))"}, - {"[[\\]|a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\" (TEXT \"]\"))))"}, - {"[[b\\]|a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\" (TEXT \"b]\"))))"}, - {"[[\\]\\||a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"a\" (TEXT \"]|\"))))"}, - {"[[http://a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"http://a\")))"}, - {"[[http://a|http://a]]", "(BLOCK (PARA (LINK-EXTERNAL () \"http://a\" (TEXT \"http://a\"))))"}, - {"[[[[a]]]]", "(BLOCK (PARA (TEXT \"[[\") (LINK-EXTERNAL () \"a\") (TEXT \"]]\")))"}, - {"[[query:title]]", "(BLOCK (PARA (LINK-QUERY () \"title\")))"}, - {"[[query:title syntax]]", "(BLOCK (PARA (LINK-QUERY () \"title syntax\")))"}, - {"[[query:title | action]]", "(BLOCK (PARA (LINK-QUERY () \"title | action\")))"}, - {"[[Text|query:title]]", "(BLOCK (PARA (LINK-QUERY () \"title\" (TEXT \"Text\"))))"}, - {"[[Text|query:title syntax]]", "(BLOCK (PARA (LINK-QUERY () \"title syntax\" (TEXT \"Text\"))))"}, - {"[[Text|query:title | action]]", "(BLOCK (PARA (LINK-QUERY () \"title | action\" (TEXT \"Text\"))))"}, + {"[[b\nc|a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\") (TEXT \"b\") (SOFT) (TEXT \"c\"))))"}, + {"[[b c|a#n]]", "(BLOCK (PARA (LINK () (HOSTED \"a#n\") (TEXT \"b c\"))))"}, + {"[[a]]go", "(BLOCK (PARA (LINK () (HOSTED \"a\")) (TEXT \"go\")))"}, + {"[[b|a]]{go}", "(BLOCK (PARA (LINK ((\"go\" . \"\")) (HOSTED \"a\") (TEXT \"b\"))))"}, + {"[[[[a]]|b]]", "(BLOCK (PARA (TEXT \"[[\") (LINK () (HOSTED \"a\")) (TEXT \"|b]]\")))"}, + {"[[a[b]c|d]]", "(BLOCK (PARA (LINK () (HOSTED \"d\") (TEXT \"a[b]c\"))))"}, + {"[[[b]c|d]]", "(BLOCK (PARA (TEXT \"[\") (LINK () (HOSTED \"d\") (TEXT \"b]c\"))))"}, + {"[[a[]c|d]]", "(BLOCK (PARA (LINK () (HOSTED \"d\") (TEXT \"a[]c\"))))"}, + {"[[a[b]|d]]", "(BLOCK (PARA (LINK () (HOSTED \"d\") (TEXT \"a[b]\"))))"}, + {"[[\\|]]", "(BLOCK (PARA (LINK () (INVALID \"\\\\|\"))))"}, + {"[[\\||a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\") (TEXT \"|\"))))"}, + {"[[b\\||a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\") (TEXT \"b|\"))))"}, + {"[[b\\|c|a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\") (TEXT \"b|c\"))))"}, + {"[[\\]]]", "(BLOCK (PARA (LINK () (INVALID \"\\\\]\"))))"}, + {"[[\\]|a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\") (TEXT \"]\"))))"}, + {"[[b\\]|a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\") (TEXT \"b]\"))))"}, + {"[[\\]\\||a]]", "(BLOCK (PARA (LINK () (HOSTED \"a\") (TEXT \"]|\"))))"}, + {"[[http://a]]", "(BLOCK (PARA (LINK () (EXTERNAL \"http://a\"))))"}, + {"[[http://a|http://a]]", "(BLOCK (PARA (LINK () (EXTERNAL \"http://a\") (TEXT \"http://a\"))))"}, + {"[[[[a]]]]", "(BLOCK (PARA (TEXT \"[[\") (LINK () (HOSTED \"a\")) (TEXT \"]]\")))"}, + {"[[query:title]]", "(BLOCK (PARA (LINK () (QUERY \"title\"))))"}, + {"[[query:title syntax]]", "(BLOCK (PARA (LINK () (QUERY \"title syntax\"))))"}, + {"[[query:title | action]]", "(BLOCK (PARA (LINK () (QUERY \"title | action\"))))"}, + {"[[Text|query:title]]", "(BLOCK (PARA (LINK () (QUERY \"title\") (TEXT \"Text\"))))"}, + {"[[Text|query:title syntax]]", "(BLOCK (PARA (LINK () (QUERY \"title syntax\") (TEXT \"Text\"))))"}, + {"[[Text|query:title | action]]", "(BLOCK (PARA (LINK () (QUERY \"title | action\") (TEXT \"Text\"))))"}, }) } func TestEmbed(t *testing.T) { t.Parallel() @@ -198,34 +207,34 @@ {"{{|}}", "(BLOCK (PARA (TEXT \"{{|}}\")))"}, {"{{ }}", "(BLOCK (PARA (TEXT \"{{ }}\")))"}, {"{{\n}}", "(BLOCK (PARA (TEXT \"{{\") (SOFT) (TEXT \"}}\")))"}, {"{{a }}", "(BLOCK (PARA (TEXT \"{{a }}\")))"}, {"{{a\n}}", "(BLOCK (PARA (TEXT \"{{a\") (SOFT) (TEXT \"}}\")))"}, - {"{{a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a\") \"\")))"}, + {"{{a}}", "(BLOCK (PARA (EMBED () (HOSTED \"a\") \"\")))"}, {"{{12345678901234}}", "(BLOCK (PARA (EMBED () (ZETTEL \"12345678901234\") \"\")))"}, - {"{{ a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a\") \"\")))"}, + {"{{ a}}", "(BLOCK (PARA (EMBED () (HOSTED \"a\") \"\")))"}, {"{{a}", "(BLOCK (PARA (TEXT \"{{a}\")))"}, {"{{|a}}", "(BLOCK (PARA (TEXT \"{{|a}}\")))"}, {"{{b|}}", "(BLOCK (PARA (TEXT \"{{b|}}\")))"}, - {"{{b|a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a\") \"\" (TEXT \"b\"))))"}, - {"{{b| a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a\") \"\" (TEXT \"b\"))))"}, + {"{{b|a}}", "(BLOCK (PARA (EMBED () (HOSTED \"a\") \"\" (TEXT \"b\"))))"}, + {"{{b| a}}", "(BLOCK (PARA (EMBED () (HOSTED \"a\") \"\" (TEXT \"b\"))))"}, {"{{b|a}", "(BLOCK (PARA (TEXT \"{{b|a}\")))"}, - {"{{b\nc|a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a\") \"\" (TEXT \"b\") (SOFT) (TEXT \"c\"))))"}, - {"{{b c|a#n}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a#n\") \"\" (TEXT \"b c\"))))"}, - {"{{a}}{go}", "(BLOCK (PARA (EMBED ((\"go\" . \"\")) (EXTERNAL \"a\") \"\")))"}, - {"{{{{a}}|b}}", "(BLOCK (PARA (TEXT \"{{\") (EMBED () (EXTERNAL \"a\") \"\") (TEXT \"|b}}\")))"}, - {"{{\\|}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"\\\\|\") \"\")))"}, - {"{{\\||a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a\") \"\" (TEXT \"|\"))))"}, - {"{{b\\||a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a\") \"\" (TEXT \"b|\"))))"}, - {"{{b\\|c|a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a\") \"\" (TEXT \"b|c\"))))"}, - {"{{\\}}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"\\\\}\") \"\")))"}, - {"{{\\}|a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a\") \"\" (TEXT \"}\"))))"}, - {"{{b\\}|a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a\") \"\" (TEXT \"b}\"))))"}, - {"{{\\}\\||a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"a\") \"\" (TEXT \"}|\"))))"}, + {"{{b\nc|a}}", "(BLOCK (PARA (EMBED () (HOSTED \"a\") \"\" (TEXT \"b\") (SOFT) (TEXT \"c\"))))"}, + {"{{b c|a#n}}", "(BLOCK (PARA (EMBED () (HOSTED \"a#n\") \"\" (TEXT \"b c\"))))"}, + {"{{a}}{go}", "(BLOCK (PARA (EMBED ((\"go\" . \"\")) (HOSTED \"a\") \"\")))"}, + {"{{{{a}}|b}}", "(BLOCK (PARA (TEXT \"{{\") (EMBED () (HOSTED \"a\") \"\") (TEXT \"|b}}\")))"}, + {"{{\\|}}", "(BLOCK (PARA (EMBED () (INVALID \"\\\\|\") \"\")))"}, + {"{{\\||a}}", "(BLOCK (PARA (EMBED () (HOSTED \"a\") \"\" (TEXT \"|\"))))"}, + {"{{b\\||a}}", "(BLOCK (PARA (EMBED () (HOSTED \"a\") \"\" (TEXT \"b|\"))))"}, + {"{{b\\|c|a}}", "(BLOCK (PARA (EMBED () (HOSTED \"a\") \"\" (TEXT \"b|c\"))))"}, + {"{{\\}}}", "(BLOCK (PARA (EMBED () (INVALID \"\\\\}\") \"\")))"}, + {"{{\\}|a}}", "(BLOCK (PARA (EMBED () (HOSTED \"a\") \"\" (TEXT \"}\"))))"}, + {"{{b\\}|a}}", "(BLOCK (PARA (EMBED () (HOSTED \"a\") \"\" (TEXT \"b}\"))))"}, + {"{{\\}\\||a}}", "(BLOCK (PARA (EMBED () (HOSTED \"a\") \"\" (TEXT \"}|\"))))"}, {"{{http://a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"http://a\") \"\")))"}, {"{{http://a|http://a}}", "(BLOCK (PARA (EMBED () (EXTERNAL \"http://a\") \"\" (TEXT \"http://a\"))))"}, - {"{{{{a}}}}", "(BLOCK (PARA (TEXT \"{{\") (EMBED () (EXTERNAL \"a\") \"\") (TEXT \"}}\")))"}, + {"{{{{a}}}}", "(BLOCK (PARA (TEXT \"{{\") (EMBED () (HOSTED \"a\") \"\") (TEXT \"}}\")))"}, }) } func TestCite(t *testing.T) { t.Parallel() @@ -311,18 +320,18 @@ }) } func TestFormat(t *testing.T) { symMap := symbolMap{ - "_": sz.SymFormatEmph, - "*": sz.SymFormatStrong, - ">": sz.SymFormatInsert, - "~": sz.SymFormatDelete, - "^": sz.SymFormatSuper, - ",": sz.SymFormatSub, - "#": sz.SymFormatMark, - ":": sz.SymFormatSpan, + "_": zsx.SymFormatEmph, + "*": zsx.SymFormatStrong, + ">": zsx.SymFormatInsert, + "~": zsx.SymFormatDelete, + "^": zsx.SymFormatSuper, + ",": zsx.SymFormatSub, + "#": zsx.SymFormatMark, + ":": zsx.SymFormatSpan, } t.Parallel() // Not for Insert / '>', because collision with quoted list // Not for Quote / '"', because escaped representation. for _, ch := range []string{"_", "*", "~", "^", ",", "#", ":"} { @@ -352,11 +361,11 @@ })) checkTcs(t, replace(ch, symMap, TestCases{ {"$$a\n\na$$", "(BLOCK (PARA (TEXT \"$$a\")) (PARA (TEXT \"a$$\")))"}, })) } - checkTcs(t, replace(`"`, symbolMap{`"`: sz.SymFormatQuote}, TestCases{ + checkTcs(t, replace(`"`, symbolMap{`"`: zsx.SymFormatQuote}, TestCases{ {"$", "(BLOCK (PARA (TEXT \"\\\"\")))"}, {"$$", "(BLOCK (PARA (TEXT \"\\\"\\\"\")))"}, {"$$$", "(BLOCK (PARA (TEXT \"\\\"\\\"\\\"\")))"}, {"$$$$", "(BLOCK (PARA ($% ())))"}, @@ -381,13 +390,13 @@ }) } func TestLiteral(t *testing.T) { symMap := symbolMap{ - "`": sz.SymLiteralCode, - "'": sz.SymLiteralInput, - "=": sz.SymLiteralOutput, + "`": zsx.SymLiteralCode, + "'": zsx.SymLiteralInput, + "=": zsx.SymLiteralOutput, } t.Parallel() for _, ch := range []string{"`", "'", "="} { checkTcs(t, replace(ch, symMap, TestCases{ {"$", "(BLOCK (PARA (TEXT \"$\")))"}, @@ -610,15 +619,15 @@ {"=\n", "(BLOCK (PARA (TEXT \"=\")))"}, {"a=", "(BLOCK (PARA (TEXT \"a=\")))"}, {" =", "(BLOCK (PARA (TEXT \"=\")))"}, {"=== h\na", "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"h\")) (PARA (TEXT \"a\")))"}, {"=== h i {-}", "(BLOCK (HEADING 1 ((\"-\" . \"\")) \"\" \"\" (TEXT \"h i\")))"}, - {"=== h {{a}}", "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"h \") (EMBED () (EXTERNAL \"a\") \"\")))"}, - {"=== h{{a}}", "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"h\") (EMBED () (EXTERNAL \"a\") \"\")))"}, - {"=== {{a}}", "(BLOCK (HEADING 1 () \"\" \"\" (EMBED () (EXTERNAL \"a\") \"\")))"}, - {"=== h {{a}}{-}", "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"h \") (EMBED ((\"-\" . \"\")) (EXTERNAL \"a\") \"\")))"}, - {"=== h {{a}} {-}", "(BLOCK (HEADING 1 ((\"-\" . \"\")) \"\" \"\" (TEXT \"h \") (EMBED () (EXTERNAL \"a\") \"\")))"}, + {"=== h {{a}}", "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"h \") (EMBED () (HOSTED \"a\") \"\")))"}, + {"=== h{{a}}", "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"h\") (EMBED () (HOSTED \"a\") \"\")))"}, + {"=== {{a}}", "(BLOCK (HEADING 1 () \"\" \"\" (EMBED () (HOSTED \"a\") \"\")))"}, + {"=== h {{a}}{-}", "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"h \") (EMBED ((\"-\" . \"\")) (HOSTED \"a\") \"\")))"}, + {"=== h {{a}} {-}", "(BLOCK (HEADING 1 ((\"-\" . \"\")) \"\" \"\" (TEXT \"h \") (EMBED () (HOSTED \"a\") \"\")))"}, {"=== h {-}{{a}}", "(BLOCK (HEADING 1 ((\"-\" . \"\")) \"\" \"\" (TEXT \"h\")))"}, {"=== h{id=abc}", "(BLOCK (HEADING 1 ((\"id\" . \"abc\")) \"\" \"\" (TEXT \"h\")))"}, {"=== h\n=== h", "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"h\")) (HEADING 1 () \"\" \"\" (TEXT \"h\")))"}, }) } @@ -652,127 +661,127 @@ {"$$ ", "(BLOCK (PARA (TEXT \"$$\")))"}, {"$$$ ", "(BLOCK (PARA (TEXT \"$$$\")))"}, })) } checkTcs(t, TestCases{ - {"* abc", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")))))"}, - {"** abc", "(BLOCK (UNORDERED (BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")))))))"}, - {"*** abc", "(BLOCK (UNORDERED (BLOCK (UNORDERED (BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")))))))))"}, - {"**** abc", "(BLOCK (UNORDERED (BLOCK (UNORDERED (BLOCK (UNORDERED (BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")))))))))))"}, - {"** abc\n**** def", "(BLOCK (UNORDERED (BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")) (UNORDERED (BLOCK (UNORDERED (BLOCK (PARA (TEXT \"def\")))))))))))"}, - {"* abc\ndef", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")))) (PARA (TEXT \"def\")))"}, - {"* abc\n def", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")))) (PARA (TEXT \"def\")))"}, - {"* abc\n* def", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\"))) (BLOCK (PARA (TEXT \"def\")))))"}, - {"* abc\n def", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\") (SOFT) (TEXT \"def\")))))"}, - {"* abc\n def", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\") (SOFT) (TEXT \"def\")))))"}, - {"* abc\n\ndef", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")))) (PARA (TEXT \"def\")))"}, - {"* abc\n\n def", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")))) (PARA (TEXT \"def\")))"}, - {"* abc\n\n def", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")) (PARA (TEXT \"def\")))))"}, - {"* abc\n\n def", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")) (PARA (TEXT \"def\")))))"}, - {"* abc\n** def", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")) (UNORDERED (BLOCK (PARA (TEXT \"def\")))))))"}, - {"* abc\n** def\n* ghi", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")) (UNORDERED (BLOCK (PARA (TEXT \"def\"))))) (BLOCK (PARA (TEXT \"ghi\")))))"}, - {"* abc\n\n def\n* ghi", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")) (PARA (TEXT \"def\"))) (BLOCK (PARA (TEXT \"ghi\")))))"}, - {"* abc\n** def\n ghi\n jkl", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")) (UNORDERED (BLOCK (PARA (TEXT \"def\") (SOFT) (TEXT \"ghi\")))) (PARA (TEXT \"jkl\")))))"}, + {"* abc", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")))))"}, + {"** abc", "(BLOCK (UNORDERED () (BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")))))))"}, + {"*** abc", "(BLOCK (UNORDERED () (BLOCK (UNORDERED () (BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")))))))))"}, + {"**** abc", "(BLOCK (UNORDERED () (BLOCK (UNORDERED () (BLOCK (UNORDERED () (BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")))))))))))"}, + {"** abc\n**** def", "(BLOCK (UNORDERED () (BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")) (UNORDERED () (BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"def\")))))))))))"}, + {"* abc\ndef", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")))) (PARA (TEXT \"def\")))"}, + {"* abc\n def", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")))) (PARA (TEXT \"def\")))"}, + {"* abc\n* def", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\"))) (BLOCK (PARA (TEXT \"def\")))))"}, + {"* abc\n def", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\") (SOFT) (TEXT \"def\")))))"}, + {"* abc\n def", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\") (SOFT) (TEXT \"def\")))))"}, + {"* abc\n\ndef", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")))) (PARA (TEXT \"def\")))"}, + {"* abc\n\n def", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")))) (PARA (TEXT \"def\")))"}, + {"* abc\n\n def", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")) (PARA (TEXT \"def\")))))"}, + {"* abc\n\n def", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")) (PARA (TEXT \"def\")))))"}, + {"* abc\n** def", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")) (UNORDERED () (BLOCK (PARA (TEXT \"def\")))))))"}, + {"* abc\n** def\n* ghi", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")) (UNORDERED () (BLOCK (PARA (TEXT \"def\"))))) (BLOCK (PARA (TEXT \"ghi\")))))"}, + {"* abc\n\n def\n* ghi", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")) (PARA (TEXT \"def\"))) (BLOCK (PARA (TEXT \"ghi\")))))"}, + {"* abc\n** def\n ghi\n jkl", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")) (UNORDERED () (BLOCK (PARA (TEXT \"def\") (SOFT) (TEXT \"ghi\")))) (PARA (TEXT \"jkl\")))))"}, // A list does not last beyond a region - {":::\n# abc\n:::\n# def", "(BLOCK (REGION-BLOCK () ((ORDERED (BLOCK (PARA (TEXT \"abc\")))))) (ORDERED (BLOCK (PARA (TEXT \"def\")))))"}, + {":::\n# abc\n:::\n# def", "(BLOCK (REGION-BLOCK () ((ORDERED () (BLOCK (PARA (TEXT \"abc\")))))) (ORDERED () (BLOCK (PARA (TEXT \"def\")))))"}, // A HRule creates a new list - {"* abc\n---\n* def", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")))) (THEMATIC ()) (UNORDERED (BLOCK (PARA (TEXT \"def\")))))"}, + {"* abc\n---\n* def", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")))) (THEMATIC ()) (UNORDERED () (BLOCK (PARA (TEXT \"def\")))))"}, // Changing list type adds a new list - {"* abc\n# def", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")))) (ORDERED (BLOCK (PARA (TEXT \"def\")))))"}, + {"* abc\n# def", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")))) (ORDERED () (BLOCK (PARA (TEXT \"def\")))))"}, // Quotation lists may have empty items - {">", "(BLOCK (QUOTATION (BLOCK)))"}, + {">", "(BLOCK (QUOTATION () (BLOCK)))"}, // Empty continuation - {"* abc\n ", "(BLOCK (UNORDERED (BLOCK (PARA (TEXT \"abc\")))))"}, + {"* abc\n ", "(BLOCK (UNORDERED () (BLOCK (PARA (TEXT \"abc\")))))"}, }) } func TestQuoteList(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {"> w1 w2", "(BLOCK (QUOTATION (BLOCK (PARA (TEXT \"w1 w2\")))))"}, - {"> w1\n> w2", "(BLOCK (QUOTATION (BLOCK (PARA (TEXT \"w1\") (SOFT) (TEXT \"w2\")))))"}, - {"> w1\n>w2", "(BLOCK (QUOTATION (BLOCK (PARA (TEXT \"w1\")))) (PARA (TEXT \">w2\")))"}, - {"> w1\n>\n>w2", "(BLOCK (QUOTATION (BLOCK (PARA (TEXT \"w1\"))) (BLOCK)) (PARA (TEXT \">w2\")))"}, - {"> w1\n> \n> w2", "(BLOCK (QUOTATION (BLOCK (PARA (TEXT \"w1\"))) (BLOCK) (BLOCK (PARA (TEXT \"w2\")))))"}, + {"> w1 w2", "(BLOCK (QUOTATION () (BLOCK (PARA (TEXT \"w1 w2\")))))"}, + {"> w1\n> w2", "(BLOCK (QUOTATION () (BLOCK (PARA (TEXT \"w1\") (SOFT) (TEXT \"w2\")))))"}, + {"> w1\n>w2", "(BLOCK (QUOTATION () (BLOCK (PARA (TEXT \"w1\")))) (PARA (TEXT \">w2\")))"}, + {"> w1\n>\n>w2", "(BLOCK (QUOTATION () (BLOCK (PARA (TEXT \"w1\"))) (BLOCK)) (PARA (TEXT \">w2\")))"}, + {"> w1\n> \n> w2", "(BLOCK (QUOTATION () (BLOCK (PARA (TEXT \"w1\"))) (BLOCK) (BLOCK (PARA (TEXT \"w2\")))))"}, }) } func TestEnumAfterPara(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {"abc\n* def", "(BLOCK (PARA (TEXT \"abc\")) (UNORDERED (BLOCK (PARA (TEXT \"def\")))))"}, + {"abc\n* def", "(BLOCK (PARA (TEXT \"abc\")) (UNORDERED () (BLOCK (PARA (TEXT \"def\")))))"}, {"abc\n*def", "(BLOCK (PARA (TEXT \"abc\") (SOFT) (TEXT \"*def\")))"}, }) } func TestDefinition(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {";", "(BLOCK (PARA (TEXT \";\")))"}, {"; ", "(BLOCK (PARA (TEXT \";\")))"}, - {"; abc", "(BLOCK (DESCRIPTION ((TEXT \"abc\"))))"}, - {"; abc\ndef", "(BLOCK (DESCRIPTION ((TEXT \"abc\"))) (PARA (TEXT \"def\")))"}, - {"; abc\n def", "(BLOCK (DESCRIPTION ((TEXT \"abc\"))) (PARA (TEXT \"def\")))"}, - {"; abc\n def", "(BLOCK (DESCRIPTION ((TEXT \"abc\") (SOFT) (TEXT \"def\"))))"}, - {"; abc\n def\n ghi", "(BLOCK (DESCRIPTION ((TEXT \"abc\") (SOFT) (TEXT \"def\") (SOFT) (TEXT \"ghi\"))))"}, + {"; abc", "(BLOCK (DESCRIPTION () ((TEXT \"abc\"))))"}, + {"; abc\ndef", "(BLOCK (DESCRIPTION () ((TEXT \"abc\"))) (PARA (TEXT \"def\")))"}, + {"; abc\n def", "(BLOCK (DESCRIPTION () ((TEXT \"abc\"))) (PARA (TEXT \"def\")))"}, + {"; abc\n def", "(BLOCK (DESCRIPTION () ((TEXT \"abc\") (SOFT) (TEXT \"def\"))))"}, + {"; abc\n def\n ghi", "(BLOCK (DESCRIPTION () ((TEXT \"abc\") (SOFT) (TEXT \"def\") (SOFT) (TEXT \"ghi\"))))"}, {":", "(BLOCK (PARA (TEXT \":\")))"}, {": ", "(BLOCK (PARA (TEXT \":\")))"}, {": abc", "(BLOCK (PARA (TEXT \": abc\")))"}, - {"; abc\n: def", "(BLOCK (DESCRIPTION ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\"))))))"}, - {"; abc\n: def\nghi", "(BLOCK (DESCRIPTION ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\"))))) (PARA (TEXT \"ghi\")))"}, - {"; abc\n: def\n ghi", "(BLOCK (DESCRIPTION ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\"))))) (PARA (TEXT \"ghi\")))"}, - {"; abc\n: def\n ghi", "(BLOCK (DESCRIPTION ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\") (SOFT) (TEXT \"ghi\"))))))"}, - {"; abc\n: def\n\n ghi", "(BLOCK (DESCRIPTION ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\")) (PARA (TEXT \"ghi\"))))))"}, - {"; abc\n: def\n\n ghi\n\n jkl", "(BLOCK (DESCRIPTION ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\")) (PARA (TEXT \"ghi\")) (PARA (TEXT \"jkl\"))))))"}, - {"; abc\n:", "(BLOCK (DESCRIPTION ((TEXT \"abc\"))) (PARA (TEXT \":\")))"}, - {"; abc\n: def\n: ghi", "(BLOCK (DESCRIPTION ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\"))) (BLOCK (PARA (TEXT \"ghi\"))))))"}, - {"; abc\n: def\n; ghi\n: jkl", "(BLOCK (DESCRIPTION ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\")))) ((TEXT \"ghi\")) (BLOCK (BLOCK (PARA (TEXT \"jkl\"))))))"}, + {"; abc\n: def", "(BLOCK (DESCRIPTION () ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\"))))))"}, + {"; abc\n: def\nghi", "(BLOCK (DESCRIPTION () ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\"))))) (PARA (TEXT \"ghi\")))"}, + {"; abc\n: def\n ghi", "(BLOCK (DESCRIPTION () ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\"))))) (PARA (TEXT \"ghi\")))"}, + {"; abc\n: def\n ghi", "(BLOCK (DESCRIPTION () ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\") (SOFT) (TEXT \"ghi\"))))))"}, + {"; abc\n: def\n\n ghi", "(BLOCK (DESCRIPTION () ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\")) (PARA (TEXT \"ghi\"))))))"}, + {"; abc\n: def\n\n ghi\n\n jkl", "(BLOCK (DESCRIPTION () ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\")) (PARA (TEXT \"ghi\")) (PARA (TEXT \"jkl\"))))))"}, + {"; abc\n:", "(BLOCK (DESCRIPTION () ((TEXT \"abc\"))) (PARA (TEXT \":\")))"}, + {"; abc\n: def\n: ghi", "(BLOCK (DESCRIPTION () ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\"))) (BLOCK (PARA (TEXT \"ghi\"))))))"}, + {"; abc\n: def\n; ghi\n: jkl", "(BLOCK (DESCRIPTION () ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\")))) ((TEXT \"ghi\")) (BLOCK (BLOCK (PARA (TEXT \"jkl\"))))))"}, // Empty description - {"; abc\n: ", "(BLOCK (DESCRIPTION ((TEXT \"abc\"))) (PARA (TEXT \":\")))"}, + {"; abc\n: ", "(BLOCK (DESCRIPTION () ((TEXT \"abc\"))) (PARA (TEXT \":\")))"}, // Empty continuation of definition - {"; abc\n: def\n ", "(BLOCK (DESCRIPTION ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\"))))))"}, + {"; abc\n: def\n ", "(BLOCK (DESCRIPTION () ((TEXT \"abc\")) (BLOCK (BLOCK (PARA (TEXT \"def\"))))))"}, }) } func TestTable(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"|", "()"}, - {"||", "(BLOCK (TABLE () ((CELL))))"}, - {"| |", "(BLOCK (TABLE () ((CELL))))"}, - {"|a", "(BLOCK (TABLE () ((CELL (TEXT \"a\")))))"}, - {"|a|", "(BLOCK (TABLE () ((CELL (TEXT \"a\")))))"}, - {"|a| ", "(BLOCK (TABLE () ((CELL (TEXT \"a\")) (CELL))))"}, - {"|a|b", "(BLOCK (TABLE () ((CELL (TEXT \"a\")) (CELL (TEXT \"b\")))))"}, - {"|a\n|b", "(BLOCK (TABLE () ((CELL (TEXT \"a\"))) ((CELL (TEXT \"b\")))))"}, - {"|a|b\n|c|d", "(BLOCK (TABLE () ((CELL (TEXT \"a\")) (CELL (TEXT \"b\"))) ((CELL (TEXT \"c\")) (CELL (TEXT \"d\")))))"}, + {"||", "(BLOCK (TABLE () ((CELL ()))))"}, + {"| |", "(BLOCK (TABLE () ((CELL ()))))"}, + {"|a", "(BLOCK (TABLE () ((CELL () (TEXT \"a\")))))"}, + {"|a|", "(BLOCK (TABLE () ((CELL () (TEXT \"a\")))))"}, + {"|a| ", "(BLOCK (TABLE () ((CELL () (TEXT \"a\")) (CELL ()))))"}, + {"|a|b", "(BLOCK (TABLE () ((CELL () (TEXT \"a\")) (CELL () (TEXT \"b\")))))"}, + {"|a\n|b", "(BLOCK (TABLE () ((CELL () (TEXT \"a\"))) ((CELL () (TEXT \"b\")))))"}, + {"|a|b\n|c|d", "(BLOCK (TABLE () ((CELL () (TEXT \"a\")) (CELL () (TEXT \"b\"))) ((CELL () (TEXT \"c\")) (CELL () (TEXT \"d\")))))"}, {"|%", "()"}, - {"|=a", "(BLOCK (TABLE ((CELL (TEXT \"a\")))))"}, - {"|=a\n|b", "(BLOCK (TABLE ((CELL (TEXT \"a\"))) ((CELL (TEXT \"b\")))))"}, - {"|a|b\n|%---\n|c|d", "(BLOCK (TABLE () ((CELL (TEXT \"a\")) (CELL (TEXT \"b\"))) ((CELL (TEXT \"c\")) (CELL (TEXT \"d\")))))"}, - {"|a|b\n|c", "(BLOCK (TABLE () ((CELL (TEXT \"a\")) (CELL (TEXT \"b\"))) ((CELL (TEXT \"c\")) (CELL))))"}, - {"|=\n|b|c", "(BLOCK (TABLE ((CELL-LEFT (TEXT \"a\")) (CELL)) ((CELL-RIGHT (TEXT \"b\")) (CELL (TEXT \"c\")))))"}, - {"|=\n||", "(BLOCK (TABLE ((CELL-LEFT (TEXT \"a\")) (CELL-RIGHT (TEXT \"b\"))) ((CELL) (CELL-RIGHT))))"}, + {"|=a", "(BLOCK (TABLE ((CELL () (TEXT \"a\")))))"}, + {"|=a\n|b", "(BLOCK (TABLE ((CELL () (TEXT \"a\"))) ((CELL () (TEXT \"b\")))))"}, + {"|a|b\n|%---\n|c|d", "(BLOCK (TABLE () ((CELL () (TEXT \"a\")) (CELL () (TEXT \"b\"))) ((CELL () (TEXT \"c\")) (CELL () (TEXT \"d\")))))"}, + {"|a|b\n|c", "(BLOCK (TABLE () ((CELL () (TEXT \"a\")) (CELL () (TEXT \"b\"))) ((CELL () (TEXT \"c\")) (CELL ()))))"}, + {"|=\n|b|c", "(BLOCK (TABLE ((CELL ((align . \"left\")) (TEXT \"a\")) (CELL ())) ((CELL ((align . \"right\")) (TEXT \"b\")) (CELL () (TEXT \"c\")))))"}, + {"|=\n||", "(BLOCK (TABLE ((CELL ((align . \"left\")) (TEXT \"a\")) (CELL ((align . \"right\")) (TEXT \"b\"))) ((CELL ()) (CELL ((align . \"right\"))))))"}, }) } func TestTransclude(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ - {"{{{a}}}", "(BLOCK (TRANSCLUDE () (EXTERNAL \"a\")))"}, - {"{{{a}}}b", "(BLOCK (TRANSCLUDE ((\"\" . \"b\")) (EXTERNAL \"a\")))"}, - {"{{{a}}}}", "(BLOCK (TRANSCLUDE () (EXTERNAL \"a\")))"}, - {"{{{a\\}}}}", "(BLOCK (TRANSCLUDE () (EXTERNAL \"a\\\\}\")))"}, - {"{{{a\\}}}}b", "(BLOCK (TRANSCLUDE ((\"\" . \"b\")) (EXTERNAL \"a\\\\}\")))"}, - {"{{{a}}", "(BLOCK (PARA (TEXT \"{\") (EMBED () (EXTERNAL \"a\") \"\")))"}, - {"{{{a}}}{go=b}", "(BLOCK (TRANSCLUDE ((\"go\" . \"b\")) (EXTERNAL \"a\")))"}, + {"{{{a}}}", "(BLOCK (TRANSCLUDE () (HOSTED \"a\")))"}, + {"{{{a}}}b", "(BLOCK (TRANSCLUDE ((\"\" . \"b\")) (HOSTED \"a\")))"}, + {"{{{a}}}}", "(BLOCK (TRANSCLUDE () (HOSTED \"a\")))"}, + {"{{{a\\}}}}", "(BLOCK (TRANSCLUDE () (INVALID \"a\\\\}\")))"}, + {"{{{a\\}}}}b", "(BLOCK (TRANSCLUDE ((\"\" . \"b\")) (INVALID \"a\\\\}\")))"}, + {"{{{a}}", "(BLOCK (PARA (TEXT \"{\") (EMBED () (HOSTED \"a\") \"\")))"}, + {"{{{a}}}{go=b}", "(BLOCK (TRANSCLUDE ((\"go\" . \"b\")) (HOSTED \"a\")))"}, }) } func TestBlockAttr(t *testing.T) { t.Parallel() Index: text/text.go ================================================================== --- text/text.go +++ text/text.go @@ -16,12 +16,12 @@ import ( "strings" "t73f.de/r/sx" - "t73f.de/r/zsc/input" - "t73f.de/r/zsc/sz" + "t73f.de/r/zsx" + "t73f.de/r/zsx/input" ) // Encoder is the structure to hold relevant data to execute the encoding. type Encoder struct { sb strings.Builder @@ -60,11 +60,11 @@ } sym := cmd.Car() if sx.IsNil(sym) { return } - if sym.IsEqual(sz.SymText) { + if sym.IsEqual(zsx.SymText) { args := cmd.Tail() if args == nil { return } if val, isString := sx.GetString(args.Car()); isString { @@ -79,13 +79,13 @@ enc.sb.WriteRune(ch) hadSpace = false } } } - } else if sym.IsEqual(sz.SymSoft) { + } else if sym.IsEqual(zsx.SymSoft) { enc.sb.WriteByte(' ') - } else if sym.IsEqual(sz.SymHard) { + } else if sym.IsEqual(zsx.SymHard) { enc.sb.WriteByte('\n') } else if !sym.IsEqual(sx.SymbolQuote) { enc.executeList(cmd.Tail()) } } Index: www/changes.wiki ================================================================== --- www/changes.wiki +++ www/changes.wiki @@ -1,9 +1,20 @@ Change Log + +

Changes for Version 0.22.0 (pending)

+ + +

Changes for Version 0.21.0 (2025-04-17)

+ * Sz encoding changed (LINK-* -> LINK, lists, descriptions, block BLOBs, + tables and its cells got attributes; cell attributes now defines cell + alignment). + * Add API call to retrieve external references from a zettel. + * Move some code to package t73f.de/r/zsx + -

Changes for Version 0.20.0 (pending)

+

Changes for Version 0.20.0 (2025-03-07)

* Add Zettelmarkup-Parser that translates to sz expressions * Add domain specific data structure from main zettelstore * client.QueryZettelData will support the new encoding of result lists. This make the client incompatible to version 0.19. Index: www/index.wiki ================================================================== --- www/index.wiki +++ www/index.wiki @@ -1,16 +1,16 @@ -Home +ZSC This repository contains Go client software to access [https://zettelstore.de|Zettelstore] via its API. -

Latest Release: 0.20.0 (2024-12-13)

- * [./changes.wiki#0_20|Change summary] - * [/timeline?p=v0.20.0&bt=v0.19.0&y=ci|Check-ins for version 0.20], - [/vdiff?to=v0.20.0&from=v0.19.0|content diff] - * [/timeline?df=v0.20.0&y=ci|Check-ins derived from the 0.20 release], - [/vdiff?from=v0.20.0&to=trunk|content diff] +

Latest Release: 0.21.0 (2025-03-07)

+ * [./changes.wiki#0_21|Change summary] + * [/timeline?p=v0.21.0&bt=v0.20.0&y=ci|Check-ins for version 0.21], + [/vdiff?to=v0.21.0&from=v0.20.0|content diff] + * [/timeline?df=v0.21.0&y=ci|Check-ins derived from the 0.21 release], + [/vdiff?from=v0.21.0&to=trunk|content diff] * [/timeline?t=release|Timeline of all past releases]

Usage instructions

To import this library into your own [https://go.dev/|Go] software, you need to