add code & testing files for continue-on-error flag

- add continue-on-error flag as a command line option
    - if set, doesnt exit on error and continues
        processing other files that were passed in
- add fatalErrorHandler to handle fatal errors
    - if continue-on-error flag is set, does not exit
- add temporary tests for continue-on-error flag
    - add tests in batch-tests subdirectory
This commit is contained in:
iyz 2025-02-19 14:50:10 -05:00 committed by Manuel Rüger
parent ff015e2c24
commit 024259e480
6 changed files with 200 additions and 46 deletions

35
error_handler.go Normal file
View File

@ -0,0 +1,35 @@
package main
import (
"fmt"
"github.com/reconquest/pkg/log"
)
type FatalErrorHandler struct {
ContinueOnError bool
}
func NewErrorHandler(continueOnError bool) *FatalErrorHandler {
return &FatalErrorHandler{
ContinueOnError: continueOnError,
}
}
func (h *FatalErrorHandler) Handle(err error, format string, args ...interface{}) {
errorMesage := fmt.Sprintf(format, args...)
if err == nil {
if h.ContinueOnError {
log.Error(errorMesage)
return
}
log.Fatal(errorMesage)
}
if h.ContinueOnError {
log.Errorf(err, errorMesage)
return
}
log.Fatalf(err, errorMesage)
}

152
main.go
View File

