diff --git a/README.md b/README.md
index 785a1ed..e9ff468 100644
--- a/README.md
+++ b/README.md
@@ -809,7 +809,7 @@ USAGE:
mark [global options]
VERSION:
- v15.1.0@b3a6f1efae97dfaa1400a3175cdd3377f8176e88
+ v15.2.0@1c82927c11a2999a39e5052aae6d2c65a201260c
DESCRIPTION:
Mark is a tool to update Atlassian Confluence pages from markdown. Documentation is available here: https://github.com/kovetskiy/mark
@@ -833,7 +833,7 @@ GLOBAL OPTIONS:
--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 for Confluence. Alternative option for base_url config field. [$MARK_BASE_URL]
- --config string, -c string use the specified configuration file. (default: "$HOME/.config/mark.toml") [$MARK_CONFIG]
+ --config string, -c string use the specified configuration file. (default: "${HOME}/.config/mark.toml") [$MARK_CONFIG]
--ci run on CI mode. It won't fail if files are not found. [$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]
@@ -842,7 +842,7 @@ GLOBAL OPTIONS:
--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. [$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, mkdocsadmonitions (default: "mermaid") [$MARK_FEATURES]
+ --features string [ --features string ] Enables optional features. Current features: d2, mermaid, mention, mkdocsadmonitions (default: "mermaid", "mention") [$MARK_FEATURES]
--insecure-skip-tls-verify skip TLS certificate verification (useful for self-signed certificates) [$MARK_INSECURE_SKIP_TLS_VERIFY]
--help, -h show help
--version, -v print the version
diff --git a/main.go b/main.go
index fd20301..5967b6c 100644
--- a/main.go
+++ b/main.go
@@ -10,7 +10,6 @@ import (
"github.com/urfave/cli/v3"
)
-
var (
version = "dev"
commit = "none"
diff --git a/markdown/markdown.go b/markdown/markdown.go
index 2ae7edc..d412670 100644
--- a/markdown/markdown.go
+++ b/markdown/markdown.go
@@ -70,6 +70,18 @@ func (c *ConfluenceExtension) Extend(m goldmark.Markdown) {
))
}
+ if slices.Contains(c.MarkConfig.Features, "mention") {
+ m.Parser().AddOptions(
+ parser.WithInlineParsers(
+ util.Prioritized(cparser.NewMentionParser(), 99),
+ ),
+ )
+
+ m.Renderer().AddOptions(renderer.WithNodeRenderers(
+ util.Prioritized(crenderer.NewConfluenceMentionRenderer(c.Stdlib), 100),
+ ))
+ }
+
m.Parser().AddOptions(parser.WithInlineParsers(
// Must be registered with a higher priority than goldmark's linkParser to make sure goldmark doesn't parse
// the tags.
diff --git a/markdown/markdown_test.go b/markdown/markdown_test.go
index 0c02c95..91fe041 100644
--- a/markdown/markdown_test.go
+++ b/markdown/markdown_test.go
@@ -64,7 +64,7 @@ func TestCompileMarkdown(t *testing.T) {
D2Scale: 1.0,
DropFirstH1: false,
StripNewlines: false,
- Features: []string{"mkdocsadmonitions"},
+ Features: []string{"mkdocsadmonitions", "mention"},
}
actual, _ := mark.CompileMarkdown(markdown, lib, filename, cfg)
@@ -106,7 +106,7 @@ func TestCompileMarkdownDropH1(t *testing.T) {
D2Scale: 1.0,
DropFirstH1: true,
StripNewlines: false,
- Features: []string{"mkdocsadmonitions"},
+ Features: []string{"mkdocsadmonitions", "mention"},
}
actual, _ := mark.CompileMarkdown(markdown, lib, filename, cfg)
@@ -137,7 +137,7 @@ func TestCompileMarkdownStripNewlines(t *testing.T) {
}
var variant string
switch filename {
- case "testdata/quotes.md", "testdata/codes.md", "testdata/newlines.md", "testdata/macro-include.md", "testdata/admonitions.md":
+ case "testdata/quotes.md", "testdata/codes.md", "testdata/newlines.md", "testdata/macro-include.md", "testdata/admonitions.md", "testdata/mention.md":
variant = "-stripnewlines"
default:
variant = ""
@@ -150,7 +150,7 @@ func TestCompileMarkdownStripNewlines(t *testing.T) {
D2Scale: 1.0,
DropFirstH1: false,
StripNewlines: true,
- Features: []string{"mkdocsadmonitions"},
+ Features: []string{"mkdocsadmonitions", "mention"},
}
actual, _ := mark.CompileMarkdown(markdown, lib, filename, cfg)
diff --git a/parser/mention.go b/parser/mention.go
new file mode 100644
index 0000000..7c90f75
--- /dev/null
+++ b/parser/mention.go
@@ -0,0 +1,60 @@
+package parser
+
+import (
+ "bytes"
+
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/text"
+)
+
+type Mention struct {
+ ast.BaseInline
+ Name []byte
+}
+
+func (m *Mention) Dump(source []byte, level int) {
+ ast.DumpHelper(m, source, level, map[string]string{
+ "Name": string(m.Name),
+ }, nil)
+}
+
+var KindMention = ast.NewNodeKind("Mention")
+
+func (m *Mention) Kind() ast.NodeKind {
+ return KindMention
+}
+
+func NewMention(name []byte) *Mention {
+ return &Mention{
+ Name: name,
+ }
+}
+
+type mentionParser struct {
+}
+
+func NewMentionParser() parser.InlineParser {
+ return &mentionParser{}
+}
+
+func (s *mentionParser) Trigger() []byte {
+ return []byte{'@'}
+}
+
+func (s *mentionParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
+ line, _ := block.PeekLine()
+ if len(line) < 3 || line[1] != '{' {
+ return nil
+ }
+
+ index := bytes.IndexByte(line, '}')
+ if index == -1 || index <= 2 {
+ return nil
+ }
+
+ name := line[2:index]
+ block.Advance(index + 1)
+
+ return NewMention(name)
+}
diff --git a/renderer/mention.go b/renderer/mention.go
new file mode 100644
index 0000000..8ad5328
--- /dev/null
+++ b/renderer/mention.go
@@ -0,0 +1,42 @@
+package renderer
+
+import (
+ "github.com/kovetskiy/mark/parser"
+ "github.com/kovetskiy/mark/stdlib"
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/util"
+)
+
+type ConfluenceMentionRenderer struct {
+ Stdlib *stdlib.Lib
+}
+
+func NewConfluenceMentionRenderer(stdlib *stdlib.Lib) renderer.NodeRenderer {
+ return &ConfluenceMentionRenderer{
+ Stdlib: stdlib,
+ }
+}
+
+func (r *ConfluenceMentionRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(parser.KindMention, r.renderMention)
+}
+
+func (r *ConfluenceMentionRenderer) renderMention(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+
+ n := node.(*parser.Mention)
+
+ err := r.Stdlib.Templates.ExecuteTemplate(w, "ac:link:user", struct {
+ Name string
+ }{
+ Name: string(n.Name),
+ })
+ if err != nil {
+ return ast.WalkStop, err
+ }
+
+ return ast.WalkContinue, nil
+}
diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go
index 28eeac8..5ef845d 100644
--- a/stdlib/stdlib.go
+++ b/stdlib/stdlib.go
@@ -5,14 +5,12 @@ import (
"text/template"
"github.com/kovetskiy/mark/confluence"
- "github.com/kovetskiy/mark/macro"
"github.com/reconquest/pkg/log"
"github.com/reconquest/karma-go"
)
type Lib struct {
- Macros []macro.Macro
Templates *template.Template
}
@@ -27,7 +25,6 @@ func New(api *confluence.API) (*Lib, error) {
return nil, err
}
- lib.Macros, err = macros(lib.Templates)
if err != nil {
return nil, err
}
@@ -35,31 +32,6 @@ func New(api *confluence.API) (*Lib, error) {
return &lib, nil
}
-func macros(templates *template.Template) ([]macro.Macro, error) {
- text := func(line ...string) []byte {
- return []byte(strings.Join(line, "\n"))
- }
-
- macros, _, err := macro.ExtractMacros(
- "",
- "",
- text(
- ``,
-
- // TODO(seletskiy): more macros here
- ),
-
- templates,
- )
- if err != nil {
- return nil, err
- }
-
- return macros, nil
-}
-
func templates(api *confluence.API) (*template.Template, error) {
text := func(line ...string) string {
return strings.Join(line, ``)
@@ -68,6 +40,9 @@ func templates(api *confluence.API) (*template.Template, error) {
templates := template.New(`stdlib`).Funcs(
template.FuncMap{
"user": func(name string) *confluence.User {
+ if api == nil {
+ return nil
+ }
user, err := api.GetUserByName(name)
if err != nil {
log.Error(err)
diff --git a/testdata/mention-stripnewlines.html b/testdata/mention-stripnewlines.html
new file mode 100644
index 0000000..d099d67
--- /dev/null
+++ b/testdata/mention-stripnewlines.html
@@ -0,0 +1,3 @@
+
Hello username! Unclosed @{mention Not a mention @{} Another one name Multiple one and two In a link mention me In a code @{username} In a block:
+falseIndented:
+false
\ No newline at end of file
diff --git a/testdata/mention.html b/testdata/mention.html
new file mode 100644
index 0000000..a26657e
--- /dev/null
+++ b/testdata/mention.html
@@ -0,0 +1,10 @@
+Hello username!
+Unclosed @{mention
+Not a mention @{}
+Another one name
+Multiple one and two
+In a link mention me
+In a code @{username}
+In a block:
+falseIndented:
+false
diff --git a/testdata/mention.md b/testdata/mention.md
new file mode 100644
index 0000000..c36f19c
--- /dev/null
+++ b/testdata/mention.md
@@ -0,0 +1,15 @@
+Hello @{username}!
+Unclosed @{mention
+Not a mention @{}
+Another one @{name}
+Multiple @{one} and @{two}
+In a link [mention @{me}](http://example.com)
+In a code `@{username}`
+In a block:
+```
+@{username}
+```
+
+Indented:
+
+ @{username}
\ No newline at end of file
diff --git a/types/types.go b/types/types.go
index 177aaaf..832daeb 100644
--- a/types/types.go
+++ b/types/types.go
@@ -1,9 +1,9 @@
package types
type MarkConfig struct {
- MermaidScale float64
- D2Scale float64
- DropFirstH1 bool
- StripNewlines bool
- Features []string
+ MermaidScale float64
+ D2Scale float64
+ DropFirstH1 bool
+ StripNewlines bool
+ Features []string
}
diff --git a/util/auth.go b/util/auth.go
index 4efdddd..fddaf85 100644
--- a/util/auth.go
+++ b/util/auth.go
@@ -67,7 +67,7 @@ func GetCredentials(
"flag or be stored in configuration file",
)
}
-
+
if baseURL == "" {
baseURL = url.Scheme + "://" + url.Host
}
diff --git a/util/cli.go b/util/cli.go
index 360b73c..55e1a86 100644
--- a/util/cli.go
+++ b/util/cli.go
@@ -186,8 +186,6 @@ func processFile(
return nil
}
- macros = append(macros, stdlib.Macros...)
-
for _, macro := range macros {
markdown, err = macro.Apply(markdown)
if err != nil {
diff --git a/util/flags.go b/util/flags.go
index 297a5fa..c10a7cc 100644
--- a/util/flags.go
+++ b/util/flags.go
@@ -192,8 +192,8 @@ var Flags = []cli.Flag{
&cli.StringSliceFlag{
Name: "features",
- Value: []string{"mermaid"},
- Usage: "Enables optional features. Current features: d2, mermaid, mkdocsadmonitions",
+ Value: []string{"mermaid", "mention"},
+ Usage: "Enables optional features. Current features: d2, mermaid, mention, mkdocsadmonitions",
Sources: cli.NewValueSourceChain(cli.EnvVar("MARK_FEATURES"), altsrctoml.TOML("features", altsrc.NewStringPtrSourcer(&filename))),
},
&cli.BoolFlag{