rework attachments, improve docs

This commit is contained in:
Egor Kovetskiy 2020-07-25 11:02:54 +03:00
commit 951b276a9b
4 changed files with 94 additions and 43 deletions

View File

@ -3,38 +3,32 @@
Mark — tool for syncing your markdown documentation with Atlassian Confluence Mark — tool for syncing your markdown documentation with Atlassian Confluence
pages. pages.
This is very usable if you store documentation to your orthodox software in git This is very useful if you store documentation to your software in a Git
repository and don't want to do a handjob with updating Confluence page using repository and don't want to do an extra job of updating Confluence page using
fucking tinymce wysiwyg enterprise core editor. a tinymce wysiwyg enterprise core editor which always breaks everything.
You can store a user credentials in the configuration file, which should be Mark does the same but in a different way. Mark reads your markdown file, creates a Confluence page
located in ~/.config/mark with following format: if it's not found by its name, uploads attachments, translates Markdown into HTML and updates the
contents of the page via REST API. It's like you don't even need to create sections/pages in your
Confluence anymore, just use them in your Markdown documentation.
```toml Mark uses an extended file format, which, still being valid markdown,
username = "smith" contains several HTML-ish metadata headers, which can be used to locate page inside
password = "matrixishere"
base_url = "http://confluence.local"
```
*NOTE: Cloud Confluence also need to specify /wiki path to the base_url
parameter.*
Mark understands extended file format, which, still being valid markdown,
contains several metadata headers, which can be used to locate page inside
Confluence instance and update it accordingly. Confluence instance and update it accordingly.
File in extended format should follow specification File in the extended format should follow the specification:
```markdown ```markdown
<!-- Space: <space key> --> <!-- Space: <space key> -->
<!-- Parent: <parent 1> --> <!-- Parent: <parent 1> -->
<!-- Parent: <parent 2> --> <!-- Parent: <parent 2> -->
<!-- Title: <title> --> <!-- Title: <title> -->
<!-- Attachment: <local path> -->
<page contents> <page contents>
``` ```
There can be any number of '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. parent by title, Mark creates it.
Also, optional following headers are supported: Also, optional following headers are supported:
@ -53,14 +47,29 @@ to the template relative to current working dir, e.g.:
<!-- Include: <path> --> <!-- Include: <path> -->
``` ```
Templates may accept configuration data in YAML format which immediately Templates can accept configuration data in YAML format which immediately
follows include tag: follows the `Include` tag:
```markdown ```markdown
<!-- Include: <path> <!-- Include: <path>
<yaml-data> --> <yaml-data> -->
``` ```
Mark also supports attachments. The standard way involves declaring an
`Attachment` along with the other items in the header, then have any links
with the same path:
```markdown
<!-- Attachment: <path-to-image> -->
<beginning of page content>
An attached link is [here](<path-to-image>)
```
**NOTE**: Be careful with `Attachment`! If your path string is a subset of
another longer string or referenced in text, you may get undesired behavior.
Mark also supports macro definitions, which are defined as regexps which will Mark also supports macro definitions, which are defined as regexps which will
be replaced with specified template: be replaced with specified template:
@ -183,6 +192,16 @@ mark -h | --help
- `-v | --version` — Show version. - `-v | --version` — Show version.
- `-h | --help` — Show help screen and call 911. - `-h | --help` — Show help screen and call 911.
You can store user credentials in the configuration file, which should be
located in ~/.config/mark with the following format (TOML):
```toml
username = "smith"
password = "matrixishere"
# If you are using Confluence Cloud add the /wiki suffix to base_url
base_url = "http://confluence.local"
```
# Tricks # Tricks
## Confluence & Gitlab integration ## Confluence & Gitlab integration
@ -221,5 +240,5 @@ Apply:
and declare the following environment variables (secrets) and declare the following environment variables (secrets)
* `DOCS_WIKI_USERNAME` — Username for access to Confluence * `DOCS_WIKI_USERNAME` — Username for access to Confluence
* `DOCS_WIKI_PASSWORD` — Password for access to Confluence * `DOCS_WIKI_PASSWORD` — Password for access to Confluence
* `DOCS_WIKI_BASE_URL` — Base URL of Confluence (cloud users should add /wiki at the end) * `DOCS_WIKI_BASE_URL` — Base URL of Confluence (cloud users should add /wiki at the end)

View File

@ -324,8 +324,13 @@ func main() {
} }
} }
fmt.Printf( log.Infof(
"page successfully updated: %s\n", nil,
"page successfully updated: %s",
creds.BaseURL+target.Links.Full, creds.BaseURL+target.Links.Full,
) )
fmt.Println(
creds.BaseURL + target.Links.Full,
)
} }

