Repository: kovetskiy/mark
Pull request #510 (2024-10-01 15:15:30)

Use query param for label deletion
(I struggle greatly with Golang, so please assume that if it's possible to do anything wrong, I have done it wrong.)

The Confluence API [is documented](https://developer.atlassian.com/cloud/confluence/rest/v1/api-group-content-labels/#api-wiki-rest-api-content-id-label-label-delete) as preferring this endpoint when the label has slashes in.

I have *not* checked whether it's necessary to URL query-parameter escape this; maybe it is necessary?

(The whitespace change appears to satisfy `go fmt`, but surprised me.)


Patch:
diff --git a/Dockerfile b/Dockerfile
index d82a335..183c3e7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:1.23.1 AS builder
+FROM golang:1.23.2 AS builder
 ENV GOPATH="/go"
 WORKDIR /go/src/github.com/kovetskiy/mark
 COPY / .
diff --git a/README.md b/README.md
index 47acdd2..be7e8db 100644
--- a/README.md
+++ b/README.md
@@ -775,7 +775,7 @@ USAGE:
    mark [global options] 
 
 VERSION:
-   11.1.0
+   11.2.0
 
 DESCRIPTION:
    Mark is a tool to update Atlassian Confluence pages from markdown. Documentation is available here: https://github.com/kovetskiy/mark
@@ -788,6 +788,7 @@ GLOBAL OPTIONS:
    --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 accomodate non-standard Confluence behavior (default: false) [$MARK_STRIP_LINEBREAK]
    --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]
diff --git a/confluence/api.go b/confluence/api.go
index 902dc7b..f9e4614 100644
--- a/confluence/api.go
+++ b/confluence/api.go
@@ -599,8 +599,8 @@ func (api *API) AddPageLabels(page *PageInfo, newLabels []string) (*LabelInfo, e
 func (api *API) DeletePageLabel(page *PageInfo, label string) (*LabelInfo, error) {
 
 	request, err := api.rest.Res(
-		"content/"+page.ID+"/label/"+label, &LabelInfo{},
-	).Delete()
+		"content/"+page.ID+"/label", &LabelInfo{},
+    ).SetQuery(map[string]string{"name": label}).Delete()
 	if err != nil {
 		return nil, err
 	}
diff --git a/go.mod b/go.mod
index 06a7c49..b9ccbf7 100644
--- a/go.mod
+++ b/go.mod
@@ -3,26 +3,26 @@ module github.com/kovetskiy/mark
 go 1.23
 
 require (
-	github.com/bmatcuk/doublestar/v4 v4.6.1
-	github.com/dreampuf/mermaid.go v0.0.18
+	github.com/bmatcuk/doublestar/v4 v4.7.1
+	github.com/dreampuf/mermaid.go v0.0.19
 	github.com/kovetskiy/gopencils v0.0.0-20240830111426-6b65e95c9cb0
 	github.com/kovetskiy/lorg v1.2.1-0.20240830111423-ba4fe8b6f7c4
 	github.com/reconquest/karma-go v1.5.0
 	github.com/reconquest/pkg v1.3.1-0.20240901105413-68c2adbf2b64
 	github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4
 	github.com/stretchr/testify v1.9.0
-	github.com/urfave/cli/v2 v2.27.4
-	github.com/yuin/goldmark v1.7.4
-	golang.org/x/tools v0.25.0
+	github.com/urfave/cli/v2 v2.27.5
+	github.com/yuin/goldmark v1.7.6
+	golang.org/x/tools v0.26.0
 	gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
 	github.com/BurntSushi/toml v1.4.0 // indirect
-	github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476 // indirect
+	github.com/chromedp/cdproto v0.0.0-20241003230502-a4a8f7c660df // indirect
 	github.com/chromedp/chromedp v0.10.0 // indirect
 	github.com/chromedp/sysutil v1.0.0 // indirect
-	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/gobwas/httphead v0.1.0 // indirect
 	github.com/gobwas/pool v0.2.1 // indirect
@@ -35,6 +35,6 @@ require (
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
 	github.com/zazab/zhash v0.0.0-20221031090444-2b0d50417446 // indirect
-	golang.org/x/sys v0.25.0 // indirect
+	golang.org/x/sys v0.26.0 // indirect
 	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
 )
diff --git a/go.sum b/go.sum
index 28014c9..b2fcc39 100644
--- a/go.sum
+++ b/go.sum
@@ -1,21 +1,21 @@
 github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
 github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
-github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
-github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
+github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q=
+github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
 github.com/chromedp/cdproto v0.0.0-20240801214329-3f85d328b335/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
-github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476 h1:VnjHsRXCRti7Av7E+j4DCha3kf68echfDzQ+wD11SBU=
-github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
+github.com/chromedp/cdproto v0.0.0-20241003230502-a4a8f7c660df h1:cbtSn19AtqQha1cxmP2Qvgd3fFMz51AeAEKLJMyEUhc=
+github.com/chromedp/cdproto v0.0.0-20241003230502-a4a8f7c660df/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
 github.com/chromedp/chromedp v0.10.0 h1:bRclRYVpMm/UVD76+1HcRW9eV3l58rFfy7AdBvKab1E=
 github.com/chromedp/chromedp v0.10.0/go.mod h1:ei/1ncZIqXX1YnAYDkxhD4gzBgavMEUu7JCKvztdomE=
 github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
 github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
-github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
-github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
+github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 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/dreampuf/mermaid.go v0.0.18 h1:PTDbfWrsi9JRIs1ocq65ajj8/eJnD1oVRsUKDEOmwKk=
-github.com/dreampuf/mermaid.go v0.0.18/go.mod h1:I8KXPKfpH96jHVi1BygPq3pRn1tk970UhnEYbgfRI5A=
+github.com/dreampuf/mermaid.go v0.0.19 h1:Xhv+RMwUvOYU8Pn7l+ePzOskGRgNJsOVOtrEMxYtM4g=
+github.com/dreampuf/mermaid.go v0.0.19/go.mod h1:Rc2VGHxFYg1tAiZirnKPIHRhijrWwxGpghdBeiDYHs4=
 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=
@@ -55,20 +55,20 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
-github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
+github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
+github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
-github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
-github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
+github.com/yuin/goldmark v1.7.6 h1:cZgJxVh5mL5cu8KOnwxvFJy5TFB0BHUskZZyq7TYbDg=
+github.com/yuin/goldmark v1.7.6/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
 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=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
-golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
-golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
+golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
 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=
diff --git a/main.go b/main.go
index ec70bc7..31f80ef 100644
--- a/main.go
+++ b/main.go
@@ -27,7 +27,7 @@ import (
 )
 
 const (
-	version     = "11.1.0"
+	version     = "11.2.0"
 	usage       = "A tool for updating Atlassian Confluence pages from markdown."
 	description = `Mark is a tool to update Atlassian Confluence pages from markdown. Documentation is available here: https://github.com/kovetskiy/mark`
 )
@@ -81,6 +81,12 @@ var flags = []cli.Flag{
 		Usage:   "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.",
 		EnvVars: []string{"MARK_H1_TITLE"},
 	}),
+	altsrc.NewBoolFlag(&cli.BoolFlag{
+		Name:    "title-append-generated-hash",
+		Value:   false,
+		Usage:   "appends a short hash generated from the path of the page (space, parents, and title) to the title",
+		EnvVars: []string{"MARK_TITLE_APPEND_GENERATED_HASH"},
+	}),
 	altsrc.NewBoolFlag(&cli.BoolFlag{
 		Name:    "minor-edit",
 		Value:   false,
@@ -309,7 +315,7 @@ func processFile(
 
 	parents := strings.Split(cCtx.String("parents"), cCtx.String("parents-delimiter"))
 
-	meta, markdown, err := metadata.ExtractMeta(markdown, cCtx.String("space"), cCtx.Bool("title-from-h1"), parents)
+	meta, markdown, err := metadata.ExtractMeta(markdown, cCtx.String("space"), cCtx.Bool("title-from-h1"), parents, cCtx.Bool("title-append-generated-hash"))
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -388,7 +394,7 @@ func processFile(
 		}
 	}
 
-	links, err := page.ResolveRelativeLinks(api, meta, markdown, filepath.Dir(file), cCtx.String("space"), cCtx.Bool("title-from-h1"), parents)
+	links, err := page.ResolveRelativeLinks(api, meta, markdown, filepath.Dir(file), cCtx.String("space"), cCtx.Bool("title-from-h1"), parents, cCtx.Bool("title-append-generated-hash"))
 	if err != nil {
 		log.Fatalf(err, "unable to resolve relative links")
 	}
diff --git a/metadata/metadata.go b/metadata/metadata.go
index 0365f99..280a074 100644
--- a/metadata/metadata.go
+++ b/metadata/metadata.go
@@ -3,6 +3,8 @@ package metadata
 import (
 	"bufio"
 	"bytes"
+	"crypto/sha256"
+	"fmt"
 	"regexp"
 	"strings"
 
@@ -44,7 +46,7 @@ var (
 	reHeaderPatternMacro = regexp.MustCompile(`<!-- Macro: .*`)
 )
 
-func ExtractMeta(data []byte, spaceFromCli string, titleFromH1 bool, parents []string) (*Meta, []byte, error) {
+func ExtractMeta(data []byte, spaceFromCli string, titleFromH1 bool, parents []string, titleAppendGeneratedHash bool) (*Meta, []byte, error) {
 	var (
 		meta   *Meta
 		offset int
@@ -164,6 +166,19 @@ func ExtractMeta(data []byte, spaceFromCli string, titleFromH1 bool, parents []s
 		meta.Parents = append(parents, meta.Parents...)
 	}
 
+	// deterministically generate a hash from the page's parents, space, and title
+	if titleAppendGeneratedHash {
+		path := strings.Join(append(meta.Parents, meta.Space, meta.Title), "/")
+		pathHash := sha256.Sum256([]byte(path))
+		// postfix is an 8-character hexadecimal string representation of the first 4 out of 32 bytes of the hash
+		meta.Title = fmt.Sprintf("%s - %x", meta.Title, pathHash[0:4])
+		log.Debugf(
+			nil,
+			"appended hash to page title: %s",
+			meta.Title,
+		)
+	}
+
 	return meta, data[offset:], nil
 }
 
diff --git a/page/link.go b/page/link.go
index 2ce740a..55357e1 100644
--- a/page/link.go
+++ b/page/link.go
@@ -34,6 +34,7 @@ func ResolveRelativeLinks(
 	spaceFromCli string,
 	titleFromH1 bool,
 	parents []string,
+	titleAppendGeneratedHash bool,
 ) ([]LinkSubstitution, error) {
 	matches := parseLinks(string(markdown))
 
@@ -46,7 +47,7 @@ func ResolveRelativeLinks(
 			match.filename,
 			match.hash,
 		)
-		resolved, err := resolveLink(api, base, match, spaceFromCli, titleFromH1, parents)
+		resolved, err := resolveLink(api, base, match, spaceFromCli, titleFromH1, parents, titleAppendGeneratedHash)
 		if err != nil {
 			return nil, karma.Format(err, "resolve link: %q", match.full)
 		}
@@ -71,6 +72,7 @@ func resolveLink(
 	spaceFromCli string,
 	titleFromH1 bool,
 	parents []string,
+	titleAppendGeneratedHash bool,
 ) (string, error) {
 	var result string
 
@@ -105,7 +107,7 @@ func resolveLink(
 
 		// This helps to determine if found link points to file that's
 		// not markdown or have mark required metadata
-		linkMeta, _, err := metadata.ExtractMeta(linkContents, spaceFromCli, titleFromH1, parents)
+		linkMeta, _, err := metadata.ExtractMeta(linkContents, spaceFromCli, titleFromH1, parents, titleAppendGeneratedHash)
 		if err != nil {
 			log.Errorf(
 				err,
diff --git a/renderer/blockquote.go b/renderer/blockquote.go
index 12100b3..30e8fd0 100644
--- a/renderer/blockquote.go
+++ b/renderer/blockquote.go
@@ -118,13 +118,12 @@ func ParseBlockQuoteType(node ast.Node, source []byte) BlockQuoteType {
 				// Classifying the next text type node (midNode) will confirm that.
 				if t == None {
 					midNode := node.NextSibling()
-					rightNode := midNode.NextSibling()
 
-					if midNode.Kind() == ast.KindText {
+					if midNode != nil && midNode.Kind() == ast.KindText {
+						rightNode := midNode.NextSibling()
 						midTextNode := midNode.(*ast.Text)
 						if rightNode != nil && rightNode.Kind() == ast.KindText {
 							rightTextNode := rightNode.(*ast.Text)
-
 							if string(n.Text(source)) == "[" && string(rightTextNode.Text(source)) == "]" {
 								t = ghAlertsClassifier.ClassifyingBlockQuote(string(midTextNode.Text(source)))
 							}


Test patch:
diff --git a/testdata/quotes-droph1.html b/testdata/quotes-droph1.html
index 8b98bc4..4fdba68 100644
--- a/testdata/quotes-droph1.html
+++ b/testdata/quotes-droph1.html
@@ -98,3 +98,7 @@ b</p>
 <blockquote>
 <p>[NOTE]</p>
 </blockquote>
+<blockquote>
+<p><strong>TL;DR:</strong> Thingy!
+More stuff</p>
+</blockquote>
diff --git a/testdata/quotes-stripnewlines.html b/testdata/quotes-stripnewlines.html
index ba9c973..e07a047 100644
--- a/testdata/quotes-stripnewlines.html
+++ b/testdata/quotes-stripnewlines.html
@@ -96,3 +96,6 @@
 <blockquote>
 <p>[NOTE]</p>
 </blockquote>
+<blockquote>
+<p><strong>TL;DR:</strong> Thingy! More stuff</p>
+</blockquote>
diff --git a/testdata/quotes.html b/testdata/quotes.html
index a2d2a00..15a2317 100644
--- a/testdata/quotes.html
+++ b/testdata/quotes.html
@@ -99,3 +99,7 @@ b</p>
 <blockquote>
 <p>[NOTE]</p>
 </blockquote>
+<blockquote>
+<p><strong>TL;DR:</strong> Thingy!
+More stuff</p>
+</blockquote>
diff --git a/testdata/quotes.md b/testdata/quotes.md
index d9de8e0..aeeb7c1 100644
--- a/testdata/quotes.md
+++ b/testdata/quotes.md
@@ -88,3 +88,8 @@
 > [Hey !NOTE]
 
 > [NOTE]
+
+
+
+> **TL;DR:** Thingy!
+> More stuff