Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Release: update the Homebrew handoff to publish through `openclaw/tap`.
- Version: `gog --version` now reports an informative fallback (for example, `v0.17.0-dev`) when built from source with plain `go build` instead of returning `dev`.
- Docs: `gog docs insert` now defaults to end-of-doc when `--index` is omitted, instead of always inserting at position 1 (which silently reversed iterative inserts across multiple calls). Pass `--index 1` explicitly to keep the previous behaviour. (#606)
- Docs: markdown empty-header table rows (e.g. `| | |`) no longer collide with the separator detection — previously `docs write --append --markdown` swallowed both the empty header and the real `|---|---|` separator, leaving the last data row re-parsed as a literal pipe paragraph after the table. (#609)

## 0.17.0 - 2026-05-15

Expand Down
9 changes: 8 additions & 1 deletion internal/cmd/docs_markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@ func isTableSeparator(line string) bool {
inner := strings.Trim(trimmed, "|")
// Split by | and check each segment
segments := strings.Split(inner, "|")
// A genuine separator must have at least one segment that actually contains
// dashes. Without this guard a row of empty pipe cells (e.g. an empty
// markdown table header `| | |`) would be misclassified as a
// separator because every segment hits the `continue` and never trips the
// dash check — see #609.
sawDashSegment := false
for _, seg := range segments {
seg = strings.TrimSpace(seg)
if seg == "" {
Expand All @@ -237,8 +243,9 @@ func isTableSeparator(line string) bool {
if strings.Count(seg, "-") == 0 {
return false
}
sawDashSegment = true
}
return len(segments) > 1
return sawDashSegment && len(segments) > 1
}

// parseMarkdownTable parses a markdown table into rows of cells
Expand Down
49 changes: 49 additions & 0 deletions internal/cmd/docs_markdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,52 @@ func TestParseMarkdown_TableDoesNotSkipFollowingLine(t *testing.T) {
t.Fatalf("second element = %#v, want paragraph 'After table'", got[1])
}
}

func TestIsTableSeparator_EmptyPipeRowRejected(t *testing.T) {
// Regression for #609: a row of empty pipe cells (e.g. an empty markdown
// table header) must not be classified as a separator line. Otherwise the
// outer parser drops a row from the table and re-parses the next data line
// as a literal pipe paragraph.
tests := []struct {
name string
line string
want bool
}{
{"empty cells", "| | |", false},
{"empty cells tight", "||", false},
{"empty cells three cols", "| | | |", false},
{"normal separator", "|---|---|", true},
{"spaced separator", "| --- | --- |", true},
{"left align", "|:---|---|", true},
{"center align", "|:---:|---:|", true},
{"mixed empty+dashes still valid", "|---| |", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isTableSeparator(tt.line); got != tt.want {
t.Errorf("isTableSeparator(%q) = %v, want %v", tt.line, got, tt.want)
}
})
}
}

func TestParseMarkdown_EmptyHeaderTableKeepsAllDataRows(t *testing.T) {
// Regression for #609: an empty-header table previously had its last data
// row re-parsed as a literal pipe paragraph (because the empty pipe row
// matched isTableSeparator and the outer loop advanced too far).
input := "| | |\n|-----|-----|\n| Label A | Value A |\n| Label B | Value B |"
got := ParseMarkdown(input)
if len(got) != 1 {
t.Fatalf("expected 1 element (table only), got %d: %#v", len(got), got)
}
if got[0].Type != MDTable {
t.Fatalf("element type = %v, want MDTable", got[0].Type)
}
if len(got[0].TableCells) != 3 {
t.Fatalf("expected 3 rows (empty header + 2 data), got %d: %#v", len(got[0].TableCells), got[0].TableCells)
}
last := got[0].TableCells[2]
if len(last) != 2 || last[0] != "Label B" || last[1] != "Value B" {
t.Fatalf("last row = %#v, want [Label B, Value B]", last)
}
}
Loading