Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From v0.8.0 To v0.7.0
2022-10-24
| ||
15:42 | Increase version to 0.9.0-dev to begin next development cycle ... (check-in: f38439155f user: stern tags: trunk) | |
2022-10-20
| ||
17:44 | Version 0.8.0 ... (check-in: 2790cb4cf1 user: stern tags: trunk, release, v0.8.0) | |
12:39 | Add manual to help when HTML content is not shown ... (check-in: f8a4ebc639 user: stern tags: trunk) | |
2022-09-18
| ||
12:52 | Increase version to 0.7.1-dev to begin release update development cycle ... (check-in: f005505248 user: stern tags: release-0.7) | |
2022-09-17
| ||
15:10 | Increase version to 0.8.0-dev to begin next development cycle ... (check-in: 97bb0f0d93 user: t73fde tags: trunk) | |
12:28 | Version 0.7.0 ... (check-in: f2b8d470c7 user: stern tags: trunk, release, v0.7.0) | |
11:54 | Update to newest go/x/crypto, go/x/sys ... (check-in: 61528d5462 user: stern tags: trunk) | |
Changes to VERSION.
|
| | | 1 | 0.7.0 |
Changes to ast/inline.go.
︙ | ︙ | |||
50 51 52 53 54 55 56 57 58 59 60 61 62 63 | Text string // The text itself. } func (*TextNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. func (*TextNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // SpaceNode tracks inter-word space characters. type SpaceNode struct { Lexeme string } | > > > > > > > > > > > > | 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 | Text string // The text itself. } func (*TextNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. func (*TextNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // TagNode contains a tag. type TagNode struct { Tag string // The text itself. } func (*TagNode) inlineNode() { /* Just a marker */ } // WalkChildren does nothing. func (*TagNode) WalkChildren(Visitor) { /* No children*/ } // -------------------------------------------------------------------------- // SpaceNode tracks inter-word space characters. type SpaceNode struct { Lexeme string } |
︙ | ︙ |
Changes to box/constbox/base.css.
︙ | ︙ | |||
180 181 182 183 184 185 186 187 188 189 190 191 192 193 | div.zs-indication p:first-child { margin-top: 0 } span.zs-indication { border: 1px solid black; border-radius: .25rem; padding: .1rem .2rem; font-size: 95%; } .zs-info { background-color: lightblue; padding: .5rem 1rem; } .zs-warning { background-color: lightyellow; padding: .5rem 1rem; | > | 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | div.zs-indication p:first-child { margin-top: 0 } span.zs-indication { border: 1px solid black; border-radius: .25rem; padding: .1rem .2rem; font-size: 95%; } .zs-example { border-style: dotted !important } .zs-info { background-color: lightblue; padding: .5rem 1rem; } .zs-warning { background-color: lightyellow; padding: .5rem 1rem; |
︙ | ︙ |
Changes to box/constbox/base.mustache.
︙ | ︙ | |||
39 40 41 42 43 44 45 | <a href="{{{ListRolesURL}}}">List Roles</a> <a href="{{{ListTagsURL}}}">List Tags</a> {{#CanRefresh}} <a href="{{{RefreshURL}}}">Refresh</a> {{/CanRefresh}} </nav> </div> | | | | | | 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | <a href="{{{ListRolesURL}}}">List Roles</a> <a href="{{{ListTagsURL}}}">List Tags</a> {{#CanRefresh}} <a href="{{{RefreshURL}}}">Refresh</a> {{/CanRefresh}} </nav> </div> {{#HasNewZettelLinks}} <div class="zs-dropdown"> <button>New</button> <nav class="zs-dropdown-content"> {{#NewZettelLinks}} <a href="{{{URL}}}">{{Text}}</a> {{/NewZettelLinks}} </nav> </div> {{/HasNewZettelLinks}} <form action="{{{SearchURL}}}"> <input type="text" placeholder="Search.." name="{{QueryKeyQuery}}"> </form> </nav> <main class="content"> {{{Content}}} </main> |
︙ | ︙ |
Changes to box/constbox/constbox.go.
︙ | ︙ | |||
182 183 184 185 186 187 188 | api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, api.KeyCreated: "20210504135842", | | | 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | api.KeyTitle: "Zettelstore Dependencies", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: api.ValueSyntaxZmk, api.KeyLang: api.ValueLangEN, api.KeyReadOnly: api.ValueTrue, api.KeyVisibility: api.ValueVisibilityLogin, api.KeyCreated: "20210504135842", api.KeyModified: "20220824161200", }, domain.NewContent(contentDependencies)}, id.BaseTemplateZid: { constHeader{ api.KeyTitle: "Zettelstore Base HTML Template", api.KeyRole: api.ValueRoleConfiguration, api.KeySyntax: syntaxTemplate, |
︙ | ︙ |
Changes to box/constbox/delete.mustache.
1 2 3 4 5 6 7 8 9 10 11 | <article> <header> <h1>Delete Zettel {{Zid}}</h1> </header> <p>Do you really want to delete this zettel?</p> {{#HasShadows}} <div class="zs-info"> <h2>Infomation</h2> <p>If you delete this zettel, the previoulsy shadowed zettel from overlayed box {{ShadowedBox}} becomes available.</p> </div> {{/HasShadows}} | | | | | | 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 | <article> <header> <h1>Delete Zettel {{Zid}}</h1> </header> <p>Do you really want to delete this zettel?</p> {{#HasShadows}} <div class="zs-info"> <h2>Infomation</h2> <p>If you delete this zettel, the previoulsy shadowed zettel from overlayed box {{ShadowedBox}} becomes available.</p> </div> {{/HasShadows}} {{#HasIncoming}} <div class="zs-warning"> <h2>Warning!</h2> <p>If you delete this zettel, incoming references from the following zettel will become invalid.</p> <ul> {{#Incoming}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/Incoming}} </ul> </div> {{/HasIncoming}} {{#HasUselessFiles}} <div class="zs-warning"> <h2>Warning!</h2> <p>Deleting this zettel will also delete the following files, so that they will not be interpreted as content for this zettel.</p> <ul> {{#UselessFiles}} <li>{{{.}}}</li> |
︙ | ︙ |
Changes to box/constbox/dependencies.zettel.
︙ | ︙ | |||
70 71 72 73 74 75 76 | ; URL : [[https://fsnotify.org/]] ; License : BSD 3-Clause "New" or "Revised" License ; Source : [[https://github.com/fsnotify/fsnotify]] ``` | | | | | > | | | | | > | | | | | | | > | | | | | | | 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 | ; URL : [[https://fsnotify.org/]] ; License : BSD 3-Clause "New" or "Revised" License ; Source : [[https://github.com/fsnotify/fsnotify]] ``` Copyright (c) 2012 The Go Authors. All rights reserved. Copyright (c) 2012-2019 fsnotify Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` === gopikchr ; URL & Source : [[https://github.com/gopikchr/gopikchr]] ; License : MIT License |
︙ | ︙ |
Changes to box/constbox/info.mustache.
1 2 3 4 5 6 | <article> <header> <h1>Information for Zettel {{Zid}}</h1> <a href="{{{WebURL}}}">Web</a> · <a href="{{{ContextURL}}}">Context</a> {{#CanWrite}} · <a href="{{{EditURL}}}">Edit</a>{{/CanWrite}} | < < > | | | | | 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 | <article> <header> <h1>Information for Zettel {{Zid}}</h1> <a href="{{{WebURL}}}">Web</a> · <a href="{{{ContextURL}}}">Context</a> {{#CanWrite}} · <a href="{{{EditURL}}}">Edit</a>{{/CanWrite}} {{#CanFolge}} · <a href="{{{FolgeURL}}}">Folge</a>{{/CanFolge}} {{#CanCopy}} · <a href="{{{CopyURL}}}">Copy</a>{{/CanCopy}} {{#CanRename}}· <a href="{{{RenameURL}}}">Rename</a>{{/CanRename}} {{#CanDelete}}· <a href="{{{DeleteURL}}}">Delete</a>{{/CanDelete}} </header> <h2>Interpreted Metadata</h2> <table>{{#MetaData}}<tr><td>{{Key}}</td><td>{{{Value}}}</td></tr>{{/MetaData}}</table> <h2>References</h2> {{#HasLocLinks}} <h3>Local</h3> <ul> {{#LocLinks}} {{#Valid}}<li><a href="{{{Zid}}}">{{Zid}}</a></li>{{/Valid}} {{^Valid}}<li>{{Zid}}</li>{{/Valid}} {{/LocLinks}} </ul> {{/HasLocLinks}} {{#HasQueryLinks}} <h3>Queries</h3> <ul> {{#QueryLinks}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/QueryLinks}} </ul> {{/HasQueryLinks}} {{#HasExtLinks}} <h3>External</h3> <ul> {{#ExtLinks}} <li><a href="{{{.}}}"{{{ExtNewWindow}}}>{{.}}</a></li> {{/ExtLinks}} </ul> |
︙ | ︙ |
Changes to box/constbox/rename.mustache.
1 2 3 4 5 | <article> <header> <h1>Rename Zettel {{Zid}}</h1> </header> <p>Do you really want to rename this zettel?</p> | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <article> <header> <h1>Rename Zettel {{Zid}}</h1> </header> <p>Do you really want to rename this zettel?</p> {{#HasIncoming}} <div class="zs-warning"> <h2>Warning!</h2> <p>If you rename this zettel, incoming references from the following zettel will become invalid.</p> <ul> {{#Incoming}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/Incoming}} </ul> </div> {{/HasIncoming}} {{#HasUselessFiles}} <div class="zs-warning"> <h2>Warning!</h2> <p>Renaming this zettel will also delete the following files, so that they will not be interpreted as content for a zettel with identifier {{Zid}}.</p> <ul> {{#UselessFiles}} <li>{{{.}}}</li> |
︙ | ︙ |
Changes to box/constbox/zettel.mustache.
1 2 3 4 5 6 7 8 | <article> <header> <h1>{{{HTMLTitle}}}</h1> <div class="zs-meta"> {{#CanWrite}}<a href="{{{EditURL}}}">Edit</a> ·{{/CanWrite}} {{Zid}} · <a href="{{{InfoURL}}}">Info</a> · (<a href="{{{RoleURL}}}">{{RoleText}}</a>) | | < < < | > | | > | | > | | < < < < < < < < | < | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | <article> <header> <h1>{{{HTMLTitle}}}</h1> <div class="zs-meta"> {{#CanWrite}}<a href="{{{EditURL}}}">Edit</a> ·{{/CanWrite}} {{Zid}} · <a href="{{{InfoURL}}}">Info</a> · (<a href="{{{RoleURL}}}">{{RoleText}}</a>) {{#HasTags}}· {{#Tags}} <a href="{{{URL}}}">{{Text}}</a>{{/Tags}}{{/HasTags}} {{#CanCopy}}· <a href="{{{CopyURL}}}">Copy</a>{{/CanCopy}} {{#CanFolge}}· <a href="{{{FolgeURL}}}">Folge</a>{{/CanFolge}} {{#PrecursorRefs}}<br>Precursor: {{{PrecursorRefs}}}{{/PrecursorRefs}} {{#HasExtURL}}<br>URL: <a href="{{{ExtURL}}}"{{{ExtNewWindow}}}>{{ExtURL}}</a>{{/HasExtURL}} {{#Author}}<br>By {{Author}}{{/Author}} </div> </header> {{{Content}}} </article> {{#HasFolgeLinks}} <nav> <details open> <summary>Folgezettel</summary> <ul> {{#FolgeLinks}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/FolgeLinks}} </ul> </details> </nav> {{/HasFolgeLinks}} {{#HasBackLinks}} <nav> <details open> <summary>Incoming</summary> <ul> {{#BackLinks}} <li><a href="{{{URL}}}">{{Text}}</a></li> {{/BackLinks}} </ul> </details> </nav> {{/HasBackLinks}} |
Changes to box/manager/collect.go.
︙ | ︙ | |||
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 | "zettelstore.de/z/strfun" ) type collectData struct { refs id.Set words store.WordSet urls store.WordSet } func (data *collectData) initialize() { data.refs = id.NewSet() data.words = store.NewWordSet() data.urls = store.NewWordSet() } func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) { ast.Walk(data, &zn.Ast) } func collectInlineIndexData(is *ast.InlineSlice, data *collectData) { ast.Walk(data, is) } func (data *collectData) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.VerbatimNode: data.addText(string(n.Content)) case *ast.TranscludeNode: data.addRef(n.Ref) case *ast.TextNode: data.addText(n.Text) case *ast.LinkNode: data.addRef(n.Ref) case *ast.EmbedRefNode: data.addRef(n.Ref) case *ast.CiteNode: data.addText(n.Key) case *ast.LiteralNode: | > > > > > | 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 | "zettelstore.de/z/strfun" ) type collectData struct { refs id.Set words store.WordSet urls store.WordSet itags store.WordSet } func (data *collectData) initialize() { data.refs = id.NewSet() data.words = store.NewWordSet() data.urls = store.NewWordSet() data.itags = store.NewWordSet() } func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) { ast.Walk(data, &zn.Ast) } func collectInlineIndexData(is *ast.InlineSlice, data *collectData) { ast.Walk(data, is) } func (data *collectData) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.VerbatimNode: data.addText(string(n.Content)) case *ast.TranscludeNode: data.addRef(n.Ref) case *ast.TextNode: data.addText(n.Text) case *ast.TagNode: data.addText(n.Tag) data.itags.Add("#" + strings.ToLower(n.Tag)) case *ast.LinkNode: data.addRef(n.Ref) case *ast.EmbedRefNode: data.addRef(n.Ref) case *ast.CiteNode: data.addText(n.Key) case *ast.LiteralNode: |
︙ | ︙ |
Changes to box/manager/indexer.go.
︙ | ︙ | |||
204 205 206 207 208 209 210 211 212 213 214 215 216 217 | zi.AddBackRef(ref) } else { zi.AddDeadRef(ref) } } zi.SetWords(cData.words) zi.SetUrls(cData.urls) } func (mgr *Manager) idxUpdateValue(ctx context.Context, inverseKey, value string, zi *store.ZettelIndex) { zid, err := id.Parse(value) if err != nil { return } | > | 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | zi.AddBackRef(ref) } else { zi.AddDeadRef(ref) } } zi.SetWords(cData.words) zi.SetUrls(cData.urls) zi.SetITags(cData.itags) } func (mgr *Manager) idxUpdateValue(ctx context.Context, inverseKey, value string, zi *store.ZettelIndex) { zid, err := id.Parse(value) if err != nil { return } |
︙ | ︙ |
Changes to box/manager/memstore/memstore.go.
︙ | ︙ | |||
34 35 36 37 38 39 40 41 42 43 44 45 46 47 | type zettelIndex struct { dead id.Slice forward id.Slice backward id.Slice meta map[string]metaRefs words []string urls []string } func (zi *zettelIndex) isEmpty() bool { if len(zi.forward) > 0 || len(zi.backward) > 0 || len(zi.dead) > 0 || len(zi.words) > 0 { return false } return len(zi.meta) == 0 | > | 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | type zettelIndex struct { dead id.Slice forward id.Slice backward id.Slice meta map[string]metaRefs words []string urls []string itags string // Inline tags } func (zi *zettelIndex) isEmpty() bool { if len(zi.forward) > 0 || len(zi.backward) > 0 || len(zi.dead) > 0 || len(zi.words) > 0 { return false } return len(zi.meta) == 0 |
︙ | ︙ | |||
107 108 109 110 111 112 113 114 115 116 117 118 119 120 | updated = true } } if len(back) > 0 { m.Set(api.KeyBack, back.String()) updated = true } return updated } // SearchEqual returns all zettel that contains the given exact word. // The word must be normalized through Unicode NKFD, trimmed and not empty. func (ms *memStore) SearchEqual(word string) id.Set { ms.mx.RLock() | > > > > > > > > > > > > | 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 | updated = true } } if len(back) > 0 { m.Set(api.KeyBack, back.String()) updated = true } if itags := zi.itags; itags != "" { m.Set(api.KeyContentTags, itags) if tags, ok2 := m.Get(api.KeyTags); ok2 { m.Set(api.KeyAllTags, tags+" "+itags) } else { m.Set(api.KeyAllTags, itags) } updated = true } else if tags, ok2 := m.Get(api.KeyTags); ok2 { m.Set(api.KeyAllTags, tags) updated = true } return updated } // SearchEqual returns all zettel that contains the given exact word. // The word must be normalized through Unicode NKFD, trimmed and not empty. func (ms *memStore) SearchEqual(word string) id.Set { ms.mx.RLock() |
︙ | ︙ | |||
144 145 146 147 148 149 150 151 152 153 154 155 | func (ms *memStore) SearchPrefix(prefix string) id.Set { ms.mx.RLock() defer ms.mx.RUnlock() result := ms.selectWithPred(prefix, strings.HasPrefix) l := len(prefix) if l > 14 { return result } maxZid, err := id.Parse(prefix + "99999999999999"[:14-l]) if err != nil { return result } | > > > > < < < < < < < < < | 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | func (ms *memStore) SearchPrefix(prefix string) id.Set { ms.mx.RLock() defer ms.mx.RUnlock() result := ms.selectWithPred(prefix, strings.HasPrefix) l := len(prefix) if l > 14 { return result } minZid, err := id.Parse(prefix + "00000000000000"[:13-l] + "1") if err != nil { return result } maxZid, err := id.Parse(prefix + "99999999999999"[:14-l]) if err != nil { return result } for zid, zi := range ms.idx { if minZid <= zid && zid <= maxZid { addBackwardZids(result, zid, zi) } } return result } |
︙ | ︙ | |||
279 280 281 282 283 284 285 286 287 288 289 290 291 292 | } ms.updateDeadReferences(zidx, zi) ms.updateForwardBackwardReferences(zidx, zi) ms.updateMetadataReferences(zidx, zi) zi.words = updateWordSet(zidx.Zid, ms.words, zi.words, zidx.GetWords()) zi.urls = updateWordSet(zidx.Zid, ms.urls, zi.urls, zidx.GetUrls()) // Check if zi must be inserted into ms.idx if !ziExist && !zi.isEmpty() { ms.idx[zidx.Zid] = zi } return toCheck | > | 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | } ms.updateDeadReferences(zidx, zi) ms.updateForwardBackwardReferences(zidx, zi) ms.updateMetadataReferences(zidx, zi) zi.words = updateWordSet(zidx.Zid, ms.words, zi.words, zidx.GetWords()) zi.urls = updateWordSet(zidx.Zid, ms.urls, zi.urls, zidx.GetUrls()) zi.itags = setITags(zidx.GetITags()) // Check if zi must be inserted into ms.idx if !ziExist && !zi.isEmpty() { ms.idx[zidx.Zid] = zi } return toCheck |
︙ | ︙ | |||
371 372 373 374 375 376 377 378 379 380 381 382 383 384 | delete(srefs, word) continue } srefs[word] = refs2 } return next.Words() } func (ms *memStore) getEntry(zid id.Zid) *zettelIndex { // Must only be called if ms.mx is write-locked! if zi, ok := ms.idx[zid]; ok { return zi } zi := &zettelIndex{} | > > > > > > > > > | 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 | delete(srefs, word) continue } srefs[word] = refs2 } return next.Words() } func setITags(next store.WordSet) string { itags := next.Words() if len(itags) == 0 { return "" } sort.Strings(itags) return strings.Join(itags, " ") } func (ms *memStore) getEntry(zid id.Zid) *zettelIndex { // Must only be called if ms.mx is write-locked! if zi, ok := ms.idx[zid]; ok { return zi } zi := &zettelIndex{} |
︙ | ︙ |
Changes to box/manager/store/zettel.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 23 24 25 26 27 28 29 | type ZettelIndex struct { Zid id.Zid // zid of the indexed zettel backrefs id.Set // set of back references metarefs map[string]id.Set // references to inverse keys deadrefs id.Set // set of dead references words WordSet urls WordSet } // NewZettelIndex creates a new zettel index. func NewZettelIndex(zid id.Zid) *ZettelIndex { return &ZettelIndex{ Zid: zid, backrefs: id.NewSet(), | > | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | type ZettelIndex struct { Zid id.Zid // zid of the indexed zettel backrefs id.Set // set of back references metarefs map[string]id.Set // references to inverse keys deadrefs id.Set // set of dead references words WordSet urls WordSet itags WordSet } // NewZettelIndex creates a new zettel index. func NewZettelIndex(zid id.Zid) *ZettelIndex { return &ZettelIndex{ Zid: zid, backrefs: id.NewSet(), |
︙ | ︙ | |||
55 56 57 58 59 60 61 62 63 64 65 66 67 68 | // SetWords sets the words to the given value. func (zi *ZettelIndex) SetWords(words WordSet) { zi.words = words } // SetUrls sets the words to the given value. func (zi *ZettelIndex) SetUrls(urls WordSet) { zi.urls = urls } // GetDeadRefs returns all dead references as a sorted list. func (zi *ZettelIndex) GetDeadRefs() id.Slice { return zi.deadrefs.Sorted() } // GetBackRefs returns all back references as a sorted list. func (zi *ZettelIndex) GetBackRefs() id.Slice { | > > > | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | // SetWords sets the words to the given value. func (zi *ZettelIndex) SetWords(words WordSet) { zi.words = words } // SetUrls sets the words to the given value. func (zi *ZettelIndex) SetUrls(urls WordSet) { zi.urls = urls } // SetITags sets the words to the given value. func (zi *ZettelIndex) SetITags(itags WordSet) { zi.itags = itags } // GetDeadRefs returns all dead references as a sorted list. func (zi *ZettelIndex) GetDeadRefs() id.Slice { return zi.deadrefs.Sorted() } // GetBackRefs returns all back references as a sorted list. func (zi *ZettelIndex) GetBackRefs() id.Slice { |
︙ | ︙ | |||
82 83 84 85 86 87 88 | } // GetWords returns a reference to the set of words. It must not be modified. func (zi *ZettelIndex) GetWords() WordSet { return zi.words } // GetUrls returns a reference to the set of URLs. It must not be modified. func (zi *ZettelIndex) GetUrls() WordSet { return zi.urls } | > > > | 86 87 88 89 90 91 92 93 94 95 | } // GetWords returns a reference to the set of words. It must not be modified. func (zi *ZettelIndex) GetWords() WordSet { return zi.words } // GetUrls returns a reference to the set of URLs. It must not be modified. func (zi *ZettelIndex) GetUrls() WordSet { return zi.urls } // GetITags returns a reference to the set of internal tags. It must not be modified. func (zi *ZettelIndex) GetITags() WordSet { return zi.itags } |
Changes to box/notify/fsdir.go.
︙ | ︙ | |||
97 98 99 100 101 102 103 | for fsdn.readAndProcessEvent() { } } func (fsdn *fsdirNotifier) readAndProcessEvent() bool { select { case <-fsdn.done: | < < < < < < < > | > < < < < | < < | | > > > | > < < < < | < < < < | < < < | < < | | | < < < < < < < < < | | | < | > > > | 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 | for fsdn.readAndProcessEvent() { } } func (fsdn *fsdirNotifier) readAndProcessEvent() bool { select { case <-fsdn.done: return false default: } select { case <-fsdn.done: return false case <-fsdn.refresh: listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) case err, ok := <-fsdn.base.Errors: if !ok { return false } select { case fsdn.events <- Event{Op: Error, Err: err}: case <-fsdn.done: return false } case ev, ok := <-fsdn.base.Events: if !ok { return false } if !fsdn.processEvent(&ev) { return false } } return true } func (fsdn *fsdirNotifier) processEvent(ev *fsnotify.Event) bool { if strings.HasPrefix(ev.Name, fsdn.path) { if len(ev.Name) == len(fsdn.path) { return fsdn.processDirEvent(ev) } return fsdn.processFileEvent(ev) } return true } func (fsdn *fsdirNotifier) processDirEvent(ev *fsnotify.Event) bool { const deleteFsDirOps = fsnotify.Remove | fsnotify.Rename if ev.Op&deleteFsDirOps != 0 { fsdn.log.Debug().Str("name", fsdn.path).Msg("Directory removed") fsdn.base.Remove(fsdn.path) select { case fsdn.events <- Event{Op: Destroy}: case <-fsdn.done: return false } } else if ev.Op&fsnotify.Create != 0 { err := fsdn.base.Add(fsdn.path) if err != nil { fsdn.log.IfErr(err).Str("name", fsdn.path).Msg("Unable to add directory") select { case fsdn.events <- Event{Op: Error, Err: err}: case <-fsdn.done: return false } } fsdn.log.Debug().Str("name", fsdn.path).Msg("Directory added") return listDirElements(fsdn.log, fsdn.fetcher, fsdn.events, fsdn.done) } else { fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("Directory processed") } return true } func (fsdn *fsdirNotifier) processFileEvent(ev *fsnotify.Event) bool { const deleteFsFileOps = fsnotify.Remove const updateFsFileOps = fsnotify.Create | fsnotify.Write | fsnotify.Rename if ev.Op&updateFsFileOps != 0 { if fi, err := os.Lstat(ev.Name); err != nil || !fi.Mode().IsRegular() { return true } fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File updated") select { case fsdn.events <- Event{Op: Update, Name: filepath.Base(ev.Name)}: case <-fsdn.done: return false } } else if ev.Op&deleteFsFileOps != 0 { fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File deleted") select { case fsdn.events <- Event{Op: Delete, Name: filepath.Base(ev.Name)}: case <-fsdn.done: return false } } else { fsdn.log.Trace().Str("name", ev.Name).Str("op", ev.Op.String()).Msg("File processed") } return true } func (fsdn *fsdirNotifier) Close() { close(fsdn.done) } |
Changes to cmd/cmd_run.go.
︙ | ︙ | |||
69 70 71 72 73 74 75 76 77 78 79 80 81 82 | ucGetAllMeta := usecase.NewGetAllMeta(protectedBoxManager) ucGetZettel := usecase.NewGetZettel(protectedBoxManager) ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel) ucListMeta := usecase.NewListMeta(protectedBoxManager) ucEvaluate := usecase.NewEvaluate(rtConfig, ucGetZettel, ucGetMeta, ucListMeta) ucListSyntax := usecase.NewListSyntax(protectedBoxManager) ucListRoles := usecase.NewListRoles(protectedBoxManager) ucZettelContext := usecase.NewZettelContext(protectedBoxManager, rtConfig) ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager) ucRename := usecase.NewRenameZettel(logUc, protectedBoxManager) ucUnlinkedRefs := usecase.NewUnlinkedReferences(protectedBoxManager, rtConfig) ucRefresh := usecase.NewRefresh(logUc, protectedBoxManager) ucVersion := usecase.NewVersion(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) | > | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | ucGetAllMeta := usecase.NewGetAllMeta(protectedBoxManager) ucGetZettel := usecase.NewGetZettel(protectedBoxManager) ucParseZettel := usecase.NewParseZettel(rtConfig, ucGetZettel) ucListMeta := usecase.NewListMeta(protectedBoxManager) ucEvaluate := usecase.NewEvaluate(rtConfig, ucGetZettel, ucGetMeta, ucListMeta) ucListSyntax := usecase.NewListSyntax(protectedBoxManager) ucListRoles := usecase.NewListRoles(protectedBoxManager) ucListTags := usecase.NewListTags(protectedBoxManager) ucZettelContext := usecase.NewZettelContext(protectedBoxManager, rtConfig) ucDelete := usecase.NewDeleteZettel(logUc, protectedBoxManager) ucUpdate := usecase.NewUpdateZettel(logUc, protectedBoxManager) ucRename := usecase.NewRenameZettel(logUc, protectedBoxManager) ucUnlinkedRefs := usecase.NewUnlinkedReferences(protectedBoxManager, rtConfig) ucRefresh := usecase.NewRefresh(logUc, protectedBoxManager) ucVersion := usecase.NewVersion(kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) |
︙ | ︙ | |||
111 112 113 114 115 116 117 | ucParseZettel, &ucEvaluate, ucGetMeta, ucGetAllMeta, ucUnlinkedRefs)) webSrv.AddZettelRoute('k', server.MethodGet, wui.MakeZettelContextHandler( ucZettelContext, &ucEvaluate)) // API webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate)) webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler()) | | > | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | ucParseZettel, &ucEvaluate, ucGetMeta, ucGetAllMeta, ucUnlinkedRefs)) webSrv.AddZettelRoute('k', server.MethodGet, wui.MakeZettelContextHandler( ucZettelContext, &ucEvaluate)) // API webSrv.AddListRoute('a', server.MethodPost, a.MakePostLoginHandler(&ucAuthenticate)) webSrv.AddListRoute('a', server.MethodPut, a.MakeRenewAuthHandler()) webSrv.AddListRoute('j', server.MethodGet, a.MakeListMetaHandler(ucListMeta)) webSrv.AddZettelRoute('j', server.MethodGet, a.MakeGetZettelHandler(ucGetZettel)) webSrv.AddListRoute('m', server.MethodGet, a.MakeListMapMetaHandler(ucListRoles, ucListTags)) webSrv.AddZettelRoute('m', server.MethodGet, a.MakeGetMetaHandler(ucGetMeta)) webSrv.AddZettelRoute('o', server.MethodGet, a.MakeGetOrderHandler( usecase.NewZettelOrder(protectedBoxManager, ucEvaluate))) webSrv.AddZettelRoute('p', server.MethodGet, a.MakeGetParsedZettelHandler(ucParseZettel)) webSrv.AddListRoute('q', server.MethodGet, a.MakeQueryHandler(ucListMeta)) webSrv.AddZettelRoute('u', server.MethodGet, a.MakeListUnlinkedMetaHandler( ucGetMeta, ucUnlinkedRefs, &ucEvaluate)) |
︙ | ︙ |
Changes to cmd/main.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // under this license. //----------------------------------------------------------------------------- package cmd import ( "crypto/sha256" "flag" "fmt" "net" "net/url" "os" "runtime/debug" "strconv" | > | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // under this license. //----------------------------------------------------------------------------- package cmd import ( "crypto/sha256" "errors" "flag" "fmt" "net" "net/url" "os" "runtime/debug" "strconv" |
︙ | ︙ | |||
118 119 120 121 122 123 124 | } func getConfig(fs *flag.FlagSet) *meta.Meta { cfg := fetchStartupConfiguration(fs) fs.Visit(func(flg *flag.Flag) { switch flg.Name { case "p": | > | > > | > | 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | } func getConfig(fs *flag.FlagSet) *meta.Meta { cfg := fetchStartupConfiguration(fs) fs.Visit(func(flg *flag.Flag) { switch flg.Name { case "p": if portStr, err := parsePort(flg.Value.String()); err == nil { cfg.Set(keyListenAddr, net.JoinHostPort("127.0.0.1", portStr)) } case "a": if portStr, err := parsePort(flg.Value.String()); err == nil { cfg.Set(keyAdminPort, portStr) } case "d": val := flg.Value.String() if strings.HasPrefix(val, "/") { val = "dir://" + val } else { val = "dir:" + val } |
︙ | ︙ | |||
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | cfg.Set(keyReadOnly, flg.Value.String()) case "v": cfg.Set(keyVerbose, flg.Value.String()) } }) return cfg } func deleteConfiguredBoxes(cfg *meta.Meta) { for _, p := range cfg.PairsRest() { if key := p.Key; strings.HasPrefix(key, kernel.BoxURIs) { cfg.Delete(key) } } } const ( keyAdminPort = "admin-port" keyAssetDir = "asset-dir" keyBaseURL = "base-url" keyDebug = "debug-mode" keyDefaultDirBoxType = "default-dir-box-type" keyInsecureCookie = "insecure-cookie" | > > > > > > > > > < | | | | | | | | | | | | | | | | | | | | | | | > > > > | | < | | | | < | | | | 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 | cfg.Set(keyReadOnly, flg.Value.String()) case "v": cfg.Set(keyVerbose, flg.Value.String()) } }) return cfg } func parsePort(s string) (string, error) { port, err := net.LookupPort("tcp", s) if err != nil { fmt.Fprintf(os.Stderr, "Wrong port specification: %q", s) return "", err } return strconv.Itoa(port), nil } func deleteConfiguredBoxes(cfg *meta.Meta) { for _, p := range cfg.PairsRest() { if key := p.Key; strings.HasPrefix(key, kernel.BoxURIs) { cfg.Delete(key) } } } const ( keyAdminPort = "admin-port" keyAssetDir = "asset-dir" keyBaseURL = "base-url" keyDebug = "debug-mode" keyDefaultDirBoxType = "default-dir-box-type" keyInsecureCookie = "insecure-cookie" keyListenAddr = "listen-addr" keyLogLevel = "log-level" keyMaxRequestSize = "max-request-size" keyOwner = "owner" keyPersistentCookie = "persistent-cookie" keyBoxOneURI = kernel.BoxURIs + "1" keyReadOnly = "read-only-mode" keyTokenLifetimeHTML = "token-lifetime-html" keyTokenLifetimeAPI = "token-lifetime-api" keyURLPrefix = "url-prefix" keyVerbose = "verbose-mode" ) func setServiceConfig(cfg *meta.Meta) error { debugMode := cfg.GetBool(keyDebug) if debugMode && kernel.Main.GetKernelLogger().Level() > logger.DebugLevel { kernel.Main.SetGlobalLogLevel(logger.DebugLevel) } if strLevel, found := cfg.Get(keyLogLevel); found { if level := logger.ParseLevel(strLevel); level.IsValid() { kernel.Main.SetGlobalLogLevel(level) } } ok := setConfigValue(true, kernel.CoreService, kernel.CoreDebug, debugMode) ok = setConfigValue(ok, kernel.CoreService, kernel.CoreVerbose, cfg.GetBool(keyVerbose)) if val, found := cfg.Get(keyAdminPort); found { ok = setConfigValue(ok, kernel.CoreService, kernel.CorePort, val) } ok = setConfigValue(ok, kernel.AuthService, kernel.AuthOwner, cfg.GetDefault(keyOwner, "")) ok = setConfigValue(ok, kernel.AuthService, kernel.AuthReadonly, cfg.GetBool(keyReadOnly)) ok = setConfigValue( ok, kernel.BoxService, kernel.BoxDefaultDirType, cfg.GetDefault(keyDefaultDirBoxType, kernel.BoxDirTypeNotify)) ok = setConfigValue(ok, kernel.BoxService, kernel.BoxURIs+"1", "dir:./zettel") for i := 1; ; i++ { key := kernel.BoxURIs + strconv.Itoa(i) val, found := cfg.Get(key) if !found { break } ok = setConfigValue(ok, kernel.BoxService, key, val) } ok = setConfigValue( ok, kernel.WebService, kernel.WebListenAddress, cfg.GetDefault(keyListenAddr, "127.0.0.1:23123")) if val, found := cfg.Get(keyBaseURL); found { ok = setConfigValue(ok, kernel.WebService, kernel.WebBaseURL, val) } if val, found := cfg.Get(keyURLPrefix); found { ok = setConfigValue(ok, kernel.WebService, kernel.WebURLPrefix, val) } ok = setConfigValue(ok, kernel.WebService, kernel.WebSecureCookie, !cfg.GetBool(keyInsecureCookie)) ok = setConfigValue(ok, kernel.WebService, kernel.WebPersistentCookie, cfg.GetBool(keyPersistentCookie)) if val, found := cfg.Get(keyMaxRequestSize); found { ok = setConfigValue(ok, kernel.WebService, kernel.WebMaxRequestSize, val) } ok = setConfigValue( ok, kernel.WebService, kernel.WebTokenLifetimeAPI, cfg.GetDefault(keyTokenLifetimeAPI, "")) ok = setConfigValue( ok, kernel.WebService, kernel.WebTokenLifetimeHTML, cfg.GetDefault(keyTokenLifetimeHTML, "")) if val, found := cfg.Get(keyAssetDir); found { ok = setConfigValue(ok, kernel.WebService, kernel.WebAssetDir, val) } if !ok { return errors.New("unable to set configuration") } return nil } func setConfigValue(ok bool, subsys kernel.Service, key string, val interface{}) bool { done := kernel.Main.SetConfig(subsys, key, fmt.Sprintf("%v", val)) if !done { kernel.Main.GetKernelLogger().Error().Str(key, fmt.Sprint(val)).Msg("Unable to set configuration") } return ok && done } func executeCommand(name string, args ...string) int { command, ok := Get(name) if !ok { fmt.Fprintf(os.Stderr, "Unknown command %q\n", name) return 1 } fs := command.GetFlags() if err := fs.Parse(args); err != nil { fmt.Fprintf(os.Stderr, "%s: unable to parse flags: %v %v\n", name, args, err) return 1 } cfg := getConfig(fs) if err := setServiceConfig(cfg); err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", name, err) return 2 } kern := kernel.Main var createManager kernel.CreateBoxManagerFunc if command.Boxes { createManager = func(boxURIs []*url.URL, authManager auth.Manager, rtConfig config.Config) (box.Manager, error) { |
︙ | ︙ |
Changes to config/config.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | // Package config provides functions to retrieve runtime configuration data. package config import ( "context" | < | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // Package config provides functions to retrieve runtime configuration data. package config import ( "context" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // Key values that are supported by Config.Get const ( KeyFooterHTML = "footer-html" |
︙ | ︙ | |||
39 40 41 42 43 44 45 | // GetSiteName returns the current value of the "site-name" key. GetSiteName() string // GetHomeZettel returns the value of the "home-zettel" key. GetHomeZettel() id.Zid | < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 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 | // GetSiteName returns the current value of the "site-name" key. GetSiteName() string // GetHomeZettel returns the value of the "home-zettel" key. GetHomeZettel() id.Zid // GetMaxTransclusions return the maximum number of indirect transclusions. GetMaxTransclusions() int // GetYAMLHeader returns the current value of the "yaml-header" key. GetYAMLHeader() bool // GetZettelFileSyntax returns the current value of the "zettel-file-syntax" key. GetZettelFileSyntax() []string } // AuthConfig are relevant configuration values for authentication. type AuthConfig interface { // GetSimpleMode returns true if system tuns in simple-mode. GetSimpleMode() bool // GetExpertMode returns the current value of the "expert-mode" key. GetExpertMode() bool // GetVisibility returns the visibility value of the metadata. GetVisibility(m *meta.Meta) meta.Visibility } |
Deleted docs/manual/00000000025001.
|
| < < < < < < < |
Deleted docs/manual/00000000025001.css.
|
| < < |
Changes to docs/manual/00001002000000.zettel.
1 2 3 4 5 | id: 00001002000000 title: Design goals for the Zettelstore role: manual tags: #design #goal #manual #zettelstore syntax: zmk | < | | < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | id: 00001002000000 title: Design goals for the Zettelstore role: manual tags: #design #goal #manual #zettelstore syntax: zmk modified: 20211124131628 Zettelstore supports the following design goals: ; Longevity of stored notes / zettel : Every zettel you create should be readable without the help of any tool, even without Zettelstore. : It should be not hard to write other software that works with your zettel. ; Single user : All zettel belong to you, only to you. Zettelstore provides its services only to one person: you. If your device is securely configured, there should be no risk that others are able to read or update your zettel. : If you want, you can customize Zettelstore in a way that some specific or all persons are able to read some of your zettel. ; Ease of installation : If you want to use the Zettelstore software, all you need is to copy the executable to an appropriate file directory and start working. : Upgrading the software is done just by replacing the executable with a newer one. ; Ease of operation : There is only one executable for Zettelstore and one directory, where your zettel are stored. : If you decide to use multiple directories, you are free to configure Zettelstore appropriately. ; Multiple modes of operation : You can use Zettelstore as a standalone software on your device, but you are not restricted to it. : You can install the software on a central server, or you can install it on all your devices with no restrictions how to synchronize your zettel. ; Multiple user interfaces : Zettelstore provides a default [[web-based user interface|00001014000000]]. Anybody can provide alternative user interfaces, e.g. for special purposes. ; Simple service : The purpose of Zettelstore is to safely store your zettel and to provide some initial relations between them. : External software can be written to deeply analyze your zettel and the structures they form. |
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: 20220914183434 The configuration file, as specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup options. These options cannot be stored in a [[configuration zettel|00001004020000]] because either they are needed before Zettelstore can start or because of security reasons. For example, Zettelstore need to know in advance, on which network address is 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. |
︙ | ︙ | |||
64 65 66 67 68 69 70 | Default: ""notify"" ; [!insecure-cookie|''insecure-cookie''] : Must be set to [[true|00001006030500]], if authentication is enabled and Zettelstore is not accessible not via HTTPS (but via HTTP). Otherwise web browser are free to ignore the authentication cookie. Default: ""false"" | < < < < < < < < < < | 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | Default: ""notify"" ; [!insecure-cookie|''insecure-cookie''] : Must be set to [[true|00001006030500]], if authentication is enabled and Zettelstore is not accessible not via HTTPS (but via HTTP). Otherwise web browser are free to ignore the authentication cookie. Default: ""false"" ; [!listen-addr|''listen-addr''] : Configures the network address, where the Zettelstore service is listening for requests. Syntax is: ''[NETWORKIP]:PORT'', where ''NETWORKIP'' is the IP-address of the networking interface (or something like ""0.0.0.0"" if you want to listen on all network interfaces, and ''PORT'' is the TCP port. Default value: ""127.0.0.1:23123"" ; [!log-level|''log-level''] : Specify the global [[logging level|00001004059700]] for the whole application, overwriting the level ""debug"" set by configuration [[''debug-mode''|#debug-mode]]. |
︙ | ︙ |
Changes to docs/manual/00001006020000.zettel.
1 2 3 4 5 6 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 | | > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | id: 00001006020000 title: Supported Metadata Keys role: manual tags: #manual #meta #reference #zettel #zettelstore syntax: zmk created: 20210126175322 modified: 20220915181826 Although you are free to define your own metadata, by using any key (according to the [[syntax|00001006010000]]), some keys have a special meaning that is enforced by Zettelstore. See the [[computed list of supported metadata keys|00000000000090]] for details. Most keys conform to a [[type|00001006030000]]. ; [!all-tags|''all-tags''] : A property (a computed values that is not stored) that contains both the value of [[''tags''|#tags]] and the value of [[''content-tags''|#content-tags]]. ; [!author|''author''] : A string value describing the author of a zettel. If given, it will be shown in the [[web user interface|00001014000000]] for the zettel. ; [!back|''back''] : Is a property that contains the identifier of all zettel that reference the zettel of this metadata, that are not referenced by this zettel. Basically, it is the value of [[''backward''|#backward]], but without any zettel identifier that is contained in [[''forward''|#forward]]. ; [!backward|''backward''] : Is a property that contains the identifier of all zettel that reference the zettel of this metadata. References within invertible values are not included here, e.g. [[''precursor''|#precursor]]. ; [!box-number|''box-number''] : Is a computed value and contains the number of the box where the zettel was found. For all but the [[predefined zettel|00001005090000]], this number is equal to the number __X__ specified in startup configuration key [[''box-uri-__X__''|00001004010000#box-uri-x]]. ; [!content-tags|''content-tags''] : A property that contains all [[inline tags|00001007040000#tag]] defined within the content. ; [!copyright|''copyright''] : Defines a copyright string that will be encoded. If not given, the value ''default-copyright'' from the [[configuration zettel|00001004020000#default-copyright]] will be used. ; [!created|''created''] : Date and time when a zettel was created through Zettelstore. If you create a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value. |
︙ | ︙ | |||
66 67 68 69 70 71 72 | If you edit a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value. This is a computed value. There is no need to set it via Zettelstore. ; [!precursor|''precursor''] : References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel. Basically the inverse of key [[''folge''|#folge]]. | < < < < < < < < < < | 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 | If you edit a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value. This is a computed value. There is no need to set it via Zettelstore. ; [!precursor|''precursor''] : References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel. Basically the inverse of key [[''folge''|#folge]]. ; [!published|''published''] : This property contains the timestamp of the mast modification / creation of the zettel. If [[''modified''|#modified]] is set with a valid timestamp, it contains the its value. Otherwise, if [[''created''|#created]] is set with a valid timestamp, it contains the its value. Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used. In all other cases, this property is not set. It can be used for [[sorting|00001007700000]] zettel based on their publication date. It is a computed value. There is no need to set it via Zettelstore. ; [!read-only|''read-only''] : Marks a zettel as read-only. The interpretation of [[supported values|00001006020400]] for this key depends, whether authentication is [[enabled|00001010040100]] or not. ; [!role|''role''] : Defines the role of the zettel. Can be used for selecting zettel. See [[supported zettel roles|00001006020100]]. If not given, it is ignored. ; [!syntax|''syntax''] : Specifies the syntax that should be used for interpreting the zettel. The zettel about [[other markup languages|00001008000000]] defines supported values. If it is not given, it defaults to ''plain''. ; [!tags|''tags''] : Contains a space separated list of tags to describe the zettel further. Each Tag must begin with the number sign character (""''#''"", U+0023). |
︙ | ︙ |
Changes to docs/manual/00001007000000.zettel.
1 2 3 4 5 6 | id: 00001007000000 title: Zettelmarkup role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 | | | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | id: 00001007000000 title: Zettelmarkup role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220913135505 Zettelmarkup is a rich plain-text based markup language for writing zettel content. Besides the zettel content, Zettelmarkup is also used for specifying the title of a zettel, regardless of the syntax of a zettel. Zettelmarkup supports the longevity of stored notes by providing a syntax that any person can easily read, as well as a computer. Zettelmarkup can be much easier parsed / consumed by a software compared to other markup languages. Writing a parser for [[Markdown|https://daringfireball.net/projects/markdown/syntax]] is quite challenging. [[CommonMark|00001008010500]] is an attempt to make it simpler by providing a comprehensive specification, combined with an extra chapter to give hints for the implementation. Zettelmarkup follows some simple principles that anybody who knows to ho write software should be able understand to create an implementation. Zettelmarkup is a markup language on its own. This is in contrast to Markdown, which is basically a super-set of HTML. While HTML is a markup language that will probably last for a long time, it cannot be easily translated to other formats, such as PDF, JSON, or LaTeX. Additionally, it is allowed to embed other languages into HTML, such as CSS or even JavaScript. This could create problems with longevity as well as security problems. Zettelmarkup is a rich markup language, but it focuses on relatively short zettel content. It allows embedding other content, simple tables, quotations, description lists, and images. It provides a broad range of inline formatting, including __emphasized__, **strong**, ~~deleted~~{-} and >>inserted>> text. Footnotes[^like this] are supported, links to other zettel and to external material, as well as citation keys. Zettelmarkup might be seen as a proprietary markup language. But if you want to use [[Markdown/CommonMark|00001008010000]] and you need support for footnotes or tables, you'll end up with proprietary extensions. However, the Zettelstore supports CommonMark as a zettel syntax, so you can mix both Zettelmarkup zettel and CommonMark zettel in one store to get the best of both worlds. * [[General principles|00001007010000]] * [[Basic definitions|00001007020000]] |
︙ | ︙ |
Changes to docs/manual/00001007031100.zettel.
1 2 3 4 5 6 | id: 00001007031100 title: Zettelmarkup: Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20220131151022 | | | | | < | | 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: 00001007031100 title: Zettelmarkup: Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20220131151022 modified: 20220913135545 A transclusion allows to include the content of other zettel into the current zettel. The transclusion specification begins with three consecutive left curly bracket characters (""''{''"", U+007B) at the first position of a line and ends with three consecutive right curly bracket characters (""''}''"", U+007D). The curly brackets delimit either a [[zettel identifier|00001006050000]] or a searched zettel list. You can add some [[attributes|00001007050000]], although a transclusion does not support the default attribute. Any other characters in this line will be ignored. This leads to two variants of transclusion: # Transclusion of the content of another zettel into the current zettel. This is done if you specify a zettel identifier, and is called ""zettel transclusion"". # Transclusion of the list of zettel references that satisfy a [[query expression|00001007700000]]. This is called ""query transclusion"". The variants are described on separate zettel: * [[Zettel transclusion|00001007031110]] * [[Query transclusion|00001007031140]] |
Changes to docs/manual/00001007031110.zettel.
1 2 3 4 5 6 | id: 00001007031110 title: Zettelmarkup: Zettel Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 | | | | 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 | id: 00001007031110 title: Zettelmarkup: Zettel Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 modified: 20220825190116 A zettel transclusion is specified by the following sequence, starting at the first position in a line: ''{{{zettel-identifier}}}''. When evaluated, the referenced zettel is read. If it contains some transclusions itself, these will be expanded, recursively. When a recursion is detected, expansion does not take place. Instead an error message replaces the transclude specification. An error message is also given, if the zettel cannot be read or if too many transclusions are made. The maximum number of transclusion can be controlled by setting the value [[''max-transclusions''|00001004020000#max-transclusions]] of the runtime configuration zettel. If everything went well, the referenced, expanded zettel will replace the transclusion element. For example, to include the text of the Zettel titled ""Zettel identifier"", just specify its identifier [[''00001006050000''|00001006050000]] in the transclude element: ```zmk {{{00001006050000}}} ``` This will result in: :::zs-example {{{00001006050000}}} ::: Please note: if the referenced zettel is changed, all transclusions will also change. This allows, for example, to create a bigger document just by transcluding smaller zettel. |
︙ | ︙ |
Changes to docs/manual/00001007031140.zettel.
1 2 3 4 5 6 | id: 00001007031140 title: Zettelmarkup: Query Transclusion role: manual tags: #manual #search #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 | | | | | | | < < < | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 | id: 00001007031140 title: Zettelmarkup: Query Transclusion role: manual tags: #manual #search #zettelmarkup #zettelstore syntax: zmk created: 20220809132350 modified: 20220913145104 A query transclusion is specified by the following sequence, starting at the first position in a line: ''{{{query:query-expression}}}''. The line must literally start with the sequence ''{{{query:''. Everything after this prefix is interpreted as a [[query expression|00001007700000]]. When evaluated, the query expression is evaluated, often resulting in a list of [[links|00001007040310]] to zettel, matching the query expression. The result replaces the query transclusion element. For example, to include the list of all zettel with the [[all-tags|00001006020000#all-tags]] ""#search"", ordered by title specify the following query transclude element: ```zmk {{{query:all-tags:#search ORDER title}}} ``` This will result in: :::zs-example {{{query:all-tags:#search ORDER title}}} ::: For example, this allows to create a dynamic list of zettel inside a zettel, maybe to provide some introductory text followed by a list of child zettel. The query will deliver only those zettel, which the current user is allowed to read. In the above example, the action list is empty. This leads to the described list of zettel. The following actions are supported, parameter and aggregate actions: ; ''N'' (or any word that starts with ""''N''"" (parameter) : The resulting list will be a numbered list. ; ''MINn'' (parameter) : Emit only those values with at least __n__ aggregated values. __n__ must be a positive integer, ''MIN'' must be given in upper-case letters. ; ''MAXn'' (parameter) : Emit only those values with at most __n__ aggregated values. __n__ must be a positive integer, ''MAX'' must be given in upper-case letters. ; ''TITLE'' (parameter) : All words following ''TITLE'' are joined together to form a title. It is used for the ''RSS'' action. ; ''RSS'' (aggregate) : Transform the zettel list into an [[RSS 2.0|https://www.rssboard.org/rss-specification]]-conformant document. The document is embedded into the referencing zettel. ; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]], [[WordSet|00001006036000]], or [[TagSet|00001006034000]] (aggregates) : Emit an aggregate of the given metadata key. The key can be given in any letter case. ```zmk {{{query:all-tags:#search | all-tags}}} ``` This in a tag cloud of all tags that are used together with the tag #search: :::zs-example {{{query:all-tags:#search | all-tags}}} ::: |
Changes to docs/manual/00001007031200.zettel.
1 2 3 4 5 | id: 00001007031200 title: Zettelmarkup: Inline-Zettel Block role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007031200 title: Zettelmarkup: Inline-Zettel Block role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220311112247 An inline-zettel block allows to specify some content with another syntax without creating a new zettel. This is useful, for example, if you want to embed some [[Markdown|00001008010500]] content, because you are too lazy to translate Markdown into Zettelmarkup. Another example is to specify HTML code to use it for some kind of web front-end framework. As all other [[line-range blocks|00001007030000#line-range-blocks]], an inline-zettel block begins with at least three identical characters, starting at the first position of a line. For inline-zettel blocks, the at-sign character (""''@''"", U+0040) is used. |
︙ | ︙ | |||
32 33 34 35 36 37 38 | will be rendered as: :::example @@@markdown A link to [this](00001007031200) zettel. @@@ ::: | < | | | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | will be rendered as: :::example @@@markdown A link to [this](00001007031200) zettel. @@@ ::: Using HTML: ```zmk @@@html <h1>H1 Heading</h1> Alea iacta est @@@ ``` will a section heading of level 1, which is not allowed within Zettelmarkup: :::example @@@html <h1>H1 Heading</h1> Alea iacta est @@@ ::: :::note |
︙ | ︙ |
Changes to docs/manual/00001007040000.zettel.
1 2 3 4 5 6 | id: 00001007040000 title: Zettelmarkup: Inline-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007040000 title: Zettelmarkup: Inline-Structured Elements role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210126175322 modified: 20220913144717 Most characters you type is concerned with inline-structured elements. The content of a zettel contains is many cases just ordinary text, lightly formatted. Inline-structured elements allow to format your text and add some helpful links or images. Sometimes, you want to enter characters that have no representation on your keyboard. ; Text formatting |
︙ | ︙ | |||
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | ==== Backslash The backslash character (""''\\''"", U+005C) gives the next character another meaning. * If a space character follows, it is converted in a non-breaking space (U+00A0). * If a line ending follows the backslash character, the line break is converted from a __soft break__ into a __hard break__. * Every other character is taken as itself, but without the interpretation of a Zettelmarkup element. For example, if you want to enter a ""'']''"" into a [[footnote text|00001007040330]], you should escape it with a backslash. ==== Entities & more Sometimes it is not easy to enter special characters. If you know the Unicode code point of that character, or its name according to the [[HTML standard|https://html.spec.whatwg.org/multipage/named-characters.html]], you can enter it by number or by name. Regardless which method you use, an entity always begins with an ampersand character (""''&''"", U+0026) and ends with a semicolon character (""'';''"", U+003B). If you know the HTML name of the character you want to enter, put it between these two character. Example: ``&`` is rendered as ::&::{=example}. If you want to enter its numeric code point, a number sign character must follow the ampersand character, followed by digits to base 10. Example: ``&`` is rendered in HTML as ::&::{=example}. You also can enter its numeric code point as a hex number, if you put the letter ""x"" after the numeric sign character. Example: ``&`` is rendered in HTML as ::&::{=example}. | > > > > > > > > > > > > > > > > > > > > > > > > < < < | 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 | ==== Backslash The backslash character (""''\\''"", U+005C) gives the next character another meaning. * If a space character follows, it is converted in a non-breaking space (U+00A0). * If a line ending follows the backslash character, the line break is converted from a __soft break__ into a __hard break__. * Every other character is taken as itself, but without the interpretation of a Zettelmarkup element. For example, if you want to enter a ""'']''"" into a [[footnote text|00001007040330]], you should escape it with a backslash. ==== Tag Any text that begins with a number sign character (""''#''"", U+0023), followed by a non-empty sequence of Unicode letters, Unicode digits, the hyphen-minus character (""''-''"", U+002D), or the low line character (""''_''"", U+005F) is interpreted as an __inline tag__. They are be considered equivalent to tags in metadata. **This element is deprecated in version 0.7 and will be removed in version 0.8!** The use of inline tags is problematic, because: * The number sign is often used as, well, a number sign, esp. in the English language. This introduces unintended tags. * An inline tag is rendered in HTML as a link. However, an inline tag may be the contained in the text part of a [[link element|00001007040310]]. This will produce a HTML link within a HTML link. * Similar, an inline tag may be part of the title of a zettel. When a zettel list is rendered in HTML, this also produces a HTML link for each zettel, which contains the inline tag HTML link. * The naming of metadata, [[''tags''|00001006020000#tags]] (names the tags within the metadata section of a zettel), [[''content-tags''|00001006020000#content-tags]] (all inline tags), and [[''all-tags''|00001006020000#all-tags]], is confusing for some users. For example, if you follow the link of a tag, it is converted into a [[query|00001007700000]] for the key ''all-tags''. A search for ''tags'' will most likely produce different results. To find all zettel with inline tags, please use the query [[::query:content-tags?::|query:content-tags?]]. There are two, non-exclusive options for migration: # Move inline tags into the metadata section of a zettel, under the key ''tags''. This will allow you to find the zettel via a search for tags in the future. # Replace the inline tag with a link to a search for that tag: ``#TAG`` could be replace with ``[[#TAG|query:tags:TAG]]``, where ''TAG'' is the placeholder for the actual tag. ==== Entities & more Sometimes it is not easy to enter special characters. If you know the Unicode code point of that character, or its name according to the [[HTML standard|https://html.spec.whatwg.org/multipage/named-characters.html]], you can enter it by number or by name. Regardless which method you use, an entity always begins with an ampersand character (""''&''"", U+0026) and ends with a semicolon character (""'';''"", U+003B). If you know the HTML name of the character you want to enter, put it between these two character. Example: ``&`` is rendered as ::&::{=example}. If you want to enter its numeric code point, a number sign character must follow the ampersand character, followed by digits to base 10. Example: ``&`` is rendered in HTML as ::&::{=example}. You also can enter its numeric code point as a hex number, if you put the letter ""x"" after the numeric sign character. Example: ``&`` is rendered in HTML as ::&::{=example}. Since some Unicode character are used quite often, a special notation is introduced for them: * Two consecutive hyphen-minus characters result in an __en-dash__ character. It is typically used in numeric ranges. ``pages 4--7`` will be rendered in HTML as: ::pages 4--7::{=example}. Alternative specifications are: ``–``, ``&x8211``, and ``–``. |
Changes to docs/manual/00001007040310.zettel.
1 2 3 4 5 6 | id: 00001007040310 title: Zettelmarkup: Links role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210810155955 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001007040310 title: Zettelmarkup: Links role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk created: 20210810155955 modified: 20220913144754 There are two kinds of links, regardless of links to (internal) other zettel or to (external) material. Both kinds begin with two consecutive left square bracket characters (""''[''"", U+005B) and ends with two consecutive right square bracket characters (""'']''"", U+005D). The first form provides some text plus the link specification, delimited by a vertical bar character (""''|''"", U+007C): ``[[text|linkspecification]]``. The text is a sequence of [[inline elements|00001007040000]]. However, it should not contain links itself. |
︙ | ︙ | |||
22 23 24 25 26 27 28 | The resulting reference is called ""zettel reference"". If the link specification begins with the string ''query:'', the text following this string will be interpreted as a [[query expression|00001007700000]]. The resulting reference is called ""query reference"". When this type of references is rendered, it will typically reference a list of all zettel that fulfills the query expression. A link specification starting with one slash character (""''/''"", U+002F), or one or two full stop characters (""''.''"", U+002E) followed by a slash character, | | | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | The resulting reference is called ""zettel reference"". If the link specification begins with the string ''query:'', the text following this string will be interpreted as a [[query expression|00001007700000]]. The resulting reference is called ""query reference"". When this type of references is rendered, it will typically reference a list of all zettel that fulfills the query expression. A link specification starting with one slash character (""''/''"", U+002F), or one or two full stop characters (""''.''"", U+002E) followed by a slash character, will be interpreted as a local reference, called ""hosted reference"". Such references will be interpreted relative to the web server hosting the Zettelstore. If a link specification begins with two slash characters, it will be interpreted relative to the value of [[''url-prefix''|00001004010000#url-prefix]]. To specify some material outside the Zettelstore, just use an normal Uniform Resource Identifier (URI) as defined by [[RFC\ 3986|https://tools.ietf.org/html/rfc3986]]. === Other topics If the link references another zettel, and this zettel is not readable for the current user, because of a missing access rights, then only the associated text is presented. |
Changes to docs/manual/00001007040324.zettel.
1 2 3 4 5 | id: 00001007040324 title: Zettelmarkup: Inline-mode Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007040324 title: Zettelmarkup: Inline-mode Transclusion role: manual tags: #manual #zettelmarkup #zettelstore syntax: zmk modified: 20220311110814 Inline-mode transclusion applies to all zettel that are parsed in a non-trivial way, e.g. as structured textual content. For example, textual content is assumed if the [[syntax|00001006020000#syntax]] of a zettel is ""zmk"" ([[Zettelmarkup|00001007000000]]), or ""markdown"" / ""md"" ([[Markdown|00001008010000]]). Since this type of transclusion is at the level of [[inline-structured elements|00001007040000]], the transclude specification must be replaced with some inline-structured elements. First, the referenced zettel is read. |
︙ | ︙ | |||
32 33 34 35 36 37 38 | Initial spaces and line breaks are ignored in this case. Example: ``{{00001007040322#spin}}`` is rendered as ::{{00001007040322#spin}}::{=example}. ** Just specifying the fragment identifier will reference something in the current page. This is not allowed, to prevent a possible endless recursion. | < < < < | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | Initial spaces and line breaks are ignored in this case. Example: ``{{00001007040322#spin}}`` is rendered as ::{{00001007040322#spin}}::{=example}. ** Just specifying the fragment identifier will reference something in the current page. This is not allowed, to prevent a possible endless recursion. If no inline-structured elements are found, the transclude specification is replaced by an error message. To avoid an exploding ""transclusion bomb"", a form of a [[billion laughs attack|https://en.wikipedia.org/wiki/Billion_laughs_attack]] (also known as ""XML bomb""), the total number of transclusions / expansions is limited. The limit can be controlled by setting the value [[''max-transclusions''|00001004020000#max-transclusions]] of the runtime configuration zettel. === See also [[Full transclusion|00001007031100]] does not work inside some text, but is used for [[block-structured elements|00001007030000]]. |
Changes to docs/manual/00001007790000.zettel.
1 2 3 4 5 6 | id: 00001007790000 title: Useful query expressions role: manual tags: #example #manual #search #zettelstore syntax: zmk created: 20220810144539 | | > | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | id: 00001007790000 title: Useful query expressions role: manual tags: #example #manual #search #zettelstore syntax: zmk created: 20220810144539 modified: 20220913144959 |= Query Expression |= Meaning | [[query:role:configuration]] | Zettel that contains some configuration data for the Zettelstore | [[query:ORDER REVERSE created LIMIT 40]] | 40 recently created zettel | [[query:ORDER REVERSE published LIMIT 40]] | 40 recently updated zettel | [[query:RANDOM LIMIT 40]] | 40 random zettel | [[query:dead?]] | Zettel with invalid / dead links | [[query:backward!? precursor!?]] | Zettel that are not referenced by other zettel | [[query:all-tags!?]] | Zettel without any tags | [[query:tags!?]] | Zettel without tags that are defined within metadata | [[query:content-tags?]] | Zettel with tags within content |
Changes to docs/manual/00001007900000.zettel.
1 2 3 4 5 | id: 00001007900000 title: Zettelmarkup: Tutorial role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk | < | 1 2 3 4 5 6 7 8 9 | id: 00001007900000 title: Zettelmarkup: Tutorial role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk modified: 20220811135314 * [[First steps|00001007903000]]: learn something about paragraphs, emphasized text, and lists. * [[Second steps|00001007906000]]: know about links, thematic breaks, and headings. |
Changes to docs/manual/00001007903000.zettel.
1 2 3 4 5 | id: 00001007903000 title: Zettelmarkup: First Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007903000 title: Zettelmarkup: First Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk modified: 20220811122618 [[Zettelmarkup|00001007000000]] allows you to leave your text as it is, at least in many situations. Some characters have a special meaning, but you have to enter them is a defined way to see a visible change. Zettelmarkup is designed to be used for zettel, which are relatively short. It allows to produce longer texts, but you should probably use a different tool, if you want to produce an scientific paper, to name an example. === Paragraphs |
︙ | ︙ | |||
48 49 50 51 52 53 54 | * First item * Second item * Third item ``` This is rendered as: | | | | 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 | * First item * Second item * Third item ``` This is rendered as: :::zs-example * First item * Second item * Third item ::: Similar, an numbered list element begins a line with the number sign (sic!) followed by a space character: ```zmk # First item # Second item # Third item ``` This is rendered as: :::zs-example # First item # Second item # Third item ::: --- After trying out these markup elements, you might want to continue with the [[second steps|00001007906000]]. |
Changes to docs/manual/00001007906000.zettel.
1 2 3 4 5 | id: 00001007906000 title: Zettelmarkup: Second Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001007906000 title: Zettelmarkup: Second Steps role: manual tags: #manual #tutorial #zettelmarkup #zettelstore syntax: zmk modified: 20220811135024 After you have [[learned|00001007903000]] the basic concepts and markup of Zettelmarkup (paragraphs, emphasized text, and lists), this zettel introduces you into the concepts of links, thematic breaks, and headings. === Links A Zettelstore is much more useful, if you connect related zettel. If you read a zettel later, this allows you to know about the context of a zettel. [[Zettelmarkup|00001007000000]] allows you to specify such a connection. |
︙ | ︙ | |||
49 50 51 52 53 54 55 | --- Second paragraph. ``` Both are rendered as: | | | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | --- Second paragraph. ``` Both are rendered as: :::zs-example First paragraph. --- Second paragraph. ::: Try it! |
︙ | ︙ |
Changes to docs/manual/00001008000000.zettel.
1 2 3 4 5 6 | id: 00001008000000 title: Other Markup Languages role: manual tags: #manual #zettelstore syntax: zmk created: 20210126175300 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001008000000 title: Other Markup Languages role: manual tags: #manual #zettelstore syntax: zmk created: 20210126175300 modified: 20220824114649 [[Zettelmarkup|00001007000000]] is not the only markup language you can use to define your content. Zettelstore is quite agnostic with respect to markup languages. Of course, Zettelmarkup plays an important role. However, with the exception of zettel titles, you can use any (markup) language that is supported: * CSS |
︙ | ︙ | |||
26 27 28 29 30 31 32 | ; [!gif|''gif'']; [!jpeg|''jpeg'']; [!jpg|''jpg'']; [!png|''png''] : The formats for pixel graphics. Typically the data is stored in a separate file and the syntax is given in the meta-file, which has the same name as the zettel identifier and has no file extension.[^Before version 0.2, the meta-file had the file extension ''.meta''] ; [!html|''html''] : Hypertext Markup Language, will not be parsed further. Instead, it is treated as [[text|#text]], but will be encoded differently for [[HTML format|00001012920510]] (same for the [[web user interface|00001014000000]]). | < < | 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | ; [!gif|''gif'']; [!jpeg|''jpeg'']; [!jpg|''jpg'']; [!png|''png''] : The formats for pixel graphics. Typically the data is stored in a separate file and the syntax is given in the meta-file, which has the same name as the zettel identifier and has no file extension.[^Before version 0.2, the meta-file had the file extension ''.meta''] ; [!html|''html''] : Hypertext Markup Language, will not be parsed further. Instead, it is treated as [[text|#text]], but will be encoded differently for [[HTML format|00001012920510]] (same for the [[web user interface|00001014000000]]). For security reasons, equivocal elements will not be encoded in the HTML format / web user interface. The ``< script ...>`` tag is an example. See [[security aspects of Markdown|00001008010000#security-aspects]] for some details. ; [!markdown|''markdown''], [!md|''md''] : For those who desperately need [[Markdown|https://daringfireball.net/projects/markdown/]]. Since the world of Markdown is so diverse, a [[CommonMark|00001008010500]] parser is used. See [[Use Markdown within Zettelstore|00001008010000]]. |
︙ | ︙ |
Changes to docs/manual/00001008010000.zettel.
1 2 3 4 5 | id: 00001008010000 title: Use Markdown within Zettelstore role: manual tags: #manual #markdown #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001008010000 title: Use Markdown within Zettelstore role: manual tags: #manual #markdown #zettelstore syntax: zmk modified: 20220627192014 If you are customized to use Markdown as your markup language, you can configure Zettelstore to support your decision. Zettelstore supports the [[CommonMark|00001008010500]] dialect of Markdown. === Use Markdown as the default markup language of Zettelstore Update the [[New Zettel|00000000090001]] template (and other relevant template zettel) by setting the syntax value to ''md'' or ''markdown''. |
︙ | ︙ | |||
28 29 30 31 32 33 34 | Not every Markdown tool allows both file extensions. BTW, metadata is stored in a file without a file extension, if neither ''yaml-header'' nor ''zettel-file-syntax'' is set. === Security aspects You should be aware that Markdown is a super-set of HTML. | | < < < | | 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | Not every Markdown tool allows both file extensions. BTW, metadata is stored in a file without a file extension, if neither ''yaml-header'' nor ''zettel-file-syntax'' is set. === Security aspects You should be aware that Markdown is a super-set of HTML. Any HTML code is valid Markdown code. If you write your own zettel, this is probably not a problem. However, if you receive zettel from others, you should be careful. An attacker might include malicious HTML code in your zettel. For example, HTML allows to embed JavaScript, a full-sized programming language that drives many web sites. When a zettel is displayed, JavaScript code might be executed, sometimes with harmful results. Zettelstore mitigates this problem by ignoring suspicious text when it encodes a zettel as HTML. Any HTML text that might contain the ``<script>`` tag or the ``<iframe>`` tag is ignored. This may lead to unexpected results if you depend on these. Other [[encodings|00001012920500]] may still contain the full HTML text. Any external client of Zettelstore, which does not use Zettelstore's [[HTML encoding|00001012920510]], must be programmed to take care of malicious code. |
Changes to docs/manual/00001008010500.zettel.
1 2 3 4 5 | id: 00001008010500 title: CommonMark role: manual tags: #manual #markdown #zettelstore syntax: zmk | < | < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | id: 00001008010500 title: CommonMark role: manual tags: #manual #markdown #zettelstore syntax: zmk modified: 20220113193000 url: https://commonmark.org/ [[CommonMark|https://commonmark.org/]] is a Markdown dialect, an [[attempt|https://xkcd.com/927/]] to unify all the different, divergent dialects of Markdown by providing an unambiguous syntax specification for Markdown, together with a suite of comprehensive tests to validate implementation. Time will show, if this attempt is successful. However, CommonMark is a well specified Markdown dialect, in contrast to most (if not all) other dialects. Other software adopts CommonMark somehow, notably [[GitHub Flavored Markdown|https://github.github.com/gfm/]] (GFM). But they provide proprietary extensions, which makes it harder to change to another CommonMark implementation if needed. Plus, they sometimes build on an older specification of CommonMark. Zettelstore supports the latest CommonMark [[specification version 0.30 (2021-06-19)|https://spec.commonmark.org/0.30/]]. If possible, Zettelstore will adapt to newer versions when they are available. To provide CommonMark support, Zettelstore uses currently the [[Goldmark|https://github.com/yuin/goldmark]] implementation, which passes all validation tests of CommonMark. Internally, CommonMark is translated into some kind of super-set of [[Zettelmarkup|00001007000000]], which additionally allows to use HTML code.[^Effectively, Markdown and CommonMark are itself super-sets of HTML.] This Zettelmarkup super-set is later [[encoded|00001012920500]], often into [[HTML|00001012920510]]. Because Zettelstore HTML encoding philosophy differs a little bit to that of CommonMark, Zettelstore itself will not pass the CommonMark test suite fully. However, no CommonMark language element will fail to be encoded as HTML. In most cases, the differences are not visible for an user, but only by comparing the generated HTML code. |
Changes to docs/manual/00001010000000.zettel.
1 2 3 4 5 | id: 00001010000000 title: Security role: manual tags: #configuration #manual #security #zettelstore syntax: zmk | < | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | id: 00001010000000 title: Security role: manual tags: #configuration #manual #security #zettelstore syntax: zmk modified: 20211124140614 Your zettel could contain sensitive content. You probably want to ensure that only authorized person can read and/or modify them. Zettelstore ensures this in various ways. === Local first The Zettelstore is designed to run on your local computer. If you do not configure it in other ways, no person from another computer can connect to your Zettelstore. You must explicitly configure it to allow access from other computers. In the case that your own multiple computers, you do not have to access the Zettelstore remotely. You could install Zettelstore on each computer and set-up some software to synchronize your zettel. Since zettel are stored as ordinary files, this task could be done in various ways. === Read-only You can start the Zettelstore in an read-only mode. Nobody, not even you as the owner of the Zettelstore, can change something via its interfaces[^However, as an owner, you have access to the files that store the zettel. If you modify the files, these changes will be reflected via its interfaces.]. |
︙ | ︙ | |||
57 58 59 60 61 62 63 | When Zettelstore is accessed remotely, the messages that are sent between Zettelstore and the client must be encrypted. Otherwise, an eavesdropper could fetch sensible data, such as passwords or precious content that is not for the public. The Zettelstore itself does not encrypt messages. But you can put a server in front of it, which is able to handle encryption. Most generic web server software do allow this. | | | 56 57 58 59 60 61 62 63 64 65 66 67 | When Zettelstore is accessed remotely, the messages that are sent between Zettelstore and the client must be encrypted. Otherwise, an eavesdropper could fetch sensible data, such as passwords or precious content that is not for the public. The Zettelstore itself does not encrypt messages. But you can put a server in front of it, which is able to handle encryption. Most generic web server software do allow this. To enforce encryption, [[authentication sessions|00001010040700]] are marked as secure by default. If you still want to access the Zettelstore remotely without encryption, you must change the startup configuration. Otherwise, authentication will not work. * [[Use a server for encryption|00001010090100]] |
Changes to docs/manual/00001010070200.zettel.
1 2 3 4 5 6 | id: 00001010070200 title: Visibility rules for zettel role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | id: 00001010070200 title: Visibility rules for zettel role: manual tags: #authorization #configuration #manual #security #zettelstore syntax: zmk created: 20210126175322 modified: 20220913144845 For every zettel you can specify under which condition the zettel is visible to others. This is controlled with the metadata key [[''visibility''|00001006020000#visibility]]. The following values are supported: ; [!public|""public""] : The zettel is visible to everybody, even if the user is not authenticated. |
︙ | ︙ | |||
37 38 39 40 41 42 43 | Please note: if [[authentication is not enabled|00001010040100]], every user has the same rights as the owner of a Zettelstore. This is also true, if the Zettelstore runs additionally in [[read-only mode|00001004010000#read-only-mode]]. In this case, the [[runtime configuration zettel|00001004020000]] is shown (its visibility is ""owner""). The [[startup configuration|00001004010000]] is not shown, because the associated computed zettel with identifier ''00000000000096'' is stored with the visibility ""expert"". If you want to show such a zettel, you must set ''expert-mode'' to true. === Examples | | | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | Please note: if [[authentication is not enabled|00001010040100]], every user has the same rights as the owner of a Zettelstore. This is also true, if the Zettelstore runs additionally in [[read-only mode|00001004010000#read-only-mode]]. In this case, the [[runtime configuration zettel|00001004020000]] is shown (its visibility is ""owner""). The [[startup configuration|00001004010000]] is not shown, because the associated computed zettel with identifier ''00000000000096'' is stored with the visibility ""expert"". If you want to show such a zettel, you must set ''expert-mode'' to true. === Examples Similar to the [[API|00001012051840]], you can easily create a zettel list based on the ''visibility'' metadata key: | public | [[query:visibility:public]] | login | [[query:visibility:login]] | creator | [[query:visibility:creator]] | owner | [[query:visibility:owner]] | expert | [[query:visibility:expert]][^Only if [[''expert-mode''|00001004020000#expert-mode]] is enabled, this list will show some zettel.] |
Changes to docs/manual/00001012000000.zettel.
1 2 3 4 5 6 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 | | > > > < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | id: 00001012000000 title: API role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20220913141632 The API (short for ""**A**pplication **P**rogramming **I**nterface"") is the primary way to communicate with a running Zettelstore. Most integration with other systems and services is done through the API. The [[web user interface|00001014000000]] is just an alternative, secondary way of interacting with a Zettelstore. === Background The API is HTTP-based and uses plain text and JSON as its main encoding format for exchanging messages between a Zettelstore and its client software. There is an [[overview zettel|00001012920000]] that shows the structure of the endpoints used by the API and gives an indication about its use. === Authentication If [[authentication is enabled|00001010040100]], most API calls must include an [[access token|00001010040700]] that proves the identity of the caller. * [[Authenticate an user|00001012050200]] to obtain an access token * [[Renew an access token|00001012050400]] without costly re-authentication * [[Provide an access token|00001012050600]] when doing an API call === Zettel lists * [[List metadata of all zettel|00001012051200]] ** [[Query expressions|00001012051840]] (includes content search) * [[Map metadata values to lists of zettel identifier|00001012052400]] * [[Query the list of all zettel|00001012051400]] === Working with zettel * [[Create a new zettel|00001012053200]] * [[Retrieve metadata and content of an existing zettel|00001012053300]] * [[Retrieve metadata of an existing zettel|00001012053400]] * [[Retrieve evaluated metadata and content of an existing zettel in various encodings|00001012053500]] * [[Retrieve parsed metadata and content of an existing zettel in various encodings|00001012053600]] |
︙ | ︙ |
Changes to docs/manual/00001012051200.zettel.
1 | id: 00001012051200 | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < | < | < | < < < < < < < < | < < < < < < < < < < < < < | < < | 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 | id: 00001012051200 title: API: List metadata of all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210126175322 modified: 20220913151852 To list the metadata of all zettel just send a HTTP GET request to the [[endpoint|00001012920000]] ''/j''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. If successful, the output is a JSON object: ```sh # curl http://127.0.0.1:23123/j {"query":"","list":[{"id":"00001012051200","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012050600","meta":{"title":"API: Provide an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012050400","meta":{"title":"API: Renew an access token","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012050200","meta":{"title":"API: Authenticate a client","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62},{"id":"00001012000000","meta":{"title":"API","tags":"#api #manual #zettelstore","syntax":"zmk","role":"manual"},"rights":62}]} ``` The JSON object contains a key ''"list"'' where its value is a list of zettel JSON objects. These zettel JSON objects themselves contains the keys ''"id"'' (value is a string containing the [[zettel identifier|00001006050000]]), ''"meta"'' (value as a JSON object), and ''"rights"'' (encodes the [[access rights|00001012921200]] for the given zettel). The value of key ''"meta"'' effectively contains all metadata of the identified zettel, where metadata keys are encoded as JSON object keys and metadata values encoded as JSON strings. Additionally, the JSON object contains the keys ''"query"'' and ''"human"'' with a string value. Both will contain a textual description of the underlying query if you select only some zettel with a [[query expression|00001012051840]]. Without a selection, the values are the empty string. ''"query"'' returns the normalized query expression itself, while ''"human"'' is the normalized query expression to be read by humans. If you reformat the JSON output from the ''GET /j'' call, you'll see its structure better: ```json { "query": "", "human": "", "list": [ { "id": "00001012051200", "meta": { "title": "API: List for all zettel some data", "tags": "#api #manual #zettelstore", "syntax": "zmk", "role": "manual" }, "rights":62 }, { "id": "00001012050600", "meta": { "title": "API: Provide an access token", "tags": "#api #manual #zettelstore", "syntax": "zmk", "role": "manual" }, "rights":62 }, { "id": "00001012050400", "meta": { "title": "API: Renew an access token", "tags": "#api #manual #zettelstore", "syntax": "zmk", "role": "manual" }, "rights":62 }, { "id": "00001012050200", "meta": { "title": "API: Authenticate a client", "tags": "#api #manual #zettelstore", "syntax": "zmk", "role": "manual" }, "rights":62 }, { "id": "00001012000000", "meta": { "title": "API", "tags": "#api #manual #zettelstore", "syntax": "zmk", "role": "manual" }, "rights":62 } ] } ``` In this special case, the metadata of each zettel just contains the four default keys ''title'', ''tags'', ''syntax'', and ''role''. [!plain]Alternatively, you can retrieve the list of zettel in a simple, plain format using the [[endpoint|00001012920000]] ''/z''. In this case, a plain text document is returned, with one line per zettel. Each line contains in the first 14 characters the [[zettel identifier|00001006050000]]. Separated by a space character, the title of the zettel follows: ```sh # curl http://127.0.0.1:23123/z 00001012051200 API: Renew an access token 00001012050600 API: Provide an access token 00001012050400 API: Renew an access token 00001012050200 API: Authenticate a client 00001012000000 API ``` === Note This request will always return a list of metadata, provided the request was syntactically correct. There will never be a HTTP status code 403 (Forbidden), even if [[authentication was enabled|00001010040100]] and you did not provide a valid access token. In this case, the resulting list might be quite short (some zettel will have [[public visibility|00001010070200]]) or the list might be empty. With this call, you cannot differentiate between an empty result list (e.g because your [[content search|00001012051840]] did not found a zettel with the specified term) and an empty list because of missing authorization (e.g. an invalid access token). === HTTP Status codes ; ''200'' : Retrieval was successful, the body contains an [[appropriate JSON object|00001012921000]]. ; ''400'' : Request was not valid. There are several reasons for this. |
︙ | ︙ |
Changes to docs/manual/00001012051400.zettel.
1 2 3 4 5 6 | id: 00001012051400 title: API: Query the list of all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20220912111111 | | | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | id: 00001012051400 title: API: Query the list of all zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20220912111111 modified: 20220913150204 The [[endpoint|00001012920000]] ''/q'' allows to query the list of all zettel. A [[query|00001007700000]] is an optional [[search expression|00001007700000#search-expression]], together with an optional [[list of actions|00001007700000#action-list]] (described below). An empty search expression will select all zettel. An empty list of action will return nothing. It is an error, if both are empty. Search expression and action list are separated by a vertical bar character (""''|''"", U+007C), and must be given with the query parameter ''q''. For example, to list all roles used in the Zettelstore, send a HTTP GET request to the endpoint ''/q?q=|role''. If successful, the output is a JSON object: ```sh |
︙ | ︙ | |||
49 50 51 52 53 54 55 | __n__ must be a positive integer, ''MAX'' must be given in upper-case letters. ; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]], [[WordSet|00001006036000]], or [[TagSet|00001006034000]] (aggregates) : Emit an aggregate of the given metadata key. The key can be given in any letter case. Only the first aggregate action will be executed. | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | __n__ must be a positive integer, ''MAX'' must be given in upper-case letters. ; Any [[metadata key|00001006020000]] of type [[Word|00001006035500]], [[WordSet|00001006036000]], or [[TagSet|00001006034000]] (aggregates) : Emit an aggregate of the given metadata key. The key can be given in any letter case. Only the first aggregate action will be executed. === HTTP Status codes ; ''200'' : Query was successful. ; ''204'' : Query was successful, but results in no content. Most likely, you specified no appropriate aggregator. ; ''400'' : Request was not valid. There are several reasons for this. Maybe the access bearer token was not valid, or you forgot to specify a valid query. |
Added docs/manual/00001012051840.zettel.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | id: 00001012051840 title: API: Shape the list of zettel metadata by specifying a query expression role: manual tags: #api #manual #search #zettelstore syntax: zmk created: 20210709143714 modified: 20220913150355 The query parameter ""''q''"" allows you to specify [[query expressions|00001007700000]] for a full-text search of all zettel content and/or restricting the search according to specific metadata. You are allowed to specify this query parameter more than once, as well as the other query parameters. All results will be intersected, i.e. a zettel will be included into the list if all of the provided values match. This parameter loosely resembles the search form of the [[web user interface|00001014000000]]. For example, if you want to retrieve all zettel that contain the string ""API"" in its title, your request will be: ```sh # curl 'http://127.0.0.1:23123/j?q=title%3AAPI' {"query":"title MATCH API","list":[{"id":"00001012921000","meta":{"title":"API: JSON structure of an access token","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012920500","meta":{"title":"Formats available by the API","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}},{"id":"00001012920000","meta":{"title":"Endpoints used by the API","tags":"#api #manual #reference #zettelstore","syntax":"zmk","role":"manual"}}, ... ``` However, if you want all zettel that does not match a given value, you must prefix the value with the exclamation mark character (""!"", U+0021). For example, if you want to retrieve all zettel that do not contain the string ""API"" in their title, your request will be: ```sh # curl 'http://127.0.0.1:23123/j?q=title!%3AAPI' {"query":"title NOT MATCH API","list":[{"id":"00010000000000","meta":{"back":"00001003000000 00001005090000","backward":"00001003000000 00001005090000","copyright":"(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>","forward":"00000000000001 00000000000003 00000000000096 00000000000100","lang":"en","license":"EUPL-1.2-or-later","role":"zettel","syntax":"zmk","title":"Home"}},{"id":"00001014000000","meta":{"back":"00001000000000 00001004020000 00001012920510","backward":"00001000000000 00001004020000 00001012000000 00001012920510","copyright":"(c) 2020-2021 by Detlef Stern <ds@zettelstore.de>","forward":"00001012000000","lang":"en","license":"EUPL-1.2-or-later","published":"00001014000000","role":"manual","syntax":"zmk","tags":"#manual #webui #zettelstore","title":"Web user interface"}}, ... ``` In both cases, an implicit precondition is that the zettel must contain the given metadata key. For a metadata key like [[''title''|00001006020000#title]], which has a default value, this precondition should always be true. But the situation is different for a key like [[''url''|00001006020000#url]]. Both ``curl 'http://localhost:23123/j?q=url%3A'`` and ``curl 'http://localhost:23123/j?q=url%3A!'`` may result in an empty list. Alternatively, you also can use the [[endpoint|00001012920000]] ''/z'' for a simpler result format. The first example translates to: ```sh # curl 'http://127.0.0.1:23123/z?q=title%3AAPI' 00001012921000 API: JSON structure of an access token 00001012920500 Formats available by the API 00001012920000 Endpoints used by the API ... ``` |
Added docs/manual/00001012052400.zettel.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | id: 00001012052400 title: API: Map metadata values to list of zettel identifier role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210216172944 modified: 20220912112253 **Note**: this endpoint is deprecated since v0.7 and will be removed in v0.8. Please use a [[query|00001012051400]] instead. --- The [[endpoint|00001012920000]] ''/m'' allows to retrieve a map of metadata values (of a specific key) to the list of zettel identifier, which reference zettel containing this value under the given metadata key. Currently, two keys are supported: * [[''role''|00001006020100]] * [[''tags''|00001006020000#tags]] To list all roles used in the Zettelstore, send a HTTP GET request to the endpoint ''/m?key=role''. If successful, the output is a JSON object: ```sh # curl http://127.0.0.1:23123/m?key=role {"map":{"configuration":["00000000090002","00000000090000", ... ,"00000000000001"],"manual":["00001014000000", ... ,"00001000000000"],"zettel":["00010000000000", ... ,"00001012070500","00000000090001"]}} ``` The JSON object only contains the key ''"map"'' with the value of another object. This second object contains all role names as keys and the list of identifier of those zettel with this specific role as a value. Similar, to list all tags used in the Zettelstore, send a HTTP GET request to the endpoint ''/m?key=tags''. If successful, the output is a JSON object: ```sh # curl http://127.0.0.1:23123/m?key=tags {"map":{"#api":[:["00001012921000","00001012920800","00001012920522",...],"#authorization":["00001010040700","00001010040400",...],...,"#zettelstore":["00010000000000","00001014000000",...,"00001001000000"]}} ``` The JSON object only contains the key ''"map"'' with the value of another object. This second object contains all tags as keys and the list of identifier of those zettel with this tag as a value. Please note that this structure will likely change in the future to be more compliant with other API calls. |
Changes to docs/manual/00001012053400.zettel.
1 2 3 4 5 6 | id: 00001012053400 title: API: Retrieve metadata of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210726174524 | | | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | id: 00001012053400 title: API: Retrieve metadata of an existing zettel role: manual tags: #api #manual #zettelstore syntax: zmk created: 20210726174524 modified: 20220908162635 The [[endpoint|00001012920000]] to work with metadata of a specific zettel is ''/m/{ID}'', where ''{ID}'' is a placeholder for the [[zettel identifier|00001006050000]]. For example, to retrieve some data about this zettel you are currently viewing, just send a HTTP GET request to the endpoint ''/j/00001012053400''[^If [[authentication is enabled|00001010040100]], you must include the a valid [[access token|00001012050200]] in the ''Authorization'' header]. If successful, the output is a JSON object: ```sh # curl http://127.0.0.1:23123/m/00001012053400 {"meta":{"all-tags":"#api #manual #zettelstore","back":"00001012000000 00001012053300","backward":"00001012000000 00001012053300 00001012920000","box-number":"1","forward":"00001010040100 00001012050200 00001012920000 00001012920800","modified":"20211004111240","published":"20211004111240","role":"manual","syntax":"zmk","tags":"#api #manual #zettelstore","title":"API: Retrieve metadata of an existing zettel"},"rights":62} ``` Pretty-printed, this results in: ``` { "meta": { "all-tags": "#api #manual #zettelstore", "back": "00001012000000 00001012053300", "backward": "00001012000000 00001012053300 00001012920000", "box-number": "1", "forward": "00001010040100 00001012050200 00001012920000 00001012920800", "modified": "20211004111240", "published": "20211004111240", "role": "manual", |
︙ | ︙ |
Changes to docs/manual/00001012080500.zettel.
1 2 3 4 5 6 | id: 00001012080500 title: API: Refresh internal data role: manual tags: #api #manual #zettelstore syntax: zmk created: 20211230230441 | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | id: 00001012080500 title: API: Refresh internal data role: manual tags: #api #manual #zettelstore syntax: zmk created: 20211230230441 modified: 20220908163223 Zettelstore maintains some internal data to allow faster operations. One example is the [[content search|00001012051840]] for a term: Zettelstore does not need to scan all zettel to find all occurrences for the term. Instead, all word are stored internally, with a list of zettel where they occur. Another example is the way to determine which zettel are stored in a [[ZIP file|00001004011200]]. Scanning a ZIP file is a lengthy operation, therefore Zettelstore maintains a directory of zettel for each ZIP file. All these internal data may become stale. This should not happen, but when it comes e.g. to file handling, every operating systems behaves differently in very subtle ways. |
︙ | ︙ |
Changes to docs/manual/00001012920000.zettel.
1 2 3 4 5 6 | id: 00001012920000 title: Endpoints used by the API role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | id: 00001012920000 title: Endpoints used by the API role: manual tags: #api #manual #reference #zettelstore syntax: zmk created: 20210126175322 modified: 20220912115218 All API endpoints conform to the pattern ''[PREFIX]LETTER[/ZETTEL-ID]'', where: ; ''PREFIX'' : is the URL prefix (default: ""/""), configured via the ''url-prefix'' [[startup configuration|00001004010000]], ; ''LETTER'' : is a single letter that specifies the resource type, ; ''ZETTEL-ID'' : is an optional 14 digits string that uniquely [[identify a zettel|00001006050000]]. The following letters are currently in use: |= Letter:| Without zettel identifier | With [[zettel identifier|00001006050000]] | Mnemonic | ''a'' | POST: [[client authentication|00001012050200]] | | **A**uthenticate | | PUT: [[renew access token|00001012050400]] | | ''j'' | GET: [[list zettel AS JSON|00001012051200]] | GET: [[retrieve zettel AS JSON|00001012053300]] | **J**SON | | POST: [[create new zettel|00001012053200]] | PUT: [[update a zettel|00001012054200]] | | | DELETE: [[delete the zettel|00001012054600]] | | | MOVE: [[rename the zettel|00001012054400]] | ''m'' | GET: [[map metadata values|00001012052400]] (deprecated) | GET: [[retrieve metadata|00001012053400]] | **M**etadata | ''o'' | | GET: [[list zettel order|00001012054000]] | **O**rder | ''p'' | | GET: [[retrieve parsed zettel|00001012053600]]| **P**arsed | ''q'' | GET: [[query zettel list|00001012051400]] | | **Q**uery | ''u'' | | GET [[unlinked references|00001012053900]] | **U**nlinked | ''v'' | | GET: [[retrieve evaluated zettel|00001012053500]] | E**v**aluated | ''x'' | GET: [[retrieve administrative data|00001012070500]] | GET: [[list zettel context|00001012053800]] | Conte**x**t | | POST: [[execute command|00001012080100]] |
︙ | ︙ |
Changes to docs/manual/00001018000000.zettel.
1 2 3 4 5 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 | id: 00001018000000 title: Troubleshooting role: manual tags: #manual #zettelstore syntax: zmk modified: 20220823195041 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. |
︙ | ︙ | |||
31 32 33 34 35 36 37 | If you access the zettel via Zettelstore, a fatal error is reported. ** **Explanation:** Sometimes, the operating system does not tell Zettelstore about the removed zettel. This occurs mostly under MacOS. ** **Solution 1:** If you are running Zettelstore in [[""simple-mode""|00001004051100]] or if you have enabled [[''expert-mode''|00001004020000#expert-mode]], you are allowed to refresh the internal data by selecting ""Refresh"" in the Web User Interface (you find it in the menu ""Lists""). ** **Solution 2:** There is an [[API|00001012080500]] call to make Zettelstore aware of this change. ** **Solution 3:** If you have an enabled [[Administrator Console|00001004100000]] you can use the command [[''refresh''|00001004101000#refresh]] to make your changes visible. ** **Solution 4:** You configure the zettel box as [[""simple""|00001004011400]]. | < < < < < < < < < < < < < < < < < < < | 30 31 32 33 34 35 36 | If you access the zettel via Zettelstore, a fatal error is reported. ** **Explanation:** Sometimes, the operating system does not tell Zettelstore about the removed zettel. This occurs mostly under MacOS. ** **Solution 1:** If you are running Zettelstore in [[""simple-mode""|00001004051100]] or if you have enabled [[''expert-mode''|00001004020000#expert-mode]], you are allowed to refresh the internal data by selecting ""Refresh"" in the Web User Interface (you find it in the menu ""Lists""). ** **Solution 2:** There is an [[API|00001012080500]] call to make Zettelstore aware of this change. ** **Solution 3:** If you have an enabled [[Administrator Console|00001004100000]] you can use the command [[''refresh''|00001004101000#refresh]] to make your changes visible. ** **Solution 4:** You configure the zettel box as [[""simple""|00001004011400]]. |
Changes to domain/meta/meta.go.
︙ | ︙ | |||
130 131 132 133 134 135 136 | func init() { registerKey(api.KeyID, TypeID, usageComputed, "") registerKey(api.KeyTitle, TypeZettelmarkup, usageUser, "") registerKey(api.KeyRole, TypeWord, usageUser, "") registerKey(api.KeyTags, TypeTagSet, usageUser, "") registerKey(api.KeySyntax, TypeWord, usageUser, "") | < | < < > > < | 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 | func init() { registerKey(api.KeyID, TypeID, usageComputed, "") registerKey(api.KeyTitle, TypeZettelmarkup, usageUser, "") registerKey(api.KeyRole, TypeWord, usageUser, "") registerKey(api.KeyTags, TypeTagSet, usageUser, "") registerKey(api.KeySyntax, TypeWord, usageUser, "") registerKey(api.KeyAllTags, TypeTagSet, usageProperty, "") registerKey(api.KeyAuthor, TypeString, usageUser, "") registerKey(api.KeyBack, TypeIDSet, usageProperty, "") registerKey(api.KeyBackward, TypeIDSet, usageProperty, "") registerKey(api.KeyBoxNumber, TypeNumber, usageProperty, "") registerKey(api.KeyContentTags, TypeTagSet, usageProperty, "") registerKey(api.KeyCopyright, TypeString, usageUser, "") registerKey(api.KeyCreated, TypeTimestamp, usageComputed, "") registerKey(api.KeyCredential, TypeCredential, usageUser, "") registerKey(api.KeyDead, TypeIDSet, usageProperty, "") registerKey(api.KeyFolge, TypeIDSet, usageProperty, "") registerKey(api.KeyForward, TypeIDSet, usageProperty, "") registerKey(api.KeyLang, TypeWord, usageUser, "") registerKey(api.KeyLicense, TypeEmpty, usageUser, "") registerKey(api.KeyModified, TypeTimestamp, usageComputed, "") registerKey(api.KeyPrecursor, TypeIDSet, usageUser, api.KeyFolge) registerKey(api.KeyPublished, TypeTimestamp, usageProperty, "") registerKey(api.KeyReadOnly, TypeWord, usageUser, "") registerKey(api.KeySummary, TypeZettelmarkup, usageUser, "") registerKey(api.KeyURL, TypeURL, usageUser, "") registerKey(api.KeyUselessFiles, TypeString, usageProperty, "") registerKey(api.KeyUserID, TypeWord, usageUser, "") registerKey(api.KeyUserRole, TypeWord, usageUser, "") |
︙ | ︙ |
Changes to encoder/encoder_blob_test.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 | package encoder_test import ( "testing" "zettelstore.de/c/api" | < | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package encoder_test import ( "testing" "zettelstore.de/c/api" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" "zettelstore.de/z/parser" _ "zettelstore.de/z/parser/blob" // Allow to use BLOB parser. ) |
︙ | ︙ | |||
50 51 52 53 54 55 56 | } func TestBlob(t *testing.T) { m := meta.New(id.Invalid) m.Set(api.KeyTitle, "PNG") for testNum, tc := range pngTestCases { inp := input.NewInput(tc.blob) | | | 49 50 51 52 53 54 55 56 57 58 59 | } func TestBlob(t *testing.T) { m := meta.New(id.Invalid) m.Set(api.KeyTitle, "PNG") for testNum, tc := range pngTestCases { inp := input.NewInput(tc.blob) pe := &peBlocks{bs: parser.ParseBlocks(inp, m, "png")} checkEncodings(t, testNum, pe, tc.descr, tc.expect, "???") } } |
Changes to encoder/encoder_inline_test.go.
︙ | ︙ | |||
473 474 475 476 477 478 479 | encoderZmk: useZmk, }, }, { descr: "Inline HTML Zettel", zmk: `@@<hr>@@{="html"}`, expect: expectMap{ | | | | < < < < < < < < < < < | 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 | encoderZmk: useZmk, }, }, { descr: "Inline HTML Zettel", zmk: `@@<hr>@@{="html"}`, expect: expectMap{ encoderZJSON: `[{"":"HTML","s":"<hr>"}]`, encoderHTML: `<hr>`, encoderSexpr: `((LITERAL-HTML () "<hr>"))`, encoderText: `<hr>`, encoderZmk: useZmk, }, }, { descr: "", zmk: ``, |
︙ | ︙ |
Changes to encoder/encoder_test.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | "fmt" "testing" "codeberg.org/t73fde/sxpf" "zettelstore.de/c/api" "zettelstore.de/c/sexpr" "zettelstore.de/z/ast" | < | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | "fmt" "testing" "codeberg.org/t73fde/sxpf" "zettelstore.de/c/api" "zettelstore.de/c/sexpr" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" "zettelstore.de/z/input" "zettelstore.de/z/parser" _ "zettelstore.de/z/encoder/htmlenc" // Allow to use HTML encoder. _ "zettelstore.de/z/encoder/sexprenc" // Allow to use sexpr encoder. _ "zettelstore.de/z/encoder/textenc" // Allow to use text encoder. |
︙ | ︙ | |||
63 64 65 66 67 68 69 | inp := input.NewInput([]byte(tc.zmk)) var pe parserEncoder if tc.inline { is := parser.ParseInlines(inp, api.ValueSyntaxZmk) cleaner.CleanInlineSlice(&is) pe = &peInlines{is: is} } else { | | | 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | inp := input.NewInput([]byte(tc.zmk)) var pe parserEncoder if tc.inline { is := parser.ParseInlines(inp, api.ValueSyntaxZmk) cleaner.CleanInlineSlice(&is) pe = &peInlines{is: is} } else { pe = &peBlocks{bs: parser.ParseBlocks(inp, nil, api.ValueSyntaxZmk)} } checkEncodings(t, testNum, pe, tc.descr, tc.expect, tc.zmk) checkSexpr(t, testNum, pe, tc.descr) } } func checkEncodings(t *testing.T, testNum int, pe parserEncoder, descr string, expected expectMap, zmkDefault string) { |
︙ | ︙ |
Changes to encoder/sexprenc/transform.go.
︙ | ︙ | |||
79 80 81 82 83 84 85 86 87 88 89 90 91 92 | return t.getTable(n) case *ast.TranscludeNode: return sxpf.NewPairFromValues(sexpr.SymTransclude, getAttributes(n.Attrs), getReference(n.Ref)) case *ast.BLOBNode: return getBLOB(n) case *ast.TextNode: return sxpf.NewPairFromValues(sexpr.SymText, sxpf.NewString(n.Text)) case *ast.SpaceNode: if t.inVerse { return sxpf.NewPairFromValues(sexpr.SymSpace, sxpf.NewString(n.Lexeme)) } return sxpf.NewPairFromValues(sexpr.SymSpace) case *ast.BreakNode: if n.Hard { | > > | 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | return t.getTable(n) case *ast.TranscludeNode: return sxpf.NewPairFromValues(sexpr.SymTransclude, getAttributes(n.Attrs), getReference(n.Ref)) case *ast.BLOBNode: return getBLOB(n) case *ast.TextNode: return sxpf.NewPairFromValues(sexpr.SymText, sxpf.NewString(n.Text)) case *ast.TagNode: return sxpf.NewPairFromValues(sexpr.SymTag, sxpf.NewString(n.Tag)) case *ast.SpaceNode: if t.inVerse { return sxpf.NewPairFromValues(sexpr.SymSpace, sxpf.NewString(n.Lexeme)) } return sxpf.NewPairFromValues(sexpr.SymSpace) case *ast.BreakNode: if n.Hard { |
︙ | ︙ |
Changes to encoder/textenc/textenc.go.
︙ | ︙ | |||
127 128 129 130 131 132 133 134 135 136 137 138 139 140 | v.visitTable(n) return nil case *ast.TranscludeNode, *ast.BLOBNode: return nil case *ast.TextNode: v.b.WriteString(n.Text) return nil case *ast.SpaceNode: v.b.WriteByte(' ') return nil case *ast.BreakNode: if n.Hard { v.b.WriteByte('\n') } else { | > > > | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | v.visitTable(n) return nil case *ast.TranscludeNode, *ast.BLOBNode: return nil case *ast.TextNode: v.b.WriteString(n.Text) return nil case *ast.TagNode: v.b.WriteString(n.Tag) return nil case *ast.SpaceNode: v.b.WriteByte(' ') return nil case *ast.BreakNode: if n.Hard { v.b.WriteByte('\n') } else { |
︙ | ︙ |
Changes to encoder/zjsonenc/zjsonenc.go.
︙ | ︙ | |||
120 121 122 123 124 125 126 127 128 129 130 131 132 133 | writeEscaped(&v.b, n.Ref.String()) case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.writeNodeStart(zjson.TypeText) v.writeContentStart(zjson.NameString) writeEscaped(&v.b, n.Text) case *ast.SpaceNode: v.writeNodeStart(zjson.TypeSpace) if v.inVerse { v.writeContentStart(zjson.NameString) writeEscaped(&v.b, n.Lexeme) } case *ast.BreakNode: | > > > > | 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | writeEscaped(&v.b, n.Ref.String()) case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.writeNodeStart(zjson.TypeText) v.writeContentStart(zjson.NameString) writeEscaped(&v.b, n.Text) case *ast.TagNode: v.writeNodeStart(zjson.TypeTag) v.writeContentStart(zjson.NameString) writeEscaped(&v.b, n.Tag) case *ast.SpaceNode: v.writeNodeStart(zjson.TypeSpace) if v.inVerse { v.writeContentStart(zjson.NameString) writeEscaped(&v.b, n.Lexeme) } case *ast.BreakNode: |
︙ | ︙ |
Changes to encoder/zmkenc/zmkenc.go.
︙ | ︙ | |||
127 128 129 130 131 132 133 134 135 136 137 138 139 140 | case *ast.TranscludeNode: v.b.WriteStrings("{{{", n.Ref.String(), "}}}") v.visitAttributes(n.Attrs) case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.visitText(n) case *ast.SpaceNode: v.b.WriteString(n.Lexeme) case *ast.BreakNode: v.visitBreak(n) case *ast.LinkNode: v.visitLink(n) case *ast.EmbedRefNode: | > > | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | case *ast.TranscludeNode: v.b.WriteStrings("{{{", n.Ref.String(), "}}}") v.visitAttributes(n.Attrs) case *ast.BLOBNode: v.visitBLOB(n) case *ast.TextNode: v.visitText(n) case *ast.TagNode: v.b.WriteStrings("#", n.Tag) case *ast.SpaceNode: v.b.WriteString(n.Lexeme) case *ast.BreakNode: v.visitBreak(n) case *ast.LinkNode: v.visitLink(n) case *ast.EmbedRefNode: |
︙ | ︙ |
Deleted encoding/atom/atom.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted encoding/encoding.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to encoding/rss/rss.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 17 18 19 20 21 22 | // Package rss provides a RSS encoding. package rss import ( "bytes" "context" "time" "zettelstore.de/c/api" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/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 | // Package rss provides a RSS encoding. package rss import ( "bytes" "context" "encoding/xml" "time" "zettelstore.de/c/api" "zettelstore.de/z/config" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" "zettelstore.de/z/query" ) const ContentType = "application/rss+xml" type Configuration struct { Title string Language string |
︙ | ︙ | |||
46 47 48 49 50 51 52 | c.Copyright = defVals.GetDefault(api.KeyCopyright, "") c.Generator = (kernel.Main.GetConfig(kernel.CoreService, kernel.CoreProgname).(string) + " " + kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) c.NewURLBuilderAbs = func() *api.URLBuilder { return api.NewURLBuilder(baseURL, 'h') } } | | | | | | < < | < | < | > | < < < < | < < < < < < | < > > | | | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < | < < < | < < < < | | > > > > | | > > > > > > > > > > > > > | > > > > > | > > > | > | < < < | 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 | c.Copyright = defVals.GetDefault(api.KeyCopyright, "") c.Generator = (kernel.Main.GetConfig(kernel.CoreService, kernel.CoreProgname).(string) + " " + kernel.Main.GetConfig(kernel.CoreService, kernel.CoreVersion).(string)) c.NewURLBuilderAbs = func() *api.URLBuilder { return api.NewURLBuilder(baseURL, 'h') } } func (c *Configuration) Marshal(q *query.Query, ml []*meta.Meta) ([]byte, error) { textEnc := textenc.Create() rssItems := make([]*RssItem, 0, len(ml)) maxPublished := time.Date(1, time.January, 1, 0, 0, 0, 0, time.Local) for _, m := range ml { var title bytes.Buffer titleIns := parser.ParseMetadata(m.GetTitle()) if _, err := textEnc.WriteInlines(&title, &titleIns); err != nil { title.Reset() title.WriteString(m.GetTitle()) } itemPublished := "" if val, found := m.Get(api.KeyPublished); found { if published, err := time.ParseInLocation(id.ZidLayout, val, time.Local); err == nil { itemPublished = published.UTC().Format(time.RFC1123Z) if maxPublished.Before(published) { maxPublished = published } } } rssItems = append(rssItems, &RssItem{ Title: title.String(), GUID: c.NewURLBuilderAbs().SetZid(api.ZettelID(m.Zid.String())).String(), PubDate: itemPublished, }) } rssPublished := "" if maxPublished.Year() > 1 { rssPublished = maxPublished.UTC().Format(time.RFC1123Z) } var atomLink *AtomLink if s := q.String(); s != "" { atomLink = &AtomLink{ Href: c.NewURLBuilderAbs().AppendQuery(s).String(), Rel: "self", Type: ContentType, } } rssFeed := RssFeed{ Version: "2.0", AtomNamespace: "http://www.w3.org/2005/Atom", Channel: &RssChannel{ Title: c.Title, Link: c.NewURLBuilderAbs().String(), Language: c.Language, Copyright: c.Copyright, PubDate: rssPublished, LastBuildDate: rssPublished, Generator: c.Generator, Docs: "https://www.rssboard.org/rss-specification", AtomLink: atomLink, Items: rssItems, }, } return xml.MarshalIndent(&rssFeed, "", " ") } type ( RssFeed struct { XMLName xml.Name `xml:"rss"` Version string `xml:"version,attr"` AtomNamespace string `xml:"xmlns:atom,attr"` Channel *RssChannel } RssChannel struct { XMLName xml.Name `xml:"channel"` Title string `xml:"title"` Link string `xml:"link"` Description string `xml:"description"` Language string `xml:"language,omitempty"` Copyright string `xml:"copyright,omitempty"` PubDate string `xml:"pubDate,omitempty"` // RFC822 LastBuildDate string `xml:"lastBuildDate,omitempty"` // RFC822 Generator string `xml:"generator,omitempty"` Docs string `xml:"docs,omitempty"` AtomLink *AtomLink Items []*RssItem `xml:"item"` } AtomLink struct { XMLName xml.Name `xml:"atom:link"` Href string `xml:"href,attr"` Rel string `xml:"rel,attr"` Type string `xml:"type,attr"` } RssItem struct { XMLName xml.Name `xml:"item"` Title string `xml:"title"` GUID string `xml:"guid"` PubDate string `xml:"pubDate,omitempty"` // RFC822 } ) |
Deleted encoding/xml/xml.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to evaluator/evaluator.go.
︙ | ︙ | |||
11 12 13 14 15 16 17 | // Package evaluator interprets and evaluates the AST. package evaluator import ( "context" "errors" "fmt" | < | 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // Package evaluator interprets and evaluates the AST. package evaluator import ( "context" "errors" "fmt" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/c/attrs" "zettelstore.de/z/ast" "zettelstore.de/z/box" |
︙ | ︙ | |||
52 53 54 55 56 57 58 | EvaluateBlock(ctx, port, rtConfig, &zn.Ast) } // EvaluateBlock evaluates the given block list in the given context, with // the given ports, and the given environment. func EvaluateBlock(ctx context.Context, port Port, rtConfig config.Config, bns *ast.BlockSlice) { evaluateNode(ctx, port, rtConfig, bns) | | | 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | EvaluateBlock(ctx, port, rtConfig, &zn.Ast) } // EvaluateBlock evaluates the given block list in the given context, with // the given ports, and the given environment. func EvaluateBlock(ctx context.Context, port Port, rtConfig config.Config, bns *ast.BlockSlice) { evaluateNode(ctx, port, rtConfig, bns) cleaner.CleanBlockSlice(bns) } // EvaluateInline evaluates the given inline list in the given context, with // the given ports, and the given environment. func EvaluateInline(ctx context.Context, port Port, rtConfig config.Config, is *ast.InlineSlice) { evaluateNode(ctx, port, rtConfig, is) cleaner.CleanInlineSlice(is) |
︙ | ︙ | |||
200 201 202 203 204 205 206 | // Only zettel references will be evaluated. case ast.RefStateInvalid, ast.RefStateBroken: e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Invalid", "or", "broken", "transclusion", "reference")) case ast.RefStateSelf: e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Self", "transclusion", "reference")) | | < < < < < < | 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | // Only zettel references will be evaluated. case ast.RefStateInvalid, ast.RefStateBroken: e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Invalid", "or", "broken", "transclusion", "reference")) case ast.RefStateSelf: e.transcludeCount++ return makeBlockNode(createInlineErrorText(ref, "Self", "transclusion", "reference")) case ast.RefStateFound, ast.RefStateHosted, ast.RefStateBased, ast.RefStateExternal: return tn case ast.RefStateQuery: e.transcludeCount++ return e.evalQueryTransclusion(tn.Ref.Value) default: return makeBlockNode(createInlineErrorText(ref, "Illegal", "block", "state", strconv.Itoa(int(ref.State)))) } |
︙ | ︙ | |||
381 382 383 384 385 386 387 | // Only zettel references will be evaluated. case ast.RefStateInvalid, ast.RefStateBroken: e.transcludeCount++ return createInlineErrorImage(en) case ast.RefStateSelf: e.transcludeCount++ return createInlineErrorText(ref, "Self", "embed", "reference") | | < < < < < < < | 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 | // Only zettel references will be evaluated. case ast.RefStateInvalid, ast.RefStateBroken: e.transcludeCount++ return createInlineErrorImage(en) case ast.RefStateSelf: e.transcludeCount++ return createInlineErrorText(ref, "Self", "embed", "reference") case ast.RefStateFound, ast.RefStateHosted, ast.RefStateBased, ast.RefStateExternal: return en default: return createInlineErrorText(ref, "Illegal", "inline", "state", strconv.Itoa(int(ref.State))) } zid := mustParseZid(ref) zettel, err := e.port.GetZettel(box.NoEnrichContext(e.ctx), zid) |
︙ | ︙ | |||
498 499 500 501 502 503 504 | Kind: ast.FormatStrong, Inlines: ast.InlineSlice{ln}, } fn.Attrs = fn.Attrs.AddClass("error") return fn } | < < < < < < < < < < < < < < < | 484 485 486 487 488 489 490 491 492 493 494 495 496 497 | Kind: ast.FormatStrong, Inlines: ast.InlineSlice{ln}, } fn.Attrs = fn.Attrs.AddClass("error") return fn } func (e *evaluator) evaluateEmbeddedInline(content []byte, syntax string) ast.InlineSlice { is := parser.ParseInlines(input.NewInput(content), syntax) ast.Walk(e, &is) return is } func (e *evaluator) evaluateEmbeddedZettel(zettel domain.Zettel) *ast.ZettelNode { |
︙ | ︙ |
Changes to evaluator/list.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package evaluator import ( "bytes" "context" | | < | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | //----------------------------------------------------------------------------- package evaluator import ( "bytes" "context" "log" "sort" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/c/attrs" "zettelstore.de/z/ast" "zettelstore.de/z/config" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoding/rss" "zettelstore.de/z/parser" "zettelstore.de/z/query" ) // QueryAction transforms a list of metadata according to query actions into a AST nested list. func QueryAction(ctx context.Context, q *query.Query, ml []*meta.Meta, rtConfig config.Config) ast.BlockNode { |
︙ | ︙ | |||
62 63 64 65 66 67 68 | if act == "TITLE" && i+1 < len(actions) { ap.title = strings.Join(actions[i+1:], " ") break } acts = append(acts, act) } for _, act := range acts { | < < < | | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | if act == "TITLE" && i+1 < len(actions) { ap.title = strings.Join(actions[i+1:], " ") break } acts = append(acts, act) } for _, act := range acts { if act == "RSS" { return ap.createBlockNodeRSS(rtConfig) } key := strings.ToLower(act) switch meta.Type(key) { case meta.TypeWord: return ap.createBlockNodeWord(key) case meta.TypeTagSet: |
︙ | ︙ | |||
218 219 220 221 222 223 224 | buf.WriteByte(':') bufLen := buf.Len() return ccs, bufLen } const fontSizes = 6 // Must be the number of CSS classes zs-font-size-* in base.css | < | 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | buf.WriteByte(':') bufLen := buf.Len() return ccs, bufLen } const fontSizes = 6 // Must be the number of CSS classes zs-font-size-* in base.css func (*actionPara) calcFontSizes(ccs meta.CountedCategories) map[int]attrs.Attributes { var fsAttrs [fontSizes]attrs.Attributes var a attrs.Attributes for i := 0; i < fontSizes; i++ { fsAttrs[i] = a.AddClass("zs-font-size-" + strconv.Itoa(i)) } |
︙ | ︙ | |||
250 251 252 253 254 255 256 | result[count] = fsAttrs[curSize] curSize++ } return result } // Idea: the number of occurences for a specific count is substracted from a budget. | | > < | < | | | < < < < < | | > | < < < < < < < < < < < | 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 | result[count] = fsAttrs[curSize] curSize++ } return result } // Idea: the number of occurences for a specific count is substracted from a budget. budget := len(ccs) / (fontSizes - 1) curBudget := budget curSize := 0 for _, count := range countList { result[count] = fsAttrs[curSize] curBudget -= countMap[count] for curBudget <= 0 { curBudget += budget curSize++ if curSize >= fontSizes { curSize = fontSizes - 1 } } } return result } func (ap *actionPara) createBlockNodeRSS(cfg config.Config) ast.BlockNode { var rssConfig rss.Configuration rssConfig.Setup(ap.ctx, cfg) rssConfig.Title = ap.title data, err := rssConfig.Marshal(ap.q, ap.ml) if err != nil { log.Println("ERRR", err) return nil } return &ast.VerbatimNode{ Kind: ast.VerbatimProg, Attrs: attrs.Attributes{"lang": "xml"}, Content: data, } } |
Changes to go.mod.
1 2 3 4 5 6 | module zettelstore.de/z go 1.19 require ( codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | module zettelstore.de/z go 1.19 require ( codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 github.com/fsnotify/fsnotify v1.5.4 github.com/pascaldekloe/jwt v1.12.0 github.com/yuin/goldmark v1.4.14 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 golang.org/x/text v0.3.7 zettelstore.de/c v0.7.0 ) require golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect |
Changes to go.sum.
1 2 | codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 h1:viya/OgeF16+i8caBPJmcLQhGpZodPh+/nxtJzSSO1s= codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0/go.mod h1:4fAHEF3VH+ofbZkF6NzqiItTNy2X11tVCnZX99jXouA= | | | | | | | | | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0 h1:viya/OgeF16+i8caBPJmcLQhGpZodPh+/nxtJzSSO1s= codeberg.org/t73fde/sxpf v0.0.0-20220719090054-749a39d0a7a0/go.mod h1:4fAHEF3VH+ofbZkF6NzqiItTNy2X11tVCnZX99jXouA= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/pascaldekloe/jwt v1.12.0 h1:imQSkPOtAIBAXoKKjL9ZVJuF/rVqJ+ntiLGpLyeqMUQ= github.com/pascaldekloe/jwt v1.12.0/go.mod h1:LiIl7EwaglmH1hWThd/AmydNCnHf/mmfluBlNqHbk8U= github.com/yuin/goldmark v1.4.14 h1:jwww1XQfhJN7Zm+/a1ZA/3WUiEBEroYFNTiV3dKwM8U= github.com/yuin/goldmark v1.4.14/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc= golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= zettelstore.de/c v0.7.0 h1:+DmAB81uVLtgf5xFKy4HqFqja+6itFyuk45S9QZeP+k= zettelstore.de/c v0.7.0/go.mod h1:+SoneUhKQ81A2Id/bC6FdDYYQAHYfVryh7wHFnnklew= |
Deleted input/entity.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted input/entity_test.go.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to input/input.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // under this license. //----------------------------------------------------------------------------- // Package input provides an abstraction for data to be read. package input import ( "unicode/utf8" ) // Input is an abstract input source type Input struct { // Read-only, will never change Src []byte // The source string | > > | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // under this license. //----------------------------------------------------------------------------- // Package input provides an abstraction for data to be read. package input import ( "html" "unicode" "unicode/utf8" ) // Input is an abstract input source type Input struct { // Read-only, will never change Src []byte // The source string |
︙ | ︙ | |||
132 133 134 135 136 137 138 139 140 141 142 143 144 145 | switch inp.Ch { case EOS, '\n', '\r': return } inp.Next() } } // ScanLineContent reads the reaining input stream and interprets it as lines of text. func (inp *Input) ScanLineContent() []byte { result := make([]byte, 0, len(inp.Src)-inp.Pos+1) for { inp.EatEOL() posL := inp.Pos | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | switch inp.Ch { case EOS, '\n', '\r': return } inp.Next() } } // ScanEntity scans either a named or a numbered entity and returns it as a string. // // For numbered entities (like { or ģ) html.UnescapeString returns // sometimes other values as expected, if the number is not well-formed. This // may happen because of some strange HTML parsing rules. But these do not // apply to Zettelmarkup. Therefore, I parse the number here in the code. func (inp *Input) ScanEntity() (res string, success bool) { if inp.Ch != '&' { return "", false } pos := inp.Pos inp.Next() if inp.Ch == '#' { inp.Next() if inp.Ch == 'x' || inp.Ch == 'X' { return inp.scanEntityBase16() } return inp.scanEntityBase10() } return inp.scanEntityNamed(pos) } func (inp *Input) scanEntityBase16() (string, bool) { inp.Next() if inp.Ch == ';' { return "", false } code := 0 for { switch ch := inp.Ch; ch { case ';': inp.Next() return string(rune(code)), true case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': code = 16*code + int(ch-'0') case 'a', 'b', 'c', 'd', 'e', 'f': code = 16*code + int(ch-'a'+10) case 'A', 'B', 'C', 'D', 'E', 'F': code = 16*code + int(ch-'A'+10) default: return "", false } if code > unicode.MaxRune { return "", false } inp.Next() } } func (inp *Input) scanEntityBase10() (string, bool) { // Base 10 code if inp.Ch == ';' { return "", false } code := 0 for { switch ch := inp.Ch; ch { case ';': inp.Next() return string(rune(code)), true case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': code = 10*code + int(ch-'0') default: return "", false } if code > unicode.MaxRune { return "", false } inp.Next() } } func (inp *Input) scanEntityNamed(pos int) (string, bool) { for { switch inp.Ch { case EOS, '\n', '\r', '&': return "", false case ';': inp.Next() es := string(inp.Src[pos:inp.Pos]) ues := html.UnescapeString(es) if es == ues { return "", false } return ues, true default: inp.Next() } } } // ScanLineContent reads the reaining input stream and interprets it as lines of text. func (inp *Input) ScanLineContent() []byte { result := make([]byte, 0, len(inp.Src)-inp.Pos+1) for { inp.EatEOL() posL := inp.Pos |
︙ | ︙ |
Changes to input/input_test.go.
︙ | ︙ | |||
33 34 35 36 37 38 39 40 41 42 43 44 45 46 | t.Errorf("First ch != 'A', got %q", inp.Ch) } inp.EatEOL() if inp.Ch != 'A' { t.Errorf("First ch != 'A', got %q", inp.Ch) } } func TestAccept(t *testing.T) { t.Parallel() testcases := []struct { accept string src string acc bool | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | t.Errorf("First ch != 'A', got %q", inp.Ch) } inp.EatEOL() if inp.Ch != 'A' { t.Errorf("First ch != 'A', got %q", inp.Ch) } } func TestScanEntity(t *testing.T) { t.Parallel() var testcases = []struct { text string exp string }{ {"", ""}, {"a", ""}, {"&", "&"}, {"	", "\t"}, {""", "\""}, } for id, tc := range testcases { inp := input.NewInput([]byte(tc.text)) got, ok := inp.ScanEntity() if !ok { if tc.exp != "" { t.Errorf("ID=%d, text=%q: expected error, but got %q", id, tc.text, got) } if inp.Pos != 0 { t.Errorf("ID=%d, text=%q: input position advances to %d", id, tc.text, inp.Pos) } continue } if tc.exp != got { t.Errorf("ID=%d, text=%q: expected %q, but got %q", id, tc.text, tc.exp, got) } } } func TestScanIllegalEntity(t *testing.T) { t.Parallel() testcases := []string{"", "a", "& Input →"} for i, tc := range testcases { inp := input.NewInput([]byte(tc)) got, ok := inp.ScanEntity() if ok { t.Errorf("%d: scanning %q was unexpected successful, got %q", i, tc, got) continue } } } func TestAccept(t *testing.T) { t.Parallel() testcases := []struct { accept string src string acc bool |
︙ | ︙ |
Changes to kernel/impl/auth.go.
1 | //----------------------------------------------------------------------------- | | | < < < < | | < < < | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | //----------------------------------------------------------------------------- // Copyright (c) 2021 Detlef Stern // // This file is part of zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package impl import ( "sync" "zettelstore.de/z/auth" "zettelstore.de/z/domain/id" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) type authService struct { srvConfig mxService sync.RWMutex manager auth.Manager createManager kernel.CreateAuthManagerFunc } func (as *authService) Initialize(logger *logger.Logger) { as.logger = logger as.descr = descriptionMap{ kernel.AuthOwner: { "Owner's zettel id", func(val string) interface{} { if owner := as.cur[kernel.AuthOwner]; owner != nil && owner != id.Invalid { return nil } return parseZid(val) }, false, }, kernel.AuthReadonly: { "Read-only mode", func(val string) interface{} { if ro := as.cur[kernel.AuthReadonly]; ro == true { return nil } return parseBool(val) }, true, }, } as.next = interfaceMap{ |
︙ | ︙ |
Changes to kernel/impl/box.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- package impl import ( "context" | < < < | | | | | | | 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 | // under this license. //----------------------------------------------------------------------------- package impl import ( "context" "fmt" "io" "net/url" "strconv" "sync" "zettelstore.de/z/box" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) type boxService struct { srvConfig mxService sync.RWMutex manager box.Manager createManager kernel.CreateBoxManagerFunc } func (ps *boxService) Initialize(logger *logger.Logger) { ps.logger = logger ps.descr = descriptionMap{ kernel.BoxDefaultDirType: { "Default directory box type", ps.noFrozen(func(val string) interface{} { switch val { case kernel.BoxDirTypeNotify, kernel.BoxDirTypeSimple: return val } return nil }), true, }, kernel.BoxURIs: { "Box URI", func(val string) interface{} { uVal, err := url.Parse(val) if err != nil { return nil } if uVal.Scheme == "" { uVal.Scheme = "dir" } return uVal }, true, }, } ps.next = interfaceMap{ kernel.BoxDefaultDirType: kernel.BoxDirTypeNotify, } |
︙ | ︙ |
Changes to kernel/impl/cfg.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- package impl import ( "context" | < | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // under this license. //----------------------------------------------------------------------------- package impl import ( "context" "fmt" "strconv" "strings" "sync" "zettelstore.de/c/api" "zettelstore.de/z/box" |
︙ | ︙ | |||
44 45 46 47 48 49 50 | keyHomeZettel = "home-zettel" keyMaxTransclusions = "max-transclusions" keySiteName = "site-name" keyYAMLHeader = "yaml-header" keyZettelFileSyntax = "zettel-file-syntax" ) | < < | | | | | | < < < < < < < < < < < < < < < | | | | | | | < | | | | | | | | 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 | keyHomeZettel = "home-zettel" keyMaxTransclusions = "max-transclusions" keySiteName = "site-name" keyYAMLHeader = "yaml-header" keyZettelFileSyntax = "zettel-file-syntax" ) func (cs *configService) Initialize(logger *logger.Logger) { cs.logger = logger cs.descr = descriptionMap{ keyDefaultCopyright: {"Default copyright", parseString, true}, keyDefaultLicense: {"Default license", parseString, true}, keyDefaultVisibility: { "Default zettel visibility", func(val string) interface{} { vis := meta.GetVisibility(val) if vis == meta.VisibilityUnknown { return nil } return vis }, true, }, keyExpertMode: {"Expert mode", parseBool, true}, config.KeyFooterHTML: {"Footer HTML", parseString, true}, keyHomeZettel: {"Home zettel", parseZid, true}, api.KeyLang: {"Language", parseString, true}, config.KeyMarkerExternal: {"Marker external URL", parseString, true}, keyMaxTransclusions: {"Maximum transclusions", parseInt64, true}, keySiteName: {"Site name", parseString, true}, keyYAMLHeader: {"YAML header", parseBool, true}, keyZettelFileSyntax: { "Zettel file syntax", func(val string) interface{} { return strings.Fields(val) }, true, }, kernel.ConfigSimpleMode: {"Simple mode", cs.noFrozen(parseBool), true}, } cs.next = interfaceMap{ keyDefaultCopyright: "", keyDefaultLicense: "", keyDefaultVisibility: meta.VisibilityLogin, keyExpertMode: false, config.KeyFooterHTML: "", keyHomeZettel: id.DefaultHomeZid, api.KeyLang: api.ValueLangEN, config.KeyMarkerExternal: "➚", keyMaxTransclusions: int64(1024), keySiteName: "Zettelstore", keyYAMLHeader: false, keyZettelFileSyntax: nil, kernel.ConfigSimpleMode: false, } } func (cs *configService) GetLogger() *logger.Logger { return cs.logger } func (cs *configService) Start(*myKernel) error { cs.logger.Info().Msg("Start Service") data := meta.New(id.ConfigurationZid) |
︙ | ︙ | |||
246 247 248 249 250 251 252 | if result == m { result = m.Clone() } result.Set(key, val) return result } | < < < < | 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | if result == m { result = m.Clone() } result.Set(key, val) return result } // GetSiteName returns the current value of the "site-name" key. func (cs *configService) GetSiteName() string { return cs.GetConfig(keySiteName).(string) } // GetHomeZettel returns the value of the "home-zettel" key. func (cs *configService) GetHomeZettel() id.Zid { homeZid := cs.GetConfig(keyHomeZettel).(id.Zid) if homeZid != id.Invalid { |
︙ | ︙ |
Changes to kernel/impl/cmd.go.
︙ | ︙ | |||
288 289 290 291 292 293 294 | } srvD, found := getService(sess, args[0]) if !found { return true } key := args[1] newValue := strings.Join(args[2:], " ") | | | | 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | } srvD, found := getService(sess, args[0]) if !found { return true } key := args[1] newValue := strings.Join(args[2:], " ") if srvD.srv.SetConfig(key, newValue) { sess.kern.logger.Mandatory().Str("key", key).Str("value", newValue).Msg("Update system configuration") } else { sess.println("Unable to set key", args[1], "to value", newValue) } return true } func cmdServices(sess *cmdSession, _ string, _ []string) bool { table := [][]string{{"Service", "Status"}} for _, name := range sortedServiceNames(sess) { |
︙ | ︙ |
Changes to kernel/impl/config.go.
1 2 3 4 5 6 7 8 9 10 11 12 13 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package impl import ( | < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | //----------------------------------------------------------------------------- // Copyright (c) 2021-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package impl import ( "fmt" "sort" "strconv" "strings" "sync" "zettelstore.de/c/maps" "zettelstore.de/z/domain/id" "zettelstore.de/z/kernel" "zettelstore.de/z/logger" ) type parseFunc func(string) interface{} type configDescription struct { text string parse parseFunc canList bool } type descriptionMap map[string]configDescription type interfaceMap map[string]interface{} |
︙ | ︙ | |||
64 65 66 67 68 69 70 | text = text + " (list)" } result = append(result, serviceConfigDescription{Key: k, Descr: text}) } return result } | < < | | < < | | | | | | | | | | 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 | text = text + " (list)" } result = append(result, serviceConfigDescription{Key: k, Descr: text}) } return result } func (cfg *srvConfig) noFrozen(parse parseFunc) parseFunc { return func(val string) interface{} { if cfg.frozen { return nil } return parse(val) } } func (cfg *srvConfig) SetConfig(key, value string) bool { cfg.mxConfig.Lock() defer cfg.mxConfig.Unlock() descr, ok := cfg.descr[key] if !ok { d, baseKey, num := cfg.getListDescription(key) if num < 0 { return false } for i := num + 1; ; i++ { k := baseKey + strconv.Itoa(i) if _, ok = cfg.next[k]; !ok { break } delete(cfg.next, k) } if num == 0 { return true } descr = d } parse := descr.parse if parse == nil { if cfg.frozen { return false } cfg.next[key] = value return true } iVal := parse(value) if iVal == nil { return false } cfg.next[key] = iVal return true } func (cfg *srvConfig) getListDescription(key string) (configDescription, string, int) { for k, d := range cfg.descr { if !strings.HasSuffix(k, "-") { continue } |
︙ | ︙ | |||
210 211 212 213 214 215 216 | func (cfg *srvConfig) SwitchNextToCur() { cfg.mxConfig.Lock() defer cfg.mxConfig.Unlock() cfg.cur = cfg.next.Clone() } | | < < | | | | | | < < > | | < < > | 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 | func (cfg *srvConfig) SwitchNextToCur() { cfg.mxConfig.Lock() defer cfg.mxConfig.Unlock() cfg.cur = cfg.next.Clone() } func parseString(val string) interface{} { return val } func parseBool(val string) interface{} { if val == "" { return false } switch val[0] { case '0', 'f', 'F', 'n', 'N': return false } return true } func parseInt64(val string) any { if u64, err := strconv.ParseInt(val, 10, 64); err == nil { return u64 } return nil } func parseZid(val string) interface{} { if zid, err := id.Parse(val); err == nil { return zid } return id.Invalid } |
Changes to kernel/impl/core.go.
︙ | ︙ | |||
46 47 48 49 50 51 52 | kernel.CoreDebug: {"Debug mode", parseBool, false}, kernel.CoreGoArch: {"Go processor architecture", nil, false}, kernel.CoreGoOS: {"Go Operating System", nil, false}, kernel.CoreGoVersion: {"Go Version", nil, false}, kernel.CoreHostname: {"Host name", nil, false}, kernel.CorePort: { "Port of command line server", | | | | | | | | 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 | kernel.CoreDebug: {"Debug mode", parseBool, false}, kernel.CoreGoArch: {"Go processor architecture", nil, false}, kernel.CoreGoOS: {"Go Operating System", nil, false}, kernel.CoreGoVersion: {"Go Version", nil, false}, kernel.CoreHostname: {"Host name", nil, false}, kernel.CorePort: { "Port of command line server", cs.noFrozen(func(val string) interface{} { port, err := net.LookupPort("tcp", val) if err != nil { return nil } return port }), true, }, kernel.CoreProgname: {"Program name", nil, false}, kernel.CoreStarted: {"Start time", nil, false}, kernel.CoreVerbose: {"Verbose output", parseBool, true}, kernel.CoreVersion: { "Version", cs.noFrozen(func(val string) interface{} { if val == "" { return kernel.CoreDefaultVersion } return val }), false, }, kernel.CoreVTime: {"Version time", nil, false}, } cs.next = interfaceMap{ kernel.CoreDebug: false, |
︙ | ︙ |
Changes to kernel/impl/impl.go.
︙ | ︙ | |||
299 300 301 302 303 304 305 | kern.profile = nil kern.profileFile = nil return err } // --- Service handling -------------------------------------------------- | < < | | | 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | kern.profile = nil kern.profileFile = nil return err } // --- Service handling -------------------------------------------------- func (kern *myKernel) SetConfig(srvnum kernel.Service, key, value string) bool { kern.mx.Lock() defer kern.mx.Unlock() if srvD, ok := kern.srvs[srvnum]; ok { return srvD.srv.SetConfig(key, value) } return false } func (kern *myKernel) GetConfig(srvnum kernel.Service, key string) interface{} { kern.mx.RLock() defer kern.mx.RUnlock() if srvD, ok := kern.srvs[srvnum]; ok { return srvD.srv.GetConfig(key) |
︙ | ︙ | |||
446 447 448 449 450 451 452 | // Get service logger. GetLogger() *logger.Logger // ConfigDescriptions returns a sorted list of configuration descriptions. ConfigDescriptions() []serviceConfigDescription // SetConfig stores a configuration value. | | | 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 | // Get service logger. GetLogger() *logger.Logger // ConfigDescriptions returns a sorted list of configuration descriptions. ConfigDescriptions() []serviceConfigDescription // SetConfig stores a configuration value. SetConfig(key, value string) bool // GetConfig returns the current configuration value. GetConfig(key string) interface{} // GetNextConfig returns the next configuration value. GetNextConfig(key string) interface{} |
︙ | ︙ |
Changes to kernel/impl/web.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 | //----------------------------------------------------------------------------- package impl import ( "errors" "net" | < | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //----------------------------------------------------------------------------- package impl import ( "errors" "net" "net/url" "os" "path/filepath" "strconv" "strings" "sync" "time" |
︙ | ︙ | |||
31 32 33 34 35 36 37 | type webService struct { srvConfig mxService sync.RWMutex srvw server.Server setupServer kernel.SetupWebServerFunc } | < < | | < < > | | | | < | | < > < | | | | | | | | | | | | 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 | type webService struct { srvConfig mxService sync.RWMutex srvw server.Server setupServer kernel.SetupWebServerFunc } func (ws *webService) Initialize(logger *logger.Logger) { ws.logger = logger ws.descr = descriptionMap{ kernel.WebAssetDir: { "Asset file directory", func(val string) any { val = filepath.Clean(val) if finfo, err := os.Stat(val); err == nil && finfo.IsDir() { return val } return nil }, true, }, kernel.WebBaseURL: { "Base URL", func(val string) any { if _, err := url.Parse(val); err != nil { return nil } return val }, true, }, kernel.WebListenAddress: { "Listen address", func(val string) interface{} { host, port, err := net.SplitHostPort(val) if err != nil { return nil } if _, err = net.LookupPort("tcp", port); err != nil { return nil } return net.JoinHostPort(host, port) }, true}, kernel.WebMaxRequestSize: {"Max Request Size", parseInt64, true}, kernel.WebPersistentCookie: {"Persistent cookie", parseBool, true}, kernel.WebSecureCookie: {"Secure cookie", parseBool, true}, kernel.WebTokenLifetimeAPI: { "Token lifetime API", makeDurationParser(10*time.Minute, 0, 1*time.Hour), true, }, kernel.WebTokenLifetimeHTML: { "Token lifetime HTML", makeDurationParser(1*time.Hour, 1*time.Minute, 30*24*time.Hour), true, }, kernel.WebURLPrefix: { "URL prefix under which the web server runs", func(val string) interface{} { if val != "" && val[0] == '/' && val[len(val)-1] == '/' { return val } return nil }, true, }, } ws.next = interfaceMap{ kernel.WebAssetDir: "", kernel.WebBaseURL: "http://127.0.0.1:23123/", kernel.WebListenAddress: "127.0.0.1:23123", kernel.WebMaxRequestSize: int64(16 * 1024 * 1024), kernel.WebPersistentCookie: false, kernel.WebSecureCookie: true, kernel.WebTokenLifetimeAPI: 1 * time.Hour, kernel.WebTokenLifetimeHTML: 10 * time.Minute, kernel.WebURLPrefix: "/", } } func makeDurationParser(defDur, minDur, maxDur time.Duration) parseFunc { return func(val string) interface{} { if d, err := strconv.ParseUint(val, 10, 64); err == nil { secs := time.Duration(d) * time.Minute if secs < minDur { return minDur } if secs > maxDur { return maxDur } return secs } return defDur } } var errWrongBasePrefix = errors.New(kernel.WebURLPrefix + " does not match " + kernel.WebBaseURL) func (ws *webService) GetLogger() *logger.Logger { return ws.logger } |
︙ | ︙ | |||
147 148 149 150 151 152 153 | if !strings.HasSuffix(baseURL, urlPrefix) { ws.logger.Fatal().Str("base-url", baseURL).Str("url-prefix", urlPrefix).Msg( "url-prefix is not a suffix of base-url") return errWrongBasePrefix } | < < < < | 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | if !strings.HasSuffix(baseURL, urlPrefix) { ws.logger.Fatal().Str("base-url", baseURL).Str("url-prefix", urlPrefix).Msg( "url-prefix is not a suffix of base-url") return errWrongBasePrefix } srvw := impl.New(ws.logger, listenAddr, baseURL, urlPrefix, persistentCookie, secureCookie, maxRequestSize, kern.auth.manager) err := kern.web.setupServer(srvw, kern.box.manager, kern.auth.manager, &kern.cfg) if err != nil { ws.logger.Fatal().Err(err).Msg("Unable to create") return err } if kern.core.GetNextConfig(kernel.CoreDebug).(bool) { |
︙ | ︙ | |||
174 175 176 177 178 179 180 | ws.mxService.Unlock() if kern.cfg.GetConfig(kernel.ConfigSimpleMode).(bool) { listenAddr := ws.GetNextConfig(kernel.WebListenAddress).(string) if idx := strings.LastIndexByte(listenAddr, ':'); idx >= 0 { ws.logger.Mandatory().Msg(strings.Repeat("--------------------", 3)) ws.logger.Mandatory().Msg("Open your browser and enter the following URL:") | | < < < | 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | ws.mxService.Unlock() if kern.cfg.GetConfig(kernel.ConfigSimpleMode).(bool) { listenAddr := ws.GetNextConfig(kernel.WebListenAddress).(string) if idx := strings.LastIndexByte(listenAddr, ':'); idx >= 0 { ws.logger.Mandatory().Msg(strings.Repeat("--------------------", 3)) ws.logger.Mandatory().Msg("Open your browser and enter the following URL:") ws.logger.Mandatory().Msg(" http://localhost%v" + listenAddr[idx:]) } } return nil } func (ws *webService) IsStarted() bool { |
︙ | ︙ |
Changes to kernel/kernel.go.
︙ | ︙ | |||
58 59 60 61 62 63 64 | // StopProfiling stops the current profiling and writes the result to // the file, which was named during StartProfiling(). // It will always be called before the software stops its operations. StopProfiling() error // SetConfig stores a configuration value. | | | 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | // StopProfiling stops the current profiling and writes the result to // the file, which was named during StartProfiling(). // It will always be called before the software stops its operations. StopProfiling() error // SetConfig stores a configuration value. SetConfig(srv Service, key, value string) bool // GetConfig returns a configuration value. GetConfig(srv Service, key string) interface{} // GetConfigList returns a sorted list of configuration data. GetConfigList(Service) []KeyDescrValue |
︙ | ︙ | |||
147 148 149 150 151 152 153 | // Defined values for core service. const ( CoreDefaultVersion = "unknown" ) // Constants for config service keys. const ( | | < < < < < < < < < | 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 | // Defined values for core service. const ( CoreDefaultVersion = "unknown" ) // Constants for config service keys. const ( ConfigSimpleMode = "simple-mode" ) // Constants for authentication service keys. const ( AuthOwner = "owner" AuthReadonly = "readonly" ) // Constants for box service keys. const ( BoxDefaultDirType = "defdirtype" BoxURIs = "box-uri-" ) // Allowed values for BoxDefaultDirType const ( BoxDirTypeNotify = "notify" BoxDirTypeSimple = "simple" ) // Constants for web service keys. const ( WebAssetDir = "asset-dir" WebBaseURL = "base-url" WebListenAddress = "listen" WebPersistentCookie = "persistent" WebMaxRequestSize = "max-request-size" |
︙ | ︙ |
Changes to logger/message.go.
︙ | ︙ | |||
65 66 67 68 69 70 71 | buf = append(buf, '=') m.buf = append(buf, val...) } return m } // Bool adds a boolean value to the full message | | < | 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | buf = append(buf, '=') m.buf = append(buf, val...) } return m } // Bool adds a boolean value to the full message func (m *Message) Bool(text string, val bool) { if val { m.Str(text, "true") } else { m.Str(text, "false") } } // Bytes adds a byte slice value to the full message func (m *Message) Bytes(text string, val []byte) *Message { if m.Enabled() { buf := append(m.buf, ',', ' ') buf = append(buf, text...) |
︙ | ︙ |
Changes to parser/cleaner/cleaner.go.
1 2 3 4 5 6 7 8 9 10 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- | | | | | | < | | | | < | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package cleaner provides funxtions to clean up the parsed AST. package cleaner import ( "bytes" "strconv" "zettelstore.de/z/ast" "zettelstore.de/z/encoder/textenc" "zettelstore.de/z/strfun" ) // CleanBlockSlice cleans the given block list. func CleanBlockSlice(bs *ast.BlockSlice) { cleanNode(bs) } // CleanInlineSlice cleans the given inline list. func CleanInlineSlice(is *ast.InlineSlice) { cleanNode(is) } func cleanNode(n ast.Node) { cv := cleanVisitor{ textEnc: textenc.Create(), hasMark: false, doMark: false, } ast.Walk(&cv, n) if cv.hasMark { cv.doMark = true ast.Walk(&cv, n) } } type cleanVisitor struct { textEnc *textenc.Encoder ids map[string]ast.Node hasMark bool doMark bool } func (cv *cleanVisitor) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.HeadingNode: cv.visitHeading(n) return nil case *ast.MarkNode: cv.visitMark(n) return nil } return cv } func (cv *cleanVisitor) visitHeading(hn *ast.HeadingNode) { if cv.doMark || hn == nil || len(hn.Inlines) == 0 { return } if hn.Slug == "" { var buf bytes.Buffer _, err := cv.textEnc.WriteInlines(&buf, &hn.Inlines) |
︙ | ︙ |
Changes to parser/parser.go.
︙ | ︙ | |||
89 90 91 92 93 94 95 | if !ok { return false } return pi.IsImageFormat } // ParseBlocks parses some input and returns a slice of block nodes. | | < < < | | 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | if !ok { return false } return pi.IsImageFormat } // ParseBlocks parses some input and returns a slice of block nodes. func ParseBlocks(inp *input.Input, m *meta.Meta, syntax string) ast.BlockSlice { bs := Get(syntax).ParseBlocks(inp, m, syntax) cleaner.CleanBlockSlice(&bs) return bs } // ParseInlines parses some input and returns a slice of inline nodes. func ParseInlines(inp *input.Input, syntax string) ast.InlineSlice { // Do not clean, because we don't know the context where this function will be called. return Get(syntax).ParseInlines(inp, syntax) |
︙ | ︙ | |||
132 133 134 135 136 137 138 | if syntax == "" { syntax, _ = inhMeta.Get(api.KeySyntax) } parseMeta := inhMeta if syntax == api.ValueSyntaxNone { parseMeta = m } | < < < < < | | 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | if syntax == "" { syntax, _ = inhMeta.Get(api.KeySyntax) } parseMeta := inhMeta if syntax == api.ValueSyntaxNone { parseMeta = m } return &ast.ZettelNode{ Meta: m, Content: zettel.Content, Zid: m.Zid, InhMeta: inhMeta, Ast: ParseBlocks(input.NewInput(zettel.Content.AsBytes()), parseMeta, syntax), Syntax: syntax, } } |
Changes to parser/pikchr/internal/pikchr.go.
︙ | ︙ | |||
128 129 130 131 132 133 134 | "math" "os" "regexp" "strconv" "strings" ) | < < < < | 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | "math" "os" "regexp" "strconv" "strings" ) // Numeric value type PNum = float64 // Compass points const ( CP_N uint8 = iota + 1 CP_NE |
︙ | ︙ | |||
364 365 366 367 368 369 370 | } /* Each call to the pikchr() subroutine uses an instance of the following ** object to pass around context to all of its subroutines. */ type Pik struct { nErr int /* Number of errors seen */ | < | 360 361 362 363 364 365 366 367 368 369 370 371 372 373 | } /* Each call to the pikchr() subroutine uses an instance of the following ** object to pass around context to all of its subroutines. */ type Pik struct { nErr int /* Number of errors seen */ sIn PToken /* Input Pikchr-language text */ zOut bytes.Buffer /* Result accumulates here */ nOut uint /* Bytes written to zOut[] so far */ nOutAlloc uint /* Space allocated to zOut[] */ eDir uint8 /* Current direction */ mFlags uint /* Flags passed to pikchr() */ cur *PObj /* Object under construction */ |
︙ | ︙ | |||
438 439 440 441 442 443 444 | xOffset func(*Pik, *PObj, uint8) PPoint /* Offset from .c to edge point */ xFit func(pik *Pik, pobj *PObj, w PNum, h PNum) /* Size to fit text */ xRender func(*Pik, *PObj) /* Render */ } func yytestcase(condition bool) {} | | | 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 | xOffset func(*Pik, *PObj, uint8) PPoint /* Offset from .c to edge point */ xFit func(pik *Pik, pobj *PObj, w PNum, h PNum) /* Size to fit text */ xRender func(*Pik, *PObj) /* Render */ } func yytestcase(condition bool) {} //line 475 "pikchr.go" /**************** End of %include directives **********************************/ /* These constants specify the various numeric values for terminal symbols. ***************** Begin token definitions *************************************/ const ( T_ID = 1 |
︙ | ︙ | |||
1614 1615 1616 1617 1618 1619 1620 | ** Note: during a reduce, the only symbols destroyed are those ** which appear on the RHS of the rule, but which are *not* used ** inside the C code. */ /********* Begin destructor definitions ***************************************/ case 99: /* statement_list */ { | | | | | | 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 | ** Note: during a reduce, the only symbols destroyed are those ** which appear on the RHS of the rule, but which are *not* used ** inside the C code. */ /********* Begin destructor definitions ***************************************/ case 99: /* statement_list */ { //line 455 "pikchr.y" p.pik_elist_free(&(yypminor.yy186)) //line 1651 "pikchr.go" } break case 100: /* statement */ case 101: /* unnamed_statement */ case 102: /* basetype */ { //line 457 "pikchr.y" p.pik_elem_free((yypminor.yy104)) //line 1660 "pikchr.go" } break /********* End destructor definitions *****************************************/ default: break /* If no destructor action specified: do nothing */ } } |
︙ | ︙ | |||
1837 1838 1839 1840 1841 1842 1843 | } for yypParser.yytos > 0 { yypParser.yy_pop_parser_stack() } /* Here code is inserted which will execute if the parser ** stack every overflows */ /******** Begin %stack_overflow code ******************************************/ | | | | 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 | } for yypParser.yytos > 0 { yypParser.yy_pop_parser_stack() } /* Here code is inserted which will execute if the parser ** stack every overflows */ /******** Begin %stack_overflow code ******************************************/ //line 488 "pikchr.y" p.pik_error(nil, "parser stack overflow") //line 1874 "pikchr.go" /******** End %stack_overflow code ********************************************/ /* Suppress warning about unused %extra_argument var */ yypParser.p = p } /* |
︙ | ︙ | |||
2263 2264 2265 2266 2267 2268 2269 | ** #line <lineno> <grammarfile> ** { ... } // User supplied code ** #line <lineno> <thisfile> ** break; */ /********** Begin reduce actions **********************************************/ case 0: /* document ::= statement_list */ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 | ** #line <lineno> <grammarfile> ** { ... } // User supplied code ** #line <lineno> <thisfile> ** break; */ /********** Begin reduce actions **********************************************/ case 0: /* document ::= statement_list */ //line 492 "pikchr.y" { p.pik_render(yypParser.yystack[yypParser.yytos+0].minor.yy186) } //line 2301 "pikchr.go" break case 1: /* statement_list ::= statement */ //line 495 "pikchr.y" { yylhsminor.yy186 = p.pik_elist_append(nil, yypParser.yystack[yypParser.yytos+0].minor.yy104) } //line 2306 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy186 = yylhsminor.yy186 break case 2: /* statement_list ::= statement_list EOL statement */ //line 497 "pikchr.y" { yylhsminor.yy186 = p.pik_elist_append(yypParser.yystack[yypParser.yytos+-2].minor.yy186, yypParser.yystack[yypParser.yytos+0].minor.yy104) } //line 2312 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy186 = yylhsminor.yy186 break case 3: /* statement ::= */ //line 500 "pikchr.y" { yypParser.yystack[yypParser.yytos+1].minor.yy104 = nil } //line 2318 "pikchr.go" break case 4: /* statement ::= direction */ //line 501 "pikchr.y" { p.pik_set_direction(uint8(yypParser.yystack[yypParser.yytos+0].minor.yy0.eCode)) yylhsminor.yy104 = nil } //line 2323 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy104 = yylhsminor.yy104 break case 5: /* statement ::= lvalue ASSIGN rvalue */ //line 502 "pikchr.y" { p.pik_set_var(&yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy153, &yypParser.yystack[yypParser.yytos+-1].minor.yy0) yylhsminor.yy104 = nil } //line 2329 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy104 = yylhsminor.yy104 break case 6: /* statement ::= PLACENAME COLON unnamed_statement */ //line 504 "pikchr.y" { yylhsminor.yy104 = yypParser.yystack[yypParser.yytos+0].minor.yy104 p.pik_elem_setname(yypParser.yystack[yypParser.yytos+0].minor.yy104, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2335 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy104 = yylhsminor.yy104 break case 7: /* statement ::= PLACENAME COLON position */ //line 506 "pikchr.y" { yylhsminor.yy104 = p.pik_elem_new(nil, nil, nil) if yylhsminor.yy104 != nil { yylhsminor.yy104.ptAt = yypParser.yystack[yypParser.yytos+0].minor.yy79 p.pik_elem_setname(yylhsminor.yy104, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } } //line 2342 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy104 = yylhsminor.yy104 break case 8: /* statement ::= unnamed_statement */ //line 508 "pikchr.y" { yylhsminor.yy104 = yypParser.yystack[yypParser.yytos+0].minor.yy104 } //line 2348 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy104 = yylhsminor.yy104 break case 9: /* statement ::= print prlist */ //line 509 "pikchr.y" { p.pik_append("<br>\n") yypParser.yystack[yypParser.yytos+-1].minor.yy104 = nil } //line 2354 "pikchr.go" break case 10: /* statement ::= ASSERT LP expr EQ expr RP */ //line 514 "pikchr.y" { yypParser.yystack[yypParser.yytos+-5].minor.yy104 = p.pik_assert(yypParser.yystack[yypParser.yytos+-3].minor.yy153, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+-1].minor.yy153) } //line 2359 "pikchr.go" break case 11: /* statement ::= ASSERT LP position EQ position RP */ //line 516 "pikchr.y" { yypParser.yystack[yypParser.yytos+-5].minor.yy104 = p.pik_position_assert(&yypParser.yystack[yypParser.yytos+-3].minor.yy79, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, &yypParser.yystack[yypParser.yytos+-1].minor.yy79) } //line 2364 "pikchr.go" break case 12: /* statement ::= DEFINE ID CODEBLOCK */ //line 517 "pikchr.y" { yypParser.yystack[yypParser.yytos+-2].minor.yy104 = nil p.pik_add_macro(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2369 "pikchr.go" break case 13: /* rvalue ::= PLACENAME */ //line 528 "pikchr.y" { yylhsminor.yy153 = p.pik_lookup_color(&yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2374 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy153 = yylhsminor.yy153 break case 14: /* pritem ::= FILL */ fallthrough case 15: /* pritem ::= COLOR */ yytestcase(yyruleno == 15) fallthrough case 16: /* pritem ::= THICKNESS */ yytestcase(yyruleno == 16) //line 533 "pikchr.y" { p.pik_append_num("", p.pik_value(yypParser.yystack[yypParser.yytos+0].minor.yy0.String(), nil)) } //line 2384 "pikchr.go" break case 17: /* pritem ::= rvalue */ //line 536 "pikchr.y" { p.pik_append_num("", yypParser.yystack[yypParser.yytos+0].minor.yy153) } //line 2389 "pikchr.go" break case 18: /* pritem ::= STRING */ //line 537 "pikchr.y" { p.pik_append_text(string(yypParser.yystack[yypParser.yytos+0].minor.yy0.z[1:yypParser.yystack[yypParser.yytos+0].minor.yy0.n-1]), 0) } //line 2394 "pikchr.go" break case 19: /* prsep ::= COMMA */ //line 538 "pikchr.y" { p.pik_append(" ") } //line 2399 "pikchr.go" break case 20: /* unnamed_statement ::= basetype attribute_list */ //line 541 "pikchr.y" { yylhsminor.yy104 = yypParser.yystack[yypParser.yytos+-1].minor.yy104 p.pik_after_adding_attributes(yylhsminor.yy104) } //line 2404 "pikchr.go" yypParser.yystack[yypParser.yytos+-1].minor.yy104 = yylhsminor.yy104 break case 21: /* basetype ::= CLASSNAME */ //line 543 "pikchr.y" { yylhsminor.yy104 = p.pik_elem_new(&yypParser.yystack[yypParser.yytos+0].minor.yy0, nil, nil) } //line 2410 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy104 = yylhsminor.yy104 break case 22: /* basetype ::= STRING textposition */ //line 545 "pikchr.y" { yypParser.yystack[yypParser.yytos+-1].minor.yy0.eCode = int16(yypParser.yystack[yypParser.yytos+0].minor.yy112) yylhsminor.yy104 = p.pik_elem_new(nil, &yypParser.yystack[yypParser.yytos+-1].minor.yy0, nil) } //line 2416 "pikchr.go" yypParser.yystack[yypParser.yytos+-1].minor.yy104 = yylhsminor.yy104 break case 23: /* basetype ::= LB savelist statement_list RB */ //line 547 "pikchr.y" { p.list = yypParser.yystack[yypParser.yytos+-2].minor.yy186 yypParser.yystack[yypParser.yytos+-3].minor.yy104 = p.pik_elem_new(nil, nil, yypParser.yystack[yypParser.yytos+-1].minor.yy186) if yypParser.yystack[yypParser.yytos+-3].minor.yy104 != nil { yypParser.yystack[yypParser.yytos+-3].minor.yy104.errTok = yypParser.yystack[yypParser.yytos+0].minor.yy0 } } //line 2422 "pikchr.go" break case 24: /* savelist ::= */ //line 552 "pikchr.y" { yypParser.yystack[yypParser.yytos+1].minor.yy186 = p.list p.list = nil } //line 2427 "pikchr.go" break case 25: /* relexpr ::= expr */ //line 559 "pikchr.y" { yylhsminor.yy10.rAbs = yypParser.yystack[yypParser.yytos+0].minor.yy153 yylhsminor.yy10.rRel = 0 } //line 2432 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy10 = yylhsminor.yy10 break case 26: /* relexpr ::= expr PERCENT */ //line 560 "pikchr.y" { yylhsminor.yy10.rAbs = 0 yylhsminor.yy10.rRel = yypParser.yystack[yypParser.yytos+-1].minor.yy153 / 100 } //line 2438 "pikchr.go" yypParser.yystack[yypParser.yytos+-1].minor.yy10 = yylhsminor.yy10 break case 27: /* optrelexpr ::= */ //line 562 "pikchr.y" { yypParser.yystack[yypParser.yytos+1].minor.yy10.rAbs = 0 yypParser.yystack[yypParser.yytos+1].minor.yy10.rRel = 1.0 } //line 2444 "pikchr.go" break case 28: /* attribute_list ::= relexpr alist */ //line 564 "pikchr.y" { p.pik_add_direction(nil, &yypParser.yystack[yypParser.yytos+-1].minor.yy10) } //line 2449 "pikchr.go" break case 29: /* attribute ::= numproperty relexpr */ //line 568 "pikchr.y" { p.pik_set_numprop(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy10) } //line 2454 "pikchr.go" break case 30: /* attribute ::= dashproperty expr */ //line 569 "pikchr.y" { p.pik_set_dashed(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy153) } //line 2459 "pikchr.go" break case 31: /* attribute ::= dashproperty */ //line 570 "pikchr.y" { p.pik_set_dashed(&yypParser.yystack[yypParser.yytos+0].minor.yy0, nil) } //line 2464 "pikchr.go" break case 32: /* attribute ::= colorproperty rvalue */ //line 571 "pikchr.y" { p.pik_set_clrprop(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy153) } //line 2469 "pikchr.go" break case 33: /* attribute ::= go direction optrelexpr */ //line 572 "pikchr.y" { p.pik_add_direction(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy10) } //line 2474 "pikchr.go" break case 34: /* attribute ::= go direction even position */ //line 573 "pikchr.y" { p.pik_evenwith(&yypParser.yystack[yypParser.yytos+-2].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2479 "pikchr.go" break case 35: /* attribute ::= CLOSE */ //line 574 "pikchr.y" { p.pik_close_path(&yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2484 "pikchr.go" break case 36: /* attribute ::= CHOP */ //line 575 "pikchr.y" { p.cur.bChop = true } //line 2489 "pikchr.go" break case 37: /* attribute ::= FROM position */ //line 576 "pikchr.y" { p.pik_set_from(p.cur, &yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2494 "pikchr.go" break case 38: /* attribute ::= TO position */ //line 577 "pikchr.y" { p.pik_add_to(p.cur, &yypParser.yystack[yypParser.yytos+-1].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2499 "pikchr.go" break case 39: /* attribute ::= THEN */ //line 578 "pikchr.y" { p.pik_then(&yypParser.yystack[yypParser.yytos+0].minor.yy0, p.cur) } //line 2504 "pikchr.go" break case 40: /* attribute ::= THEN optrelexpr HEADING expr */ fallthrough case 42: /* attribute ::= GO optrelexpr HEADING expr */ yytestcase(yyruleno == 42) //line 580 "pikchr.y" { p.pik_move_hdg(&yypParser.yystack[yypParser.yytos+-2].minor.yy10, &yypParser.yystack[yypParser.yytos+-1].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy153, nil, &yypParser.yystack[yypParser.yytos+-3].minor.yy0) } //line 2511 "pikchr.go" break case 41: /* attribute ::= THEN optrelexpr EDGEPT */ fallthrough case 43: /* attribute ::= GO optrelexpr EDGEPT */ yytestcase(yyruleno == 43) //line 581 "pikchr.y" { p.pik_move_hdg(&yypParser.yystack[yypParser.yytos+-1].minor.yy10, nil, 0, &yypParser.yystack[yypParser.yytos+0].minor.yy0, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2518 "pikchr.go" break case 44: /* attribute ::= AT position */ //line 586 "pikchr.y" { p.pik_set_at(nil, &yypParser.yystack[yypParser.yytos+0].minor.yy79, &yypParser.yystack[yypParser.yytos+-1].minor.yy0) } //line 2523 "pikchr.go" break case 45: /* attribute ::= SAME */ //line 588 "pikchr.y" { p.pik_same(nil, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2528 "pikchr.go" break case 46: /* attribute ::= SAME AS object */ //line 589 "pikchr.y" { p.pik_same(yypParser.yystack[yypParser.yytos+0].minor.yy104, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2533 "pikchr.go" break case 47: /* attribute ::= STRING textposition */ //line 590 "pikchr.y" { p.pik_add_txt(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, int16(yypParser.yystack[yypParser.yytos+0].minor.yy112)) } //line 2538 "pikchr.go" break case 48: /* attribute ::= FIT */ //line 591 "pikchr.y" { p.pik_size_to_fit(&yypParser.yystack[yypParser.yytos+0].minor.yy0, 3) } //line 2543 "pikchr.go" break case 49: /* attribute ::= BEHIND object */ //line 592 "pikchr.y" { p.pik_behind(yypParser.yystack[yypParser.yytos+0].minor.yy104) } //line 2548 "pikchr.go" break case 50: /* withclause ::= DOT_E edge AT position */ fallthrough case 51: /* withclause ::= edge AT position */ yytestcase(yyruleno == 51) //line 600 "pikchr.y" { p.pik_set_at(&yypParser.yystack[yypParser.yytos+-2].minor.yy0, &yypParser.yystack[yypParser.yytos+0].minor.yy79, &yypParser.yystack[yypParser.yytos+-1].minor.yy0) } //line 2555 "pikchr.go" break case 52: /* numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */ //line 604 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+0].minor.yy0 } //line 2560 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy0 = yylhsminor.yy0 break case 53: /* boolproperty ::= CW */ //line 615 "pikchr.y" { p.cur.cw = true } //line 2566 "pikchr.go" break case 54: /* boolproperty ::= CCW */ //line 616 "pikchr.y" { p.cur.cw = false } //line 2571 "pikchr.go" break case 55: /* boolproperty ::= LARROW */ //line 617 "pikchr.y" { p.cur.larrow = true p.cur.rarrow = false } //line 2576 "pikchr.go" break case 56: /* boolproperty ::= RARROW */ //line 618 "pikchr.y" { p.cur.larrow = false p.cur.rarrow = true } //line 2581 "pikchr.go" break case 57: /* boolproperty ::= LRARROW */ //line 619 "pikchr.y" { p.cur.larrow = true p.cur.rarrow = true } //line 2586 "pikchr.go" break case 58: /* boolproperty ::= INVIS */ //line 620 "pikchr.y" { p.cur.sw = 0.0 } //line 2591 "pikchr.go" break case 59: /* boolproperty ::= THICK */ //line 621 "pikchr.y" { p.cur.sw *= 1.5 } //line 2596 "pikchr.go" break case 60: /* boolproperty ::= THIN */ //line 622 "pikchr.y" { p.cur.sw *= 0.67 } //line 2601 "pikchr.go" break case 61: /* boolproperty ::= SOLID */ //line 623 "pikchr.y" { p.cur.sw = p.pik_value("thickness", nil) p.cur.dotted = 0.0 p.cur.dashed = 0.0 } //line 2607 "pikchr.go" break case 62: /* textposition ::= */ //line 626 "pikchr.y" { yypParser.yystack[yypParser.yytos+1].minor.yy112 = 0 } //line 2612 "pikchr.go" break case 63: /* textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */ //line 629 "pikchr.y" { yylhsminor.yy112 = pik_text_position(yypParser.yystack[yypParser.yytos+-1].minor.yy112, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2617 "pikchr.go" yypParser.yystack[yypParser.yytos+-1].minor.yy112 = yylhsminor.yy112 break case 64: /* position ::= expr COMMA expr */ //line 632 "pikchr.y" { yylhsminor.yy79.x = yypParser.yystack[yypParser.yytos+-2].minor.yy153 yylhsminor.yy79.y = yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2623 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yylhsminor.yy79 break case 65: /* position ::= place PLUS expr COMMA expr */ //line 634 "pikchr.y" { yylhsminor.yy79.x = yypParser.yystack[yypParser.yytos+-4].minor.yy79.x + yypParser.yystack[yypParser.yytos+-2].minor.yy153 yylhsminor.yy79.y = yypParser.yystack[yypParser.yytos+-4].minor.yy79.y + yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2629 "pikchr.go" yypParser.yystack[yypParser.yytos+-4].minor.yy79 = yylhsminor.yy79 break case 66: /* position ::= place MINUS expr COMMA expr */ //line 635 "pikchr.y" { yylhsminor.yy79.x = yypParser.yystack[yypParser.yytos+-4].minor.yy79.x - yypParser.yystack[yypParser.yytos+-2].minor.yy153 yylhsminor.yy79.y = yypParser.yystack[yypParser.yytos+-4].minor.yy79.y - yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2635 "pikchr.go" yypParser.yystack[yypParser.yytos+-4].minor.yy79 = yylhsminor.yy79 break case 67: /* position ::= place PLUS LP expr COMMA expr RP */ //line 637 "pikchr.y" { yylhsminor.yy79.x = yypParser.yystack[yypParser.yytos+-6].minor.yy79.x + yypParser.yystack[yypParser.yytos+-3].minor.yy153 yylhsminor.yy79.y = yypParser.yystack[yypParser.yytos+-6].minor.yy79.y + yypParser.yystack[yypParser.yytos+-1].minor.yy153 } //line 2641 "pikchr.go" yypParser.yystack[yypParser.yytos+-6].minor.yy79 = yylhsminor.yy79 break case 68: /* position ::= place MINUS LP expr COMMA expr RP */ //line 639 "pikchr.y" { yylhsminor.yy79.x = yypParser.yystack[yypParser.yytos+-6].minor.yy79.x - yypParser.yystack[yypParser.yytos+-3].minor.yy153 yylhsminor.yy79.y = yypParser.yystack[yypParser.yytos+-6].minor.yy79.y - yypParser.yystack[yypParser.yytos+-1].minor.yy153 } //line 2647 "pikchr.go" yypParser.yystack[yypParser.yytos+-6].minor.yy79 = yylhsminor.yy79 break case 69: /* position ::= LP position COMMA position RP */ //line 640 "pikchr.y" { yypParser.yystack[yypParser.yytos+-4].minor.yy79.x = yypParser.yystack[yypParser.yytos+-3].minor.yy79.x yypParser.yystack[yypParser.yytos+-4].minor.yy79.y = yypParser.yystack[yypParser.yytos+-1].minor.yy79.y } //line 2653 "pikchr.go" break case 70: /* position ::= LP position RP */ //line 641 "pikchr.y" { yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yypParser.yystack[yypParser.yytos+-1].minor.yy79 } //line 2658 "pikchr.go" break case 71: /* position ::= expr between position AND position */ //line 643 "pikchr.y" { yylhsminor.yy79 = pik_position_between(yypParser.yystack[yypParser.yytos+-4].minor.yy153, yypParser.yystack[yypParser.yytos+-2].minor.yy79, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2663 "pikchr.go" yypParser.yystack[yypParser.yytos+-4].minor.yy79 = yylhsminor.yy79 break case 72: /* position ::= expr LT position COMMA position GT */ //line 645 "pikchr.y" { yylhsminor.yy79 = pik_position_between(yypParser.yystack[yypParser.yytos+-5].minor.yy153, yypParser.yystack[yypParser.yytos+-3].minor.yy79, yypParser.yystack[yypParser.yytos+-1].minor.yy79) } //line 2669 "pikchr.go" yypParser.yystack[yypParser.yytos+-5].minor.yy79 = yylhsminor.yy79 break case 73: /* position ::= expr ABOVE position */ //line 646 "pikchr.y" { yylhsminor.yy79 = yypParser.yystack[yypParser.yytos+0].minor.yy79 yylhsminor.yy79.y += yypParser.yystack[yypParser.yytos+-2].minor.yy153 } //line 2675 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yylhsminor.yy79 break case 74: /* position ::= expr BELOW position */ //line 647 "pikchr.y" { yylhsminor.yy79 = yypParser.yystack[yypParser.yytos+0].minor.yy79 yylhsminor.yy79.y -= yypParser.yystack[yypParser.yytos+-2].minor.yy153 } //line 2681 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yylhsminor.yy79 break case 75: /* position ::= expr LEFT OF position */ //line 648 "pikchr.y" { yylhsminor.yy79 = yypParser.yystack[yypParser.yytos+0].minor.yy79 yylhsminor.yy79.x -= yypParser.yystack[yypParser.yytos+-3].minor.yy153 } //line 2687 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy79 = yylhsminor.yy79 break case 76: /* position ::= expr RIGHT OF position */ //line 649 "pikchr.y" { yylhsminor.yy79 = yypParser.yystack[yypParser.yytos+0].minor.yy79 yylhsminor.yy79.x += yypParser.yystack[yypParser.yytos+-3].minor.yy153 } //line 2693 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy79 = yylhsminor.yy79 break case 77: /* position ::= expr ON HEADING EDGEPT OF position */ //line 651 "pikchr.y" { yylhsminor.yy79 = pik_position_at_hdg(yypParser.yystack[yypParser.yytos+-5].minor.yy153, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2699 "pikchr.go" yypParser.yystack[yypParser.yytos+-5].minor.yy79 = yylhsminor.yy79 break case 78: /* position ::= expr HEADING EDGEPT OF position */ //line 653 "pikchr.y" { yylhsminor.yy79 = pik_position_at_hdg(yypParser.yystack[yypParser.yytos+-4].minor.yy153, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2705 "pikchr.go" yypParser.yystack[yypParser.yytos+-4].minor.yy79 = yylhsminor.yy79 break case 79: /* position ::= expr EDGEPT OF position */ //line 655 "pikchr.y" { yylhsminor.yy79 = pik_position_at_hdg(yypParser.yystack[yypParser.yytos+-3].minor.yy153, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2711 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy79 = yylhsminor.yy79 break case 80: /* position ::= expr ON HEADING expr FROM position */ //line 657 "pikchr.y" { yylhsminor.yy79 = pik_position_at_angle(yypParser.yystack[yypParser.yytos+-5].minor.yy153, yypParser.yystack[yypParser.yytos+-2].minor.yy153, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2717 "pikchr.go" yypParser.yystack[yypParser.yytos+-5].minor.yy79 = yylhsminor.yy79 break case 81: /* position ::= expr HEADING expr FROM position */ //line 659 "pikchr.y" { yylhsminor.yy79 = pik_position_at_angle(yypParser.yystack[yypParser.yytos+-4].minor.yy153, yypParser.yystack[yypParser.yytos+-2].minor.yy153, yypParser.yystack[yypParser.yytos+0].minor.yy79) } //line 2723 "pikchr.go" yypParser.yystack[yypParser.yytos+-4].minor.yy79 = yylhsminor.yy79 break case 82: /* place ::= edge OF object */ //line 671 "pikchr.y" { yylhsminor.yy79 = p.pik_place_of_elem(yypParser.yystack[yypParser.yytos+0].minor.yy104, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2729 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yylhsminor.yy79 break case 83: /* place2 ::= object */ //line 672 "pikchr.y" { yylhsminor.yy79 = p.pik_place_of_elem(yypParser.yystack[yypParser.yytos+0].minor.yy104, nil) } //line 2735 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy79 = yylhsminor.yy79 break case 84: /* place2 ::= object DOT_E edge */ //line 673 "pikchr.y" { yylhsminor.yy79 = p.pik_place_of_elem(yypParser.yystack[yypParser.yytos+-2].minor.yy104, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2741 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy79 = yylhsminor.yy79 break case 85: /* place2 ::= NTH VERTEX OF object */ //line 674 "pikchr.y" { yylhsminor.yy79 = p.pik_nth_vertex(&yypParser.yystack[yypParser.yytos+-3].minor.yy0, &yypParser.yystack[yypParser.yytos+-2].minor.yy0, yypParser.yystack[yypParser.yytos+0].minor.yy104) } //line 2747 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy79 = yylhsminor.yy79 break case 86: /* object ::= nth */ //line 686 "pikchr.y" { yylhsminor.yy104 = p.pik_find_nth(nil, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2753 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy104 = yylhsminor.yy104 break case 87: /* object ::= nth OF|IN object */ //line 687 "pikchr.y" { yylhsminor.yy104 = p.pik_find_nth(yypParser.yystack[yypParser.yytos+0].minor.yy104, &yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2759 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy104 = yylhsminor.yy104 break case 88: /* objectname ::= THIS */ //line 689 "pikchr.y" { yypParser.yystack[yypParser.yytos+0].minor.yy104 = p.cur } //line 2765 "pikchr.go" break case 89: /* objectname ::= PLACENAME */ //line 690 "pikchr.y" { yylhsminor.yy104 = p.pik_find_byname(nil, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2770 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy104 = yylhsminor.yy104 break case 90: /* objectname ::= objectname DOT_U PLACENAME */ //line 692 "pikchr.y" { yylhsminor.yy104 = p.pik_find_byname(yypParser.yystack[yypParser.yytos+-2].minor.yy104, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2776 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy104 = yylhsminor.yy104 break case 91: /* nth ::= NTH CLASSNAME */ //line 694 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+0].minor.yy0 yylhsminor.yy0.eCode = p.pik_nth_value(&yypParser.yystack[yypParser.yytos+-1].minor.yy0) } //line 2782 "pikchr.go" yypParser.yystack[yypParser.yytos+-1].minor.yy0 = yylhsminor.yy0 break case 92: /* nth ::= NTH LAST CLASSNAME */ //line 695 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+0].minor.yy0 yylhsminor.yy0.eCode = -p.pik_nth_value(&yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2788 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy0 = yylhsminor.yy0 break case 93: /* nth ::= LAST CLASSNAME */ //line 696 "pikchr.y" { yypParser.yystack[yypParser.yytos+-1].minor.yy0 = yypParser.yystack[yypParser.yytos+0].minor.yy0 yypParser.yystack[yypParser.yytos+-1].minor.yy0.eCode = -1 } //line 2794 "pikchr.go" break case 94: /* nth ::= LAST */ //line 697 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+0].minor.yy0 yylhsminor.yy0.eCode = -1 } //line 2799 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy0 = yylhsminor.yy0 break case 95: /* nth ::= NTH LB RB */ //line 698 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+-1].minor.yy0 yylhsminor.yy0.eCode = p.pik_nth_value(&yypParser.yystack[yypParser.yytos+-2].minor.yy0) } //line 2805 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy0 = yylhsminor.yy0 break case 96: /* nth ::= NTH LAST LB RB */ //line 699 "pikchr.y" { yylhsminor.yy0 = yypParser.yystack[yypParser.yytos+-1].minor.yy0 yylhsminor.yy0.eCode = -p.pik_nth_value(&yypParser.yystack[yypParser.yytos+-3].minor.yy0) } //line 2811 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy0 = yylhsminor.yy0 break case 97: /* nth ::= LAST LB RB */ //line 700 "pikchr.y" { yypParser.yystack[yypParser.yytos+-2].minor.yy0 = yypParser.yystack[yypParser.yytos+-1].minor.yy0 yypParser.yystack[yypParser.yytos+-2].minor.yy0.eCode = -1 } //line 2817 "pikchr.go" break case 98: /* expr ::= expr PLUS expr */ //line 702 "pikchr.y" { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy153 + yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2822 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 99: /* expr ::= expr MINUS expr */ //line 703 "pikchr.y" { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy153 - yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2828 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 100: /* expr ::= expr STAR expr */ //line 704 "pikchr.y" { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy153 * yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2834 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 101: /* expr ::= expr SLASH expr */ //line 705 "pikchr.y" { if yypParser.yystack[yypParser.yytos+0].minor.yy153 == 0.0 { p.pik_error(&yypParser.yystack[yypParser.yytos+-1].minor.yy0, "division by zero") yylhsminor.yy153 = 0.0 } else { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy153 / yypParser.yystack[yypParser.yytos+0].minor.yy153 } } //line 2842 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 102: /* expr ::= MINUS expr */ //line 708 "pikchr.y" { yypParser.yystack[yypParser.yytos+-1].minor.yy153 = -yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2848 "pikchr.go" break case 103: /* expr ::= PLUS expr */ //line 709 "pikchr.y" { yypParser.yystack[yypParser.yytos+-1].minor.yy153 = yypParser.yystack[yypParser.yytos+0].minor.yy153 } //line 2853 "pikchr.go" break case 104: /* expr ::= LP expr RP */ //line 710 "pikchr.y" { yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yypParser.yystack[yypParser.yytos+-1].minor.yy153 } //line 2858 "pikchr.go" break case 105: /* expr ::= LP FILL|COLOR|THICKNESS RP */ //line 711 "pikchr.y" { yypParser.yystack[yypParser.yytos+-2].minor.yy153 = p.pik_get_var(&yypParser.yystack[yypParser.yytos+-1].minor.yy0) } //line 2863 "pikchr.go" break case 106: /* expr ::= NUMBER */ //line 712 "pikchr.y" { yylhsminor.yy153 = pik_atof(&yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2868 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy153 = yylhsminor.yy153 break case 107: /* expr ::= ID */ //line 713 "pikchr.y" { yylhsminor.yy153 = p.pik_get_var(&yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2874 "pikchr.go" yypParser.yystack[yypParser.yytos+0].minor.yy153 = yylhsminor.yy153 break case 108: /* expr ::= FUNC1 LP expr RP */ //line 714 "pikchr.y" { yylhsminor.yy153 = p.pik_func(&yypParser.yystack[yypParser.yytos+-3].minor.yy0, yypParser.yystack[yypParser.yytos+-1].minor.yy153, 0.0) } //line 2880 "pikchr.go" yypParser.yystack[yypParser.yytos+-3].minor.yy153 = yylhsminor.yy153 break case 109: /* expr ::= FUNC2 LP expr COMMA expr RP */ //line 715 "pikchr.y" { yylhsminor.yy153 = p.pik_func(&yypParser.yystack[yypParser.yytos+-5].minor.yy0, yypParser.yystack[yypParser.yytos+-3].minor.yy153, yypParser.yystack[yypParser.yytos+-1].minor.yy153) } //line 2886 "pikchr.go" yypParser.yystack[yypParser.yytos+-5].minor.yy153 = yylhsminor.yy153 break case 110: /* expr ::= DIST LP position COMMA position RP */ //line 716 "pikchr.y" { yypParser.yystack[yypParser.yytos+-5].minor.yy153 = pik_dist(&yypParser.yystack[yypParser.yytos+-3].minor.yy79, &yypParser.yystack[yypParser.yytos+-1].minor.yy79) } //line 2892 "pikchr.go" break case 111: /* expr ::= place2 DOT_XY X */ //line 717 "pikchr.y" { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy79.x } //line 2897 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 112: /* expr ::= place2 DOT_XY Y */ //line 718 "pikchr.y" { yylhsminor.yy153 = yypParser.yystack[yypParser.yytos+-2].minor.yy79.y } //line 2903 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break case 113: /* expr ::= object DOT_L numproperty */ fallthrough case 114: /* expr ::= object DOT_L dashproperty */ yytestcase(yyruleno == 114) fallthrough case 115: /* expr ::= object DOT_L colorproperty */ yytestcase(yyruleno == 115) //line 719 "pikchr.y" { yylhsminor.yy153 = pik_property_of(yypParser.yystack[yypParser.yytos+-2].minor.yy104, &yypParser.yystack[yypParser.yytos+0].minor.yy0) } //line 2913 "pikchr.go" yypParser.yystack[yypParser.yytos+-2].minor.yy153 = yylhsminor.yy153 break default: /* (116) lvalue ::= ID */ yytestcase(yyruleno == 116) /* (117) lvalue ::= FILL */ yytestcase(yyruleno == 117) /* (118) lvalue ::= COLOR */ yytestcase(yyruleno == 118) /* (119) lvalue ::= THICKNESS */ yytestcase(yyruleno == 119) |
︙ | ︙ | |||
3252 3253 3254 3255 3256 3257 3258 | p := yypParser.p _ = p TOKEN := yyminor _ = TOKEN /************ Begin %syntax_error code ****************************************/ | | | | 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 | p := yypParser.p _ = p TOKEN := yyminor _ = TOKEN /************ Begin %syntax_error code ****************************************/ //line 481 "pikchr.y" if TOKEN.z != nil && TOKEN.z[0] != 0 { p.pik_error(&TOKEN, "syntax error") } else { p.pik_error(nil, "syntax error") } //line 3026 "pikchr.go" /************ End %syntax_error code ******************************************/ /* Suppress warning about unused %extra_argument variable */ yypParser.p = p } |
︙ | ︙ | |||
3546 3547 3548 3549 3550 3551 3552 | // to check invariants. func assert(condition bool, message string) { if !condition { panic(message) } } | | | 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 | // to check invariants. func assert(condition bool, message string) { if !condition { panic(message) } } //line 724 "pikchr.y" /* Chart of the 148 official CSS color names with their ** corresponding RGB values thru Color Module Level 4: ** https://developer.mozilla.org/en-US/docs/Web/CSS/color_value ** ** Two new names "None" and "Off" are added with a value ** of -1. |
︙ | ︙ | |||
4013 4014 4015 4016 4017 4018 4019 | pObj.h = pObj.w pObj.rad = 0.5 * pObj.w } func circleNumProp(p *Pik, pObj *PObj, pId *PToken) { /* For a circle, the width must equal the height and both must ** be twice the radius. Enforce those constraints. */ switch pId.eType { | | | 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 | pObj.h = pObj.w pObj.rad = 0.5 * pObj.w } func circleNumProp(p *Pik, pObj *PObj, pId *PToken) { /* For a circle, the width must equal the height and both must ** be twice the radius. Enforce those constraints. */ switch pId.eType { case T_RADIUS: pObj.w = 2.0 * pObj.rad pObj.h = 2.0 * pObj.rad case T_WIDTH: pObj.h = pObj.w pObj.rad = 0.5 * pObj.w case T_HEIGHT: pObj.w = pObj.h |
︙ | ︙ | |||
4829 4830 4831 4832 4833 4834 4835 | /* ** Append raw text to zOut */ func (p *Pik) pik_append(zText string) { p.zOut.WriteString(zText) } | | < | < | | | | | | < < < < < < < | < | < < | | < < < | | | 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 | /* ** Append raw text to zOut */ func (p *Pik) pik_append(zText string) { p.zOut.WriteString(zText) } var html_re_with_space = regexp.MustCompile(`[<>& ]`) /* ** Append text to zOut with HTML characters escaped. ** ** * The space character is changed into non-breaking space (U+00a0) ** if mFlags has the 0x01 bit set. This is needed when outputting ** text to preserve leading and trailing whitespace. Turns out we ** cannot use as that is an HTML-ism and is not valid in XML. ** ** * The "&" character is changed into "&" if mFlags has the ** 0x02 bit set. This is needed when generating error message text. ** ** * Except for the above, only "<" and ">" are escaped. */ func (p *Pik) pik_append_text(zText string, mFlags int) { bQSpace := mFlags&1 > 0 bQAmp := mFlags&2 > 0 text := html_re_with_space.ReplaceAllStringFunc(zText, func(s string) string { switch { case s == "<": return "<" case s == ">": return ">" case s == "&" && bQAmp: return "&" case s == " " && bQSpace: return "\302\240" default: return s } }) p.pik_append(text) } /* ** Append error message text. This is either a raw append, or an append ** with HTML escapes, depending on whether the PIKCHR_PLAINTEXT_ERRORS flag ** is set. */ |
︙ | ︙ | |||
6194 6195 6196 6197 6198 6199 6200 | } p.pik_reset_samepath() n := 0 for n < 1 { n = p.pik_next_rpath(pErr) } if pHeading != nil { | | > > > | 6174 6175 6176 6177 6178 6179 6180 6181 6182 6183 6184 6185 6186 6187 6188 6189 6190 6191 | } p.pik_reset_samepath() n := 0 for n < 1 { n = p.pik_next_rpath(pErr) } if pHeading != nil { if rHdg < 0.0 || rHdg > 360.0 { p.pik_error(pHeading, "headings should be between 0 and 360") return } } else if pEdgept.eEdge == CP_C { p.pik_error(pEdgept, "syntax error") return } else { rHdg = pik_hdg_angle[pEdgept.eEdge] } if rHdg <= 45.0 { |
︙ | ︙ | |||
7122 7123 7124 7125 7126 7127 7128 | /* Compute one of the built-in functions */ func (p *Pik) pik_func(pFunc *PToken, x PNum, y PNum) PNum { var v PNum switch pFunc.eCode { case FN_ABS: | < | | | | 7105 7106 7107 7108 7109 7110 7111 7112 7113 7114 7115 7116 7117 7118 7119 7120 7121 | /* Compute one of the built-in functions */ func (p *Pik) pik_func(pFunc *PToken, x PNum, y PNum) PNum { var v PNum switch pFunc.eCode { case FN_ABS: v = x if v < 0 { v = -v } case FN_COS: v = math.Cos(x) case FN_INT: v = math.Round(x) case FN_SIN: v = math.Sin(x) |
︙ | ︙ | |||
8342 8343 8344 8345 8346 8347 8348 | n = 0 } fmt.Printf("******** Token %s (%d): \"%s\" **************\n", yyTokenName[token.eType], token.eType, string(token.z[:n])) } // #endif token.n = sz | < < < < < | 8324 8325 8326 8327 8328 8329 8330 8331 8332 8333 8334 8335 8336 8337 | n = 0 } fmt.Printf("******** Token %s (%d): \"%s\" **************\n", yyTokenName[token.eType], token.eType, string(token.z[:n])) } // #endif token.n = sz pParser.pik_parser(token.eType, token) } } /* ** Parse the PIKCHR script contained in zText[]. Return a rendering. Or ** if an error is encountered, return the error text. The error message |
︙ | ︙ | |||
8481 8482 8483 8484 8485 8486 8487 | if b[i] != bb { return false } } return true } | | | 8458 8459 8460 8461 8462 8463 8464 8465 | if b[i] != bb { return false } } return true } //line 8230 "pikchr.go" |
Changes to parser/pikchr/internal/pikchr.y.
︙ | ︙ | |||
127 128 129 130 131 132 133 | "math" "os" "regexp" "strconv" "strings" ) | < < < < | 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | "math" "os" "regexp" "strconv" "strings" ) // Numeric value type PNum = float64 // Compass points const ( CP_N uint8 = iota + 1 CP_NE |
︙ | ︙ | |||
363 364 365 366 367 368 369 | } /* Each call to the pikchr() subroutine uses an instance of the following ** object to pass around context to all of its subroutines. */ type Pik struct { nErr int /* Number of errors seen */ | < | 359 360 361 362 363 364 365 366 367 368 369 370 371 372 | } /* Each call to the pikchr() subroutine uses an instance of the following ** object to pass around context to all of its subroutines. */ type Pik struct { nErr int /* Number of errors seen */ sIn PToken /* Input Pikchr-language text */ zOut bytes.Buffer /* Result accumulates here */ nOut uint /* Bytes written to zOut[] so far */ nOutAlloc uint /* Space allocated to zOut[] */ eDir uint8 /* Current direction */ mFlags uint /* Flags passed to pikchr() */ cur *PObj /* Object under construction */ |
︙ | ︙ | |||
1189 1190 1191 1192 1193 1194 1195 | pObj.h = pObj.w pObj.rad = 0.5 * pObj.w } func circleNumProp(p *Pik, pObj *PObj, pId *PToken) { /* For a circle, the width must equal the height and both must ** be twice the radius. Enforce those constraints. */ switch pId.eType { | | | 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 | pObj.h = pObj.w pObj.rad = 0.5 * pObj.w } func circleNumProp(p *Pik, pObj *PObj, pId *PToken) { /* For a circle, the width must equal the height and both must ** be twice the radius. Enforce those constraints. */ switch pId.eType { case T_RADIUS: pObj.w = 2.0 * pObj.rad pObj.h = 2.0 * pObj.rad case T_WIDTH: pObj.h = pObj.w pObj.rad = 0.5 * pObj.w case T_HEIGHT: pObj.w = pObj.h |
︙ | ︙ | |||
2005 2006 2007 2008 2009 2010 2011 | /* ** Append raw text to zOut */ func (p *Pik) pik_append(zText string) { p.zOut.WriteString(zText) } | | < | < | | | | | | < < < < < < < | < | < < | | < < < | | | 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 | /* ** Append raw text to zOut */ func (p *Pik) pik_append(zText string) { p.zOut.WriteString(zText) } var html_re_with_space = regexp.MustCompile(`[<>& ]`) /* ** Append text to zOut with HTML characters escaped. ** ** * The space character is changed into non-breaking space (U+00a0) ** if mFlags has the 0x01 bit set. This is needed when outputting ** text to preserve leading and trailing whitespace. Turns out we ** cannot use as that is an HTML-ism and is not valid in XML. ** ** * The "&" character is changed into "&" if mFlags has the ** 0x02 bit set. This is needed when generating error message text. ** ** * Except for the above, only "<" and ">" are escaped. */ func (p *Pik) pik_append_text(zText string, mFlags int) { bQSpace := mFlags&1 > 0 bQAmp := mFlags&2 > 0 text := html_re_with_space.ReplaceAllStringFunc(zText, func(s string) string { switch { case s == "<": return "<" case s == ">": return ">" case s == "&" && bQAmp: return "&" case s == " " && bQSpace: return "\302\240" default: return s } }) p.pik_append(text) } /* ** Append error message text. This is either a raw append, or an append ** with HTML escapes, depending on whether the PIKCHR_PLAINTEXT_ERRORS flag ** is set. */ |
︙ | ︙ | |||
3370 3371 3372 3373 3374 3375 3376 | } p.pik_reset_samepath() n := 0 for n < 1 { n = p.pik_next_rpath(pErr) } if pHeading != nil { | | > > > | 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 | } p.pik_reset_samepath() n := 0 for n < 1 { n = p.pik_next_rpath(pErr) } if pHeading != nil { if rHdg < 0.0 || rHdg > 360.0 { p.pik_error(pHeading, "headings should be between 0 and 360") return } } else if pEdgept.eEdge == CP_C { p.pik_error(pEdgept, "syntax error") return } else { rHdg = pik_hdg_angle[pEdgept.eEdge] } if rHdg <= 45.0 { |
︙ | ︙ | |||
4298 4299 4300 4301 4302 4303 4304 | /* Compute one of the built-in functions */ func (p *Pik) pik_func(pFunc *PToken, x PNum, y PNum) PNum { var v PNum switch pFunc.eCode { case FN_ABS: | < | | | | 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 | /* Compute one of the built-in functions */ func (p *Pik) pik_func(pFunc *PToken, x PNum, y PNum) PNum { var v PNum switch pFunc.eCode { case FN_ABS: v = x if v < 0 { v = -v } case FN_COS: v = math.Cos(x) case FN_INT: v = math.Round(x) case FN_SIN: v = math.Sin(x) |
︙ | ︙ | |||
5518 5519 5520 5521 5522 5523 5524 | n = 0 } fmt.Printf("******** Token %s (%d): \"%s\" **************\n", yyTokenName[token.eType], token.eType, string(token.z[:n])) } // #endif token.n = sz | < < < < < | 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 | n = 0 } fmt.Printf("******** Token %s (%d): \"%s\" **************\n", yyTokenName[token.eType], token.eType, string(token.z[:n])) } // #endif token.n = sz pParser.pik_parser(token.eType, token) } } /* ** Parse the PIKCHR script contained in zText[]. Return a rendering. Or ** if an error is encountered, return the error text. The error message |
︙ | ︙ |
Changes to parser/zettelmark/inline.go.
︙ | ︙ | |||
63 64 65 66 67 68 69 70 71 72 73 74 75 76 | in, success = cp.parseMark() } case '{': inp.Next() if inp.Ch == '{' { in, success = cp.parseEmbed() } case '%': in, success = cp.parseComment() case '_', '*', '>', '~', '^', ',', '"', ':': in, success = cp.parseFormat() case '@', '\'', '`', '=', runeModGrave: in, success = cp.parseLiteral() case '$': | > > | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | in, success = cp.parseMark() } case '{': inp.Next() if inp.Ch == '{' { in, success = cp.parseEmbed() } case '#': return cp.parseTag() case '%': in, success = cp.parseComment() case '_', '*', '>', '~', '^', ',', '"', ':': in, success = cp.parseFormat() case '@', '\'', '`', '=', runeModGrave: in, success = cp.parseLiteral() case '$': |
︙ | ︙ | |||
98 99 100 101 102 103 104 | return cp.parseBackslashRest() } for { inp.Next() switch inp.Ch { // The following case must contain all runes that occur in parseInline! // Plus the closing brackets ] and } and ) and the middle | | | | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | return cp.parseBackslashRest() } for { inp.Next() switch inp.Ch { // The following case must contain all runes that occur in parseInline! // Plus the closing brackets ] and } and ) and the middle | case input.EOS, '\n', '\r', ' ', '\t', '[', ']', '{', '}', '(', ')', '|', '#', '%', '_', '*', '>', '~', '^', ',', '"', ':', '\'', '@', '`', runeModGrave, '$', '=', '\\', '-', '&': return &ast.TextNode{Text: string(inp.Src[pos:inp.Pos])} } } } func (cp *zmkP) parseBackslash() ast.InlineNode { inp := cp.inp |
︙ | ︙ | |||
377 378 379 380 381 382 383 384 385 386 387 388 389 390 | } } else { inp.Next() } mn := &ast.MarkNode{Mark: string(mark), Inlines: ins} return mn, true } func (cp *zmkP) parseComment() (res *ast.LiteralNode, success bool) { inp := cp.inp inp.Next() if inp.Ch != '%' { return nil, false } | > > > > > > > > > > > > > > | 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 | } } else { inp.Next() } mn := &ast.MarkNode{Mark: string(mark), Inlines: ins} return mn, true } func (cp *zmkP) parseTag() ast.InlineNode { inp := cp.inp posH := inp.Pos inp.Next() pos := inp.Pos for isNameRune(inp.Ch) { inp.Next() } if pos == inp.Pos || inp.Ch == '#' { return &ast.TextNode{Text: string(inp.Src[posH:inp.Pos])} } return &ast.TagNode{Tag: string(inp.Src[pos:inp.Pos])} } func (cp *zmkP) parseComment() (res *ast.LiteralNode, success bool) { inp := cp.inp inp.Next() if inp.Ch != '%' { return nil, false } |
︙ | ︙ |
Changes to parser/zettelmark/post-processor.go.
︙ | ︙ | |||
277 278 279 280 281 282 283 284 285 286 287 288 289 290 | toPos++ } } for pos := toPos; pos < len(*bs); pos++ { (*bs)[pos] = nil // Allow excess nodes to be garbage collected. } *bs = (*bs)[:toPos:toPos] } // processItemSlice post-processes a slice of items. // It is one of the working horses for post-processing. func (pp *postProcessor) processItemSlice(ins ast.ItemSlice) ast.ItemSlice { if len(ins) == 0 { return nil | > | 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | toPos++ } } for pos := toPos; pos < len(*bs); pos++ { (*bs)[pos] = nil // Allow excess nodes to be garbage collected. } *bs = (*bs)[:toPos:toPos] } // processItemSlice post-processes a slice of items. // It is one of the working horses for post-processing. func (pp *postProcessor) processItemSlice(ins ast.ItemSlice) ast.ItemSlice { if len(ins) == 0 { return nil |
︙ | ︙ |
Changes to parser/zettelmark/zettelmark_test.go.
︙ | ︙ | |||
16 17 18 19 20 21 22 | "fmt" "strings" "testing" "zettelstore.de/c/api" "zettelstore.de/c/attrs" "zettelstore.de/z/ast" | < | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | "fmt" "strings" "testing" "zettelstore.de/c/api" "zettelstore.de/c/attrs" "zettelstore.de/z/ast" "zettelstore.de/z/input" "zettelstore.de/z/parser" ) type TestCase struct{ source, want string } type TestCases []TestCase |
︙ | ︙ | |||
42 43 44 45 46 47 48 | func checkTcs(t *testing.T, tcs TestCases) { t.Helper() for tcn, tc := range tcs { t.Run(fmt.Sprintf("TC=%02d,src=%q", tcn, tc.source), func(st *testing.T) { st.Helper() inp := input.NewInput([]byte(tc.source)) | | | 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | func checkTcs(t *testing.T, tcs TestCases) { t.Helper() for tcn, tc := range tcs { t.Run(fmt.Sprintf("TC=%02d,src=%q", tcn, tc.source), func(st *testing.T) { st.Helper() inp := input.NewInput([]byte(tc.source)) bns := parser.ParseBlocks(inp, nil, api.ValueSyntaxZmk) var tv TestVisitor ast.Walk(&tv, &bns) got := tv.String() if tc.want != got { st.Errorf("\nwant=%q\n got=%q", tc.want, got) } }) |
︙ | ︙ | |||
244 245 246 247 248 249 250 251 252 253 254 255 256 257 | {"{{b\\}|a}}", "(PARA (EMBED a b}))"}, {"{{\\}\\||a}}", "(PARA (EMBED a }|))"}, {"{{http://a}}", "(PARA (EMBED http://a))"}, {"{{http://a|http://a}}", "(PARA (EMBED http://a http://a))"}, {"{{{{a}}}}", "(PARA (EMBED %7B%7Ba) }})"}, }) } func TestMark(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"[!", "(PARA [!)"}, {"[!\n", "(PARA [!)"}, {"[!]", "(PARA (MARK #*))"}, | > > > > > > > > > > > > > | 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 | {"{{b\\}|a}}", "(PARA (EMBED a b}))"}, {"{{\\}\\||a}}", "(PARA (EMBED a }|))"}, {"{{http://a}}", "(PARA (EMBED http://a))"}, {"{{http://a|http://a}}", "(PARA (EMBED http://a http://a))"}, {"{{{{a}}}}", "(PARA (EMBED %7B%7Ba) }})"}, }) } func TestTag(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"#", "(PARA #)"}, {"##", "(PARA ##)"}, {"###", "(PARA ###)"}, {"#tag", "(PARA #tag#)"}, {"#tag,", "(PARA #tag# ,)"}, {"#t-g ", "(PARA #t-g#)"}, {"#t_g", "(PARA #t_g#)"}, }) } func TestMark(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"[!", "(PARA [!)"}, {"[!\n", "(PARA [!)"}, {"[!]", "(PARA (MARK #*))"}, |
︙ | ︙ | |||
397 398 399 400 401 402 403 | func TestEntity(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"&", "(PARA &)"}, {"&;", "(PARA &;)"}, {"&#;", "(PARA &#;)"}, | | | | < < < < | | 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 | func TestEntity(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"&", "(PARA &)"}, {"&;", "(PARA &;)"}, {"&#;", "(PARA &#;)"}, {"a;", "(PARA & #1a# ;)"}, {"&#x;", "(PARA & #x# ;)"}, {"�z;", "(PARA & #x0z# ;)"}, {"&1;", "(PARA &1;)"}, // Good cases {"<", "(PARA <)"}, {"0", "(PARA 0)"}, {"J", "(PARA J)"}, {"J", "(PARA J)"}, {"…", "(PARA \u2026)"}, {"E: &, ;
.", "(PARA E: SP &,\r;\n.)"}, }) } func TestVerbatimZettel(t *testing.T) { t.Parallel() checkTcs(t, TestCases{ {"@@@\n@@@", "(ZETTEL)"}, |
︙ | ︙ | |||
875 876 877 878 879 880 881 882 883 884 885 886 887 888 | tv.visitAttributes(n.Attrs) case *ast.BLOBNode: tv.buf.WriteString("(BLOB ") tv.buf.WriteString(n.Syntax) tv.buf.WriteString(")") case *ast.TextNode: tv.buf.WriteString(n.Text) case *ast.SpaceNode: if l := n.Count(); l == 1 { tv.buf.WriteString("SP") } else { fmt.Fprintf(&tv.buf, "SP%d", l) } case *ast.BreakNode: | > > > > | 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 | tv.visitAttributes(n.Attrs) case *ast.BLOBNode: tv.buf.WriteString("(BLOB ") tv.buf.WriteString(n.Syntax) tv.buf.WriteString(")") case *ast.TextNode: tv.buf.WriteString(n.Text) case *ast.TagNode: tv.buf.WriteByte('#') tv.buf.WriteString(n.Tag) tv.buf.WriteByte('#') case *ast.SpaceNode: if l := n.Count(); l == 1 { tv.buf.WriteString("SP") } else { fmt.Fprintf(&tv.buf, "SP%d", l) } case *ast.BreakNode: |
︙ | ︙ |
Changes to query/query.go.
︙ | ︙ | |||
10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // Package query provides a query for zettel. package query import ( "math/rand" "sort" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // Searcher is used to select zettel identifier based on search criteria. type Searcher interface { | > | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // Package query provides a query for zettel. package query import ( "math/rand" "sort" "strings" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" ) // Searcher is used to select zettel identifier based on search criteria. type Searcher interface { |
︙ | ︙ | |||
250 251 252 253 254 255 256 | // EnrichNeeded returns true, if the query references a metadata key that // is calculated via metadata enrichments. func (q *Query) EnrichNeeded() bool { if q == nil { return false } | < < < < > > > > > | 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 | // EnrichNeeded returns true, if the query references a metadata key that // is calculated via metadata enrichments. func (q *Query) EnrichNeeded() bool { if q == nil { return false } for _, term := range q.terms { for key := range term.keys { if meta.IsProperty(key) { return true } } for key := range term.mvals { if meta.IsProperty(key) { return true } } } for _, o := range q.order { if meta.IsProperty(o.key) { return true } } for _, a := range q.actions { if meta.IsProperty(strings.ToLower(a)) { return true } } return false } // RetrieveAndCompile queries the search index and returns a predicate // for its results and returns a matching predicate. func (q *Query) RetrieveAndCompile(searcher Searcher) Compiled { |
︙ | ︙ |
Changes to tests/markdown_test.go.
︙ | ︙ | |||
15 16 17 18 19 20 21 | "encoding/json" "fmt" "os" "testing" "zettelstore.de/c/api" "zettelstore.de/z/ast" | < | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | "encoding/json" "fmt" "os" "testing" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/encoder" _ "zettelstore.de/z/encoder/htmlenc" _ "zettelstore.de/z/encoder/sexprenc" _ "zettelstore.de/z/encoder/textenc" _ "zettelstore.de/z/encoder/zjsonenc" _ "zettelstore.de/z/encoder/zmkenc" "zettelstore.de/z/input" |
︙ | ︙ | |||
64 65 66 67 68 69 70 | } var testcases []markdownTestCase if err = json.Unmarshal(content, &testcases); err != nil { panic(err) } for _, tc := range testcases { | | | | | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | } var testcases []markdownTestCase if err = json.Unmarshal(content, &testcases); err != nil { panic(err) } for _, tc := range testcases { ast := createMDBlockSlice(tc.Markdown) testAllEncodings(t, tc, &ast) testZmkEncoding(t, tc, &ast) } } func createMDBlockSlice(markdown string) ast.BlockSlice { return parser.ParseBlocks(input.NewInput([]byte(markdown)), nil, "markdown") } func testAllEncodings(t *testing.T, tc markdownTestCase, ast *ast.BlockSlice) { var buf bytes.Buffer testID := tc.Example*100 + 1 for _, enc := range encodings { t.Run(fmt.Sprintf("Encode %v %v", enc, testID), func(st *testing.T) { |
︙ | ︙ | |||
95 96 97 98 99 100 101 | testID := tc.Example*100 + 1 t.Run(fmt.Sprintf("Encode zmk %14d", testID), func(st *testing.T) { buf.Reset() zmkEncoder.WriteBlocks(&buf, ast) // gotFirst := buf.String() testID = tc.Example*100 + 2 | | | | | 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 | testID := tc.Example*100 + 1 t.Run(fmt.Sprintf("Encode zmk %14d", testID), func(st *testing.T) { buf.Reset() zmkEncoder.WriteBlocks(&buf, ast) // gotFirst := buf.String() testID = tc.Example*100 + 2 secondAst := parser.ParseBlocks(input.NewInput(buf.Bytes()), nil, api.ValueSyntaxZmk) buf.Reset() zmkEncoder.WriteBlocks(&buf, &secondAst) gotSecond := buf.String() // if gotFirst != gotSecond { // st.Errorf("\nCMD: %q\n1st: %q\n2nd: %q", tc.Markdown, gotFirst, gotSecond) // } testID = tc.Example*100 + 3 thirdAst := parser.ParseBlocks(input.NewInput(buf.Bytes()), nil, api.ValueSyntaxZmk) buf.Reset() zmkEncoder.WriteBlocks(&buf, &thirdAst) gotThird := buf.String() if gotSecond != gotThird { st.Errorf("\n1st: %q\n2nd: %q", 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) var buf bytes.Buffer for i, tc := range testcases { ast := createMDBlockSlice(tc.md) buf.Reset() zmkEncoder.WriteBlocks(&buf, &ast) got := buf.String() if got != tc.exp { t.Errorf("%d: %q -> %q, but got %q", i, tc.md, tc.exp, got) } } } |
Changes to tests/regression_test.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | "path/filepath" "testing" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" | < | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | "path/filepath" "testing" "zettelstore.de/c/api" "zettelstore.de/z/ast" "zettelstore.de/z/box" "zettelstore.de/z/box/manager" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoder" "zettelstore.de/z/kernel" "zettelstore.de/z/parser" _ "zettelstore.de/z/box/dirbox" |
︙ | ︙ | |||
159 160 161 162 163 164 165 | type myConfig struct{} func (*myConfig) Get(context.Context, *meta.Meta, string) string { return "" } func (*myConfig) AddDefaultValues(_ context.Context, m *meta.Meta) *meta.Meta { return m } | < | | | | | | 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | type myConfig struct{} func (*myConfig) Get(context.Context, *meta.Meta, string) string { return "" } func (*myConfig) AddDefaultValues(_ context.Context, m *meta.Meta) *meta.Meta { return m } func (*myConfig) GetHomeZettel() id.Zid { return id.Invalid } func (*myConfig) GetListPageSize() int { return 0 } func (*myConfig) GetSiteName() string { return "" } func (*myConfig) GetYAMLHeader() bool { return false } func (*myConfig) GetZettelFileSyntax() []string { return nil } func (*myConfig) GetSimpleMode() bool { return false } func (*myConfig) GetExpertMode() bool { return false } func (*myConfig) GetVisibility(*meta.Meta) meta.Visibility { return meta.VisibilityPublic } func (*myConfig) GetMaxTransclusions() int { return 1024 } var testConfig = &myConfig{} |
︙ | ︙ |
Changes to usecase/create_zettel.go.
︙ | ︙ | |||
54 55 56 57 58 59 60 | m.Set(api.KeyReadOnly, copyReadonly(readonly)) } content := origZettel.Content content.TrimSpace() return domain.Zettel{Meta: m, Content: content} } | < < < < < < < < < < < < < | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | m.Set(api.KeyReadOnly, copyReadonly(readonly)) } content := origZettel.Content content.TrimSpace() return domain.Zettel{Meta: m, Content: content} } // PrepareFolge the zettel for further modification. func (*CreateZettel) PrepareFolge(origZettel domain.Zettel) domain.Zettel { origMeta := origZettel.Meta m := meta.New(id.Invalid) if title, found := origMeta.Get(api.KeyTitle); found { m.Set(api.KeyTitle, prependTitle(title, "Folge", "Folge of ")) } |
︙ | ︙ | |||
109 110 111 112 113 114 115 | if len(title) > 0 { return s1 + title } return s0 } func copyReadonly(string) string { | | | | 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | if len(title) > 0 { return s1 + title } return s0 } func copyReadonly(string) string { // Currently, "false" is a safe value. // // If the current user and its role is known, a mor elaborative calculation // could be done: set it to a value, so that the current user will be able // to modify it later. return api.ValueFalse } // Run executes the use case. func (uc *CreateZettel) Run(ctx context.Context, zettel domain.Zettel) (id.Zid, error) { |
︙ | ︙ |
Changes to usecase/lists.go.
︙ | ︙ | |||
98 99 100 101 102 103 104 | q := query.Parse(api.KeyRole + api.ExistOperator) // We look for all metadata with an existing role key metas, err := uc.port.SelectMeta(box.NoEnrichContext(ctx), q) if err != nil { return nil, err } return meta.CreateArrangement(metas, api.KeyRole), nil } | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | q := query.Parse(api.KeyRole + api.ExistOperator) // We look for all metadata with an existing role key metas, err := uc.port.SelectMeta(box.NoEnrichContext(ctx), q) if err != nil { return nil, err } return meta.CreateArrangement(metas, api.KeyRole), nil } // -------- List tags -------------------------------------------------------- // ListTagsPort is the interface used by this use case. type ListTagsPort interface { // SelectMeta returns all zettel metadata that match the selection criteria. SelectMeta(ctx context.Context, q *query.Query) ([]*meta.Meta, error) } // ListTags is the data for this use case. type ListTags struct { port ListTagsPort } // NewListTags creates a new use case. func NewListTags(port ListTagsPort) ListTags { return ListTags{port: port} } // Run executes the use case. func (uc ListTags) Run(ctx context.Context, minCount int) (meta.Arrangement, error) { q := query.Parse(api.KeyAllTags + api.ExistOperator) // We look for all metadata with a tag metas, err := uc.port.SelectMeta(ctx, q) if err != nil { return nil, err } result := meta.CreateArrangement(metas, api.KeyAllTags) if minCount > 1 { for t, ms := range result { if len(ms) < minCount { delete(result, t) } } } return result, nil } |
Changes to usecase/unlinked_refs.go.
︙ | ︙ | |||
108 109 110 111 112 113 114 | } } syntax := zettel.Meta.GetDefault(api.KeySyntax, "") if !parser.IsTextParser(syntax) { continue } | | | 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | } } syntax := zettel.Meta.GetDefault(api.KeySyntax, "") if !parser.IsTextParser(syntax) { continue } zn, err := parser.ParseZettel(ctx, zettel, syntax, nil), nil if err != nil { continue } evaluator.EvaluateZettel(ctx, uc.port, uc.rtConfig, zn) ast.Walk(&v, &zn.Ast) if v.found { result = append(result, cand) |
︙ | ︙ |
Changes to web/adapter/api/command.go.
︙ | ︙ | |||
21 22 23 24 25 26 27 | // MakePostCommandHandler creates a new HTTP handler to execute certain commands. func (a *API) MakePostCommandHandler( ucIsAuth *usecase.IsAuthenticated, ucRefresh *usecase.Refresh, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() | > > > > > | | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | // MakePostCommandHandler creates a new HTTP handler to execute certain commands. func (a *API) MakePostCommandHandler( ucIsAuth *usecase.IsAuthenticated, ucRefresh *usecase.Refresh, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := r.URL.Query() cmd := q.Get(api.QueryKeyCommand) if cmd == "" { cmd = q.Get("_cmd") } switch api.Command(cmd) { case api.CommandAuthenticated: handleIsAuthenticated(ctx, w, ucIsAuth) return case api.CommandRefresh: err := ucRefresh.Run(ctx) if err != nil { a.reportUsecaseError(w, err) |
︙ | ︙ |
Added web/adapter/api/get_lists.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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package api import ( "bytes" "net/http" "strconv" "zettelstore.de/c/api" "zettelstore.de/z/domain/meta" "zettelstore.de/z/usecase" ) // MakeListMapMetaHandler creates a new HTTP handler to retrieve mappings of // metadata values of a specific key to the list of zettel IDs, which contain // this value. func (a *API) MakeListMapMetaHandler(listRole usecase.ListRoles, listTags usecase.ListTags) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var buf bytes.Buffer query := r.URL.Query() minVal := query.Get(api.QueryKeyMin) if minVal == "" { minVal = query.Get("_min") } iMinCount, err := strconv.Atoi(minVal) if err != nil || iMinCount < 0 { iMinCount = 0 } key := query.Get(api.QueryKeyKey) if key == "" { key = query.Get("_key") } var ar meta.Arrangement switch key { case api.KeyRole: ar, err = listRole.Run(ctx) case api.KeyTags: ar, err = listTags.Run(ctx, iMinCount) default: a.log.Info().Str("key", key).Msg("illegal key for retrieving meta map") http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if err != nil { a.reportUsecaseError(w, err) return } mm := make(api.MapMeta, len(ar)) for tag, metaList := range ar { zidList := make([]api.ZettelID, 0, len(metaList)) for _, m := range metaList { zidList = append(zidList, api.ZettelID(m.Zid.String())) } mm[tag] = zidList } buf.Reset() err = encodeJSONData(&buf, api.MapListJSON{Map: mm}) if err != nil { a.log.Fatal().Err(err).Msg("Unable to store map list in buffer") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } err = writeBuffer(w, &buf, ctJSON) a.log.IfErr(err).Str("key", key).Msg("write meta map") } } |
Changes to web/adapter/api/get_unlinked_refs.go.
︙ | ︙ | |||
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | if err != nil { a.reportUsecaseError(w, err) return } que := r.URL.Query() phrase := que.Get(api.QueryKeyPhrase) if phrase == "" { if zmkTitle, found := zm.Get(api.KeyTitle); found { isTitle := evaluate.RunMetadata(ctx, zmkTitle) encdr := textenc.Create() var b strings.Builder _, err = encdr.WriteInlines(&b, &isTitle) if err == nil { phrase = b.String() } } } | > > > > | | 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 | if err != nil { a.reportUsecaseError(w, err) return } que := r.URL.Query() phrase := que.Get(api.QueryKeyPhrase) if phrase == "" { phrase = que.Get("_phrase") } if phrase == "" { if zmkTitle, found := zm.Get(api.KeyTitle); found { isTitle := evaluate.RunMetadata(ctx, zmkTitle) encdr := textenc.Create() var b strings.Builder _, err = encdr.WriteInlines(&b, &isTitle) if err == nil { phrase = b.String() } } } metaList, err := unlinkedRefs.Run( ctx, phrase, adapter.AddUnlinkedRefsToQuery(adapter.GetQuery(que), zm)) if err != nil { a.reportUsecaseError(w, err) return } result := api.ZidMetaRelatedList{ ID: api.ZettelID(zid.String()), |
︙ | ︙ |
Changes to web/adapter/api/get_zettel_context.go.
︙ | ︙ | |||
25 26 27 28 29 30 31 | return func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } q := r.URL.Query() | > > > > | > > > > > > | 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 | return func(w http.ResponseWriter, r *http.Request) { zid, err := id.Parse(r.URL.Path[1:]) if err != nil { http.NotFound(w, r) return } q := r.URL.Query() dirVal := q.Get(api.QueryKeyDir) if dirVal == "" { dirVal = q.Get("_dir") } dir := adapter.GetZCDirection(dirVal) depth, ok := adapter.GetInteger(q, api.QueryKeyDepth) if !ok { depth, ok = adapter.GetInteger(q, "_depth") } if !ok || depth < 0 { depth = 5 } limit, ok := adapter.GetInteger(q, api.QueryKeyLimit) if !ok { limit, ok = adapter.GetInteger(q, "_limit") } if !ok || limit < 0 { limit = 200 } ctx := r.Context() metaList, err := getContext.Run(ctx, zid, dir, depth, limit) if err != nil { a.reportUsecaseError(w, err) |
︙ | ︙ |
Changes to web/adapter/api/get_zettel_list.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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package api import ( "bytes" "fmt" "net/http" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeListPlainHandler creates a new HTTP handler for the use case "list some zettel". func (a *API) MakeListPlainHandler(listMeta usecase.ListMeta) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() metaList, err := listMeta.Run(ctx, adapter.GetQuery(r.URL.Query())) if err != nil { | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package api provides api handlers for web requests. package api import ( "bytes" "fmt" "net/http" "zettelstore.de/c/api" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeListMetaHandler creates a new HTTP handler for the use case "list some zettel". func (a *API) MakeListMetaHandler(listMeta usecase.ListMeta) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := adapter.GetQuery(r.URL.Query()) metaList, err := listMeta.Run(ctx, q) if err != nil { a.reportUsecaseError(w, err) return } result := make([]api.ZidMetaJSON, 0, len(metaList)) for _, m := range metaList { result = append(result, api.ZidMetaJSON{ ID: api.ZettelID(m.Zid.String()), Meta: m.Map(), Rights: a.getRights(ctx, m), }) } var buf bytes.Buffer err = encodeJSONData(&buf, api.ZettelListJSON{ Query: q.String(), Human: q.Human(), List: result, }) if err != nil { a.log.Fatal().Err(err).Msg("Unable to store meta list in buffer") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } err = writeBuffer(w, &buf, ctJSON) a.log.IfErr(err).Msg("Write JSON List") } } // MakeListPlainHandler creates a new HTTP handler for the use case "list some zettel". func (a *API) MakeListPlainHandler(listMeta usecase.ListMeta) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() metaList, err := listMeta.Run(ctx, adapter.GetQuery(r.URL.Query())) if err != nil { |
︙ | ︙ |
Changes to web/adapter/api/query.go.
︙ | ︙ | |||
8 9 10 11 12 13 14 | // under this license. //----------------------------------------------------------------------------- package api import ( "bytes" | < > > > > > | > > > | | | 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 | // under this license. //----------------------------------------------------------------------------- package api import ( "bytes" "io" "net/http" "strconv" "strings" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/meta" "zettelstore.de/z/query" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" ) // MakeQueryHandler creates a new HTTP handler to perform a query. func (a *API) MakeQueryHandler(listMeta usecase.ListMeta) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() q := adapter.GetQuery(r.URL.Query()) if q == nil { a.log.Sense().Msg("no parameter for query") http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } if !q.EnrichNeeded() { ctx = box.NoEnrichContext(ctx) } metaList, err := listMeta.Run(ctx, q) if err != nil { a.reportUsecaseError(w, err) return } var buf bytes.Buffer contentType, err := queryAction(&buf, q, metaList) if err != nil { a.reportUsecaseError(w, err) return } err = writeBuffer(w, &buf, contentType) a.log.IfErr(err).Msg("write action") } } func queryAction(w io.Writer, q *query.Query, ml []*meta.Meta) (string, error) { ap := actionPara{ w: w, q: q, ml: ml, min: -1, max: -1, } |
︙ | ︙ | |||
77 78 79 80 81 82 83 | key := strings.ToLower(act) switch meta.Type(key) { case meta.TypeWord, meta.TypeTagSet: return ap.createMapMeta(key) } } } | < | | 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | key := strings.ToLower(act) switch meta.Type(key) { case meta.TypeWord, meta.TypeTagSet: return ap.createMapMeta(key) } } } return "", nil } type actionPara struct { w io.Writer q *query.Query ml []*meta.Meta min int |
︙ | ︙ | |||
113 114 115 116 117 118 119 | } mm[tag] = zidList } err := encodeJSONData(ap.w, api.MapListJSON{Map: mm}) return ctJSON, err } | < < < < < < < < < < < < < < < < < < | 119 120 121 122 123 124 125 | } mm[tag] = zidList } err := encodeJSONData(ap.w, api.MapListJSON{Map: mm}) return ctJSON, err } |
Changes to web/adapter/api/request.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 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- package api import ( "io" "net/http" "net/url" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" ) // getEncoding returns the data encoding selected by the caller. func getEncoding(r *http.Request, q url.Values, defEncoding api.EncodingEnum) (api.EncodingEnum, string) { encoding := q.Get(api.QueryKeyEncoding) if encoding != "" { return api.Encoder(encoding), encoding } if enc, ok := getOneEncoding(r, api.HeaderAccept); ok { return api.Encoder(enc), enc } if enc, ok := getOneEncoding(r, api.HeaderContentType); ok { | > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | //----------------------------------------------------------------------------- // Copyright (c) 2020-2022 Detlef Stern // // This file is part of Zettelstore. // // Zettelstore is licensed under the latest version of the EUPL (European Union // Public License). Please see file LICENSE.txt for your rights and obligations // under this license. //----------------------------------------------------------------------------- // Package api provides api handlers for web requests. package api import ( "io" "net/http" "net/url" "zettelstore.de/c/api" "zettelstore.de/z/domain" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/input" ) // getEncoding returns the data encoding selected by the caller. func getEncoding(r *http.Request, q url.Values, defEncoding api.EncodingEnum) (api.EncodingEnum, string) { encoding := q.Get(api.QueryKeyEncoding) if encoding == "" { encoding = q.Get("_enc") } if encoding != "" { return api.Encoder(encoding), encoding } if enc, ok := getOneEncoding(r, api.HeaderAccept); ok { return api.Encoder(enc), enc } if enc, ok := getOneEncoding(r, api.HeaderContentType); ok { |
︙ | ︙ | |||
73 74 75 76 77 78 79 80 81 82 83 84 85 86 | api.PartContent: partContent, api.PartZettel: partZettel, } func getPart(q url.Values, defPart partType) partType { if part, ok := partMap[q.Get(api.QueryKeyPart)]; ok { return part } return defPart } func (p partType) String() string { switch p { case partMeta: | > > > | 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | api.PartContent: partContent, api.PartZettel: partZettel, } func getPart(q url.Values, defPart partType) partType { if part, ok := partMap[q.Get(api.QueryKeyPart)]; ok { return part } if part, ok := partMap[q.Get("_part")]; ok { return part } return defPart } func (p partType) String() string { switch p { case partMeta: |
︙ | ︙ |
Changes to web/adapter/request.go.
︙ | ︙ | |||
51 52 53 54 55 56 57 58 59 60 61 62 63 64 | } // GetQuery retrieves the specified options from a query. func GetQuery(vals url.Values) *query.Query { if exprs, found := vals[api.QueryKeyQuery]; found { return query.Parse(strings.Join(exprs, " ")) } return nil } // GetZCDirection returns a direction value for a given string. func GetZCDirection(s string) usecase.ZettelContextDirection { switch s { case api.DirBackward: | > > > | 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | } // GetQuery retrieves the specified options from a query. func GetQuery(vals url.Values) *query.Query { if exprs, found := vals[api.QueryKeyQuery]; found { return query.Parse(strings.Join(exprs, " ")) } if exprs, found := vals["_s"]; found { return query.Parse(strings.Join(exprs, " ")) } return nil } // GetZCDirection returns a direction value for a given string. func GetZCDirection(s string) usecase.ZettelContextDirection { switch s { case api.DirBackward: |
︙ | ︙ |
Changes to web/adapter/webui/const.go.
︙ | ︙ | |||
12 13 14 15 16 17 18 | // WebUI related constants. const queryKeyAction = "action" // Values for queryKeyAction const ( | | | | < < < < | 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 | // WebUI related constants. const queryKeyAction = "action" // Values for queryKeyAction const ( valueActionCopy = "copy" valueActionFolge = "folge" valueActionNew = "new" ) // Enumeration for queryKeyAction type createAction uint8 const ( actionCopy createAction = iota actionFolge actionNew ) func getCreateAction(s string) createAction { switch s { case valueActionCopy: return actionCopy case valueActionFolge: return actionFolge case valueActionNew: return actionNew default: return actionCopy } } |
Changes to web/adapter/webui/create_zettel.go.
︙ | ︙ | |||
45 46 47 48 49 50 51 | return } roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) switch op { case actionCopy: wui.renderZettelForm(ctx, w, createZettel.PrepareCopy(origZettel), "Copy Zettel", "Copy Zettel", roleData, syntaxData) | < < | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | return } roleData, syntaxData := retrieveDataLists(ctx, ucListRoles, ucListSyntax) switch op { case actionCopy: wui.renderZettelForm(ctx, w, createZettel.PrepareCopy(origZettel), "Copy Zettel", "Copy Zettel", roleData, syntaxData) case actionFolge: wui.renderZettelForm(ctx, w, createZettel.PrepareFolge(origZettel), "Folge Zettel", "Folgezettel", roleData, syntaxData) case actionNew: m := origZettel.Meta title := parser.ParseMetadata(m.GetTitle()) textTitle, err2 := encodeInlinesText(&title, wui.gentext) if err2 != nil { |
︙ | ︙ |
Changes to web/adapter/webui/delete_zettel.go.
︙ | ︙ | |||
42 43 44 45 46 47 48 | if err != nil { wui.reportError(ctx, w, err) return } m := ms[0] var shadowedBox string | | > | > | 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 | if err != nil { wui.reportError(ctx, w, err) return } m := ms[0] var shadowedBox string var incomingLinks []simpleLink if len(ms) > 1 { shadowedBox = ms[1].GetDefault(api.KeyBoxNumber, "???") } else { getTextTitle := wui.makeGetTextTitle(createGetMetadataFunc(ctx, getMeta), createEvalMetadataFunc(ctx, evaluate)) incomingLinks = wui.encodeIncoming(m, getTextTitle) } uselessFiles := retrieveUselessFiles(m) user := server.GetUser(ctx) var base baseData wui.makeBaseData(ctx, wui.rtConfig.Get(ctx, m, api.KeyLang), "Delete Zettel "+m.Zid.String(), "", user, &base) wui.renderTemplate(ctx, w, id.DeleteTemplateZid, &base, struct { Zid string MetaPairs []meta.Pair HasShadows bool ShadowedBox string HasIncoming bool Incoming []simpleLink HasUselessFiles bool UselessFiles []string }{ Zid: zid.String(), MetaPairs: m.ComputedPairs(), HasShadows: shadowedBox != "", ShadowedBox: shadowedBox, HasIncoming: len(incomingLinks) > 0, Incoming: incomingLinks, HasUselessFiles: len(uselessFiles) > 0, UselessFiles: uselessFiles, }) } } |
︙ | ︙ | |||
99 100 101 102 103 104 105 | wui.reportError(ctx, w, err) return } wui.redirectFound(w, r, wui.NewURLBuilder('/')) } } | | | | 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 | wui.reportError(ctx, w, err) return } wui.redirectFound(w, r, wui.NewURLBuilder('/')) } } func (wui *WebUI) encodeIncoming(m *meta.Meta, getTextTitle getTextTitleFunc) []simpleLink { zidMap := make(strfun.Set) addListValues(zidMap, m, api.KeyBackward) for _, kd := range meta.GetSortedKeyDescriptions() { inverseKey := kd.Inverse if inverseKey == "" { continue } ikd := meta.GetDescription(inverseKey) switch ikd.Type { case meta.TypeID: if val, ok := m.Get(inverseKey); ok { zidMap.Set(val) } case meta.TypeIDSet: addListValues(zidMap, m, inverseKey) } } return wui.encodeZidLinks(maps.Keys(zidMap), getTextTitle) } func addListValues(zidMap strfun.Set, m *meta.Meta, key string) { if values, ok := m.GetList(key); ok { for _, val := range values { zidMap.Set(val) } } } |
Changes to web/adapter/webui/get_info.go.
︙ | ︙ | |||
119 120 121 122 123 124 125 | wui.makeBaseData(ctx, wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang), textTitle, "", user, &base) wui.renderTemplate(ctx, w, id.InfoTemplateZid, &base, struct { Zid string WebURL string ContextURL string CanWrite bool EditURL string | | | < < | | > | | | | < < | > | | 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 | wui.makeBaseData(ctx, wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang), textTitle, "", user, &base) wui.renderTemplate(ctx, w, id.InfoTemplateZid, &base, struct { Zid string WebURL string ContextURL string CanWrite bool EditURL string CanFolge bool FolgeURL string CanCopy bool CopyURL string CanRename bool RenameURL string CanDelete bool DeleteURL string MetaData []metaDataInfo HasLocLinks bool LocLinks []localLink HasQueryLinks bool QueryLinks []simpleLink HasExtLinks bool ExtLinks []string ExtNewWindow string UnLinksContent string UnLinksPhrase string QueryKeyPhrase string EvalMatrix []matrixLine ParseMatrix []matrixLine HasShadowLinks bool ShadowLinks []string Endnotes string }{ Zid: zid.String(), WebURL: wui.NewURLBuilder('h').SetZid(apiZid).String(), ContextURL: wui.NewURLBuilder('k').SetZid(apiZid).String(), CanWrite: wui.canWrite(ctx, user, zn.Meta, zn.Content), EditURL: wui.NewURLBuilder('e').SetZid(apiZid).String(), CanFolge: canCreate, FolgeURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionFolge).String(), CanCopy: canCreate && !zn.Content.IsBinary(), CopyURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionCopy).String(), CanRename: wui.canRename(ctx, user, zn.Meta), RenameURL: wui.NewURLBuilder('b').SetZid(apiZid).String(), CanDelete: wui.canDelete(ctx, user, zn.Meta), DeleteURL: wui.NewURLBuilder('d').SetZid(apiZid).String(), MetaData: metaData, HasLocLinks: len(locLinks) > 0, LocLinks: locLinks, HasQueryLinks: len(queryLinks) > 0, QueryLinks: queryLinks, HasExtLinks: len(extLinks) > 0, ExtLinks: extLinks, ExtNewWindow: htmlAttrNewWindow(len(extLinks) > 0), UnLinksContent: unlinkedContent, UnLinksPhrase: phrase, QueryKeyPhrase: api.QueryKeyPhrase, EvalMatrix: wui.infoAPIMatrix('v', zid), |
︙ | ︙ |
Changes to web/adapter/webui/get_zettel.go.
︙ | ︙ | |||
58 59 60 61 62 63 64 65 66 67 | return } if cssZid != id.Invalid { roleCSSURL = wui.NewURLBuilder('z').SetZid(api.ZettelID(cssZid.String())).String() } user := server.GetUser(ctx) roleText := zn.Meta.GetDefault(api.KeyRole, "") canCreate := wui.canCreate(ctx, user) getTextTitle := wui.makeGetTextTitle(createGetMetadataFunc(ctx, getMeta), evalMetadata) extURL, hasExtURL := zn.Meta.Get(api.KeyURL) | > | | < | | | | | | | | > | | | < < | | < | | | | | | | | > | < | | | | | | | | > | | | < < | | < | | | | | | | | > | < | 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 | return } if cssZid != id.Invalid { roleCSSURL = wui.NewURLBuilder('z').SetZid(api.ZettelID(cssZid.String())).String() } user := server.GetUser(ctx) roleText := zn.Meta.GetDefault(api.KeyRole, "") tags := wui.buildTagInfos(zn.Meta) canCreate := wui.canCreate(ctx, user) getTextTitle := wui.makeGetTextTitle(createGetMetadataFunc(ctx, getMeta), evalMetadata) extURL, hasExtURL := zn.Meta.Get(api.KeyURL) folgeLinks := wui.encodeZettelLinks(zn.InhMeta, api.KeyFolge, getTextTitle) backLinks := wui.encodeZettelLinks(zn.InhMeta, api.KeyBack, getTextTitle) apiZid := api.ZettelID(zid.String()) var base baseData wui.makeBaseData(ctx, wui.rtConfig.Get(ctx, zn.InhMeta, api.KeyLang), textTitle, roleCSSURL, user, &base) base.MetaHeader = enc.MetaString(zn.InhMeta, evalMetadata) wui.renderTemplate(ctx, w, id.ZettelTemplateZid, &base, struct { HTMLTitle string RoleCSS string CanWrite bool EditURL string Zid string InfoURL string RoleText string RoleURL string HasTags bool Tags []simpleLink CanCopy bool CopyURL string CanFolge bool FolgeURL string PrecursorRefs string HasExtURL bool ExtURL string ExtNewWindow string Author string Content string HasFolgeLinks bool FolgeLinks []simpleLink HasBackLinks bool BackLinks []simpleLink }{ HTMLTitle: htmlTitle, RoleCSS: roleCSSURL, CanWrite: wui.canWrite(ctx, user, zn.Meta, zn.Content), EditURL: wui.NewURLBuilder('e').SetZid(apiZid).String(), Zid: zid.String(), InfoURL: wui.NewURLBuilder('i').SetZid(apiZid).String(), RoleText: roleText, RoleURL: wui.NewURLBuilder('h').AppendQuery(api.KeyRole + api.SearchOperatorHas + roleText).String(), HasTags: len(tags) > 0, Tags: tags, CanCopy: canCreate && !zn.Content.IsBinary(), CopyURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionCopy).String(), CanFolge: canCreate, FolgeURL: wui.NewURLBuilder('c').SetZid(apiZid).AppendKVQuery(queryKeyAction, valueActionFolge).String(), PrecursorRefs: wui.encodeIdentifierSet(zn.InhMeta, api.KeyPrecursor, getTextTitle), ExtURL: extURL, HasExtURL: hasExtURL, ExtNewWindow: htmlAttrNewWindow(hasExtURL), Author: zn.Meta.GetDefault(api.KeyAuthor, ""), Content: htmlContent, HasFolgeLinks: len(folgeLinks) > 0, FolgeLinks: folgeLinks, HasBackLinks: len(backLinks) > 0, BackLinks: backLinks, }) } } func encodeInlinesText(is *ast.InlineSlice, enc *textenc.Encoder) (string, error) { if is == nil || len(*is) == 0 { return "", nil |
︙ | ︙ | |||
145 146 147 148 149 150 151 | func (wui *WebUI) buildTagInfos(m *meta.Meta) []simpleLink { var tagInfos []simpleLink if tags, ok := m.GetList(api.KeyTags); ok { ub := wui.NewURLBuilder('h') tagInfos = make([]simpleLink, len(tags)) for i, tag := range tags { | | | 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | func (wui *WebUI) buildTagInfos(m *meta.Meta) []simpleLink { var tagInfos []simpleLink if tags, ok := m.GetList(api.KeyTags); ok { ub := wui.NewURLBuilder('h') tagInfos = make([]simpleLink, len(tags)) for i, tag := range tags { tagInfos[i] = simpleLink{Text: tag, URL: ub.AppendQuery(api.KeyAllTags + api.SearchOperatorHas + tag).String()} ub.ClearQuery() } } return tagInfos } func (wui *WebUI) encodeIdentifierSet(m *meta.Meta, key string, getTextTitle getTextTitleFunc) string { |
︙ | ︙ |
Changes to web/adapter/webui/htmlgen.go.
︙ | ︙ | |||
45 46 47 48 49 50 51 52 53 54 55 56 57 58 | gen := &htmlGenerator{ builder: builder, textEnc: textenc.Create(), extMarker: extMarker, env: env, } env.Builtins.Set(sexpr.SymLinkZettel, sxpf.NewBuiltin("linkZ", true, 2, -1, gen.generateLinkZettel)) env.Builtins.Set(sexpr.SymLinkFound, sxpf.NewBuiltin("linkZ", true, 2, -1, gen.generateLinkZettel)) env.Builtins.Set(sexpr.SymLinkBased, sxpf.NewBuiltin("linkB", true, 2, -1, gen.generateLinkBased)) env.Builtins.Set(sexpr.SymLinkQuery, sxpf.NewBuiltin("linkQ", true, 2, -1, gen.generateLinkQuery)) env.Builtins.Set(sexpr.SymLinkExternal, sxpf.NewBuiltin("linkE", true, 2, -1, gen.generateLinkExternal)) f, err := env.Builtins.LookupForm(sexpr.SymEmbed) | > | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | gen := &htmlGenerator{ builder: builder, textEnc: textenc.Create(), extMarker: extMarker, env: env, } env.Builtins.Set(sexpr.SymTag, sxpf.NewBuiltin("tag", true, 0, -1, gen.generateTag)) env.Builtins.Set(sexpr.SymLinkZettel, sxpf.NewBuiltin("linkZ", true, 2, -1, gen.generateLinkZettel)) env.Builtins.Set(sexpr.SymLinkFound, sxpf.NewBuiltin("linkZ", true, 2, -1, gen.generateLinkZettel)) env.Builtins.Set(sexpr.SymLinkBased, sxpf.NewBuiltin("linkB", true, 2, -1, gen.generateLinkBased)) env.Builtins.Set(sexpr.SymLinkQuery, sxpf.NewBuiltin("linkQ", true, 2, -1, gen.generateLinkQuery)) env.Builtins.Set(sexpr.SymLinkExternal, sxpf.NewBuiltin("linkE", true, 2, -1, gen.generateLinkExternal)) f, err := env.Builtins.LookupForm(sexpr.SymEmbed) |
︙ | ︙ | |||
69 70 71 72 73 74 75 | api.KeyLicense: "license", } func (g *htmlGenerator) MetaString(m *meta.Meta, evalMeta encoder.EvalMetaFunc) string { ignore := strfun.NewSet(api.KeyTitle, api.KeyLang) var buf bytes.Buffer | | > > > > | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | api.KeyLicense: "license", } func (g *htmlGenerator) MetaString(m *meta.Meta, evalMeta encoder.EvalMetaFunc) string { ignore := strfun.NewSet(api.KeyTitle, api.KeyLang) var buf bytes.Buffer if tags, ok := m.Get(api.KeyAllTags); ok { writeMetaTags(&buf, tags) ignore.Set(api.KeyAllTags) ignore.Set(api.KeyTags) } else if tags, ok = m.Get(api.KeyTags); ok { writeMetaTags(&buf, tags) ignore.Set(api.KeyTags) } for _, p := range m.ComputedPairs() { key := p.Key if ignore.Has(key) { |
︙ | ︙ | |||
129 130 131 132 133 134 135 136 137 138 139 140 141 142 | // InlinesString writes an inline slice to the writer func (g *htmlGenerator) InlinesString(is *ast.InlineSlice) (string, error) { if is == nil || len(*is) == 0 { return "", nil } return html.EvaluateInline(g.env, sexprenc.GetSexpr(is), true, false), nil } func (g *htmlGenerator) generateLinkZettel(senv sxpf.Environment, args *sxpf.Pair, _ int) (sxpf.Value, error) { env := senv.(*html.EncEnvironment) if a, refValue, ok := html.PrepareLink(env, args); ok { zid, fragment, hasFragment := strings.Cut(refValue, "#") u := g.builder.NewURLBuilder('h').SetZid(api.ZettelID(zid)) if hasFragment { | > > > > > > > > > > > > > > > > | 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 | // InlinesString writes an inline slice to the writer func (g *htmlGenerator) InlinesString(is *ast.InlineSlice) (string, error) { if is == nil || len(*is) == 0 { return "", nil } return html.EvaluateInline(g.env, sexprenc.GetSexpr(is), true, false), nil } func (g *htmlGenerator) generateTag(senv sxpf.Environment, args *sxpf.Pair, _ int) (sxpf.Value, error) { if !sxpf.IsNil(args) { env := senv.(*html.EncEnvironment) s := env.GetString(args) if env.IgnoreLinks() { env.WriteEscaped(s) } else { u := g.builder.NewURLBuilder('h').AppendQuery(api.KeyAllTags + ":#" + strings.ToLower(s)) env.WriteStrings(`<a href="`, u.String(), `">#`) env.WriteEscaped(s) env.WriteString("</a>") } } return nil, nil } func (g *htmlGenerator) generateLinkZettel(senv sxpf.Environment, args *sxpf.Pair, _ int) (sxpf.Value, error) { env := senv.(*html.EncEnvironment) if a, refValue, ok := html.PrepareLink(env, args); ok { zid, fragment, hasFragment := strings.Cut(refValue, "#") u := g.builder.NewURLBuilder('h').SetZid(api.ZettelID(zid)) if hasFragment { |
︙ | ︙ |
Changes to web/adapter/webui/lists.go.
︙ | ︙ | |||
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //----------------------------------------------------------------------------- package webui import ( "bytes" "context" "io" "net/http" "net/url" "strings" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" | > < < | > | < < < < < | | < | 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 | //----------------------------------------------------------------------------- package webui import ( "bytes" "context" "encoding/xml" "io" "net/http" "net/url" "strings" "zettelstore.de/c/api" "zettelstore.de/z/box" "zettelstore.de/z/domain/id" "zettelstore.de/z/domain/meta" "zettelstore.de/z/encoding/rss" "zettelstore.de/z/evaluator" "zettelstore.de/z/query" "zettelstore.de/z/usecase" "zettelstore.de/z/web/adapter" "zettelstore.de/z/web/server" ) // MakeListHTMLMetaHandler creates a HTTP handler for rendering the list of // zettel as HTML. func (wui *WebUI) MakeListHTMLMetaHandler(listMeta usecase.ListMeta, evaluate *usecase.Evaluate) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { q := adapter.GetQuery(r.URL.Query()) ctx := r.Context() if !q.EnrichNeeded() { ctx = box.NoEnrichContext(ctx) } metaList, err := listMeta.Run(ctx, q) if err != nil { wui.reportError(ctx, w, err) return } if actions := q.Actions(); len(actions) > 0 && actions[0] == "RSS" { wui.renderRSS(ctx, w, q, metaList) return } bns := evaluate.RunBlockNode(ctx, evaluator.QueryAction(ctx, q, metaList, wui.rtConfig)) enc := wui.getSimpleHTMLEncoder() htmlContent, err := enc.BlocksString(&bns) if err != nil { wui.reportError(ctx, w, err) return |
︙ | ︙ | |||
83 84 85 86 87 88 89 | func (wui *WebUI) renderRSS(ctx context.Context, w http.ResponseWriter, q *query.Query, ml []*meta.Meta) { var rssConfig rss.Configuration rssConfig.Setup(ctx, wui.rtConfig) if actions := q.Actions(); len(actions) > 2 && actions[1] == "TITLE" { rssConfig.Title = strings.Join(actions[2:], " ") } | | > > > | < < < < < < < < < < < < < < < < < < < < | 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 | func (wui *WebUI) renderRSS(ctx context.Context, w http.ResponseWriter, q *query.Query, ml []*meta.Meta) { var rssConfig rss.Configuration rssConfig.Setup(ctx, wui.rtConfig) if actions := q.Actions(); len(actions) > 2 && actions[1] == "TITLE" { rssConfig.Title = strings.Join(actions[2:], " ") } data, err := rssConfig.Marshal(q, ml) if err != nil { wui.reportError(ctx, w, err) return } adapter.PrepareHeader(w, rss.ContentType) w.WriteHeader(http.StatusOK) if _, err = io.WriteString(w, xml.Header); err == nil { _, err = w.Write(data) } if err != nil { wui.log.IfErr(err).Msg("unable to write RSS data") } } // MakeZettelContextHandler creates a new HTTP handler for the use case "zettel context". func (wui *WebUI) MakeZettelContextHandler(getContext usecase.ZettelContext, evaluate *usecase.Evaluate) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() zid, err := id.Parse(r.URL.Path[1:]) if err != nil { wui.reportError(ctx, w, box.ErrNotFound) |
︙ | ︙ |
Changes to web/adapter/webui/rename_zettel.go.
︙ | ︙ | |||
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | m, err := getMeta.Run(ctx, zid) if err != nil { wui.reportError(ctx, w, err) return } getTextTitle := wui.makeGetTextTitle(createGetMetadataFunc(ctx, getMeta), createEvalMetadataFunc(ctx, evaluate)) uselessFiles := retrieveUselessFiles(m) user := server.GetUser(ctx) var base baseData wui.makeBaseData(ctx, wui.rtConfig.Get(ctx, m, api.KeyLang), "Rename Zettel "+zid.String(), "", user, &base) wui.renderTemplate(ctx, w, id.RenameTemplateZid, &base, struct { Zid string MetaPairs []meta.Pair | > > | > | | 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 | m, err := getMeta.Run(ctx, zid) if err != nil { wui.reportError(ctx, w, err) return } getTextTitle := wui.makeGetTextTitle(createGetMetadataFunc(ctx, getMeta), createEvalMetadataFunc(ctx, evaluate)) incomingLinks := wui.encodeIncoming(m, getTextTitle) uselessFiles := retrieveUselessFiles(m) user := server.GetUser(ctx) var base baseData wui.makeBaseData(ctx, wui.rtConfig.Get(ctx, m, api.KeyLang), "Rename Zettel "+zid.String(), "", user, &base) wui.renderTemplate(ctx, w, id.RenameTemplateZid, &base, struct { Zid string MetaPairs []meta.Pair HasIncoming bool Incoming []simpleLink HasUselessFiles bool UselessFiles []string }{ Zid: zid.String(), MetaPairs: m.ComputedPairs(), HasIncoming: len(incomingLinks) > 0, Incoming: incomingLinks, HasUselessFiles: len(uselessFiles) > 0, UselessFiles: uselessFiles, }) } } // MakePostRenameZettelHandler creates a new HTTP handler to rename an existing zettel. |
︙ | ︙ |
Changes to web/adapter/webui/webui.go.
︙ | ︙ | |||
96 97 98 99 100 101 102 | tokenLifetime: kernel.Main.GetConfig(kernel.WebService, kernel.WebTokenLifetimeHTML).(time.Duration), cssBaseURL: ab.NewURLBuilder('z').SetZid(api.ZidBaseCSS).String(), cssUserURL: ab.NewURLBuilder('z').SetZid(api.ZidUserCSS).String(), homeURL: ab.NewURLBuilder('/').String(), listZettelURL: ab.NewURLBuilder('h').String(), listRolesURL: ab.NewURLBuilder('h').AppendQuery(api.ActionSeparator + api.KeyRole).String(), | | | 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | tokenLifetime: kernel.Main.GetConfig(kernel.WebService, kernel.WebTokenLifetimeHTML).(time.Duration), cssBaseURL: ab.NewURLBuilder('z').SetZid(api.ZidBaseCSS).String(), cssUserURL: ab.NewURLBuilder('z').SetZid(api.ZidUserCSS).String(), homeURL: ab.NewURLBuilder('/').String(), listZettelURL: ab.NewURLBuilder('h').String(), listRolesURL: ab.NewURLBuilder('h').AppendQuery(api.ActionSeparator + api.KeyRole).String(), listTagsURL: ab.NewURLBuilder('h').AppendQuery(api.ActionSeparator + api.KeyAllTags).String(), refreshURL: ab.NewURLBuilder('g').AppendKVQuery("_c", "r").String(), withAuth: authz.WithAuth(), loginURL: loginoutBase.String(), logoutURL: loginoutBase.AppendKVQuery("logout", "").String(), searchURL: ab.NewURLBuilder('h').String(), } wui.observe(box.UpdateInfo{Box: mgr, Reason: box.OnReload, Zid: id.Invalid}) |
︙ | ︙ | |||
227 228 229 230 231 232 233 | } type simpleLink struct { Text string URL string } | < < < < < < < < < < < < | | | | | | | | | | | | | | | | | | | > | | | | | | > > | | 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 | } type simpleLink struct { Text string URL string } type baseData struct { Lang string MetaHeader string CSSBaseURL string CSSUserURL string CSSRoleURL string Title string HomeURL string WithUser bool WithAuth bool UserIsValid bool UserZettelURL string UserIdent string LoginURL string LogoutURL string ListZettelURL string ListRolesURL string ListTagsURL string CanRefresh bool RefreshURL string HasNewZettelLinks bool NewZettelLinks []simpleLink SearchURL string QueryKeyQuery string Content string FooterHTML string DebugMode bool } func (wui *WebUI) makeBaseData(ctx context.Context, lang, title, roleCSSURL string, user *meta.Meta, data *baseData) { var userZettelURL string var userIdent string userIsValid := user != nil if userIsValid { userZettelURL = wui.NewURLBuilder('h').SetZid(api.ZettelID(user.Zid.String())).String() userIdent = user.GetDefault(api.KeyUserID, "") } newZettelLinks := wui.fetchNewTemplates(ctx, user) data.Lang = lang data.CSSBaseURL = wui.cssBaseURL data.CSSUserURL = wui.cssUserURL data.CSSRoleURL = roleCSSURL data.Title = title data.HomeURL = wui.homeURL data.WithAuth = wui.withAuth data.WithUser = data.WithAuth data.UserIsValid = userIsValid data.UserZettelURL = userZettelURL data.UserIdent = userIdent data.LoginURL = wui.loginURL data.LogoutURL = wui.logoutURL data.ListZettelURL = wui.listZettelURL data.ListRolesURL = wui.listRolesURL data.ListTagsURL = wui.listTagsURL data.CanRefresh = wui.canRefresh(user) data.RefreshURL = wui.refreshURL data.HasNewZettelLinks = len(newZettelLinks) > 0 data.NewZettelLinks = newZettelLinks data.SearchURL = wui.searchURL data.QueryKeyQuery = api.QueryKeyQuery data.FooterHTML = wui.rtConfig.Get(ctx, nil, config.KeyFooterHTML) data.DebugMode = wui.debug } func (wui *WebUI) getSimpleHTMLEncoder() *htmlGenerator { return createGenerator(wui, "") } |
︙ | ︙ |
Changes to www/changes.wiki.
1 2 | <title>Change Log</title> | < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <title>Change Log</title> <a name="0_8"></a> <h2>Changes for Version 0.8.0 (2022-09-17)</h2> <a name="0_7"></a> <h2>Changes for Version 0.7.0 (pending)</h2> * Removes support for URL query parameter to search for metadata values, sorting, offset, and limit a zettel list. Deprecated in version 0.6.0 (breaking: api, webui) * Allow to search for the existence / non-existence of a metadata key with the "?" operator: <tt>key?</tt> and <tt>key!?</tt>. Previously, the ":" operator was used for this by specifying an empty search value. Now you can use the ":" operator to find empty / non-empty metadata values. If you |
︙ | ︙ |
Changes to www/download.wiki.
1 2 3 4 5 6 7 8 9 10 11 | <title>Download</title> <h1>Download of Zettelstore Software</h1> <h2>Foreword</h2> * Zettelstore is free/libre open source software, licensed under EUPL-1.2-or-later. * The software is provided as-is. * There is no guarantee that it will not damage your system. * However, it is in use by the main developer since March 2020 without any damage. * It may be useful for you. It is useful for me. * Take a look at the [https://zettelstore.de/manual/|manual] to know how to start and use it. <h2>ZIP-ped Executables</h2> | | | | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <title>Download</title> <h1>Download of Zettelstore Software</h1> <h2>Foreword</h2> * Zettelstore is free/libre open source software, licensed under EUPL-1.2-or-later. * The software is provided as-is. * There is no guarantee that it will not damage your system. * However, it is in use by the main developer since March 2020 without any damage. * It may be useful for you. It is useful for me. * Take a look at the [https://zettelstore.de/manual/|manual] to know how to start and use it. <h2>ZIP-ped Executables</h2> Build: <code>v0.7.0</code> (2022-09-17). * [/uv/zettelstore-0.7.0-linux-amd64.zip|Linux] (amd64) * [/uv/zettelstore-0.7.0-linux-arm.zip|Linux] (arm6, e.g. Raspberry Pi) * [/uv/zettelstore-0.7.0-windows-amd64.zip|Windows] (amd64) * [/uv/zettelstore-0.7.0-darwin-amd64.zip|macOS] (amd64) * [/uv/zettelstore-0.7.0-darwin-arm64.zip|macOS] (arm64, aka Apple silicon) Unzip the appropriate file, install and execute Zettelstore according to the manual. <h2>Zettel for the manual</h2> As a starter, you can download the zettel for the manual [/uv/manual-0.7.0.zip|here]. Just unzip the contained files and put them into your zettel folder or configure a file box to read the zettel directly from the ZIP file. |
Changes to www/index.wiki.
︙ | ︙ | |||
20 21 22 23 24 25 26 | access Zettelstore via its API more easily, [https://zettelstore.de/contrib|Zettelstore Contrib] contains contributed software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. [https://twitter.com/zettelstore|Stay tuned] … <hr> | | | | | | | | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | access Zettelstore via its API more easily, [https://zettelstore.de/contrib|Zettelstore Contrib] contains contributed software, which often connects to Zettelstore via its API. Some of the software packages may be experimental. [https://twitter.com/zettelstore|Stay tuned] … <hr> <h3>Latest Release: 0.7.0 (2022-09-17)</h3> * [./download.wiki|Download] * [./changes.wiki#0_7|Change summary] * [/timeline?p=v0.7.0&bt=v0.6.0&y=ci|Check-ins for version 0.7.0], [/vdiff?to=v0.7.0&from=v0.6.0|content diff] * [/timeline?df=v0.7.0&y=ci|Check-ins derived from the 0.7.0 release], [/vdiff?from=v0.7.0&to=trunk|content diff] * [./plan.wiki|Limitations and planned improvements] * [/timeline?t=release|Timeline of all past releases] <hr> <h2>Build instructions</h2> Just install [https://golang.org/dl/|Go] and some Go-based tools. Please read the [./build.md|instructions] for details. |
︙ | ︙ |