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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Module `github.com/mozilla/markfluence` (`go 1.25`). `main.go` is a shim to `cmd
- `cmd/root.go` — the cobra root: `--url`/`--username`/`--debug`/`--no-color` persistent flags, version from `internal/buildinfo`, and registration of the four subcommands. `Execute()` prints cobra-generated errors (bad args/flags) but not `ui.ErrSilent`, which marks a failure a command already reported.
- `cmd/{update,create,fix,info}/` — one package per command (each exports `Cmd`), orchestrating the `internal` packages and `internal/ui` output. `create` is two-phase and transactional (validate all, then create parents-first in topological order); `fix` is read-only on the server.
- `internal/client` — `ConfluenceClient` over `net/http` with basic auth. Pages are Confluence **v2**; attachment writes and the user lookup are **v1** (`/wiki/rest/api/...`). Typed `HTTPError`, per-attempt context timeouts, centralized retry/backoff in `send` (429 for any method honoring `Retry-After`, plus 502/503/504 and network errors for idempotent methods only; exponential backoff capped), `SetContentProperty` retry-once on top (recovers a lost create-POST response), `SyncAttachments` (SHA-256-in-comment skip/update), `_links.next` pagination. `config.go` holds `Resolve` and the `.env` reader.
- `internal/convert` — the converter (the crux). `MdToConfluence(md *frontmatter.MarkdownFile, baseURL, spaceKey, version string) (*ConfluencePage, error)`. It parses with goldmark (GFM) and renders through a custom `storageRenderer` registered at priority 100 (below the default HTML=1000 and table=500 renderers) that emits Confluence storage format. `shield.go` renames raw `ac:`/`ri:` tags to colon-free sentinels around the goldmark step so pasted storage passes through; `callouts.go` is an AST transformer + blockquote renderer for GitHub alerts; `images.go`, `links.go` (sibling-file scans, GitHub/Confluence slugs, doc-link + anchor rewriting), and `renderer.go` (code macros, text soft-break→space, images, links) do the rest. The `<!-- confluence-toc -->` and `<!-- markfluence-version -->` token substitutions happen **inside** `MdToConfluence`.
- `internal/convert` — the converter (the crux). `MdToConfluence(md *frontmatter.MarkdownFile, baseURL, spaceKey, version string) (*ConfluencePage, error)`. It parses with goldmark (GFM) and renders through a custom `storageRenderer` registered at priority 100 (below the default HTML=1000 and table=500 renderers) that emits Confluence storage format. `shield.go` renames raw `ac:`/`ri:` tags to colon-free sentinels around the goldmark step so pasted storage passes through; `callouts.go` is an AST transformer + blockquote renderer for GitHub alerts; `images.go`, `links.go` (sibling-file scans, GitHub/Confluence slugs, doc-link + anchor rewriting), `tables.go` (the `<table>` tag only, stamped with Confluence's `data-layout="align-start"` so tables auto-size to their content and left-align; rows and cells still fall through to the GFM renderer), and `renderer.go` (code macros, text soft-break→space, images, links) do the rest. The `<!-- confluence-toc -->` and `<!-- markfluence-version -->` token substitutions happen **inside** `MdToConfluence`.
- `internal/frontmatter` — flat YAML frontmatter parse/quote/`UpdateField`, and the `MarkdownFile` type (`Parse`/`ParseFile`, exported `Filename`/`Content`/`Frontmatter`/`Body`, and `Title`/`PageID`/`Space`/`Parent` accessors that normalize missing/blank/`"null"`).
- `internal/pagewidth` — the `page_width` `Width` enum (`narrow`/`wide`/`max`, default `max`), `Declared`, the vocab↔content-property maps, `WidthFromProperties`, and `Apply`/`Read` against the client.
- `internal/buildinfo` — `Version` (set via ldflags), `CommitDate` (from the `vcs.time` build setting), and `Stamp`.
Expand Down
1 change: 1 addition & 0 deletions internal/convert/renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func (r *storageRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer)
reg.Register(ast.KindBlockquote, r.renderBlockquote)
reg.Register(ast.KindImage, r.renderImage)
reg.Register(ast.KindLink, r.renderLink)
reg.Register(tableKind, r.renderTable)
}

// renderText renders inline text, collapsing soft line breaks to a single space
Expand Down
36 changes: 36 additions & 0 deletions internal/convert/tables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package convert

import (
"github.com/yuin/goldmark/ast"
east "github.com/yuin/goldmark/extension/ast"
"github.com/yuin/goldmark/util"
)

// tableLayout is the Confluence table layout every table is published with.
// Without a layout attribute Confluence auto-sizes the table but leaves it
// unanchored; "align-start" auto-sizes it to its content and left-aligns it on the
// page, which is what a markdown table should look like. The other values Confluence
// accepts are "center", "wide", and "full-width".
//
// Note that a colwidth <colgroup> on a table with no layout attribute makes
// Confluence default the layout to "full-width", so this attribute must stay if
// column widths are ever emitted.
const tableLayout = "align-start"

// renderTable emits the <table> tag with the Confluence layout attribute. Only the
// table element itself is overridden; the GFM renderer still emits the thead/tbody,
// rows, and cells.
func (r *storageRenderer) renderTable(
w util.BufWriter, _ []byte, _ ast.Node, entering bool,
) (ast.WalkStatus, error) {
if entering {
_, _ = w.WriteString(`<table data-layout="` + tableLayout + "\">\n")
} else {
_, _ = w.WriteString("</table>\n")
}
return ast.WalkContinue, nil
}

