diff --git a/Makefile b/Makefile index 9f1df9b..b58dbd6 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ build: -gcflags "-trimpath $(GOPATH)/src" test: - go test -race -coverprofile=profile.cov ./... + go test -race -coverprofile=profile.cov ./... -v image: @echo :: building image $(NAME):$(VERSION) diff --git a/README.md b/README.md index 6e93b4a..ac4830d 100644 --- a/README.md +++ b/README.md @@ -719,7 +719,7 @@ Currently this is not compatible with the automated upload of inline images. ### Render Mermaid Diagram Confluence doesn't provide [mermaid.js](https://github.com/mermaid-js/mermaid) support natively. Mark provides a convenient way to enable the feature like [Github does](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/). -As long as you have a code block and are marked as "mermaid", the mark will automatically render it as a PNG image and insert into before the code block. +As long as you have a code block marked as "mermaid", mark will automatically render it as a PNG image and attach it to the page as a rendered version of the code block. ```mermaid title diagrams_example graph TD; @@ -729,7 +729,17 @@ A-->B; In order to properly render mermaid, you can choose between the following mermaid providers: * "mermaid-go" via [mermaid.go](https://github.com/dreampuf/mermaid.go) -* "cloudscript" via [cloudscript-io-mermaid-addon](https://marketplace.atlassian.com/apps/1219878/cloudscript-io-mermaid-addon) +* "cloudscript" via [cloudscript-io-mermaid-addon](https://marketplace.atlassian.com/apps/1219878/cloudscript-io-mermaid-addon) (deprecated) + +### Render D2 Diagram + +Optionally you can enable [D2](https://github.com/terrastruct/d2) rendering via `--features="d2"`. +This will transform the d2 diagram into a png that will be attached to Confluence, similar to how mermaid-go support works. +All you need is a codeblock marked as "d2". + +```d2 +X -> Y +``` ## Installation @@ -789,33 +799,36 @@ DESCRIPTION: Mark is a tool to update Atlassian Confluence pages from markdown. Documentation is available here: https://github.com/kovetskiy/mark GLOBAL OPTIONS: - --files value, -f value use specified markdown file(s) for converting to html. Supports file globbing patterns (needs to be quoted). [$MARK_FILES] - --compile-only show resulting HTML and don't update Confluence page content. (default: false) [$MARK_COMPILE_ONLY] - --dry-run resolve page and ancestry, show resulting HTML and exit. (default: false) [$MARK_DRY_RUN] - --edit-lock, -k lock page editing to current user only to prevent accidental manual edits over Confluence Web UI. (default: false) [$MARK_EDIT_LOCK] - --drop-h1, --h1_drop don't include the first H1 heading in Confluence output. (default: false) [$MARK_H1_DROP] - --strip-linebreaks, -L remove linebreaks inside of tags, to accommodate non-standard Confluence behavior (default: false) [$MARK_STRIP_LINEBREAKS] - --title-from-h1, --h1_title extract page title from a leading H1 heading. If no H1 heading on a page exists, then title must be set in the page metadata. (default: false) [$MARK_H1_TITLE] - --title-append-generated-hash appends a short hash generated from the path of the page (space, parents, and title) to the title (default: false) [$MARK_TITLE_APPEND_GENERATED_HASH] - --minor-edit don't send notifications while updating Confluence page. (default: false) [$MARK_MINOR_EDIT] - --version-message value add a message to the page version, to explain the edit (default: "") [$MARK_VERSION_MESSAGE] - --color value display logs in color. Possible values: auto, never. (default: "auto") [$MARK_COLOR] - --log-level value set the log level. Possible values: TRACE, DEBUG, INFO, WARNING, ERROR, FATAL. (default: "info") [$MARK_LOG_LEVEL] - --username value, -u value use specified username for updating Confluence page. [$MARK_USERNAME] - --password value, -p value use specified token for updating Confluence page. Specify - as password to read password from stdin, or your Personal access token. Username is not mandatory if personal access token is provided. For more info please see: https://developer.atlassian.com/server/confluence/confluence-server-rest-api/#authentication. [$MARK_PASSWORD] - --target-url value, -l value edit specified Confluence page. If -l is not specified, file should contain metadata (see above). [$MARK_TARGET_URL] - --base-url value, -b value, --base_url value base URL for Confluence. Alternative option for base_url config field. [$MARK_BASE_URL] - --config value, -c value use the specified configuration file. (default: System specific) [$MARK_CONFIG] - --ci run on CI mode. It won't fail if files are not found. (default: false) [$MARK_CI] - --space value use specified space key. If the space key is not specified, it must be set in the page metadata. [$MARK_SPACE] - --parents value A list containing the parents of the document separated by parents-delimiter (default: '/'). These will be prepended to the ones defined in the document itself. [$MARK_PARENTS] - --parents-delimiter value The delimiter used for the parents list (default: "/") [$MARK_PARENTS_DELIMITER] - --mermaid-provider value defines the mermaid provider to use. Supported options are: cloudscript, mermaid-go. (default: "cloudscript") [$MARK_MERMAID_PROVIDER] - --mermaid-scale value defines the scaling factor for mermaid renderings. (default: 1) [$MARK_MERMAID_SCALE] - --include-path value Path for shared includes, used as a fallback if the include doesn't exist in the current directory. [$MARK_INCLUDE_PATH] - --changes-only Avoids re-uploading pages that haven't changed since the last run. (default: false) [$MARK_CHANGES_ONLY] - --help, -h show help - --version, -v print the version + --files string, -f string use specified markdown file(s) for converting to html. Supports file globbing patterns (needs to be quoted). [$MARK_FILES] + --continue-on-error don't exit if an error occurs while processing a file, continue processing remaining files. (default: false) [$MARK_CONTINUE_ON_ERROR] + --compile-only show resulting HTML and don't update Confluence page content. (default: false) [$MARK_COMPILE_ONLY] + --dry-run resolve page and ancestry, show resulting HTML and exit. (default: false) [$MARK_DRY_RUN] + --edit-lock, -k lock page editing to current user only to prevent accidental manual edits over Confluence Web UI. (default: false) [$MARK_EDIT_LOCK] + --drop-h1, --h1_drop don't include the first H1 heading in Confluence output. (default: false) [$MARK_H1_DROP] + --strip-linebreaks, -L remove linebreaks inside of tags, to accommodate non-standard Confluence behavior (default: false) [$MARK_STRIP_LINEBREAKS] + --title-from-h1, --h1_title extract page title from a leading H1 heading. If no H1 heading on a page exists, then title must be set in the page metadata. (default: false) [$MARK_H1_TITLE] + --title-append-generated-hash appends a short hash generated from the path of the page (space, parents, and title) to the title (default: false) [$MARK_TITLE_APPEND_GENERATED_HASH] + --minor-edit don't send notifications while updating Confluence page. (default: false) [$MARK_MINOR_EDIT] + --version-message string add a message to the page version, to explain the edit (default: "") [$MARK_VERSION_MESSAGE] + --color string display logs in color. Possible values: auto, never. (default: "auto") [$MARK_COLOR] + --log-level string set the log level. Possible values: TRACE, DEBUG, INFO, WARNING, ERROR, FATAL. (default: "info") [$MARK_LOG_LEVEL] + --username string, -u string use specified username for updating Confluence page. [$MARK_USERNAME] + --password string, -p string use specified token for updating Confluence page. Specify - as password to read password from stdin, or your Personal access token. Username is not mandatory if personal access token is provided. For more info please see: https://developer.atlassian.com/server/confluence/confluence-server-rest-api/#authentication. [$MARK_PASSWORD] + --target-url string, -l string edit specified Confluence page. If -l is not specified, file should contain metadata (see above). [$MARK_TARGET_URL] + --base-url string, -b string, --base_url string base URL for Confluence. Alternative option for base_url config field. [$MARK_BASE_URL] + --config string, -c string use the specified configuration file. (default: "/home/mrueg/.config/mark") [$MARK_CONFIG] + --ci run on CI mode. It won't fail if files are not found. (default: false) [$MARK_CI] + --space string use specified space key. If the space key is not specified, it must be set in the page metadata. [$MARK_SPACE] + --parents string A list containing the parents of the document separated by parents-delimiter (default: '/'). These will be prepended to the ones defined in the document itself. [$MARK_PARENTS] + --parents-delimiter string The delimiter used for the parents list (default: "/") [$MARK_PARENTS_DELIMITER] + --mermaid-provider string defines the mermaid provider to use. Supported options are: cloudscript, mermaid-go. (default: "cloudscript") [$MARK_MERMAID_PROVIDER] + --mermaid-scale float defines the scaling factor for mermaid renderings. (default: 1) [$MARK_MERMAID_SCALE] + --include-path string Path for shared includes, used as a fallback if the include doesn't exist in the current directory. [$MARK_INCLUDE_PATH] + --changes-only Avoids re-uploading pages that haven't changed since the last run. (default: false) [$MARK_CHANGES_ONLY] + --d2-scale float defines the scaling factor for d2 renderings. (default: 1) [$MARK_D2_SCALE] + --features string [ --features string ] Enables optional features. Current features: d2, mermaid (default: "mermaid") [$MARK_FEATURES] + --help, -h show help + --version, -v print the version ``` You can store user credentials in the configuration file, which should be diff --git a/attachment/attachment_test.go b/attachment/attachment_test.go index a89324f..c3dc547 100644 --- a/attachment/attachment_test.go +++ b/attachment/attachment_test.go @@ -45,7 +45,7 @@ func TestPrepareAttachmentsWithWorkDirBase(t *testing.T) { } attaches, err := prepareAttachments(testingOpener, ".", replacements) - t.Logf("attaches: %s", err) + t.Logf("attaches: %v", err) if err != nil { println(err.Error()) t.Fatal(err) diff --git a/d2/d2.go b/d2/d2.go new file mode 100644 index 0000000..d6b85fc --- /dev/null +++ b/d2/d2.go @@ -0,0 +1,107 @@ +package d2 + +import ( + "bytes" + "context" + "encoding/base64" + "fmt" + "strconv" + "time" + + "github.com/chromedp/cdproto/dom" + "github.com/chromedp/chromedp" + + "github.com/kovetskiy/mark/attachment" + "github.com/reconquest/pkg/log" + + "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" + "oss.terrastruct.com/d2/d2lib" + "oss.terrastruct.com/d2/d2renderers/d2svg" + "oss.terrastruct.com/d2/d2themes/d2themescatalog" + d2log "oss.terrastruct.com/d2/lib/log" + "oss.terrastruct.com/d2/lib/textmeasure" + "oss.terrastruct.com/util-go/go2" +) + +var renderTimeout = 120 * time.Second + +func ProcessD2(title string, d2Diagram []byte, scale float64) (attachment.Attachment, error) { + ctx, cancel := context.WithTimeout(context.TODO(), renderTimeout) + ctx = d2log.WithDefault(ctx) + defer cancel() + + ruler, err := textmeasure.NewRuler() + if err != nil { + return attachment.Attachment{}, err + } + layoutResolver := func(engine string) (d2graph.LayoutGraph, error) { + return d2dagrelayout.DefaultLayout, nil + } + renderOpts := &d2svg.RenderOpts{ + Pad: go2.Pointer(int64(5)), + ThemeID: &d2themescatalog.GrapeSoda.ID, + } + compileOpts := &d2lib.CompileOptions{ + LayoutResolver: layoutResolver, + Ruler: ruler, + } + + diagram, _, err := d2lib.Compile(ctx, string(d2Diagram), compileOpts, renderOpts) + if err != nil { + return attachment.Attachment{}, err + } + + out, err := d2svg.Render(diagram, renderOpts) + if err != nil { + return attachment.Attachment{}, err + } + + log.Debugf(nil, "Rendering: %q", title) + pngBytes, boxModel, err := convertSVGtoPNG(ctx, out, scale) + if err != nil { + return attachment.Attachment{}, err + } + + checkSum, err := attachment.GetChecksum(bytes.NewReader(d2Diagram)) + log.Debugf(nil, "Checksum: %q -> %s", title, checkSum) + + if err != nil { + return attachment.Attachment{}, err + } + if title == "" { + title = checkSum + } + + fileName := title + ".png" + + return attachment.Attachment{ + ID: "", + Name: title, + Filename: fileName, + FileBytes: pngBytes, + Checksum: checkSum, + Replace: title, + Width: strconv.FormatInt(boxModel.Width, 10), + Height: strconv.FormatInt(boxModel.Height, 10), + }, nil +} + +func convertSVGtoPNG(ctx context.Context, svg []byte, scale float64) (png []byte, m *dom.BoxModel, err error) { + var ( + result []byte + model *dom.BoxModel + ) + ctx, cancel := chromedp.NewContext(ctx) + defer cancel() + + err = chromedp.Run(ctx, + chromedp.Navigate(fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(svg))), + chromedp.ScreenshotScale(`document.querySelector("svg > svg")`, scale, &result, chromedp.ByJSPath), + chromedp.Dimensions(`document.querySelector("svg > svg")`, &model, chromedp.ByJSPath), + ) + if err != nil { + return nil, nil, err + } + return result, model, err +} diff --git a/d2/d2_test.go b/d2/d2_test.go new file mode 100644 index 0000000..043129f --- /dev/null +++ b/d2/d2_test.go @@ -0,0 +1,102 @@ +package d2 + +import ( + "fmt" + "testing" + + "github.com/kovetskiy/mark/attachment" + "github.com/stretchr/testify/assert" +) + +var diagram string = `d2 +vars: { + d2-config: { + layout-engine: elk + # Terminal theme code + theme-id: 300 + } +} +network: { + cell tower: { + satellites: { + shape: stored_data + style.multiple: true + } + + transmitter + + satellites -> transmitter: send + satellites -> transmitter: send + satellites -> transmitter: send + } + + online portal: { + ui: {shape: hexagon} + } + + data processor: { + storage: { + shape: cylinder + style.multiple: true + } + } + + cell tower.transmitter -> data processor.storage: phone logs +} + +user: { + shape: person + width: 130 +} + +user -> network.cell tower: make call +user -> network.online portal.ui: access { + style.stroke-dash: 3 +} + +api server -> network.online portal.ui: display +api server -> logs: persist +logs: {shape: page; style.multiple: true} + +network.data processor -> api server +` + +func TestExtractD2Image(t *testing.T) { + tests := []struct { + name string + markdown []byte + scale float64 + want attachment.Attachment + wantErr assert.ErrorAssertionFunc + }{ + {"example", []byte(diagram), 1.0, attachment.Attachment{ + // This is only the PNG Magic Header + FileBytes: []byte{0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa}, + Filename: "example.png", + Name: "example", + Replace: "example", + Checksum: "58fa387384181445e2d8f90a8c7fda945cb75174f73e8b9853ff59b9e0103ddd", + ID: "", + Width: "198", + Height: "441", + }, + assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ProcessD2(tt.name, tt.markdown, tt.scale) + if !tt.wantErr(t, err, fmt.Sprintf("processD2(%v, %v)", tt.name, string(tt.markdown))) { + return + } + assert.Equal(t, tt.want.Filename, got.Filename, "processD2(%v, %v)", tt.name, string(tt.markdown)) + // We only test for the header as png changes based on system png library + assert.Equal(t, tt.want.FileBytes, got.FileBytes[0:8], "processD2(%v, %v)", tt.name, string(tt.markdown)) + assert.Equal(t, tt.want.Name, got.Name, "processD2(%v, %v)", tt.name, string(tt.markdown)) + assert.Equal(t, tt.want.Replace, got.Replace, "processD2(%v, %v)", tt.name, string(tt.markdown)) + assert.Equal(t, tt.want.Checksum, got.Checksum, "processD2(%v, %v)", tt.name, string(tt.markdown)) + assert.Equal(t, tt.want.ID, got.ID, "processD2(%v, %v)", tt.name, string(tt.markdown)) + assert.Equal(t, tt.want.Width, got.Width, "processD2(%v, %v)", tt.name, string(tt.markdown)) + assert.Equal(t, tt.want.Height, got.Height, "processD2(%v, %v)", tt.name, string(tt.markdown)) + }) + } +} diff --git a/go.mod b/go.mod index 7d17c3a..bc86a25 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ toolchain go1.24.2 require ( github.com/bmatcuk/doublestar/v4 v4.8.1 + github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8 + github.com/chromedp/chromedp v0.13.3 github.com/dreampuf/mermaid.go v0.0.27 github.com/kovetskiy/gopencils v0.0.0-20250404051442-0b776066936a github.com/kovetskiy/lorg v1.2.1-0.20240830111423-ba4fe8b6f7c4 @@ -18,22 +20,39 @@ require ( github.com/yuin/goldmark v1.7.12 golang.org/x/tools v0.33.0 gopkg.in/yaml.v3 v3.0.1 + oss.terrastruct.com/d2 v0.7.0 + oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a ) require ( github.com/BurntSushi/toml v1.5.0 // indirect - github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8 // indirect - github.com/chromedp/chromedp v0.13.3 // indirect + github.com/PuerkitoBio/goquery v1.10.0 // indirect + github.com/alecthomas/chroma/v2 v2.14.0 // indirect + github.com/andybalholm/cascadia v1.3.2 // indirect github.com/chromedp/sysutil v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dlclark/regexp2 v1.11.4 // indirect + github.com/dop251/goja v0.0.0-20240927123429-241b342198c2 // indirect github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 // indirect + github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.4.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/google/pprof v0.0.0-20240927180334-d43a67379298 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mazznoer/csscolorparser v0.1.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/reconquest/cog v0.0.0-20240830113510-c7ba12d0beeb // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/zazab/zhash v0.0.0-20221031090444-2b0d50417446 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/image v0.20.0 // indirect + golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + gonum.org/v1/plot v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) diff --git a/go.sum b/go.sum index dcd922b..0c84e46 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,27 @@ +git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= +git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= +github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= github.com/Shopify/toxiproxy/v2 v2.12.0 h1:d1x++lYZg/zijXPPcv7PH0MvHMzEI5aX/YuUi/Sw+yg= github.com/Shopify/toxiproxy/v2 v2.12.0/go.mod h1:R9Z38Pw6k2cGZWXHe7tbxjGW9azmY1KbDQJ1kd+h7Tk= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= +github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= +github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8 h1:AqW2bDQf67Zbq6Tpop/+yJSIknxhiQecO2B8jNYTAPs= github.com/chromedp/cdproto v0.0.0-20250319231242-a755498943c8/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= github.com/chromedp/chromedp v0.13.3 h1:c6nTn97XQBykzcXiGYL5LLebw3h3CEyrCihm4HquYh0= @@ -13,16 +31,34 @@ github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHG github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= +github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dop251/goja v0.0.0-20240927123429-241b342198c2 h1:Ux9RXuPQmTB4C1MKagNLme0krvq8ulewfor+ORO/QL4= +github.com/dop251/goja v0.0.0-20240927123429-241b342198c2/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4= github.com/dreampuf/mermaid.go v0.0.27 h1:uriWHpcc4clTaAUdJqpyDzyGvAZumeLb61n2VBxc0ZQ= github.com/dreampuf/mermaid.go v0.0.27/go.mod h1:13PeW5y49ouLGlP3RdZm6ke+lQIcz3z7rdVoqRkt5hY= +github.com/go-fonts/liberation v0.3.1 h1:9RPT2NhUpxQ7ukUvz3jeUckmN42T9D9TpjtQcqK/ceM= +github.com/go-fonts/liberation v0.3.1/go.mod h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY= github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 h1:yE7argOs92u+sSCRgqqe6eF+cDaVhSPlioy1UkA0p/w= github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s= +github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs= +github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM= +github.com/go-pdf/fpdf v0.8.0 h1:IJKpdaagnWUeSkUFUjTcSzTppFxmv8ucGQyNPQWxYOQ= +github.com/go-pdf/fpdf v0.8.0/go.mod h1:gfqhcNwXrsd3XYKte9a7vM3smvU/jB4ZRDrmWSxpfdc= +github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q= +github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/pprof v0.0.0-20240927180334-d43a67379298 h1:dMHbguTqGtorivvHTaOnbYp+tFzrw5M9gjkU4lCplgg= +github.com/google/pprof v0.0.0-20240927180334-d43a67379298/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/kovetskiy/gopencils v0.0.0-20250404051442-0b776066936a h1:OPt6gCghZXQ/WZpT6EhGkA7v+YMAYzcCb8SPQWmsb/8= github.com/kovetskiy/gopencils v0.0.0-20250404051442-0b776066936a/go.mod h1:gRW37oDEg9LzOHApv31YzxKBICcCmPtDogaImsxZ6xc= github.com/kovetskiy/lorg v1.2.1-0.20240830111423-ba4fe8b6f7c4 h1:2eV8tF1u58dqRJMlFUD/Df26BxcIlGVy71rZHN+aNoI= @@ -33,6 +69,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mazznoer/csscolorparser v0.1.5 h1:Wr4uNIE+pHWN3TqZn2SGpA2nLRG064gB7WdSfSS5cz4= +github.com/mazznoer/csscolorparser v0.1.5/go.mod h1:OQRVvgCyHDCAquR1YWfSwwaDcM0LhnSffGnlbOew/3I= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -46,6 +86,8 @@ github.com/reconquest/pkg v1.3.1-0.20240901105413-68c2adbf2b64 h1:OBNLiZay5PYLmG github.com/reconquest/pkg v1.3.1-0.20240901105413-68c2adbf2b64/go.mod h1:r1Z1JNh3in9xLWbhv5u7cdox9vvGFjlKp89VI10Jrdo= github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4 h1:bcDXaTFC09IIg13Z8gfQHk4gSu001ET7ssW/wKRvPzg= github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4/go.mod h1:OI1di2iiFSwX3D70iZjzdmCPPfssjOl+HX40tI3VaXA= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= @@ -54,15 +96,63 @@ github.com/urfave/cli-altsrc/v3 v3.0.1 h1:v+gHk59syLk8ao9rYybZs43+D5ut/gzj0omqQ1 github.com/urfave/cli-altsrc/v3 v3.0.1/go.mod h1:8UtsKKcxFVzvaoySFPfvQOk413T+IXJhaCWyyoPW3yM= github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY= github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/zazab/zhash v0.0.0-20221031090444-2b0d50417446 h1:75pcOSsb40+ub185cJI7g5uykl9Uu76rD5ONzK/4s40= github.com/zazab/zhash v0.0.0-20221031090444-2b0d50417446/go.mod h1:NtepZ8TEXErPsmQDMUoN72f8aIy4+xNinSJ3f1giess= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= +golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= +gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -70,3 +160,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +oss.terrastruct.com/d2 v0.7.0 h1:nFTap/RgAQtm1aAmUOOJxO8vgSCj3SLILcOkStnyHeI= +oss.terrastruct.com/d2 v0.7.0/go.mod h1:QseS95MrwfSRDJcFmVpBBIKuPIr8/RUoR3526QQ3rVk= +oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a h1:UXF/Z9i9tOx/wqGUOn/T12wZeez1Gg0sAVKKl7YUDwM= +oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a/go.mod h1:eMWv0sOtD9T2RUl90DLWfuShZCYp4NrsqNpI8eqO6U4= +rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main.go b/main.go index faace92..48f8689 100644 --- a/main.go +++ b/main.go @@ -17,11 +17,11 @@ const ( func main() { cmd := &cli.Command{ - Name: "mark", - Usage: usage, - Description: description, - Version: version, - Flags: util.Flags, + Name: "mark", + Usage: usage, + Description: description, + Version: version, + Flags: util.Flags, EnableShellCompletion: true, HideHelpCommand: true, Action: util.RunMark, diff --git a/markdown/markdown.go b/markdown/markdown.go index 3d00922..47635bc 100644 --- a/markdown/markdown.go +++ b/markdown/markdown.go @@ -7,6 +7,7 @@ import ( cparser "github.com/kovetskiy/mark/parser" crenderer "github.com/kovetskiy/mark/renderer" "github.com/kovetskiy/mark/stdlib" + "github.com/kovetskiy/mark/types" "github.com/reconquest/pkg/log" "github.com/yuin/goldmark" @@ -20,26 +21,20 @@ import ( // Renderer renders anchor [Node]s. type ConfluenceExtension struct { html.Config - Stdlib *stdlib.Lib - Path string - MermaidProvider string - MermaidScale float64 - DropFirstH1 bool - StripNewlines bool - Attachments []attachment.Attachment + Stdlib *stdlib.Lib + Path string + MarkConfig types.MarkConfig + Attachments []attachment.Attachment } // NewConfluenceRenderer creates a new instance of the ConfluenceRenderer -func NewConfluenceExtension(stdlib *stdlib.Lib, path string, mermaidProvider string, mermaidScale float64, dropFirstH1 bool, stripNewlines bool) *ConfluenceExtension { +func NewConfluenceExtension(stdlib *stdlib.Lib, path string, cfg types.MarkConfig) *ConfluenceExtension { return &ConfluenceExtension{ - Config: html.NewConfig(), - Stdlib: stdlib, - Path: path, - MermaidProvider: mermaidProvider, - MermaidScale: mermaidScale, - DropFirstH1: dropFirstH1, - StripNewlines: stripNewlines, - Attachments: []attachment.Attachment{}, + Config: html.NewConfig(), + Stdlib: stdlib, + Path: path, + MarkConfig: cfg, + Attachments: []attachment.Attachment{}, } } @@ -50,12 +45,12 @@ func (c *ConfluenceExtension) Attach(a attachment.Attachment) { func (c *ConfluenceExtension) Extend(m goldmark.Markdown) { m.Renderer().AddOptions(renderer.WithNodeRenderers( - util.Prioritized(crenderer.NewConfluenceTextRenderer(c.StripNewlines), 100), + util.Prioritized(crenderer.NewConfluenceTextRenderer(c.MarkConfig.StripNewlines), 100), util.Prioritized(crenderer.NewConfluenceBlockQuoteRenderer(), 100), util.Prioritized(crenderer.NewConfluenceCodeBlockRenderer(c.Stdlib, c.Path), 100), - util.Prioritized(crenderer.NewConfluenceFencedCodeBlockRenderer(c.Stdlib, c, c.MermaidProvider, c.MermaidScale), 100), + util.Prioritized(crenderer.NewConfluenceFencedCodeBlockRenderer(c.Stdlib, c, c.MarkConfig), 100), util.Prioritized(crenderer.NewConfluenceHTMLBlockRenderer(c.Stdlib), 100), - util.Prioritized(crenderer.NewConfluenceHeadingRenderer(c.DropFirstH1), 100), + util.Prioritized(crenderer.NewConfluenceHeadingRenderer(c.MarkConfig.DropFirstH1), 100), util.Prioritized(crenderer.NewConfluenceImageRenderer(c.Stdlib, c, c.Path), 100), util.Prioritized(crenderer.NewConfluenceParagraphRenderer(), 100), util.Prioritized(crenderer.NewConfluenceLinkRenderer(), 100), @@ -68,10 +63,10 @@ func (c *ConfluenceExtension) Extend(m goldmark.Markdown) { )) } -func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, path string, mermaidProvider string, mermaidScale float64, dropFirstH1 bool, stripNewlines bool) (string, []attachment.Attachment) { +func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, path string, cfg types.MarkConfig) (string, []attachment.Attachment) { log.Tracef(nil, "rendering markdown:\n%s", string(markdown)) - confluenceExtension := NewConfluenceExtension(stdlib, path, mermaidProvider, mermaidScale, dropFirstH1, stripNewlines) + confluenceExtension := NewConfluenceExtension(stdlib, path, cfg) converter := goldmark.New( goldmark.WithExtensions( @@ -81,7 +76,7 @@ func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, path string, mermaidPr extension.WithTableCellAlignMethod(extension.TableCellAlignStyle), ), confluenceExtension, - extension.GFM, + extension.GFM, ), goldmark.WithParserOptions( parser.WithAutoHeadingID(), diff --git a/markdown/markdown_test.go b/markdown/markdown_test.go index 85059f7..40091c5 100644 --- a/markdown/markdown_test.go +++ b/markdown/markdown_test.go @@ -11,6 +11,7 @@ import ( mark "github.com/kovetskiy/mark/markdown" "github.com/kovetskiy/mark/stdlib" + "github.com/kovetskiy/mark/types" "github.com/kovetskiy/mark/util" "github.com/stretchr/testify/assert" "github.com/urfave/cli/v3" @@ -55,8 +56,17 @@ func TestCompileMarkdown(t *testing.T) { panic(err) } markdown, htmlname, html := loadData(t, filename, "") - actual, _ := mark.CompileMarkdown(markdown, lib, filename, "", 1.0, false, false) - test.EqualValues(string(html), actual, filename+" vs "+htmlname) + + cfg := types.MarkConfig{ + MermaidProvider: "", + MermaidScale: 1.0, + DropFirstH1: false, + StripNewlines: false, + Features: []string{}, + } + + actual, _ := mark.CompileMarkdown(markdown, lib, filename, cfg) + test.EqualValues(strings.TrimSuffix(string(html), "\n"), strings.TrimSuffix(actual, "\n"), filename+" vs "+htmlname) } } @@ -88,8 +98,18 @@ func TestCompileMarkdownDropH1(t *testing.T) { variant = "" } markdown, htmlname, html := loadData(t, filename, variant) - actual, _ := mark.CompileMarkdown(markdown, lib, filename, "", 1.0, true, false) - test.EqualValues(string(html), actual, filename+" vs "+htmlname) + + cfg := types.MarkConfig{ + MermaidProvider: "", + MermaidScale: 1.0, + DropFirstH1: true, + StripNewlines: false, + Features: []string{}, + } + + actual, _ := mark.CompileMarkdown(markdown, lib, filename, cfg) + test.EqualValues(strings.TrimSuffix(string(html), "\n"), strings.TrimSuffix(actual, "\n"), filename+" vs "+htmlname) + } } @@ -122,8 +142,18 @@ func TestCompileMarkdownStripNewlines(t *testing.T) { } markdown, htmlname, html := loadData(t, filename, variant) - actual, _ := mark.CompileMarkdown(markdown, lib, filename, "", 1.0, false, true) - test.EqualValues(string(html), actual, filename+" vs "+htmlname) + + cfg := types.MarkConfig{ + MermaidProvider: "", + MermaidScale: 1.0, + DropFirstH1: false, + StripNewlines: true, + Features: []string{}, + } + + actual, _ := mark.CompileMarkdown(markdown, lib, filename, cfg) + test.EqualValues(strings.TrimSuffix(string(html), "\n"), strings.TrimSuffix(actual, "\n"), filename+" vs "+htmlname) + } } diff --git a/mermaid/mermaid.go b/mermaid/mermaid.go index 3096db0..8fa8b08 100644 --- a/mermaid/mermaid.go +++ b/mermaid/mermaid.go @@ -11,7 +11,7 @@ import ( "github.com/reconquest/pkg/log" ) -var renderTimeout = 90 * time.Second +var renderTimeout = 120 * time.Second func ProcessMermaidLocally(title string, mermaidDiagram []byte, scale float64) (attachment.Attachment, error) { ctx, cancel := context.WithTimeout(context.TODO(), renderTimeout) diff --git a/parser/confluenceids.go b/parser/confluenceids.go index 1f7acd2..ede06a1 100644 --- a/parser/confluenceids.go +++ b/parser/confluenceids.go @@ -11,7 +11,6 @@ type ConfluenceIDs struct { Values map[string]bool } - // https://github.com/yuin/goldmark/blob/d9c03f07f08c2d36f23afe52dda865f05320ac86/parser/parser.go#L75 func (s *ConfluenceIDs) Generate(value []byte, kind ast.NodeKind) []byte { value = util.TrimLeftSpace(value) diff --git a/renderer/fencedcodeblock.go b/renderer/fencedcodeblock.go index 50d5285..ab2bf04 100644 --- a/renderer/fencedcodeblock.go +++ b/renderer/fencedcodeblock.go @@ -3,11 +3,14 @@ package renderer import ( "fmt" "regexp" + "slices" "strings" "github.com/kovetskiy/mark/attachment" + "github.com/kovetskiy/mark/d2" "github.com/kovetskiy/mark/mermaid" "github.com/kovetskiy/mark/stdlib" + "github.com/kovetskiy/mark/types" "github.com/reconquest/pkg/log" "github.com/yuin/goldmark/ast" @@ -18,10 +21,9 @@ import ( type ConfluenceFencedCodeBlockRenderer struct { html.Config - Stdlib *stdlib.Lib - MermaidProvider string - MermaidScale float64 - Attachments attachment.Attacher + Stdlib *stdlib.Lib + MarkConfig types.MarkConfig + Attachments attachment.Attacher } var reBlockDetails = regexp.MustCompile( @@ -31,13 +33,12 @@ var reBlockDetails = regexp.MustCompile( ) // NewConfluenceRenderer creates a new instance of the ConfluenceRenderer -func NewConfluenceFencedCodeBlockRenderer(stdlib *stdlib.Lib, attachments attachment.Attacher, mermaidProvider string, mermaidScale float64, opts ...html.Option) renderer.NodeRenderer { +func NewConfluenceFencedCodeBlockRenderer(stdlib *stdlib.Lib, attachments attachment.Attacher, cfg types.MarkConfig, opts ...html.Option) renderer.NodeRenderer { return &ConfluenceFencedCodeBlockRenderer{ - Config: html.NewConfig(), - Stdlib: stdlib, - MermaidProvider: mermaidProvider, - MermaidScale: mermaidScale, - Attachments: attachments, + Config: html.NewConfig(), + Stdlib: stdlib, + MarkConfig: cfg, + Attachments: attachments, } } @@ -126,8 +127,39 @@ func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.Bu lval = append(lval, line.Value(source)...) } - if lang == "mermaid" && r.MermaidProvider == "mermaid-go" { - attachment, err := mermaid.ProcessMermaidLocally(title, lval, r.MermaidScale) + if lang == "d2" && slices.Contains(r.MarkConfig.Features, "d2") { + attachment, err := d2.ProcessD2(title, lval, r.MarkConfig.D2Scale) + if err != nil { + log.Debugf(nil, "error: %v", err) + return ast.WalkStop, err + } + r.Attachments.Attach(attachment) + err = r.Stdlib.Templates.ExecuteTemplate( + writer, + "ac:image", + struct { + Width string + Height string + Title string + Alt string + Attachment string + Url string + }{ + attachment.Width, + attachment.Height, + attachment.Name, + "", + attachment.Filename, + "", + }, + ) + + if err != nil { + return ast.WalkStop, err + } + + } else if lang == "mermaid" && slices.Contains(r.MarkConfig.Features, "mermaid") && r.MarkConfig.MermaidProvider == "mermaid-go" { + attachment, err := mermaid.ProcessMermaidLocally(title, lval, r.MarkConfig.MermaidScale) if err != nil { log.Debugf(nil, "error: %v", err) return ast.WalkStop, err diff --git a/testdata/codes-stripnewlines.html b/testdata/codes-stripnewlines.html index 63eff28..84a08fa 100644 --- a/testdata/codes-stripnewlines.html +++ b/testdata/codes-stripnewlines.html @@ -16,4 +16,53 @@ with multiple lines]]>B; A-->C; B-->D; - C-->D;]]> \ No newline at end of file + C-->D;]]>d2false transmitter: send + satellites -> transmitter: send + satellites -> transmitter: send + } + + online portal: { + ui: {shape: hexagon} + } + + data processor: { + storage: { + shape: cylinder + style.multiple: true + } + } + + cell tower.transmitter -> data processor.storage: phone logs +} + +user: { + shape: person + width: 130 +} + +user -> network.cell tower: make call +user -> network.online portal.ui: access { + style.stroke-dash: 3 +} + +api server -> network.online portal.ui: display +api server -> logs: persist +logs: {shape: page; style.multiple: true} + +network.data processor -> api server]]> diff --git a/testdata/codes.html b/testdata/codes.html index 733410b..c4608ce 100644 --- a/testdata/codes.html +++ b/testdata/codes.html @@ -17,4 +17,53 @@ with multiple lines]]>B; A-->C; B-->D; - C-->D;]]> \ No newline at end of file + C-->D;]]>d2false transmitter: send + satellites -> transmitter: send + satellites -> transmitter: send + } + + online portal: { + ui: {shape: hexagon} + } + + data processor: { + storage: { + shape: cylinder + style.multiple: true + } + } + + cell tower.transmitter -> data processor.storage: phone logs +} + +user: { + shape: person + width: 130 +} + +user -> network.cell tower: make call +user -> network.online portal.ui: access { + style.stroke-dash: 3 +} + +api server -> network.online portal.ui: display +api server -> logs: persist +logs: {shape: page; style.multiple: true} + +network.data processor -> api server]]> diff --git a/testdata/codes.md b/testdata/codes.md index 951f7e1..082f710 100644 --- a/testdata/codes.md +++ b/testdata/codes.md @@ -65,3 +65,56 @@ graph TD; B-->D; C-->D; ``` + +```d2 +vars: { + d2-config: { + layout-engine: elk + # Terminal theme code + theme-id: 300 + } +} +network: { + cell tower: { + satellites: { + shape: stored_data + style.multiple: true + } + + transmitter + + satellites -> transmitter: send + satellites -> transmitter: send + satellites -> transmitter: send + } + + online portal: { + ui: {shape: hexagon} + } + + data processor: { + storage: { + shape: cylinder + style.multiple: true + } + } + + cell tower.transmitter -> data processor.storage: phone logs +} + +user: { + shape: person + width: 130 +} + +user -> network.cell tower: make call +user -> network.online portal.ui: access { + style.stroke-dash: 3 +} + +api server -> network.online portal.ui: display +api server -> logs: persist +logs: {shape: page; style.multiple: true} + +network.data processor -> api server +``` diff --git a/types/types.go b/types/types.go new file mode 100644 index 0000000..49f5941 --- /dev/null +++ b/types/types.go @@ -0,0 +1,10 @@ +package types + +type MarkConfig struct { + MermaidProvider string + MermaidScale float64 + D2Scale float64 + DropFirstH1 bool + StripNewlines bool + Features []string +} diff --git a/util/cli.go b/util/cli.go index 7487024..4aed017 100644 --- a/util/cli.go +++ b/util/cli.go @@ -23,6 +23,7 @@ import ( "github.com/kovetskiy/mark/metadata" "github.com/kovetskiy/mark/page" "github.com/kovetskiy/mark/stdlib" + "github.com/kovetskiy/mark/types" "github.com/kovetskiy/mark/vfs" "github.com/reconquest/karma-go" "github.com/reconquest/pkg/log" @@ -217,7 +218,15 @@ func processFile( "the leading H1 heading will be excluded from the Confluence output", ) } - html, _ := mark.CompileMarkdown(markdown, stdlib, file, cmd.String("mermaid-provider"), cmd.Float("mermaid-scale"), cmd.Bool("drop-h1"), cmd.Bool("strip-linebreaks")) + + cfg := types.MarkConfig{ + MermaidProvider: cmd.String("mermaid-provider"), + MermaidScale: cmd.Float("mermaid-scale"), + DropFirstH1: cmd.Bool("drop-h1"), + StripNewlines: cmd.Bool("strip-linebreaks"), + Features: cmd.StringSlice("features"), + } + html, _ := mark.CompileMarkdown(markdown, stdlib, file, cfg) fmt.Println(html) return nil } @@ -289,8 +298,15 @@ func processFile( "the leading H1 heading will be excluded from the Confluence output", ) } + cfg := types.MarkConfig{ + MermaidProvider: cmd.String("mermaid-provider"), + MermaidScale: cmd.Float("mermaid-scale"), + DropFirstH1: cmd.Bool("drop-h1"), + StripNewlines: cmd.Bool("strip-linebreaks"), + Features: cmd.StringSlice("features"), + } - html, inlineAttachments := mark.CompileMarkdown(markdown, stdlib, file, cmd.String("mermaid-provider"), cmd.Float("mermaid-scale"), cmd.Bool("drop-h1"), cmd.Bool("strip-linebreaks")) + html, inlineAttachments := mark.CompileMarkdown(markdown, stdlib, file, cfg) // Resolve attachements detected from markdown _, err = attachment.ResolveAttachments( diff --git a/util/flags.go b/util/flags.go index e7db36b..31e29ba 100644 --- a/util/flags.go +++ b/util/flags.go @@ -55,7 +55,7 @@ var Flags = []cli.Flag{ Name: "strip-linebreaks", Value: false, Aliases: []string{"L"}, - Usage: "remove linebreaks inside of tags, to accomodate non-standard Confluence behavior", + Usage: "remove linebreaks inside of tags, to accommodate non-standard Confluence behavior", Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_STRIP_LINEBREAKS"), altsrctoml.TOML("strip_linebreaks", configFile)), }, &cli.BoolFlag{ @@ -127,12 +127,12 @@ var Flags = []cli.Flag{ altsrctoml.TOML("base_url", configFile)), }, &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Value: ConfigFilePath(), - Usage: "use the specified configuration file.", - TakesFile: true, - Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_CONFIG")), + Name: "config", + Aliases: []string{"c"}, + Value: ConfigFilePath(), + Usage: "use the specified configuration file.", + TakesFile: true, + Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_CONFIG")), Destination: &filename, }, &cli.BoolFlag{ @@ -184,4 +184,17 @@ var Flags = []cli.Flag{ Usage: "Avoids re-uploading pages that haven't changed since the last run.", Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_CHANGES_ONLY"), altsrctoml.TOML("changes_only", configFile)), }, + &cli.FloatFlag{ + Name: "d2-scale", + Value: 1.0, + Usage: "defines the scaling factor for d2 renderings.", + Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_D2_SCALE"), altsrctoml.TOML("d2_scale", configFile)), + }, + + &cli.StringSliceFlag{ + Name: "features", + Value: []string{"mermaid"}, + Usage: "Enables optional features. Current features: d2, mermaid", + Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_FEATURES"), altsrctoml.TOML("features", configFile)), + }, }