@@ 12,7 12,7 @@
<h2 class="text-xl">Speculative specification</h2>
<dl>
<dt>Version</dt>
- <dd>2022.05.02.dev</dd>
+ <dd>2022.08.05.dev</dd>
<dt>Status</dt>
<dd>Draft</dd>
@@ 41,16 41,15 @@
<p>
The source code for our parser can be found
- <a href="https://github.com/neurosnap/lists.sh/blob/main/pkg/parser.go">here</a>.
- </p>
-
- <p>
- The source code for an example list demonstrating all the features can be found
- <a href="https://github.com/neurosnap/lists-official-blog/blob/main/spec-example.txt">here</a>.
+ <a href="https://git.sr.ht/~erock/pico/tree/main/item/lists/parser.go">here</a>.
</p>
</section>
<section id="parameters">
+ <h2 class="text-xl">
+ <a href="#parameters" rel="nofollow noopener">#</a>
+ Parameters
+ </h2>
<p>
As a subtype of the top-level media type "text", "text/plain" inherits the "charset"
parameter defined in <a href="https://datatracker.ietf.org/doc/html/rfc2046#section-4.1">RFC 2046</a>.
@@ 59,6 58,10 @@
</section>
<section id="line-orientation">
+ <h2 class="text-xl">
+ <a href="#line-orientation" rel="nofollow noopener">#</a>
+ Line orientation
+ </h2>
<p>
As mentioned, the text format is line-oriented. Each line of a document has a single
"line type". It is possible to unambiguously determine a line's type purely by
@@ 69,7 72,10 @@
</section>
<section id="file-extensions">
- <h2 class="text-xl">File extension</h2>
+ <h2 class="text-xl">
+ <a href="#file-extensions" rel="nofollow noopener">#</a>
+ File extension
+ </h2>
<p>
{{.Site.Domain}} only supports the <code>.txt</code> file extension and will
ignore all other file extensions.
@@ 77,7 83,10 @@
</section>
<section id="list-item">
- <h2 class="text-xl">List item</h2>
+ <h2 class="text-xl">
+ <a href="#list-item" rel="nofollow noopener">#</a>
+ List item
+ </h2>
<p>
List items are separated by newline characters <code>\n</code>.
Each list item is on its own line. A list item does not require any special formatting.
@@ 90,7 99,10 @@
</section>
<section id="hyperlinks">
- <h2 class="text-xl">Hyperlinks</h2>
+ <h2 class="text-xl">
+ <a href="#hyperlinks" rel="nofollow noopener">#</a>
+ Hyperlinks
+ </h2>
<p>
Hyperlinks are denoted by the prefix <code>=></code>. The following text should then be
the hyperlink.
@@ 100,8 112,26 @@
<pre>=> https://{{.Site.Domain}} microblog for lists</pre>
</section>
+ <section id="nested-lists">
+ <h2 class="text-xl">
+ <a href="#nested-lists" rel="nofollow noopener">#</a>
+ Nested lists
+ </h2>
+ <p>
+ Users can create nested lists. Tabbing a list will nest it under the list item
+ directly above it. Both tab character `\t` or whitespace as tabs are permitted.
+ </p>
+ <pre>first item
+ second item
+ third item
+last item</pre>
+ </section>
+
<section id="images">
- <h2 class="text-xl">Images</h2>
+ <h2 class="text-xl">
+ <a href="#hyperlinks" rel="nofollow noopener">#</a>
+ Images
+ </h2>
<p>
List items can be represented as images by prefixing the line with <code>=<</code>.
</p>
@@ 111,7 141,10 @@
</section>
<section id="headers">
- <h2 class="text-xl">Headers</h2>
+ <h2 class="text-xl">
+ <a href="#headers" rel="nofollow noopener">#</a>
+ Headers
+ </h2>
<p>
List items can be represented as headers. We support two headers currently. Headers
will end the previous list and then create a new one after it. This allows a single
@@ 122,7 155,10 @@
</section>
<section id="blockquotes">
- <h2 class="text-xl">Blockquotes</h2>
+ <h2 class="text-xl">
+ <a href="#headers" rel="nofollow noopener">#</a>
+ Blockquotes
+ </h2>
<p>
List items can be represented as blockquotes.
</p>
@@ 130,7 166,10 @@
</section>
<section id="preformatted">
- <h2 class="text-xl">Preformatted</h2>
+ <h2 class="text-xl">
+ <a href="#preformatted" rel="nofollow noopener">#</a>
+ Preformatted
+ </h2>
<p>
List items can be represented as preformatted text where newline characters are not
considered part of new list items. They can be represented by prefixing the line with
@@ 154,7 193,10 @@ echo "This will not render properly"```</pre>
</section>
<section id="variables">
- <h2 class="text-xl">Variables</h2>
+ <h2 class="text-xl">
+ <a href="#variables" rel="nofollow noopener">#</a>
+ Variables
+ </h2>
<p>
Variables allow us to store metadata within our system. Variables are list items with
key value pairs denoted by <code>=:</code> followed by the key, a whitespace character,
@@ 3,15 3,18 @@ package lists
import (
"fmt"
"html/template"
+ "regexp"
"strings"
"time"
"github.com/araddon/dateparse"
)
+var reIndent = regexp.MustCompile(`^[[:blank:]]+`)
+
type ParsedText struct {
- Items []*ListItem
- MetaData *MetaData
+ Items []*ListItem
+ *MetaData
}
type ListItem struct {
@@ 25,6 28,7 @@ type ListItem struct {
IsHeaderTwo bool
IsImg bool
IsPre bool
+ Indent int
}
type MetaData struct {
@@ 107,6 111,63 @@ func KeyAsValue(token *SplitToken) string {
return token.Value
}
+func parseItem(meta *MetaData, li *ListItem, prevItem *ListItem, pre bool, mod int) (bool, bool, int) {
+ skip := false
+
+ if strings.HasPrefix(li.Value, preToken) {
+ pre = !pre
+ if pre {
+ nextValue := strings.Replace(li.Value, preToken, "", 1)
+ li.IsPre = true
+ li.Value = nextValue
+ } else {
+ skip = true
+ }
+ } else if pre {
+ nextValue := strings.Replace(li.Value, preToken, "", 1)
+ prevItem.Value = fmt.Sprintf("%s\n%s", prevItem.Value, nextValue)
+ skip = true
+ } else if strings.HasPrefix(li.Value, urlToken) {
+ li.IsURL = true
+ split := TextToSplitToken(strings.Replace(li.Value, urlToken, "", 1))
+ li.URL = template.URL(split.Key)
+ li.Value = KeyAsValue(split)
+ } else if strings.HasPrefix(li.Value, blockToken) {
+ li.IsBlock = true
+ li.Value = strings.Replace(li.Value, blockToken, "", 1)
+ } else if strings.HasPrefix(li.Value, imgToken) {
+ li.IsImg = true
+ split := TextToSplitToken(strings.Replace(li.Value, imgToken, "", 1))
+ li.URL = template.URL(split.Key)
+ li.Value = KeyAsValue(split)
+ } else if strings.HasPrefix(li.Value, varToken) {
+ split := TextToSplitToken(strings.Replace(li.Value, varToken, "", 1))
+ TokenToMetaField(meta, split)
+ } else if strings.HasPrefix(li.Value, headerTwoToken) {
+ li.IsHeaderTwo = true
+ li.Value = strings.Replace(li.Value, headerTwoToken, "", 1)
+ } else if strings.HasPrefix(li.Value, headerOneToken) {
+ li.IsHeaderOne = true
+ li.Value = strings.Replace(li.Value, headerOneToken, "", 1)
+ } else if reIndent.MatchString(li.Value) {
+ trim := reIndent.ReplaceAllString(li.Value, "")
+ old := len(li.Value)
+ li.Value = trim
+
+ pre, skip, _ = parseItem(meta, li, prevItem, pre, mod)
+ if prevItem.Indent == 0 {
+ mod = old - len(trim)
+ li.Indent = 1
+ } else {
+ li.Indent = (old - len(trim)) / mod
+ }
+ } else {
+ li.IsText = true
+ }
+
+ return pre, skip, mod
+}
+
func ParseText(text string) *ParsedText {
textItems := SplitByNewline(text)
items := []*ListItem{}
@@ 116,58 177,19 @@ func ParseText(text string) *ParsedText {
}
pre := false
skip := false
+ mod := 0
var prevItem *ListItem
for _, t := range textItems {
- skip = false
-
if len(items) > 0 {
prevItem = items[len(items)-1]
}
li := ListItem{
- Value: strings.Trim(t, " "),
+ Value: t,
}
- if strings.HasPrefix(li.Value, preToken) {
- pre = !pre
- if pre {
- nextValue := strings.Replace(li.Value, preToken, "", 1)
- li.IsPre = true
- li.Value = nextValue
- } else {
- skip = true
- }
- } else if pre {
- nextValue := strings.Replace(li.Value, preToken, "", 1)
- prevItem.Value = fmt.Sprintf("%s\n%s", prevItem.Value, nextValue)
- skip = true
- } else if strings.HasPrefix(li.Value, urlToken) {
- li.IsURL = true
- split := TextToSplitToken(strings.Replace(li.Value, urlToken, "", 1))
- li.URL = template.URL(split.Key)
- li.Value = KeyAsValue(split)
- } else if strings.HasPrefix(li.Value, blockToken) {
- li.IsBlock = true
- li.Value = strings.Replace(li.Value, blockToken, "", 1)
- } else if strings.HasPrefix(li.Value, imgToken) {
- li.IsImg = true
- split := TextToSplitToken(strings.Replace(li.Value, imgToken, "", 1))
- li.URL = template.URL(split.Key)
- li.Value = KeyAsValue(split)
- } else if strings.HasPrefix(li.Value, varToken) {
- split := TextToSplitToken(strings.Replace(li.Value, varToken, "", 1))
- TokenToMetaField(&meta, split)
- continue
- } else if strings.HasPrefix(li.Value, headerTwoToken) {
- li.IsHeaderTwo = true
- li.Value = strings.Replace(li.Value, headerTwoToken, "", 1)
- } else if strings.HasPrefix(li.Value, headerOneToken) {
- li.IsHeaderOne = true
- li.Value = strings.Replace(li.Value, headerOneToken, "", 1)
- } else {
- li.IsText = true
- }
+ pre, skip, mod = parseItem(&meta, &li, prevItem, pre, mod)
if li.IsText && li.Value == "" {
skip = true
@@ 61,6 61,24 @@ func ServeFile(file string, contentType string) http.HandlerFunc {
}
}
+func minus(a, b int) int {
+ return a - b
+}
+
+func intRange(start, end int) []int {
+ n := end - start + 1
+ result := make([]int, n)
+ for i := 0; i < n; i++ {
+ result[i] = start + i
+ }
+ return result
+}
+
+var funcMap = template.FuncMap{
+ "minus": minus,
+ "intRange": intRange,
+}
+
func RenderTemplate(cfg *ConfigSite, templates []string) (*template.Template, error) {
files := make([]string, len(templates))
copy(files, templates)
@@ 71,7 89,8 @@ func RenderTemplate(cfg *ConfigSite, templates []string) (*template.Template, er
cfg.StaticPath("html/base.layout.tmpl"),
)
- ts, err := template.ParseFiles(files...)
+ ts, err := template.New("base").Funcs(funcMap).ParseFiles(files...)
+ // ts, err := template.ParseFiles(files...)
if err != nil {
return nil, err
}