Implement renderer test suite
This implements a Markdown / Gemtext suite, testing the entire renderer at complex Markdown documents, containing the entirety of current Markdown features accessible with gmnhg. The test suite can be expanded by adding a pair of .md/.gmi files. Minor bug fixes in JSON/Org metadata parsing where bugs were detected with the test suite are also included in this patch. Fixes #13.
This commit is contained in:
parent
c80b72613d
commit
fc76187e13
22 changed files with 653 additions and 5 deletions
|
@ -40,6 +40,9 @@ The renderer will also treat lists of links and paragraphs consisting of
|
|||
links only the special way: it will render only the links block for
|
||||
them.
|
||||
|
||||
To get a better idea of how source Markdown looks like after the
|
||||
conversion to Gemtext, see [testdata](testdata) directory.
|
||||
|
||||
[gomarkdown]: https://github.com/gomarkdown/markdown
|
||||
|
||||
## gmnhg
|
||||
|
|
3
go.mod
3
go.mod
|
@ -7,8 +7,10 @@ require (
|
|||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/gomarkdown/markdown v0.0.0-20210915032930-fe0e174ee09a
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/niklasfasching/go-org v1.5.0
|
||||
|
@ -16,5 +18,6 @@ require (
|
|||
github.com/spf13/cast v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 // indirect
|
||||
golang.org/x/net v0.0.0-20210917163549-3c21e5b27794 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
|
10
go.sum
10
go.sum
|
@ -21,12 +21,19 @@ github.com/gomarkdown/markdown v0.0.0-20210915032930-fe0e174ee09a/go.mod h1:JDGc
|
|||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
|
@ -81,8 +88,9 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
|
|
|
@ -17,6 +17,7 @@ package gmnhg
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
@ -48,6 +49,8 @@ func parseValue(value interface{}) interface{} {
|
|||
return value
|
||||
}
|
||||
|
||||
var errKeyNotFound = errors.New("cannot find tagged key in struct")
|
||||
|
||||
// for key "key" will set either map key "key" or struct field tagged
|
||||
// `tag:"key"` with value; expects a pointer
|
||||
func reflectSetKey(mapOrStruct interface{}, tag, key string, value interface{}) (err error) {
|
||||
|
@ -75,7 +78,7 @@ func reflectSetKey(mapOrStruct interface{}, tag, key string, value interface{})
|
|||
fieldName = field.Name
|
||||
}
|
||||
if fieldName == "" {
|
||||
return fmt.Errorf("cannot find tag %v with key %v in struct", tag, key)
|
||||
return fmt.Errorf("%v: %v %w", tag, key, errKeyNotFound)
|
||||
}
|
||||
v.FieldByName(fieldName).Set(reflect.ValueOf(parseValue(value)))
|
||||
default:
|
||||
|
@ -103,7 +106,7 @@ func unmarshalORG(data []byte, p interface{}) (err error) {
|
|||
} else if k == "date" {
|
||||
value = parseORGDate(v)
|
||||
}
|
||||
if err := reflectSetKey(p, "org", strings.ToLower(key), value); err != nil {
|
||||
if err := reflectSetKey(p, "org", strings.ToLower(key), value); err != nil && !errors.Is(err, errKeyNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ var (
|
|||
yamlDelimiter = []byte("---\n")
|
||||
tomlDelimiter = []byte("+++\n")
|
||||
jsonObjectRegex = regexp.MustCompile(`\A(\{[\s\S]*\})\n\n`)
|
||||
orgModeRegex = regexp.MustCompile(`\A((?:#\+\w+: ?\S*\n)*)`)
|
||||
orgModeRegex = regexp.MustCompile(`\A((?:#\+\w+\[?\]?: ?[^\n\r]*\n)+)`)
|
||||
)
|
||||
|
||||
// ParseMetadata extracts TOML/JSON/YAML/org-mode format front matter
|
||||
|
@ -109,7 +109,7 @@ func ParseMetadata(source []byte) (markdown []byte, metadata Metadata) {
|
|||
if err := json.Unmarshal(metadataContent, &metadata); err != nil {
|
||||
return
|
||||
}
|
||||
markdown = source[blockEnd+1:] // JSON end + \n\n - 1
|
||||
markdown = source[blockEnd:]
|
||||
} else if match := orgModeRegex.FindIndex(source); match != nil {
|
||||
blockEnd = match[1]
|
||||
metadataContent = source[:blockEnd]
|
||||
|
|
77
render_test.go
Normal file
77
render_test.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
// This file is part of gmnhg.
|
||||
|
||||
// gmnhg is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// gmnhg is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with gmnhg. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package gemini
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/hexops/gotextdiff"
|
||||
"github.com/hexops/gotextdiff/myers"
|
||||
"github.com/hexops/gotextdiff/span"
|
||||
"github.com/tdemin/gmnhg/internal/gmnhg"
|
||||
)
|
||||
|
||||
var fileList []string
|
||||
|
||||
var (
|
||||
mdFilenameRegex = regexp.MustCompile(`^(.+)\.md$`)
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// go test implicitly sets cwd to tested package directory; sadly,
|
||||
// this fact is undocumented
|
||||
files, err := ioutil.ReadDir("testdata")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, fileInfo := range files {
|
||||
if match := mdFilenameRegex.FindStringSubmatch(fileInfo.Name()); !fileInfo.IsDir() && match != nil {
|
||||
fileList = append(fileList, match[1])
|
||||
}
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestRenderer(t *testing.T) {
|
||||
for _, testName := range fileList {
|
||||
t.Logf("testing %s", testName)
|
||||
mdContents, err := ioutil.ReadFile(path.Join("testdata", testName+".md"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open Markdown test %s: %v", testName, err)
|
||||
}
|
||||
gmiContents, err := ioutil.ReadFile(path.Join("testdata", testName+".gmi"))
|
||||
if err != nil {
|
||||
t.Logf("%s: cannot open Gemtext file, skipping: %v", testName, err)
|
||||
continue
|
||||
}
|
||||
content, _ := gmnhg.ParseMetadata(mdContents)
|
||||
geminiContent, err := RenderMarkdown(content, Defaults)
|
||||
if err != nil {
|
||||
t.Errorf("failed to convert %s Markdown to Gemtext: %v", testName, err)
|
||||
}
|
||||
if !bytes.Equal(geminiContent, gmiContents) {
|
||||
diff := myers.ComputeEdits(span.URIFromPath("a.gmi"),
|
||||
string(geminiContent), string(gmiContents))
|
||||
t.Errorf("content mismatch on %s, diff:\n%s", testName,
|
||||
gotextdiff.ToUnified("a.gmi", "b.gmi", string(geminiContent), diff))
|
||||
}
|
||||
}
|
||||
}
|
1
testdata/front_matter_json.gmi
vendored
Normal file
1
testdata/front_matter_json.gmi
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
gmnhg test suite should parse the metadata above but disregard it.
|
6
testdata/front_matter_json.md
vendored
Normal file
6
testdata/front_matter_json.md
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"title": "JSON front matter test",
|
||||
"draft": true
|
||||
}
|
||||
|
||||
gmnhg test suite should parse the metadata above but disregard it.
|
1
testdata/front_matter_org.gmi
vendored
Normal file
1
testdata/front_matter_org.gmi
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
gmnhg test suite should parse the metadata above but disregard it.
|
5
testdata/front_matter_org.md
vendored
Normal file
5
testdata/front_matter_org.md
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
#+title: "ORG front matter test"
|
||||
#+draft: true
|
||||
#+tags[]: org,gmnhg,emacs
|
||||
|
||||
gmnhg test suite should parse the metadata above but disregard it.
|
1
testdata/front_matter_toml.gmi
vendored
Normal file
1
testdata/front_matter_toml.gmi
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
gmnhg test suite should parse the metadata above but disregard it.
|
6
testdata/front_matter_toml.md
vendored
Normal file
6
testdata/front_matter_toml.md
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
+++
|
||||
title = "TOML front matter test"
|
||||
draft = true
|
||||
+++
|
||||
|
||||
gmnhg test suite should parse the metadata above but disregard it.
|
1
testdata/front_matter_yaml.gmi
vendored
Normal file
1
testdata/front_matter_yaml.gmi
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
gmnhg test suite should parse the metadata above but disregard it.
|
6
testdata/front_matter_yaml.md
vendored
Normal file
6
testdata/front_matter_yaml.md
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: "YAML front matter test"
|
||||
draft: true
|
||||
---
|
||||
|
||||
gmnhg test suite should parse the metadata above but disregard it.
|
89
testdata/general_text.gmi
vendored
Normal file
89
testdata/general_text.gmi
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
# General text
|
||||
|
||||
Paragraphs are printed verbatim in gmnhg.
|
||||
|
||||
Single newlines (like in this multi-line paragraph) will get replaced by a space, as Gemini specification p. 5.4.1 recommends this for soft-wrapping text by clients.
|
||||
|
||||
Inline formatting bits (like this **bold** text, *emphasized* text, ~~strikethrough~~ text, `preformatted text`) are kept to make sure Gemini readers still have the stylistic context of your text.
|
||||
|
||||
## Blockquotes
|
||||
|
||||
Newlines in blockquote paragraphs, unlike usual paragraphs, aren't replaced with a space. This facilitates appending authorship information to the quote, or using blockquotes to write poems.
|
||||
|
||||
> "Never trouble another for what you can do yourself"
|
||||
> — Thomas Jefferson, 3rd president of the US
|
||||
|
||||
> "Wow, writing comprehensive test suites is hard!"
|
||||
> — Timur Demin, while writing this very test file
|
||||
|
||||
> "Somehow I know these two paragraphs will be broken into two separate
|
||||
> blockquotes by gmnhg. I think my knowledge of that comes from being
|
||||
> the author of this program."
|
||||
|
||||
> — also Timur Demin, in the process of writing this test file
|
||||
|
||||
## Code
|
||||
|
||||
gmnhg will use Gemtext preformatted blocks for that. Markdown alt-text for preformatted blocks is supported, and is used to render alt-text as specified by Gemini spec p. 5.4.3.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
func main() {
|
||||
println("gmnhg is awesome!")
|
||||
}
|
||||
```
|
||||
|
||||
Preformatted Markdown of course isn't rendered:
|
||||
|
||||
```
|
||||
# I am a test Markdown document
|
||||
|
||||
I contain text in **bold**.
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
gmnhg supports links, images, and footnotes. Links are a very interesting topic on itself; see a separate document for those.
|
||||
|
||||
=> links.md separate document
|
||||
|
||||
## Lists
|
||||
|
||||
Definition lists, numbered and ordered lists are all supported in gmnhg. There's also a separate document displaying those.
|
||||
|
||||
=> lists.md separate document
|
||||
|
||||
## Tables
|
||||
|
||||
Markdown tables are supported in gmnhg, and are better displayed by a separate document.
|
||||
|
||||
=> tables.md separate document
|
||||
|
||||
## Headings
|
||||
|
||||
Gemini specification allows up to three heading levels, with an optional space after the last heading symbol, `#`. With Markdown, you get 6; gmnhg will simply print the relevant number of #-s, making the client up to parse more heading levels and keeping context of the source document.
|
||||
|
||||
Since clients like Lagrange treat the fourth and the rest of #-s as heading content, it's best to avoid using H4-H6 in Gemini-aware Markdown entirely. Headings from H3 to H6 are provided below so you can test how your client handles that.
|
||||
|
||||
### Heading 3
|
||||
|
||||
#### Heading 4
|
||||
|
||||
##### Heading 5
|
||||
|
||||
###### Heading 6
|
||||
|
||||
## Misc
|
||||
|
||||
Inline HTML is currently stripped, but HTML contents remain on-screen. This may change in the future.
|
||||
|
||||
> There's currently a bug in gmnhg which prevents it from
|
||||
> stripping HTML in certain scenarios. HTML is noticeably still present
|
||||
> inside <span>blockquotes</span>.
|
||||
|
||||
=> https://github.com/tdemin/gmnhg/issues/6 bug in gmnhg
|
||||
|
||||
---
|
||||
|
||||
The Markdown horizontal line above is rendered as triple dashes.
|
102
testdata/general_text.md
vendored
Normal file
102
testdata/general_text.md
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
# General text
|
||||
|
||||
Paragraphs are printed verbatim in gmnhg.
|
||||
|
||||
Single newlines (like in this multi-line paragraph) will get replaced by
|
||||
a space, as Gemini specification p. 5.4.1 recommends this for
|
||||
soft-wrapping text by clients.
|
||||
|
||||
Inline formatting bits (like this **bold** text, _emphasized_ text,
|
||||
~~strikethrough~~ text, `preformatted text`) are kept to make sure
|
||||
Gemini readers still have the stylistic context of your text.
|
||||
|
||||
## Blockquotes
|
||||
|
||||
Newlines in blockquote paragraphs, unlike usual paragraphs, aren't
|
||||
replaced with a space. This facilitates appending authorship information
|
||||
to the quote, or using blockquotes to write poems.
|
||||
|
||||
> "Never trouble another for what you can do yourself"
|
||||
> — Thomas Jefferson, 3rd president of the US
|
||||
|
||||
> "Wow, writing comprehensive test suites is hard!"
|
||||
> — Timur Demin, while writing this very test file
|
||||
|
||||
> "Somehow I know these two paragraphs will be broken into two separate
|
||||
> blockquotes by gmnhg. I think my knowledge of that comes from being
|
||||
> the author of this program."
|
||||
>
|
||||
> — also Timur Demin, in the process of writing this test file
|
||||
|
||||
## Code
|
||||
|
||||
gmnhg will use Gemtext preformatted blocks for that. Markdown alt-text
|
||||
for preformatted blocks is supported, and is used to render alt-text as
|
||||
specified by Gemini spec p. 5.4.3.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
func main() {
|
||||
println("gmnhg is awesome!")
|
||||
}
|
||||
```
|
||||
|
||||
Preformatted Markdown of course isn't rendered:
|
||||
|
||||
```
|
||||
# I am a test Markdown document
|
||||
|
||||
I contain text in **bold**.
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
gmnhg supports links, images, and footnotes. Links are a very
|
||||
interesting topic on itself; see a [separate document](links.md) for
|
||||
those.
|
||||
|
||||
## Lists
|
||||
|
||||
Definition lists, numbered and ordered lists are all supported in gmnhg.
|
||||
There's also a [separate document](lists.md) displaying those.
|
||||
|
||||
## Tables
|
||||
|
||||
Markdown tables are supported in gmnhg, and are better displayed by a
|
||||
[separate document](tables.md).
|
||||
|
||||
## Headings
|
||||
|
||||
Gemini specification allows up to three heading levels, with an optional
|
||||
space after the last heading symbol, `#`. With Markdown, you get 6;
|
||||
gmnhg will simply print the relevant number of #-s, making the client up
|
||||
to parse more heading levels and keeping context of the source document.
|
||||
|
||||
Since clients like Lagrange treat the fourth and the rest of #-s as
|
||||
heading content, it's best to avoid using H4-H6 in Gemini-aware Markdown
|
||||
entirely. Headings from H3 to H6 are provided below so you can test how
|
||||
your client handles that.
|
||||
|
||||
### Heading 3
|
||||
|
||||
#### Heading 4
|
||||
|
||||
##### Heading 5
|
||||
|
||||
###### Heading 6
|
||||
|
||||
## Misc
|
||||
|
||||
Inline HTML is <span class="bold">currently</span> stripped, but HTML
|
||||
contents remain on-screen. This may change in the future.
|
||||
|
||||
> There's currently a [bug in gmnhg][bug] which prevents it from
|
||||
> stripping HTML in certain scenarios. HTML is noticeably still present
|
||||
> inside <span>blockquotes</span>.
|
||||
|
||||
***
|
||||
|
||||
The Markdown horizontal line above is rendered as triple dashes.
|
||||
|
||||
[bug]: https://github.com/tdemin/gmnhg/issues/6
|
69
testdata/links.gmi
vendored
Normal file
69
testdata/links.gmi
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Links
|
||||
|
||||
gmnhg supports links, images, and footnotes. These are extracted from paragraphs and other block elements recursively.
|
||||
|
||||
As there's no inline links in Gemtext, gmnhg instead renders links blocks after paragraphs. Links blocks are sorted by type: first footnotes, then images, then links, blocks of a distinct type separated by a single newline.
|
||||
|
||||
## Inline links & images
|
||||
|
||||
For inline Markdown links, the text inside the square brackets is used as link title: for instance, the link to Gemini specification along with a link to the current CommonMark spec will generate a block of two links.
|
||||
|
||||
=> https://gemini.circumlunar.space/docs/specification.gmi Gemini specification
|
||||
=> https://spec.commonmark.org/0.30/ CommonMark spec
|
||||
|
||||
gomarkdown works with reference-style links as well. Unused reference links are ignored.
|
||||
|
||||
=> https://github.com/gomarkdown/markdown gomarkdown
|
||||
|
||||
xkcd #1853 serves quite well as an inline image example.
|
||||
|
||||
=> https://imgs.xkcd.com/comics/once_per_day.png xkcd #1853
|
||||
|
||||
Other container elements can contain inline links as well. For instance, this is an example of a link inside a blockquote:
|
||||
|
||||
> OTR has significant usability drawbacks for inter-client mobility.
|
||||
> — XEP-0384
|
||||
|
||||
=> https://xmpp.org/extensions/xep-0384.html XEP-0384
|
||||
|
||||
## Footnotes
|
||||
|
||||
gmnhg supports footnotes, written like this[^1]. Footnotes can use any references, including alphanumeric ones[^2]; alphanumeric references will be replaced with numeric IDs on render.
|
||||
|
||||
[^1]: Footnotes can only consist of a single source line due to a quirk of gomarkdown.
|
||||
[^2]: Footnotes can contain any kind of inline **formatting** paragraphs do. For instance, this is a link to GitHub.
|
||||
|
||||
=> https://github.com GitHub
|
||||
|
||||
This line looks like it would belong to footnote 1, but it actually doesn't, and is therefore treated as a new paragraph.
|
||||
|
||||
## Lists of links
|
||||
|
||||
gmnhg additionally supports a special kind of lists: lists consisting solely of links. For these, content rendering will be skipped entirely, and a links block will be rendered instead.
|
||||
|
||||
### Markdown lists
|
||||
|
||||
Links-only lists can be of any type, but they can only be of level 1. The two lists below will get rendered as links blocks:
|
||||
|
||||
=> https://gemini.circumlunar.space/docs/specification.gmi Gemini specification
|
||||
=> https://github.com/tdemin/gmnhg gmnhg
|
||||
|
||||
=> https://gemini.circumlunar.space/docs/best-practices.gmi Best practices for Gemini implementers
|
||||
=> https://gemini.circumlunar.space/docs/faq.gmi Project Gemini FAQ
|
||||
|
||||
The list below contains other meaningful text in its items, and will get rendered as a regular list:
|
||||
|
||||
* Gemini specification is a must-read for a Gemini developer.
|
||||
|
||||
=> https://gemini.circumlunar.space/docs/specification.gmi Gemini specification
|
||||
|
||||
### Series of links
|
||||
|
||||
A series of inline links in a single paragraph, if the paragraph contains no extra meaningful symbols (aside from spaces and newlines), will also get rendered as a single links block:
|
||||
|
||||
=> https://github.com/tdemin/gmnhg gmnhg
|
||||
=> https://gemini.circumlunar.space/docs/specification.gmi Gemini specification
|
||||
|
||||
This also works for single-link paragraphs:
|
||||
|
||||
=> https://gemini.circumlunar.space/docs/specification.gmi Gemini specification
|
80
testdata/links.md
vendored
Normal file
80
testdata/links.md
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
# Links
|
||||
|
||||
gmnhg supports links, images, and footnotes. These are extracted from
|
||||
paragraphs and other block elements recursively.
|
||||
|
||||
As there's no inline links in Gemtext, gmnhg instead renders links
|
||||
blocks after paragraphs. Links blocks are sorted by type: first
|
||||
footnotes, then images, then links, blocks of a distinct type separated
|
||||
by a single newline.
|
||||
|
||||
## Inline links & images
|
||||
|
||||
For inline Markdown links, the text inside the square brackets is used
|
||||
as link title: for instance, the link to [Gemini specification][gemspec]
|
||||
along with a link to the current [CommonMark spec][cmark] will generate
|
||||
a block of two links.
|
||||
|
||||
[gemspec]: https://gemini.circumlunar.space/docs/specification.gmi "This alt text will never be printed, as there's no tools in Gemtext for that"
|
||||
[cmark]: https://spec.commonmark.org/0.30/
|
||||
|
||||
[gomarkdown](https://github.com/gomarkdown/markdown) works with
|
||||
reference-style links as well. Unused reference links are ignored.
|
||||
|
||||
[gmnhg]: https://github.com/tdemin/gmnhg "This link will get entirely ignored"
|
||||
|
||||
![xkcd #1853](https://imgs.xkcd.com/comics/once_per_day.png) serves
|
||||
quite well as an inline image example.
|
||||
|
||||
Other container elements can contain inline links as well. For instance,
|
||||
this is an example of a link inside a blockquote:
|
||||
|
||||
> OTR has significant usability drawbacks for inter-client mobility.
|
||||
> — [XEP-0384](https://xmpp.org/extensions/xep-0384.html)
|
||||
|
||||
## Footnotes
|
||||
|
||||
gmnhg supports footnotes, written like this[^1]. Footnotes can use any
|
||||
references, including alphanumeric ones[^foo]; alphanumeric references
|
||||
will be replaced with numeric IDs on render.
|
||||
|
||||
[^1]: Footnotes can only consist of a single source line due to a quirk of gomarkdown.
|
||||
This line looks like it would belong to footnote 1, but it actually
|
||||
doesn't, and is therefore treated as a new paragraph.
|
||||
|
||||
[^foo]: Footnotes can contain any kind of inline **formatting** paragraphs do. For instance, this is a link to [GitHub](https://github.com).
|
||||
|
||||
## Lists of links
|
||||
|
||||
gmnhg additionally supports a special kind of lists: lists consisting
|
||||
solely of links. For these, content rendering will be skipped entirely,
|
||||
and a links block will be rendered instead.
|
||||
|
||||
### Markdown lists
|
||||
|
||||
Links-only lists can be of any type, but they can only be of level 1.
|
||||
The two lists below will get rendered as links blocks:
|
||||
|
||||
* [Gemini specification][gemspec]
|
||||
* [gmnhg](https://github.com/tdemin/gmnhg)
|
||||
|
||||
1. [Best practices for Gemini implementers](https://gemini.circumlunar.space/docs/best-practices.gmi)
|
||||
2. [Project Gemini FAQ](https://gemini.circumlunar.space/docs/faq.gmi)
|
||||
|
||||
The list below contains other meaningful text in its items, and will get
|
||||
rendered as a regular list:
|
||||
|
||||
* [Gemini specification][gemspec] is a must-read for a Gemini developer.
|
||||
|
||||
### Series of links
|
||||
|
||||
A series of inline links in a single paragraph, if the paragraph
|
||||
contains no extra meaningful symbols (aside from spaces and newlines),
|
||||
will also get rendered as a single links block:
|
||||
|
||||
[gmnhg](https://github.com/tdemin/gmnhg)
|
||||
[Gemini specification][gemspec]
|
||||
|
||||
This also works for single-link paragraphs:
|
||||
|
||||
[Gemini specification][gemspec]
|
47
testdata/lists.gmi
vendored
Normal file
47
testdata/lists.gmi
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Lists
|
||||
|
||||
Definition lists, numbered and ordered lists are all supported in gmnhg.
|
||||
|
||||
## Definition lists
|
||||
|
||||
The lists of definitions get converted into regular unordered lists, prefixed with a star (`*`) as specified by Gemini spec p. 5.5.2.
|
||||
|
||||
gmnhg
|
||||
* a program to generate a Gemini site from an existing Hugo site
|
||||
* a library converting Markdown to Gemtext, based on gomarkdown
|
||||
|
||||
md2gmn
|
||||
* a program to convert Markdown to Gemtext
|
||||
* a wrapper to the gmnhg library
|
||||
|
||||
## Normal lists
|
||||
|
||||
* This is the first item of an unordered list.
|
||||
* This is its second item.
|
||||
* This is a list item that was using the `+` sign. Gemini readers should see this item as the continuation of the previous list.
|
||||
|
||||
1. This is an ordered list first item.
|
||||
2. This is the second item.
|
||||
|
||||
## Lists containing a sub-list
|
||||
|
||||
As there's no indented list line type in Gemtext, gmnhg will indent these with tabs. The tabs number is equivalent to list level minus one (e.g. single tab for second list level).
|
||||
|
||||
Unordered lists can be children of ordered lists, and vice versa.
|
||||
|
||||
* This item contains a child ordered list.
|
||||
1. This ordered list item should get picked up as regular text.
|
||||
2. Whether or not this looks nicely depends on the client.
|
||||
* This item contains a child definition list.
|
||||
Markdown
|
||||
* an overly complex text markup format invented in 2004 whose sole specification of CommonMark lacks both tables and footnotes
|
||||
* a text format that has zero parsers completely compatible between each other.
|
||||
|
||||
1. This item contains a child unordered list.
|
||||
* This whole list should get treated as plain text by clients.
|
||||
|
||||
## Links of lists
|
||||
|
||||
A special case of lists consisting solely of links to something is documented in the links test document.
|
||||
|
||||
=> links.md links test document
|
53
testdata/lists.md
vendored
Normal file
53
testdata/lists.md
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
# Lists
|
||||
|
||||
Definition lists, numbered and ordered lists are all supported in gmnhg.
|
||||
|
||||
## Definition lists
|
||||
|
||||
The lists of definitions get converted into regular unordered lists,
|
||||
prefixed with a star (`*`) as specified by Gemini spec p. 5.5.2.
|
||||
|
||||
gmnhg
|
||||
: a program to generate a Gemini site from an existing Hugo site
|
||||
: a library converting Markdown to Gemtext, based on gomarkdown
|
||||
|
||||
md2gmn
|
||||
: a program to convert Markdown to Gemtext
|
||||
: a wrapper to the gmnhg library
|
||||
|
||||
## Normal lists
|
||||
|
||||
* This is the first item of an unordered list.
|
||||
* This is its second item.
|
||||
|
||||
+ This is a list item that was using the `+` sign. Gemini readers should
|
||||
see this item as the continuation of the previous list.
|
||||
|
||||
1. This is an ordered list first item.
|
||||
2. This is the second item.
|
||||
|
||||
## Lists containing a sub-list
|
||||
|
||||
As there's no indented list line type in Gemtext, gmnhg will indent
|
||||
these with tabs. The tabs number is equivalent to list level minus one
|
||||
(e.g. single tab for second list level).
|
||||
|
||||
Unordered lists can be children of ordered lists, and vice versa.
|
||||
|
||||
* This item contains a child ordered list.
|
||||
1. This ordered list item should get picked up as regular text.
|
||||
2. Whether or not this looks nicely depends on the client.
|
||||
* This item contains a child definition list.
|
||||
Markdown
|
||||
: an overly complex text markup format invented in 2004 whose sole
|
||||
specification of CommonMark lacks both tables and footnotes
|
||||
: a text format that has zero parsers completely compatible between
|
||||
each other.
|
||||
|
||||
1. This item contains a child unordered list.
|
||||
* This whole list should get treated as plain text by clients.
|
||||
|
||||
## Links of lists
|
||||
|
||||
A special case of lists consisting solely of links to something is
|
||||
documented in the [links test document](links.md).
|
52
testdata/tables.gmi
vendored
Normal file
52
testdata/tables.gmi
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
# Tables
|
||||
|
||||
gmnhg uses preformatted text blocks to render ASCII text tables.
|
||||
|
||||
## Simple table example
|
||||
|
||||
```
|
||||
+-----------+-------------+
|
||||
| Syntax | Description |
|
||||
+-----------+-------------+
|
||||
| Header | Title |
|
||||
| Paragraph | Text |
|
||||
+-----------+-------------+
|
||||
```
|
||||
|
||||
## Empty rows or cells
|
||||
|
||||
These are picked up as well.
|
||||
|
||||
```
|
||||
+-------+------+
|
||||
| test | nice |
|
||||
+-------+------+
|
||||
| `est` | |
|
||||
+-------+------+
|
||||
```
|
||||
|
||||
```
|
||||
+------+------+
|
||||
| test | nice |
|
||||
+------+------+
|
||||
| | |
|
||||
+------+------+
|
||||
```
|
||||
|
||||
## Formatting inside tables
|
||||
|
||||
Text formatting is fully supported inside tables. Links will also get picked up, and a links block will appear after the parent table if needed.
|
||||
|
||||
```
|
||||
+----------+----------+--------------+
|
||||
| Header 1 | Header 2 | Header 3[^1] |
|
||||
+----------+----------+--------------+
|
||||
| Item 1 | Item 2 | Item 3 |
|
||||
| Item 1a | Item 2a | Item 3a |
|
||||
+----------+----------+--------------+
|
||||
```
|
||||
|
||||
[^1]: Example footnote that explains header 3.
|
||||
|
||||
=> https://example.tld Header 2
|
||||
=> https://www.example.com Item 2
|
35
testdata/tables.md
vendored
Normal file
35
testdata/tables.md
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Tables
|
||||
|
||||
gmnhg uses preformatted text blocks to render ASCII text tables.
|
||||
|
||||
## Simple table example
|
||||
|
||||
| Syntax | Description |
|
||||
| ----------- | ----------- |
|
||||
| Header | Title |
|
||||
| Paragraph | Text |
|
||||
|
||||
## Empty rows or cells
|
||||
|
||||
These are picked up as well.
|
||||
|
||||
| test | nice |
|
||||
|------|------|
|
||||
| `est` | |
|
||||
|
||||
| test | nice |
|
||||
|------|------|
|
||||
| | |
|
||||
|
||||
## Formatting inside tables
|
||||
|
||||
Text formatting is fully supported inside tables. Links will also get
|
||||
picked up, and a links block will appear after the parent table if
|
||||
needed.
|
||||
|
||||
| Header 1 | [Header 2](https://example.tld) | Header 3[^foo] |
|
||||
|----------|----------|----------|
|
||||
| Item 1 | [Item 2](https://www.example.com) | Item 3 |
|
||||
| Item 1a | Item 2a | Item 3a |
|
||||
|
||||
[^foo]: Example footnote that explains header 3.
|
Loading…
Reference in a new issue