mirror of
https://github.com/kovetskiy/mark.git
synced 2026-03-14 06:07:36 +08:00
Move mention macro into a goldmark-parser/renderer
This commit is contained in:
parent
b85e40402c
commit
e294a7317e
@ -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
|
||||
|
||||
1
main.go
1
main.go
@ -10,7 +10,6 @@ import (
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
version = "dev"
|
||||
commit = "none"
|
||||
|
||||
@ -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 <ac:*/> tags.
|
||||
|
||||
@ -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)
|
||||
|
||||
60
parser/mention.go
Normal file
60
parser/mention.go
Normal file
@ -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)
|
||||
}
|
||||
42
renderer/mention.go
Normal file
42
renderer/mention.go
Normal file
@ -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
|
||||
}
|
||||
@ -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(
|
||||
`<!-- Macro: @\{([^}]+)\}`,
|
||||
` Template: ac:link:user`,
|
||||
` Name: ${1} -->`,
|
||||
|
||||
// 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)
|
||||
|
||||
3
testdata/mention-stripnewlines.html
vendored
Normal file
3
testdata/mention-stripnewlines.html
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<p>Hello username! Unclosed @{mention Not a mention @{} Another one name Multiple one and two In a link <a href="http://example.com">mention me</a> In a code <code>@{username}</code> In a block:</p>
|
||||
<ac:structured-macro ac:name="code"><ac:parameter ac:name="language"></ac:parameter><ac:parameter ac:name="collapse">false</ac:parameter><ac:plain-text-body><![CDATA[@{username}]]></ac:plain-text-body></ac:structured-macro><p>Indented:</p>
|
||||
<ac:structured-macro ac:name="code"><ac:parameter ac:name="language"></ac:parameter><ac:parameter ac:name="collapse">false</ac:parameter><ac:plain-text-body><![CDATA[@{username}]]></ac:plain-text-body></ac:structured-macro>
|
||||
10
testdata/mention.html
vendored
Normal file
10
testdata/mention.html
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<p>Hello username!
|
||||
Unclosed @{mention
|
||||
Not a mention @{}
|
||||
Another one name
|
||||
Multiple one and two
|
||||
In a link <a href="http://example.com">mention me</a>
|
||||
In a code <code>@{username}</code>
|
||||
In a block:</p>
|
||||
<ac:structured-macro ac:name="code"><ac:parameter ac:name="language"></ac:parameter><ac:parameter ac:name="collapse">false</ac:parameter><ac:plain-text-body><![CDATA[@{username}]]></ac:plain-text-body></ac:structured-macro><p>Indented:</p>
|
||||
<ac:structured-macro ac:name="code"><ac:parameter ac:name="language"></ac:parameter><ac:parameter ac:name="collapse">false</ac:parameter><ac:plain-text-body><![CDATA[@{username}]]></ac:plain-text-body></ac:structured-macro>
|
||||
15
testdata/mention.md
vendored
Normal file
15
testdata/mention.md
vendored
Normal file
@ -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}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ func GetCredentials(
|
||||
"flag or be stored in configuration file",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
if baseURL == "" {
|
||||
baseURL = url.Scheme + "://" + url.Host
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user