// tableKind is the GFM table node kind, aliased so renderer.go's registration list
// does not need the extension AST import.
var tableKind = east.KindTable
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
}
],
"broken": [],
"html": "<h1>Release Notes</h1>\n<ac:structured-macro ac:name=\"toc\" ac:schema-version=\"1\" />\n<p>See the <a href=\"https://wiki.example.net/wiki/spaces/ENG/pages/5001/Upgrade+Guide\">upgrade guide</a> before starting.</p>\n<ac:structured-macro ac:name=\"warning\" ac:schema-version=\"1\"><ac:rich-text-body><p>Back up your data before upgrading.</p>\n</ac:rich-text-body></ac:structured-macro><h2>What's New</h2>\n<p>A soft-wrapped paragraph describing the release across multiple source lines that collapse into one.</p>\n<p><ac:image ac:alt=\"the new dashboard\" ac:width=\"480\" ac:align=\"center\"><ri:attachment ri:filename=\"assets_logo.png\" /></ac:image></p>\n<h2>Configuration</h2>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Setting</th>\n<th align=\"left\">Default</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"left\">retries</td>\n<td align=\"left\">3</td>\n</tr>\n<tr>\n<td align=\"left\">timeout</td>\n<td align=\"left\">30s</td>\n</tr>\n</tbody>\n</table>\n<ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\"><ac:parameter ac:name=\"language\">python</ac:parameter><ac:plain-text-body><![CDATA[config = {\"retries\": 3, \"timeout\": \"30s\"}]]></ac:plain-text-body></ac:structured-macro><p>A pasted status macro: <ac:structured-macro ac:name=\"status\" ac:schema-version=\"1\"><ac:parameter ac:name=\"title\">Stable</ac:parameter></ac:structured-macro></p>\n",
"html": "<h1>Release Notes</h1>\n<ac:structured-macro ac:name=\"toc\" ac:schema-version=\"1\" />\n<p>See the <a href=\"https://wiki.example.net/wiki/spaces/ENG/pages/5001/Upgrade+Guide\">upgrade guide</a> before starting.</p>\n<ac:structured-macro ac:name=\"warning\" ac:schema-version=\"1\"><ac:rich-text-body><p>Back up your data before upgrading.</p>\n</ac:rich-text-body></ac:structured-macro><h2>What's New</h2>\n<p>A soft-wrapped paragraph describing the release across multiple source lines that collapse into one.</p>\n<p><ac:image ac:alt=\"the new dashboard\" ac:width=\"480\" ac:align=\"center\"><ri:attachment ri:filename=\"assets_logo.png\" /></ac:image></p>\n<h2>Configuration</h2>\n<table data-layout=\"align-start\">\n<thead>\n<tr>\n<th align=\"left\">Setting</th>\n<th align=\"left\">Default</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"left\">retries</td>\n<td align=\"left\">3</td>\n</tr>\n<tr>\n<td align=\"left\">timeout</td>\n<td align=\"left\">30s</td>\n</tr>\n</tbody>\n</table>\n<ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\"><ac:parameter ac:name=\"language\">python</ac:parameter><ac:plain-text-body><![CDATA[config = {\"retries\": 3, \"timeout\": \"30s\"}]]></ac:plain-text-body></ac:structured-macro><p>A pasted status macro: <ac:structured-macro ac:name=\"status\" ac:schema-version=\"1\"><ac:parameter ac:name=\"title\">Stable</ac:parameter></ac:structured-macro></p>\n",
"warnings": []
}
2 changes: 1 addition & 1 deletion internal/convert/testdata/regression/tables/test.output
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"attachments": [],
"broken": [],
"html": "<h1>Tables</h1>\n<p>A GFM table with alignment:</p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Name</th>\n<th align=\"center\">Role</th>\n<th align=\"right\">Count</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"left\">Alice</td>\n<td align=\"center\">Maintainer</td>\n<td align=\"right\">12</td>\n</tr>\n<tr>\n<td align=\"left\">Bob</td>\n<td align=\"center\">Contributor</td>\n<td align=\"right\">3</td>\n</tr>\n</tbody>\n</table>\n<p>Text after the table.</p>\n",
"html": "<h1>Tables</h1>\n<p>A GFM table with alignment:</p>\n<table data-layout=\"align-start\">\n<thead>\n<tr>\n<th align=\"left\">Name</th>\n<th align=\"center\">Role</th>\n<th align=\"right\">Count</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"left\">Alice</td>\n<td align=\"center\">Maintainer</td>\n<td align=\"right\">12</td>\n</tr>\n<tr>\n<td align=\"left\">Bob</td>\n<td align=\"center\">Contributor</td>\n<td align=\"right\">3</td>\n</tr>\n</tbody>\n</table>\n<p>Text after the table.</p>\n",
"warnings": []
}