-
Notifications
You must be signed in to change notification settings - Fork 393
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(examples): Implement markdown package (#2912)
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
1 parent
ce6a4aa
commit dcd3834
Showing
11 changed files
with
815 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/p/sunspirit/md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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```") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/p/sunspirit/table |
Oops, something went wrong.