@ -44,6 +44,12 @@ var flags = []cli.Flag{
TakesFile: true,
EnvVars: []string{"MARK_FILES"},
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "continue-on-error",
Value: false,
Usage: "dont exit if an error occurs while processing a file, continue processing remaining files.",
EnvVars: []string{"MARK_CONTINUE_ON_ERROR"},
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "compile-only",
Value: false,
@ -276,6 +282,11 @@ func RunMark(cCtx *cli.Context) error {
}
}
fatalErrorHandler := NewErrorHandler(cCtx.Bool("continue-on-error"))
fmt.Printf("Processing %d files\n", len(files))
fmt.Printf("continue-on-error: %t\n", cCtx.Bool("continue-on-error"))
// Loop through files matched by glob pattern
for _, file := range files {
log.Infof(
@ -284,9 +295,9 @@ func RunMark(cCtx *cli.Context) error {
file,
)
target := processFile(file, api, cCtx, creds.PageID, creds.Username)
target := processFile(file, api, cCtx, creds.PageID, creds.Username, fatalErrorHandler)
if target != nil && !(cCtx.Bool("dry-run") || cCtx.Bool("compile-only")) { // on dry-run or compile-only, the target is nil
if target != nil { // on dry-run or compile-only, the target is nil
log.Infof(
nil,
"page successfully updated: %s",
@ -304,10 +315,13 @@ func processFile(
cCtx *cli.Context,
pageID string,
username string,
fatalErrorHandler *FatalErrorHandler,
) *confluence.PageInfo {
markdown, err := os.ReadFile(file)
if err != nil {
log.Fatal(err)
fatalErrorHandler.Handle(err, "unable to read file %q", file)
return nil
// log.Fatal(err)
}
markdown = bytes.ReplaceAll(markdown, []byte("\r\n"), []byte("\n"))
@ -316,7 +330,9 @@ func processFile(
meta, markdown, err := metadata.ExtractMeta(markdown, cCtx.String("space"), cCtx.Bool("title-from-h1"), parents, cCtx.Bool("title-append-generated-hash"))
if err != nil {
log.Fatal(err)
fatalErrorHandler.Handle(err, "unable to extract metadata from file %q", file)
return nil
// log.Fatal(err)
}
if pageID != "" && meta != nil {
@ -329,29 +345,37 @@ func processFile(
}
if 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`,
)
fatalErrorHandler.Handle(nil, "specified file doesn't contain metadata and URL is not specified via command line or doesn't contain pageId GET-parameter")
return nil
// log.Fatal(
// `specified file doesn't contain metadata ` +
// `and URL is not specified via command line ` +
// `or doesn't contain pageId GET-parameter`,
// )
}
if meta.Space == "" {
log.Fatal(
"space is not set ('Space' header is not set and '--space' option is not set)",
)
fatalErrorHandler.Handle(nil, "space is not set ('Space' header is not set and '--space' option is not set)")
return nil
// log.Fatal(
// "space is not set ('Space' header is not set and '--space' option is not set)",
// )
}
if meta.Title == "" {
log.Fatal(
`page title is not set ('Title' header is not set ` +
`and '--title-from-h1' option and 'h1_title' config is not set or there is no H1 in the file)`,
)
fatalErrorHandler.Handle(nil, "page title is not set ('Title' header is not set and '--title-from-h1' option and 'h1_title' config is not set or there is no H1 in the file)")
return nil
// log.Fatal(
// `page title is not set ('Title' header is not set ` +
// `and '--title-from-h1' option and 'h1_title' config is not set or there is no H1 in the file)`,
// )
}
stdlib, err := stdlib.New(api)
if err != nil {
log.Fatal(err)
fatalErrorHandler.Handle(err, "unable to retrieve standard library")
return nil
// log.Fatal(err)
}
templates := stdlib.Templates
@ -366,7 +390,9 @@ func processFile(
templates,
)
if err != nil {
log.Fatal(err)
fatalErrorHandler.Handle(err, "unable to process includes")
return nil
// log.Fatal(err)
}
if !recurse {
@ -381,7 +407,9 @@ func processFile(
templates,
)
if err != nil {
log.Fatal(err)
fatalErrorHandler.Handle(err, "unable to extract macros")
return nil
// log.Fatal(err)
}
macros = append(macros, stdlib.Macros...)
@ -389,13 +417,17 @@ func processFile(
for _, macro := range macros {
markdown, err = macro.Apply(markdown)
if err != nil {
log.Fatal(err)
fatalErrorHandler.Handle(err, "unable to apply macro")
return nil
// log.Fatal(err)
}
}
links, err := page.ResolveRelativeLinks(api, meta, markdown, filepath.Dir(file), cCtx.String("space"), cCtx.Bool("title-from-h1"), parents, cCtx.Bool("title-append-generated-hash"))
if err != nil {
log.Fatalf(err, "unable to resolve relative links")
fatalErrorHandler.Handle(err, "unable to resolve relative links")
return nil
// log.Fatalf(err, "unable to resolve relative links")
}
markdown = page.SubstituteLinks(markdown, links)
@ -403,7 +435,9 @@ func processFile(
if cCtx.Bool("dry-run") {
_, _, err := page.ResolvePage(cCtx.Bool("dry-run"), api, meta)
if err != nil {
log.Fatalf(err, "unable to resolve page location")
fatalErrorHandler.Handle(err, "unable to resolve page location")
return nil
// log.Fatalf(err, "unable to resolve page location")
}
}
@ -413,7 +447,6 @@ func processFile(
"the leading H1 heading will be excluded from the Confluence output",
)
}
html, _ := mark.CompileMarkdown(markdown, stdlib, file, cCtx.String("mermaid-provider"), cCtx.Float64("mermaid-scale"), cCtx.Bool("drop-h1"), cCtx.Bool("strip-linebreaks"))
fmt.Println(html)
return nil
@ -424,11 +457,13 @@ func processFile(
if meta != nil {
parent, page, err := page.ResolvePage(cCtx.Bool("dry-run"), api, meta)
if err != nil {
log.Fatalf(
karma.Describe("title", meta.Title).Reason(err),
"unable to resolve %s",
meta.Type,
)
// log.Fatalf(
// karma.Describe("title", meta.Title).Reason(err),
// "unable to resolve %s",
// meta.Type,
// )
fatalErrorHandler.Handle(karma.Describe("title", meta.Title).Reason(err), "unable to resolve %s", meta.Type)
return nil
}
if page == nil {
@ -440,12 +475,14 @@ func processFile(
``,
)
if err != nil {
log.Fatalf(
err,
"can't create %s %q",
meta.Type,
meta.Title,
)
fatalErrorHandler.Handle(err, "can't create %s %q", meta.Type, meta.Title)
return nil
// log.Fatalf(
// err,
// "can't create %s %q",
// meta.Type,
// meta.Title,
// )
}
// (issues/139): A delay between the create and update call
// helps mitigate a 409 conflict that can occur when attempting
@ -456,12 +493,16 @@ func processFile(
target = page
} else {
if pageID == "" {
log.Fatalf(nil, "URL should provide 'pageId' GET-parameter")
fatalErrorHandler.Handle(nil, "URL should provide 'pageId' GET-parameter")
return nil
// log.Fatalf(nil, "URL should provide 'pageId' GET-parameter")
}
page, err := api.GetPageByID(pageID)
if err != nil {
log.Fatalf(err, "unable to retrieve page by id")
fatalErrorHandler.Handle(err, "unable to retrieve page by id")
return nil
// log.Fatalf(err, "unable to retrieve page by id")
}
target = page
@ -470,7 +511,9 @@ func processFile(
// Resolve attachments created from <!-- Attachment: --> directive
localAttachments, err := attachment.ResolveLocalAttachments(vfs.LocalOS, filepath.Dir(file), meta.Attachments)
if err != nil {
log.Fatalf(err, "unable to locate attachments")
fatalErrorHandler.Handle(err, "unable to locate attachments")
return nil
// log.Fatalf(err, "unable to locate attachments")
}
attaches, err := attachment.ResolveAttachments(
@ -479,7 +522,9 @@ func processFile(
localAttachments,
)
if err != nil {
log.Fatalf(err, "unable to create/update attachments")
fatalErrorHandler.Handle(err, "unable to create/update attachments")
return nil
// log.Fatalf(err, "unable to create/update attachments")
}
markdown = attachment.CompileAttachmentLinks(markdown, attaches)
@ -499,7 +544,9 @@ func processFile(
inlineAttachments,
)
if err != nil {
log.Fatalf(err, "unable to create/update attachments")
fatalErrorHandler.Handle(err, "unable to create/update attachments")
return nil
// log.Fatalf(err, "unable to create/update attachments")
}
{
@ -519,7 +566,9 @@ func processFile(
},
)
if err != nil {
log.Fatal(err)
fatalErrorHandler.Handle(err, "unable to execute layout template")
return nil
// log.Fatal(err)
}
html = buffer.String()
@ -567,11 +616,15 @@ func processFile(
if shouldUpdatePage {
err = api.UpdatePage(target, html, cCtx.Bool("minor-edit"), finalVersionMessage, meta.Labels, meta.ContentAppearance, meta.Emoji)
if err != nil {
log.Fatal(err)
fatalErrorHandler.Handle(err, "unable to update page")
return nil
// log.Fatal(err)
}
}
updateLabels(api, target, meta)
if !updateLabels(api, target, meta, fatalErrorHandler) { // on error updating labels, return nil
return nil
}
if cCtx.Bool("edit-lock") {
log.Infof(
@ -583,14 +636,16 @@ func processFile(
err := api.RestrictPageUpdates(target, username)
if err != nil {
log.Fatal(err)
fatalErrorHandler.Handle(err, "unable to restrict page updates")
return nil
// log.Fatal(err)
}
}
return target
}
func updateLabels(api *confluence.API, target *confluence.PageInfo, meta *metadata.Meta) {
func updateLabels(api *confluence.API, target *confluence.PageInfo, meta *metadata.Meta, fatalErrorHandler *FatalErrorHandler) bool {
labelInfo, err := api.GetPageLabels(target, "global")
if err != nil {
log.Fatal(err)
@ -613,16 +668,21 @@ func updateLabels(api *confluence.API, target *confluence.PageInfo, meta *metada
if len(addLabels) > 0 {
_, err = api.AddPageLabels(target, addLabels)
if err != nil {
log.Fatal(err)
fatalErrorHandler.Handle(err, "error adding labels")
return false
// log.Fatal(err)
}
}
for _, label := range delLabels {
_, err = api.DeletePageLabel(target, label)
if err != nil {
log.Fatal(err)
fatalErrorHandler.Handle(err, "error deleting labels")
return false
// log.Fatal(err)
}
}
return true
}
// Page has label but label not in Metadata

15
testdata/batch-tests/broken-test.md vendored Normal file
View File

@ -0,0 +1,15 @@
# a
## b
### c
#### d
##### e
# f
## g
# This/is some_Heading.yml

10
testdata/batch-tests/irfan-test.md vendored Normal file
View File

@ -0,0 +1,10 @@
<!-- Space: MySpace -->
<!-- Parent: Parent -->
<!-- Title: whatnot -->
## Foo
> **TL;DR:** Thingy!
> More stuff
Foo

15
testdata/batch-tests/mark-test.md vendored Normal file
View File

@ -0,0 +1,15 @@
# a
## b
### c
#### d
##### e
# f
## g
# This/is some_Heading.yml

19
testdata/batch-tests/rich-test.md vendored Normal file
View File

@ -0,0 +1,19 @@
<!-- Space: TEST2 -->
<!-- Title: H2 -->
<!-- Title: whatnot -->
# a
## b
### c
#### d
##### e
# f
## g
# This/is some_Heading.yml