diff --git a/error_handler.go b/error_handler.go new file mode 100644 index 0000000..181b305 --- /dev/null +++ b/error_handler.go @@ -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) +} diff --git a/main.go b/main.go index 748d636..5364121 100644 --- a/main.go +++ b/main.go @@ -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 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 diff --git a/testdata/batch-tests/broken-test.md b/testdata/batch-tests/broken-test.md new file mode 100644 index 0000000..a37a5d6 --- /dev/null +++ b/testdata/batch-tests/broken-test.md @@ -0,0 +1,15 @@ +# a + +## b + +### c + +#### d + +##### e + +# f + +## g + +# This/is some_Heading.yml diff --git a/testdata/batch-tests/irfan-test.md b/testdata/batch-tests/irfan-test.md new file mode 100644 index 0000000..7335254 --- /dev/null +++ b/testdata/batch-tests/irfan-test.md @@ -0,0 +1,10 @@ + + + + +## Foo + +> **TL;DR:** Thingy! +> More stuff + +Foo diff --git a/testdata/batch-tests/mark-test.md b/testdata/batch-tests/mark-test.md new file mode 100644 index 0000000..a37a5d6 --- /dev/null +++ b/testdata/batch-tests/mark-test.md @@ -0,0 +1,15 @@ +# a + +## b + +### c + +#### d + +##### e + +# f + +## g + +# This/is some_Heading.yml diff --git a/testdata/batch-tests/rich-test.md b/testdata/batch-tests/rich-test.md new file mode 100644 index 0000000..d3ac661 --- /dev/null +++ b/testdata/batch-tests/rich-test.md @@ -0,0 +1,19 @@ + + + + +# a + +## b + +### c + +#### d + +##### e + +# f + +## g + +# This/is some_Heading.yml