Skip to content

Commit

Permalink
feat(examples): Implement markdown package (#2912)
Browse files Browse the repository at this point in the history
From #2753

I keep this PR open to see if I am on the right approach. If it's
suitable, I'll investigate to make further improvements and provide
implementation examples in the `Render()` functions of some current demo
realms to demonstrate the use of this package

![Screenshot from 2024-10-23
19-28-43](https://github.com/user-attachments/assets/a321f13a-01c7-432f-9f06-b02b5e86951a)


cc @moul 

<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [ ] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>

---------

Co-authored-by: Leon Hudak <[email protected]>
Co-authored-by: leohhhn <[email protected]>
  • Loading branch information
3 people authored Feb 6, 2025
1 parent ce6a4aa commit dcd3834
Show file tree
Hide file tree
Showing 11 changed files with 815 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/gno.land/p/sunspirit/md/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/sunspirit/md
179 changes: 179 additions & 0 deletions examples/gno.land/p/sunspirit/md/md.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package md

import (
"strings"

"gno.land/p/demo/ufmt"
)

// Builder helps to build a Markdown string from individual elements
type Builder struct {
elements []string
}

// NewBuilder creates a new Builder instance
func NewBuilder() *Builder {
return &Builder{}
}

// Add adds a Markdown element to the builder
func (m *Builder) Add(md ...string) *Builder {
m.elements = append(m.elements, md...)
return m
}

// Render returns the final Markdown string joined with the specified separator
func (m *Builder) Render(separator string) string {
return strings.Join(m.elements, separator)
}

// Bold returns bold text for markdown
func Bold(text string) string {
return ufmt.Sprintf("**%s**", text)
}

// Italic returns italicized text for markdown
func Italic(text string) string {
return ufmt.Sprintf("*%s*", text)
}

// Strikethrough returns strikethrough text for markdown
func Strikethrough(text string) string {
return ufmt.Sprintf("~~%s~~", text)
}

// H1 returns a level 1 header for markdown
func H1(text string) string {
return ufmt.Sprintf("# %s\n", text)
}

// H2 returns a level 2 header for markdown
func H2(text string) string {
return ufmt.Sprintf("## %s\n", text)
}

// H3 returns a level 3 header for markdown
func H3(text string) string {
return ufmt.Sprintf("### %s\n", text)
}

// H4 returns a level 4 header for markdown
func H4(text string) string {
return ufmt.Sprintf("#### %s\n", text)
}

// H5 returns a level 5 header for markdown
func H5(text string) string {
return ufmt.Sprintf("##### %s\n", text)
}

// H6 returns a level 6 header for markdown
func H6(text string) string {
return ufmt.Sprintf("###### %s\n", text)
}

// BulletList returns an bullet list for markdown
func BulletList(items []string) string {
var sb strings.Builder
for _, item := range items {
sb.WriteString(ufmt.Sprintf("- %s\n", item))
}
return sb.String()
}

// OrderedList returns an ordered list for markdown
func OrderedList(items []string) string {
var sb strings.Builder
for i, item := range items {
sb.WriteString(ufmt.Sprintf("%d. %s\n", i+1, item))
}
return sb.String()
}

// TodoList returns a list of todo items with checkboxes for markdown
func TodoList(items []string, done []bool) string {
var sb strings.Builder

for i, item := range items {
checkbox := " "
if done[i] {
checkbox = "x"
}
sb.WriteString(ufmt.Sprintf("- [%s] %s\n", checkbox, item))
}
return sb.String()
}

// Blockquote returns a blockquote for markdown
func Blockquote(text string) string {
lines := strings.Split(text, "\n")
var sb strings.Builder
for _, line := range lines {
sb.WriteString(ufmt.Sprintf("> %s\n", line))
}

return sb.String()
}

// InlineCode returns inline code for markdown
func InlineCode(code string) string {
return ufmt.Sprintf("`%s`", code)
}

// CodeBlock creates a markdown code block
func CodeBlock(content string) string {
return ufmt.Sprintf("```\n%s\n```", content)
}

// LanguageCodeBlock creates a markdown code block with language-specific syntax highlighting
func LanguageCodeBlock(language, content string) string {
return ufmt.Sprintf("```%s\n%s\n```", language, content)
}

// LineBreak returns the specified number of line breaks for markdown
func LineBreak(count uint) string {
if count > 0 {
return strings.Repeat("\n", int(count)+1)
}
return ""
}

// HorizontalRule returns a horizontal rule for markdown
func HorizontalRule() string {
return "---\n"
}

// Link returns a hyperlink for markdown
func Link(text, url string) string {
return ufmt.Sprintf("[%s](%s)", text, url)
}

// Image returns an image for markdown
func Image(altText, url string) string {
return ufmt.Sprintf("![%s](%s)", altText, url)
}

// Footnote returns a footnote for markdown
func Footnote(reference, text string) string {
return ufmt.Sprintf("[%s]: %s", reference, text)
}

// Paragraph wraps the given text in a Markdown paragraph
func Paragraph(content string) string {
return ufmt.Sprintf("%s\n", content)
}

// MdTable is an interface for table types that can be converted to Markdown format
type MdTable interface {
String() string
}

// Table takes any MdTable implementation and returns its markdown representation
func Table(table MdTable) string {
return table.String()
}

// EscapeMarkdown escapes special markdown characters in a string
func EscapeMarkdown(text string) string {
return ufmt.Sprintf("``%s``", text)
}
175 changes: 175 additions & 0 deletions examples/gno.land/p/sunspirit/md/md_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package md

import (
"testing"

"gno.land/p/demo/uassert"
"gno.land/p/sunspirit/table"
)

func TestNewBuilder(t *testing.T) {
mdBuilder := NewBuilder()

uassert.Equal(t, len(mdBuilder.elements), 0, "Expected 0 elements")
}

func TestAdd(t *testing.T) {
mdBuilder := NewBuilder()

header := H1("Hi")
body := Paragraph("This is a test")

mdBuilder.Add(header, body)

uassert.Equal(t, len(mdBuilder.elements), 2, "Expected 2 element")
uassert.Equal(t, mdBuilder.elements[0], header, "Expected element %s, got %s", header, mdBuilder.elements[0])
uassert.Equal(t, mdBuilder.elements[1], body, "Expected element %s, got %s", body, mdBuilder.elements[1])
}

func TestRender(t *testing.T) {
mdBuilder := NewBuilder()

header := H1("Hello")
body := Paragraph("This is a test")

seperator := "\n"
expected := header + seperator + body

output := mdBuilder.Add(header, body).Render(seperator)

uassert.Equal(t, output, expected, "Expected rendered string %s, got %s", expected, output)
}

func Test_Bold(t *testing.T) {
uassert.Equal(t, Bold("Hello"), "**Hello**")
}

func Test_Italic(t *testing.T) {
uassert.Equal(t, Italic("Hello"), "*Hello*")
}

func Test_Strikethrough(t *testing.T) {
uassert.Equal(t, Strikethrough("Hello"), "~~Hello~~")
}

func Test_H1(t *testing.T) {
uassert.Equal(t, H1("Header 1"), "# Header 1\n")
}

func Test_H2(t *testing.T) {
uassert.Equal(t, H2("Header 2"), "## Header 2\n")
}

func Test_H3(t *testing.T) {
uassert.Equal(t, H3("Header 3"), "### Header 3\n")
}

func Test_H4(t *testing.T) {
uassert.Equal(t, H4("Header 4"), "#### Header 4\n")
}

func Test_H5(t *testing.T) {
uassert.Equal(t, H5("Header 5"), "##### Header 5\n")
}

func Test_H6(t *testing.T) {
uassert.Equal(t, H6("Header 6"), "###### Header 6\n")
}

func Test_BulletList(t *testing.T) {
items := []string{"Item 1", "Item 2", "Item 3"}
result := BulletList(items)
expected := "- Item 1\n- Item 2\n- Item 3\n"
uassert.Equal(t, result, expected)
}

func Test_OrderedList(t *testing.T) {
items := []string{"Item 1", "Item 2", "Item 3"}
result := OrderedList(items)
expected := "1. Item 1\n2. Item 2\n3. Item 3\n"
uassert.Equal(t, result, expected)
}

func Test_TodoList(t *testing.T) {
items := []string{"Task 1", "Task 2"}
done := []bool{true, false}
result := TodoList(items, done)
expected := "- [x] Task 1\n- [ ] Task 2\n"
uassert.Equal(t, result, expected)
}

func Test_Blockquote(t *testing.T) {
text := "This is a blockquote.\nIt has multiple lines."
result := Blockquote(text)
expected := "> This is a blockquote.\n> It has multiple lines.\n"
uassert.Equal(t, result, expected)
}

func Test_InlineCode(t *testing.T) {
result := InlineCode("code")
uassert.Equal(t, result, "`code`")
}

func Test_LanguageCodeBlock(t *testing.T) {
result := LanguageCodeBlock("python", "print('Hello')")
expected := "```python\nprint('Hello')\n```"
uassert.Equal(t, result, expected)
}

func Test_CodeBlock(t *testing.T) {
result := CodeBlock("print('Hello')")
expected := "```\nprint('Hello')\n```"
uassert.Equal(t, result, expected)
}

func Test_LineBreak(t *testing.T) {
result := LineBreak(2)
expected := "\n\n\n"
uassert.Equal(t, result, expected)

result = LineBreak(0)
expected = ""
uassert.Equal(t, result, expected)
}

func Test_HorizontalRule(t *testing.T) {
result := HorizontalRule()
uassert.Equal(t, result, "---\n")
}

func Test_Link(t *testing.T) {
result := Link("Google", "http://google.com")
uassert.Equal(t, result, "[Google](http://google.com)")
}

func Test_Image(t *testing.T) {
result := Image("Alt text", "http://image.url")
uassert.Equal(t, result, "![Alt text](http://image.url)")
}

func Test_Footnote(t *testing.T) {
result := Footnote("1", "This is a footnote.")
uassert.Equal(t, result, "[1]: This is a footnote.")
}

func Test_Paragraph(t *testing.T) {
result := Paragraph("This is a paragraph.")
uassert.Equal(t, result, "This is a paragraph.\n")
}

func Test_Table(t *testing.T) {
tb, err := table.New([]string{"Header1", "Header2"}, [][]string{
{"Row1Col1", "Row1Col2"},
{"Row2Col1", "Row2Col2"},
})
uassert.NoError(t, err)

result := Table(tb)
expected := "| Header1 | Header2 |\n| ---|---|\n| Row1Col1 | Row1Col2 |\n| Row2Col1 | Row2Col2 |\n"
uassert.Equal(t, result, expected)
}

func Test_EscapeMarkdown(t *testing.T) {
result := EscapeMarkdown("- This is `code`")
uassert.Equal(t, result, "``- This is `code```")
}
1 change: 1 addition & 0 deletions examples/gno.land/p/sunspirit/table/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/sunspirit/table
Loading

0 comments on commit dcd3834

Please sign in to comment.