Browse Source

Added markdown (github-flavored) rendering support

Benton Edmondson 2 years ago
parent
commit
003cb61cfd
11 changed files with 111 additions and 12 deletions
  1. 1 1
      gemtext/gemtext.go
  2. 4 1
      go.mod
  3. 2 0
      go.sum
  4. 10 7
      hypertext/hypertext.go
  5. 33 0
      hypertext/hypertext_test.go
  6. 2 0
      kinds/post.go
  7. 24 0
      markdown/markdown.go
  8. 26 0
      markdown/markdown_test.go
  9. 2 0
      notes.md
  10. 4 3
      plaintext/plaintext.go
  11. 3 0
      render/render.go

+ 1 - 1
gemtext/gemtext.go

@@ -81,7 +81,7 @@ func Render(text string) (string, error) {
 		result += style.CodeBlock(strings.TrimSuffix(preformattedBuffer, "\n")) + "\n"
 	}
 
-	return strings.Trim(result, " \t\r\n"), nil
+	return strings.TrimSpace(result), nil
 }
 
 func renderLink(text string) (string, error) {

+ 4 - 1
go.mod

@@ -2,4 +2,7 @@ module mimicry
 
 go 1.19
 
-require golang.org/x/net v0.5.0 // indirect
+require (
+	github.com/yuin/goldmark v1.5.4 // indirect
+	golang.org/x/net v0.5.0 // indirect
+)

+ 2 - 0
go.sum

@@ -1,2 +1,4 @@
+github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
+github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=

+ 10 - 7
hypertext/hypertext.go

@@ -9,10 +9,6 @@ import (
 	"errors"
 )
 
-// func Render(text string) string {
-// 	return "nothing"
-// }
-
 /* Terminal codes and control characters should already be escaped
    by this point */
 func Render(text string) (string, error) {
@@ -29,7 +25,7 @@ func Render(text string) (string, error) {
 		return "", err
 	}
 
-	return strings.Trim(serialized, " \n"), nil
+	return strings.TrimSpace(serialized), nil
 }
 
 func serializeList(nodes []*html.Node) (string, error) {
@@ -127,7 +123,7 @@ func renderNode(node *html.Node, preserveWhitespace bool) (string, error) {
 		return block(style.QuoteBlock(content)), nil
 	case "ul":
 		list, err := bulletedList(node, preserveWhitespace)
-		return block(list), err
+		return list, err
 	// case "ul":
 	// 	return numberedList(node), nil
 
@@ -196,7 +192,14 @@ func bulletedList(node *html.Node, preserveWhitespace bool) (string, error) {
 		}
 		output += "\n" + style.Bullet(result)
 	}
-	return block(output), nil
+
+	if node.Parent == nil {
+		return block(output), nil
+	} else if node.Parent.Data == "li" {
+		return output, nil
+	} else {
+		return block(output), nil
+	}
 }
 
 func getAttribute(name string, attributes []html.Attribute) string {

+ 33 - 0
hypertext/hypertext_test.go

@@ -117,3 +117,36 @@ func TestNestedBlocks(t *testing.T) {
 ` + style.LinkBlock("https://i.snap.as/P8qpdMbM.jpg")
 	util.AssertEqual(expected, output, t)
 }
+
+func TestAdjacentLists(t *testing.T) {
+	input := `<ul><li>top list</li></ul><ul><li>bottom list</li></ul>`
+	output, err := Render(input)	
+	if err != nil {
+		panic(err)
+	}
+	expected := style.Bullet("top list") + "\n\n" +
+		style.Bullet("bottom list")
+	util.AssertEqual(expected, output, t)
+}
+
+func TestNestedLists(t *testing.T) {
+	input := `<ul><li>top list<ul><li>nested</li></ul></li></ul>`
+	output, err := Render(input)	
+	if err != nil {
+		panic(err)
+	}
+	expected := style.Bullet("top list\n" + style.Bullet("nested"))
+		
+	util.AssertEqual(expected, output, t)
+}
+
+func TestBlockInList(t *testing.T) {
+	input := `<ul><li>top list<p><ul><li>paragraph</li></ul></p></li></ul>`
+	output, err := Render(input)	
+	if err != nil {
+		panic(err)
+	}
+	expected := style.Bullet("top list\n\n" + style.Bullet("paragraph"))
+		
+	util.AssertEqual(expected, output, t)
+}

+ 2 - 0
kinds/post.go

@@ -106,6 +106,8 @@ func (p Post) String() (string, error) {
 	if body, err := p.Body(); err == nil {
 		output += body
 		output += "\n"
+	} else {
+		return "", err
 	}
 
 	if created, err := p.Created(); err == nil {

+ 24 - 0
markdown/markdown.go

@@ -0,0 +1,24 @@
+package markdown
+
+import (
+    "bytes"
+    "github.com/yuin/goldmark"
+	"github.com/yuin/goldmark/extension"
+	"mimicry/hypertext"
+	"strings"
+)
+
+func Render(text string) (string, error) {
+	renderer := goldmark.New(goldmark.WithExtensions(extension.GFM))
+
+	var buf bytes.Buffer
+	if err := renderer.Convert([]byte(text), &buf); err != nil {
+		return "", nil
+	}
+	output := buf.String()
+	rendered, err := hypertext.Render(output)
+	if err != nil {
+		return "", err
+	}
+	return strings.TrimSpace(rendered), nil
+}

+ 26 - 0
markdown/markdown_test.go

@@ -0,0 +1,26 @@
+package markdown
+
+import (
+	"mimicry/util"
+	"testing"
+	"mimicry/style"
+)
+
+func TestBasic(t *testing.T) {
+	input := `[Here's a link!](https://wikipedia.org)
+
+![This is a beautiful image!](https://upload.wikimedia.org/wikipedia/commons/thumb/c/cb/Francesco_Melzi_-_Portrait_of_Leonardo.png/800px-Francesco_Melzi_-_Portrait_of_Leonardo.png)
+
+* Nested list
+  * Nesting`
+	output, err := Render(input)
+	if err != nil {
+		panic(err)
+	}
+
+	expected := style.Link("Here's a link!") + "\n\n" +
+		style.LinkBlock("This is a beautiful image!") + "\n\n" +
+		style.Bullet("Nested list\n" + style.Bullet("Nesting"))
+
+	util.AssertEqual(expected, output, t)
+}

+ 2 - 0
notes.md

@@ -56,6 +56,8 @@ In the ActivityPub JSON, assume media type has no parameters and no whitespace.
 
 Document the reasoning for treating everything as JSON instead of JSON-LD.
 
+Convert all tabs to spaces and reconfigure VSCode to not use tabs for golang.
+
 ## Minimal HTTPS
 
 After learning HTTP it feels like HTTP1.0 and HTTP3 have good niches. HTTP1.0 is super simple, `Connection: close` by default, so it is delimited by TCP close (which is fine for JSON). It has no chunking, so that isn't a problem. One TCP connection per request. On the other hand, HTTP3 is a binary protocol. (Amazing to think that the entire Web has been run off of a text-based protocol. No wonder everything breaks all the time.) Also, HTTP3 itself seems relatively lightweight because it looks like lower-level stuff is in QUIC instead of jammed in the HTTP headers.

+ 4 - 3
plaintext/plaintext.go

@@ -3,6 +3,7 @@ package plaintext
 import (
 	"regexp"
 	"mimicry/style"
+	"strings"
 )
 
 func Render(text string) (string, error) {
@@ -15,8 +16,8 @@ func Render(text string) (string, error) {
 			<hierarchy> is any of the characters listed in Appendix A:
 				A-Z a-z 0-9 - . ? # / @ : [ ] % _ ~ ! $ & ' ( ) * + , ; =
 	*/
-	
-	url := regexp.MustCompile(`[A-Za-z][A-Za-z0-9+\-.]*://[A-Za-z0-9.?#/@:%_~!$&'()*+,;=\[\]\-]+`)
 
-	return url.ReplaceAllStringFunc(text, style.Link), nil
+	url := regexp.MustCompile(`[A-Za-z][A-Za-z0-9+\-.]*://[A-Za-z0-9.?#/@:%_~!$&'()*+,;=\[\]\-]+`)
+	rendered := url.ReplaceAllStringFunc(text, style.Link)
+	return strings.TrimSpace(rendered), nil
 }

+ 3 - 0
render/render.go

@@ -5,6 +5,7 @@ import (
 	"mimicry/hypertext"
 	"mimicry/plaintext"
 	"mimicry/gemtext"
+	"mimicry/markdown"
 )
 
 func Render(text string, mediaType string) (string, error) {
@@ -15,6 +16,8 @@ func Render(text string, mediaType string) (string, error) {
 		return hypertext.Render(text)
 	case mediaType == "text/gemini":
 		return gemtext.Render(text)
+	case mediaType == "text/markdown":
+		return markdown.Render(text)
 	default:
 		return "", errors.New("Cannot render text of mime type " + mediaType)
 	}