mark/main.go

337 lines
7.3 KiB
Go
Raw Normal View History

2015-09-19 23:51:49 +06:00
package main
import (
2019-08-02 22:58:08 +03:00
"bytes"
2015-09-19 23:51:49 +06:00
"fmt"
"io/ioutil"
"os"
"path/filepath"
2020-07-25 11:09:06 +03:00
"github.com/docopt/docopt-go"
2020-11-03 17:12:51 +03:00
"github.com/kovetskiy/lorg"
2019-04-08 22:44:27 +03:00
"github.com/kovetskiy/mark/pkg/confluence"
"github.com/kovetskiy/mark/pkg/mark"
2019-08-02 22:58:08 +03:00
"github.com/kovetskiy/mark/pkg/mark/includes"
"github.com/kovetskiy/mark/pkg/mark/macro"
"github.com/kovetskiy/mark/pkg/mark/stdlib"
2019-04-08 22:44:27 +03:00
"github.com/reconquest/karma-go"
2020-11-03 17:12:51 +03:00
"github.com/reconquest/pkg/log"
2015-09-19 23:51:49 +06:00
)
type Flags struct {
FileGlobPatten string `docopt:"-f"`
CompileOnly bool `docopt:"--compile-only"`
DryRun bool `docopt:"--dry-run"`
EditLock bool `docopt:"-k"`
DropH1 bool `docopt:"--drop-h1"`
MinorEdit bool `docopt:"--minor-edit"`
Color string `docopt:"--color"`
Debug bool `docopt:"--debug"`
Trace bool `docopt:"--trace"`
Username string `docopt:"-u"`
Password string `docopt:"-p"`
TargetURL string `docopt:"-l"`
BaseURL string `docopt:"--base-url"`
2021-04-02 13:15:54 -04:00
}
2015-09-19 23:51:49 +06:00
const (
version = "5.6"
usage = `mark - a tool for updating Atlassian Confluence pages from markdown.
2015-09-20 00:34:52 +06:00
Docs: https://github.com/kovetskiy/mark
2019-08-02 22:58:08 +03:00
2015-09-19 23:51:49 +06:00
Usage:
2019-04-22 13:35:55 +03:00
mark [options] [-u <username>] [-p <token>] [-k] [-l <url>] -f <file>
2019-03-26 14:42:26 +03:00
mark [options] [-u <username>] [-p <password>] [-k] [-b <url>] -f <file>
mark -v | --version
mark -h | --help
2015-09-19 23:51:49 +06:00
Options:
2019-03-26 14:42:26 +03:00
-u <username> Use specified username for updating Confluence page.
2019-04-22 13:35:55 +03:00
-p <token> Use specified token for updating Confluence page.
Specify - as password to read password from stdin.
2019-03-26 14:42:26 +03:00
-l <url> Edit specified Confluence page.
If -l is not specified, file should contain metadata (see
above).
-b --base-url <url> Base URL for Confluence.
Alternative option for base_url config field.
2021-04-02 13:15:54 -04:00
-f <file> Use specified markdown file(s) for converting to html. Supports file globbing patterns (needs to be quoted).
2019-03-26 14:42:26 +03:00
-k Lock page editing to current user only to prevent accidental
manual edits over Confluence Web UI.
--drop-h1 Don't include H1 headings in Confluence output.
--dry-run Resolve page and ancestry, show resulting HTML and exit.
--compile-only Show resulting HTML and don't update Confluence page content.
2021-01-04 15:42:34 +02:00
--minor-edit Don't send notifications while updating Confluence page.
2019-04-20 10:24:30 +03:00
--debug Enable debug logs.
2019-03-26 14:42:26 +03:00
--trace Enable trace logs.
--color <when> Display logs in color. Possible values: auto, never.
[default: auto]
2019-03-26 14:42:26 +03:00
-h --help Show this screen and call 911.
-v --version Show version.
2015-09-19 23:51:49 +06:00
`
)
func main() {
cmd, err := docopt.ParseArgs(usage, nil, version)
2015-09-19 23:51:49 +06:00
if err != nil {
panic(err)
}
var flags Flags
err = cmd.Bind(&flags)
if err != nil {
log.Fatal(err)
2021-04-02 13:15:54 -04:00
}
2015-09-19 23:51:49 +06:00
if flags.Debug {
2020-11-03 17:12:51 +03:00
log.SetLevel(lorg.LevelDebug)
}
if flags.Trace {
2020-11-03 17:12:51 +03:00
log.SetLevel(lorg.LevelTrace)
}
if flags.Color == "never" {
log.GetLogger().SetFormat(
lorg.NewFormat(
`${time:2006-01-02 15:04:05.000} ${level:%s:left:true} ${prefix}%s`,
),
)
log.GetLogger().SetOutput(os.Stderr)
}
2019-07-31 01:35:17 +03:00
config, err := LoadConfig(filepath.Join(os.Getenv("HOME"), ".config/mark"))
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatal(err)
2015-09-20 01:00:17 +06:00
}
creds, err := GetCredentials(flags, config)
2015-09-20 00:58:39 +06:00
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatal(err)
}
2019-08-02 22:58:08 +03:00
api := confluence.NewAPI(creds.BaseURL, creds.Username, creds.Password)
files, err := filepath.Glob(flags.FileGlobPatten)
2021-04-02 13:15:54 -04:00
if err != nil {
log.Fatal(err)
}
if len(files) == 0 {
log.Fatal("No files matched")
2021-04-02 13:15:54 -04:00
}
// Loop through files matched by glob pattern
for _, file := range files {
log.Infof(
nil,
"processing %s",
2021-04-02 13:15:54 -04:00
file,
)
target := processFile(file, api, flags, creds.PageID, creds.Username)
log.Infof(
nil,
"page successfully updated: %s",
creds.BaseURL+target.Links.Full,
)
fmt.Println(creds.BaseURL + target.Links.Full)
2021-04-02 13:15:54 -04:00
}
}
func processFile(
file string,
api *confluence.API,
flags Flags,
pageID string,
username string,
) *confluence.PageInfo {
2021-04-02 13:15:54 -04:00
markdown, err := ioutil.ReadFile(file)
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatal(err)
2015-09-20 00:58:39 +06:00
}
2019-08-08 23:41:26 +03:00
meta, markdown, err := mark.ExtractMeta(markdown)
2019-08-02 22:58:08 +03:00
if err != nil {
log.Fatal(err)
2015-09-20 00:58:39 +06:00
}
2019-08-02 22:58:08 +03:00
stdlib, err := stdlib.New(api)
2015-09-19 23:51:49 +06:00
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatal(err)
2015-09-19 23:51:49 +06:00
}
2019-08-02 22:58:08 +03:00
templates := stdlib.Templates
var recurse bool
for {
templates, markdown, recurse, err = includes.ProcessIncludes(
markdown,
templates,
)
if err != nil {
log.Fatal(err)
}
if !recurse {
break
}
}
2019-08-08 23:41:26 +03:00
macros, markdown, err := macro.ExtractMacros(markdown, templates)
2019-08-02 22:58:08 +03:00
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)
}
}
2020-12-04 00:28:52 +03:00
links, err := mark.ResolveRelativeLinks(api, meta, markdown, ".")
if err != nil {
log.Fatalf(err, "unable to resolve relative links")
}
2020-12-04 00:28:52 +03:00
markdown = mark.SubstituteLinks(markdown, links)
if flags.DryRun {
flags.CompileOnly = true
_, _, err := mark.ResolvePage(flags.DryRun, api, meta)
if err != nil {
log.Fatalf(err, "unable to resolve page location")
}
}
if flags.CompileOnly {
2019-08-02 22:58:08 +03:00
fmt.Println(mark.CompileMarkdown(markdown, stdlib))
os.Exit(0)
}
2021-04-02 13:15:54 -04:00
if pageID != "" && meta != nil {
2019-08-02 22:58:08 +03:00
log.Warning(
`specified file contains metadata, ` +
2019-04-08 22:20:31 +03:00
`but it will be ignored due specified command line URL`,
)
meta = nil
2015-09-19 23:51:49 +06:00
}
2021-04-02 13:15:54 -04:00
if pageID == "" && meta == nil {
2019-08-02 22:58:08 +03:00
log.Fatal(
`specified file doesn't contain metadata ` +
`and URL is not specified via command line ` +
`or doesn't contain pageId GET-parameter`,
)
2015-09-19 23:51:49 +06:00
}
2019-04-08 22:44:27 +03:00
var target *confluence.PageInfo
if meta != nil {
parent, page, err := mark.ResolvePage(flags.DryRun, api, meta)
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatalf(
karma.Describe("title", meta.Title).Reason(err),
"unable to resolve %s",
meta.Type,
2019-04-19 10:31:41 +03:00
)
}
if page == nil {
page, err = api.CreatePage(
meta.Space,
meta.Type,
parent,
meta.Title,
``,
)
if err != nil {
log.Fatalf(
err,
"can't create %s %q",
meta.Type,
meta.Title,
)
}
}
target = page
} else {
2021-04-02 13:15:54 -04:00
if pageID == "" {
2019-04-19 10:31:41 +03:00
log.Fatalf(nil, "URL should provide 'pageId' GET-parameter")
}
2021-04-02 13:15:54 -04:00
page, err := api.GetPageByID(pageID)
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatalf(err, "unable to retrieve page by id")
}
target = page
}
2019-04-20 10:24:30 +03:00
attaches, err := mark.ResolveAttachments(api, target, ".", meta.Attachments)
2019-04-19 17:35:05 +03:00
if err != nil {
log.Fatalf(err, "unable to create/update attachments")
}
2019-04-19 10:31:41 +03:00
2019-08-02 22:58:08 +03:00
markdown = mark.CompileAttachmentLinks(markdown, attaches)
2019-04-20 10:24:30 +03:00
if flags.DropH1 {
log.Info(
"the leading H1 heading will be excluded from the Confluence output",
)
markdown = mark.DropDocumentLeadingH1(markdown)
}
2019-08-02 22:58:08 +03:00
html := mark.CompileMarkdown(markdown, stdlib)
2019-04-20 10:24:30 +03:00
2019-08-02 22:58:08 +03:00
{
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, flags.MinorEdit, meta.Labels)
2015-09-19 23:51:49 +06:00
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatal(err)
2015-09-19 23:51:49 +06:00
}
if flags.EditLock {
2019-04-19 10:31:41 +03:00
log.Infof(
nil,
`edit locked on page %q by user %q to prevent manual edits`,
target.Title,
2021-04-02 13:15:54 -04:00
username,
)
err := api.RestrictPageUpdates(target, username)
if err != nil {
2019-04-19 10:31:41 +03:00
log.Fatal(err)
}
}
2021-04-02 13:15:54 -04:00
return target
2015-09-19 23:51:49 +06:00
}