diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.md b/.github/ISSUE_TEMPLATE/BUG-REPORT.md index 70a20a4c47e..a63b450d678 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.md +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.md @@ -1,6 +1,7 @@ --- name: Bug Report Template about: Create a bug report +labels: "🐞 bug" # NOTE: keep in sync with gnovm/cmd/gno/bug.go --- diff --git a/examples/gno.land/p/moul/mdtable/gno.mod b/examples/gno.land/p/moul/mdtable/gno.mod new file mode 100644 index 00000000000..0cea0458895 --- /dev/null +++ b/examples/gno.land/p/moul/mdtable/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/moul/mdtable + +require gno.land/p/demo/urequire v0.0.0-latest diff --git a/examples/gno.land/p/moul/mdtable/mdtable.gno b/examples/gno.land/p/moul/mdtable/mdtable.gno new file mode 100644 index 00000000000..13812bd973d --- /dev/null +++ b/examples/gno.land/p/moul/mdtable/mdtable.gno @@ -0,0 +1,66 @@ +// Package mdtable provides a simple way to create Markdown tables. +// +// Example usage: +// +// import "gno.land/p/moul/mdtable" +// +// func Render(path string) string { +// table := mdtable.Table{ +// Headers: []string{"ID", "Title", "Status", "Date"}, +// } +// table.Append([]string{"#1", "Add a new validator", "succeed", "2024-01-01"}) +// table.Append([]string{"#2", "Change parameter", "timed out", "2024-01-02"}) +// return table.String() +// } +// +// Output: +// +// | ID | Title | Status | Date | +// | --- | --- | --- | --- | +// | #1 | Add a new validator | succeed | 2024-01-01 | +// | #2 | Change parameter | timed out | 2024-01-02 | +package mdtable + +import ( + "strings" +) + +type Table struct { + Headers []string + Rows [][]string + // XXX: optional headers alignment. +} + +func (t *Table) Append(row []string) { + t.Rows = append(t.Rows, row) +} + +func (t Table) String() string { + // XXX: switch to using text/tabwriter when porting to Gno to support + // better-formatted raw Markdown output. + + if len(t.Headers) == 0 && len(t.Rows) == 0 { + return "" + } + + var sb strings.Builder + + if len(t.Headers) == 0 { + t.Headers = make([]string, len(t.Rows[0])) + } + + // Print header. + sb.WriteString("| " + strings.Join(t.Headers, " | ") + " |\n") + sb.WriteString("|" + strings.Repeat(" --- |", len(t.Headers)) + "\n") + + // Print rows. + for _, row := range t.Rows { + escapedRow := make([]string, len(row)) + for i, cell := range row { + escapedRow[i] = strings.ReplaceAll(cell, "|", "|") // Escape pipe characters. + } + sb.WriteString("| " + strings.Join(escapedRow, " | ") + " |\n") + } + + return sb.String() +} diff --git a/examples/gno.land/p/moul/mdtable/mdtable_test.gno b/examples/gno.land/p/moul/mdtable/mdtable_test.gno new file mode 100644 index 00000000000..87836a3ab11 --- /dev/null +++ b/examples/gno.land/p/moul/mdtable/mdtable_test.gno @@ -0,0 +1,158 @@ +package mdtable_test + +import ( + "testing" + + "gno.land/p/demo/urequire" + "gno.land/p/moul/mdtable" +) + +// XXX: switch to `func Example() {}` when supported. +func TestExample(t *testing.T) { + table := mdtable.Table{ + Headers: []string{"ID", "Title", "Status"}, + Rows: [][]string{ + {"#1", "Add a new validator", "succeed"}, + {"#2", "Change parameter", "timed out"}, + {"#3", "Fill pool", "active"}, + }, + } + + got := table.String() + expected := `| ID | Title | Status | +| --- | --- | --- | +| #1 | Add a new validator | succeed | +| #2 | Change parameter | timed out | +| #3 | Fill pool | active | +` + + urequire.Equal(t, got, expected) +} + +func TestTableString(t *testing.T) { + tests := []struct { + name string + table mdtable.Table + expected string + }{ + { + name: "With Headers and Rows", + table: mdtable.Table{ + Headers: []string{"ID", "Title", "Status", "Date"}, + Rows: [][]string{ + {"#1", "Add a new validator", "succeed", "2024-01-01"}, + {"#2", "Change parameter", "timed out", "2024-01-02"}, + }, + }, + expected: `| ID | Title | Status | Date | +| --- | --- | --- | --- | +| #1 | Add a new validator | succeed | 2024-01-01 | +| #2 | Change parameter | timed out | 2024-01-02 | +`, + }, + { + name: "Without Headers", + table: mdtable.Table{ + Rows: [][]string{ + {"#1", "Add a new validator", "succeed", "2024-01-01"}, + {"#2", "Change parameter", "timed out", "2024-01-02"}, + }, + }, + expected: `| | | | | +| --- | --- | --- | --- | +| #1 | Add a new validator | succeed | 2024-01-01 | +| #2 | Change parameter | timed out | 2024-01-02 | +`, + }, + { + name: "Without Rows", + table: mdtable.Table{ + Headers: []string{"ID", "Title", "Status", "Date"}, + }, + expected: `| ID | Title | Status | Date | +| --- | --- | --- | --- | +`, + }, + { + name: "With Pipe Character in Content", + table: mdtable.Table{ + Headers: []string{"ID", "Title", "Status", "Date"}, + Rows: [][]string{ + {"#1", "Add a new | validator", "succeed", "2024-01-01"}, + {"#2", "Change parameter", "timed out", "2024-01-02"}, + }, + }, + expected: `| ID | Title | Status | Date | +| --- | --- | --- | --- | +| #1 | Add a new | validator | succeed | 2024-01-01 | +| #2 | Change parameter | timed out | 2024-01-02 | +`, + }, + { + name: "With Varying Row Sizes", // XXX: should we have a different behavior? + table: mdtable.Table{ + Headers: []string{"ID", "Title"}, + Rows: [][]string{ + {"#1", "Add a new validator"}, + {"#2", "Change parameter", "Extra Column"}, + {"#3", "Fill pool"}, + }, + }, + expected: `| ID | Title | +| --- | --- | +| #1 | Add a new validator | +| #2 | Change parameter | Extra Column | +| #3 | Fill pool | +`, + }, + { + name: "With UTF-8 Characters", + table: mdtable.Table{ + Headers: []string{"ID", "Title", "Status", "Date"}, + Rows: [][]string{ + {"#1", "Café", "succeed", "2024-01-01"}, + {"#2", "München", "timed out", "2024-01-02"}, + {"#3", "São Paulo", "active", "2024-01-03"}, + }, + }, + expected: `| ID | Title | Status | Date | +| --- | --- | --- | --- | +| #1 | Café | succeed | 2024-01-01 | +| #2 | München | timed out | 2024-01-02 | +| #3 | São Paulo | active | 2024-01-03 | +`, + }, + { + name: "With no Headers and no Rows", + table: mdtable.Table{}, + expected: ``, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.table.String() + urequire.Equal(t, got, tt.expected) + }) + } +} + +func TestTableAppend(t *testing.T) { + table := mdtable.Table{ + Headers: []string{"ID", "Title", "Status", "Date"}, + } + + // Use the Append method to add rows to the table + table.Append([]string{"#1", "Add a new validator", "succeed", "2024-01-01"}) + table.Append([]string{"#2", "Change parameter", "timed out", "2024-01-02"}) + table.Append([]string{"#3", "Fill pool", "active", "2024-01-03"}) + got := table.String() + + expected := `| ID | Title | Status | Date | +| --- | --- | --- | --- | +| #1 | Add a new validator | succeed | 2024-01-01 | +| #2 | Change parameter | timed out | 2024-01-02 | +| #3 | Fill pool | active | 2024-01-03 | +` + urequire.Equal(t, got, expected) +}