implement macros & includes

This commit is contained in:
Stanislav Seletskiy 2019-08-02 22:58:08 +03:00
parent e77e589494
commit 07a8e3f9d7
No known key found for this signature in database
GPG Key ID: E6B40F71C367E6B5
15 changed files with 870 additions and 149 deletions

124
README.md
View File

@ -30,10 +30,130 @@ File in extended format should follow specification
<page contents>
```
There can be any number of 'X-Parent' headers, if mark can't find specified
There can be any number of 'Parent' headers, if mark can't find specified
parent by title, it will be created.
## Usage:
Also, optional following headers are supported:
```markdown
<!-- Layout: (article|plain) -->
```
* (default) article: content will be put in narrow column for ease of
reading;
* plain: content will fill all page;
Mark supports Go templates, which can be includes into article by using path
to the template relative to current working dir, e.g.:
```markdown
<!-- Include: <path> -->
```
Templates may accept configuration data in YAML format which immediately
follows include tag:
```markdown
<!-- Include: <path>
<yaml-data> -->
```
Mark also supports macro definitions, which are defined as regexps which will
be replaced with specified template:
```markdown
<!-- Macro: <regexp>
Template: <path>
<yaml-data> -->
```
Capture groups can be defined in the macro's `<regexp>` which can be later
referenced in the `<yaml-data>` using `${<number>}` syntax.
By default, mark provides several built-in templates and macros:
* template `ac:status` to include badge-like text, which accepts following
parameters:
- Title: text to display in the badge
- Color: color to use as background/border for badge
- Grey
- Red
- Yellow
- Green
- Blue
- Subtle: specify to fill badge with background or not
- true
- false
See: https://confluence.atlassian.com/conf59/status-macro-792499207.html
* macro `@{...}` to mention user by name specified in the braces.
## Template & Macros Usecases
### Insert Disclamer
**disclamer.md**
```markdown
**NOTE**: this document is generated, do not edit manually.
```
**article.md**
```markdown
<!-- Space: TEST -->
<!-- Title: My Article -->
<!-- Include: disclamer.md -->
This is my article.
```
### Insert Status Badge
**article.md**
```markdown
<!-- Space: TEST -->
<!-- Title: TODO List -->
<!-- Macro: :done:
Template: ac:status
Title: DONE
Color: Green -->
<!-- Macro: :todo:
Template: ac:status
Title: TODO
Color: Blue -->
* :done: Write Article
* :todo: Publish Article
```
## Insert Jira Ticket
**ticket.md**
```markdown
[{{ .Ticket }}](http://myjira.atlassian.net/browse/{{ .Ticket }})
```
**article.md**
```markdown
<!-- Space: TEST -->
<!-- Title: TODO List -->
<!-- Macro: MYJIRA-\d+
Template: ticket.md
Ticket: ${0} -->
See task MYJIRA-123.
```
## Usage
```
mark [options] [-u <username>] [-p <password>] [-k] [-l <url>] -f <file>
mark [options] [-u <username>] [-p <password>] [-k] [-n] -c <file>

View File

@ -1 +0,0 @@
package main

View File

@ -1,29 +0,0 @@
package main
import "fmt"
type MacroLayout struct {
layout string
columns [][]byte
}
func (layout MacroLayout) Render() string {
switch layout.layout {
case "plain":
return string(layout.columns[0])
case "article":
fallthrough
default:
return fmt.Sprintf(
`<ac:layout>`+
`<ac:layout-section ac:type="two_right_sidebar">`+
`<ac:layout-cell>%s</ac:layout-cell>`+
`<ac:layout-cell></ac:layout-cell>`+
`</ac:layout-section>`+
`</ac:layout>`,
string(layout.columns[0]),
)
}
}

176
main.go
View File

@ -1,6 +1,7 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
@ -8,10 +9,12 @@ import (
"strings"
"github.com/kovetskiy/godocs"
"github.com/kovetskiy/lorg"
"github.com/kovetskiy/mark/pkg/confluence"
"github.com/kovetskiy/mark/pkg/log"
"github.com/kovetskiy/mark/pkg/mark"
"github.com/reconquest/cog"
"github.com/kovetskiy/mark/pkg/mark/includes"
"github.com/kovetskiy/mark/pkg/mark/macro"
"github.com/kovetskiy/mark/pkg/mark/stdlib"
"github.com/reconquest/karma-go"
)
@ -48,12 +51,51 @@ parent by title, it will be created.
Also, optional following headers are supported:
* <!-- Layout: <article|plain> -->
* <!-- Layout: (article|plain) -->
- (default) article: content will be put in narrow column for ease of
reading;
- plain: content will fill all page;
Mark supports Go templates, which can be includes into article by using path
to the template relative to current working dir, e.g.:
<!-- Include: <path> -->
Templates may accept configuration data in YAML format which immediately
follows include tag:
<!-- Include: <path>
<yaml-data> -->
Mark also supports macro definitions, which are defined as regexps which will
be replaced with specified template:
<!-- Macro: <regexp>
Template: <path>
<yaml-data> -->
Capture groups can be defined in the macro's <regexp> which can be later
referenced in the <yaml-data> using ${<number>} syntax.
By default, mark provides several built-in templates and macros:
* template 'ac:status' to include badge-like text, which accepts following
parameters:
- Title: text to display in the badge
- Color: color to use as background/border for badge
- Grey
- Yellow
- Red
- Blue
- Subtle: specify to fill badge with background or not
- true
- false
See: https://confluence.atlassian.com/conf59/status-macro-792499207.html
* macro '@{...}' to mention user by name specified in the braces.
Usage:
mark [options] [-u <username>] [-p <token>] [-k] [-l <url>] -f <file>
mark [options] [-u <username>] [-p <password>] [-k] [-b <url>] -f <file>
@ -80,31 +122,6 @@ Options:
`
)
var (
log *cog.Logger
)
func initlog(debug, trace bool) {
stderr := lorg.NewLog()
stderr.SetIndentLines(true)
stderr.SetFormat(
lorg.NewFormat("${time} ${level:[%s]:right:short} ${prefix}%s"),
)
log = cog.NewLogger(stderr)
if debug {
log.SetLevel(lorg.LevelDebug)
}
if trace {
log.SetLevel(lorg.LevelTrace)
}
mark.SetLogger(log)
confluence.SetLogger(log)
}
func main() {
args, err := godocs.Parse(usage, "mark 1.0", godocs.UsePager)
if err != nil {
@ -117,28 +134,13 @@ func main() {
editLock = args["-k"].(bool)
)
initlog(args["--debug"].(bool), args["--trace"].(bool))
log.Init(args["--debug"].(bool), args["--trace"].(bool))
config, err := LoadConfig(filepath.Join(os.Getenv("HOME"), ".config/mark"))
if err != nil {
log.Fatal(err)
}
markdownData, err := ioutil.ReadFile(targetFile)
if err != nil {
log.Fatal(err)
}
meta, err := mark.ExtractMeta(markdownData)
if err != nil {
log.Fatal(err)
}
if dryRun {
fmt.Println(string(mark.CompileMarkdown(markdownData)))
os.Exit(0)
}
creds, err := GetCredentials(args, config)
if err != nil {
log.Fatal(err)
@ -146,9 +148,60 @@ func main() {
api := confluence.NewAPI(creds.BaseURL, creds.Username, creds.Password)
markdown, err := ioutil.ReadFile(targetFile)
if err != nil {
log.Fatal(err)
}
meta, err := mark.ExtractMeta(markdown)
if err != nil {
log.Fatal(err)
}
stdlib, err := stdlib.New(api)
if err != nil {
log.Fatal(err)
}
templates := stdlib.Templates
var recurse bool
for {
templates, markdown, recurse, err = includes.ProcessIncludes(
markdown,
templates,
)
if err != nil {
log.Fatal(err)
}
if !recurse {
break
}
}
macros, markdown, err := macro.LoadMacros(markdown, templates)
if err != nil {
log.Fatal(err)
}
macros = append(macros, stdlib.Macros...)
for _, macro := range macros {
markdown, err = macro.Apply(markdown)
if err != nil {
log.Fatal(err)
}
}
if dryRun {
fmt.Println(mark.CompileMarkdown(markdown, stdlib))
os.Exit(0)
}
if creds.PageID != "" && meta != nil {
log.Warningf(
nil,
log.Warning(
`specified file contains metadata, ` +
`but it will be ignored due specified command line URL`,
)
@ -157,8 +210,7 @@ func main() {
}
if creds.PageID == "" && meta == nil {
log.Fatalf(
nil,
log.Fatal(
`specified file doesn't contain metadata ` +
`and URL is not specified via command line ` +
`or doesn't contain pageId GET-parameter`,
@ -195,18 +247,36 @@ func main() {
log.Fatalf(err, "unable to create/update attachments")
}
markdownData = mark.CompileAttachmentLinks(markdownData, attaches)
markdown = mark.CompileAttachmentLinks(markdown, attaches)
htmlData := mark.CompileMarkdown(markdownData)
html := mark.CompileMarkdown(markdown, stdlib)
err = api.UpdatePage(
target,
MacroLayout{meta.Layout, [][]byte{htmlData}}.Render(),
{
var buffer bytes.Buffer
err := stdlib.Templates.ExecuteTemplate(
&buffer,
"ac:layout",
struct {
Layout string
Body string
}{
Layout: meta.Layout,
Body: html,
},
)
if err != nil {
log.Fatal(err)
}
html = buffer.String()
}
err = api.UpdatePage(target, html)
if err != nil {
log.Fatal(err)
}
if editLock {
log.Infof(
nil,

View File

@ -12,8 +12,6 @@ import (
"strings"
"github.com/bndr/gopencils"
"github.com/kovetskiy/lorg"
"github.com/reconquest/cog"
"github.com/reconquest/karma-go"
)
@ -59,20 +57,6 @@ type AttachmentInfo struct {
} `json:"_links"`
}
func discarder() *lorg.Log {
stderr := lorg.NewLog()
stderr.SetOutput(ioutil.Discard)
return stderr
}
var (
log = cog.NewLogger(discarder())
)
func SetLogger(logger *cog.Logger) {
log = logger
}
type form struct {
buffer io.Reader
writer *multipart.Writer
@ -471,6 +455,35 @@ func (api *API) UpdatePage(
return nil
}
func (api *API) GetUserByName(name string) (*User, error) {
var response struct {
Results []struct {
User User
}
}
_, err := api.rest.
Res("search").
Res("user", &response).
Get(map[string]string{
"cql": fmt.Sprintf("user.fullname~%q", name),
})
if err != nil {
return nil, err
}
if len(response.Results) == 0 {
return nil, karma.
Describe("name", name).
Reason(
"user with given name is not found",
)
}
return &response.Results[0].User, nil
}
func (api *API) GetCurrentUser() (*User, error) {
var user User

100
pkg/log/log.go Normal file
View File

@ -0,0 +1,100 @@
package log
import (
"github.com/kovetskiy/lorg"
"github.com/reconquest/cog"
)
var (
log *cog.Logger
)
func Init(debug, trace bool) {
stderr := lorg.NewLog()
stderr.SetIndentLines(true)
stderr.SetFormat(
lorg.NewFormat("${time} ${level:[%s]:right:short} ${prefix}%s"),
)
log = cog.NewLogger(stderr)
if debug {
log.SetLevel(lorg.LevelDebug)
}
if trace {
log.SetLevel(lorg.LevelTrace)
}
}
func Fatalf(
reason interface{},
message string,
args ...interface{},
) {
log.Fatalf(reason, message, args...)
}
func Errorf(
reason interface{},
message string,
args ...interface{},
) {
log.Errorf(reason, message, args...)
}
func Warningf(
reason interface{},
message string,
args ...interface{},
) {
log.Warningf(reason, message, args...)
}
func Infof(
context interface{},
message string,
args ...interface{},
) {
log.Infof(context, message, args...)
}
func Debugf(
context interface{},
message string,
args ...interface{},
) {
log.Debugf(context, message, args...)
}
func Tracef(
context interface{},
message string,
args ...interface{},
) {
log.Tracef(context, message, args...)
}
func Fatal(values ...interface{}) {
log.Fatal(values...)
}
func Error(values ...interface{}) {
log.Error(values...)
}
func Warning(values ...interface{}) {
log.Warning(values...)
}
func Info(values ...interface{}) {
log.Info(values...)
}
func Debug(values ...interface{}) {
log.Debug(values...)
}
func Trace(values ...interface{}) {
log.Trace(values...)
}

View File

@ -5,7 +5,7 @@ import (
"strings"
"github.com/kovetskiy/mark/pkg/confluence"
"github.com/reconquest/faces/logger"
"github.com/kovetskiy/mark/pkg/log"
"github.com/reconquest/karma-go"
)
@ -56,7 +56,7 @@ func EnsureAncestry(
return parent, nil
}
logger.Debugf(
log.Debugf(
"empty pages under %q to be created: %s",
parent.Title,
strings.Join(rest, ` > `),

View File

@ -13,6 +13,7 @@ import (
"strings"
"github.com/kovetskiy/mark/pkg/confluence"
"github.com/kovetskiy/mark/pkg/log"
"github.com/reconquest/karma-go"
)

View File

@ -0,0 +1,155 @@
package includes
import (
"bytes"
"fmt"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
"text/template"
"gopkg.in/yaml.v2"
"github.com/kovetskiy/mark/pkg/log"
"github.com/reconquest/karma-go"
)
var (
reIncludeDirective = regexp.MustCompile(`(?s)<!-- Include: (\S+)(.*?)-->`)
)
func LoadTemplate(
path string,
templates *template.Template,
) (string, *template.Template, error) {
var (
name = strings.TrimSuffix(path, filepath.Ext(path))
facts = karma.Describe("name", name)
)
if template := templates.Lookup(name); template != nil {
return name, template, nil
}
var body []byte
body, err := ioutil.ReadFile(path)
if err != nil {
err = facts.Format(
err,
"unable to read template file",
)
return name, nil, err
}
templates, err = templates.New(name).Parse(string(body))
if err != nil {
err = facts.Format(
err,
"unable to parse template",
)
return name, nil, err
}
return name, templates, nil
}
func ProcessIncludes(
contents []byte,
templates *template.Template,
) (*template.Template, []byte, bool, error) {
vardump := func(
facts *karma.Context,
data map[string]interface{},
) *karma.Context {
for key, value := range data {
key = "var " + key
facts = facts.Describe(
key,
strings.ReplaceAll(
fmt.Sprint(value),
"\n",
"\n"+strings.Repeat(" ", len(key)+2),
),
)
}
return facts
}
var (
recurse bool
err error
)
contents = reIncludeDirective.ReplaceAllFunc(
contents,
func(spec []byte) []byte {
if err != nil {
return nil
}
groups := reIncludeDirective.FindSubmatch(spec)
var (
path, config = string(groups[1]), groups[2]
data = map[string]interface{}{}
facts = karma.Describe("path", path)
)
err = yaml.Unmarshal(config, &data)
if err != nil {
err = facts.
Describe("config", string(config)).
Format(
err,
"unable to unmarshal template data config",
)
return nil
}
log.Tracef(vardump(facts, data), "including template %q", path)
var name string
name, templates, err = LoadTemplate(path, templates)
if err != nil {
err = facts.Format(err, "unable to load template")
return nil
}
facts = facts.Describe("name", name)
template := templates.Lookup(string(name))
if template == nil {
err = facts.Reason("template not found")
return nil
}
var buffer bytes.Buffer
err = template.Execute(&buffer, data)
if err != nil {
err = vardump(facts, data).Format(
err,
"unable to execute template",
)
return nil
}
recurse = true
return buffer.Bytes()
},
)
return templates, contents, recurse, err
}

162
pkg/mark/macro/macro.go Normal file
View File

@ -0,0 +1,162 @@
package macro
import (
"bytes"
"fmt"
"regexp"
"strings"
"text/template"
"github.com/kovetskiy/mark/pkg/log"
"github.com/kovetskiy/mark/pkg/mark/includes"
"github.com/reconquest/karma-go"
"gopkg.in/yaml.v2"
)
var reMacroDirective = regexp.MustCompile(
`(?s)<!-- Macro: ([^\n]+)\n\s*Template: (\S+)\n(.*?)-->`,
)
type Macro struct {
Regexp *regexp.Regexp
Template *template.Template
Config map[string]interface{}
}
func (macro *Macro) Apply(
content []byte,
) ([]byte, error) {
var err error
content = macro.Regexp.ReplaceAllFunc(
content,
func(match []byte) []byte {
config := macro.configure(
macro.Config,
macro.Regexp.FindSubmatch(match),
)
var buffer bytes.Buffer
err = macro.Template.Execute(&buffer, config)
if err != nil {
err = karma.Format(
err,
"unable to execute macros template",
)
}
return buffer.Bytes()
},
)
return content, err
}
func (macro *Macro) configure(node interface{}, groups [][]byte) interface{} {
switch node := node.(type) {
case map[interface{}]interface{}:
for key, value := range node {
node[key] = macro.configure(value, groups)
}
return node
case map[string]interface{}:
for key, value := range node {
node[key] = macro.configure(value, groups)
}
return node
case []interface{}:
for key, value := range node {
node[key] = macro.configure(value, groups)
}
return node
case string:
for i, group := range groups {
node = strings.ReplaceAll(
node,
fmt.Sprintf("${%d}", i),
string(group),
)
}
return node
}
return node
}
func LoadMacros(
contents []byte,
templates *template.Template,
) ([]Macro, []byte, error) {
var err error
var macros []Macro
contents = reMacroDirective.ReplaceAllFunc(
contents,
func(spec []byte) []byte {
if err != nil {
return spec
}
groups := reMacroDirective.FindSubmatch(spec)
var (
expr, path, config = groups[1], string(groups[2]), groups[3]
macro Macro
)
_, macro.Template, err = includes.LoadTemplate(path, templates)
if err != nil {
err = karma.Format(err, "unable to load template")
return nil
}
facts := karma.
Describe("template", path).
Describe("expr", string(expr))
macro.Regexp, err = regexp.Compile(string(expr))
if err != nil {
err = facts.
Format(
err,
"unable to compile macros regexp",
)
return nil
}
err = yaml.Unmarshal(config, &macro.Config)
if err != nil {
err = facts.
Describe("config", string(config)).
Format(
err,
"unable to unmarshal template data config",
)
return nil
}
log.Tracef(
facts.Describe("config", macro.Config),
"loaded macro %q",
expr,
)
macros = append(macros, macro)
return []byte{}
},
)
return macros, contents, err
}

View File

@ -4,7 +4,7 @@ import (
"strings"
"github.com/kovetskiy/mark/pkg/confluence"
"github.com/reconquest/faces/logger"
"github.com/kovetskiy/mark/pkg/log"
"github.com/reconquest/karma-go"
)
@ -47,7 +47,7 @@ func ResolvePage(
path := meta.Parents
path = append(path, meta.Title)
logger.Debugf(
log.Debugf(
"resolving page path: ??? > %s",
strings.Join(path, ` > `),
)

View File

@ -2,14 +2,17 @@ package mark
import (
"bytes"
"fmt"
"regexp"
"github.com/kovetskiy/mark/pkg/log"
"github.com/kovetskiy/mark/pkg/mark/stdlib"
"github.com/russross/blackfriday"
)
type ConfluenceRenderer struct {
blackfriday.Renderer
Stdlib *stdlib.Lib
}
func (renderer ConfluenceRenderer) BlockCode(
@ -17,22 +20,16 @@ func (renderer ConfluenceRenderer) BlockCode(
text []byte,
lang string,
) {
out.WriteString(MacroCode{lang, text}.Render())
}
type MacroCode struct {
lang string
code []byte
}
func (code MacroCode) Render() string {
return fmt.Sprintf(
`<ac:structured-macro ac:name="code">`+
`<ac:parameter ac:name="language">%s</ac:parameter>`+
`<ac:parameter ac:name="collapse">false</ac:parameter>`+
`<ac:plain-text-body><![CDATA[%s]]></ac:plain-text-body>`+
`</ac:structured-macro>`,
code.lang, code.code,
renderer.Stdlib.Templates.ExecuteTemplate(
out,
"ac:code",
struct {
Language string
Text string
}{
lang,
string(text),
},
)
}
@ -41,12 +38,13 @@ func (code MacroCode) Render() string {
// <a href="ac:rich-text-body">ac:rich-text-body</a> for whatever reason.
func CompileMarkdown(
markdown []byte,
) []byte {
stdlib *stdlib.Lib,
) string {
log.Tracef(nil, "rendering markdown:\n%s", string(markdown))
colon := regexp.MustCompile(`---BLACKFRIDAY-COLON---`)
tags := regexp.MustCompile(`<(/?\S+):(\S+)>`)
tags := regexp.MustCompile(`<(/?\S+?):(\S+?)>`)
markdown = tags.ReplaceAll(
markdown,
@ -54,7 +52,7 @@ func CompileMarkdown(
)
renderer := ConfluenceRenderer{
blackfriday.HtmlRenderer(
Renderer: blackfriday.HtmlRenderer(
blackfriday.HTML_USE_XHTML|
blackfriday.HTML_USE_SMARTYPANTS|
blackfriday.HTML_SMARTYPANTS_FRACTIONS|
@ -62,6 +60,8 @@ func CompileMarkdown(
blackfriday.HTML_SMARTYPANTS_LATEX_DASHES,
"", "",
),
Stdlib: stdlib,
}
html := blackfriday.MarkdownOptions(
@ -88,5 +88,5 @@ func CompileMarkdown(
log.Tracef(nil, "rendered markdown to html:\n%s", string(html))
return html
return string(html)
}

View File

@ -4,28 +4,12 @@ import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"regexp"
"strings"
"github.com/kovetskiy/lorg"
"github.com/reconquest/cog"
"github.com/kovetskiy/mark/pkg/log"
)
func discarder() *lorg.Log {
stderr := lorg.NewLog()
stderr.SetOutput(ioutil.Discard)
return stderr
}
var (
log = cog.NewLogger(discarder())
)
func SetLogger(logger *cog.Logger) {
log = logger
}
const (
HeaderParent = `Parent`
HeaderSpace = `Space`

147
pkg/mark/stdlib/stdlib.go Normal file
View File

@ -0,0 +1,147 @@
package stdlib
import (
"strings"
"text/template"
"github.com/kovetskiy/mark/pkg/confluence"
"github.com/kovetskiy/mark/pkg/log"
"github.com/kovetskiy/mark/pkg/mark/macro"
"github.com/reconquest/karma-go"
)
type Lib struct {
Macros []macro.Macro
Templates *template.Template
}
func New(api *confluence.API) (*Lib, error) {
var (
lib Lib
err error
)
lib.Templates, err = templates(api)
if err != nil {
return nil, err
}
lib.Macros, err = macros(lib.Templates)
if err != nil {
return nil, err
}
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.LoadMacros(
[]byte(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, ``)
}
templates := template.New(`stdlib`).Funcs(
template.FuncMap{
"user": func(name string) *confluence.User {
user, err := api.GetUserByName(name)
if err != nil {
log.Error(err)
}
return user
},
// The only way to escape CDATA end marker ']]>' is to split it
// into two CDATA sections.
"cdata": func(data string) string {
return strings.ReplaceAll(
data,
"]]>",
"]]><![CDATA[]]]]><![CDATA[>",
)
},
},
)
var err error
for name, body := range map[string]string{
// This template is used to select whole article layout
`ac:layout`: text(
`{{ if eq .Layout "article" }}`,
/**/ `<ac:layout>`,
/**/ `<ac:layout-section ac:type="two_right_sidebar">`,
/**/ `<ac:layout-cell>{{ .Body }}</ac:layout-cell>`,
/**/ `<ac:layout-cell></ac:layout-cell>`,
/**/ `</ac:layout-section>`,
/**/ `</ac:layout>`,
`{{ else }}`,
/**/ `{{ .Body }}`,
`{{ end }}`,
),
// This template is used for rendering code in ```
`ac:code`: text(
`<ac:structured-macro ac:name="code">`,
`<ac:parameter ac:name="language">{{ .Language }}</ac:parameter>`,
`<ac:parameter ac:name="collapse">false</ac:parameter>`,
`<ac:plain-text-body><![CDATA[{{ .Text | cdata }}]]></ac:plain-text-body>`,
`</ac:structured-macro>`,
),
`ac:status`: text(
`<ac:structured-macro ac:name="status">`,
`<ac:parameter ac:name="colour">{{ or .Color "Grey" }}</ac:parameter>`,
`<ac:parameter ac:name="title">{{ or .Title .Color }}</ac:parameter>`,
`<ac:parameter ac:name="subtle">{{ or .Subtle false }}</ac:parameter>`,
`</ac:structured-macro>`,
),
`ac:link:user`: text(
`{{ with .Name | user }}`,
/**/ `<ac:link>`,
/**/ `<ri:user ri:account-id="{{ .AccountID }}"/>`,
/**/ `</ac:link>`,
`{{ else }}`,
/**/ `{{ .Name }}`,
`{{ end }}`,
),
// TODO(seletskiy): more templates here
} {
templates, err = templates.New(name).Parse(body)
if err != nil {
return nil, karma.
Describe("template", body).
Format(
err,
"unable to parse template",
)
}
}
return templates, nil
}

View File

@ -1 +0,0 @@
package main