Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gno.land): Implement markdown package #2912

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/gno.land/p/demo/markdown/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module gno.land/p/demo/markdown

require (
gno.land/p/demo/uassert v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/p/demo/urequire v0.0.0-latest
)
126 changes: 126 additions & 0 deletions examples/gno.land/p/demo/markdown/markdown.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package markdown

import (
"strings"

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

// 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", text)
}

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

// H3 returns a level 3 header for markdown
func H3(text string) string {
return ufmt.Sprintf("### %s", 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()
}

// TodoItem represents a single todo item
type TodoItem struct {
Item string
Done bool
}

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

for _, item := range items {
checkbox := " "
if item.Done {
checkbox = "x"
}
sb.WriteString(ufmt.Sprintf("- [%s] %s\n", checkbox, item.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 returns a code block for markdown, optionally specifying the language
func CodeBlock(code string, language string) string {
if language != "" {
return ufmt.Sprintf("```%s\n%s\n```", language, code)
}
return ufmt.Sprintf("```\n%s\n```", code)
}

// LineBreak returns the specified number of line breaks for markdown
func LineBreak(count uint) string {
if count > 0 {
return strings.Repeat("\n", int(count))
}
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)
}
118 changes: 118 additions & 0 deletions examples/gno.land/p/demo/markdown/markdown_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package markdown

import (
"testing"

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

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")
}

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

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

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 := []TodoItem{
{Item: "Task 1", Done: true},
{Item: "Task 2", Done: false},
}
result := TodoList(items)
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_CodeBlock(t *testing.T) {
result := CodeBlock("print('Hello')", "python")
expected := "```python\nprint('Hello')\n```"
uassert.Equal(t, result, expected)

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"
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_Table(t *testing.T) {
table, err := NewTable([]string{"Header1", "Header2"}, [][]string{
{"Row1Col1", "Row1Col2"},
{"Row2Col1", "Row2Col2"},
})
uassert.NoError(t, err)

expected := "| Header1 | Header2 |\n| ---|---|\n| Row1Col1 | Row1Col2 |\n| Row2Col1 | Row2Col2 |\n"
uassert.Equal(t, table.String(), expected)
}
106 changes: 106 additions & 0 deletions examples/gno.land/p/demo/markdown/table.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package markdown

import (
"strings"

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

// Table defines the structure for a markdown table
type Table struct {
header []string
rows [][]string
}

// ValidateColumns checks if the number of columns in each row matches the number of columns in the header
func (t *Table) ValidateColumns() error {
numCols := len(t.header)
for _, row := range t.rows {
if len(row) != numCols {
return ufmt.Errorf("row %v does not match header length %d", row, numCols)
}
}
return nil
}

// NewTable creates a new Table instance, ensuring the header and rows match in size
func NewTable(header []string, rows [][]string) (*Table, error) {
t := &Table{
header: header,
rows: rows,
}

if err := t.ValidateColumns(); err != nil {
return nil, err
}

return t, nil
}

// Table returns a markdown string for the given Table
func (t *Table) String() string {
if err := t.ValidateColumns(); err != nil {
panic(err)
}

var sb strings.Builder

sb.WriteString("| " + strings.Join(t.header, " | ") + " |\n")
sb.WriteString("| " + strings.Repeat("---|", len(t.header)) + "\n")

for _, row := range t.rows {
sb.WriteString("| " + strings.Join(row, " | ") + " |\n")
}

return sb.String()
}

// AddRow adds a new row to the table
func (t *Table) AddRow(row []string) error {
if len(row) != len(t.header) {
return ufmt.Errorf("row %v does not match header length %d", row, len(t.header))
}
t.rows = append(t.rows, row)
return nil
}

// AddColumn adds a new column to the table with the specified values
func (t *Table) AddColumn(header string, values []string) error {
if len(values) != len(t.rows) {
return ufmt.Errorf("values length %d does not match the number of rows %d", len(values), len(t.rows))
}

// Add the new header
t.header = append(t.header, header)

// Add the new column values to each row
for i, value := range values {
t.rows[i] = append(t.rows[i], value)
}
return nil
}

// RemoveRow removes a row from the table by its index
func (t *Table) RemoveRow(index int) error {
if index < 0 || index >= len(t.rows) {
return ufmt.Errorf("index %d is out of range", index)
}
t.rows = append(t.rows[:index], t.rows[index+1:]...)
return nil
}

// RemoveColumn removes a column from the table by its index
func (t *Table) RemoveColumn(index int) error {
if index < 0 || index >= len(t.header) {
return ufmt.Errorf("index %d is out of range", index)
}

// Remove the column from the header
t.header = append(t.header[:index], t.header[index+1:]...)

// Remove the corresponding column from each row
for i := range t.rows {
t.rows[i] = append(t.rows[i][:index], t.rows[i][index+1:]...)
}
return nil
}
Loading
Loading