Skip to content

Commit dcd3834

Browse files
sunspirit99leohhhn
andauthored
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]>
1 parent ce6a4aa commit dcd3834

File tree

11 files changed

+815
-0
lines changed

11 files changed

+815
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module gno.land/p/sunspirit/md
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package md
2+
3+
import (
4+
"strings"
5+
6+
"gno.land/p/demo/ufmt"
7+
)
8+
9+
// Builder helps to build a Markdown string from individual elements
10+
type Builder struct {
11+
elements []string
12+
}
13+
14+
// NewBuilder creates a new Builder instance
15+
func NewBuilder() *Builder {
16+
return &Builder{}
17+
}
18+
19+
// Add adds a Markdown element to the builder
20+
func (m *Builder) Add(md ...string) *Builder {
21+
m.elements = append(m.elements, md...)
22+
return m
23+
}
24+
25+
// Render returns the final Markdown string joined with the specified separator
26+
func (m *Builder) Render(separator string) string {
27+
return strings.Join(m.elements, separator)
28+
}
29+
30+
// Bold returns bold text for markdown
31+
func Bold(text string) string {
32+
return ufmt.Sprintf("**%s**", text)
33+
}
34+
35+
// Italic returns italicized text for markdown
36+
func Italic(text string) string {
37+
return ufmt.Sprintf("*%s*", text)
38+
}
39+
40+
// Strikethrough returns strikethrough text for markdown
41+
func Strikethrough(text string) string {
42+
return ufmt.Sprintf("~~%s~~", text)
43+
}
44+
45+
// H1 returns a level 1 header for markdown
46+
func H1(text string) string {
47+
return ufmt.Sprintf("# %s\n", text)
48+
}
49+
50+
// H2 returns a level 2 header for markdown
51+
func H2(text string) string {
52+
return ufmt.Sprintf("## %s\n", text)
53+
}
54+
55+
// H3 returns a level 3 header for markdown
56+
func H3(text string) string {
57+
return ufmt.Sprintf("### %s\n", text)
58+
}
59+
60+
// H4 returns a level 4 header for markdown
61+
func H4(text string) string {
62+
return ufmt.Sprintf("#### %s\n", text)
63+
}
64+
65+
// H5 returns a level 5 header for markdown
66+
func H5(text string) string {
67+
return ufmt.Sprintf("##### %s\n", text)
68+
}
69+
70+
// H6 returns a level 6 header for markdown
71+
func H6(text string) string {
72+
return ufmt.Sprintf("###### %s\n", text)
73+
}
74+
75+
// BulletList returns an bullet list for markdown
76+
func BulletList(items []string) string {
77+
var sb strings.Builder
78+
for _, item := range items {
79+
sb.WriteString(ufmt.Sprintf("- %s\n", item))
80+
}
81+
return sb.String()
82+
}
83+
84+
// OrderedList returns an ordered list for markdown
85+
func OrderedList(items []string) string {
86+
var sb strings.Builder
87+
for i, item := range items {
88+
sb.WriteString(ufmt.Sprintf("%d. %s\n", i+1, item))
89+
}
90+
return sb.String()
91+
}
92+
93+
// TodoList returns a list of todo items with checkboxes for markdown
94+
func TodoList(items []string, done []bool) string {
95+
var sb strings.Builder
96+
97+
for i, item := range items {
98+
checkbox := " "
99+
if done[i] {
100+
checkbox = "x"
101+
}
102+
sb.WriteString(ufmt.Sprintf("- [%s] %s\n", checkbox, item))
103+
}
104+
return sb.String()
105+
}
106+
107+
// Blockquote returns a blockquote for markdown
108+
func Blockquote(text string) string {
109+
lines := strings.Split(text, "\n")
110+
var sb strings.Builder
111+
for _, line := range lines {
112+
sb.WriteString(ufmt.Sprintf("> %s\n", line))
113+
}
114+
115+
return sb.String()
116+
}
117+
118+
// InlineCode returns inline code for markdown
119+
func InlineCode(code string) string {
120+
return ufmt.Sprintf("`%s`", code)
121+
}
122+
123+
// CodeBlock creates a markdown code block
124+
func CodeBlock(content string) string {
125+
return ufmt.Sprintf("```\n%s\n```", content)
126+
}
127+
128+
// LanguageCodeBlock creates a markdown code block with language-specific syntax highlighting
129+
func LanguageCodeBlock(language, content string) string {
130+
return ufmt.Sprintf("```%s\n%s\n```", language, content)
131+
}
132+
133+
// LineBreak returns the specified number of line breaks for markdown
134+
func LineBreak(count uint) string {
135+
if count > 0 {
136+
return strings.Repeat("\n", int(count)+1)
137+
}
138+
return ""
139+
}
140+
141+
// HorizontalRule returns a horizontal rule for markdown
142+
func HorizontalRule() string {
143+
return "---\n"
144+
}
145+
146+
// Link returns a hyperlink for markdown
147+
func Link(text, url string) string {
148+
return ufmt.Sprintf("[%s](%s)", text, url)
149+
}
150+
151+
// Image returns an image for markdown
152+
func Image(altText, url string) string {
153+
return ufmt.Sprintf("![%s](%s)", altText, url)
154+
}
155+
156+
// Footnote returns a footnote for markdown
157+
func Footnote(reference, text string) string {
158+
return ufmt.Sprintf("[%s]: %s", reference, text)
159+
}
160+
161+
// Paragraph wraps the given text in a Markdown paragraph
162+
func Paragraph(content string) string {
163+
return ufmt.Sprintf("%s\n", content)
164+
}
165+
166+
// MdTable is an interface for table types that can be converted to Markdown format
167+
type MdTable interface {
168+
String() string
169+
}
170+
171+
// Table takes any MdTable implementation and returns its markdown representation
172+
func Table(table MdTable) string {
173+
return table.String()
174+
}
175+
176+
// EscapeMarkdown escapes special markdown characters in a string
177+
func EscapeMarkdown(text string) string {
178+
return ufmt.Sprintf("``%s``", text)
179+
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package md
2+
3+
import (
4+
"testing"
5+
6+
"gno.land/p/demo/uassert"
7+
"gno.land/p/sunspirit/table"
8+
)
9+
10+
func TestNewBuilder(t *testing.T) {
11+
mdBuilder := NewBuilder()
12+
13+
uassert.Equal(t, len(mdBuilder.elements), 0, "Expected 0 elements")
14+
}
15+
16+
func TestAdd(t *testing.T) {
17+
mdBuilder := NewBuilder()
18+
19+
header := H1("Hi")
20+
body := Paragraph("This is a test")
21+
22+
mdBuilder.Add(header, body)
23+
24+
uassert.Equal(t, len(mdBuilder.elements), 2, "Expected 2 element")
25+
uassert.Equal(t, mdBuilder.elements[0], header, "Expected element %s, got %s", header, mdBuilder.elements[0])
26+
uassert.Equal(t, mdBuilder.elements[1], body, "Expected element %s, got %s", body, mdBuilder.elements[1])
27+
}
28+
29+
func TestRender(t *testing.T) {
30+
mdBuilder := NewBuilder()
31+
32+
header := H1("Hello")
33+
body := Paragraph("This is a test")
34+
35+
seperator := "\n"
36+
expected := header + seperator + body
37+
38+
output := mdBuilder.Add(header, body).Render(seperator)
39+
40+
uassert.Equal(t, output, expected, "Expected rendered string %s, got %s", expected, output)
41+
}
42+
43+
func Test_Bold(t *testing.T) {
44+
uassert.Equal(t, Bold("Hello"), "**Hello**")
45+
}
46+
47+
func Test_Italic(t *testing.T) {
48+
uassert.Equal(t, Italic("Hello"), "*Hello*")
49+
}
50+
51+
func Test_Strikethrough(t *testing.T) {
52+
uassert.Equal(t, Strikethrough("Hello"), "~~Hello~~")
53+
}
54+
55+
func Test_H1(t *testing.T) {
56+
uassert.Equal(t, H1("Header 1"), "# Header 1\n")
57+
}
58+
59+
func Test_H2(t *testing.T) {
60+
uassert.Equal(t, H2("Header 2"), "## Header 2\n")
61+
}
62+
63+
func Test_H3(t *testing.T) {
64+
uassert.Equal(t, H3("Header 3"), "### Header 3\n")
65+
}
66+
67+
func Test_H4(t *testing.T) {
68+
uassert.Equal(t, H4("Header 4"), "#### Header 4\n")
69+
}
70+
71+
func Test_H5(t *testing.T) {
72+
uassert.Equal(t, H5("Header 5"), "##### Header 5\n")
73+
}
74+
75+
func Test_H6(t *testing.T) {
76+
uassert.Equal(t, H6("Header 6"), "###### Header 6\n")
77+
}
78+
79+
func Test_BulletList(t *testing.T) {
80+
items := []string{"Item 1", "Item 2", "Item 3"}
81+
result := BulletList(items)
82+
expected := "- Item 1\n- Item 2\n- Item 3\n"
83+
uassert.Equal(t, result, expected)
84+
}
85+
86+
func Test_OrderedList(t *testing.T) {
87+
items := []string{"Item 1", "Item 2", "Item 3"}
88+
result := OrderedList(items)
89+
expected := "1. Item 1\n2. Item 2\n3. Item 3\n"
90+
uassert.Equal(t, result, expected)
91+
}
92+
93+
func Test_TodoList(t *testing.T) {
94+
items := []string{"Task 1", "Task 2"}
95+
done := []bool{true, false}
96+
result := TodoList(items, done)
97+
expected := "- [x] Task 1\n- [ ] Task 2\n"
98+
uassert.Equal(t, result, expected)
99+
}
100+
101+
func Test_Blockquote(t *testing.T) {
102+
text := "This is a blockquote.\nIt has multiple lines."
103+
result := Blockquote(text)
104+
expected := "> This is a blockquote.\n> It has multiple lines.\n"
105+
uassert.Equal(t, result, expected)
106+
}
107+
108+
func Test_InlineCode(t *testing.T) {
109+
result := InlineCode("code")
110+
uassert.Equal(t, result, "`code`")
111+
}
112+
113+
func Test_LanguageCodeBlock(t *testing.T) {
114+
result := LanguageCodeBlock("python", "print('Hello')")
115+
expected := "```python\nprint('Hello')\n```"
116+
uassert.Equal(t, result, expected)
117+
}
118+
119+
func Test_CodeBlock(t *testing.T) {
120+
result := CodeBlock("print('Hello')")
121+
expected := "```\nprint('Hello')\n```"
122+
uassert.Equal(t, result, expected)
123+
}
124+
125+
func Test_LineBreak(t *testing.T) {
126+
result := LineBreak(2)
127+
expected := "\n\n\n"
128+
uassert.Equal(t, result, expected)
129+
130+
result = LineBreak(0)
131+
expected = ""
132+
uassert.Equal(t, result, expected)
133+
}
134+
135+
func Test_HorizontalRule(t *testing.T) {
136+
result := HorizontalRule()
137+
uassert.Equal(t, result, "---\n")
138+
}
139+
140+
func Test_Link(t *testing.T) {
141+
result := Link("Google", "http://google.com")
142+
uassert.Equal(t, result, "[Google](http://google.com)")
143+
}
144+
145+
func Test_Image(t *testing.T) {
146+
result := Image("Alt text", "http://image.url")
147+
uassert.Equal(t, result, "![Alt text](http://image.url)")
148+
}
149+
150+
func Test_Footnote(t *testing.T) {
151+
result := Footnote("1", "This is a footnote.")
152+
uassert.Equal(t, result, "[1]: This is a footnote.")
153+
}
154+
155+
func Test_Paragraph(t *testing.T) {
156+
result := Paragraph("This is a paragraph.")
157+
uassert.Equal(t, result, "This is a paragraph.\n")
158+
}
159+
160+
func Test_Table(t *testing.T) {
161+
tb, err := table.New([]string{"Header1", "Header2"}, [][]string{
162+
{"Row1Col1", "Row1Col2"},
163+
{"Row2Col1", "Row2Col2"},
164+
})
165+
uassert.NoError(t, err)
166+
167+
result := Table(tb)
168+
expected := "| Header1 | Header2 |\n| ---|---|\n| Row1Col1 | Row1Col2 |\n| Row2Col1 | Row2Col2 |\n"
169+
uassert.Equal(t, result, expected)
170+
}
171+
172+
func Test_EscapeMarkdown(t *testing.T) {
173+
result := EscapeMarkdown("- This is `code`")
174+
uassert.Equal(t, result, "``- This is `code```")
175+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module gno.land/p/sunspirit/table

0 commit comments

Comments
 (0)