forked from kovetskiy/mark
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
331 lines (265 loc) · 7.94 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/kovetskiy/godocs"
"github.com/kovetskiy/mark/pkg/confluence"
"github.com/kovetskiy/mark/pkg/log"
"github.com/kovetskiy/mark/pkg/mark"
"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"
)
const (
usage = `mark - tool for updating Atlassian Confluence pages from markdown.
This is very usable if you store documentation to your orthodox software in git
repository and don't want to do a handjob with updating Confluence page using
fucking tinymce wysiwyg enterprise core editor.
You can store a user credentials in the configuration file, which should be
located in ~/.config/mark with following format:
username = "smith"
password = "matrixishere"
base_url = "http://confluence.local"
where 'smith' it's your username, 'matrixishere' it's your password and
'http://confluence.local' is base URL for your Confluence instance.
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.
File in extended format should follow specification:
<!-- Space: <space key> -->
<!-- Parent: <parent 1> -->
<!-- Parent: <parent 2> -->
<!-- Title: <title> -->
<page contents>
There can be any number of 'Parent' headers, if mark can't find specified
parent by title, it will be created.
Also, optional following headers are supported:
* <!-- 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 included 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, where <number> is
number of a capture group in regexp (${0} is used for entire regexp match), for
example:
<!-- Macro: MYJIRA-\d+
Template: ac:jira:ticket
Ticket: ${0} -->
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
* template 'ac:jira:ticket' to include JIRA ticket link. Parameters:
- Ticket: Jira ticket number like BUGS-123.
* 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>
mark [options] [-u <username>] [-p <password>] [-k] [-n] -c <file>
mark -v | --version
mark -h | --help
Options:
-u <username> Use specified username for updating Confluence page.
-p <token> Use specified token for updating Confluence page.
-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.
-f <file> Use specified markdown file for converting to html.
-k Lock page editing to current user only to prevent accidental
manual edits over Confluence Web UI.
--dry-run Resolve page and ancestry, show resulting HTML and exit.
--compile-only Show resulting HTML and don't update Confluence page content.
--debug Enable debug logs.
--trace Enable trace logs.
-h --help Show this screen and call 911.
-v --version Show version.
`
)
func main() {
args, err := godocs.Parse(usage, "mark 1.0", godocs.UsePager)
if err != nil {
panic(err)
}
var (
targetFile, _ = args["-f"].(string)
compileOnly = args["--compile-only"].(bool)
dryRun = args["--dry-run"].(bool)
editLock = args["-k"].(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)
}
creds, err := GetCredentials(args, config)
if err != nil {
log.Fatal(err)
}
api := confluence.NewAPI(creds.BaseURL, creds.Username, creds.Password)
markdown, err := ioutil.ReadFile(targetFile)
if err != nil {
log.Fatal(err)
}
meta, markdown, 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.ExtractMacros(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 {
compileOnly = true
_, _, err := mark.ResolvePage(dryRun, api, meta)
if err != nil {
log.Fatalf(err, "unable to resolve page location")
}
}
if compileOnly {
fmt.Println(mark.CompileMarkdown(markdown, stdlib))
os.Exit(0)
}
if creds.PageID != "" && meta != nil {
log.Warning(
`specified file contains metadata, ` +
`but it will be ignored due specified command line URL`,
)
meta = nil
}
if creds.PageID == "" && meta == nil {
log.Fatal(
`specified file doesn't contain metadata ` +
`and URL is not specified via command line ` +
`or doesn't contain pageId GET-parameter`,
)
}
var target *confluence.PageInfo
if meta != nil {
parent, page, err := mark.ResolvePage(dryRun, api, meta)
if err != nil {
log.Fatalf(
karma.Describe("title", meta.Title).Reason(err),
"unable to resolve page",
)
}
if page == nil {
page, err = api.CreatePage(meta.Space, parent, meta.Title, ``)
if err != nil {
log.Fatalf(
err,
"can't create page %q",
meta.Title,
)
}
}
target = page
} else {
if creds.PageID == "" {
log.Fatalf(nil, "URL should provide 'pageId' GET-parameter")
}
page, err := api.GetPageByID(creds.PageID)
if err != nil {
log.Fatalf(err, "unable to retrieve page by id")
}
target = page
}
attaches, err := mark.ResolveAttachments(api, target, ".", meta.Attachments)
if err != nil {
log.Fatalf(err, "unable to create/update attachments")
}
markdown = mark.CompileAttachmentLinks(markdown, attaches)
html := mark.CompileMarkdown(markdown, stdlib)
{
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,
`edit locked on page %q by user %q to prevent manual edits`,
target.Title,
creds.Username,
)
err := api.RestrictPageUpdates(
target,
creds.Username,
)
if err != nil {
log.Fatal(err)
}
}
fmt.Printf(
"page successfully updated: %s\n",
creds.BaseURL+target.Links.Full,
)
}