Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.22.0 To trunk
|
2025-11-06
| ||
| 13:52 | Update dependencies ... (Leaf check-in: a3b149d584 user: stern tags: trunk) | |
|
2025-10-27
| ||
| 17:13 | Parser: remove unneeded text encoder in Markdown parser ... (check-in: 6bb0627d73 user: stern tags: trunk) | |
|
2025-07-08
| ||
| 13:41 | Fix manual w.r.t. key types ... (check-in: c997eeffe6 user: stern tags: trunk) | |
|
2025-07-07
| ||
| 08:54 | Version 0.22.0 ... (check-in: 5336da12eb user: stern tags: trunk, release, v0.22.0) | |
| 07:31 | Update dependencies ... (check-in: 46803d88ec user: stern tags: trunk) | |
Deleted .github/dependabot.yml.
|
| < < < < < < < < < < < < |
Changes to VERSION.
|
| | | 1 | 0.23.0-dev |
Changes to cmd/cmd_file.go.
| ︙ | ︙ | |||
43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
zettel.Zettel{
Meta: m,
Content: zettel.NewContent(inp.Src[inp.Pos:]),
},
string(m.GetDefault(meta.KeySyntax, meta.DefaultSyntax)),
nil,
)
encdr := encoder.Create(
api.Encoder(enc),
&encoder.CreateParameter{Lang: string(m.GetDefault(meta.KeyLang, meta.ValueLangEN))})
if encdr == nil {
fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc)
return 2, nil
}
| > | < | 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
zettel.Zettel{
Meta: m,
Content: zettel.NewContent(inp.Src[inp.Pos:]),
},
string(m.GetDefault(meta.KeySyntax, meta.DefaultSyntax)),
nil,
)
parser.Clean(z.Blocks)
encdr := encoder.Create(
api.Encoder(enc),
&encoder.CreateParameter{Lang: string(m.GetDefault(meta.KeyLang, meta.ValueLangEN))})
if encdr == nil {
fmt.Fprintf(os.Stderr, "Unknown format %q\n", enc)
return 2, nil
}
if err = encdr.WriteZettel(os.Stdout, z); err != nil {
return 2, err
}
fmt.Println()
return 0, nil
}
|
| ︙ | ︙ |
Changes to cmd/cmd_run.go.
| ︙ | ︙ | |||
91 92 93 94 95 96 97 98 99 100 |
webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager))
if assetDir := kern.GetConfig(kernel.WebService, kernel.WebAssetDir).(string); assetDir != "" {
const assetPrefix = "/assets/"
webSrv.Handle(assetPrefix, http.StripPrefix(assetPrefix, http.FileServer(http.Dir(assetDir))))
webSrv.Handle("/favicon.ico", wui.MakeFaviconHandler(assetDir))
}
// Web user interface
if !authManager.IsReadonly() {
| > > | | | | | | | | | | | | | | | | | | | | | | | | | 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
webSrv.Handle("/", wui.MakeGetRootHandler(protectedBoxManager))
if assetDir := kern.GetConfig(kernel.WebService, kernel.WebAssetDir).(string); assetDir != "" {
const assetPrefix = "/assets/"
webSrv.Handle(assetPrefix, http.StripPrefix(assetPrefix, http.FileServer(http.Dir(assetDir))))
webSrv.Handle("/favicon.ico", wui.MakeFaviconHandler(assetDir))
}
const isAPI = true
// Web user interface
if !authManager.IsReadonly() {
webSrv.AddListRoute(!isAPI, 'c', server.MethodGet, wui.MakeGetZettelFromListHandler(&ucQuery, &ucEvaluate, ucListRoles, ucListSyntax))
webSrv.AddListRoute(!isAPI, 'c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel))
webSrv.AddZettelRoute(!isAPI, 'c', server.MethodGet, wui.MakeGetCreateZettelHandler(
ucGetZettel, &ucCreateZettel, ucListRoles, ucListSyntax))
webSrv.AddZettelRoute(!isAPI, 'c', server.MethodPost, wui.MakePostCreateZettelHandler(&ucCreateZettel))
webSrv.AddZettelRoute(!isAPI, 'd', server.MethodGet, wui.MakeGetDeleteZettelHandler(ucGetZettel, ucGetAllZettel))
webSrv.AddZettelRoute(!isAPI, 'd', server.MethodPost, wui.MakePostDeleteZettelHandler(&ucDelete))
webSrv.AddZettelRoute(!isAPI, 'e', server.MethodGet, wui.MakeEditGetZettelHandler(ucGetZettel, ucListRoles, ucListSyntax))
webSrv.AddZettelRoute(!isAPI, 'e', server.MethodPost, wui.MakeEditSetZettelHandler(&ucUpdate))
}
webSrv.AddListRoute(!isAPI, 'g', server.MethodGet, wui.MakeGetGoActionHandler(&ucRefresh))
webSrv.AddListRoute(!isAPI, 'h', server.MethodGet, wui.MakeListHTMLMetaHandler(&ucQuery, &ucTagZettel, &ucRoleZettel, &ucReIndex))
webSrv.AddZettelRoute(!isAPI, 'h', server.MethodGet, wui.MakeGetHTMLZettelHandler(&ucEvaluate, ucGetZettel))
webSrv.AddListRoute(!isAPI, 'i', server.MethodGet, wui.MakeGetLoginOutHandler())
webSrv.AddListRoute(!isAPI, 'i', server.MethodPost, wui.MakePostLoginHandler(&ucAuthenticate))
webSrv.AddZettelRoute(!isAPI, 'i', server.MethodGet, wui.MakeGetInfoHandler(
ucParseZettel, ucGetReferences, &ucEvaluate, ucGetZettel, ucGetAllZettel, &ucQuery))
// API
webSrv.AddListRoute(isAPI, 'a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate))
webSrv.AddListRoute(isAPI, 'a', server.MethodPut, a.MakeRenewAuthHandler())
webSrv.AddZettelRoute(isAPI, 'r', server.MethodGet, a.MakeGetReferencesHandler(ucParseZettel, ucGetReferences))
webSrv.AddListRoute(isAPI, 'x', server.MethodGet, a.MakeGetDataHandler(ucVersion))
webSrv.AddListRoute(isAPI, 'x', server.MethodPost, a.MakePostCommandHandler(&ucIsAuth, &ucRefresh))
webSrv.AddListRoute(isAPI, 'z', server.MethodGet, a.MakeQueryHandler(&ucQuery, &ucTagZettel, &ucRoleZettel, &ucReIndex))
webSrv.AddZettelRoute(isAPI, 'z', server.MethodGet, a.MakeGetZettelHandler(ucGetZettel, ucParseZettel, ucEvaluate))
if !authManager.IsReadonly() {
webSrv.AddListRoute(isAPI, 'z', server.MethodPost, a.MakePostCreateZettelHandler(&ucCreateZettel))
webSrv.AddZettelRoute(isAPI, 'z', server.MethodPut, a.MakeUpdateZettelHandler(&ucUpdate))
webSrv.AddZettelRoute(isAPI, 'z', server.MethodDelete, a.MakeDeleteZettelHandler(&ucDelete))
}
if authManager.WithAuth() {
webSrv.SetUserRetriever(usecase.NewGetUserByZid(boxManager))
}
}
|
| ︙ | ︙ |
Changes to docs/manual/00001004010000.zettel.
1 2 3 4 5 6 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001004010000 title: Zettelstore startup configuration role: manual tags: #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20250828135622 The configuration file, specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup options. These cannot be stored in a [[configuration zettel|00001004020000]] because they are needed before Zettelstore can start or because of security reasons. For example, Zettelstore needs to know in advance on which network address it must listen or where zettel are stored. An attacker that is able to change the owner can do anything. Therefore, only the owner of the computer on which Zettelstore runs can change this information. |
| ︙ | ︙ | |||
47 48 49 50 51 52 53 | During startup, __X__ is incremented, starting with one, until no key is found. This allows you to configure than one box. If no ''box-uri-1'' key is given, the overall effect will be the same as if only ''box-uri-1'' was specified with the value ""dir://.zettel"". In this case, even a key ''box-uri-2'' will be ignored. ; [!debug-mode|''debug-mode''] : If set to [[true|00001006030500]], allows to debug the Zettelstore software (mostly used by Zettelstore developers). | < | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | During startup, __X__ is incremented, starting with one, until no key is found. This allows you to configure than one box. If no ''box-uri-1'' key is given, the overall effect will be the same as if only ''box-uri-1'' was specified with the value ""dir://.zettel"". In this case, even a key ''box-uri-2'' will be ignored. ; [!debug-mode|''debug-mode''] : If set to [[true|00001006030500]], allows to debug the Zettelstore software (mostly used by Zettelstore developers). Sets [[''log-level''|#log-level]] to ""debug"". Enables [[''runtime-profiling''|#runtime-profiling]]. Do not enable it for a production server. Default: ""false"" ; [!default-dir-box-type|''default-dir-box-type''] |
| ︙ | ︙ |
Changes to docs/manual/00001004051000.zettel.
1 2 3 4 5 6 | id: 00001004051000 title: The ''run'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001004051000 title: The ''run'' sub-command role: manual tags: #command #configuration #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20250828135353 === ``zettelstore run`` This starts the web service. ``` zettelstore run [-a PORT] [-c CONFIGFILE] [-d DIR] [-debug] [-p PORT] [-r] [-v] ``` |
| ︙ | ︙ | |||
22 23 24 25 26 27 28 |
Default: tries to read the following files in the ""current directory"": ''zettelstore.cfg'', ''zsconfig.txt'', ''zscfg.txt'', ''_zscfg'', and ''.zscfg''.
; [!d|''-d DIR'']
: Specifies ''DIR'' as the directory that contains all zettel.
Default is ''./zettel'' (''.\\zettel'' on Windows), where ''.'' denotes the ""current directory"".
; [!debug|''-debug'']
| | | < < | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
Default: tries to read the following files in the ""current directory"": ''zettelstore.cfg'', ''zsconfig.txt'', ''zscfg.txt'', ''_zscfg'', and ''.zscfg''.
; [!d|''-d DIR'']
: Specifies ''DIR'' as the directory that contains all zettel.
Default is ''./zettel'' (''.\\zettel'' on Windows), where ''.'' denotes the ""current directory"".
; [!debug|''-debug'']
: Allows debugging of the internal web server.
Same as setting [[''debug-mode''|00001004010000#debug-mode]] to ""true"".
; [!p|''-p PORT'']
: Specifies the integer value ''PORT'' as the TCP port, where the Zettelstore web server listens for requests.
Default: 23123.
Zettelstore listens only on ''127.0.0.1'', e.g. only requests from the current computer will be processed.
If you want to listen on network card to process requests from other computer, please use [[''listen-addr''|00001004010000#listen-addr]] of the configuration file as described below.
|
| ︙ | ︙ |
Changes to docs/manual/00001006030000.zettel.
1 2 3 4 5 6 | id: 00001006030000 title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 | | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | id: 00001006030000 title: Supported Key Types role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 modified: 20250708154024 All [[supported metadata keys|00001006020000]] conform to a type. User-defined metadata keys conform also to a type, based on the suffix of the key. |=Suffix|Type | ''-date'' | [[Timestamp|00001006034500]] | ''-number'' | [[Number|00001006033000]] | ''-ref'' | [[Identifier|00001006032000]] | ''-refs'' | [[IdentifierSet|00001006032500]] | ''-role'' | [[Word|00001006035500]] | ''-time'' | [[Timestamp|00001006034500]] | ''-url'' | [[URL|00001006035000]] | ''-zettel'' | [[Identifier|00001006032000]] | ''-zid'' | [[Identifier|00001006032000]] | ''-zids'' | [[IdentifierSet|00001006032500]] | any other suffix | [[EString|00001006031500]] |
| ︙ | ︙ |
Changes to docs/manual/00001010000000.zettel.
1 2 3 4 5 6 | id: 00001010000000 title: Security role: manual tags: #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001010000000 title: Security role: manual tags: #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20250828121450 Your zettel may contain sensitive content. You probably want to ensure that only authorized persons can read and/or modify them. Zettelstore ensures this in various ways. === Local first The Zettelstore is designed to run on your local computer. |
| ︙ | ︙ | |||
61 62 63 64 65 66 67 | But you can put a server in front of it, which is able to handle encryption. Most generic web server software allow this. To enforce encryption, [[authenticated sessions|00001010040700]] are marked as secure by default. If you still want to access the Zettelstore remotely without encryption, you must change the startup configuration. Otherwise, authentication will not work. | | | 61 62 63 64 65 66 67 68 | But you can put a server in front of it, which is able to handle encryption. Most generic web server software allow this. To enforce encryption, [[authenticated sessions|00001010040700]] are marked as secure by default. If you still want to access the Zettelstore remotely without encryption, you must change the startup configuration. Otherwise, authentication will not work. * [[Use a server, also for encryption|00001010090100]] |
Changes to docs/manual/00001010090100.zettel.
1 | id: 00001010090100 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001010090100 title: External server, also to encrypt message transport role: manual tags: #configuration #encryption #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20250828135632 Since Zettelstore does not encrypt the messages it exchanges with its clients, you may need some additional software to enable encryption. === Public-key encryption To enable encryption, you probably use some kind of encryption keys. In most cases, you need to deploy a ""public-key encryption"" process, where your side publishes a public encryption key that only works with a corresponding private decryption key. Technically, this is not trivial. |
| ︙ | ︙ | |||
65 66 67 68 69 70 71 | } ``` This will forward requests with the prefix ""/manual/"" to the running Zettelstore. All other requests will be handled by Caddy itself. In this case you must specify the [[startup configuration key ''url-prefix''|00001004010000#url-prefix]] with the value ""/manual/"". This is to allow Zettelstore to ignore the prefix while reading web requests and to give the correct URLs with the given prefix when sending a web response. | > > > > > > > > > > > > > | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | } ``` This will forward requests with the prefix ""/manual/"" to the running Zettelstore. All other requests will be handled by Caddy itself. In this case you must specify the [[startup configuration key ''url-prefix''|00001004010000#url-prefix]] with the value ""/manual/"". This is to allow Zettelstore to ignore the prefix while reading web requests and to give the correct URLs with the given prefix when sending a web response. === Cross-origin protection Zettelstore protects you against [[Cross-Site Request Forgery (CSRF)|https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF]]. The protection will work only if the external server forwards certain HTTP request headers to Zettelstore: * [[''Sec-Fetch-Site''|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site]] is read first and should contain the values ""same-origin"" or ""none"". * [[''Origin''|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin]] is read next, if ''Sec-Fetch-Site'' is either missing or contains other values. In this case ''Origin'' should contain the same host name as read via header [[''Host''|https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Host]]. Otherwise, an cross-origin request will be detected. To debug the configuration of the external server, Zettelstore will log the received header values, if Zettelstore is run with the [[''-debug''|00001004051000#debug]] parameter. |
Changes to docs/manual/00001012053800.zettel.
1 2 3 4 5 6 | id: 00001012053800 title: API: Retrieve references of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20250415154139 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
id: 00001012053800
title: API: Retrieve references of an existing zettel
role: manual
tags: #api #manual #zettelstore
syntax: zmk
created: 20250415154139
modified: 20251013185705
The [[endpoint|00001012920000]] to retrieve references of a specific zettel is ''/r/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]].
A zettel may contain references to external material, in the form of an URI.
External material may be referenced via Zettelmarkup [[links|00001007040310]], [[inline embedding|00001007040320]], or a [[block transclusion|00001007031100]].
It may also be referenced within the [[metadata|00001006000000]] of a zettel, when a key of [[type|00001006030000]] [[URL|00001006035000]] is given.
|
| ︙ | ︙ | |||
37 38 39 40 41 42 43 | https://commonmark.org/ https://xkcd.com/927/ https://github.github.com/gfm/ https://spec.commonmark.org/0.31.2/ https://github.com/yuin/goldmark ``` | | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | https://commonmark.org/ https://xkcd.com/927/ https://github.github.com/gfm/ https://spec.commonmark.org/0.31.2/ https://github.com/yuin/goldmark ``` These examples illustrate that the data remains unsorted and that duplicates are preserved. The client must handle this, e.g.: ```sh # curl 'http://127.0.0.1:23123/r/00001008010500' | sort -u https://commonmark.org/ https://github.com/yuin/goldmark https://github.github.com/gfm/ https://spec.commonmark.org/0.31.2/ |
| ︙ | ︙ |
Changes to docs/manual/00001012920525.zettel.
1 2 3 4 5 6 | id: 00001012920525 title: SHTML Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230316181044 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012920525 title: SHTML Encoding role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230316181044 modified: 20250806192348 A zettel representation that is a [[s-expression|00001012930000]], syntactically similar to the [[Sz encoding|00001012920516]], but denotes [[HTML|00001012920510]] semantics. It is derived from a XML encoding in s-expressions, called [[SXML|https://en.wikipedia.org/wiki/SXML]]. It is (relatively) easy to parse and contains everything to transform it into real HTML. In contrast to HTML, SHTML is easier to parse and to manipulate. For example, take a look at the SHTML encoding of this page, which is available via the ""Info"" sub-page of this zettel: |
| ︙ | ︙ | |||
28 29 30 31 32 33 34 |
A list may contain a possibly empty sequence of elements, i.e. lists and / or atoms.
Before the last element of a list of at least two elements, a full stop character (""''.''"", U+002E) signal a pair as the last two elements.
This allows a more space economic storage of data.
An HTML tag like ``< a href="link">Text</a>`` is encoded in SHTML with a list, where the first element is a symbol named a the tag.
The second element is an optional encoding of the tag's attributes.
Further elements are either other tag encodings or a string.
| | | | 28 29 30 31 32 33 34 35 36 |
A list may contain a possibly empty sequence of elements, i.e. lists and / or atoms.
Before the last element of a list of at least two elements, a full stop character (""''.''"", U+002E) signal a pair as the last two elements.
This allows a more space economic storage of data.
An HTML tag like ``< a href="link">Text</a>`` is encoded in SHTML with a list, where the first element is a symbol named a the tag.
The second element is an optional encoding of the tag's attributes.
Further elements are either other tag encodings or a string.
The above tag is encoded as ``(a ((href . "link")) "Text")``.
Also possible is to encode the attribute without pairs: ``(a ((href "link")) "Text")`` (note the missing full stop character).
|
Changes to docs/manual/00001012931000.zettel.
1 2 3 4 5 6 | id: 00001012931000 title: Encoding of Sz role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403153903 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012931000 title: Encoding of Sz role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403153903 modified: 20251021190931 Zettel in a [[Sz encoding|00001012920516]] are represented as a [[symbolic expression|00001012930000]]. To process these symbolic expressions, you need to know, how a specific part of a zettel is represented by a symbolic expression. Basically, each part of a zettel is represented as a list, often a nested list. The first element of that list is always a unique symbol, which denotes that part. The meaning / semantic of all other elements depend on that symbol. |
| ︙ | ︙ | |||
64 65 66 67 68 69 70 71 |
Either, there are no attributes.
These are specified by the empty list ''()''.
Or there are attributes.
In this case, the first element of the list must be the symbol ''quote'': ''(quote'' ''('' A,,1,, A,,2,, … A,,n,, '')'''')''.
=== Other
A list with ''UNKNOWN'' as its first element signals an internal error during transforming a zettel into the Sz encoding.
| > | | > | > > > > > > > > > | 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
Either, there are no attributes.
These are specified by the empty list ''()''.
Or there are attributes.
In this case, the first element of the list must be the symbol ''quote'': ''(quote'' ''('' A,,1,, A,,2,, … A,,n,, '')'''')''.
=== Other
==== ''UNKNOWN''
A list with ''UNKNOWN'' as its first element signals an internal error during transforming a zettel into the Sz encoding.
It may be ignored or may result in an error.
:::syntax
__Unknown__ **=** ''(UNKNOWN'' Object … '')''.
:::
The list may only contain the symbol ''UNKNOWN'', or, in addition, an unlimited amount of other objects.
==== ''**xyz:NOT-FOUND**''
Any symbol with the pattern ''**xyz:NOT-FOUND**'', where ""''xyz''"" is any string, signals an internal error.
==== ''*SPLICE-NODES*''
A list with ''*SPLICE-NODES*'' as its first element may occur during internal processing.
It signals that some processing routine intends to return more than one object instead of just one [[__BlockElement__|00001012931400]] or one [[__InlineElement__|00001012931600]].
The elements of such a list should be ""spliced"" into the parent list.
==== NIL (or ''()'')
Analogous to a splice node, which signal that more than one values is about to be returned, a ""NIL"" value (denoted as ""''()''"") signals that no object should be returned.
Therefore, a [[__BlockElement__|00001012931400]] or an [[__InlineElement__|00001012931600]] may be ""NIL"".
|
Changes to docs/manual/00001012931400.zettel.
1 2 3 4 5 6 | id: 00001012931400 title: Encoding of Sz Block Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161803 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012931400 title: Encoding of Sz Block Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161803 modified: 20251007144738 === ''PARA'' :::syntax __Paragraph__ **=** ''(PARA'' [[__InlineElement__|00001012931600]] … '')''. ::: A paragraph is just a list of inline elements. |
| ︙ | ︙ | |||
27 28 29 30 31 32 33 | ::: === ''ORDERED'', ''UNORDERED'', ''QUOTATION'' These three symbols are specifying different kinds of lists / enumerations: an ordered list, an unordered list, and a quotation list. Their structure is the same. :::syntax | | | | < < < < < | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | ::: === ''ORDERED'', ''UNORDERED'', ''QUOTATION'' These three symbols are specifying different kinds of lists / enumerations: an ordered list, an unordered list, and a quotation list. Their structure is the same. :::syntax __OrderedList__ **=** ''(ORDERED'' [[__Attributes__|00001012931000#attribute]] [[__Block__|00001012931000#block]] … '')''. __UnorderedList__ **=** ''(UNORDERED'' [[__Attributes__|00001012931000#attribute]] [[__Block__|00001012931000#block]] … '')''. __QuotationList__ **=** ''(QUOTATION'' [[__Attributes__|00001012931000#attribute]] [[__Block__|00001012931000#block]] … '')''. ::: === ''DESCRIPTION'' :::syntax __Description__ **=** ''(DESCRIPTION'' [[__Attributes__|00001012931000#attribute]] __DescriptionTerm__ __DescriptionValues__ __DescriptionTerm__ __DescriptionValues__ … '')''. ::: A description is a sequence of one or more terms and values. :::syntax |
| ︙ | ︙ | |||
147 148 149 150 151 152 153 | :::syntax __ZettelVerbatim__ **=** ''(VERBATIM-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as (nested) zettel content. === ''BLOB'' :::syntax | | | | 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | :::syntax __ZettelVerbatim__ **=** ''(VERBATIM-ZETTEL'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as (nested) zettel content. === ''BLOB'' :::syntax __BLOB__ **=** ''(BLOB'' [[__Attributes__|00001012931000#attribute]] String,,1,, String,,2,, [[__InlineElement__|00001012931600]] … '')''. ::: A BLOB contains an image in block mode. The inline elements states some description. The first string contains the syntax of the image. The second string contains the actual image. If the syntax is ""''svg''"", then the second string contains the SVG code. Otherwise the (binary) image data is encoded with base64. === ''TRANSCLUDE'' :::syntax __Transclude__ **=** ''(TRANSCLUDE'' [[__Attributes__|00001012931000#attribute]] [[__Reference__|00001012931900]] [[__InlineElement__|00001012931600]] … '')''. ::: A transclude list only occurs for a parsed zettel, but not for a evaluated zettel. Evaluating a zettel also means that all transclusions are resolved. __Reference__ denotes the zettel to be transcluded. |
Changes to docs/manual/00001012931600.zettel.
1 2 3 4 5 6 | id: 00001012931600 title: Encoding of Sz Inline Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161845 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001012931600 title: Encoding of Sz Inline Elements role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20230403161845 modified: 20251007144653 === ''TEXT'' :::syntax __Text__ **=** ''(TEXT'' String '')''. ::: Specifies the string as some text content, including white space characters. |
| ︙ | ︙ | |||
45 46 47 48 49 50 51 | If the string value is empty, it is an inline transclusion. Otherwise it contains the syntax of an image. The __InlineElement__ at the end of the list is interpreted as describing text. === ''EMBED-BLOB'' :::syntax | | | | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | If the string value is empty, it is an inline transclusion. Otherwise it contains the syntax of an image. The __InlineElement__ at the end of the list is interpreted as describing text. === ''EMBED-BLOB'' :::syntax __EmbedBLOB__ **=** ''(EMBED-BLOB'' [[__Attributes__|00001012931000#attribute]] String,,1,, String,,2,, [[__InlineElement__|00001012931600]] … '')''. ::: If used if some processed image has to be embedded inside some inline material. The first string specifies the syntax of the image content. The second string contains the image content. If the syntax is ""''svg''"", the image content is not encoded further. Otherwise a base64 encoding is used. === ''CITE'' :::syntax __CiteBLOB__ **=** ''(CITE'' [[__Attributes__|00001012931000#attribute]] String [[__InlineElement__|00001012931600]] … '')''. ::: The string contains the citation key. |
| ︙ | ︙ | |||
137 138 139 140 141 142 143 | The string contains text that should be treated as executable code. :::syntax __CommentLiteral__ **=** ''(LITERAL-COMMENT'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as an internal comment not to be interpreted further. | < < < < < < < < < < | 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | The string contains text that should be treated as executable code. :::syntax __CommentLiteral__ **=** ''(LITERAL-COMMENT'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as an internal comment not to be interpreted further. :::syntax __InputLiteral__ **=** ''(LITERAL-INPUT'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as input entered by a user. :::syntax __MathLiteral__ **=** ''(LITERAL-MATH'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as special code to be interpreted as mathematical formulas. :::syntax __OutputLiteral__ **=** ''(LITERAL-OUTPUT'' [[__Attributes__|00001012931000#attribute]] String '')''. ::: The string contains text that should be treated as computer output to be read by a user. |
Changes to docs/manual/00001018000000.zettel.
1 2 3 4 5 6 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk created: 20211027105921 | | > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk created: 20211027105921 modified: 20250828140447 This page lists some problems and their solutions that may occur when using your Zettelstore. === Installation * **Problem:** When you double-click on the Zettelstore executable icon, macOS complains that Zettelstore is an application from an unknown developer. Therefore, it will not start Zettelstore. ** **Solution:** Press the ''Ctrl'' key while opening the context menu of the Zettelstore executable with a right-click. A dialog is then opened where you can acknowledge that you understand the possible risks when you start Zettelstore. This dialog is only presented once for a given Zettelstore executable. * **Problem:** When you double-click on the Zettelstore executable icon, Windows complains that Zettelstore is an application from an unknown developer. ** **Solution:** Windows displays a dialog where you can acknowledge possible risks and allow to start Zettelstore. === Authentication * **Problem:** [[Authentication is enabled|00001010040100]] for a local running Zettelstore and there is a valid [[user zettel|00001010040200]] for the owner. But entering user name and password at the [[web user interface|00001014000000]] seems to be ignored, while entering a wrong password will result in an error message. ** **Explanation:** A local running Zettelstore typically means, that you are accessing the Zettelstore using a URL with schema ''http://'', and not ''https://'', for example ''http://localhost:23123/''. The difference between these two is the missing encryption of user name / password and for the answer of the Zettelstore if you use the ''http://'' schema. To be secure by default, the Zettelstore will not work in an insecure environment. ** **Solution 1:** If you are sure that your communication medium is safe, even if you use the ''http:/\/'' schema (for example, you are running the Zettelstore on the same computer you are working on, or if the Zettelstore is running on a computer in your protected local network), then you could add the entry ''insecure-cookie: true'' in your [[startup configuration|00001004010000#insecure-cookie]] file. ** **Solution 2:** If you are not sure about the security of your communication medium (for example, if unknown persons might use your local network), then you should run an [[external server|00001010090100]] in front of your Zettelstore to enable the use of the ''https://'' schema. * **Problem:** [[Authentication is enabled|00001010040100]] for a Zettelstore running behind an external server (e.g. for encryption) Entering user name and password produces an error message, stating that a ""cross-origin request"" was detected. ** **Explanation:** The external web server dos not transfer needed HTTP header data to your Zettelstore. ** **Solution:** Try to run Zettelstore in debug mode, as described in [[Cross-origin protection|00001010090100#cross-origin-protection]]. === Working with Zettel Files * **Problem:** When you delete a zettel file by removing it from the ""disk"", e.g. by dropping it into the trash folder, by dragging into another folder, or by removing it from the command line, Zettelstore sometimes does not detect the change. If you access the zettel via Zettelstore, an error is reported. ** **Explanation:** Sometimes, the operating system does not tell Zettelstore about the removed zettel. This occurs mostly under MacOS. ** **Solution 1:** If you are running Zettelstore in [[""simple-mode""|00001004051100]] or if you have enabled [[''expert-mode''|00001004020000#expert-mode]], you are allowed to refresh the internal data by selecting ""Refresh"" in the Web User Interface (you can find in the menu ""Lists""). |
| ︙ | ︙ |
Changes to go.mod.
1 2 | module zettelstore.de/z | | | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | module zettelstore.de/z go 1.25 require ( github.com/fsnotify/fsnotify v1.9.0 github.com/yuin/goldmark v1.7.13 golang.org/x/crypto v0.43.0 golang.org/x/term v0.36.0 t73f.de/r/sx v0.0.0-20251106134512-57bd2ba0e8e3 t73f.de/r/sxwebs v0.0.0-20251106134640-a792e6dfefd8 t73f.de/r/webs v0.0.0-20251106132628-d89a2b2c2373 t73f.de/r/zero v0.0.0-20251106132433-8bb93fc3269f t73f.de/r/zsc v0.0.0-20251106134919-31f6048e2c2e t73f.de/r/zsx v0.0.0-20251106134735-30b13dc3ce8a ) require ( golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect ) |
Changes to go.sum.
1 2 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= | | | | | | | | | | | | | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= t73f.de/r/sx v0.0.0-20251106134512-57bd2ba0e8e3 h1:8EQK+viyafJrYKprKFjxv8KO80XLjH2Av5gaI24298g= t73f.de/r/sx v0.0.0-20251106134512-57bd2ba0e8e3/go.mod h1:4hTEMWfmRB4ocR4ir0WuVoV+q0ed58pwGZwsznSXt1Q= t73f.de/r/sxwebs v0.0.0-20251106134640-a792e6dfefd8 h1:4I4hAwUuMb4bDNqttF1ZXGbZRnTTBvgHeWZOdELUZw8= t73f.de/r/sxwebs v0.0.0-20251106134640-a792e6dfefd8/go.mod h1:0rjepPI8mKx3vIe3+IVN3qJVr25uma2A6ozUDpnqbwY= t73f.de/r/webs v0.0.0-20251106132628-d89a2b2c2373 h1:puhtswOd5A9+RzeTBRfuSsvhs0Nsn9e3PHM7zM4Yges= t73f.de/r/webs v0.0.0-20251106132628-d89a2b2c2373/go.mod h1:n6yEgRO4XVZYi8F5ilHWgv7oFzVsRDZrXorCdXxdVqk= t73f.de/r/zero v0.0.0-20251106132433-8bb93fc3269f h1:v334GYGrruo8wr9Wh9R/KahJr8gMX/Nbysg/wpgtAC8= t73f.de/r/zero v0.0.0-20251106132433-8bb93fc3269f/go.mod h1:6TIoFD0Qn7oEE4GYUzA1cQzwrvhGAADYsm930FK6Yz0= t73f.de/r/zsc v0.0.0-20251106134919-31f6048e2c2e h1:JEDbKtuem7NdzueTqPiJ7zhJBT1sue9c7+9Rsf4Oy50= t73f.de/r/zsc v0.0.0-20251106134919-31f6048e2c2e/go.mod h1:WST5do8U/LEf6bprTRnvpLqNnW9qS7Am7qCKnFe4Br4= t73f.de/r/zsx v0.0.0-20251106134735-30b13dc3ce8a h1:hPo18TPk3Wlmh34Vuj0DVGJN13TiZ8Mule18EvwYs1Y= t73f.de/r/zsx v0.0.0-20251106134735-30b13dc3ce8a/go.mod h1:sQaYZqc37hSYmHENHrBqIiazhuoZc4Q4dpP9Wc6AM/I= |
Changes to internal/ast/ast.go.
| ︙ | ︙ | |||
11 12 13 14 15 16 17 | // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- // Package ast provides the abstract syntax tree for parsed zettel content. package ast import ( | | < > > > | < | | | | | | | > | | < < | < | < < < | < | < < < | < > | | < < < < < < < | | | > > | > > > > > > | | < | < | < | > | < < > | < < < < < < < < < < < | | < < < < > | | > | < < | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
// SPDX-FileCopyrightText: 2020-present Detlef Stern
//-----------------------------------------------------------------------------
// Package ast provides the abstract syntax tree for parsed zettel content.
package ast
import (
"fmt"
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/id"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"zettelstore.de/z/internal/zettel"
)
// Zettel is the root node of the abstract syntax tree.
type Zettel struct {
Meta *meta.Meta // Original metadata
Content zettel.Content // Original content
Zid id.Zid // Zettel identification.
InhMeta *meta.Meta // Metadata of the zettel, with inherited values.
Blocks *sx.Pair // Syntax tree, encodes as an sx.Object.
Syntax string // Syntax / parser that produced the Ast
}
var mapMetaTypeS = map[*meta.DescriptionType]*sx.Symbol{
meta.TypeCredential: sz.SymTypeCredential,
meta.TypeEmpty: sz.SymTypeEmpty,
meta.TypeID: sz.SymTypeID,
meta.TypeIDSet: sz.SymTypeIDSet,
meta.TypeNumber: sz.SymTypeNumber,
meta.TypeString: sz.SymTypeString,
meta.TypeTagSet: sz.SymTypeTagSet,
meta.TypeTimestamp: sz.SymTypeTimestamp,
meta.TypeURL: sz.SymTypeURL,
meta.TypeWord: sz.SymTypeWord,
}
// GetMetaSz transforms the given metadata into a sz list.
func GetMetaSz(m *meta.Meta) *sx.Pair {
var lb sx.ListBuilder
lb.Add(sz.SymMeta)
for key, val := range m.Computed() {
ty := m.Type(key)
symType := mapGetS(mapMetaTypeS, ty)
var obj sx.Object
if ty.IsSet {
var setObjs sx.ListBuilder
for _, val := range val.AsSlice() {
setObjs.Add(sx.MakeString(val))
}
obj = setObjs.List()
} else {
obj = sx.MakeString(string(val))
}
lb.Add(sx.Nil().Cons(obj).Cons(sx.MakeSymbol(key)).Cons(symType))
}
return lb.List()
}
func mapGetS[T comparable](m map[T]*sx.Symbol, k T) *sx.Symbol {
if result, found := m[k]; found {
return result
}
return sx.MakeSymbol(fmt.Sprintf("**%v:NOT-FOUND**", k))
}
|
Deleted internal/ast/block.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted internal/ast/inline.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted internal/ast/ref.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted internal/ast/ref_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted internal/ast/sztrans/sztrans.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted internal/ast/walk.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted internal/ast/walk_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to internal/box/constbox/base.sxn.
| ︙ | ︙ | |||
8 9 10 11 12 13 14 | ;;; obligations under this license. ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(@@@@ | | | | | | | | | | | | | | | | | | | | | | | | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
;;; obligations under this license.
;;;
;;; SPDX-License-Identifier: EUPL-1.2
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(@@@@
(html ,@(if lang `(((lang ,lang))))
(head
(meta ((charset "utf-8")))
(meta ((name "viewport") (content "width=device-width, initial-scale=1.0")))
(meta ((name "generator") (content "Zettelstore")))
(meta ((name "format-detection") (content "telephone=no")))
,@META-HEADER
(link ((rel "stylesheet") (href ,css-base-url)))
(link ((rel "stylesheet") (href ,css-user-url)))
,@(ROLE-DEFAULT-meta (current-frame))
,@(let* ((frame (current-frame))(rem (resolve-symbol 'ROLE-EXTRA-meta frame))) (if (defined? rem) (rem frame)))
(title ,title))
(body
(nav ((class "zs-menu"))
,(wui-href home-url "Home")
,@(if with-auth
`((div ((class "zs-dropdown"))
(button "User")
(nav ((class "zs-dropdown-content"))
,@(if user-is-valid
`(,(wui-href user-zettel-url user-ident)
,(wui-href logout-url "Logout"))
`(,(wui-href login-url "Login"))
)
)))
)
(div ((class "zs-dropdown"))
(button "Lists")
(nav ((class "zs-dropdown-content"))
,@list-urls
,(if (symbol-bound? 'refresh-url) (wui-href refresh-url "Refresh"))
))
,@(if new-zettel-links
`((div ((class "zs-dropdown"))
(button "New")
(nav ((class "zs-dropdown-content"))
,@(map wui-link new-zettel-links)
)))
)
(search (form ((action ,search-url))
(input ((type "search") (inputmode "search") (name ,query-key-query)
(title "General search field, with same behaviour as search field in search result list")
(placeholder "Search..") (dir "auto")))))
)
(main ((class "content")) ,DETAIL)
,@(if FOOTER `((footer (hr) ,@FOOTER)))
,@(if debug-mode '((div (b "WARNING: Debug mode is enabled. DO NOT USE IN PRODUCTION!"))))
)))
|
Changes to internal/box/constbox/constbox.go.
| ︙ | ︙ | |||
162 163 164 165 166 167 168 |
zettel.NewContent(contentDependencies)},
id.ZidBaseTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Base HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20230510155100",
| | | | | | | | | 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
zettel.NewContent(contentDependencies)},
id.ZidBaseTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Base HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20230510155100",
meta.KeyModified: "20250806182400",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentBaseSxn)},
id.ZidLoginTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Login Form HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20200804111624",
meta.KeyModified: "20250806182600",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentLoginSxn)},
id.ZidZettelTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Zettel HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20230510155300",
meta.KeyModified: "20250806182700",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentZettelSxn)},
id.ZidInfoTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Info HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20200804111624",
meta.KeyModified: "20250806182500",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentInfoSxn)},
id.ZidFormTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Form HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20200804111624",
meta.KeyModified: "20250806182500",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentFormSxn)},
id.ZidDeleteTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Delete HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20200804111624",
meta.KeyModified: "20250806182500",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentDeleteSxn)},
id.ZidListTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore List Zettel HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20230704122100",
meta.KeyModified: "20250806182600",
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentListZettelSxn)},
id.ZidErrorTemplate: {
constHeader{
meta.KeyTitle: "Zettelstore Error HTML Template",
meta.KeyRole: meta.ValueRoleConfiguration,
|
| ︙ | ︙ | |||
253 254 255 256 257 258 259 |
zettel.NewContent(contentStartCodeSxn)},
id.ZidSxnBase: {
constHeader{
meta.KeyTitle: "Zettelstore Sxn Base Code",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20230619132800",
| | | 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
zettel.NewContent(contentStartCodeSxn)},
id.ZidSxnBase: {
constHeader{
meta.KeyTitle: "Zettelstore Sxn Base Code",
meta.KeyRole: meta.ValueRoleConfiguration,
meta.KeySyntax: meta.ValueSyntaxSxn,
meta.KeyCreated: "20230619132800",
meta.KeyModified: "20250806182700",
meta.KeyReadOnly: meta.ValueTrue,
meta.KeyVisibility: meta.ValueVisibilityExpert,
},
zettel.NewContent(contentBaseCodeSxn)},
id.ZidBaseCSS: {
constHeader{
meta.KeyTitle: "Zettelstore Base CSS",
|
| ︙ | ︙ |
Changes to internal/box/constbox/delete.sxn.
| ︙ | ︙ | |||
11 12 13 14 15 16 17 | ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Delete Zettel " ,zid)) (p "Do you really want to delete this zettel?") ,@(if shadowed-box | | | | | | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(article
(header (h1 "Delete Zettel " ,zid))
(p "Do you really want to delete this zettel?")
,@(if shadowed-box
`((div ((class "zs-info"))
(h2 "Information")
(p "If you delete this zettel, the previously shadowed zettel from overlayed box " ,shadowed-box " becomes available.")
))
)
,@(if incoming
`((div ((class "zs-warning"))
(h2 "Warning!")
(p "If you delete this zettel, incoming references from the following zettel will become invalid.")
(ul ,@(map wui-item-link incoming))
))
)
,@(if (and (symbol-bound? 'useless) useless)
`((div ((class "zs-warning"))
(h2 "Warning!")
(p "Deleting this zettel will also delete the following files, so that they will not be interpreted as content for this zettel.")
(ul ,@(map wui-item useless))
))
)
,(wui-meta-desc metapairs)
(form ((method "POST")) (input ((class "zs-primary") (type "submit") (value "Delete"))))
)
|
Changes to internal/box/constbox/form.sxn.
| ︙ | ︙ | |||
9 10 11 12 13 14 15 | ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading)) | | | | | | | | | | | | | | | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
;;;
;;; SPDX-License-Identifier: EUPL-1.2
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(article
(header (h1 ,heading))
(form ((action ,form-action-url) (method "POST") (enctype "multipart/form-data"))
(div
(label ((for "zs-title")) "Title " (a ((title "Main heading of this zettel.")) (@H "ⓘ")))
(input ((class "zs-input") (type "text") (id "zs-title") (name "title")
(title "Title of this zettel")
(placeholder "Title..") (value ,meta-title) (dir "auto") (autofocus))))
(div
(label ((for "zs-role")) "Role " (a ((title "One word, without spaces, to set the main role of this zettel.")) (@H "ⓘ")))
(input ((class "zs-input") (type "text") (pattern "\\w*") (id "zs-role") (name "role")
(title "One word, letters and digits, but no spaces, to set the main role of the zettel.")
(placeholder "role..") (value ,meta-role) (dir "auto")
,@(if role-data '((list "zs-role-data")))
))
,@(wui-datalist "zs-role-data" role-data)
)
(div
(label ((for "zs-tags")) "Tags " (a ((title "Tags must begin with an '#' sign. They are separated by spaces.")) (@H "ⓘ")))
(input ((class "zs-input") (type "text") (id "zs-tags") (name "tags")
(title "Tags/keywords to categorize the zettel. Each tags is a word that begins with a '#' character; they are separated by spaces")
(placeholder "#tag") (value ,meta-tags) (dir "auto"))))
(div
(label ((for "zs-meta")) "Metadata " (a ((title "Other metadata for this zettel. Each line contains a key/value pair, separated by a colon ':'.")) (@H "ⓘ")))
(textarea ((class "zs-input") (id "zs-meta") (name "meta") (rows "4")
(title "Additional metadata about the zettel")
(placeholder "metakey: metavalue") (dir "auto")) ,meta))
(div
(label ((for "zs-syntax")) "Syntax " (a ((title "Syntax of zettel content below, one word. Typically 'zmk' (for zettelmarkup).")) (@H "ⓘ")))
(input ((class "zs-input") (type "text") (pattern "\\w*") (id "zs-syntax") (name "syntax")
(title "Syntax/format of zettel content below, one word, letters and digits, no spaces.")
(placeholder "syntax..") (value ,meta-syntax) (dir "auto")
,@(if syntax-data '((list "zs-syntax-data")))
))
,@(wui-datalist "zs-syntax-data" syntax-data)
)
,@(if (symbol-bound? 'content)
`((div
(label ((for "zs-content")) "Content " (a ((title "Content for this zettel, according to above syntax.")) (@H "ⓘ")))
(textarea ((class "zs-input zs-content") (id "zs-content") (name "content") (rows "20")
(title "Zettel content, according to the given syntax")
(placeholder "Zettel content..") (dir "auto")) ,content)
))
)
(div
(input ((class "zs-primary") (type "submit") (value "Submit")))
(input ((class "zs-secondary") (type "submit") (value "Save") (formaction "?save")))
(input ((class "zs-upload") (type "file") (id "zs-file") (name "file")))
))
)
|
Changes to internal/box/constbox/info.sxn.
| ︙ | ︙ | |||
10 11 12 13 14 15 16 |
;;; SPDX-License-Identifier: EUPL-1.2
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(article
(header (h1 "Information for Zettel " ,zid)
(p
| | | | | | | | | | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
;;; SPDX-License-Identifier: EUPL-1.2
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(article
(header (h1 "Information for Zettel " ,zid)
(p
,(wui-href web-url "Web")
,@(if (symbol-bound? 'edit-url) `((@H " · ") ,(wui-href edit-url "Edit")))
(@H " · ") ,(wui-href context-full-url "Full Context")
,@(if (symbol-bound? 'thread-query-url)
`((@H " · [") ,(wui-href thread-query-url "Thread")
,@(if (symbol-bound? 'folge-query-url) `((@H ", ") ,(wui-href folge-query-url "Folge")))
,@(if (symbol-bound? 'sequel-query-url) `((@H ", ") ,(wui-href sequel-query-url "Sequel")))
(@H "]")))
,@(ROLE-DEFAULT-actions (current-frame))
,@(let* ((frame (current-frame))(rea (resolve-symbol 'ROLE-EXTRA-actions frame))) (if (defined? rea) (rea frame)))
,@(if (symbol-bound? 'reindex-url) `((@H " · ") ,(wui-href reindex-url "Reindex")))
,@(if (symbol-bound? 'delete-url) `((@H " · ") ,(wui-href delete-url "Delete")))
)
)
(h2 "Interpreted Metadata")
(table ,@(map wui-info-meta-table-row metadata))
(h2 "References")
,@(if local-links `((h3 "Local") (ul ,@(map wui-local-link local-links))))
,@(if query-links `((h3 "Queries") (ul ,@(map wui-item-link query-links))))
,@(if ext-links `((h3 "External") (ul ,@(map wui-item-popup-link ext-links))))
(h3 "Unlinked")
,@unlinked-content
(form
(label ((for "phrase")) "Search Phrase")
(input ((class "zs-input") (type "text") (id "phrase") (name ,query-key-phrase) (placeholder "Phrase..") (value ,phrase)))
)
(h2 "Parts and encodings")
,(wui-enc-matrix enc-eval)
(h3 "Parsed (not evaluated)")
,(wui-enc-matrix enc-parsed)
,@(if shadow-links
`((h2 "Shadowed Boxes")
|
| ︙ | ︙ |
Changes to internal/box/constbox/listzettel.sxn.
| ︙ | ︙ | |||
9 10 11 12 13 14 15 | ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 ,heading)) | | | | | | | | | | | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
;;;
;;; SPDX-License-Identifier: EUPL-1.2
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(article
(header (h1 ,heading))
(search (form ((action ,search-url))
(input ((class "zs-input") (type "search") (inputmode "search") (name ,query-key-query)
(title "Contains the search that leads to the list below. You're allowed to modify it")
(placeholder "Search..") (value ,query-value) (dir "auto")))))
,@(if (symbol-bound? 'tag-zettel)
`((p ((class "zs-meta-zettel")) "Tag zettel: " ,@tag-zettel))
)
,@(if (symbol-bound? 'create-tag-zettel)
`((p ((class "zs-meta-zettel")) "Create tag zettel: " ,@create-tag-zettel))
)
,@(if (symbol-bound? 'role-zettel)
`((p ((class "zs-meta-zettel")) "Role zettel: " ,@role-zettel))
)
,@(if (symbol-bound? 'create-role-zettel)
`((p ((class "zs-meta-zettel")) "Create role zettel: " ,@create-role-zettel))
)
,@content
,@endnotes
(form ((action ,(if (symbol-bound? 'create-url) create-url)))
,(if (symbol-bound? 'data-url)
`(@L "Other encodings"
,(if (> num-entries 3) `(@L " of these " ,num-entries " entries: ") ": ")
,(wui-href data-url "data")
", "
,(wui-href plain-url "plain")
)
)
,@(if (symbol-bound? 'create-url)
`((input ((type "hidden") (name ,query-key-query) (value ,query-value)))
(input ((type "hidden") (name ,query-key-seed) (value ,seed)))
(input ((class "zs-primary") (type "submit") (value "Save As Zettel")))
)
)
)
)
|
Changes to internal/box/constbox/login.sxn.
| ︙ | ︙ | |||
9 10 11 12 13 14 15 | ;;; ;;; SPDX-License-Identifier: EUPL-1.2 ;;; SPDX-FileCopyrightText: 2023-present Detlef Stern ;;;---------------------------------------------------------------------------- `(article (header (h1 "Login")) | | | | | | | | | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
;;;
;;; SPDX-License-Identifier: EUPL-1.2
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(article
(header (h1 "Login"))
,@(if retry '((div ((class "zs-indication zs-error")) "Wrong user name / password. Try again.")))
(form ((method "POST") (action ""))
(div
(label ((for "username")) "User name:")
(input ((class "zs-input") (type "text") (id "username") (name "username") (placeholder "Your user name..") (autofocus))))
(div
(label ((for "password")) "Password:")
(input ((class "zs-input") (type "password") (id "password") (name "password") (placeholder "Your password.."))))
(div
(input ((class "zs-primary") (type "submit") (value "Login"))))
)
)
|
Changes to internal/box/constbox/wuicode.sxn.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 | ;; wui-list-item returns the argument as a HTML list item. (defun wui-item (s) `(li ,s)) ;; wui-info-meta-table-row takes a pair and translates it into a HTML table row ;; with two columns. (defun wui-info-meta-table-row (p) | | > > > | | | | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
;; wui-list-item returns the argument as a HTML list item.
(defun wui-item (s) `(li ,s))
;; wui-info-meta-table-row takes a pair and translates it into a HTML table row
;; with two columns.
(defun wui-info-meta-table-row (p)
`(tr (td ((class zs-info-meta-key)) ,(car p)) (td ((class zs-info-meta-value)) ,(cdr p))))
;; wui-href builds an HTML link
(defun wui-href (url text . attrs) `(a ((href ,url) ,@attrs) ,text))
;; wui-local-link translates a local link into HTML.
(defun wui-local-link (l) `(li ,(wui-href l l)))
;; wui-link takes a link (title . url) and returns a HTML reference.
(defun wui-link (q) (wui-href (cdr q) (car q)))
;; wui-item-link taks a pair (text . url) and returns a HTML link inside
;; a list item.
(defun wui-item-link (q) `(li ,(wui-link q)))
;; wui-tdata-link taks a pair (text . url) and returns a HTML link inside
;; a table data item.
(defun wui-tdata-link (q) `(td ,(wui-link q)))
;; wui-item-popup-link is like 'wui-item-link, but the HTML link will open
;; a new tab / window.
(defun wui-item-popup-link (e)
`(li ,(wui-href e e '(target "_blank") '(rel "external noreferrer"))))
;; wui-option-value returns a value for an HTML option element.
(defun wui-option-value (v) `(option ((value ,v))))
;; wui-datalist returns a HTML datalist with the given HTML identifier and a
;; list of values.
(defun wui-datalist (id lst)
(if lst
`((datalist ((id ,id)) ,@(map wui-option-value lst)))))
;; wui-pair-desc-item takes a pair '(term . text) and returns a list with
;; a HTML description term and a HTML description data.
(defun wui-pair-desc-item (p) `((dt ,(car p)) (dd ,(cdr p))))
;; wui-meta-desc returns a HTML description list made from the list of pairs
;; given.
|
| ︙ | ︙ | |||
65 66 67 68 69 70 71 |
(lambda (row) `(tr (th ,(car row)) ,@(map wui-tdata-link (cdr row))))
matrix)))
;; wui-optional-link puts the text into a link, if symbol is defined. Otherwise just return the text
(defun wui-optional-link (text url-sym)
(let ((url (resolve-symbol url-sym)))
(if (defined? url)
| | | | | | | | | 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
(lambda (row) `(tr (th ,(car row)) ,@(map wui-tdata-link (cdr row))))
matrix)))
;; wui-optional-link puts the text into a link, if symbol is defined. Otherwise just return the text
(defun wui-optional-link (text url-sym)
(let ((url (resolve-symbol url-sym)))
(if (defined? url)
(wui-href url text)
text)))
;; CSS-ROLE-map is a mapping (pair list, assoc list) of role names to zettel
;; identifier. It is used in the base template to update the metadata of the
;; HTML page to include some role specific CSS code.
;; Referenced in function "ROLE-DEFAULT-meta".
(defvar CSS-ROLE-map '())
;; ROLE-DEFAULT-meta returns some metadata for the base template. Any role
;; specific code should include the returned list of this function.
(defun ROLE-DEFAULT-meta (frame)
`(,@(let* ((meta-role (resolve-symbol 'meta-role frame))
(entry (assoc CSS-ROLE-map meta-role)))
(if (pair? entry)
`((link ((rel "stylesheet") (href ,(zid-content-path (cdr entry))))))
)
)
)
)
;; ACTION-SEPARATOR defines a HTML value that separates actions links.
(defvar ACTION-SEPARATOR '(@H " · "))
;; ROLE-DEFAULT-actions returns the default text for actions.
(defun ROLE-DEFAULT-actions (frame)
`(,@(let ((copy-url (resolve-symbol 'copy-url frame)))
(if (defined? copy-url) `((@H " · ") ,(wui-href copy-url "Copy"))))
,@(let ((sequel-url (resolve-symbol 'sequel-url frame)))
(if (defined? sequel-url) `((@H " · ") ,(wui-href sequel-url "Sequel"))))
,@(let ((folge-url (resolve-symbol 'folge-url frame)))
(if (defined? folge-url) `((@H " · ") ,(wui-href folge-url "Folge"))))
)
)
;; ROLE-tag-actions returns an additional action "Zettel" for zettel with role "tag".
(defun ROLE-tag-actions (frame)
`(,@(let ((title (resolve-symbol 'title frame)))
(if (and (defined? title) title)
`(,ACTION-SEPARATOR ,(wui-href (query->url (concat "tags:" title)) "Zettel"))
)
)
)
)
;; ROLE-role-actions returns an additional action "Zettel" for zettel with role "role".
(defun ROLE-role-actions (frame)
`(,@(let ((title (resolve-symbol 'title frame)))
(if (and (defined? title) title)
`(,ACTION-SEPARATOR ,(wui-href (query->url (concat "role:" title)) "Zettel"))
)
)
)
)
;; ROLE-DEFAULT-heading returns the default text for headings, below the
;; references of a zettel. In most cases it should be called from an
|
| ︙ | ︙ |
Changes to internal/box/constbox/zettel.sxn.
| ︙ | ︙ | |||
10 11 12 13 14 15 16 |
;;; SPDX-License-Identifier: EUPL-1.2
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(article
(header
(h1 ,heading)
| | | | | | | | | | | | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
;;; SPDX-License-Identifier: EUPL-1.2
;;; SPDX-FileCopyrightText: 2023-present Detlef Stern
;;;----------------------------------------------------------------------------
`(article
(header
(h1 ,heading)
(div ((class "zs-meta"))
,@(if (symbol-bound? 'edit-url) `(,(wui-href edit-url "Edit") (@H " · ")))
,zid (@H " · ")
,(wui-href info-url "Info") (@H " · ")
"(" ,@(if (symbol-bound? 'role-url) `(,(wui-href role-url meta-role)))
,@(if (and (symbol-bound? 'folge-role-url) (symbol-bound? 'meta-folge-role))
`((@H " → ") ,(wui-href folge-role-url meta-folge-role)))
")"
,@(if tag-refs `((@H " · ") ,@tag-refs))
(@H " · ") ,(wui-href context-url "Context")
,@(if (symbol-bound? 'thread-query-url) `((@H " · ") ,(wui-href thread-query-url "Thread")))
,@(ROLE-DEFAULT-actions (current-frame))
,@(let* ((frame (current-frame))(rea (resolve-symbol 'ROLE-EXTRA-actions frame))) (if (defined? rea) (rea frame)))
,@(if superior-refs `((br) "Superior: " ,superior-refs))
,@(if prequel-refs `((br) ,(wui-optional-link "Prequel" 'sequel-query-url) ": " ,prequel-refs))
,@(if precursor-refs `((br) ,(wui-optional-link "Precursor" 'folge-query-url) ": " ,precursor-refs))
,@(ROLE-DEFAULT-heading (current-frame))
,@(let* ((frame (current-frame))(reh (resolve-symbol 'ROLE-EXTRA-heading frame))) (if (defined? reh) (reh frame)))
)
)
,@content
,endnotes
,@(if (or folge-links sequel-links back-links subordinate-links)
`((nav
,@(if folge-links
`((details ((,folge-open))
(summary ,(wui-optional-link "Folgezettel" 'folge-query-url))
(ul ,@(map wui-item-link folge-links)))))
,@(if sequel-links
`((details ((,sequel-open))
(summary ,(wui-optional-link "Sequel" 'sequel-query-url))
(ul ,@(map wui-item-link sequel-links)))))
,@(if subordinate-links `((details ((,subordinate-open)) (summary "Subordinates") (ul ,@(map wui-item-link subordinate-links)))))
,@(if back-links `((details ((,back-open)) (summary "Incoming") (ul ,@(map wui-item-link back-links)))))
))
)
)
|
Changes to internal/box/manager/collect.go.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 22 | //----------------------------------------------------------------------------- package manager import ( "strings" zerostrings "t73f.de/r/zero/strings" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/id/idset" | > > > < | | | > | | | | > > | | > > | | > | | > | | > | | > | | > | > > > > > > > > > > > > < < < < < < < < < < < < < < < | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
//-----------------------------------------------------------------------------
package manager
import (
"strings"
"t73f.de/r/sx"
zerostrings "t73f.de/r/zero/strings"
"t73f.de/r/zsc/domain/id"
"t73f.de/r/zsc/domain/id/idset"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/box/manager/store"
)
type collectData struct {
refs *idset.Set
words store.WordSet
urls store.WordSet
}
func (data *collectData) initialize() {
data.refs = idset.New()
data.words = store.NewWordSet()
data.urls = store.NewWordSet()
}
func collectZettelIndexData(blocks *sx.Pair, data *collectData) {
zsx.WalkIt(data, blocks, nil)
}
func (data *collectData) VisitItBefore(node *sx.Pair, _ *sx.Pair) bool {
if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
switch sym {
case zsx.SymText:
data.addText(zsx.GetText(node))
case zsx.SymVerbatimCode, zsx.SymVerbatimComment, zsx.SymVerbatimEval,
zsx.SymVerbatimHTML, zsx.SymVerbatimMath, zsx.SymVerbatimZettel:
_, _, s := zsx.GetVerbatim(node)
data.addText(s)
case zsx.SymLiteralCode, zsx.SymLiteralComment, zsx.SymLiteralInput,
zsx.SymLiteralMath, zsx.SymLiteralOutput:
_, _, s := zsx.GetLiteral(node)
data.addText(s)
case zsx.SymLink:
_, ref, _ := zsx.GetLink(node)
data.addRef(ref)
case zsx.SymEmbed:
_, ref, _, _ := zsx.GetEmbed(node)
data.addRef(ref)
case zsx.SymTransclude:
_, ref, _ := zsx.GetTransclusion(node)
data.addRef(ref)
case zsx.SymCite:
_, key, _ := zsx.GetCite(node)
data.addText(key)
}
}
return false
}
func (data *collectData) VisitItAfter(*sx.Pair, *sx.Pair) {}
func (data *collectData) addRef(ref *sx.Pair) {
sym, refValue := zsx.GetReference(ref)
if zsx.SymRefStateExternal.IsEqual(sym) {
data.urls.Add(strings.ToLower(refValue))
} else if sz.SymRefStateZettel.IsEqual(sym) {
if zid, err := id.Parse(refValue); err == nil {
data.refs.Add(zid)
}
}
}
func (data *collectData) addText(s string) {
for _, word := range zerostrings.NormalizeWords(s) {
data.words.Add(word)
}
}
|
Changes to internal/box/manager/indexer.go.
| ︙ | ︙ | |||
148 149 150 151 152 153 154 |
return true
}
func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel zettel.Zettel) {
var cData collectData
cData.initialize()
if mustIndexZettel(zettel.Meta) {
| | > | 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
return true
}
func (mgr *Manager) idxUpdateZettel(ctx context.Context, zettel zettel.Zettel) {
var cData collectData
cData.initialize()
if mustIndexZettel(zettel.Meta) {
zn := parser.ParseZettel(ctx, zettel, "", mgr.rtConfig)
collectZettelIndexData(zn.Blocks, &cData)
}
m := zettel.Meta
zi := store.NewZettelIndex(m)
mgr.idxCollectFromMeta(ctx, m, zi, &cData)
mgr.idxProcessData(ctx, zi, &cData)
toCheck := mgr.idxStore.UpdateReferences(ctx, zi)
|
| ︙ | ︙ |
Changes to internal/collect/collect.go.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | // Package collect provides functions to collect items from a syntax tree. package collect import ( "iter" | | > | | | | | | | | | | > | | > > | | > > | | | < | | > | > | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
// Package collect provides functions to collect items from a syntax tree.
package collect
import (
"iter"
"t73f.de/r/sx"
"t73f.de/r/zsx"
)
type refYielder struct {
yield func(*sx.Pair) bool
stop bool
}
// ReferenceSeq returns an iterator of all references mentioned in the given
// block slice. This also includes references to images.
func ReferenceSeq(block *sx.Pair) iter.Seq[*sx.Pair] {
return func(yield func(*sx.Pair) bool) {
yielder := refYielder{yield, false}
zsx.WalkIt(&yielder, block, nil)
}
}
// Visit all node to collect data for the summary.
func (y *refYielder) VisitItBefore(node *sx.Pair, _ *sx.Pair) bool {
if y.stop {
return true
}
if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
switch sym {
case zsx.SymLink:
_, ref, _ := zsx.GetLink(node)
y.stop = !y.yield(ref)
case zsx.SymEmbed:
_, ref, _, _ := zsx.GetEmbed(node)
y.stop = !y.yield(ref)
case zsx.SymTransclude:
_, ref, _ := zsx.GetTransclusion(node)
y.stop = !y.yield(ref)
}
if y.stop {
return true
}
}
return false
}
func (*refYielder) VisitItAfter(*sx.Pair, *sx.Pair) {}
|
Changes to internal/collect/collect_test.go.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | // Package collect_test provides some unit test for collectors. package collect_test import ( "slices" "testing" | > > > | | | > | < | | | | | | | < | < | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
// Package collect_test provides some unit test for collectors.
package collect_test
import (
"slices"
"testing"
"t73f.de/r/sx"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/collect"
)
func parseRef(s string) *sx.Pair {
r := sz.ScanReference(s)
sym, _ := zsx.GetReference(r)
if zsx.SymRefStateInvalid.IsEqualSymbol(sym) {
panic(s)
}
return r
}
func TestReferenceSeq(t *testing.T) {
t.Parallel()
summary := slices.Collect(collect.ReferenceSeq(nil))
if len(summary) != 0 {
t.Error("No references expected, but got:", summary)
}
intNode := zsx.MakeLink(nil, parseRef("01234567890123"), nil)
para := zsx.MakePara(intNode, zsx.MakeLink(nil, parseRef("https://zettelstore.de/z"), nil))
blocks := zsx.MakeBlock(para)
summary = slices.Collect(collect.ReferenceSeq(blocks))
if len(summary) != 2 {
t.Error("2 refs expected, but got:", summary)
}
para.LastPair().AppendBang(intNode)
summary = slices.Collect(collect.ReferenceSeq(blocks))
if cnt := len(summary); cnt != 3 {
t.Error("Ref count does not work. Expected: 3, got", summary)
}
blocks = zsx.MakeBlock(zsx.MakePara(zsx.MakeEmbed(nil, parseRef("12345678901234"), "", nil)))
summary = slices.Collect(collect.ReferenceSeq(blocks))
if len(summary) != 1 {
t.Error("Only one image ref expected, but got: ", summary)
}
}
|
Changes to internal/collect/order.go.
| ︙ | ︙ | |||
10 11 12 13 14 15 16 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package collect provides functions to collect items from a syntax tree. package collect | | > > | | > | > > | | | < < < | > | | | | | | | > | | > | | > > | | | > > | | | < > > > > | | > | | > | < < < | > | < < | | | > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2021-present Detlef Stern
//-----------------------------------------------------------------------------
// Package collect provides functions to collect items from a syntax tree.
package collect
import (
"t73f.de/r/sx"
"t73f.de/r/zsx"
)
// Order returns links in the items of the first list found in the given block node.
func Order(block *sx.Pair) *sx.Pair {
var lb sx.ListBuilder
blocks := zsx.GetBlock(block)
for bn := range blocks.Pairs() {
blk := bn.Head()
if sym, isSymbol := sx.GetSymbol(blk.Car()); isSymbol {
if zsx.SymListUnordered.IsEqualSymbol(sym) || zsx.SymListOrdered.IsEqualSymbol(sym) {
_, _, items := zsx.GetList(blk)
for item := range items.Pairs() {
if ln := firstItemZettelLink(item.Head()); ln != nil {
lb.Add(ln)
}
}
}
}
}
return lb.List()
}
func firstItemZettelLink(item *sx.Pair) *sx.Pair {
blocks := zsx.GetBlock(item)
for bn := range blocks.Pairs() {
blk := bn.Head()
if sym, isSymbol := sx.GetSymbol(blk.Car()); isSymbol && zsx.SymPara.IsEqualSymbol(sym) {
inlines := zsx.GetPara(blk)
if ln := firstInlineZettelLink(inlines); ln != nil {
return ln
}
}
}
return nil
}
func firstInlineZettelLink(inlines *sx.Pair) (result *sx.Pair) {
for inode := range inlines.Pairs() {
inl := inode.Head()
if sym, isSymbol := sx.GetSymbol(inl.Car()); isSymbol {
switch sym {
case zsx.SymLink:
return inl
case zsx.SymFormatDelete, zsx.SymFormatEmph, zsx.SymFormatInsert, zsx.SymFormatMark,
zsx.SymFormatQuote, zsx.SymFormatSpan, zsx.SymFormatStrong, zsx.SymFormatSub,
zsx.SymFormatSuper:
_, _, finlines := zsx.GetFormat(inl)
result = firstInlineZettelLink(finlines)
case zsx.SymCite:
_, _, cinlines := zsx.GetCite(inl)
result = firstInlineZettelLink(cinlines)
case zsx.SymEmbed:
_, _, _, einlines := zsx.GetEmbed(inl)
result = firstInlineZettelLink(einlines)
case zsx.SymEmbedBLOB:
_, _, _, binlines := zsx.GetEmbedBLOBuncode(inl)
result = firstInlineZettelLink(binlines)
}
if result != nil {
return result
}
}
}
return nil
}
|
Changes to internal/encoder/encoder.go.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// Package encoder provides a generic interface to encode the abstract syntax
// tree into some text form.
package encoder
import (
"io"
"t73f.de/r/zsc/api"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/shtml"
"zettelstore.de/z/internal/ast"
)
// Encoder is an interface that allows to encode different parts of a zettel.
type Encoder interface {
// WriteZettel encodes a whole zettel and writes it to the Writer.
| > | | | | < < | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
// Package encoder provides a generic interface to encode the abstract syntax
// tree into some text form.
package encoder
import (
"io"
"t73f.de/r/sx"
"t73f.de/r/zsc/api"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/shtml"
"zettelstore.de/z/internal/ast"
)
// Encoder is an interface that allows to encode different parts of a zettel.
type Encoder interface {
// WriteZettel encodes a whole zettel and writes it to the Writer.
WriteZettel(io.Writer, *ast.Zettel) error
// WriteMeta encodes just the metadata.
WriteMeta(io.Writer, *meta.Meta) error
// WriteSz encodes SZ represented zettel content.
WriteSz(io.Writer, *sx.Pair) error
}
// Create builds a new encoder with the given options.
func Create(enc api.EncodingEnum, params *CreateParameter) Encoder {
switch enc {
case api.EncoderHTML:
// We need a new transformer every time, because tx.inVerse must be unique.
// If we can refactor it out, the transformer can be created only once.
return &htmlEncoder{
th: shtml.NewEvaluator(1),
lang: params.Lang,
}
case api.EncoderMD:
return &mdEncoder{lang: params.Lang}
case api.EncoderSHTML:
// We need a new transformer every time, because tx.inVerse must be unique.
// If we can refactor it out, the transformer can be created only once.
return &shtmlEncoder{
th: shtml.NewEvaluator(1),
lang: params.Lang,
}
case api.EncoderSz:
// We need a new transformer every time, because trans.inVerse must be unique.
// If we can refactor it out, the transformer can be created only once.
return &szEncoder{}
case api.EncoderText:
return (*TextEncoder)(nil)
case api.EncoderZmk:
return (*zmkEncoder)(nil)
}
return nil
}
|
| ︙ | ︙ |
Changes to internal/encoder/encoder_blob_test.go.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | import ( "testing" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" | < | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import (
"testing"
"t73f.de/r/zsc/domain/id"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx/input"
"zettelstore.de/z/internal/parser"
)
type blobTestCase struct {
descr string
syntax string
blob []byte
|
| ︙ | ︙ | |||
40 41 42 43 44 45 46 |
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x7e, 0x9b,
0x55, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x62, 0x00, 0x00, 0x00,
0x06, 0x00, 0x03, 0x36, 0x37, 0x7c, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
0x42, 0x60, 0x82,
},
expect: expectMap{
encoderHTML: `<p><img alt="Minimal PNG" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="></p>`,
| | | | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x7e, 0x9b,
0x55, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x62, 0x00, 0x00, 0x00,
0x06, 0x00, 0x03, 0x36, 0x37, 0x7c, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
0x42, 0x60, 0x82,
},
expect: expectMap{
encoderHTML: `<p><img alt="Minimal PNG" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg=="></p>`,
encoderSz: `(BLOCK (BLOB () "png" "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==" (TEXT "Minimal PNG")))`,
encoderSHTML: `((p (img ((alt . "Minimal PNG") (src . "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==")))))`,
encoderText: "",
encoderZmk: `%% Unable to display BLOB with description 'Minimal PNG' and syntax 'png'.`,
},
},
}
func TestBlob(t *testing.T) {
m := meta.New(id.Invalid)
for testNum, tc := range pngTestCases {
m.Set(meta.KeyTitle, meta.Value(tc.descr))
inp := input.NewInput(tc.blob)
node := parser.Parse(inp, m, tc.syntax, nil)
checkEncodings(t, testNum, node, false, tc.descr, tc.expect, "???")
}
}
|
Changes to internal/encoder/encoder_block_test.go.
| ︙ | ︙ | |||
8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// under this license.
//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2021-present Detlef Stern
//-----------------------------------------------------------------------------
package encoder_test
var tcsBlock = []zmkTestCase{
{
descr: "Empty Zettelmarkup should produce near nothing",
zmk: "",
expect: expectMap{
encoderHTML: "",
| > > > > > > | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// under this license.
//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2021-present Detlef Stern
//-----------------------------------------------------------------------------
package encoder_test
import "t73f.de/r/zsc/domain/meta"
// func TestEncoderBlock(t *testing.T) {
// executeTestCases(t, tcsBlock)
// }
var tcsBlock = []zmkTestCase{
{
descr: "Empty Zettelmarkup should produce near nothing",
zmk: "",
expect: expectMap{
encoderHTML: "",
|
| ︙ | ︙ | |||
65 66 67 68 69 70 71 |
{
descr: "Simple Heading",
zmk: `=== Top Job`,
expect: expectMap{
encoderHTML: "<h2 id=\"top-job\">Top Job</h2>",
encoderMD: "# Top Job",
encoderSz: `(BLOCK (HEADING 1 () "top-job" "top-job" (TEXT "Top Job")))`,
| | | | | | 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
{
descr: "Simple Heading",
zmk: `=== Top Job`,
expect: expectMap{
encoderHTML: "<h2 id=\"top-job\">Top Job</h2>",
encoderMD: "# Top Job",
encoderSz: `(BLOCK (HEADING 1 () "top-job" "top-job" (TEXT "Top Job")))`,
encoderSHTML: `((h2 ((id . "top-job")) "Top Job"))`,
encoderText: `Top Job`,
encoderZmk: useZmk,
},
},
{
descr: "Simple List",
zmk: "* A\n* B\n* C",
expect: expectMap{
encoderHTML: "<ul><li>A</li><li>B</li><li>C</li></ul>",
encoderMD: "* A\n* B\n* C",
encoderSz: `(BLOCK (UNORDERED () (BLOCK (PARA (TEXT "A"))) (BLOCK (PARA (TEXT "B"))) (BLOCK (PARA (TEXT "C")))))`,
encoderSHTML: `((ul (li "A") (li "B") (li "C")))`,
encoderText: "A\nB\nC",
encoderZmk: useZmk,
},
},
{
descr: "Nested List",
zmk: "* T1\n*# T2\n* T3\n** T4\n** T5\n* T6",
expect: expectMap{
encoderHTML: `<ul><li><p>T1</p><ol><li>T2</li></ol></li><li><p>T3</p><ul><li>T4</li><li>T5</li></ul></li><li><p>T6</p></li></ul>`,
encoderMD: "* T1\n 1. T2\n* T3\n * T4\n * T5\n* T6",
encoderSz: `(BLOCK (UNORDERED () (BLOCK (PARA (TEXT "T1")) (ORDERED () (BLOCK (PARA (TEXT "T2"))))) (BLOCK (PARA (TEXT "T3")) (UNORDERED () (BLOCK (PARA (TEXT "T4"))) (BLOCK (PARA (TEXT "T5"))))) (BLOCK (PARA (TEXT "T6")))))`,
encoderSHTML: `((ul (li (p "T1") (ol (li "T2"))) (li (p "T3") (ul (li "T4") (li "T5"))) (li (p "T6"))))`,
encoderText: "T1\nT2\nT3\nT4\nT5\nT6",
encoderZmk: useZmk,
},
},
{
descr: "Sequence of two lists",
zmk: "* Item1.1\n* Item1.2\n* Item1.3\n\n* Item2.1\n* Item2.2",
expect: expectMap{
encoderHTML: "<ul><li>Item1.1</li><li>Item1.2</li><li>Item1.3</li><li>Item2.1</li><li>Item2.2</li></ul>",
encoderMD: "* Item1.1\n* Item1.2\n* Item1.3\n* Item2.1\n* Item2.2",
encoderSz: `(BLOCK (UNORDERED () (BLOCK (PARA (TEXT "Item1.1"))) (BLOCK (PARA (TEXT "Item1.2"))) (BLOCK (PARA (TEXT "Item1.3"))) (BLOCK (PARA (TEXT "Item2.1"))) (BLOCK (PARA (TEXT "Item2.2")))))`,
encoderSHTML: `((ul (li "Item1.1") (li "Item1.2") (li "Item1.3") (li "Item2.1") (li "Item2.2")))`,
encoderText: "Item1.1\nItem1.2\nItem1.3\nItem2.1\nItem2.2",
encoderZmk: "* Item1.1\n* Item1.2\n* Item1.3\n* Item2.1\n* Item2.2",
},
},
{
descr: "Simple horizontal rule",
|
| ︙ | ︙ | |||
125 126 127 128 129 130 131 |
{
descr: "Thematic break with attribute",
zmk: `---{lang="zmk"}`,
expect: expectMap{
encoderHTML: `<hr lang="zmk">`,
encoderMD: "---",
encoderSz: `(BLOCK (THEMATIC (("lang" . "zmk"))))`,
| | | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
{
descr: "Thematic break with attribute",
zmk: `---{lang="zmk"}`,
expect: expectMap{
encoderHTML: `<hr lang="zmk">`,
encoderMD: "---",
encoderSz: `(BLOCK (THEMATIC (("lang" . "zmk"))))`,
encoderSHTML: `((hr ((lang . "zmk"))))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "No list after paragraph",
zmk: "Text\n*abc",
|
| ︙ | ︙ | |||
148 149 150 151 152 153 154 |
},
{
descr: "A list after paragraph",
zmk: "Text\n# abc",
expect: expectMap{
encoderHTML: "<p>Text</p><ol><li>abc</li></ol>",
encoderMD: "Text\n\n1. abc",
| | | | 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
},
{
descr: "A list after paragraph",
zmk: "Text\n# abc",
expect: expectMap{
encoderHTML: "<p>Text</p><ol><li>abc</li></ol>",
encoderMD: "Text\n\n1. abc",
encoderSz: `(BLOCK (PARA (TEXT "Text")) (ORDERED () (BLOCK (PARA (TEXT "abc")))))`,
encoderSHTML: `((p "Text") (ol (li "abc")))`,
encoderText: "Text\nabc",
encoderZmk: useZmk,
},
},
{
descr: "Simple List Quote",
zmk: "> ToBeOrNotToBe",
expect: expectMap{
encoderHTML: "<blockquote>ToBeOrNotToBe</blockquote>",
encoderMD: "> ToBeOrNotToBe",
encoderSz: `(BLOCK (QUOTATION () (BLOCK (PARA (TEXT "ToBeOrNotToBe")))))`,
encoderSHTML: `((blockquote (@L "ToBeOrNotToBe")))`,
encoderText: "ToBeOrNotToBe",
encoderZmk: useZmk,
},
},
{
descr: "Simple Quote Block",
|
| ︙ | ︙ | |||
257 258 259 260 261 262 263 |
{
descr: "Simple Verbatim Eval",
zmk: "~~~\nHello\nWorld\n~~~",
expect: expectMap{
encoderHTML: "<pre><code class=\"zs-eval\">Hello\nWorld</code></pre>",
encoderMD: "",
encoderSz: `(BLOCK (VERBATIM-EVAL () "Hello\nWorld"))`,
| | | | 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
{
descr: "Simple Verbatim Eval",
zmk: "~~~\nHello\nWorld\n~~~",
expect: expectMap{
encoderHTML: "<pre><code class=\"zs-eval\">Hello\nWorld</code></pre>",
encoderMD: "",
encoderSz: `(BLOCK (VERBATIM-EVAL () "Hello\nWorld"))`,
encoderSHTML: "((pre (code ((class . \"zs-eval\")) \"Hello\\nWorld\")))",
encoderText: "Hello\nWorld",
encoderZmk: useZmk,
},
},
{
descr: "Simple Verbatim Math",
zmk: "$$$\nHello\n\\LaTeX\n$$$",
expect: expectMap{
encoderHTML: "<pre><code class=\"zs-math\">Hello\n\\LaTeX</code></pre>",
encoderMD: "",
encoderSz: `(BLOCK (VERBATIM-MATH () "Hello\n\\LaTeX"))`,
encoderSHTML: "((pre (code ((class . \"zs-math\")) \"Hello\\n\\\\LaTeX\")))",
encoderText: "Hello\n\\LaTeX",
encoderZmk: useZmk,
},
},
{
descr: "Simple Description List",
zmk: "; Zettel\n: Paper\n: Note\n; Zettelkasten\n: Slip box",
|
| ︙ | ︙ | |||
316 317 318 319 320 321 322 |
},
{
descr: "Simple Table",
zmk: "|c1|c2|c3\n|d1||d3",
expect: expectMap{
encoderHTML: `<table><tbody><tr><td>c1</td><td>c2</td><td>c3</td></tr><tr><td>d1</td><td></td><td>d3</td></tr></tbody></table>`,
encoderMD: "",
| | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > < < < < | 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
},
{
descr: "Simple Table",
zmk: "|c1|c2|c3\n|d1||d3",
expect: expectMap{
encoderHTML: `<table><tbody><tr><td>c1</td><td>c2</td><td>c3</td></tr><tr><td>d1</td><td></td><td>d3</td></tr></tbody></table>`,
encoderMD: "",
encoderSz: `(BLOCK (TABLE () () ((CELL () (TEXT "c1")) (CELL () (TEXT "c2")) (CELL () (TEXT "c3"))) ((CELL () (TEXT "d1")) (CELL ()) (CELL () (TEXT "d3")))))`,
encoderSHTML: `((table (tbody (tr (td "c1") (td "c2") (td "c3")) (tr (td "d1") (td) (td "d3")))))`,
encoderText: "c1 c2 c3\nd1 d3",
encoderZmk: useZmk,
},
},
{
descr: "Table with alignment and comment",
zmk: `|h1>|=h2|h3:|
|%--+---+---+
|<c1|c2|:c3|
|f1|f2|=f3`,
expect: expectMap{
encoderHTML: `<table><thead><tr><th class="right">h1</th><th>h2</th><th class="center">h3</th></tr></thead><tbody><tr><td class="left">c1</td><td>c2</td><td class="center">c3</td></tr><tr><td class="right">f1</td><td>f2</td><td class="center">=f3</td></tr></tbody></table>`,
encoderMD: "",
encoderSz: `(BLOCK (TABLE () ((CELL ((align . "right")) (TEXT "h1")) (CELL () (TEXT "h2")) (CELL ((align . "center")) (TEXT "h3"))) ((CELL ((align . "left")) (TEXT "c1")) (CELL () (TEXT "c2")) (CELL ((align . "center")) (TEXT "c3"))) ((CELL ((align . "right")) (TEXT "f1")) (CELL () (TEXT "f2")) (CELL ((align . "center")) (TEXT "=f3")))))`,
encoderSHTML: `((table (thead (tr (th ((class . "right")) "h1") (th "h2") (th ((class . "center")) "h3"))) (tbody (tr (td ((class . "left")) "c1") (td "c2") (td ((class . "center")) "c3")) (tr (td ((class . "right")) "f1") (td "f2") (td ((class . "center")) "=f3")))))`,
encoderText: "h1 h2 h3\nc1 c2 c3\nf1 f2 =f3",
encoderZmk: /*`|=h1>|=h2|=h3:
|<c1|c2|c3
|f1|f2|=f3`,*/
`|=>h1|=h2|=:h3
|<c1|c2|:c3
|>f1|f2|:=f3`,
},
},
{
descr: "Simple Endnote",
zmk: `Text[^Endnote]`,
expect: expectMap{
encoderHTML: "<p>Text<sup id=\"fnref:1\"><a class=\"zs-noteref\" href=\"#fn:1\" role=\"doc-noteref\">1</a></sup></p><ol class=\"zs-endnotes\"><li class=\"zs-endnote\" id=\"fn:1\" role=\"doc-endnote\" value=\"1\">Endnote <a class=\"zs-endnote-backref\" href=\"#fnref:1\" role=\"doc-backlink\">\u21a9\ufe0e</a></li></ol>",
encoderMD: "Text",
encoderSz: `(BLOCK (PARA (TEXT "Text") (ENDNOTE () (TEXT "Endnote"))))`,
encoderSHTML: "((p \"Text\" (sup ((id . \"fnref:1\")) (a ((class . \"zs-noteref\") (href . \"#fn:1\") (role . \"doc-noteref\")) \"1\"))))",
encoderText: "Text Endnote",
encoderZmk: useZmk,
},
},
{
descr: "Nested Endnotes",
zmk: `Text[^Endnote[^Nested]]`,
expect: expectMap{
encoderHTML: "<p>Text<sup id=\"fnref:1\"><a class=\"zs-noteref\" href=\"#fn:1\" role=\"doc-noteref\">1</a></sup></p><ol class=\"zs-endnotes\"><li class=\"zs-endnote\" id=\"fn:1\" role=\"doc-endnote\" value=\"1\">Endnote<sup id=\"fnref:2\"><a class=\"zs-noteref\" href=\"#fn:2\" role=\"doc-noteref\">2</a></sup> <a class=\"zs-endnote-backref\" href=\"#fnref:1\" role=\"doc-backlink\">\u21a9\ufe0e</a></li><li class=\"zs-endnote\" id=\"fn:2\" role=\"doc-endnote\" value=\"2\">Nested <a class=\"zs-endnote-backref\" href=\"#fnref:2\" role=\"doc-backlink\">\u21a9\ufe0e</a></li></ol>",
encoderMD: "Text",
encoderSz: `(BLOCK (PARA (TEXT "Text") (ENDNOTE () (TEXT "Endnote") (ENDNOTE () (TEXT "Nested")))))`,
encoderSHTML: "((p \"Text\" (sup ((id . \"fnref:1\")) (a ((class . \"zs-noteref\") (href . \"#fn:1\") (role . \"doc-noteref\")) \"1\"))))",
encoderText: "Text Endnote Nested",
encoderZmk: useZmk,
},
},
{
descr: "Transclusion",
zmk: `{{{http://example.com/image}}}{width="100px"}`,
expect: expectMap{
encoderHTML: `<p><img class="external" src="http://example.com/image" width="100px"></p>`,
encoderMD: "",
encoderSz: `(BLOCK (TRANSCLUDE (("width" . "100px")) (EXTERNAL "http://example.com/image")))`,
encoderSHTML: `((p (img ((class . "external") (src . "http://example.com/image") (width . "100px")))))`,
encoderText: "",
encoderZmk: useZmk,
},
},
{
descr: "A paragraph with a inline comment only should be empty in HTML",
zmk: `%% Comment`,
expect: expectMap{
// encoderHTML: ``,
encoderSz: `(BLOCK (PARA (LITERAL-COMMENT () "Comment")))`,
// encoderSHTML: ``,
encoderText: "",
encoderZmk: useZmk,
},
},
{
descr: "Zettel with disallowed syntax HTML",
zmk: "<h1>Hello</h1>\nWorld\n",
syntax: meta.ValueSyntaxHTML,
expect: expectMap{
encoderHTML: "<pre><code class=\"language-html\"><h1>Hello</h1>\nWorld</code></pre>",
encoderSz: `(BLOCK (VERBATIM-CODE (("" . "html")) "<h1>Hello</h1>\nWorld"))`,
encoderSHTML: `((pre (code ((class . "language-html")) "<h1>Hello</h1>\nWorld")))`,
encoderText: "<h1>Hello</h1>\nWorld",
encoderZmk: "```{=\"html\"}\n<h1>Hello</h1>\nWorld\n```",
},
},
{
descr: "Zettel with allowed syntax HTML",
zmk: "<h1>Hello</h1>\nWorld\n",
syntax: meta.ValueSyntaxHTML,
allowHTML: true,
expect: expectMap{
encoderHTML: "<h1>Hello</h1>\nWorld",
encoderSz: `(BLOCK (VERBATIM-HTML (("" . "html")) "<h1>Hello</h1>\nWorld"))`,
encoderSHTML: `((@H "<h1>Hello</h1>\nWorld"))`,
encoderText: "<h1>Hello</h1>\nWorld",
encoderZmk: "@@@{=\"html\"}\n<h1>Hello</h1>\nWorld\n@@@",
},
},
{
descr: "",
zmk: ``,
expect: expectMap{
encoderHTML: ``,
encoderSz: `(BLOCK)`,
encoderSHTML: `()`,
encoderText: "",
encoderZmk: useZmk,
},
},
}
|
Changes to internal/encoder/encoder_inline_test.go.
| ︙ | ︙ | |||
161 162 163 164 165 166 167 |
{
descr: "Quotes formatting (german)",
zmk: `""quotes""{lang=de}`,
expect: expectMap{
encoderHTML: `<p><span lang="de">„quotes“</span></p>`,
encoderMD: "„quotes“",
encoderSz: `(BLOCK (PARA (FORMAT-QUOTE (("lang" . "de")) (TEXT "quotes"))))`,
| | | 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
{
descr: "Quotes formatting (german)",
zmk: `""quotes""{lang=de}`,
expect: expectMap{
encoderHTML: `<p><span lang="de">„quotes“</span></p>`,
encoderMD: "„quotes“",
encoderSz: `(BLOCK (PARA (FORMAT-QUOTE (("lang" . "de")) (TEXT "quotes"))))`,
encoderSHTML: `((p (span ((lang . "de")) (@H "„") "quotes" (@H "“"))))`,
encoderText: `quotes`,
encoderZmk: `""quotes""{lang="de"}`,
},
},
{
descr: "Empty quotes (default)",
zmk: `""""`,
|
| ︙ | ︙ | |||
185 186 187 188 189 190 191 |
{
descr: "Empty quotes (unknown)",
zmk: `""""{lang=unknown}`,
expect: expectMap{
encoderHTML: `<p><span lang="unknown">""</span></p>`,
encoderMD: """",
encoderSz: `(BLOCK (PARA (FORMAT-QUOTE (("lang" . "unknown")))))`,
| | | 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
{
descr: "Empty quotes (unknown)",
zmk: `""""{lang=unknown}`,
expect: expectMap{
encoderHTML: `<p><span lang="unknown">""</span></p>`,
encoderMD: """",
encoderSz: `(BLOCK (PARA (FORMAT-QUOTE (("lang" . "unknown")))))`,
encoderSHTML: `((p (span ((lang . "unknown")) (@H """ """))))`,
encoderText: ``,
encoderZmk: `""""{lang="unknown"}`,
},
},
{
descr: "Nested quotes (default)",
zmk: `""say: ::""yes, ::""or?""::""::""`,
|
| ︙ | ︙ | |||
305 306 307 308 309 310 311 |
{
descr: "Math formatting",
zmk: `$$\TeX$$`,
expect: expectMap{
encoderHTML: `<p><code class="zs-math">\TeX</code></p>`,
encoderMD: "\\TeX",
encoderSz: `(BLOCK (PARA (LITERAL-MATH () "\\TeX")))`,
| | | > > > > > > > > > > > > | 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 |
{
descr: "Math formatting",
zmk: `$$\TeX$$`,
expect: expectMap{
encoderHTML: `<p><code class="zs-math">\TeX</code></p>`,
encoderMD: "\\TeX",
encoderSz: `(BLOCK (PARA (LITERAL-MATH () "\\TeX")))`,
encoderSHTML: `((p (code ((class . "zs-math")) "\\TeX")))`,
encoderText: `\TeX`,
encoderZmk: useZmk,
},
},
{
descr: "Nested Span Quote formatting",
zmk: `::""abc""::{lang=fr}`,
expect: expectMap{
encoderHTML: `<p><span lang="fr">« abc »</span></p>`,
encoderMD: "« abc »",
encoderSz: `(BLOCK (PARA (FORMAT-SPAN (("lang" . "fr")) (FORMAT-QUOTE () (TEXT "abc")))))`,
encoderSHTML: `((p (span ((lang . "fr")) (@L (@H "«" " ") "abc" (@H " " "»")))))`,
encoderText: `abc`,
encoderZmk: `::""abc""::{lang="fr"}`,
},
},
{
descr: "Nested Insert Quote formatting",
zmk: `>>""abc"">>{lang=fr}`,
expect: expectMap{
encoderHTML: `<p><ins lang="fr">« abc »</ins></p>`,
encoderMD: "« abc »",
encoderSz: `(BLOCK (PARA (FORMAT-INSERT (("lang" . "fr")) (FORMAT-QUOTE () (TEXT "abc")))))`,
encoderSHTML: `((p (ins ((lang . "fr")) (@L (@H "«" " ") "abc" (@H " " "»")))))`,
encoderText: `abc`,
encoderZmk: `>>""abc"">>{lang="fr"}`,
},
},
{
descr: "Simple Citation",
zmk: `[@Stern18]`,
expect: expectMap{
encoderHTML: `<p><span>Stern18</span></p>`, // TODO
encoderMD: "",
encoderSz: `(BLOCK (PARA (CITE () "Stern18")))`,
|
| ︙ | ︙ | |||
397 398 399 400 401 402 403 |
encoderZmk: useZmk,
},
},
{
descr: "Comment after text and with -->",
zmk: `Text%%{-} comment --> end`,
expect: expectMap{
| | | | | | 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 |
encoderZmk: useZmk,
},
},
{
descr: "Comment after text and with -->",
zmk: `Text%%{-} comment --> end`,
expect: expectMap{
encoderHTML: `<p>Text<!-- comment --> end --></p>`,
encoderMD: "Text",
encoderSz: `(BLOCK (PARA (TEXT "Text") (LITERAL-COMMENT (("-" . "")) "comment --> end")))`,
encoderSHTML: `((p "Text" (@@ "comment --> end")))`,
encoderText: `Text`,
encoderZmk: useZmk,
},
},
{
descr: "Simple inline endnote",
zmk: `[^endnote]`,
expect: expectMap{
encoderHTML: `<p><sup id="fnref:1"><a class="zs-noteref" href="#fn:1" role="doc-noteref">1</a></sup></p><ol class="zs-endnotes"><li class="zs-endnote" id="fn:1" role="doc-endnote" value="1">endnote <a class="zs-endnote-backref" href="#fnref:1" role="doc-backlink">↩︎</a></li></ol>`,
encoderMD: "",
encoderSz: `(BLOCK (PARA (ENDNOTE () (TEXT "endnote"))))`,
encoderSHTML: `((p (sup ((id . "fnref:1")) (a ((class . "zs-noteref") (href . "#fn:1") (role . "doc-noteref")) "1"))))`,
encoderText: `endnote`,
encoderZmk: useZmk,
},
},
{
descr: "Simple mark",
zmk: `[!mark]`,
expect: expectMap{
encoderHTML: `<p><a id="mark"></a></p>`,
encoderMD: "",
encoderSz: `(BLOCK (PARA (MARK "mark" "mark" "mark")))`,
encoderSHTML: `((p (a ((id . "mark")))))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Mark with text",
zmk: `[!mark|with text]`,
expect: expectMap{
encoderHTML: `<p><a id="mark">with text</a></p>`,
encoderMD: "with text",
encoderSz: `(BLOCK (PARA (MARK "mark" "mark" "mark" (TEXT "with text"))))`,
encoderSHTML: `((p (a ((id . "mark")) "with text")))`,
encoderText: `with text`,
encoderZmk: useZmk,
},
},
{
descr: "Invalid Link",
zmk: `[[link|00000000000000]]`,
|
| ︙ | ︙ | |||
472 473 474 475 476 477 478 |
{
descr: "Dummy Link",
zmk: `[[abc]]`,
expect: expectMap{
encoderHTML: `<p><a href="abc">abc</a></p>`,
encoderMD: "[abc](abc)",
encoderSz: `(BLOCK (PARA (LINK () (HOSTED "abc"))))`,
| | > > > > > > > > > > > > | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > | 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 |
{
descr: "Dummy Link",
zmk: `[[abc]]`,
expect: expectMap{
encoderHTML: `<p><a href="abc">abc</a></p>`,
encoderMD: "[abc](abc)",
encoderSz: `(BLOCK (PARA (LINK () (HOSTED "abc"))))`,
encoderSHTML: `((p (a ((href . "abc")) "abc")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Dummy Link with attribute",
zmk: `[[abc]]{a="b"}`,
expect: expectMap{
encoderHTML: `<p><a a="b" href="abc">abc</a></p>`,
encoderMD: "[abc](abc)",
encoderSz: `(BLOCK (PARA (LINK (("a" . "b")) (HOSTED "abc"))))`,
encoderSHTML: `((p (a ((a . "b") (href . "abc")) "abc")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Simple URL",
zmk: `[[https://zettelstore.de]]`,
expect: expectMap{
encoderHTML: `<p><a href="https://zettelstore.de" rel="external">https://zettelstore.de</a></p>`,
encoderMD: "<https://zettelstore.de>",
encoderSz: `(BLOCK (PARA (LINK () (EXTERNAL "https://zettelstore.de"))))`,
encoderSHTML: `((p (a ((href . "https://zettelstore.de") (rel . "external")) "https://zettelstore.de")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "URL with Text",
zmk: `[[Home|https://zettelstore.de]]`,
expect: expectMap{
encoderHTML: `<p><a href="https://zettelstore.de" rel="external">Home</a></p>`,
encoderMD: "[Home](https://zettelstore.de)",
encoderSz: `(BLOCK (PARA (LINK () (EXTERNAL "https://zettelstore.de") (TEXT "Home"))))`,
encoderSHTML: `((p (a ((href . "https://zettelstore.de") (rel . "external")) "Home")))`,
encoderText: `Home`,
encoderZmk: useZmk,
},
},
{
descr: "Simple Zettel ID",
zmk: `[[00000000000100]]`,
expect: expectMap{
encoderHTML: `<p><a href="00000000000100">00000000000100</a></p>`,
encoderMD: "[00000000000100](00000000000100)",
encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100"))))`,
encoderSHTML: `((p (a ((href . "00000000000100")) "00000000000100")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Zettel ID with Text",
zmk: `[[Config|00000000000100]]`,
expect: expectMap{
encoderHTML: `<p><a href="00000000000100">Config</a></p>`,
encoderMD: "[Config](00000000000100)",
encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100") (TEXT "Config"))))`,
encoderSHTML: `((p (a ((href . "00000000000100")) "Config")))`,
encoderText: `Config`,
encoderZmk: useZmk,
},
},
{
descr: "Simple Zettel ID with fragment",
zmk: `[[00000000000100#frag]]`,
expect: expectMap{
encoderHTML: `<p><a href="00000000000100#frag">00000000000100#frag</a></p>`,
encoderMD: "[00000000000100#frag](00000000000100#frag)",
encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100#frag"))))`,
encoderSHTML: `((p (a ((href . "00000000000100#frag")) "00000000000100#frag")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Zettel ID with Text and fragment",
zmk: `[[Config|00000000000100#frag]]`,
expect: expectMap{
encoderHTML: `<p><a href="00000000000100#frag">Config</a></p>`,
encoderMD: "[Config](00000000000100#frag)",
encoderSz: `(BLOCK (PARA (LINK () (ZETTEL "00000000000100#frag") (TEXT "Config"))))`,
encoderSHTML: `((p (a ((href . "00000000000100#frag")) "Config")))`,
encoderText: `Config`,
encoderZmk: useZmk,
},
},
{
descr: "Fragment link to self",
zmk: `[[#frag]]`,
expect: expectMap{
encoderHTML: `<p><a href="#frag">#frag</a></p>`,
encoderMD: "[#frag](#frag)",
encoderSz: `(BLOCK (PARA (LINK () (SELF "#frag"))))`,
encoderSHTML: `((p (a ((href . "#frag")) "#frag")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Hosted link",
zmk: `[[H|/hosted]]`,
expect: expectMap{
encoderHTML: `<p><a href="/hosted">H</a></p>`,
encoderMD: "[H](/hosted)",
encoderSz: `(BLOCK (PARA (LINK () (HOSTED "/hosted") (TEXT "H"))))`,
encoderSHTML: `((p (a ((href . "/hosted")) "H")))`,
encoderText: `H`,
encoderZmk: useZmk,
},
},
{
descr: "Based link",
zmk: `[[B|//based]]`,
expect: expectMap{
encoderHTML: `<p><a href="/based">B</a></p>`,
encoderMD: "[B](/based)",
encoderSz: `(BLOCK (PARA (LINK () (BASED "/based") (TEXT "B"))))`,
encoderText: `B`,
encoderSHTML: `((p (a ((href . "/based")) "B")))`,
encoderZmk: useZmk,
},
},
{
descr: "Relative link",
zmk: `[[R|../relative]]`,
expect: expectMap{
encoderHTML: `<p><a href="../relative">R</a></p>`,
encoderMD: "[R](../relative)",
encoderSz: `(BLOCK (PARA (LINK () (HOSTED "../relative") (TEXT "R"))))`,
encoderSHTML: `((p (a ((href . "../relative")) "R")))`,
encoderText: `R`,
encoderZmk: useZmk,
},
},
{
descr: "Query link w/o text",
zmk: `[[query:title:syntax]]`,
expect: expectMap{
encoderHTML: `<p><a href="?q=title%3Asyntax">title:syntax</a></p>`,
encoderMD: "",
encoderSz: `(BLOCK (PARA (LINK () (QUERY "title:syntax"))))`,
encoderSHTML: `((p (a ((href . "?q=title%3Asyntax")) "title:syntax")))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Query link with text",
zmk: `[[Q|query:title:syntax]]`,
expect: expectMap{
encoderHTML: `<p><a href="?q=title%3Asyntax">Q</a></p>`,
encoderMD: "Q",
encoderSz: `(BLOCK (PARA (LINK () (QUERY "title:syntax") (TEXT "Q"))))`,
encoderSHTML: `((p (a ((href . "?q=title%3Asyntax")) "Q")))`,
encoderText: `Q`,
encoderZmk: useZmk,
},
},
{
descr: "Dummy Embed",
zmk: `{{abc}}`,
expect: expectMap{
encoderHTML: `<p><img src="abc"></p>`,
encoderMD: "",
encoderSz: `(BLOCK (PARA (EMBED () (HOSTED "abc") "")))`,
encoderSHTML: `((p (img ((src . "abc")))))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Dummy Embed with attributes",
zmk: `{{abc}}{a="b"}`,
expect: expectMap{
encoderHTML: `<p><img a="b" src="abc"></p>`,
encoderMD: "",
encoderSz: `(BLOCK (PARA (EMBED (("a" . "b")) (HOSTED "abc") "")))`,
encoderSHTML: `((p (img ((a . "b") (src . "abc")))))`,
encoderText: ``,
encoderZmk: useZmk,
},
},
{
descr: "Link and attributes with quotes",
zmk: `[[text|/url]]{title="TITL \"\""}`,
expect: expectMap{
encoderHTML: `<p><a href="/url" title="TITL """>text</a></p>`,
encoderMD: "[text](/url)", // better: [text](/url "TITL \"\"")
encoderSz: `(BLOCK (PARA (LINK (("title" . "TITL \"\"")) (HOSTED "/url") (TEXT "text"))))`,
encoderSHTML: `((p (a ((href . "/url") (title . "TITL \"\"")) "text")))`,
encoderText: `text`,
encoderZmk: useZmk,
},
},
{
descr: "",
zmk: ``,
expect: expectMap{
encoderHTML: ``,
encoderMD: "",
encoderSz: `(BLOCK)`,
encoderSHTML: `()`,
encoderText: ``,
encoderZmk: useZmk,
},
},
}
|
Changes to internal/encoder/encoder_test.go.
| ︙ | ︙ | |||
14 15 16 17 18 19 20 | package encoder_test import ( "fmt" "strings" "testing" | | < < | | > > | | | > > > > > > > > | > | < | | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
package encoder_test
import (
"fmt"
"strings"
"testing"
"t73f.de/r/sx"
"t73f.de/r/zsc/api"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx/input"
"zettelstore.de/z/internal/encoder"
"zettelstore.de/z/internal/parser"
)
type zmkTestCase struct {
descr string
zmk string
syntax string
allowHTML bool
inline bool
expect expectMap
}
type expectMap map[api.EncodingEnum]string
const useZmk = "\000"
const (
encoderHTML = api.EncoderHTML
encoderMD = api.EncoderMD
encoderSz = api.EncoderSz
encoderSHTML = api.EncoderSHTML
encoderText = api.EncoderText
encoderZmk = api.EncoderZmk
)
func TestEncoder(t *testing.T) {
for i := range tcsInline {
tcsInline[i].inline = true
}
executeTestCases(t, append(append([]zmkTestCase{}, tcsBlock...), tcsInline...))
}
func executeTestCases(t *testing.T, testCases []zmkTestCase) {
for testNum, tc := range testCases {
inp := input.NewInput([]byte(tc.zmk))
syntax := tc.syntax
if syntax == "" {
syntax = meta.ValueSyntaxZmk
}
alst := sx.Nil()
if tc.allowHTML {
alst = alst.Cons(sx.Cons(parser.SymAllowHTML, nil))
}
node := parser.Parse(inp, nil, syntax, alst)
parser.Clean(node)
checkEncodings(t, testNum, node, tc.inline, tc.descr, tc.expect, tc.zmk)
}
}
func checkEncodings(t *testing.T, testNum int, node *sx.Pair, isInline bool, descr string, expected expectMap, zmkDefault string) {
for enc, exp := range expected {
encdr := encoder.Create(enc, &encoder.CreateParameter{Lang: meta.ValueLangEN})
got, err := encode(encdr, node)
if err != nil {
prefix := fmt.Sprintf("Test #%d", testNum)
if d := descr; d != "" {
prefix += "\nReason: " + d
}
prefix += "\nMode: " + mode(isInline)
t.Errorf("%s\nEncoder: %s\nError: %v", prefix, enc, err)
|
| ︙ | ︙ | |||
87 88 89 90 91 92 93 |
}
prefix += "\nMode: " + mode(isInline)
t.Errorf("%s\nEncoder: %s\nExpected: %q\nGot: %q", prefix, enc, exp, got)
}
}
}
| < < < < < < < < < < < < < < < < < < < < < < < < | | | 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
}
prefix += "\nMode: " + mode(isInline)
t.Errorf("%s\nEncoder: %s\nExpected: %q\nGot: %q", prefix, enc, exp, got)
}
}
}
func encode(e encoder.Encoder, node *sx.Pair) (string, error) {
var sb strings.Builder
err := e.WriteSz(&sb, node)
return sb.String(), err
}
func mode(isInline bool) string {
if isInline {
return "inline"
}
return "block"
}
|
Changes to internal/encoder/htmlenc.go.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 26 27 28 29 30 31 |
"io"
"strings"
"t73f.de/r/sx"
"t73f.de/r/sxwebs/sxhtml"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/shtml"
"zettelstore.de/z/internal/ast"
)
// htmlEncoder contains all data needed for encoding.
type htmlEncoder struct {
| > > < | | | | | < | | < | | | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
"io"
"strings"
"t73f.de/r/sx"
"t73f.de/r/sxwebs/sxhtml"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/shtml"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/ast"
)
// htmlEncoder contains all data needed for encoding.
type htmlEncoder struct {
th *shtml.Evaluator
lang string
textEnc TextEncoder
}
// WriteZettel encodes a full zettel as HTML5.
func (he *htmlEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
env := shtml.MakeEnvironment(he.lang)
hm, err := he.th.Evaluate(ast.GetMetaSz(zn.InhMeta), &env)
if err != nil {
return err
}
var szTitle *sx.Pair
var htitle *sx.Pair
plainTitle, hasTitle := zn.InhMeta.Get(meta.KeyTitle)
if hasTitle {
szTitle = zsx.MakeInline((zsx.MakeText(sz.NormalizedSpacedText(string(plainTitle)))))
htitle, err = he.th.Evaluate(szTitle, &env)
if err != nil {
return err
}
}
hast, err := he.th.Evaluate(zn.Blocks, &env)
if err != nil {
return err
}
hen := shtml.Endnotes(&env)
var head sx.ListBuilder
head.AddN(
shtml.SymHead,
sx.Nil().Cons(sx.Nil().Cons(sx.Cons(sxhtml.MakeSymbol("charset"), sx.MakeString("utf-8")))).Cons(shtml.SymMeta),
)
head.ExtendBang(hm)
var sb strings.Builder
if hasTitle {
_ = he.textEnc.WriteSz(&sb, szTitle)
} else {
sb.Write(zn.Meta.Zid.Bytes())
}
head.Add(sx.MakeList(shtml.SymAttrTitle, sx.MakeString(sb.String())))
var body sx.ListBuilder
body.Add(shtml.SymBody)
|
| ︙ | ︙ | |||
92 93 94 95 96 97 98 | ) gen := sxhtml.NewGenerator().SetNewline() return gen.WriteHTML(w, doc) } // WriteMeta encodes meta data as HTML5. | | | | | | | | < | | < < | | 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
)
gen := sxhtml.NewGenerator().SetNewline()
return gen.WriteHTML(w, doc)
}
// WriteMeta encodes meta data as HTML5.
func (he *htmlEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
env := shtml.MakeEnvironment(he.lang)
hm, err := he.th.Evaluate(ast.GetMetaSz(m), &env)
if err != nil {
return err
}
gen := sxhtml.NewGenerator().SetNewline()
return gen.WriteListHTML(w, hm)
}
// WriteSz encodes SZ represented zettel content.
func (he *htmlEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
env := shtml.MakeEnvironment(he.lang)
hobj, err := he.th.Evaluate(node, &env)
if err == nil {
gen := sxhtml.NewGenerator()
if err = gen.WriteListHTML(w, hobj); err != nil {
return err
}
return gen.WriteHTML(w, shtml.Endnotes(&env))
}
return err
}
|
Changes to internal/encoder/mdenc.go.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package encoder
// Encodes the abstract syntax tree back into Markdown.
import (
"io"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/shtml"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/ast"
)
// mdEncoder contains all data needed for encoding.
type mdEncoder struct {
lang string
}
// WriteZettel writes the encoded zettel to the writer.
| > > > | | | | < | | | < | < < < < < | < | | | < < | | > | | | > | < < | | > | < | > > > > | | > | | > | | | > | > | | | | > > > | | | | > > > | | > > | > | > > > > > | > > > > | > > | | > > | | > > > > > > | | > | > > | > | > | > | | < | | | > > > | > > | | | > > | > > | > | | > > < < < | < < < < < < < < < < < | < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | < < | < < | < < < | < < < < < < < | | | | | < | < | | > | | > | | < < | < < | < < < < < < < < < | | | < < | < | | > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > | > > > > | > > > > > > > | > > > > > | > > > | > > > > > > > > > | > > | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 |
package encoder
// Encodes the abstract syntax tree back into Markdown.
import (
"io"
"net/url"
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/shtml"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/ast"
)
// mdEncoder contains all data needed for encoding.
type mdEncoder struct {
lang string
}
// WriteZettel writes the encoded zettel to the writer.
func (me *mdEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
v := newMDVisitor(w, me.lang)
v.b.WriteMeta(zn.InhMeta)
if zn.InhMeta.YamlSep {
v.b.WriteString("---\n")
} else {
v.b.WriteLn()
}
v.walk(zn.Blocks, nil)
return v.b.Flush()
}
// WriteMeta encodes meta data as markdown.
func (*mdEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
ew := newEncWriter(w)
ew.WriteMeta(m)
return ew.Flush()
}
// WriteSz encodes SZ represented zettel content.
func (me *mdEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
v := newMDVisitor(w, me.lang)
zsx.WalkIt(&v, node, nil)
return v.b.Flush()
}
type mdVisitor struct {
b encWriter
listInfo []int
listPrefix string
defLang string
quoteNesting uint
}
func newMDVisitor(w io.Writer, lang string) mdVisitor {
return mdVisitor{b: newEncWriter(w), defLang: lang}
}
var symLang = sx.MakeSymbol("lang")
func (v *mdVisitor) getLanguage(alst *sx.Pair) string {
if a := alst.Assoc(symLang); a != nil {
if s, isString := sx.GetString(a.Cdr()); isString {
return s.GetValue()
}
}
return v.defLang
}
func (*mdVisitor) setLanguage(alst, attrs *sx.Pair) *sx.Pair {
if a := attrs.Assoc(sx.MakeString("lang")); a != nil {
val := a.Cdr()
if p, isPair := sx.GetPair(val); isPair {
val = p.Car()
}
return alst.Cons(sx.Cons(symLang, val))
}
return alst
}
func (v *mdVisitor) getQuotes(alst *sx.Pair) (string, string, bool) {
qi := shtml.GetQuoteInfo(v.getLanguage(alst))
leftQ, rightQ := qi.GetQuotes(v.quoteNesting)
return leftQ, rightQ, qi.GetNBSp()
}
func (v *mdVisitor) walk(node, alst *sx.Pair) { zsx.WalkIt(v, node, alst) }
func (v *mdVisitor) walkList(lst, alst *sx.Pair) { zsx.WalkItList(v, lst, 0, alst) }
func (v *mdVisitor) VisitItBefore(node *sx.Pair, alst *sx.Pair) bool {
if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
switch sym {
case zsx.SymBlock:
v.visitBlock(node, alst)
case zsx.SymText:
v.b.WriteString(zsx.GetText(node))
case zsx.SymSoft:
v.visitBreak(false)
case zsx.SymHard:
v.visitBreak(true)
case zsx.SymLink:
attrs, ref, inlines := zsx.GetLink(node)
alst = v.setLanguage(alst, attrs)
v.visitReference(ref, inlines, alst)
case zsx.SymEmbed:
attrs, ref, _, inlines := zsx.GetEmbed(node)
alst = v.setLanguage(alst, attrs)
_ = v.b.WriteByte('!')
v.visitReference(ref, inlines, alst)
case zsx.SymFormatEmph:
v.visitFormat(node, alst, "*", "*")
case zsx.SymFormatStrong:
v.visitFormat(node, alst, "__", "__")
case zsx.SymFormatQuote:
v.visitQuote(node, alst)
case zsx.SymFormatMark:
v.visitFormat(node, alst, "<mark>", "</mark>")
case zsx.SymFormatSpan, zsx.SymFormatDelete, zsx.SymFormatInsert, zsx.SymFormatSub, zsx.SymFormatSuper:
v.visitFormat(node, alst, "", "")
case zsx.SymLiteralCode, zsx.SymLiteralInput, zsx.SymLiteralOutput:
_, _, content := zsx.GetLiteral(node)
v.b.WriteStrings("`", content, "`")
case zsx.SymLiteralMath:
_, _, content := zsx.GetLiteral(node)
v.b.WriteString(content)
case zsx.SymHeading:
level, attrs, text, _, _ := zsx.GetHeading(node)
const headingSigns = "###### "
v.b.WriteString(headingSigns[len(headingSigns)-level-1:])
v.walkList(text, v.setLanguage(alst, attrs))
case zsx.SymThematic:
v.b.WriteString("---")
case zsx.SymListOrdered:
v.visitNestedList(node, alst, enumOrdered)
case zsx.SymListUnordered:
v.visitNestedList(node, alst, enumUnordered)
case zsx.SymListQuote:
if len(v.listInfo) == 0 {
v.visitListQuote(node, alst)
}
case zsx.SymVerbatimCode:
v.visitVerbatim(node)
case zsx.SymRegionQuote:
v.visitRegion(node, alst)
case zsx.SymRegionBlock, zsx.SymRegionVerse,
zsx.SymVerbatimComment, zsx.SymVerbatimEval, zsx.SymVerbatimHTML, zsx.SymVerbatimMath, zsx.SymVerbatimZettel,
zsx.SymDescription, zsx.SymTable, zsx.SymEndnote,
zsx.SymLiteralComment:
// Do nothing, ignore it.
default:
return false
}
return true
}
return false
}
func (v *mdVisitor) VisitItAfter(*sx.Pair, *sx.Pair) {}
func (v *mdVisitor) visitBlock(node *sx.Pair, alst *sx.Pair) {
first := true
for bn := range node.Tail().Pairs() {
if first {
first = false
} else {
v.b.WriteString("\n\n")
}
v.walk(bn.Head(), alst)
}
}
func (v *mdVisitor) visitBreak(isHard bool) {
if isHard {
v.b.WriteString("\\\n")
} else {
v.b.WriteLn()
}
if l := len(v.listInfo); l > 0 {
if v.listPrefix == "" {
v.writeSpaces(4*l - 4 + v.listInfo[l-1])
} else {
v.writeSpaces(4*l - 4)
v.b.WriteString(v.listPrefix)
}
}
}
func (v *mdVisitor) visitReference(ref, inlines, alst *sx.Pair) {
refState, val := zsx.GetReference(ref)
if sz.SymRefStateQuery.IsEqualSymbol(refState) {
v.walkList(inlines, alst)
} else if inlines != nil {
_ = v.b.WriteByte('[')
v.walkList(inlines, alst)
v.b.WriteStrings("](", val)
_ = v.b.WriteByte(')')
} else if isAutoLinkable(refState, val) {
_ = v.b.WriteByte('<')
v.b.WriteString(val)
_ = v.b.WriteByte('>')
} else {
v.b.WriteStrings("[", val, "](", val, ")")
}
}
func isAutoLinkable(refState *sx.Symbol, val string) bool {
if zsx.SymRefStateExternal.IsEqualSymbol(refState) {
if u, err := url.Parse(val); err == nil && u.Scheme != "" {
return true
}
}
return false
}
func (v *mdVisitor) visitFormat(node, alst *sx.Pair, delim1, delim2 string) {
_, attrs, inlines := zsx.GetFormat(node)
alst = v.setLanguage(alst, attrs)
v.b.WriteString(delim1)
v.walkList(inlines, alst)
v.b.WriteString(delim2)
}
func (v *mdVisitor) visitQuote(node, alst *sx.Pair) {
_, attrs, inlines := zsx.GetFormat(node)
alst = v.setLanguage(alst, attrs)
leftQ, rightQ, withNbsp := v.getQuotes(alst)
v.b.WriteString(leftQ)
if withNbsp {
v.b.WriteString(" ")
}
v.quoteNesting++
v.walkList(inlines, alst)
v.quoteNesting--
if withNbsp {
v.b.WriteString(" ")
}
v.b.WriteString(rightQ)
}
const enumOrdered = "1. "
const enumUnordered = "* "
func (v *mdVisitor) visitNestedList(node *sx.Pair, alst *sx.Pair, enum string) {
v.listInfo = append(v.listInfo, len(enum))
regIndent := 4*len(v.listInfo) - 4
paraIndent := regIndent + len(enum)
_, attrs, blocks := zsx.GetList(node)
alst = v.setLanguage(alst, attrs)
firstBlk := true
for blk := range blocks.Pairs() {
if firstBlk {
firstBlk = false
} else {
v.b.WriteLn()
}
v.writeSpaces(regIndent)
v.b.WriteString(enum)
first := true
for item := range blk.Head().Tail().Pairs() {
in := item.Head()
if first {
first = false
} else {
v.b.WriteLn()
if zsx.SymPara.IsEqual(in.Car()) {
v.writeSpaces(paraIndent)
}
}
v.walk(in, alst)
}
}
v.listInfo = v.listInfo[:len(v.listInfo)-1]
}
func (v *mdVisitor) visitListQuote(node *sx.Pair, alst *sx.Pair) {
v.listInfo = []int{0}
oldPrefix := v.listPrefix
v.listPrefix = "> "
_, attrs, blocks := zsx.GetList(node)
alst = v.setLanguage(alst, attrs)
firstBlk := true
for blk := range blocks.Pairs() {
if firstBlk {
firstBlk = false
} else {
v.b.WriteLn()
}
v.b.WriteString(v.listPrefix)
first := true
for item := range blk.Head().Tail().Pairs() {
in := item.Head()
if first {
first = false
} else {
v.b.WriteLn()
if zsx.SymPara.IsEqual(in.Car()) {
v.b.WriteString(v.listPrefix)
}
}
v.walk(in, alst)
}
}
v.listPrefix = oldPrefix
v.listInfo = nil
}
func (v *mdVisitor) visitVerbatim(node *sx.Pair) {
if _, _, content := zsx.GetVerbatim(node); content != "" {
lc := len(content)
v.writeSpaces(4)
lcm1 := lc - 1
for i := 0; i < lc; i++ {
b := content[i]
if b != '\n' && b != '\r' {
_ = v.b.WriteByte(b)
continue
}
j := i + 1
for ; j < lc; j++ {
c := content[j]
if c != '\n' && c != '\r' {
break
}
}
if j >= lcm1 {
break
}
v.b.WriteLn()
v.writeSpaces(4)
i = j - 1
}
}
}
func (v *mdVisitor) visitRegion(node *sx.Pair, alst *sx.Pair) {
_, attrs, blocks, _ := zsx.GetRegion(node)
alst = v.setLanguage(alst, attrs)
first := true
for n := range blocks.Pairs() {
blk := n.Head()
if zsx.SymPara.IsEqual(blk.Car()) {
if first {
first = false
} else {
v.b.WriteString("\n>\n")
}
v.b.WriteString("> ")
v.walk(blk, alst)
}
}
}
func (v *mdVisitor) writeSpaces(n int) {
for range n {
v.b.WriteSpace()
}
}
|
Changes to internal/encoder/shtmlenc.go.
| ︙ | ︙ | |||
23 24 25 26 27 28 29 |
"t73f.de/r/zsc/shtml"
"zettelstore.de/z/internal/ast"
)
// shtmlEncoder contains all data needed for encoding.
type shtmlEncoder struct {
| < | | | | | > | | | | | > | | | | > | | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
"t73f.de/r/zsc/shtml"
"zettelstore.de/z/internal/ast"
)
// shtmlEncoder contains all data needed for encoding.
type shtmlEncoder struct {
th *shtml.Evaluator
lang string
}
// WriteZettel writes the encoded zettel to the writer.
func (enc *shtmlEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
env := shtml.MakeEnvironment(enc.lang)
metaSHTML, err := enc.th.Evaluate(ast.GetMetaSz(zn.InhMeta), &env)
if err != nil {
return err
}
contentSHTML, err := enc.th.Evaluate(zn.Blocks, &env)
if err != nil {
return err
}
result := sx.Cons(metaSHTML, contentSHTML)
_, err = result.Print(w)
return err
}
// WriteMeta encodes meta data as s-expression.
func (enc *shtmlEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
env := shtml.MakeEnvironment(enc.lang)
metaSHTML, err := enc.th.Evaluate(ast.GetMetaSz(m), &env)
if err != nil {
return err
}
_, err = sx.Print(w, metaSHTML)
return err
}
// WriteSz encodes SZ represented zettel content.
func (enc *shtmlEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
env := shtml.MakeEnvironment(enc.lang)
hval, err := enc.th.Evaluate(node, &env)
if err != nil {
return err
}
_, err = hval.Print(w)
return err
}
|
Changes to internal/encoder/szenc.go.
| ︙ | ︙ | |||
21 22 23 24 25 26 27 | "t73f.de/r/sx" "t73f.de/r/zsc/domain/meta" "zettelstore.de/z/internal/ast" ) // szEncoder contains all data needed for encoding. | | < | < | < | | > | > | | | > | | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
"zettelstore.de/z/internal/ast"
)
// szEncoder contains all data needed for encoding.
type szEncoder struct{}
// WriteZettel writes the encoded zettel to the writer.
func (enc *szEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
meta := ast.GetMetaSz(zn.InhMeta)
_, err := sx.MakeList(meta, zn.Blocks).Print(w)
return err
}
// WriteMeta encodes meta data as s-expression.
func (enc *szEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
_, err := ast.GetMetaSz(m).Print(w)
return err
}
// WriteSz encodes SZ represented zettel content.
func (*szEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
_, err := node.Print(w)
return err
}
|
Deleted internal/encoder/sztransform.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to internal/encoder/textenc.go.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// textenc encodes the abstract syntax tree into its text.
import (
"io"
"iter"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx/input"
"zettelstore.de/z/internal/ast"
)
// TextEncoder encodes just the text and ignores any formatting.
type TextEncoder struct{}
// WriteZettel writes metadata and content.
| > > | | | | < | | < | | | < | < < < < | < < < < | < | | < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < | < < < < < < | < < < < < < | < < < < < < < | < | | | | | | | | | | | | | > > > > > > > > > | > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
// textenc encodes the abstract syntax tree into its text.
import (
"io"
"iter"
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx"
"t73f.de/r/zsx/input"
"zettelstore.de/z/internal/ast"
)
// TextEncoder encodes just the text and ignores any formatting.
type TextEncoder struct{}
// WriteZettel writes metadata and content.
func (te *TextEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
v := newTextVisitor(w)
_ = te.WriteMeta(&v.b, zn.InhMeta)
v.walk(zn.Blocks, nil)
return v.b.Flush()
}
// WriteMeta encodes metadata as text.
func (te *TextEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
buf := newEncWriter(w)
for key, val := range m.Computed() {
if meta.Type(key) == meta.TypeTagSet {
writeTagSet(&buf, val.Elems())
} else {
buf.WriteString(string(val))
}
buf.WriteLn()
}
return buf.Flush()
}
func writeTagSet(buf *encWriter, tags iter.Seq[meta.Value]) {
first := true
for tag := range tags {
if !first {
buf.WriteSpace()
}
first = false
buf.WriteString(string(tag.CleanTag()))
}
}
// WriteSz writes SZ encoded content to the writer.
func (*TextEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
v := newTextVisitor(w)
v.walk(node, nil)
return v.b.Flush()
}
// textVisitor writes the sx.Object-based AST to an io.Writer.
type textVisitor struct{ b encWriter }
func newTextVisitor(w io.Writer) textVisitor {
return textVisitor{b: newEncWriter(w)}
}
func (v *textVisitor) walk(node, alst *sx.Pair) { zsx.WalkIt(v, node, alst) }
func (v *textVisitor) walkList(lst, alst *sx.Pair) { zsx.WalkItList(v, lst, 0, alst) }
func (v *textVisitor) VisitItBefore(node *sx.Pair, alst *sx.Pair) bool {
if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
switch sym {
case zsx.SymText:
s := zsx.GetText(node)
spaceFound := false
for _, ch := range s {
if input.IsSpace(ch) {
if !spaceFound {
v.b.WriteSpace()
spaceFound = true
}
continue
}
spaceFound = false
v.b.WriteString(string(ch))
}
case zsx.SymHard:
v.b.WriteLn()
case zsx.SymSoft:
_ = v.b.WriteByte(' ')
case zsx.SymEndnote:
if zsx.GetWalkPos(alst) > 0 {
_ = v.b.WriteByte(' ')
}
return false
case zsx.SymLiteralCode, zsx.SymLiteralInput, zsx.SymLiteralMath, zsx.SymLiteralOutput:
if s, found := sx.GetString(node.Tail().Tail().Car()); found {
v.b.WriteString(s.GetValue())
}
case zsx.SymLiteralComment:
// Do nothing
case zsx.SymBlock:
blocks := zsx.GetBlock(node)
first := true
for n := range blocks.Pairs() {
if first {
first = false
} else {
v.b.WriteLn()
}
v.walk(n.Head(), alst)
}
case zsx.SymInline:
inlines := zsx.GetInline(node)
first := true
for n := range inlines.Pairs() {
if first {
first = false
} else {
v.b.WriteLn()
}
v.walk(n.Head(), alst)
}
case zsx.SymListOrdered, zsx.SymListUnordered, zsx.SymListQuote:
_, _, items := zsx.GetList(node)
first := true
for n := range items.Pairs() {
if first {
first = false
} else {
v.b.WriteLn()
}
v.walk(n.Head(), alst)
}
case zsx.SymTable:
_, header, rowList := zsx.GetTable(node)
firstRow := true
for n := range rowList.Cons(header).Pairs() {
row := n.Head()
if row == nil {
continue
}
if firstRow {
firstRow = false
} else {
v.b.WriteLn()
}
firstCell := true
for elem := range row.Pairs() {
if firstCell {
firstCell = false
} else {
_ = v.b.WriteByte(' ')
}
v.walk(elem.Head(), alst)
}
}
case zsx.SymDescription:
_, termsVals := zsx.GetDescription(node)
first := true
for n := termsVals; n != nil; n = n.Tail() {
if first {
first = false
} else {
v.b.WriteLn()
}
v.walkList(n.Head(), alst)
n = n.Tail()
if n == nil {
break
}
dvals := n.Head()
if zsx.SymBlock.IsEqual(dvals.Car()) {
for val := range dvals.Tail().Pairs() {
v.b.WriteLn()
v.walk(val.Head(), alst)
}
}
}
case zsx.SymRegionBlock, zsx.SymRegionQuote, zsx.SymRegionVerse:
_, _, content, inlines := zsx.GetRegion(node)
first := true
for n := range content.Pairs() {
if first {
first = false
} else {
v.b.WriteLn()
}
v.walk(n.Head(), alst)
}
if inlines != nil {
v.b.WriteLn()
v.walkList(inlines, alst)
}
case zsx.SymVerbatimCode, zsx.SymVerbatimEval, zsx.SymVerbatimHTML, zsx.SymVerbatimMath, zsx.SymVerbatimZettel:
_, _, s := zsx.GetVerbatim(node)
v.b.WriteString(s)
case zsx.SymVerbatimComment, zsx.SymBLOB:
// Do nothing
default:
return false
}
return true
}
return false
}
func (v *textVisitor) VisitItAfter(*sx.Pair, *sx.Pair) {}
|
Changes to internal/encoder/write.go.
| ︙ | ︙ | |||
10 11 12 13 14 15 16 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2020-present Detlef Stern //----------------------------------------------------------------------------- package encoder import ( | < > > | | < < < | < | | < > < < < < < < < < < < < < < < > > > > > > > > > | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2020-present Detlef Stern
//-----------------------------------------------------------------------------
package encoder
import (
"io"
"t73f.de/r/zsc/domain/meta"
)
// encWriter is a specialized writer for encoding zettel.
type encWriter struct {
w io.Writer // The io.Writer to write to
err error // Collect error
}
// newEncWriter creates a new encWriter
func newEncWriter(w io.Writer) encWriter { return encWriter{w: w} }
// Write writes the content of p.
func (w *encWriter) Write(p []byte) (l int, err error) {
if w.err != nil {
return 0, w.err
}
l, w.err = w.w.Write(p)
return l, w.err
}
// WriteString writes the content of s.
func (w *encWriter) WriteString(s string) {
if w.err != nil {
return
}
_, w.err = io.WriteString(w.w, s)
}
// WriteStrings writes the content of sl.
func (w *encWriter) WriteStrings(sl ...string) {
for _, s := range sl {
w.WriteString(s)
}
}
// WriteByte writes the content of b.
func (w *encWriter) WriteByte(b byte) error {
if w.err == nil {
_, w.err = w.Write([]byte{b})
}
return w.err
}
// WriteBytes writes the content of bs.
func (w *encWriter) WriteBytes(bs ...byte) { _, _ = w.Write(bs) }
// WriteLn writes a new line character.
func (w *encWriter) WriteLn() {
if w.err == nil {
w.err = w.WriteByte('\n')
}
}
// WriteLn writes a space character.
func (w *encWriter) WriteSpace() {
if w.err == nil {
w.err = w.WriteByte(' ')
}
}
// WriteMeta write the content of the meta data in a standard way:
//
// key: val
func (w *encWriter) WriteMeta(m *meta.Meta) {
for key, val := range m.Computed() {
w.WriteStrings(key, ": ", string(val), "\n")
}
}
// Flush returns the collected length and error.
func (w *encWriter) Flush() error { return w.err }
|
Changes to internal/encoder/zmkenc.go.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 | //----------------------------------------------------------------------------- package encoder // zmkenc encodes the abstract syntax tree back into Zettelmarkup. import ( | < > > | | | | < | | | < | < < < < < | < | | | < < | < | < < > > > | > | | < > | < | | > | < | | | | | | | < | > > | | > > | | | | | > > > > > | | | > > | > > | > > > | > | < | | | | | | | | < < < < < | | < < < < < < | < < | | < > > > | < | < < | | > > > > > > | < | > > > > | | < > > > > > > | | | | | > | > | > > > | > > > > > > | < < | < < < < > | > > | | > > | > > > > > > > > > > > | > > | > > > > > > > > > > > | > > | > > > | < < < > > > | < < > > | < > > | | > > > > | | < < < | > > > > > > > > > > > > > > | > | | > > > | > | > | | | | | | < | | < < | | > > > > | > | | > > | | | | > > | > > | > > > > > > | | > > | > > > | | > > > > | > | | > > | | | | | | | | | | | > | > > > > | < | > | < | > > > > > > > > > > | | < < | | < < | | < < > > | > > > | | | | | < | < | > | | < | > | | < | > | | > > > > > > > > > > > > | | | | | | | | < | | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < | | > | | > > | > > | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 |
//-----------------------------------------------------------------------------
package encoder
// zmkenc encodes the abstract syntax tree back into Zettelmarkup.
import (
"io"
"strings"
"t73f.de/r/sx"
"t73f.de/r/zero/set"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/ast"
)
// zmkEncoder contains all data needed for encoding.
type zmkEncoder struct{}
// WriteZettel writes the encoded zettel to the writer.
func (ze *zmkEncoder) WriteZettel(w io.Writer, zn *ast.Zettel) error {
v := newZmkVisitor(w)
v.b.WriteMeta(zn.InhMeta)
if zn.InhMeta.YamlSep {
v.b.WriteString("---\n")
} else {
v.b.WriteLn()
}
v.walk(zn.Blocks, nil)
return v.b.Flush()
}
// WriteMeta encodes meta data as zmk.
func (ze *zmkEncoder) WriteMeta(w io.Writer, m *meta.Meta) error {
ew := newEncWriter(w)
ew.WriteMeta(m)
return ew.Flush()
}
// WriteSz encodes SZ represented zettel content.
func (*zmkEncoder) WriteSz(w io.Writer, node *sx.Pair) error {
v := newZmkVisitor(w)
zsx.WalkIt(&v, node, nil)
return v.b.Flush()
}
type zmkVisitor struct {
b encWriter
prefix []byte
}
func newZmkVisitor(w io.Writer) zmkVisitor { return zmkVisitor{b: newEncWriter(w)} }
func (v *zmkVisitor) walk(node, alst *sx.Pair) { zsx.WalkIt(v, node, alst) }
func (v *zmkVisitor) walkList(lst, alst *sx.Pair) { zsx.WalkItList(v, lst, 0, alst) }
func (v *zmkVisitor) VisitItBefore(node *sx.Pair, alst *sx.Pair) bool {
if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
switch sym {
case zsx.SymText:
v.writeText(zsx.GetText(node))
case zsx.SymSoft:
v.writeBreak(false)
case zsx.SymHard:
v.writeBreak(true)
case zsx.SymFormatEmph:
v.visitFormat(node, alst, "__")
case zsx.SymFormatStrong:
v.visitFormat(node, alst, "**")
case zsx.SymFormatInsert:
v.visitFormat(node, alst, ">>")
case zsx.SymFormatDelete:
v.visitFormat(node, alst, "~~")
case zsx.SymFormatSuper:
v.visitFormat(node, alst, "^^")
case zsx.SymFormatSub:
v.visitFormat(node, alst, ",,")
case zsx.SymFormatQuote:
v.visitFormat(node, alst, `""`)
case zsx.SymFormatMark:
v.visitFormat(node, alst, "##")
case zsx.SymFormatSpan:
v.visitFormat(node, alst, "::")
case zsx.SymLiteralCode:
_, attrs, content := zsx.GetLiteral(node)
v.writeLiteral('`', attrs, content)
case zsx.SymLiteralMath:
_, attrs, content := zsx.GetLiteral(node)
v.b.WriteStrings("$$", content, "$$")
v.writeAttributes(attrs)
case zsx.SymLiteralInput:
_, attrs, content := zsx.GetLiteral(node)
v.writeLiteral('\'', attrs, content)
case zsx.SymLiteralOutput:
_, attrs, content := zsx.GetLiteral(node)
v.writeLiteral('=', attrs, content)
case zsx.SymLiteralComment:
_, attrs, content := zsx.GetLiteral(node)
v.b.WriteString("%%")
v.writeAttributes(attrs)
v.b.WriteSpace()
v.b.WriteString(content)
case zsx.SymLink:
v.visitLink(node, alst)
case zsx.SymEmbed:
v.visitEmbedRef(node, alst)
case zsx.SymEndnote:
v.visitEndnote(node, alst)
case zsx.SymCite:
v.visitCite(node, alst)
case zsx.SymMark:
v.visitMark(node, alst)
case zsx.SymBlock:
v.visitBlock(node, alst)
case zsx.SymHeading:
v.visitHeading(node, alst)
case zsx.SymThematic:
attrs := zsx.GetThematic(node)
v.b.WriteString("---")
v.writeAttributes(attrs)
case zsx.SymListOrdered:
v.visitNestedList(node, alst, '#')
case zsx.SymListQuote:
v.visitNestedList(node, alst, '>')
case zsx.SymListUnordered:
v.visitNestedList(node, alst, '*')
case zsx.SymRegionBlock:
v.visitRegion(node, alst, ":::")
case zsx.SymRegionQuote:
v.visitRegion(node, alst, "<<<")
case zsx.SymRegionVerse:
v.visitRegion(node, alst, "\"\"\"")
case zsx.SymDescription:
v.visitDescription(node, alst)
case zsx.SymTable:
v.visitTable(node, alst)
case zsx.SymCell:
v.visitCell(node, alst)
case zsx.SymVerbatimCode:
v.visitVerbatim(node, "```")
case zsx.SymVerbatimComment:
v.visitVerbatim(node, "%%%")
case zsx.SymVerbatimEval:
v.visitVerbatim(node, "~~~")
case zsx.SymVerbatimHTML:
v.visitVerbatim(node, "@@@")
case zsx.SymVerbatimMath:
v.visitVerbatim(node, "$$$")
case zsx.SymVerbatimZettel:
v.visitVerbatim(node, "@@@")
case zsx.SymBLOB:
v.visitBLOB(node)
case zsx.SymTransclude:
v.visitTransclude(node, alst)
default:
return false
}
return true
}
return false
}
func (v *zmkVisitor) VisitItAfter(*sx.Pair, *sx.Pair) {}
func (v *zmkVisitor) visitFormat(node *sx.Pair, alst *sx.Pair, delim string) {
_, attrs, inlines := zsx.GetFormat(node)
v.b.WriteString(delim)
v.walkList(inlines, alst)
v.b.WriteString(delim)
v.writeAttributes(attrs)
}
func (v *zmkVisitor) writeLiteral(code byte, attrs *sx.Pair, content string) {
v.b.WriteBytes(code, code)
v.writeEscaped(content, code)
v.b.WriteBytes(code, code)
v.writeAttributes(attrs)
}
func (v *zmkVisitor) visitLink(node *sx.Pair, alst *sx.Pair) {
attrs, ref, inlines := zsx.GetLink(node)
v.b.WriteString("[[")
if inlines != nil {
v.walkList(inlines, alst)
_ = v.b.WriteByte('|')
}
_ = sz.WriteReference(&v.b, ref)
v.b.WriteString("]]")
v.writeAttributes(attrs)
}
func (v *zmkVisitor) visitEmbedRef(node *sx.Pair, alst *sx.Pair) {
attrs, ref, _, inlines := zsx.GetEmbed(node)
v.b.WriteString("{{")
if inlines != nil {
v.walkList(inlines, alst)
_ = v.b.WriteByte('|')
}
_ = sz.WriteReference(&v.b, ref)
v.b.WriteString("}}")
v.writeAttributes(attrs)
}
func (v *zmkVisitor) visitEndnote(node *sx.Pair, alst *sx.Pair) {
attrs, inlines := zsx.GetEndnote(node)
v.b.WriteString("[^")
v.walkList(inlines, alst)
_ = v.b.WriteByte(']')
v.writeAttributes(attrs)
}
func (v *zmkVisitor) visitCite(node *sx.Pair, alst *sx.Pair) {
attrs, key, inlines := zsx.GetCite(node)
v.b.WriteStrings("[@", key)
if inlines != nil {
v.b.WriteSpace()
v.walkList(inlines, alst)
}
_ = v.b.WriteByte(']')
v.writeAttributes(attrs)
}
func (v *zmkVisitor) visitMark(node *sx.Pair, alst *sx.Pair) {
mark, _, _, inlines := zsx.GetMark(node)
v.b.WriteStrings("[!", mark)
if inlines != nil {
_ = v.b.WriteByte('|')
v.walkList(inlines, alst)
}
_ = v.b.WriteByte(']')
}
func (v *zmkVisitor) visitBlock(node *sx.Pair, alst *sx.Pair) {
blocks := zsx.GetBlock(node)
lastWasParagraph := false
first := true
for bn := range blocks.Pairs() {
blk := bn.Head()
if first {
first = false
} else {
v.b.WriteLn()
if lastWasParagraph && alst.Assoc(zsx.SymRegionVerse) == nil {
if zsx.SymPara.IsEqual(blk.Car()) {
v.b.WriteLn()
}
}
}
v.walk(blk, alst)
lastWasParagraph = zsx.SymPara.IsEqual(blk.Car())
}
}
func (v *zmkVisitor) visitHeading(node *sx.Pair, alst *sx.Pair) {
level, attrs, inlines, _, _ := zsx.GetHeading(node)
const headingSigns = "========= "
v.b.WriteString(headingSigns[len(headingSigns)-level-3:])
v.walkList(inlines, alst)
v.writeAttributes(attrs)
}
func (v *zmkVisitor) visitNestedList(node *sx.Pair, alst *sx.Pair, code byte) {
_, _, items := zsx.GetList(node)
v.prefix = append(v.prefix, code)
first := true
for itm := range items.Pairs() {
if first {
first = false
} else {
v.b.WriteLn()
}
_, _ = v.b.Write(v.prefix)
v.b.WriteSpace()
item := zsx.GetBlock(itm.Head())
second := false
for inn := range item.Pairs() {
inl := inn.Head()
if second {
v.b.WriteLn()
if zsx.SymPara.IsEqual(inl.Car()) {
v.writePrefixSpaces()
}
} else {
second = true
}
v.walk(inl, alst)
}
}
v.prefix = v.prefix[:len(v.prefix)-1]
}
func (v *zmkVisitor) visitRegion(node *sx.Pair, alst *sx.Pair, delim string) {
sym, attrs, blocks, inlines := zsx.GetRegion(node)
//TODO: Scan rn.Blocks for embedded regions to adjust length of regionCode
v.b.WriteString(delim)
v.writeAttributes(attrs)
v.b.WriteLn()
if zsx.SymRegionVerse.IsEqualSymbol(sym) {
alst = alst.Cons(sx.Cons(zsx.SymRegionVerse, sx.Nil()))
}
v.walk(zsx.MakeBlockList(blocks), alst)
v.b.WriteLn()
v.b.WriteString(delim)
if inlines != nil {
v.b.WriteSpace()
v.walkList(inlines, alst)
}
}
func (v *zmkVisitor) visitDescription(node *sx.Pair, alst *sx.Pair) {
_, termVals := zsx.GetDescription(node)
first := true
for n := termVals; n != nil; n = n.Tail() {
if first {
first = false
} else {
v.b.WriteLn()
}
v.b.WriteString("; ")
if term := n.Head(); term != nil {
v.walkList(term, alst)
}
n = n.Tail()
if n == nil {
break
}
for bns := range zsx.GetBlock(n.Head()).Pairs() {
v.b.WriteString("\n: ")
second := false
for pn := range zsx.GetBlock(bns.Head()).Pairs() {
if second {
v.b.WriteString("\n\n ")
} else {
second = true
}
v.walk(pn.Head(), alst)
}
}
}
}
func (v *zmkVisitor) visitTable(node *sx.Pair, alst *sx.Pair) {
_, headerRow, rows := zsx.GetTable(node)
if headerRow != nil {
v.writeRow(headerRow, alst, "|=")
v.b.WriteLn()
}
first := true
for row := range rows.Pairs() {
if first {
first = false
} else {
v.b.WriteLn()
}
v.writeRow(row.Head(), alst, "|")
}
}
func (v *zmkVisitor) writeRow(row *sx.Pair, alst *sx.Pair, delim string) {
for n := range row.Pairs() {
v.b.WriteString(delim)
v.walk(n.Head(), alst)
}
}
func (v *zmkVisitor) visitCell(node *sx.Pair, alst *sx.Pair) {
attrs, inlines := zsx.GetCell(node)
align := ""
if alignPair := attrs.Assoc(zsx.SymAttrAlign); alignPair != nil {
if alignValue := alignPair.Cdr(); zsx.AttrAlignCenter.IsEqual(alignValue) {
align = ":"
} else if zsx.AttrAlignLeft.IsEqual(alignValue) {
align = "<"
} else if zsx.AttrAlignRight.IsEqual(alignValue) {
align = ">"
}
}
v.b.WriteString(align)
v.walkList(inlines, alst)
}
func (v *zmkVisitor) visitVerbatim(node *sx.Pair, delim string) {
sym, attrs, content := zsx.GetVerbatim(node)
if zsx.SymVerbatimHTML.IsEqualSymbol(sym) {
attrs = attrs.RemoveAssoc(sx.MakeString(meta.KeySyntax))
attrs = attrs.Cons(sx.Cons(sx.MakeString(""), sx.MakeString(meta.ValueSyntaxHTML)))
}
// TODO: scan cn.Lines to find embedded kind[0]s at beginning
v.b.WriteString(delim)
v.writeAttributes(attrs)
v.b.WriteLn()
v.b.WriteString(content)
v.b.WriteLn()
v.b.WriteString(delim)
}
func (v *zmkVisitor) visitBLOB(node *sx.Pair) {
_, syntax, content, inlines := zsx.GetBLOBuncode(node)
if syntax == meta.ValueSyntaxSVG {
v.b.WriteString("@@@")
v.b.WriteStrings(syntax, "\n", content, "\n@@@\n")
return
}
var sb strings.Builder
var textEnc TextEncoder
_ = textEnc.WriteSz(&sb, zsx.MakeInlineList(inlines))
v.b.WriteStrings("%% Unable to display BLOB with description '", sb.String(), "' and syntax '", syntax, "'.")
}
func (v *zmkVisitor) visitTransclude(node *sx.Pair, alst *sx.Pair) {
attrs, ref, inlines := zsx.GetTransclusion(node)
v.b.WriteString("{{{")
if inlines != nil {
v.walkList(inlines, alst)
_ = v.b.WriteByte('|')
}
_ = sz.WriteReference(&v.b, ref)
v.b.WriteString("}}}")
v.writeAttributes(attrs)
}
var escapeSeqs = set.New(
"\\", "__", "**", "~~", "^^", ",,", ">>", `""`, "::", "''", "``", "++", "==", "##",
)
func (v *zmkVisitor) writeText(text string) {
last := 0
for i := 0; i < len(text); i++ {
if b := text[i]; b == '\\' {
v.b.WriteString(text[last:i])
v.b.WriteBytes('\\', b)
last = i + 1
continue
}
if i < len(text)-1 {
s := text[i : i+2]
if escapeSeqs.Contains(s) {
v.b.WriteString(text[last:i])
for j := range len(s) {
v.b.WriteBytes('\\', s[j])
}
i++
last = i + 1
continue
}
}
}
v.b.WriteString(text[last:])
}
func (v *zmkVisitor) writeBreak(isHard bool) {
if isHard {
v.b.WriteString("\\\n")
} else {
v.b.WriteLn()
}
v.writePrefixSpaces()
}
func (v *zmkVisitor) writeAttributes(attrs *sx.Pair) {
a := zsx.GetAttributes(attrs)
if a.IsEmpty() {
return
}
_ = v.b.WriteByte('{')
for i, k := range a.Keys() {
if i > 0 {
v.b.WriteSpace()
}
if k == zsx.DefaultAttribute {
_ = v.b.WriteByte('-')
continue
}
v.b.WriteString(k)
if vl := a[k]; len(vl) > 0 {
v.b.WriteString("=\"")
v.writeEscaped(vl, '"')
_ = v.b.WriteByte('"')
}
}
_ = v.b.WriteByte('}')
}
func (v *zmkVisitor) writeEscaped(s string, toEscape byte) {
last := 0
for i := range len(s) {
if b := s[i]; b == toEscape || b == '\\' {
v.b.WriteString(s[last:i])
v.b.WriteBytes('\\', b)
last = i + 1
}
}
v.b.WriteString(s[last:])
}
func (v *zmkVisitor) writePrefixSpaces() {
if prefixLen := len(v.prefix); prefixLen > 0 {
for i := 0; i <= prefixLen; i++ {
v.b.WriteSpace()
}
}
}
|
Added internal/evaluator/block.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
//-----------------------------------------------------------------------------
// Copyright (c) 2021-present Detlef Stern
//
// This file is part of Zettelstore.
//
// Zettelstore is licensed under the latest version of the EUPL (European Union
// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2021-present Detlef Stern
//-----------------------------------------------------------------------------
// Package evaluator interprets and evaluates the AST.
package evaluator
import (
"errors"
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/id"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/box"
"zettelstore.de/z/internal/parser"
"zettelstore.de/z/internal/query"
"zettelstore.de/z/internal/zettel"
)
func (e *evaluator) evalVerbatimEval(node *sx.Pair) *sx.Pair {
_, attrs, content := zsx.GetVerbatim(node)
if p := attrs.Assoc(sx.MakeString("")); p != nil {
if s, isString := sx.GetString(p.Cdr()); isString && s.GetValue() == meta.ValueSyntaxDraw {
return parser.ParseDrawBlock(attrs, []byte(content))
}
}
return node
}
func (e *evaluator) evalVerbatimZettel(vn *sx.Pair) *sx.Pair {
_, attrs, content := zsx.GetVerbatim(vn)
m := meta.New(id.Invalid)
m.Set(meta.KeySyntax, getSyntax(attrs, meta.ValueSyntaxText))
zettel := zettel.Zettel{
Meta: m,
Content: zettel.NewContent([]byte(content)),
}
e.transcludeCount++
zn := e.evaluateEmbeddedZettel(zettel)
return splicedBlocks(zn.Blocks)
}
func (e *evaluator) evalTransclusion(tn *sx.Pair) *sx.Pair {
attrs, ref, text := zsx.GetTransclusion(tn)
refSym, refVal := zsx.GetReference(ref)
// To prevent e.embedCount from counting
if errText := e.checkMaxTransclusions(ref); errText != nil {
return makeBlock(errText)
}
if !sz.SymRefStateZettel.IsEqualSymbol(refSym) {
switch refSym {
case zsx.SymRefStateInvalid, sz.SymRefStateBroken:
e.transcludeCount++
return makeBlock(createInlineErrorText(ref, "Invalid or broken transclusion reference"))
case zsx.SymRefStateSelf:
e.transcludeCount++
return makeBlock(createInlineErrorText(ref, "Self transclusion reference"))
case sz.SymRefStateFound, zsx.SymRefStateExternal:
return tn
case zsx.SymRefStateHosted, sz.SymRefStateBased:
return makeBlock(e.evalEmbed(zsx.MakeEmbed(attrs, ref, "", text)))
case sz.SymRefStateQuery:
e.transcludeCount++
return e.evalQueryTransclusion(refVal)
default:
return makeBlock(createInlineErrorText(ref, "Illegal reference symvol "+refSym.GetValue()))
}
}
zid := mustParseZid(ref, refVal)
cost, ok := e.costMap[zid]
zn := cost.zn
if zn == e.marker {
e.transcludeCount++
return makeBlock(createInlineErrorText(ref, "Recursive transclusion"))
}
if !ok {
zettel, err1 := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid)
if err1 != nil {
if errors.Is(err1, &box.ErrNotAllowed{}) {
return nil
}
e.transcludeCount++
return makeBlock(createInlineErrorText(ref, "Unable to get zettel"))
}
setMetadataFromAttributes(zettel.Meta, attrs)
ec := e.transcludeCount
e.costMap[zid] = transcludeCost{zn: e.marker, ec: ec}
zn = e.evaluateEmbeddedZettel(zettel)
e.costMap[zid] = transcludeCost{zn: zn, ec: e.transcludeCount - ec}
e.transcludeCount = 0 // No stack needed, because embedding is done left-recursive, depth-first.
}
e.transcludeCount++
if ec := cost.ec; ec > 0 {
e.transcludeCount += cost.ec
}
return splicedBlocks(zn.Blocks)
}
func (e *evaluator) evalQueryTransclusion(expr string) *sx.Pair {
q := query.Parse(expr)
ml, err := e.port.QueryMeta(e.ctx, q)
if err != nil {
if errors.Is(err, &box.ErrNotAllowed{}) {
return nil
}
return makeBlock(createInlineErrorText(nil, "Unable to search zettel"))
}
result, _ := QueryAction(e.ctx, q, ml)
if result != nil {
result = mustPair(zsx.Walk(e, result, nil))
}
return result
}
func makeBlock(inl *sx.Pair) *sx.Pair { return zsx.MakePara(inl) }
func splicedBlocks(block *sx.Pair) *sx.Pair {
blocks := zsx.GetBlock(block)
if blocks.Tail() == nil {
return blocks.Head()
}
return blocks.Cons(zsx.SymSpecialSplice)
}
|
Changes to internal/evaluator/evaluator.go.
| ︙ | ︙ | |||
11 12 13 14 15 16 17 | // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- // Package evaluator interprets and evaluates the AST. package evaluator import ( | < < < < < | | | | < | | | | < < < < < < < < < < < < < < < < < < < < < < < < < < | | | | < | | | < < < < < < < < < < < < < < < < | > > > > > | > > > > | | | | > > < < < < < | | < < < | > > > | | < < < < < | > > > | | | | < < | < < | < | | | < < < < < > > > > > | > | < > > > > | | | < < < | < < | < < < | < | | > > | > | | < < > < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | < < < < < < < < < < < < < < < < < < < < | < < | < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < | < < < < < < < < | < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < | < < < < < | < < < < < < < < | < < < < < < < < < < < < < < < < | < < < | < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
// SPDX-FileCopyrightText: 2021-present Detlef Stern
//-----------------------------------------------------------------------------
// Package evaluator interprets and evaluates the AST.
package evaluator
import (
"context"
"fmt"
"strconv"
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/id"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/ast"
"zettelstore.de/z/internal/config"
"zettelstore.de/z/internal/parser"
"zettelstore.de/z/internal/query"
"zettelstore.de/z/internal/zettel"
)
// Port contains all methods to retrieve zettel (or part of it) to evaluate a zettel.
type Port interface {
GetZettel(context.Context, id.Zid) (zettel.Zettel, error)
QueryMeta(ctx context.Context, q *query.Query) ([]*meta.Meta, error)
}
// EvaluateZettel evaluates the given zettel in the given context, with the
// given ports, and the given environment.
func EvaluateZettel(ctx context.Context, port Port, rtConfig config.Config, zn *ast.Zettel) {
switch zn.Syntax {
case meta.ValueSyntaxNone:
// AST is empty, evaluate to a description list of metadata.
zn.Blocks = evaluateMetadata(zn.Meta)
case meta.ValueSyntaxSxn:
zn.Blocks = evaluateSxn(zn.Blocks)
default:
zn.Blocks = EvaluateBlock(ctx, port, rtConfig, zn.Blocks)
}
}
// EvaluateBlock evaluates the given block list in the given context, with
// the given ports, and the given environment.
func EvaluateBlock(ctx context.Context, port Port, rtConfig config.Config, block *sx.Pair) *sx.Pair {
e := evaluator{
ctx: ctx,
port: port,
rtConfig: rtConfig,
transcludeMax: rtConfig.GetMaxTransclusions(),
transcludeCount: 0,
costMap: map[id.Zid]transcludeCost{},
embedMap: map[string]*sx.Pair{},
marker: &ast.Zettel{},
}
return mustPair(zsx.Walk(&e, block, nil))
}
type evaluator struct {
ctx context.Context
port Port
rtConfig config.Config
transcludeMax int
transcludeCount int
costMap map[id.Zid]transcludeCost
marker *ast.Zettel
embedMap map[string]*sx.Pair
}
type transcludeCost struct {
zn *ast.Zettel
ec int
}
func (e *evaluator) VisitBefore(_ *sx.Pair, _ *sx.Pair) (sx.Object, bool) {
return sx.Nil(), false
}
func (e *evaluator) VisitAfter(node *sx.Pair, _ *sx.Pair) sx.Object {
if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
switch sym {
case zsx.SymLink:
return e.evalLink(node)
case zsx.SymEmbed:
return e.evalEmbed(node)
case zsx.SymVerbatimEval:
return e.evalVerbatimEval(node)
case zsx.SymTransclude:
return e.evalTransclusion(node)
case zsx.SymVerbatimZettel:
return e.evalVerbatimZettel(node)
}
}
return node
}
func (e *evaluator) evaluateEmbeddedZettel(zettel zettel.Zettel) *ast.Zettel {
zn := parser.ParseZettel(e.ctx, zettel, string(zettel.Meta.GetDefault(meta.KeySyntax, meta.DefaultSyntax)), e.rtConfig)
parser.Clean(zn.Blocks)
zn.Blocks = mustPair(zsx.Walk(e, zn.Blocks, nil))
return zn
}
func setMetadataFromAttributes(m *meta.Meta, attrs *sx.Pair) {
for obj := range attrs.Values() {
if pair, isPair := sx.GetPair(obj); isPair {
if key, isKey := sx.GetString(pair.Car()); isKey && meta.KeyIsValid(key.GetValue()) {
if val, isVal := sx.GetString(pair.Cdr()); isVal {
m.Set(key.GetValue(), meta.Value(val.GetValue()))
}
}
}
}
}
func mustPair(obj sx.Object) *sx.Pair {
p, isPair := sx.GetPair(obj)
if !isPair {
panic(fmt.Sprintf("not a pair after evaluate: %T/%v", obj, obj))
}
return p
}
func mustParseZid(ref *sx.Pair, refVal string) id.Zid {
baseVal, _ := sz.SplitFragment(refVal)
zid, err := id.Parse(baseVal)
if err == nil {
return zid
}
refState, _ := zsx.GetReference(ref)
panic(fmt.Sprintf("%v: %q (state %v) -> %v", err, refVal, refState, ref))
}
func getSyntax(attrs *sx.Pair, defSyntax meta.Value) meta.Value {
for a := range attrs.Values() {
if pair, isPair := sx.GetPair(a); isPair {
car := pair.Car()
if car.IsEqual(sx.MakeString(meta.KeySyntax)) || car.IsEqual(sx.MakeString("")) {
if val, isString := sx.GetString(pair.Cdr()); isString {
return meta.Value(val.GetValue())
}
}
}
}
return defSyntax
}
func (e *evaluator) checkMaxTransclusions(ref *sx.Pair) *sx.Pair {
if maxTrans := e.transcludeMax; e.transcludeCount > maxTrans {
e.transcludeCount = maxTrans + 1
return createInlineErrorText(ref,
"Too many transclusions (must be at most "+strconv.Itoa(maxTrans)+
", see runtime configuration key max-transclusions)")
}
return nil
}
func createInlineErrorText(ref *sx.Pair, message string) *sx.Pair {
text := message
if ref != nil {
text += ": " + sz.ReferenceString(ref) + "."
}
ln := zsx.MakeLiteral(zsx.SymLiteralOutput, nil, text)
fn := zsx.MakeFormat(zsx.SymFormatStrong,
sx.MakeList(sx.Cons(sx.MakeString("class"), sx.MakeString("error"))),
sx.MakeList(ln))
return fn
}
func createInlineErrorImage(attrs *sx.Pair, text *sx.Pair) *sx.Pair {
ref := sz.ScanReference(id.ZidEmoji.String())
if text == nil {
text = sx.MakeList(zsx.MakeText("Error placeholder"))
}
return zsx.MakeEmbed(attrs, ref, "", text)
}
|
Added internal/evaluator/inline.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
//-----------------------------------------------------------------------------
// Copyright (c) 2021-present Detlef Stern
//
// This file is part of Zettelstore.
//
// Zettelstore is licensed under the latest version of the EUPL (European Union
// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2021-present Detlef Stern
//-----------------------------------------------------------------------------
// Package evaluator interprets and evaluates the AST.
package evaluator
import (
"errors"
"path"
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/box"
"zettelstore.de/z/internal/parser"
)
func (e *evaluator) evalLink(node *sx.Pair) *sx.Pair {
attrs, ref, inlines := zsx.GetLink(node)
refState, refVal := zsx.GetReference(ref)
newInlines := inlines
if inlines == nil {
newInlines = sx.MakeList(zsx.MakeText(refVal))
}
if !sz.SymRefStateZettel.IsEqualSymbol(refState) {
if newInlines != inlines {
return zsx.MakeLink(attrs, ref, newInlines)
}
return node
}
zid := mustParseZid(ref, refVal)
_, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid)
if errors.Is(err, &box.ErrNotAllowed{}) {
return zsx.MakeFormat(zsx.SymFormatSpan, attrs, newInlines)
}
if err != nil {
return zsx.MakeLink(attrs, zsx.MakeReference(sz.SymRefStateBroken, refVal), newInlines)
}
if newInlines != inlines {
return zsx.MakeLink(attrs, ref, newInlines)
}
return node
}
func (e *evaluator) evalEmbed(en *sx.Pair) *sx.Pair {
attrs, ref, _, inlines := zsx.GetEmbed(en)
refSym, refVal := zsx.GetReference(ref)
// To prevent e.embedCount from counting
if errText := e.checkMaxTransclusions(ref); errText != nil {
return errText
}
if !sz.SymRefStateZettel.IsEqualSymbol(refSym) {
switch refSym {
case zsx.SymRefStateInvalid, sz.SymRefStateBroken:
e.transcludeCount++
return createInlineErrorImage(attrs, inlines)
case zsx.SymRefStateSelf:
e.transcludeCount++
return createInlineErrorText(ref, "Self embed reference")
case sz.SymRefStateFound, zsx.SymRefStateExternal:
return en
case zsx.SymRefStateHosted, sz.SymRefStateBased:
if n := createLocalEmbedded(attrs, ref, refVal, inlines); n != nil {
return n
}
return en
case sz.SymRefStateQuery:
return createInlineErrorText(ref, "Query reference not allowed here")
default:
return createInlineErrorText(ref, "Illegal inline state "+refSym.GetValue())
}
}
zid := mustParseZid(ref, refVal)
zettel, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid)
if err != nil {
if errors.Is(err, &box.ErrNotAllowed{}) {
return nil
}
e.transcludeCount++
return createInlineErrorImage(attrs, inlines)
}
if syntax := string(zettel.Meta.GetDefault(meta.KeySyntax, meta.DefaultSyntax)); parser.IsImageFormat(syntax) {
return e.updateImageRefNode(attrs, ref, inlines, zettel.Meta, syntax)
} else if !parser.IsASTParser(syntax) {
// Not embeddable.
e.transcludeCount++
return createInlineErrorText(ref, "Not embeddable (syntax="+syntax+")")
}
cost, ok := e.costMap[zid]
zn := cost.zn
if zn == e.marker {
e.transcludeCount++
return createInlineErrorText(ref, "Recursive transclusion")
}
if !ok {
ec := e.transcludeCount
e.costMap[zid] = transcludeCost{zn: e.marker, ec: ec}
zn = e.evaluateEmbeddedZettel(zettel)
e.costMap[zid] = transcludeCost{zn: zn, ec: e.transcludeCount - ec}
e.transcludeCount = 0 // No stack needed, because embedding is done left-recursive, depth-first.
}
e.transcludeCount++
result, ok := e.embedMap[refVal]
if !ok {
// Search for text to be embedded.
_, fragment := sz.SplitFragment(refVal)
blocks := zsx.GetBlock(zn.Blocks)
if fragment == "" {
result = firstInlinesToEmbed(blocks)
} else {
result = findFragmentInBlocks(blocks, fragment)
}
e.embedMap[refVal] = result
}
if result == nil {
return zsx.MakeLiteral(zsx.SymLiteralComment,
sx.MakeList(sx.Cons(sx.MakeString(zsx.DefaultAttribute), sx.MakeString(""))),
"Nothing to transclude: "+sz.ReferenceString(ref),
)
}
if ec := cost.ec; ec > 0 {
e.transcludeCount += cost.ec
}
if result.Tail() == nil {
return result.Head()
}
return result.Cons(zsx.SymSpecialSplice)
}
func (e *evaluator) updateImageRefNode(
attrs *sx.Pair, ref *sx.Pair, inlines *sx.Pair, m *meta.Meta, syntax string,
) *sx.Pair {
if inlines != nil {
if is := parser.ParseDescription(m); is != nil {
if is = mustPair(zsx.Walk(e, is, nil)); is != nil {
inlines = is
}
}
}
return zsx.MakeEmbed(attrs, ref, syntax, inlines)
}
func findFragmentInBlocks(blocks *sx.Pair, fragment string) *sx.Pair {
fs := fragmentSearcher{fragment: fragment}
zsx.WalkItList(&fs, blocks, 0, nil)
return fs.result
}
type fragmentSearcher struct {
result *sx.Pair
fragment string
}
func (fs *fragmentSearcher) VisitItBefore(node *sx.Pair, alst *sx.Pair) bool {
if fs.result != nil {
return true
}
if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
switch sym {
case zsx.SymHeading:
_, _, _, _, frag := zsx.GetHeading(node)
if frag == fs.fragment {
bn := zsx.GetWalkList(alst)
fs.result = firstInlinesToEmbed(bn.Tail())
return true
}
case zsx.SymMark:
_, _, frag, inlines := zsx.GetMark(node)
if frag == fs.fragment {
next := zsx.GetWalkList(alst).Tail()
for ; next != nil; next = next.Tail() {
car := next.Head().Car()
if !zsx.SymSoft.IsEqual(car) && !zsx.SymHard.IsEqual(car) {
break
}
}
if next == nil { // Mark is last in inline list
fs.result = inlines
return true
}
var lb sx.ListBuilder
if inlines != nil {
lb.Collect(inlines.Values())
}
lb.Collect(next.Values())
fs.result = lb.List()
return true
}
}
}
return false
}
func (fs *fragmentSearcher) VisitItAfter(*sx.Pair, *sx.Pair) {}
func firstInlinesToEmbed(blocks *sx.Pair) *sx.Pair {
if blocks != nil {
if ins := firstParagraphInlines(blocks); ins != nil {
return ins
}
blk := blocks.Head()
if sym, isSymbol := sx.GetSymbol(blk.Car()); isSymbol && zsx.SymBLOB.IsEqualSymbol(sym) {
attrs, syntax, content, inlines := zsx.GetBLOBuncode(blk)
return sx.MakeList(zsx.MakeEmbedBLOBuncode(attrs, syntax, content, inlines))
}
}
return nil
}
// firstParagraphInlines returns the inline list of the first paragraph that
// contains a inline list.
func firstParagraphInlines(blocks *sx.Pair) *sx.Pair {
for blockObj := range blocks.Values() {
if block, isPair := sx.GetPair(blockObj); isPair {
if sym, isSymbol := sx.GetSymbol(block.Car()); isSymbol && zsx.SymPara.IsEqualSymbol(sym) {
if inlines := zsx.GetPara(block); inlines != nil {
return inlines
}
}
}
}
return nil
}
func createLocalEmbedded(attrs *sx.Pair, ref *sx.Pair, refValue string, inlines *sx.Pair) *sx.Pair {
ext := path.Ext(refValue)
if ext != "" && ext[0] == '.' {
ext = ext[1:]
}
pinfo := parser.Get(ext)
if pinfo == nil || !pinfo.IsImageFormat {
return nil
}
return zsx.MakeEmbed(attrs, ref, ext, inlines)
}
|
Changes to internal/evaluator/list.go.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 | "bytes" "context" "math" "slices" "strconv" "strings" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx" | > > < | | | | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
"bytes"
"context"
"math"
"slices"
"strconv"
"strings"
"t73f.de/r/sx"
"t73f.de/r/zsc/api"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/query"
)
// QueryAction transforms a list of metadata according to query actions into a SZ nested list.
func QueryAction(ctx context.Context, q *query.Query, ml []*meta.Meta) (*sx.Pair, int) {
ap := actionPara{
ctx: ctx,
q: q,
ml: ml,
kind: zsx.SymListUnordered,
minVal: -1,
maxVal: -1,
}
actions := q.Actions()
if len(actions) == 0 {
return ap.createBlockNodeMeta("")
}
acts := make([]string, 0, len(actions))
for _, act := range actions {
if strings.HasPrefix(act, api.NumberedAction[0:1]) {
ap.kind = zsx.SymListOrdered
continue
}
if strings.HasPrefix(act, api.MinAction) {
if num, err := strconv.Atoi(act[3:]); err == nil && num > 0 {
ap.minVal = num
continue
}
|
| ︙ | ︙ | |||
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
case meta.TypeTagSet:
return ap.createBlockNodeTagSet(key)
}
if firstUnknowAct == "" {
firstUnknowAct = act
}
}
bn, numItems := ap.createBlockNodeMeta(strings.ToLower(firstUnknowAct))
if bn != nil && numItems == 0 && firstUnknowAct == strings.ToUpper(firstUnknowAct) {
bn, numItems = ap.createBlockNodeMeta("")
}
return bn, numItems
}
type actionPara struct {
ctx context.Context
q *query.Query
ml []*meta.Meta
| > | | | > > < > | | | | > < | < < < | | < > > | < > | | | < | < < < | | | < > | | 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
case meta.TypeTagSet:
return ap.createBlockNodeTagSet(key)
}
if firstUnknowAct == "" {
firstUnknowAct = act
}
}
bn, numItems := ap.createBlockNodeMeta(strings.ToLower(firstUnknowAct))
if bn != nil && numItems == 0 && firstUnknowAct == strings.ToUpper(firstUnknowAct) {
bn, numItems = ap.createBlockNodeMeta("")
}
return bn, numItems
}
type actionPara struct {
ctx context.Context
q *query.Query
ml []*meta.Meta
kind *sx.Symbol
minVal int
maxVal int
}
func (ap *actionPara) createBlockNodeWord(key string) (*sx.Pair, int) {
var buf bytes.Buffer
ccs, bufLen := ap.prepareCatAction(key, &buf)
if len(ccs) == 0 {
return nil, 0
}
ccs.SortByName()
var items sx.ListBuilder
count := 0
for _, cat := range ccs {
buf.WriteString(string(cat.Name))
items.Add(zsx.MakeBlock(zsx.MakePara(
zsx.MakeLink(nil,
sz.ScanReference(buf.String()),
sx.MakeList(zsx.MakeText(string(cat.Name)))),
)))
count++
buf.Truncate(bufLen)
}
return zsx.MakeList(ap.kind, nil, items.List()), count
}
func (ap *actionPara) createBlockNodeTagSet(key string) (*sx.Pair, int) {
var buf bytes.Buffer
ccs, bufLen := ap.prepareCatAction(key, &buf)
if len(ccs) == 0 {
return nil, 0
}
ccs.SortByCount()
ccs = ap.limitTags(ccs)
countClassAttrs := ap.calcFontSizes(ccs)
ccs.SortByName()
var tags sx.ListBuilder
count := 0
for i, cat := range ccs {
if i > 0 {
tags.Add(zsx.MakeText(" "))
}
buf.WriteString(string(cat.Name))
tags.AddN(
zsx.MakeLink(
countClassAttrs[cat.Count],
sz.ScanReference(buf.String()),
sx.MakeList(zsx.MakeText(string(cat.Name)))),
zsx.MakeFormat(zsx.SymFormatSuper,
nil,
sx.MakeList(zsx.MakeText(strconv.Itoa(cat.Count)))),
)
count++
buf.Truncate(bufLen)
}
return zsx.MakeParaList(tags.List()), count
}
func (ap *actionPara) limitTags(ccs meta.CountedCategories) meta.CountedCategories {
if minVal, maxVal := ap.minVal, ap.maxVal; minVal > 0 || maxVal > 0 {
if minVal < 0 {
minVal = ccs[len(ccs)-1].Count
}
|
| ︙ | ︙ | |||
177 178 179 180 181 182 183 | } return temp } } return ccs } | | | > | | < | | < | | | < | | < | | > < | < < < | | > | | | | | > < | < < < | 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
}
return temp
}
}
return ccs
}
func (ap *actionPara) createBlockNodeMetaKeys() (*sx.Pair, int) {
arr := make(meta.Arrangement, 128)
for _, m := range ap.ml {
for k := range m.Map() {
arr[k] = append(arr[k], m)
}
}
if len(arr) == 0 {
return nil, 0
}
ccs := arr.Counted()
ccs.SortByName()
var buf bytes.Buffer
bufLen := ap.prepareSimpleQuery(&buf)
var items sx.ListBuilder
count := 0
for _, cat := range ccs {
buf.WriteString(string(cat.Name))
buf.WriteString(api.ExistOperator)
q1 := buf.String()
buf.Truncate(bufLen)
buf.WriteString(api.ActionSeparator)
buf.WriteString(string(cat.Name))
q2 := buf.String()
buf.Truncate(bufLen)
items.Add(zsx.MakeBlock(zsx.MakePara(
zsx.MakeLink(nil,
sz.ScanReference(q1),
sx.MakeList(zsx.MakeText(string(cat.Name)))),
zsx.MakeText(" "),
zsx.MakeText("("+strconv.Itoa(cat.Count)+", "),
zsx.MakeLink(nil,
sz.ScanReference(q2),
sx.MakeList(zsx.MakeText("values"))),
zsx.MakeText(")"),
)))
count++
}
return zsx.MakeList(ap.kind, nil, items.List()), count
}
func (ap *actionPara) createBlockNodeMeta(key string) (*sx.Pair, int) {
if len(ap.ml) == 0 {
return nil, 0
}
var items sx.ListBuilder
count := 0
for _, m := range ap.ml {
if key != "" {
if _, found := m.Get(key); !found {
continue
}
}
items.Add(zsx.MakeBlock(zsx.MakePara(
zsx.MakeLink(nil,
sz.ScanReference(m.Zid.String()),
sx.MakeList(zsx.MakeText(sz.NormalizedSpacedText(m.GetTitle())))),
)))
count++
}
return zsx.MakeList(ap.kind, nil, items.List()), count
}
func (ap *actionPara) prepareCatAction(key string, buf *bytes.Buffer) (meta.CountedCategories, int) {
if len(ap.ml) == 0 {
return nil, 0
}
ccs := meta.CreateArrangement(ap.ml, key).Counted()
|
| ︙ | ︙ | |||
270 271 272 273 274 275 276 |
return ccs, bufLen
}
func (ap *actionPara) prepareSimpleQuery(buf *bytes.Buffer) int {
sea := ap.q.Clone()
sea.RemoveActions()
| | | | | < | | | 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
return ccs, bufLen
}
func (ap *actionPara) prepareSimpleQuery(buf *bytes.Buffer) int {
sea := ap.q.Clone()
sea.RemoveActions()
buf.WriteString(api.QueryPrefix)
sea.Print(buf)
if buf.Len() > len(api.QueryPrefix) {
buf.WriteByte(' ')
}
return buf.Len()
}
const fontSizes = 6 // Must be the number of CSS classes zs-font-size-* in base.css
const fontSizes64 = float64(fontSizes)
func (*actionPara) calcFontSizes(ccs meta.CountedCategories) map[int]*sx.Pair {
var fsAttrs [fontSizes]*sx.Pair
for i := range fontSizes {
fsAttrs[i] = sx.MakeList(sx.Cons(sx.MakeString("class"), sx.MakeString("zs-font-size-"+strconv.Itoa(i))))
}
countMap := make(map[int]int, len(ccs))
for _, cat := range ccs {
countMap[cat.Count]++
}
countList := make([]int, 0, len(countMap))
for count := range countMap {
countList = append(countList, count)
}
slices.Sort(countList)
result := make(map[int]*sx.Pair, len(countList))
if len(countList) <= fontSizes {
// If we have less different counts, center them inside the fsAttrs vector.
curSize := (fontSizes - len(countList)) / 2
for _, count := range countList {
result[count] = fsAttrs[curSize]
curSize++
}
|
| ︙ | ︙ |
Changes to internal/evaluator/metadata.go.
| ︙ | ︙ | |||
10 11 12 13 14 15 16 17 | // SPDX-License-Identifier: EUPL-1.2 // SPDX-FileCopyrightText: 2021-present Detlef Stern //----------------------------------------------------------------------------- package evaluator import ( "t73f.de/r/zsc/domain/meta" | > > > > | | | > | < < < < < | < | < < < > > | | | < < < | > | | | | > > | < | < < | > | | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2021-present Detlef Stern
//-----------------------------------------------------------------------------
package evaluator
import (
"iter"
"t73f.de/r/sx"
zeroiter "t73f.de/r/zero/iter"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
)
func evaluateMetadata(m *meta.Meta) *sx.Pair {
var lb sx.ListBuilder
lb.AddN(zsx.SymDescription, nil)
for key, val := range m.All() {
lb.Add(sx.MakeList(zsx.MakeText(key)))
inlines := convertMetavalueToInlineSlice(val, meta.Type(key))
lb.Add(zsx.MakeBlock(zsx.MakeBlock(zsx.MakeParaList(inlines))))
}
return zsx.MakeBlock(lb.List())
}
func convertMetavalueToInlineSlice(val meta.Value, dt *meta.DescriptionType) *sx.Pair {
var vals iter.Seq[string]
if dt.IsSet {
vals = val.Fields()
} else {
vals = zeroiter.OneSeq(string(val))
}
makeLink := dt == meta.TypeID || dt == meta.TypeIDSet
var lb sx.ListBuilder
first := true
for s := range vals {
if first {
first = false
} else {
lb.Add(zsx.MakeText(" "))
}
tn := zsx.MakeText(s)
if makeLink {
lb.Add(zsx.MakeLink(nil, sz.ScanReference(s), sx.MakeList(tn)))
} else {
lb.Add(tn)
}
}
return lb.List()
}
|
Added internal/evaluator/sxn.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
//-----------------------------------------------------------------------------
// Copyright (c) 2021-present Detlef Stern
//
// This file is part of Zettelstore.
//
// Zettelstore is licensed under the latest version of the EUPL (European Union
// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2021-present Detlef Stern
//-----------------------------------------------------------------------------
package evaluator
import (
"strings"
"t73f.de/r/sx"
"t73f.de/r/sx/sxbuiltins"
"t73f.de/r/sx/sxreader"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx"
)
func evaluateSxn(block *sx.Pair) *sx.Pair {
if blocks := zsx.GetBlock(block); blocks != nil {
blk := blocks.Head()
if sym, isSymbol := sx.GetSymbol(blk.Car()); isSymbol && zsx.SymVerbatimCode.IsEqualSymbol(sym) {
_, attrs, content := zsx.GetVerbatim(blk)
if classAttr, hasClass := zsx.GetAttributes(attrs).Get(""); hasClass && classAttr == meta.ValueSyntaxSxn {
rd := sxreader.MakeReader(strings.NewReader(content))
if objs, err := rd.ReadAll(); err == nil {
var lb sx.ListBuilder
for _, obj := range objs {
var sb strings.Builder
_, _ = sxbuiltins.Print(&sb, obj)
lb.Add(zsx.MakeVerbatim(zsx.SymVerbatimCode, attrs, sb.String()))
}
return zsx.MakeBlockList(lb.List())
}
}
}
}
return block
}
|
Changes to internal/parser/blob.go.
| ︙ | ︙ | |||
53 54 55 56 57 58 59 | IsASTParser: false, IsTextFormat: false, IsImageFormat: true, Parse: parseBlob, }) } | | | | 53 54 55 56 57 58 59 60 61 62 63 64 65 |
IsASTParser: false,
IsTextFormat: false,
IsImageFormat: true,
Parse: parseBlob,
})
}
func parseBlob(inp *input.Input, m *meta.Meta, syntax string, _ *sx.Pair) *sx.Pair {
if p := Get(syntax); p != nil {
syntax = p.Name
}
return zsx.MakeBlock(zsx.MakeBLOB(nil, syntax, inp.Src, ParseDescription(m)))
}
|
Changes to internal/parser/cleaner.go.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 22 23 | // cleaner provides functions to clean up the parsed AST. import ( "strconv" "strings" zerostrings "t73f.de/r/zero/strings" | > > < | | | < < < < | | < > | | < | < | < | > | | < < | < < < > > > < < | | | > | > | | < | > | | | < < | | < < < < < < < | < < < < < < < < | < < < < < < < < < | < < < < < | < | < < | < | < < > | < | | > | > < < | | < | | | | | < < < | | < < < < > | < | | < < < | < < < < | | | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
// cleaner provides functions to clean up the parsed AST.
import (
"strconv"
"strings"
"t73f.de/r/sx"
zerostrings "t73f.de/r/zero/strings"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/encoder"
)
// Clean the given SZ syntax tree.
func Clean(node *sx.Pair) {
v1 := cleanPhase1{ids: idsNode{}}
zsx.WalkIt(&v1, node, nil)
if v1.hasMark {
v2 := cleanPhase2{ids: v1.ids}
zsx.WalkIt(&v2, node, nil)
}
}
type cleanPhase1 struct {
ids idsNode
hasMark bool // Mark nodes will be cleaned in phase 2 only
}
func (v *cleanPhase1) VisitItBefore(node *sx.Pair, _ *sx.Pair) bool {
if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
switch sym {
case zsx.SymHeading:
levelNode := node.Tail()
attrsNode := levelNode.Tail()
slugNode := attrsNode.Tail()
fragmentNode := slugNode.Tail()
textNode := fragmentNode.Tail()
var sb strings.Builder
var textEnc encoder.TextEncoder
if err := textEnc.WriteSz(&sb, textNode.Cons(zsx.SymPara)); err == nil {
slugText := zerostrings.Slugify(sb.String())
slugNode.SetCar(sx.MakeString(slugText))
fragmentNode.SetCar(sx.MakeString(v.ids.addIdentifier(slugText, node)))
}
case zsx.SymMark:
v.hasMark = true
}
}
return false
}
func (v *cleanPhase1) VisitItAfter(*sx.Pair, *sx.Pair) {}
type cleanPhase2 struct {
ids idsNode
}
func (v *cleanPhase2) VisitItBefore(node *sx.Pair, _ *sx.Pair) bool {
if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
switch sym {
case zsx.SymMark:
stringNode := node.Tail()
if markString, isString := sx.GetString(stringNode.Car()); isString {
slugNode := stringNode.Tail()
fragmentNode := slugNode.Tail()
slugText := zerostrings.Slugify(markString.GetValue())
slugNode.SetCar(sx.MakeString(slugText))
fragmentNode.SetCar(sx.MakeString(v.ids.addIdentifier(slugText, node)))
}
}
}
return false
}
func (v *cleanPhase2) VisitItAfter(*sx.Pair, *sx.Pair) {}
type idsNode map[string]*sx.Pair
func (ids idsNode) addIdentifier(id string, node *sx.Pair) string {
if n, ok := ids[id]; ok && n != node {
prefix := id + "-"
for count := 1; ; count++ {
newID := prefix + strconv.Itoa(count)
if n2, ok2 := ids[newID]; !ok2 || n2 == node {
ids[newID] = node
return newID
}
}
}
ids[id] = node
return id
}
|
Added internal/parser/cleaner_test.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
//-----------------------------------------------------------------------------
// Copyright (c) 2025-present Detlef Stern
//
// This file is part of Zettelstore.
//
// Zettelstore is licensed under the latest version of the EUPL (European Union
// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2025-present Detlef Stern
//-----------------------------------------------------------------------------
package parser_test
import (
"strings"
"testing"
"t73f.de/r/sx"
"t73f.de/r/sx/sxreader"
"zettelstore.de/z/internal/parser"
)
func TestCleaner(t *testing.T) {
var testcases = []struct {
name string
src string
exp string
}{
{name: "nil", src: "()", exp: "()"},
{name: "simple heading",
src: "(HEADING 1 () \"\" \"\" (TEXT \"Heading\"))",
exp: "(HEADING 1 () \"heading\" \"heading\" (TEXT \"Heading\"))"},
{name: "same simple heading",
src: "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"Heading\")) (HEADING 1 () \"\" \"\" (TEXT \"Heading\")))",
exp: "(BLOCK (HEADING 1 () \"heading\" \"heading\" (TEXT \"Heading\")) (HEADING 1 () \"heading\" \"heading-1\" (TEXT \"Heading\")))"},
{name: "simple mark, no text",
src: "(MARK \"m\" \"\" \"\")",
exp: "(MARK \"m\" \"m\" \"m\")"},
{name: "same simple mark, no text",
src: "(PARA (MARK \"m\" \"\" \"\") (MARK \"m\" \"\" \"\"))",
exp: "(PARA (MARK \"m\" \"m\" \"m\") (MARK \"m\" \"m\" \"m-1\"))"},
{name: "mark before heading",
src: "(BLOCK (HEADING 1 () \"\" \"\" (TEXT \"x\")) (PARA (MARK \"x\" \"\" \"\")))",
exp: "(BLOCK (HEADING 1 () \"x\" \"x\" (TEXT \"x\")) (PARA (MARK \"x\" \"x\" \"x-1\")))"},
{name: "mark in mark with text",
src: `(MARK "m" "" "" (MARK "m" "" "" (TEXT "x")))`,
exp: `(MARK "m" "m" "m" (MARK "m" "m" "m-1" (TEXT "x")))`},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
rd := sxreader.MakeReader(strings.NewReader(tc.src))
obj, err := rd.Read()
if err != nil {
t.Error(err)
return
}
node, isPair := sx.GetPair(obj)
if !isPair {
t.Error("not a pair:", obj)
}
parser.Clean(node)
if got := node.String(); got != tc.exp {
t.Errorf("\nexpected: %q\n but got: %q", tc.exp, got)
}
})
}
}
|
Changes to internal/parser/draw.go.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 | "strconv" "t73f.de/r/sx" "t73f.de/r/webs/aasvg" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx" "t73f.de/r/zsx/input" | < < | | | > | | | | | | | | | | > > | | < | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
"strconv"
"t73f.de/r/sx"
"t73f.de/r/webs/aasvg"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx"
"t73f.de/r/zsx/input"
)
func init() {
register(&Info{
Name: meta.ValueSyntaxDraw,
AltNames: []string{},
IsASTParser: true,
IsTextFormat: true,
IsImageFormat: false,
Parse: parseDraw,
})
}
const (
defaultFont = ""
defaultScaleX = 10
defaultScaleY = 20
)
func parseDraw(inp *input.Input, m *meta.Meta, _ string, _ *sx.Pair) *sx.Pair {
font := m.GetDefault("font", defaultFont)
scaleX := m.GetNumber("x-scale", defaultScaleX)
scaleY := m.GetNumber("y-scale", defaultScaleY)
if scaleX < 1 || 1000000 < scaleX {
scaleX = defaultScaleX
}
if scaleY < 1 || 1000000 < scaleY {
scaleY = defaultScaleY
}
canvas, err := aasvg.NewCanvas(inp.Src[inp.Pos:])
if err != nil {
return zsx.MakeBlock(zsx.MakeParaList(canvasErrMsg(err)))
}
svg := aasvg.CanvasToSVG(canvas, string(font), int(scaleX), int(scaleY))
if len(svg) == 0 {
return zsx.MakeBlock(zsx.MakeParaList(noSVGErrMsg()))
}
return zsx.MakeBlock(zsx.MakeBLOB(nil, meta.ValueSyntaxSVG, svg, ParseDescription(m)))
}
// ParseDrawBlock parses the content of an eval verbatim node into an SVG image BLOB.
func ParseDrawBlock(attrs *sx.Pair, content []byte) *sx.Pair {
a := zsx.GetAttributes(attrs)
font := defaultFont
if val, found := a.Get("font"); found {
font = val
}
scaleX := getScaleAST(a, "x-scale", defaultScaleX)
scaleY := getScaleAST(a, "y-scale", defaultScaleY)
canvas, err := aasvg.NewCanvas(content)
if err != nil {
return zsx.MakePara(zsx.MakeText("Error: " + err.Error()))
}
if scaleX < 1 || 1000000 < scaleX {
scaleX = defaultScaleX
}
if scaleY < 1 || 1000000 < scaleY {
scaleY = defaultScaleY
}
svg := aasvg.CanvasToSVG(canvas, font, scaleX, scaleY)
if len(svg) == 0 {
return zsx.MakePara(zsx.MakeText("NO IMAGE"))
}
return zsx.MakeBLOB(
nil,
meta.ValueSyntaxSVG,
svg,
nil, // TODO: look for attribute "summary" / "title" for a description.
)
}
func getScaleAST(a zsx.Attributes, key string, defVal int) int {
if val, found := a.Get(key); found {
if n, err := strconv.Atoi(val); err == nil && 0 < n && n < 100000 {
return n
}
}
return defVal
}
|
| ︙ | ︙ |
Changes to internal/parser/draw_test.go.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 | import ( "testing" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" | < | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import (
"testing"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx/input"
"zettelstore.de/z/internal/parser"
)
func FuzzParseDraw(f *testing.F) {
f.Fuzz(func(t *testing.T, src []byte) {
t.Parallel()
inp := input.NewInput(src)
parser.Parse(inp, nil, meta.ValueSyntaxDraw, nil)
})
}
|
Changes to internal/parser/markdown.go.
| ︙ | ︙ | |||
22 23 24 25 26 27 28 | "strings" gm "github.com/yuin/goldmark" gmAst "github.com/yuin/goldmark/ast" gmText "github.com/yuin/goldmark/text" "t73f.de/r/sx" | < < < | < | | | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
"strings"
gm "github.com/yuin/goldmark"
gmAst "github.com/yuin/goldmark/ast"
gmText "github.com/yuin/goldmark/text"
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"t73f.de/r/zsx/input"
)
func init() {
register(&Info{
Name: meta.ValueSyntaxMarkdown,
AltNames: []string{meta.ValueSyntaxMD},
IsASTParser: true,
IsTextFormat: true,
IsImageFormat: false,
Parse: parseMarkdown,
})
}
func parseMarkdown(inp *input.Input, _ *meta.Meta, _ string, alst *sx.Pair) *sx.Pair {
source := []byte(inp.Src[inp.Pos:])
parser := gm.DefaultParser()
node := parser.Parse(gmText.NewReader(source))
p := mdP{source: source, docNode: node, allowHTML: alst.Assoc(SymAllowHTML) != nil}
return p.acceptBlockChildren(p.docNode)
}
type mdP struct {
source []byte
docNode gmAst.Node
allowHTML bool
}
func (p *mdP) acceptBlockChildren(docNode gmAst.Node) *sx.Pair {
if docNode.Type() != gmAst.TypeDocument {
panic(fmt.Sprintf("Expected document, but got node type %v", docNode.Type()))
}
var result sx.ListBuilder
|
| ︙ | ︙ | |||
175 176 177 178 179 180 181 |
return zsx.MakeList(kind, a.List(), items.List())
}
func (p *mdP) acceptItemSlice(node gmAst.Node) *sx.Pair {
var result sx.ListBuilder
for elem := node.FirstChild(); elem != nil; elem = elem.NextSibling() {
if item := p.acceptBlock(elem); item != nil {
| | | 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
return zsx.MakeList(kind, a.List(), items.List())
}
func (p *mdP) acceptItemSlice(node gmAst.Node) *sx.Pair {
var result sx.ListBuilder
for elem := node.FirstChild(); elem != nil; elem = elem.NextSibling() {
if item := p.acceptBlock(elem); item != nil {
result.Add(zsx.MakeBlock(item))
}
}
return result.List()
}
func (p *mdP) acceptTextBlock(node *gmAst.TextBlock) *sx.Pair {
if is := p.acceptInlineChildren(node); is != nil {
|
| ︙ | ︙ | |||
200 201 202 203 204 205 206 |
closure = closure[:l-1]
}
if len(content) > 1 {
content = append(content, '\n')
}
content = append(content, closure...)
}
| > | > > | 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
closure = closure[:l-1]
}
if len(content) > 1 {
content = append(content, '\n')
}
content = append(content, closure...)
}
if p.allowHTML {
return zsx.MakeVerbatim(zsx.SymVerbatimHTML, nil, string(content))
}
return zsx.MakeVerbatim(zsx.SymVerbatimCode, makeAttrHTML(), string(content))
}
func (p *mdP) acceptInlineChildren(node gmAst.Node) *sx.Pair {
var result sx.ListBuilder
for child := node.FirstChild(); child != nil; child = child.NextSibling() {
n1, n2 := p.acceptInline(child)
if n1 != nil {
|
| ︙ | ︙ | |||
365 366 367 368 369 370 371 |
func (p *mdP) acceptRawHTML(node *gmAst.RawHTML) (*sx.Pair, *sx.Pair) {
segs := make([][]byte, 0, node.Segments.Len())
for i := range node.Segments.Len() {
segment := node.Segments.At(i)
segs = append(segs, segment.Value(p.source))
}
| | > | > | < < | 364 365 366 367 368 369 370 371 372 373 374 375 376 |
func (p *mdP) acceptRawHTML(node *gmAst.RawHTML) (*sx.Pair, *sx.Pair) {
segs := make([][]byte, 0, node.Segments.Len())
for i := range node.Segments.Len() {
segment := node.Segments.At(i)
segs = append(segs, segment.Value(p.source))
}
return zsx.MakeLiteral(zsx.SymLiteralCode, makeAttrHTML(), string(bytes.Join(segs, nil))), nil
}
func makeAttrHTML() *sx.Pair {
return sx.Cons(sx.Cons(sx.MakeString(""), sx.MakeString("html")), sx.Nil())
}
|
Changes to internal/parser/none.go.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
//-----------------------------------------------------------------------------
package parser
import (
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx/input"
)
// none provides a none-parser, e.g. for zettel with just metadata.
func init() {
register(&Info{
Name: meta.ValueSyntaxNone,
AltNames: []string{},
IsASTParser: false,
IsTextFormat: false,
IsImageFormat: false,
| > | > > | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
//-----------------------------------------------------------------------------
package parser
import (
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx/input"
)
// none provides a none-parser, e.g. for zettel with just metadata.
func init() {
register(&Info{
Name: meta.ValueSyntaxNone,
AltNames: []string{},
IsASTParser: false,
IsTextFormat: false,
IsImageFormat: false,
Parse: func(inp *input.Input, _ *meta.Meta, _ string, _ *sx.Pair) *sx.Pair {
return sz.ParseNoneBlocks(inp)
},
})
}
|
Changes to internal/parser/parser.go.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 | // Package parser provides a generic interface to a range of different parsers. package parser import ( "context" "fmt" | < > < > > | | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
// Package parser provides a generic interface to a range of different parsers.
package parser
import (
"context"
"fmt"
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"t73f.de/r/zsx/input"
"zettelstore.de/z/internal/ast"
"zettelstore.de/z/internal/config"
"zettelstore.de/z/internal/zettel"
)
// Info describes a single parser.
//
// Before Parse() is called, ensure the input stream to be valid. This can be
// achieved on calling inp.Next() after the input stream was created.
type Info struct {
Name string
AltNames []string
IsASTParser bool
IsTextFormat bool
IsImageFormat bool
// Parse the input, with the given metadata, the given syntax, and the given config.
Parse func(*input.Input, *meta.Meta, string, *sx.Pair) *sx.Pair
}
var registry = map[string]*Info{}
// register the parser (info) for later retrieval.
func register(pi *Info) {
if _, ok := registry[pi.Name]; ok {
|
| ︙ | ︙ | |||
93 94 95 96 97 98 99 |
pi, ok := registry[syntax]
if !ok {
return false
}
return pi.IsImageFormat
}
| | | | < < < < | < | < < | < < < < < < < < < < < < < < > | | | > > > > > < < < < | | | | | | | | | 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
pi, ok := registry[syntax]
if !ok {
return false
}
return pi.IsImageFormat
}
// Parse parses some input and returns both a Sx.Object and a slice of block nodes.
func Parse(inp *input.Input, m *meta.Meta, syntax string, alst *sx.Pair) *sx.Pair {
return Get(syntax).Parse(inp, m, syntax, alst)
}
// SymAllowHTML signals a parser to allow HTML content during parsing.
var SymAllowHTML = sx.MakeSymbol("ALLOW-HTML")
// ParseDescription returns a suitable description stored in the metadata as an inline list.
// This is done for an image in most cases.
func ParseDescription(m *meta.Meta) *sx.Pair {
if m == nil {
return nil
}
if summary, found := m.Get(meta.KeySummary); found {
return sx.Cons(zsx.MakeText(sz.NormalizedSpacedText(string(summary))), sx.Nil())
}
if title, found := m.Get(meta.KeyTitle); found {
return sx.Cons(zsx.MakeText(sz.NormalizedSpacedText(string(title))), sx.Nil())
}
return sx.Cons(zsx.MakeText("Zettel without title/summary: "+m.Zid.String()), sx.Nil())
}
// ParseZettel parses the zettel based on the syntax.
func ParseZettel(ctx context.Context, zettel zettel.Zettel, syntax string, rtConfig config.Config) *ast.Zettel {
m := zettel.Meta
inhMeta := m
if rtConfig != nil {
inhMeta = rtConfig.AddDefaultValues(ctx, inhMeta)
}
if syntax == "" {
syntax = string(inhMeta.GetDefault(meta.KeySyntax, meta.DefaultSyntax))
}
var alst *sx.Pair
if rtConfig != nil && rtConfig.GetHTMLInsecurity().AllowHTML(syntax) {
alst = alst.Cons(sx.Cons(SymAllowHTML, nil))
}
parseMeta := inhMeta
if syntax == meta.ValueSyntaxNone {
parseMeta = m
}
rootNode := Parse(input.NewInput(zettel.Content.AsBytes()), parseMeta, syntax, alst)
return &ast.Zettel{
Meta: m,
Content: zettel.Content,
Zid: m.Zid,
InhMeta: inhMeta,
Blocks: rootNode,
Syntax: syntax,
}
}
|
Changes to internal/parser/plain.go.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import (
"bytes"
"t73f.de/r/sx"
"t73f.de/r/sx/sxreader"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx"
"t73f.de/r/zsx/input"
)
func init() {
register(&Info{
Name: meta.ValueSyntaxTxt,
AltNames: []string{meta.ValueSyntaxPlain, meta.ValueSyntaxText},
IsASTParser: false,
IsTextFormat: true,
IsImageFormat: false,
Parse: parsePlain,
})
register(&Info{
Name: meta.ValueSyntaxHTML,
AltNames: []string{},
IsASTParser: false,
IsTextFormat: true,
IsImageFormat: false,
| > | | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
import (
"bytes"
"t73f.de/r/sx"
"t73f.de/r/sx/sxreader"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"t73f.de/r/zsx/input"
)
func init() {
register(&Info{
Name: meta.ValueSyntaxTxt,
AltNames: []string{meta.ValueSyntaxPlain, meta.ValueSyntaxText},
IsASTParser: false,
IsTextFormat: true,
IsImageFormat: false,
Parse: parsePlain,
})
register(&Info{
Name: meta.ValueSyntaxHTML,
AltNames: []string{},
IsASTParser: false,
IsTextFormat: true,
IsImageFormat: false,
Parse: parsePlain,
})
register(&Info{
Name: meta.ValueSyntaxCSS,
AltNames: []string{},
IsASTParser: false,
IsTextFormat: true,
IsImageFormat: false,
|
| ︙ | ︙ | |||
64 65 66 67 68 69 70 | IsASTParser: false, IsTextFormat: true, IsImageFormat: false, Parse: parsePlainSxn, }) } | | | > > | < | | > > > > > | > | < < < > | | | | | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
IsASTParser: false,
IsTextFormat: true,
IsImageFormat: false,
Parse: parsePlainSxn,
})
}
func parsePlain(inp *input.Input, _ *meta.Meta, syntax string, alst *sx.Pair) *sx.Pair {
result := sz.ParsePlainBlocks(inp, syntax)
if syntax == meta.ValueSyntaxHTML && alst.Assoc(SymAllowHTML) == nil {
zsx.WalkIt(removeHTMLVisitor{}, result, nil)
}
return result
}
type removeHTMLVisitor struct{}
func (removeHTMLVisitor) VisitItBefore(node *sx.Pair, _ *sx.Pair) bool {
if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol && zsx.SymVerbatimHTML.IsEqualSymbol(sym) {
node.SetCar(zsx.SymVerbatimCode)
return true
}
return false
}
func (removeHTMLVisitor) VisitItAfter(*sx.Pair, *sx.Pair) {}
func parsePlainSVG(inp *input.Input, _ *meta.Meta, syntax string, _ *sx.Pair) *sx.Pair {
is := parseSVGInlines(inp, syntax)
if is == nil {
return zsx.MakeBlock()
}
return zsx.MakeBlock(zsx.MakeParaList(is))
}
func parseSVGInlines(inp *input.Input, syntax string) *sx.Pair {
svgSrc := scanSVG(inp)
if svgSrc == "" {
return nil
}
return sx.Cons(zsx.MakeEmbedBLOBuncode(nil, syntax, svgSrc, nil), sx.Nil())
}
func scanSVG(inp *input.Input) string {
inp.SkipSpace()
pos := inp.Pos
if !inp.Accept("<svg") {
return ""
}
ch := inp.Ch
if input.IsSpace(ch) || input.IsEOLEOS(ch) || ch == '>' {
// TODO: check proper end </svg>
return string(inp.Src[pos:])
}
return ""
}
func parsePlainSxn(inp *input.Input, _ *meta.Meta, syntax string, _ *sx.Pair) *sx.Pair {
rd := sxreader.MakeReader(bytes.NewReader(inp.Src))
_, err := rd.ReadAll()
var blocks sx.ListBuilder
blocks.Add(zsx.MakeVerbatim(
zsx.SymVerbatimCode,
sx.Cons(sx.Cons(sx.MakeString(""), sx.MakeString(syntax)), sx.Nil()),
|
| ︙ | ︙ |
Changes to internal/parser/plain_test.go.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- package parser_test import ( "testing" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" | > < < | | | | > > > > > > > > > > > > > > > > > > > > > > > | > > | > > | > > | > | | > > > > > > > | < < | | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
//-----------------------------------------------------------------------------
package parser_test
import (
"testing"
"t73f.de/r/sx"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx/input"
"zettelstore.de/z/internal/parser"
)
func TestParsePlain(t *testing.T) {
testCases := []struct {
name string
syntax string
src string
allowHTML bool
exp string
}{
{name: "empty-default", syntax: "",
src: "",
exp: "(BLOCK (VERBATIM-CODE ((\"\" . \"\")) \"\"))"},
{name: "empty-html", syntax: meta.ValueSyntaxHTML,
src: "",
exp: "(BLOCK (VERBATIM-CODE ((\"\" . \"html\")) \"\"))"},
{name: "empty-html-allow", syntax: meta.ValueSyntaxHTML, allowHTML: true,
src: "",
exp: "(BLOCK (VERBATIM-HTML ((\"\" . \"html\")) \"\"))"},
{name: "empty-sxn", syntax: meta.ValueSyntaxSxn,
src: "",
exp: "(BLOCK (VERBATIM-CODE ((\"\" . \"sxn\")) \"\"))"},
{name: "valid-sxn", syntax: meta.ValueSyntaxSxn,
src: "(+ 3 4)",
exp: "(BLOCK (VERBATIM-CODE ((\"\" . \"sxn\")) \"(+ 3 4)\"))"},
{name: "invalid-sxn", syntax: meta.ValueSyntaxSxn,
src: "(+ 3 4",
exp: "(BLOCK (VERBATIM-CODE ((\"\" . \"sxn\")) \"(+ 3 4\") (PARA (TEXT \"ReaderError 1-6: unexpected EOF\")))"},
{name: "svg-common", syntax: meta.ValueSyntaxSVG,
src: " <svg bla",
exp: "(BLOCK (PARA (EMBED-BLOB () \"svg\" \"<svg bla\")))"},
{name: "svg-inkscape", syntax: meta.ValueSyntaxSVG,
src: "<svg\nbla",
exp: "(BLOCK (PARA (EMBED-BLOB () \"svg\" \"<svg\\nbla\")))"},
{name: "svg-selfmade", syntax: meta.ValueSyntaxSVG,
src: "<svg>",
exp: "(BLOCK (PARA (EMBED-BLOB () \"svg\" \"<svg>\")))"},
{name: "svg-error", syntax: meta.ValueSyntaxSVG,
src: "<svgbla",
exp: "(BLOCK)"},
{name: "svg-error-", syntax: meta.ValueSyntaxSVG,
src: "<svg-bla",
exp: "(BLOCK)"},
{name: "svg-error#", syntax: meta.ValueSyntaxSVG,
src: "<svg2bla",
exp: "(BLOCK)"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
inp := input.NewInput([]byte(tc.src))
alst := sx.Nil()
if tc.allowHTML {
alst = alst.Cons(sx.Cons(parser.SymAllowHTML, nil))
}
node := parser.Parse(inp, nil, tc.syntax, alst)
if got := node.String(); tc.exp != got {
t.Errorf("\nexp: %q\ngot: %q", tc.exp, got)
}
})
}
}
|
Changes to internal/parser/zettelmark.go.
| ︙ | ︙ | |||
25 26 27 28 29 30 31 |
func init() {
register(&Info{
Name: meta.ValueSyntaxZmk,
AltNames: nil,
IsASTParser: true,
IsTextFormat: true,
IsImageFormat: false,
| | | | | | 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
func init() {
register(&Info{
Name: meta.ValueSyntaxZmk,
AltNames: nil,
IsASTParser: true,
IsTextFormat: true,
IsImageFormat: false,
Parse: func(inp *input.Input, _ *meta.Meta, _ string, _ *sx.Pair) *sx.Pair {
var zmkParser zmk.Parser
zmkParser.Initialize(inp) // TODO: add alst
return zmkParser.Parse()
},
})
}
|
Deleted internal/parser/zettelmark_fuzz_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted internal/parser/zettelmark_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to internal/query/unlinked.go.
| ︙ | ︙ | |||
42 43 44 45 46 47 48 |
}
result := make([]string, 0, len(metaSeq)*4) // Assumption: four words per title
for _, m := range metaSeq {
title, hasTitle := m.Get(meta.KeyTitle)
if !hasTitle {
continue
}
| | | 42 43 44 45 46 47 48 49 50 51 52 |
}
result := make([]string, 0, len(metaSeq)*4) // Assumption: four words per title
for _, m := range metaSeq {
title, hasTitle := m.Get(meta.KeyTitle)
if !hasTitle {
continue
}
result = append(result, strings.SplitWords(string(title))...)
}
return result
}
|
Changes to internal/usecase/evaluate.go.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //----------------------------------------------------------------------------- package usecase import ( "context" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "zettelstore.de/z/internal/ast" "zettelstore.de/z/internal/config" "zettelstore.de/z/internal/evaluator" "zettelstore.de/z/internal/parser" "zettelstore.de/z/internal/query" "zettelstore.de/z/internal/zettel" | > > | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | //----------------------------------------------------------------------------- package usecase import ( "context" "t73f.de/r/sx" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx" "zettelstore.de/z/internal/ast" "zettelstore.de/z/internal/config" "zettelstore.de/z/internal/evaluator" "zettelstore.de/z/internal/parser" "zettelstore.de/z/internal/query" "zettelstore.de/z/internal/zettel" |
| ︙ | ︙ | |||
40 41 42 43 44 45 46 | rtConfig: rtConfig, ucGetZettel: ucGetZettel, ucQuery: ucQuery, } } // Run executes the use case. | | | | | | < | < | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
rtConfig: rtConfig,
ucGetZettel: ucGetZettel,
ucQuery: ucQuery,
}
}
// Run executes the use case.
func (uc *Evaluate) Run(ctx context.Context, zid id.Zid, syntax string) (*ast.Zettel, error) {
zettel, err := uc.ucGetZettel.Run(ctx, zid)
if err != nil {
return nil, err
}
return uc.RunZettel(ctx, zettel, syntax), nil
}
// RunZettel executes the use case for a given zettel.
func (uc *Evaluate) RunZettel(ctx context.Context, zettel zettel.Zettel, syntax string) *ast.Zettel {
zn := parser.ParseZettel(ctx, zettel, syntax, uc.rtConfig)
evaluator.EvaluateZettel(ctx, uc, uc.rtConfig, zn)
return zn
}
// RunBlockNode executes the use case for a metadata list, formatted as a block.
func (uc *Evaluate) RunBlockNode(ctx context.Context, block *sx.Pair) *sx.Pair {
if block == nil {
return nil
}
return evaluator.EvaluateBlock(ctx, uc, uc.rtConfig, zsx.MakeBlock(block))
}
// GetZettel retrieves the full zettel of a given zettel identifier.
func (uc *Evaluate) GetZettel(ctx context.Context, zid id.Zid) (zettel.Zettel, error) {
return uc.ucGetZettel.Run(ctx, zid)
}
// QueryMeta returns a list of metadata that comply to the given selection criteria.
func (uc *Evaluate) QueryMeta(ctx context.Context, q *query.Query) ([]*meta.Meta, error) {
return uc.ucQuery.Run(ctx, q)
}
|
Changes to internal/usecase/get_references.go.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 | //----------------------------------------------------------------------------- package usecase import ( "iter" zeroiter "t73f.de/r/zero/iter" "t73f.de/r/zsc/domain/meta" | > > > < | > | > | | | | | | | | | | > > | > | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
//-----------------------------------------------------------------------------
package usecase
import (
"iter"
"t73f.de/r/sx"
zeroiter "t73f.de/r/zero/iter"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/collect"
)
// GetReferences is the usecase to retrieve references that occur in a zettel.
type GetReferences struct{}
// NewGetReferences creates a new usecase object.
func NewGetReferences() GetReferences { return GetReferences{} }
// RunByState returns all references of a zettel, sparated by their state:
// local, external, query. No zettel references are returned.
func (uc GetReferences) RunByState(block *sx.Pair) (local, ext, query *sx.Pair) {
var lbLoc, lbQueries, lbExt sx.ListBuilder
for ref := range collect.ReferenceSeq(block) {
sym, _ := zsx.GetReference(ref)
switch sym {
case zsx.SymRefStateHosted, sz.SymRefStateBased:
lbLoc.Add(ref)
case zsx.SymRefStateExternal:
lbExt.Add(ref)
case sz.SymRefStateQuery:
lbQueries.Add(ref)
}
}
return lbLoc.List(), lbExt.List(), lbQueries.List()
}
// RunByExternal returns an iterator of all external references of a zettel.
func (uc GetReferences) RunByExternal(blocks *sx.Pair) iter.Seq[*sx.Pair] {
return zeroiter.FilterSeq(
collect.ReferenceSeq(blocks),
func(ref *sx.Pair) bool {
sym, _ := zsx.GetReference(ref)
return zsx.SymRefStateExternal.IsEqualSymbol(sym)
})
}
// RunByMeta returns all URLs that are stored in the metadata.
func (uc GetReferences) RunByMeta(m *meta.Meta) iter.Seq[string] {
return func(yield func(string) bool) {
for key, val := range m.All() {
if meta.Type(key) == meta.TypeURL && !yield(string(val)) {
|
| ︙ | ︙ |
Changes to internal/usecase/parse_zettel.go.
| ︙ | ︙ | |||
31 32 33 34 35 36 37 |
// NewParseZettel creates a new use case.
func NewParseZettel(rtConfig config.Config, getZettel GetZettel) ParseZettel {
return ParseZettel{rtConfig: rtConfig, getZettel: getZettel}
}
// Run executes the use case.
| | | > > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
// NewParseZettel creates a new use case.
func NewParseZettel(rtConfig config.Config, getZettel GetZettel) ParseZettel {
return ParseZettel{rtConfig: rtConfig, getZettel: getZettel}
}
// Run executes the use case.
func (uc ParseZettel) Run(ctx context.Context, zid id.Zid, syntax string) (*ast.Zettel, error) {
zettel, err := uc.getZettel.Run(ctx, zid)
if err != nil {
return nil, err
}
z := parser.ParseZettel(ctx, zettel, syntax, uc.rtConfig)
parser.Clean(z.Blocks)
return z, nil
}
|
Changes to internal/usecase/query.go.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package usecase import ( "context" "errors" "fmt" "strings" zerostrings "t73f.de/r/zero/strings" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/id/idset" "t73f.de/r/zsc/domain/meta" | > > > > < | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | package usecase import ( "context" "errors" "fmt" "slices" "strings" "t73f.de/r/sx" zerostrings "t73f.de/r/zero/strings" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/id/idset" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/sz" "t73f.de/r/zsx" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/collect" "zettelstore.de/z/internal/parser" "zettelstore.de/z/internal/query" "zettelstore.de/z/internal/zettel" ) |
| ︙ | ︙ | |||
128 129 130 131 132 133 134 |
func (uc *Query) processItemsDirective(ctx context.Context, _ *query.ItemsSpec, metaSeq []*meta.Meta) []*meta.Meta {
result := make([]*meta.Meta, 0, len(metaSeq))
for _, m := range metaSeq {
zn, err := uc.ucEvaluate.Run(ctx, m.Zid, string(m.GetDefault(meta.KeySyntax, meta.DefaultSyntax)))
if err != nil {
continue
}
| | | | < < | | | | > | 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
func (uc *Query) processItemsDirective(ctx context.Context, _ *query.ItemsSpec, metaSeq []*meta.Meta) []*meta.Meta {
result := make([]*meta.Meta, 0, len(metaSeq))
for _, m := range metaSeq {
zn, err := uc.ucEvaluate.Run(ctx, m.Zid, string(m.GetDefault(meta.KeySyntax, meta.DefaultSyntax)))
if err != nil {
continue
}
for ln := range collect.Order(zn.Blocks).Pairs() {
_, ref, _ := zsx.GetLink(ln.Head())
if refSym, refVal := zsx.GetReference(ref); sz.SymRefStateZettel.IsEqualSymbol(refSym) {
val, _ := sz.SplitFragment(refVal)
if collectedZid, err2 := id.Parse(val); err2 == nil {
if collectedMeta, err3 := uc.port.GetMeta(ctx, collectedZid); err3 == nil {
result = append(result, collectedMeta)
}
}
}
}
}
return result
}
|
| ︙ | ︙ | |||
195 196 197 198 199 200 201 202 203 204 205 206 |
}
}
return result
}
func (uc *Query) filterCandidates(ctx context.Context, candidates []*meta.Meta, words []string) []*meta.Meta {
result := make([]*meta.Meta, 0, len(candidates))
for _, cand := range candidates {
zettel, err := uc.port.GetZettel(ctx, cand.Zid)
if err != nil {
continue
}
| > < < < < < | > > | < < < < < | < < | | < < < < > > > > > | < | > > | | < < | < | < < < | < < < < < < < < < < < | | | < < | > | 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
}
}
return result
}
func (uc *Query) filterCandidates(ctx context.Context, candidates []*meta.Meta, words []string) []*meta.Meta {
result := make([]*meta.Meta, 0, len(candidates))
ulv := unlinkedVisitor{words: words}
for _, cand := range candidates {
zettel, err := uc.port.GetZettel(ctx, cand.Zid)
if err != nil {
continue
}
syntax := string(zettel.Meta.GetDefault(meta.KeySyntax, meta.DefaultSyntax))
if !parser.IsASTParser(syntax) {
continue
}
zn := uc.ucEvaluate.RunZettel(ctx, zettel, syntax)
ulv.found = false
zsx.WalkIt(&ulv, zn.Blocks, nil)
if ulv.found {
result = append(result, cand)
}
}
return result
}
type unlinkedVisitor struct {
words []string
found bool
}
func (v *unlinkedVisitor) VisitItBefore(node *sx.Pair, _ *sx.Pair) bool {
if v.found {
return true
}
if sym, isSymbol := sx.GetSymbol(node.Car()); isSymbol {
switch sym {
case zsx.SymHeading,
zsx.SymLink, zsx.SymEmbed, zsx.SymEmbedBLOB, zsx.SymCite:
// No further search.
return true
case zsx.SymText:
// TODO: this is way too simple. For example, two text nodes may
// be separated by a SOFT or HARD node.
textWords := zerostrings.SplitWords(zsx.GetText(node))
for i := 0; i+len(v.words) <= len(textWords); i++ {
if slices.Equal(v.words, textWords[i:i+len(v.words)]) {
v.found = true
return true
}
}
}
}
return false
}
func (*unlinkedVisitor) VisitItAfter(*sx.Pair, *sx.Pair) {}
|
Changes to internal/web/adapter/api/get_references.go.
| ︙ | ︙ | |||
18 19 20 21 22 23 24 25 | "iter" "net/http" "t73f.de/r/sx" zeroiter "t73f.de/r/zero/iter" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" | > < | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | "iter" "net/http" "t73f.de/r/sx" zeroiter "t73f.de/r/zero/iter" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsx" "zettelstore.de/z/internal/usecase" "zettelstore.de/z/internal/web/content" ) // MakeGetReferencesHandler creates a new HTTP handler to return various lists // of zettel references. func (a *API) MakeGetReferencesHandler( |
| ︙ | ︙ | |||
49 50 51 52 53 54 55 |
var seq iter.Seq[string]
q := r.URL.Query()
switch getPart(q, partZettel) {
case partZettel:
seq = zeroiter.CatSeq(
ucGetReferences.RunByMeta(zn.InhMeta),
| | | | 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
var seq iter.Seq[string]
q := r.URL.Query()
switch getPart(q, partZettel) {
case partZettel:
seq = zeroiter.CatSeq(
ucGetReferences.RunByMeta(zn.InhMeta),
getExternalURLs(zn.Blocks, ucGetReferences),
)
case partMeta:
seq = ucGetReferences.RunByMeta(zn.InhMeta)
case partContent:
seq = getExternalURLs(zn.Blocks, ucGetReferences)
}
enc, _ := getEncoding(r, q)
if enc == api.EncoderData {
var lb sx.ListBuilder
lb.Collect(zeroiter.MapSeq(seq, func(s string) sx.Object { return sx.MakeString(s) }))
if err = a.writeObject(w, zid, lb.List()); err != nil {
|
| ︙ | ︙ | |||
78 79 80 81 82 83 84 |
}
if err = writeBuffer(w, &buf, content.PlainText); err != nil {
a.logger.Error("write plain data", "err", err, "zid", zid)
}
})
}
| | | | > > > | 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
}
if err = writeBuffer(w, &buf, content.PlainText); err != nil {
a.logger.Error("write plain data", "err", err, "zid", zid)
}
})
}
func getExternalURLs(blocks *sx.Pair, ucGetReferences usecase.GetReferences) iter.Seq[string] {
return zeroiter.MapSeq(
ucGetReferences.RunByExternal(blocks),
func(ref *sx.Pair) string {
_, val := zsx.GetReference(ref)
return val
},
)
}
|
Changes to internal/web/adapter/api/get_zettel.go.
| ︙ | ︙ | |||
53 54 55 56 57 58 59 | case api.EncoderPlain: a.writePlainData(ctx, w, zid, part, getZettel) case api.EncoderData: a.writeSzData(ctx, w, zid, part, getZettel) default: | | | 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
case api.EncoderPlain:
a.writePlainData(ctx, w, zid, part, getZettel)
case api.EncoderData:
a.writeSzData(ctx, w, zid, part, getZettel)
default:
var zn *ast.Zettel
if q.Has(api.QueryKeyParseOnly) {
zn, err = parseZettel.Run(ctx, zid, q.Get(meta.KeySyntax))
} else {
zn, err = evaluate.Run(ctx, zid, q.Get(meta.KeySyntax))
}
if err != nil {
a.reportUsecaseError(w, err)
|
| ︙ | ︙ | |||
138 139 140 141 142 143 144 |
if err = a.writeObject(w, zid, obj); err != nil {
a.logger.Error("write sx data", "err", err, "zid", zid)
}
}
func (a *API) writeEncodedZettelPart(
ctx context.Context,
| | | | | | 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
if err = a.writeObject(w, zid, obj); err != nil {
a.logger.Error("write sx data", "err", err, "zid", zid)
}
}
func (a *API) writeEncodedZettelPart(
ctx context.Context,
w http.ResponseWriter, zn *ast.Zettel,
enc api.EncodingEnum, encStr string, part partType,
) {
encdr := encoder.Create(
enc,
&encoder.CreateParameter{
Lang: a.rtConfig.Get(ctx, zn.InhMeta, meta.KeyLang),
})
if encdr == nil {
adapter.BadRequest(w, fmt.Sprintf("Zettel %q not available in encoding %q", zn.Meta.Zid, encStr))
return
}
var err error
var buf bytes.Buffer
switch part {
case partZettel:
err = encdr.WriteZettel(&buf, zn)
case partMeta:
err = encdr.WriteMeta(&buf, zn.InhMeta)
case partContent:
err = encdr.WriteSz(&buf, zn.Blocks)
}
if err != nil {
a.logger.Error("Unable to store data in buffer", "err", err, "zid", zn.Zid)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
if buf.Len() == 0 {
|
| ︙ | ︙ |
Changes to internal/web/adapter/webui/create_zettel.go.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 26 | "net/http" "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" | > < | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "net/http" "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/sz" "zettelstore.de/z/internal/auth/user" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/encoder" "zettelstore.de/z/internal/evaluator" "zettelstore.de/z/internal/usecase" "zettelstore.de/z/internal/web/adapter" "zettelstore.de/z/internal/zettel" |
| ︙ | ︙ | |||
61 62 63 64 65 66 67 |
roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax)
switch op {
case actionCopy:
wui.renderZettelForm(ctx, w, createZettel.PrepareCopy(origZettel), "Copy Zettel", "", roleData, syntaxData)
case actionFolge:
wui.renderZettelForm(ctx, w, createZettel.PrepareFolge(origZettel), "Folge Zettel", "", roleData, syntaxData)
case actionNew:
| | | | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax)
switch op {
case actionCopy:
wui.renderZettelForm(ctx, w, createZettel.PrepareCopy(origZettel), "Copy Zettel", "", roleData, syntaxData)
case actionFolge:
wui.renderZettelForm(ctx, w, createZettel.PrepareFolge(origZettel), "Folge Zettel", "", roleData, syntaxData)
case actionNew:
title := sz.NormalizedSpacedText(origZettel.Meta.GetTitle())
newTitle := sz.NormalizedSpacedText(q.Get(meta.KeyTitle))
wui.renderZettelForm(ctx, w, createZettel.PrepareNew(origZettel, newTitle), title, "", roleData, syntaxData)
case actionSequel:
wui.renderZettelForm(ctx, w, createZettel.PrepareSequel(origZettel), "Sequel Zettel", "", roleData, syntaxData)
}
})
}
|
| ︙ | ︙ | |||
169 170 171 172 173 174 175 |
ctx := r.Context()
metaSeq, err := queryMeta.Run(box.NoEnrichQuery(ctx, q), q)
if err != nil {
wui.reportError(ctx, w, err)
return
}
entries, _ := evaluator.QueryAction(ctx, q, metaSeq)
| | | < | 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
ctx := r.Context()
metaSeq, err := queryMeta.Run(box.NoEnrichQuery(ctx, q), q)
if err != nil {
wui.reportError(ctx, w, err)
return
}
entries, _ := evaluator.QueryAction(ctx, q, metaSeq)
blocks := evaluate.RunBlockNode(ctx, entries)
enc := encoder.Create(api.EncoderZmk, nil)
var zmkContent bytes.Buffer
if err = enc.WriteSz(&zmkContent, blocks); err != nil {
wui.reportError(ctx, w, err)
return
}
m := meta.New(id.Invalid)
m.Set(meta.KeyTitle, meta.Value(q.Human()))
m.Set(meta.KeySyntax, meta.ValueSyntaxZmk)
|
| ︙ | ︙ |
Changes to internal/web/adapter/webui/get_info.go.
| ︙ | ︙ | |||
20 21 22 23 24 25 26 27 | "strings" "t73f.de/r/sx" zerostrings "t73f.de/r/zero/strings" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" | > > < | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | "strings" "t73f.de/r/sx" zerostrings "t73f.de/r/zero/strings" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/sz" "t73f.de/r/zsx" "zettelstore.de/z/internal/auth/user" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/encoder" "zettelstore.de/z/internal/evaluator" "zettelstore.de/z/internal/query" "zettelstore.de/z/internal/usecase" ) |
| ︙ | ︙ | |||
63 64 65 66 67 68 69 |
getTextTitle := wui.makeGetTextTitle(ctx, ucGetZettel)
var lbMetadata sx.ListBuilder
for key, val := range zn.Meta.Computed() {
sxval := wui.writeHTMLMetaValue(key, val, getTextTitle)
lbMetadata.Add(sx.Cons(sx.MakeString(key), sxval))
}
| | | | | | 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
getTextTitle := wui.makeGetTextTitle(ctx, ucGetZettel)
var lbMetadata sx.ListBuilder
for key, val := range zn.Meta.Computed() {
sxval := wui.writeHTMLMetaValue(key, val, getTextTitle)
lbMetadata.Add(sx.Cons(sx.MakeString(key), sxval))
}
locLinks, extLinks, queryLinks := wui.getLocalExtQueryLinks(ucGetReferences, zn.Blocks)
title := sz.NormalizedSpacedText(zn.InhMeta.GetTitle())
phrase := q.Get(api.QueryKeyPhrase)
if phrase == "" {
phrase = title
}
unlinkedMeta, err := ucQuery.Run(ctx, createUnlinkedQuery(zid, phrase))
if err != nil {
wui.reportError(ctx, w, err)
return
}
enc := wui.getSimpleHTMLEncoder(wui.getConfig(ctx, zn.InhMeta, meta.KeyLang))
entries, _ := evaluator.QueryAction(ctx, nil, unlinkedMeta)
blocks := ucEvaluate.RunBlockNode(ctx, entries)
unlinkedContent, _, err := enc.BlocksSxn(blocks)
if err != nil {
wui.reportError(ctx, w, err)
return
}
encTexts := encodingTexts()
shadowLinks := getShadowLinks(ctx, zid, zn.InhMeta.GetDefault(meta.KeyBoxNumber, ""), ucGetAllZettel)
|
| ︙ | ︙ | |||
111 112 113 114 115 116 117 |
}
if err != nil {
wui.reportError(ctx, w, err)
}
})
}
| | | | > | | > | | > | | | | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
}
if err != nil {
wui.reportError(ctx, w, err)
}
})
}
func (wui *WebUI) getLocalExtQueryLinks(ucGetReferences usecase.GetReferences, blocks *sx.Pair) (locLinks, extLinks, queries *sx.Pair) {
locRefs, extRefs, queryRefs := ucGetReferences.RunByState(blocks)
var lbLoc, lbQueries, lbExt sx.ListBuilder
for ref := range locRefs.Pairs() {
_, value := zsx.GetReference(ref.Head())
lbLoc.Add(sx.MakeString(value))
}
for ref := range extRefs.Pairs() {
_, value := zsx.GetReference(ref.Head())
lbExt.Add(sx.MakeString(value))
}
for ref := range queryRefs.Pairs() {
_, value := zsx.GetReference(ref.Head())
lbQueries.Add(
sx.Cons(
sx.MakeString(value),
sx.MakeString(wui.NewURLBuilder('h').AppendQuery(value).String())))
}
return lbLoc.List(), lbExt.List(), lbQueries.List()
}
func createUnlinkedQuery(zid id.Zid, phrase string) *query.Query {
var sb strings.Builder
sb.Write(zid.Bytes())
sb.WriteByte(' ')
sb.WriteString(api.UnlinkedDirective)
for word := range zerostrings.SplitWordSeq(phrase) {
sb.WriteByte(' ')
sb.WriteString(api.PhraseDirective)
sb.WriteByte(' ')
sb.WriteString(word)
}
sb.WriteByte(' ')
sb.WriteString(api.OrderDirective)
|
| ︙ | ︙ |
Changes to internal/web/adapter/webui/get_zettel.go.
| ︙ | ︙ | |||
20 21 22 23 24 25 26 27 | "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" | > < | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "t73f.de/r/zsc/sz" "zettelstore.de/z/internal/auth/user" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/config" "zettelstore.de/z/internal/usecase" ) // MakeGetHTMLZettelHandler creates a new HTTP handler for the use case "get zettel". |
| ︙ | ︙ | |||
52 53 54 55 56 57 58 | wui.reportError(ctx, w, err) return } zettelLang := wui.getConfig(ctx, zn.InhMeta, meta.KeyLang) enc := wui.getSimpleHTMLEncoder(zettelLang) metaObj := enc.MetaSxn(zn.InhMeta) | | | | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
wui.reportError(ctx, w, err)
return
}
zettelLang := wui.getConfig(ctx, zn.InhMeta, meta.KeyLang)
enc := wui.getSimpleHTMLEncoder(zettelLang)
metaObj := enc.MetaSxn(zn.InhMeta)
content, endnotes, err := enc.BlocksSxn(zn.Blocks)
if err != nil {
wui.reportError(ctx, w, err)
return
}
user := user.GetCurrentUser(ctx)
getTextTitle := wui.makeGetTextTitle(ctx, getZettel)
title := sz.NormalizedSpacedText(zn.InhMeta.GetTitle())
env, rb := wui.createRenderEnvironment(ctx, "zettel", zettelLang, title, user)
rb.bindSymbol(symMetaHeader, metaObj)
rb.bindString("heading", sx.MakeString(title))
if role, found := zn.InhMeta.Get(meta.KeyRole); found && role != "" {
rb.bindString(
"role-url",
sx.MakeString(wui.NewURLBuilder('h').AppendQuery(
|
| ︙ | ︙ |
Changes to internal/web/adapter/webui/htmlgen.go.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 | import ( "maps" "net/url" "slices" "strings" "t73f.de/r/sx" | < < < < | | | | | < | | | | > | | | | | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
import (
"maps"
"net/url"
"slices"
"strings"
"t73f.de/r/sx"
"t73f.de/r/zero/set"
"t73f.de/r/zsc/api"
"t73f.de/r/zsc/domain/id"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsc/shtml"
"t73f.de/r/zsc/sz"
"t73f.de/r/zsx"
"zettelstore.de/z/internal/ast"
)
// Builder allows to build new URLs for the web service.
type urlBuilder interface {
NewURLBuilder(key byte) *api.URLBuilder
}
type htmlGenerator struct {
th *shtml.Evaluator
lang string
symAt *sx.Symbol
}
func (wui *WebUI) createGenerator(builder urlBuilder, lang string) *htmlGenerator {
th := shtml.NewEvaluator(1)
findA := func(obj sx.Object) (assoc, rest *sx.Pair) {
pair, isTag := sx.GetPair(obj)
if !isTag || !shtml.SymA.IsEqual(pair.Car()) {
return nil, nil
}
rest = pair.Tail()
if rest == nil {
return nil, nil
}
if attr, isAssoc := sx.GetPair(rest.Car()); isAssoc {
if _, isAttr := sx.GetPair(attr.Car()); isAttr {
return attr, rest.Tail()
}
}
return nil, nil
}
rebindWrap(th, zsx.SymLink, func(args sx.Vector, env *shtml.Environment, prevFn shtml.EvalFn) sx.Object {
refSym, _ := shtml.GetReference(args[1], env)
obj := prevFn(args, env)
assoc, rest := findA(obj)
if assoc == nil {
return obj
}
if zsx.SymRefStateExternal.IsEqual(refSym) {
a := zsx.GetAttributes(assoc)
a = a.Set("target", "_blank")
a = a.Add("rel", "external").Add("rel", "noreferrer")
return rest.Cons(shtml.EvaluateAttributes(a)).Cons(shtml.SymA)
}
hrefP := assoc.Assoc(shtml.SymAttrHref)
if hrefP == nil {
return obj
|
| ︙ | ︙ | |||
95 96 97 98 99 100 101 |
if err == nil {
u = u.SetZid(zid)
}
if hasFragment {
u = u.SetFragment(fragment)
}
assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String())))
| | | | | > > > > | | < | 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
if err == nil {
u = u.SetZid(zid)
}
if hasFragment {
u = u.SetFragment(fragment)
}
assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String())))
return rest.Cons(assoc).Cons(shtml.SymA)
case sz.SymRefStateQuery:
ur, err := url.Parse(href.GetValue())
if err != nil {
return obj
}
urlQuery := ur.Query()
if !urlQuery.Has(api.QueryKeyQuery) {
return obj
}
u := builder.NewURLBuilder('h')
if q := urlQuery.Get(api.QueryKeyQuery); q != "" {
u = u.AppendQuery(q)
}
assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String())))
return rest.Cons(assoc).Cons(shtml.SymA)
case sz.SymRefStateBased:
u := builder.NewURLBuilder('/')
assoc = assoc.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()+href.GetValue()[1:])))
return rest.Cons(assoc).Cons(shtml.SymA)
}
return obj
})
rebind(th, zsx.SymEmbed, func(obj sx.Object) sx.Object {
pair, isPair := sx.GetPair(obj)
if !isPair || !shtml.SymIMG.IsEqual(pair.Car()) {
return obj
}
attr, isPair := sx.GetPair(pair.Tail().Car())
if !isPair {
return obj
}
_, isAttr := sx.GetPair(attr.Car())
if !isAttr {
return obj
}
srcP := attr.Assoc(shtml.SymAttrSrc)
if srcP == nil {
return obj
}
src, isString := sx.GetString(srcP.Cdr())
if !isString {
return obj
}
zid, err := id.Parse(src.GetValue())
if err != nil {
return obj
}
u := builder.NewURLBuilder('z').SetZid(zid)
imgAttr := attr.Tail().Cons(sx.Cons(shtml.SymAttrSrc, sx.MakeString(u.String())))
return pair.Tail().Tail().Cons(imgAttr).Cons(shtml.SymIMG)
})
return &htmlGenerator{
th: th,
lang: lang,
}
}
func rebind(ev *shtml.Evaluator, sym *sx.Symbol, fn func(sx.Object) sx.Object) {
prevFn := ev.ResolveBinding(sym)
|
| ︙ | ︙ | |||
185 186 187 188 189 190 191 |
var mapMetaKey = map[string]string{
meta.KeyCopyright: "copyright",
meta.KeyLicense: "license",
}
func (g *htmlGenerator) MetaSxn(m *meta.Meta) *sx.Pair {
| | | 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
var mapMetaKey = map[string]string{
meta.KeyCopyright: "copyright",
meta.KeyLicense: "license",
}
func (g *htmlGenerator) MetaSxn(m *meta.Meta) *sx.Pair {
tm := ast.GetMetaSz(m)
env := shtml.MakeEnvironment(g.lang)
hm, err := g.th.Evaluate(tm, &env)
if err != nil {
return nil
}
ignore := set.New(meta.KeyTitle, meta.KeyLang)
|
| ︙ | ︙ | |||
258 259 260 261 262 263 264 |
metaTags := sb.String()
if len(metaTags) == 0 {
return nil
}
return g.th.EvaluateMeta(zsx.Attributes{"name": "keywords", "content": metaTags})
}
| | | < | | < | | 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
metaTags := sb.String()
if len(metaTags) == 0 {
return nil
}
return g.th.EvaluateMeta(zsx.Attributes{"name": "keywords", "content": metaTags})
}
func (g *htmlGenerator) BlocksSxn(block *sx.Pair) (content, endnotes *sx.Pair, _ error) {
if block == nil || block.Tail() == nil {
return nil, nil, nil
}
env := shtml.MakeEnvironment(g.lang)
sh, err := g.th.Evaluate(block, &env)
if err != nil {
return nil, nil, err
}
return sh, shtml.Endnotes(&env), nil
}
func (g *htmlGenerator) szToSxHTML(node *sx.Pair) *sx.Pair {
env := shtml.MakeEnvironment(g.lang)
sh, err := g.th.Evaluate(node, &env)
if err != nil {
return nil
}
return sh
}
|
Changes to internal/web/adapter/webui/htmlmeta.go.
| ︙ | ︙ | |||
20 21 22 23 24 25 26 27 | "t73f.de/r/sx" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" | > < | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | "t73f.de/r/sx" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "t73f.de/r/zsc/sz" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/usecase" ) func (wui *WebUI) writeHTMLMetaValue( key string, value meta.Value, getTextTitle getTextTitleFunc, |
| ︙ | ︙ | |||
48 49 50 51 52 53 54 |
case meta.TypeString:
return sx.MakeString(string(value))
case meta.TypeTagSet:
return wui.transformTagSet(key, value.AsSlice())
case meta.TypeTimestamp:
if ts, ok := value.AsTime(); ok {
return sx.MakeList(
| | < | | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
case meta.TypeString:
return sx.MakeString(string(value))
case meta.TypeTagSet:
return wui.transformTagSet(key, value.AsSlice())
case meta.TypeTimestamp:
if ts, ok := value.AsTime(); ok {
return sx.MakeList(
sxhtml.MakeSymbol("time"),
sx.MakeList(
sx.Cons(sxhtml.MakeSymbol("datetime"), sx.MakeString(ts.Format("2006-01-02T15:04:05"))),
),
sx.MakeList(sxhtml.SymNoEscape, sx.MakeString(ts.Format("2006-01-02 15:04:05"))),
)
}
return sx.Nil()
case meta.TypeURL:
return wui.url2html(sx.MakeString(string(value)))
|
| ︙ | ︙ | |||
80 81 82 83 84 85 86 |
switch {
case found > 0:
ub := wui.NewURLBuilder('h').SetZid(zid)
attrs := sx.Nil()
if title != "" {
attrs = attrs.Cons(sx.Cons(shtml.SymAttrTitle, sx.MakeString(title)))
}
| | | | 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
switch {
case found > 0:
ub := wui.NewURLBuilder('h').SetZid(zid)
attrs := sx.Nil()
if title != "" {
attrs = attrs.Cons(sx.Cons(shtml.SymAttrTitle, sx.MakeString(title)))
}
attrs = attrs.Cons(sx.Cons(shtml.SymAttrHref, sx.MakeString(ub.String())))
return sx.Nil().Cons(sx.MakeString(zid.String())).Cons(attrs).Cons(shtml.SymA)
case found == 0:
return sx.MakeList(sxhtml.MakeSymbol("s"), text)
default: // case found < 0:
return text
}
}
var space = sx.MakeString(" ")
|
| ︙ | ︙ | |||
140 141 142 143 144 145 146 |
}
return buildHref(ub, text)
}
func buildHref(ub *api.URLBuilder, text string) *sx.Pair {
return sx.MakeList(
shtml.SymA,
| < < | < | | 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
}
return buildHref(ub, text)
}
func buildHref(ub *api.URLBuilder, text string) *sx.Pair {
return sx.MakeList(
shtml.SymA,
sx.MakeList(sx.Cons(shtml.SymAttrHref, sx.MakeString(ub.String()))),
sx.MakeString(text),
)
}
type getTextTitleFunc func(id.Zid) (string, int)
func (wui *WebUI) makeGetTextTitle(ctx context.Context, getZettel usecase.GetZettel) getTextTitleFunc {
return func(zid id.Zid) (string, int) {
z, err := getZettel.Run(box.NoEnrichContext(ctx), zid)
if err != nil {
if errors.Is(err, &box.ErrNotAllowed{}) {
return "", -1
}
return "", 0
}
return sz.NormalizedSpacedText(z.Meta.GetTitle()), 1
}
}
|
Changes to internal/web/adapter/webui/lists.go.
| ︙ | ︙ | |||
18 19 20 21 22 23 24 | "net/http" "net/url" "slices" "strconv" "strings" "t73f.de/r/sx" | < > < | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | "net/http" "net/url" "slices" "strconv" "strings" "t73f.de/r/sx" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "t73f.de/r/zsx" "zettelstore.de/z/internal/auth/user" "zettelstore.de/z/internal/evaluator" "zettelstore.de/z/internal/usecase" "zettelstore.de/z/internal/web/adapter" ) // MakeListHTMLMetaHandler creates a HTTP handler for rendering the list of zettel as HTML. |
| ︙ | ︙ | |||
70 71 72 73 74 75 76 |
userLang := wui.getUserLang(ctx)
var content, endnotes *sx.Pair
numEntries := 0
if bn, cnt := evaluator.QueryAction(ctx, q, metaSeq); bn != nil {
enc := wui.getSimpleHTMLEncoder(userLang)
| | | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
userLang := wui.getUserLang(ctx)
var content, endnotes *sx.Pair
numEntries := 0
if bn, cnt := evaluator.QueryAction(ctx, q, metaSeq); bn != nil {
enc := wui.getSimpleHTMLEncoder(userLang)
content, endnotes, err = enc.BlocksSxn(zsx.MakeBlock(bn))
if err != nil {
wui.reportError(ctx, w, err)
return
}
numEntries = cnt
}
|
| ︙ | ︙ | |||
171 172 173 174 175 176 177 |
}
return withZettel, withoutZettel
}
func (wui *WebUI) prependZettelLink(sxZtl *sx.Pair, name string, u *api.URLBuilder) *sx.Pair {
link := sx.MakeList(
shtml.SymA,
| < < | < | 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
}
return withZettel, withoutZettel
}
func (wui *WebUI) prependZettelLink(sxZtl *sx.Pair, name string, u *api.URLBuilder) *sx.Pair {
link := sx.MakeList(
shtml.SymA,
sx.MakeList(sx.Cons(shtml.SymAttrHref, sx.MakeString(u.String()))),
sx.MakeString(name),
)
if sxZtl != nil {
sxZtl = sxZtl.Cons(sx.MakeString(", "))
}
return sxZtl.Cons(link)
}
|
| ︙ | ︙ |
Changes to internal/web/adapter/webui/template.go.
| ︙ | ︙ | |||
27 28 29 30 31 32 33 34 35 36 37 38 39 40 | "t73f.de/r/sx/sxeval" "t73f.de/r/sx/sxreader" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "zettelstore.de/z/internal/ast" "zettelstore.de/z/internal/auth/user" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/collect" "zettelstore.de/z/internal/config" "zettelstore.de/z/internal/logging" | > > | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | "t73f.de/r/sx/sxeval" "t73f.de/r/sx/sxreader" "t73f.de/r/sxwebs/sxhtml" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/id" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsc/shtml" "t73f.de/r/zsc/sz" "t73f.de/r/zsx" "zettelstore.de/z/internal/ast" "zettelstore.de/z/internal/auth/user" "zettelstore.de/z/internal/box" "zettelstore.de/z/internal/collect" "zettelstore.de/z/internal/config" "zettelstore.de/z/internal/logging" |
| ︙ | ︙ | |||
135 136 137 138 139 140 141 |
func (wui *WebUI) url2html(text sx.String) sx.Object {
if u, errURL := url.Parse(text.GetValue()); errURL == nil {
if us := u.String(); us != "" {
return sx.MakeList(
shtml.SymA,
sx.MakeList(
| < | 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
func (wui *WebUI) url2html(text sx.String) sx.Object {
if u, errURL := url.Parse(text.GetValue()); errURL == nil {
if us := u.String(); us != "" {
return sx.MakeList(
shtml.SymA,
sx.MakeList(
sx.Cons(shtml.SymAttrHref, sx.MakeString(us)),
sx.Cons(shtml.SymAttrTarget, sx.MakeString("_blank")),
sx.Cons(shtml.SymAttrRel, sx.MakeString("external noreferrer")),
),
text)
}
}
|
| ︙ | ︙ | |||
343 344 345 346 347 348 349 |
}
func (wui *WebUI) bindQueryURL(rb *renderBinder, strZid, symName, directive string) {
rb.bindString(symName,
sx.MakeString(wui.NewURLBuilder('h').AppendQuery(strZid+" "+directive+" "+api.DirectedDirective).String()))
}
func (wui *WebUI) buildListsMenuSxn(ctx context.Context, lang string) *sx.Pair {
| | | | | | > > | | | | | 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 |
}
func (wui *WebUI) bindQueryURL(rb *renderBinder, strZid, symName, directive string) {
rb.bindString(symName,
sx.MakeString(wui.NewURLBuilder('h').AppendQuery(strZid+" "+directive+" "+api.DirectedDirective).String()))
}
func (wui *WebUI) buildListsMenuSxn(ctx context.Context, lang string) *sx.Pair {
var zn *ast.Zettel
if menuZid, err := id.Parse(wui.getConfig(ctx, nil, config.KeyListsMenuZettel)); err == nil {
if zn, err = wui.evalZettel.Run(ctx, menuZid, ""); err != nil {
zn = nil
}
}
if zn == nil {
ctx = box.NoEnrichContext(ctx)
ztl, err := wui.box.GetZettel(ctx, id.ZidTOCListsMenu)
if err != nil {
return nil
}
zn = wui.evalZettel.RunZettel(ctx, ztl, "")
}
htmlgen := wui.getSimpleHTMLEncoder(lang)
var lb sx.ListBuilder
for ln := range collect.Order(zn.Blocks).Pairs() {
lb.Add(htmlgen.szToSxHTML(ln.Head()))
}
return lb.List()
}
func (wui *WebUI) fetchNewTemplatesSxn(ctx context.Context, user *meta.Meta) *sx.Pair {
if !wui.canCreate(ctx, user) {
return nil
}
ctx = box.NoEnrichContext(ctx)
menu, err := wui.box.GetZettel(ctx, id.ZidTOCNewTemplate)
if err != nil {
return nil
}
var lb sx.ListBuilder
zn := parser.ParseZettel(ctx, menu, "", wui.rtConfig)
for ln := range collect.Order(zn.Blocks).Pairs() {
_, ref, _ := zsx.GetLink(ln.Head())
sym, val := zsx.GetReference(ref)
if !sz.SymRefStateZettel.IsEqualSymbol(sym) {
continue
}
zid, err2 := id.Parse(val)
if err2 != nil {
continue
}
z, err2 := wui.box.GetZettel(ctx, zid)
if err2 != nil {
continue
}
if !wui.policy.CanRead(user, z.Meta) {
continue
}
text := sx.MakeString(sz.NormalizedSpacedText(z.Meta.GetTitle()))
link := sx.MakeString(wui.NewURLBuilder('c').SetZid(zid).
AppendKVQuery(queryKeyAction, valueActionNew).String())
lb.Add(sx.Cons(text, link))
}
return lb.List()
}
func (wui *WebUI) calculateFooterSxn(ctx context.Context) *sx.Pair {
if footerZid, err := id.Parse(wui.getConfig(ctx, nil, config.KeyFooterZettel)); err == nil {
if zn, err2 := wui.evalZettel.Run(ctx, footerZid, ""); err2 == nil {
htmlEnc := wui.getSimpleHTMLEncoder(wui.getConfig(ctx, zn.InhMeta, meta.KeyLang)).SetUnique("footer-")
if content, endnotes, err3 := htmlEnc.BlocksSxn(zn.Blocks); err3 == nil {
if content != nil && endnotes != nil {
content.LastPair().SetCdr(sx.Cons(endnotes, nil))
}
return content
}
}
}
|
| ︙ | ︙ | |||
480 481 482 483 484 485 486 |
if err != nil {
return err
}
wui.logger.Debug("render", "page", pageObj)
gen := sxhtml.NewGenerator().SetNewline()
var sb bytes.Buffer
| | < | 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 |
if err != nil {
return err
}
wui.logger.Debug("render", "page", pageObj)
gen := sxhtml.NewGenerator().SetNewline()
var sb bytes.Buffer
if err = gen.WriteHTML(&sb, pageObj); err != nil {
return err
}
wui.prepareAndWriteHeader(w, code)
if _, err = w.Write(sb.Bytes()); err != nil {
wui.logger.Error("Unable to write HTML via template", "err", err)
}
return nil // No error reporting, since we do not know what happended during write to client.
|
| ︙ | ︙ |
Changes to internal/web/adapter/webui/webui.go.
| ︙ | ︙ | |||
189 190 191 192 193 194 195 |
return wui.policy.CanRefresh(user)
}
func (wui *WebUI) getSimpleHTMLEncoder(lang string) *htmlGenerator {
return wui.createGenerator(wui, lang)
}
| < < < | | 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
return wui.policy.CanRefresh(user)
}
func (wui *WebUI) getSimpleHTMLEncoder(lang string) *htmlGenerator {
return wui.createGenerator(wui, lang)
}
// NewURLBuilder creates a new URL builder object with the given key.
func (wui *WebUI) NewURLBuilder(key byte) *api.URLBuilder { return wui.ab.NewURLBuilder(key) }
func (wui *WebUI) clearToken(ctx context.Context, w http.ResponseWriter) context.Context {
return wui.ab.ClearToken(ctx, w)
}
func (wui *WebUI) setToken(w http.ResponseWriter, token []byte) {
wui.ab.SetToken(w, token, wui.tokenLifetime)
}
func (wui *WebUI) prepareAndWriteHeader(w http.ResponseWriter, statusCode int) {
h := adapter.PrepareHeader(w, "text/html; charset=utf-8")
h.Set("Content-Security-Policy", "default-src 'self'; img-src * data:; style-src 'self' 'unsafe-inline'")
h.Set("Permissions-Policy", "payment=(), interest-cohort=()")
h.Set("Referrer-Policy", "same-origin")
h.Set("X-Content-Type-Options", "nosniff")
if !wui.debug {
h.Set("X-Frame-Options", "sameorigin")
}
w.WriteHeader(statusCode)
}
|
Changes to internal/web/server/http.go.
| ︙ | ︙ | |||
31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
)
type webServer struct {
log *slog.Logger
baseURL string
httpServer httpServer
router httpRouter
persistentCookie bool
secureCookie bool
}
// ConfigData contains the data needed to configure a server.
type ConfigData struct {
Log *slog.Logger
| > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
)
type webServer struct {
log *slog.Logger
baseURL string
httpServer httpServer
router httpRouter
cop *http.CrossOriginProtection
persistentCookie bool
secureCookie bool
}
// ConfigData contains the data needed to configure a server.
type ConfigData struct {
Log *slog.Logger
|
| ︙ | ︙ | |||
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
}
// New creates a new web server.
func New(sd ConfigData) Server {
srv := webServer{
log: sd.Log,
baseURL: sd.BaseURL,
persistentCookie: sd.PersistentCookie,
secureCookie: sd.SecureCookie,
}
rd := routerData{
log: sd.Log,
urlPrefix: sd.URLPrefix,
maxRequestSize: sd.MaxRequestSize,
auth: sd.Auth,
loopbackIdent: sd.LoopbackIdent,
loopbackZid: sd.LoopbackZid,
profiling: sd.Profiling,
}
srv.router.initializeRouter(rd)
mwReqID := reqid.Config{WithContext: true}
mwLogReq := logging.ReqConfig{
Logger: sd.Log, Level: slog.LevelDebug,
| > | | > > > | > > > < < < | | | | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
}
// New creates a new web server.
func New(sd ConfigData) Server {
srv := webServer{
log: sd.Log,
baseURL: sd.BaseURL,
cop: http.NewCrossOriginProtection(),
persistentCookie: sd.PersistentCookie,
secureCookie: sd.SecureCookie,
}
rd := routerData{
log: sd.Log,
urlPrefix: sd.URLPrefix,
maxRequestSize: sd.MaxRequestSize,
auth: sd.Auth,
loopbackIdent: sd.LoopbackIdent,
loopbackZid: sd.LoopbackZid,
profiling: sd.Profiling,
}
srv.router.initializeRouter(rd)
mwReqID := reqid.Config{WithContext: true}
mwLogReq := logging.ReqConfig{
Logger: sd.Log, Level: slog.LevelDebug,
Message: "ServeHTTP", WithRequestID: true, WithRemote: true, WithHeaders: true}
mwLogResp := logging.RespConfig{Logger: sd.Log, Level: slog.LevelDebug,
Message: "/ServeHTTP", WithRequestID: true}
mw := middleware.NewChain(mwReqID.Build(), mwLogReq.Build(), mwLogResp.Build())
srv.httpServer.initializeHTTPServer(sd.ListenAddr, middleware.Apply(mw, &srv.router))
return &srv
}
func (srv *webServer) Handle(pattern string, handler http.Handler) {
srv.router.Handle(pattern, handler)
}
func (srv *webServer) AddListRoute(isAPI bool, key byte, method Method, handler http.Handler) {
if !isAPI {
handler = srv.cop.Handler(handler)
}
srv.router.addListRoute(key, method, handler)
}
func (srv *webServer) AddZettelRoute(isAPI bool, key byte, method Method, handler http.Handler) {
if !isAPI {
handler = srv.cop.Handler(handler)
}
srv.router.addZettelRoute(key, method, handler)
}
func (srv *webServer) SetUserRetriever(ur UserRetriever) {
srv.router.ur = ur
}
func (srv *webServer) NewURLBuilder(key byte) *api.URLBuilder {
return api.NewURLBuilder(srv.router.urlPrefix, key)
}
func (srv *webServer) NewURLBuilderAbs(key byte) *api.URLBuilder {
return api.NewURLBuilder(srv.baseURL, key)
}
const sessionName = "zsession"
func (srv *webServer) SetToken(w http.ResponseWriter, token []byte, d time.Duration) {
cookie := http.Cookie{
Name: sessionName,
Value: string(token),
Path: srv.router.urlPrefix,
Secure: srv.secureCookie,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
}
if srv.persistentCookie && d > 0 {
cookie.Expires = time.Now().Add(d).Add(30 * time.Second).UTC()
}
srv.log.Debug("SetToken", "token", cookie.Value)
if v := cookie.String(); v != "" {
w.Header().Add("Set-Cookie", v)
w.Header().Add("Cache-Control", `no-cache="Set-Cookie"`)
w.Header().Add("Vary", "Cookie")
}
}
|
| ︙ | ︙ |
Changes to internal/web/server/server.go.
| ︙ | ︙ | |||
41 42 43 44 45 46 47 |
MethodDelete
methodLAST // must always be the last one
)
// Router allows to state routes for various URL paths.
type Router interface {
Handle(pattern string, handler http.Handler)
| | | < | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
MethodDelete
methodLAST // must always be the last one
)
// Router allows to state routes for various URL paths.
type Router interface {
Handle(pattern string, handler http.Handler)
AddListRoute(isAPI bool, key byte, method Method, handler http.Handler)
AddZettelRoute(isAPI bool, key byte, method Method, handler http.Handler)
SetUserRetriever(ur UserRetriever)
}
// Builder allows to build new URLs for the web service.
type Builder interface {
NewURLBuilder(key byte) *api.URLBuilder
NewURLBuilderAbs(key byte) *api.URLBuilder
}
// Auth is the authencation interface.
type Auth interface {
// SetToken sends the token to the client.
|
| ︙ | ︙ |
Changes to tests/markdown_test.go.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 24 25 26 27 | "bytes" "encoding/json" "fmt" "os" "strings" "testing" "t73f.de/r/zsc/api" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" | > < < | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
"bytes"
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"t73f.de/r/sx"
"t73f.de/r/zsc/api"
"t73f.de/r/zsc/domain/meta"
"t73f.de/r/zsx/input"
"zettelstore.de/z/internal/encoder"
"zettelstore.de/z/internal/parser"
)
type markdownTestCase struct {
Markdown string `json:"markdown"`
HTML string `json:"html"`
|
| ︙ | ︙ | |||
63 64 65 66 67 68 69 |
}
var testcases []markdownTestCase
if err = json.Unmarshal(content, &testcases); err != nil {
panic(err)
}
for _, tc := range testcases {
| | | | | | | | | | | | | | | | | | < | 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
}
var testcases []markdownTestCase
if err = json.Unmarshal(content, &testcases); err != nil {
panic(err)
}
for _, tc := range testcases {
node := createMDBlockSlice(tc.Markdown)
testAllEncodings(t, tc, node)
testZmkEncoding(t, tc, node)
}
}
func createMDBlockSlice(markdown string) *sx.Pair {
return parser.Parse(input.NewInput([]byte(markdown)), nil, meta.ValueSyntaxMarkdown, nil)
}
func testAllEncodings(t *testing.T, tc markdownTestCase, node *sx.Pair) {
var sb strings.Builder
testID := tc.Example*100 + 1
for _, enc := range encodings {
t.Run(fmt.Sprintf("Encode %v %v", enc, testID), func(*testing.T) {
_ = encoder.Create(enc, &encoder.CreateParameter{Lang: meta.ValueLangEN}).WriteSz(&sb, node)
sb.Reset()
})
}
}
func testZmkEncoding(t *testing.T, tc markdownTestCase, node *sx.Pair) {
zmkEncoder := encoder.Create(api.EncoderZmk, nil)
var buf bytes.Buffer
testID := tc.Example*100 + 1
t.Run(fmt.Sprintf("Encode zmk %14d", testID), func(st *testing.T) {
buf.Reset()
_ = zmkEncoder.WriteSz(&buf, node)
// gotFirst := buf.String()
testID = tc.Example*100 + 2
secondNode := parser.Parse(input.NewInput(buf.Bytes()), nil, meta.ValueSyntaxZmk, nil)
buf.Reset()
_ = zmkEncoder.WriteSz(&buf, secondNode)
gotSecond := buf.String()
// if gotFirst != gotSecond {
// st.Errorf("\nCMD: %q\n1st: %q\n2nd: %q", tc.Markdown, gotFirst, gotSecond)
// }
testID = tc.Example*100 + 3
thirdNode := parser.Parse(input.NewInput(buf.Bytes()), nil, meta.ValueSyntaxZmk, nil)
buf.Reset()
_ = zmkEncoder.WriteSz(&buf, thirdNode)
gotThird := buf.String()
if gotSecond != gotThird {
st.Errorf("\ncmd: %q\n1st: %q\n2nd: %q", tc.Markdown, gotSecond, gotThird)
}
})
}
func TestAdditionalMarkdown(t *testing.T) {
testcases := []struct {
md string
exp string
}{
{`abc<br>def`, "abc``<br>``{=\"html\"}def"},
}
zmkEncoder := encoder.Create(api.EncoderZmk, nil)
var sb strings.Builder
for i, tc := range testcases {
node := createMDBlockSlice(tc.md)
sb.Reset()
_ = zmkEncoder.WriteSz(&sb, node)
if got := sb.String(); got != tc.exp {
t.Errorf("%d: %q -> %q, but got %q", i, tc.md, tc.exp, got)
}
}
}
|
Changes to tests/naughtystrings_test.go.
| ︙ | ︙ | |||
19 20 21 22 23 24 25 | "os" "path/filepath" "testing" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" | < | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | "os" "path/filepath" "testing" "t73f.de/r/zsc/domain/meta" "t73f.de/r/zsx/input" "zettelstore.de/z/internal/encoder" "zettelstore.de/z/internal/parser" _ "zettelstore.de/z/cmd" ) // Test all parser / encoder with a list of "naughty strings", i.e. unusual strings |
| ︙ | ︙ | |||
81 82 83 84 85 86 87 |
}
encs := getAllEncoder()
if len(encs) == 0 {
t.Fatal("no encoder found")
}
for _, s := range blns {
for _, pinfo := range pinfos {
| | | < | 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
}
encs := getAllEncoder()
if len(encs) == 0 {
t.Fatal("no encoder found")
}
for _, s := range blns {
for _, pinfo := range pinfos {
node := parser.Parse(input.NewInput([]byte(s)), &meta.Meta{}, pinfo.Name, nil)
for _, enc := range encs {
if err = enc.WriteSz(io.Discard, node); err != nil {
t.Error(err)
}
}
}
}
}
|
Changes to tests/regression_test.go.
| ︙ | ︙ | |||
121 122 123 124 125 126 127 |
u, err := url.Parse(p.Location())
if err != nil {
panic("Unable to parse URL '" + p.Location() + "': " + err.Error())
}
return u.Path[len(root):]
}
| | | | 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
u, err := url.Parse(p.Location())
if err != nil {
panic("Unable to parse URL '" + p.Location() + "': " + err.Error())
}
return u.Path[len(root):]
}
func checkMetaFile(t *testing.T, resultName string, zn *ast.Zettel, enc api.EncodingEnum) {
t.Helper()
if enc := encoder.Create(enc, &encoder.CreateParameter{Lang: meta.ValueLangEN}); enc != nil {
var sf strings.Builder
_ = enc.WriteMeta(&sf, zn.Meta)
checkFileContent(t, resultName, sf.String())
return
}
panic(fmt.Sprintf("Unknown writer encoding %q", enc))
}
func checkMetaBox(t *testing.T, p box.ManagedBox, wd, boxName string) {
|
| ︙ | ︙ |
Changes to tools/build/build.go.
| ︙ | ︙ | |||
59 60 61 62 63 64 65 |
const dirtySuffix = "-dirty"
func readFossilDirty() (string, error) {
s, err := tools.ExecuteCommand(nil, "fossil", "status", "--differ")
if err != nil {
return "", err
}
| | | 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
const dirtySuffix = "-dirty"
func readFossilDirty() (string, error) {
s, err := tools.ExecuteCommand(nil, "fossil", "status", "--differ")
if err != nil {
return "", err
}
for line := range zerostrings.SplitLineSeq(s) {
for _, prefix := range dirtyPrefixes {
if strings.HasPrefix(line, prefix) {
return dirtySuffix, nil
}
}
}
return "", nil
|
| ︙ | ︙ |
Changes to tools/tools.go.
| ︙ | ︙ | |||
90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
return err
}
if err := checkStaticcheck(); err != nil {
return err
}
if err := checkUnparam(forRelease); err != nil {
return err
}
if err := checkRevive(); err != nil {
return err
}
if err := checkErrCheck(); err != nil {
return err
}
| > > > | 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
return err
}
if err := checkStaticcheck(); err != nil {
return err
}
if err := checkUnparam(forRelease); err != nil {
return err
}
if err := checkDeadcode(); err != nil {
return err
}
if err := checkRevive(); err != nil {
return err
}
if err := checkErrCheck(); err != nil {
return err
}
|
| ︙ | ︙ | |||
114 115 116 117 118 119 120 |
var env []string
env = append(env, EnvDirectProxy...)
env = append(env, EnvGoVCS...)
args := []string{"test", pkg}
args = append(args, testParams...)
out, err := ExecuteCommand(env, "go", args...)
if err != nil {
| | | 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
var env []string
env = append(env, EnvDirectProxy...)
env = append(env, EnvGoVCS...)
args := []string{"test", pkg}
args = append(args, testParams...)
out, err := ExecuteCommand(env, "go", args...)
if err != nil {
for line := range zerostrings.SplitLineSeq(out) {
if strings.HasPrefix(line, "ok") || strings.HasPrefix(line, "?") {
continue
}
fmt.Fprintln(os.Stderr, line)
}
}
return err
|
| ︙ | ︙ | |||
156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
func checkStaticcheck() error {
out, err := ExecuteCommand(EnvGoVCS, "staticcheck", "./...")
if err != nil {
fmt.Fprintln(os.Stderr, "Some staticcheck problems found")
if len(out) > 0 {
fmt.Fprintln(os.Stderr, out)
}
}
return err
}
func checkRevive() error {
out, err := ExecuteCommand(EnvGoVCS, "revive", "./...")
if err != nil || out != "" {
| > > > > > > > > > > > | 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
func checkStaticcheck() error {
out, err := ExecuteCommand(EnvGoVCS, "staticcheck", "./...")
if err != nil {
fmt.Fprintln(os.Stderr, "Some staticcheck problems found")
if len(out) > 0 {
fmt.Fprintln(os.Stderr, out)
}
}
return err
}
func checkDeadcode() error {
out, err := ExecuteCommand(EnvGoVCS, "deadcode", "./...")
if err != nil || out != "" {
fmt.Fprintln(os.Stderr, "Some deadcode problems found")
if len(out) > 0 {
fmt.Fprintln(os.Stderr, out)
}
}
return err
}
func checkRevive() error {
out, err := ExecuteCommand(EnvGoVCS, "revive", "./...")
if err != nil || out != "" {
|
| ︙ | ︙ | |||
238 239 240 241 242 243 244 |
out, err := ExecuteCommand(nil, "fossil", "extra")
if err != nil {
fmt.Fprintln(os.Stderr, "Unable to execute 'fossil extra'")
return err
}
if len(out) > 0 {
fmt.Fprint(os.Stderr, "Warning: unversioned file(s):")
| > | | > > | 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
out, err := ExecuteCommand(nil, "fossil", "extra")
if err != nil {
fmt.Fprintln(os.Stderr, "Unable to execute 'fossil extra'")
return err
}
if len(out) > 0 {
fmt.Fprint(os.Stderr, "Warning: unversioned file(s):")
first := true
for extra := range zerostrings.SplitLineSeq(out) {
if first {
first = false
} else {
fmt.Fprint(os.Stderr, ",")
}
fmt.Fprintf(os.Stderr, " %q", extra)
}
fmt.Fprintln(os.Stderr)
}
return nil
}
|
Changes to www/changes.wiki.
1 2 3 4 5 6 7 8 9 10 11 |
<title>Change Log</title>
<a id="0_23"></a>
<h2>Changes for Version 0.23.0 (pending)</h2>
<a id="0_22"></a>
<h2>Changes for Version 0.22.0 (2025-07-07)</h2>
* Sx builtin <code>(bind-lookup ...)</code> is replaced with
<code>(resolve-symbol ...)</code>. If you maintain your own Sx code to
customize Zettelstore behaviour, you must update your code; otherwise it
will break. If your code use <code>(ROLE-DEFAULT-action ...)</code>
| > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<title>Change Log</title>
<a id="0_23"></a>
<h2>Changes for Version 0.23.0 (pending)</h2>
* SZ encoding of lists (ordered, unordered, and quotations) has been
simplified by allowing only block elements. Previously, inline elements
were also permitted to signal a compact list. However, this behavior is
only relevant for HTML generation. Therefore, the detection of compact
lists is now performed exclusively during HTML generation.
(breaking)
* SZ encoding of BLOBs has be changed from <code>(BLOB attrs inlines syntax
data)</code> to <code>(BLOG attrs syntax data inline ...)</code>.
(breaking)
<a id="0_22"></a>
<h2>Changes for Version 0.22.0 (2025-07-07)</h2>
* Sx builtin <code>(bind-lookup ...)</code> is replaced with
<code>(resolve-symbol ...)</code>. If you maintain your own Sx code to
customize Zettelstore behaviour, you must update your code; otherwise it
will break. If your code use <code>(ROLE-DEFAULT-action ...)</code>
|
| ︙ | ︙ |