From 724a48c7dcd4c7a1996a14abede60336dd0278ef Mon Sep 17 00:00:00 2001 From: Matthias Schmieder Date: Mon, 23 Nov 2020 14:54:07 +0100 Subject: [PATCH] added support for exclude-filters, nested parents and document titles --- README.md | 71 +++++++++++++++++++++++++++++----------- cmd/root.go | 3 +- lib/markdown.go | 86 +++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 123 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index e6b8a4b..0a3a641 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ and add the binary in your local `PATH` ```shell curl -LO https://github.com/justmiles/go-markdown2confluence/releases/download/v3.1.2/go-markdown2confluence_3.1.2_linux_x86_64.tar.gz - + sudo tar -xzvf go-markdown2confluence_3.1.2_linux_x86_64.tar.gz -C /usr/local/bin/ markdown2confluence ``` @@ -20,12 +20,12 @@ and add the binary in your local `PATH` ```shell curl -LO https://github.com/justmiles/go-markdown2confluence/releases/download/v3.1.2/go-markdown2confluence_3.1.2_darwin_x86_64.tar.gz - + sudo tar -xzvf go-markdown2confluence_3.1.2_darwin_x86_64.tar.gz -C /usr/local/bin/ markdown2confluence ``` - Windows - + Download [the latest release](https://github.com/justmiles/go-markdown2confluence/releases/download/v3.1.2/go-markdown2confluence_3.1.2_windows_x86_64.tar.gz) and add to your system `PATH` ## Use with Docker @@ -54,20 +54,22 @@ For best practice we recommend you [authenticate using an API token](https://id. Push markdown files to Confluence Cloud Usage: -markdown2confluence [flags] (files or directories) + markdown2confluence [flags] Flags: --d, --debug Enable debug logging --e, --endpoint string Confluence endpoint. (Alternatively set CONFLUENCE_ENDPOINT environment variable) (default "https://mydomain.atlassian.net/wiki") --h, --help help for markdown2confluence --m, --modified-since int Only upload files that have modifed in the past n minutes - --parent string Optional parent page to nest content under --p, --password string Confluence password. (Alternatively set CONFLUENCE_PASSWORD environment variable) --s, --space string Space in which page should be created --t, --title string Set the page title on upload (defaults to filename without extension) --u, --username string Confluence username. (Alternatively set CONFLUENCE_USERNAME environment variable) --w, --hardwraps Render newlines as
- --version version for markdown2confluence + -d, --debug Enable debug logging + -e, --endpoint string Confluence endpoint. (Alternatively set CONFLUENCE_ENDPOINT environment variable) (default "https://mydomain.atlassian.net/wiki") + -x, --exclude strings list of exclude file patterns (regex) that will be applied on markdown file paths + -w, --hardwraps Render newlines as
+ -h, --help help for markdown2confluence + -m, --modified-since int Only upload files that have modifed in the past n minutes + --parent string Optional parent page to next content under + -p, --password string Confluence password. (Alternatively set CONFLUENCE_PASSWORD environment variable) + -s, --space string Space in which page should be created + -t, --title string Set the page title on upload (defaults to filename without extension) + --use-document-title Will use the Markdown document title (# Title) if available + -u, --username string Confluence username. (Alternatively set CONFLUENCE_USERNAME environment variable) + --version version for markdown2confluence ``` ## Examples @@ -75,25 +77,56 @@ Flags: Upload a local directory of markdown files called `markdown-files` to Confluence. ```shell -markdown2confluence --space 'MyTeamSpace' markdown-files +markdown2confluence \ + --space 'MyTeamSpace' \ + markdown-files ``` Upload the same directory, but only those modified in the last 30 minutes. This is particurlarly useful for cron jobs/recurring one-way syncs. ```shell -markdown2confluence --space 'MyTeamSpace' --modified-since 30 markdown-files +markdown2confluence \ + --space 'MyTeamSpace' \ + --modified-since 30 \ + markdown-files ``` Upload a single file ```shell -markdown2confluence --space 'MyTeamSpace' markdown-files/test.md +markdown2confluence \ + --space 'MyTeamSpace' \ + markdown-files/test.md ``` Upload a directory of markdown files in space `MyTeamSpace` under the parent page `API Docs` ```shell -markdown2confluence --space 'MyTeamSpace' --parent 'API Docs' markdown-files +markdown2confluence \ + --space 'MyTeamSpace' \ + --parent 'API Docs' \ + markdown-files +``` + +Upload a directory of markdown files in space `MyTeamSpace` under a _nested_ parent page `Docs/API` and _exclude_ mardown files/directories that match `.*generated.*` or `.*temp.md` + +```shell +markdown2confluence \ + --space 'MyTeamSpace' \ + --parent 'API/Docs' \ + --exclude '.*generated.*' \ + --exclude '.*temp.md' \ + markdown-files +``` + +Upload a directory of markdown files in space `MyTeamSpace` under the parent page `API Docs` and use the markdown _document-title_ instead of the filname as document title (if available) in Confluence. + +```shell +markdown2confluence \ + --space 'MyTeamSpace' \ + --parent 'API Docs' \ + --use-document-title \ + markdown-files ``` ## Enhancements diff --git a/cmd/root.go b/cmd/root.go index a104aa1..5961eea 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -22,10 +22,11 @@ func init() { rootCmd.PersistentFlags().StringVarP(&m.Endpoint, "endpoint", "e", lib.DefaultEndpoint, "Confluence endpoint. (Alternatively set CONFLUENCE_ENDPOINT environment variable)") rootCmd.PersistentFlags().StringVar(&m.Parent, "parent", "", "Optional parent page to next content under") rootCmd.PersistentFlags().BoolVarP(&m.Debug, "debug", "d", false, "Enable debug logging") + rootCmd.PersistentFlags().BoolVarP(&m.UseDocumentTitle, "use-document-title", "", false, "Will use the Markdown document title (# Title) if available") rootCmd.PersistentFlags().BoolVarP(&m.WithHardWraps, "hardwraps", "w", false, "Render newlines as
") rootCmd.PersistentFlags().IntVarP(&m.Since, "modified-since", "m", 0, "Only upload files that have modifed in the past n minutes") rootCmd.PersistentFlags().StringVarP(&m.Title, "title", "t", "", "Set the page title on upload (defaults to filename without extension)") - + rootCmd.PersistentFlags().StringSliceVarP(&m.ExcludeFilePatterns, "exclude", "x", []string{}, "list of exclude file patterns (regex) for that will be applied on markdown file paths") m.SourceEnvironmentVariables() } diff --git a/lib/markdown.go b/lib/markdown.go index 1ef366a..5a3f049 100644 --- a/lib/markdown.go +++ b/lib/markdown.go @@ -3,8 +3,11 @@ package lib import ( "bytes" "fmt" + "io/ioutil" + "log" "os" "path/filepath" + "regexp" "strings" "sync" "time" @@ -28,19 +31,21 @@ const ( // Markdown2Confluence stores the settings for each run type Markdown2Confluence struct { - Space string - Title string - File string - Ancestor string - Debug bool - WithHardWraps bool - Since int - Username string - Password string - Endpoint string - Parent string - SourceMarkdown []string - client *confluence.Client + Space string + Title string + File string + Ancestor string + Debug bool + UseDocumentTitle bool + WithHardWraps bool + Since int + Username string + Password string + Endpoint string + Parent string + SourceMarkdown []string + ExcludeFilePatterns []string + client *confluence.Client } // CreateClient returns a new markdown clietn @@ -100,6 +105,18 @@ func (m Markdown2Confluence) Validate() error { return nil } +func (m *Markdown2Confluence) IsExcluded(p string) bool { + for _, pattern := range m.ExcludeFilePatterns { + r := regexp.MustCompile(pattern) + if r.MatchString(p) { + fmt.Printf("excluding markdown file '%s': exclude pattern '%s'\n", p, pattern) + return true + } + } + + return false +} + // Run the sync func (m *Markdown2Confluence) Run() []error { var markdownFiles []MarkdownFile @@ -133,7 +150,7 @@ func (m *Markdown2Confluence) Run() []error { return err } - if strings.HasSuffix(path, ".md") { + if strings.HasSuffix(path, ".md") && !m.IsExcluded(path) { // Only include this file if it was modified m.Since minutes ago if m.Since != 0 { @@ -156,6 +173,13 @@ func (m *Markdown2Confluence) Run() []error { tempParents = deleteFromSlice(strings.Split(filepath.Dir(strings.TrimPrefix(filepath.ToSlash(path), filepath.ToSlash(f))), "/"), ".") } + if m.UseDocumentTitle == true { + doc_title := getDocumentTitle(path) + if doc_title != "" { + tempTitle = doc_title + } + } + md = MarkdownFile{ Path: path, Parents: tempParents, @@ -163,7 +187,8 @@ func (m *Markdown2Confluence) Run() []error { } if m.Parent != "" { - md.Parents = append([]string{m.Parent}, md.Parents...) + parents := strings.Split(m.Parent, "/") + md.Parents = append(parents, md.Parents...) md.Parents = deleteEmpty(md.Parents) } @@ -183,11 +208,17 @@ func (m *Markdown2Confluence) Run() []error { } if md.Title == "" { - md.Title = strings.TrimSuffix(filepath.Base(f), ".md") + if m.UseDocumentTitle == true { + md.Title = getDocumentTitle(f) + } + if md.Title == "" { + md.Title = strings.TrimSuffix(filepath.Base(f), ".md") + } } if m.Parent != "" { - md.Parents = append([]string{m.Parent}, md.Parents...) + parents := strings.Split(m.Parent, "/") + md.Parents = append(parents, md.Parents...) md.Parents = deleteEmpty(md.Parents) } @@ -299,3 +330,24 @@ func deleteFromSlice(s []string, del string) []string { } return s } + +func getDocumentTitle(p string) string { + // Read file to check for the content + file_content, err := ioutil.ReadFile(p) + if err != nil { + log.Fatal(err) + } + // Convert []byte to string and print to screen + text := string(file_content) + + // check if there is a + e := `^#\s+(.+)` + r := regexp.MustCompile(e) + result := r.FindStringSubmatch(text) + if len(result) > 1 { + // assign the Title to the matching group + return result[1] + } + + return "" +}