View File

@ -28,20 +28,22 @@ type Attachment struct {
Path string Path string
Checksum string Checksum string
Link string Link string
Replace string
} }
func ResolveAttachments( func ResolveAttachments(
api *confluence.API, api *confluence.API,
page *confluence.PageInfo, page *confluence.PageInfo,
base string, base string,
names []string, replacements map[string]string,
) ([]Attachment, error) { ) ([]Attachment, error) {
attaches := []Attachment{} attaches := []Attachment{}
for _, name := range names { for replace, name := range replacements {
attach := Attachment{ attach := Attachment{
Name: name, Name: name,
Filename: strings.ReplaceAll(name, "/", "_"), Filename: strings.ReplaceAll(name, "/", "_"),
Path: filepath.Join(base, name), Path: filepath.Join(base, name),
Replace: replace,
} }
checksum, err := getChecksum(attach.Path) checksum, err := getChecksum(attach.Path)
@ -160,18 +162,18 @@ func ResolveAttachments(
func CompileAttachmentLinks(markdown []byte, attaches []Attachment) []byte { func CompileAttachmentLinks(markdown []byte, attaches []Attachment) []byte {
links := map[string]string{} links := map[string]string{}
names := []string{} replaces := []string{}
for _, attach := range attaches { for _, attach := range attaches {
uri, err := url.ParseRequestURI(attach.Link) uri, err := url.ParseRequestURI(attach.Link)
if err != nil { if err != nil {
links[attach.Name] = strings.ReplaceAll("&", "&amp;", attach.Link) links[attach.Replace] = strings.ReplaceAll("&", "&amp;", attach.Link)
} else { } else {
links[attach.Name] = uri.Path + links[attach.Replace] = uri.Path +
"?" + url.QueryEscape(uri.Query().Encode()) "?" + url.QueryEscape(uri.Query().Encode())
} }
names = append(names, attach.Name) replaces = append(replaces, attach.Replace)
} }
// sort by length so first items will have bigger length // sort by length so first items will have bigger length
@ -179,21 +181,45 @@ func CompileAttachmentLinks(markdown []byte, attaches []Attachment) []byte {
// attachments/a.jpg // attachments/a.jpg
// attachments/a.jpg.jpg // attachments/a.jpg.jpg
// so we replace longer and then shorter // so we replace longer and then shorter
sort.SliceStable(names, func(i, j int) bool { sort.SliceStable(replaces, func(i, j int) bool {
return len(names[i]) > len(names[j]) return len(replaces[i]) > len(replaces[j])
}) })
for _, name := range names { for _, replace := range replaces {
from := `attachment://` + name to := links[replace]
to := links[name]
log.Debugf(nil, "replacing: %q -> %q", from, to) found := false
if bytes.Contains(markdown, []byte("attachment://"+replace)) {
from := "attachment://" + replace
markdown = bytes.ReplaceAll( log.Debugf(nil, "replacing legacy link: %q -> %q", from, to)
markdown,
[]byte(from), markdown = bytes.ReplaceAll(
[]byte(to), markdown,
) []byte(from),
[]byte(to),
)
found = true
}
if bytes.Contains(markdown, []byte(replace)) {
from := replace
log.Debugf(nil, "replacing link: %q -> %q", from, to)
markdown = bytes.ReplaceAll(
markdown,
[]byte(from),
[]byte(to),
)
found = true
}
if !found {
log.Warningf(nil, "unused attachment: %s", replace)
}
} }
return markdown return markdown

View File

@ -23,7 +23,7 @@ type Meta struct {
Space string Space string
Title string Title string
Layout string Layout string
Attachments []string Attachments map[string]string
} }
var ( var (
@ -64,6 +64,7 @@ func ExtractMeta(data []byte) (*Meta, []byte, error) {
if meta == nil { if meta == nil {
meta = &Meta{} meta = &Meta{}
meta.Attachments = make(map[string]string)
} }
header := strings.Title(matches[1]) header := strings.Title(matches[1])
@ -87,7 +88,7 @@ func ExtractMeta(data []byte) (*Meta, []byte, error) {
meta.Layout = strings.TrimSpace(value) meta.Layout = strings.TrimSpace(value)
case HeaderAttachment: case HeaderAttachment:
meta.Attachments = append(meta.Attachments, value) meta.Attachments[value] = value
default: default:
log.Errorf( log.Errorf(