Repository: kovetskiy/mark
Pull request #151 (2022-01-06 12:55:44)

Fix for subdirectories related attachments (issue #31)
Fixed by setting the base path to the current file directory. Also replaced map with an array for 'replacements' as map has the same key and value.


Patch:
diff --git a/README.md b/README.md
index 9848183..b45a3ed 100644
--- a/README.md
+++ b/README.md
@@ -438,7 +438,9 @@ mark -h | --help
 - `-c <path>` or `--config <path>` — Specify a path to the configuration file.
 - `-k` — Lock page editing to current user only to prevent accidental
     manual edits over Confluence Web UI.
+- `--space <space>` - Use specified space key. If not specified space ley must be set in a page metadata.
 - `--drop-h1` – Don't include H1 headings in Confluence output.
+- `--title-from-h1` - Extract page title from a leading H1 heading. If no H1 heading on a page then title must be set in a page metadata.
 - `--dry-run` — Show resulting HTML and don't update Confluence page content.
 - `--minor-edit` — Don't send notifications while updating Confluence page.
 - `--trace` — Enable trace logs.
diff --git a/main.go b/main.go
index fbd67b8..92f14ac 100644
--- a/main.go
+++ b/main.go
@@ -24,6 +24,7 @@ type Flags struct {
 	DryRun         bool   `docopt:"--dry-run"`
 	EditLock       bool   `docopt:"-k"`
 	DropH1         bool   `docopt:"--drop-h1"`
+	TitleFromH1    bool   `docopt:"--title-from-h1"`
 	MinorEdit      bool   `docopt:"--minor-edit"`
 	Color          string `docopt:"--color"`
 	Debug          bool   `docopt:"--debug"`
@@ -34,6 +35,7 @@ type Flags struct {
 	BaseURL        string `docopt:"--base-url"`
 	Config         string `docopt:"--config"`
 	Ci             bool   `docopt:"--ci"`
+	Space          string `docopt:"--space"`
 }
 
 const (
@@ -61,7 +63,11 @@ Options:
                         Supports file globbing patterns (needs to be quoted).
   -k                   Lock page editing to current user only to prevent accidental
                         manual edits over Confluence Web UI.
+  --space <space>      Use specified space key. If not specified space ley must
+                        be set in a page metadata.
   --drop-h1            Don't include H1 headings in Confluence output.
+  --title-from-h1      Extract page title from a leading H1 heading. If no H1 heading
+                        on a page then title must be set in a page metadata.
   --dry-run            Resolve page and ancestry, show resulting HTML and exit.
   --compile-only       Show resulting HTML and don't update Confluence page content.
   --minor-edit         Don't send notifications while updating Confluence page.
@@ -170,6 +176,24 @@ func processFile(
 		log.Fatal(err)
 	}
 
+	switch {
+	case meta.Space == "" && flags.Space == "":
+		log.Fatal("space is not set ('Space' header is not set and '--space' option is not set)")
+	case meta.Space == "" && flags.Space != "":
+		meta.Space = flags.Space
+	}
+
+	if meta.Title == "" && flags.TitleFromH1 {
+		meta.Title = mark.ExtractDocumentLeadingH1(markdown)
+	}
+
+	if meta.Title == "" {
+		log.Fatal(
+			`page title is not set ('Title' header is not set ` +
+				`and '--title-from-h1' option is not set or there is no H1 in the file)`,
+		)
+	}
+
 	stdlib, err := stdlib.New(api)
 	if err != nil {
 		log.Fatal(err)
@@ -289,7 +313,7 @@ func processFile(
 		target = page
 	}
 
-	attaches, err := mark.ResolveAttachments(api, target, ".", meta.Attachments)
+	attaches, err := mark.ResolveAttachments(api, target, filepath.Dir(file), meta.Attachments)
 	if err != nil {
 		log.Fatalf(err, "unable to create/update attachments")
 	}
diff --git a/pkg/mark/attachment.go b/pkg/mark/attachment.go
index 44f8185..b67efd5 100644
--- a/pkg/mark/attachment.go
+++ b/pkg/mark/attachment.go
@@ -35,17 +35,14 @@ func ResolveAttachments(
 	api *confluence.API,
 	page *confluence.PageInfo,
 	base string,
-	replacements map[string]string,
+	replacements []string,
 ) ([]Attachment, error) {
-	attaches := []Attachment{}
-	for replace, name := range replacements {
-		attach := Attachment{
-			Name:     name,
-			Filename: strings.ReplaceAll(name, "/", "_"),
-			Path:     filepath.Join(base, name),
-			Replace:  replace,
-		}
+	attaches, err := prepareAttachments(base, replacements)
+	if err != nil {
+		return nil, err
+	}
 
+	for _, attach := range attaches {
 		checksum, err := getChecksum(attach.Path)
 		if err != nil {
 			return nil, karma.Format(
@@ -55,8 +52,6 @@ func ResolveAttachments(
 		}
 
 		attach.Checksum = checksum
-
-		attaches = append(attaches, attach)
 	}
 
 	remotes, err := api.GetAttachments(page.ID)
@@ -160,6 +155,22 @@ func ResolveAttachments(
 	return attaches, nil
 }
 
+func prepareAttachments(base string, replacements []string) ([]Attachment, error) {
+	attaches := []Attachment{}
+	for _, name := range replacements {
+		attach := Attachment{
+			Name:     name,
+			Filename: strings.ReplaceAll(name, "/", "_"),
+			Path:     filepath.Join(base, name),
+			Replace:  name,
+		}
+
+		attaches = append(attaches, attach)
+	}
+
+	return attaches, nil
+}
+
 func CompileAttachmentLinks(markdown []byte, attaches []Attachment) []byte {
 	links := map[string]string{}
 	replaces := []string{}
diff --git a/pkg/mark/markdown.go b/pkg/mark/markdown.go
index e1017f4..de418b8 100644
--- a/pkg/mark/markdown.go
+++ b/pkg/mark/markdown.go
@@ -147,3 +147,14 @@ func DropDocumentLeadingH1(
 	markdown = h1.ReplaceAll(markdown, []byte(""))
 	return markdown
 }
+
+// ExtractDocumentLeadingH1 will extract leading H1 heading
+func ExtractDocumentLeadingH1(markdown []byte) string {
+	h1 := regexp.MustCompile(`^#[^#]\s*(.*)\s*\n`)
+	groups := h1.FindSubmatch(markdown)
+	if groups == nil {
+		return ""
+	} else {
+		return string(groups[1])
+	}
+}
diff --git a/pkg/mark/meta.go b/pkg/mark/meta.go
index 3cc6d51..0f01e95 100644
--- a/pkg/mark/meta.go
+++ b/pkg/mark/meta.go
@@ -29,7 +29,7 @@ type Meta struct {
 	Title       string
 	Layout      string
 	Sidebar     string
-	Attachments map[string]string
+	Attachments []string
 	Labels      []string
 }
 
@@ -72,7 +72,6 @@ func ExtractMeta(data []byte) (*Meta, []byte, error) {
 		if meta == nil {
 			meta = &Meta{}
 			meta.Type = "page" //Default if not specified
-			meta.Attachments = make(map[string]string)
 		}
 
 		header := strings.Title(matches[1])
@@ -103,7 +102,7 @@ func ExtractMeta(data []byte) (*Meta, []byte, error) {
 			meta.Sidebar = strings.TrimSpace(value)
 
 		case HeaderAttachment:
-			meta.Attachments[value] = value
+			meta.Attachments = append(meta.Attachments, value)
 
 		case HeaderLabel:
 			meta.Labels = append(meta.Labels, value)
@@ -128,19 +127,5 @@ func ExtractMeta(data []byte) (*Meta, []byte, error) {
 		return nil, data, nil
 	}
 
-	if meta.Space == "" {
-		return nil, nil, fmt.Errorf(
-			"space key is not set (%s header is not set)",
-			HeaderSpace,
-		)
-	}
-
-	if meta.Title == "" {
-		return nil, nil, fmt.Errorf(
-			"page title is not set (%s header is not set)",
-			HeaderTitle,
-		)
-	}
-
 	return meta, data[offset:], nil
 }


Test patch:
diff --git a/pkg/mark/attachment_test.go b/pkg/mark/attachment_test.go
new file mode 100644
index 0000000..594cfa6
--- /dev/null
+++ b/pkg/mark/attachment_test.go
@@ -0,0 +1,55 @@
+package mark
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+var (
+	replacements = []string{
+		"image1.jpg",
+		"images/image2.jpg",
+		"../image3.jpg",
+	}
+)
+
+func TestPrepareAttachmentsWithWorkDirBase(t *testing.T) {
+
+	attaches, err := prepareAttachments(".", replacements)
+	if err != nil {
+		println(err.Error())
+	}
+
+	assert.Equal(t, "image1.jpg", attaches[0].Name)
+	assert.Equal(t, "image1.jpg", attaches[0].Replace)
+	assert.Equal(t, "image1.jpg", attaches[0].Path)
+
+	assert.Equal(t, "images/image2.jpg", attaches[1].Name)
+	assert.Equal(t, "images/image2.jpg", attaches[1].Replace)
+	assert.Equal(t, "images/image2.jpg", attaches[1].Path)
+
+	assert.Equal(t, "../image3.jpg", attaches[2].Name)
+	assert.Equal(t, "../image3.jpg", attaches[2].Replace)
+	assert.Equal(t, "../image3.jpg", attaches[2].Path)
+
+	assert.Equal(t, len(attaches), 3)
+}
+
+func TestPrepareAttachmentsWithSubDirBase(t *testing.T) {
+
+	attaches, _ := prepareAttachments("a/b", replacements)
+
+	assert.Equal(t, "image1.jpg", attaches[0].Name)
+	assert.Equal(t, "image1.jpg", attaches[0].Replace)
+	assert.Equal(t, "a/b/image1.jpg", attaches[0].Path)
+
+	assert.Equal(t, "images/image2.jpg", attaches[1].Name)
+	assert.Equal(t, "images/image2.jpg", attaches[1].Replace)
+	assert.Equal(t, "a/b/images/image2.jpg", attaches[1].Path)
+
+	assert.Equal(t, "../image3.jpg", attaches[2].Name)
+	assert.Equal(t, "../image3.jpg", attaches[2].Replace)
+	assert.Equal(t, "a/image3.jpg", attaches[2].Path)
+
+	assert.Equal(t, len(attaches), 3)
+}
diff --git a/pkg/mark/markdown_test.go b/pkg/mark/markdown_test.go
index d831b88..9e936a7 100644
--- a/pkg/mark/markdown_test.go
+++ b/pkg/mark/markdown_test.go
@@ -48,3 +48,16 @@ func TestCompileMarkdown(t *testing.T) {
 		test.EqualValues(string(html), actual, filename+" vs "+htmlname)
 	}
 }
+
+func TestExtractDocumentLeadingH1(t *testing.T) {
+	filename := "testdata/header.md"
+
+	markdown, err := ioutil.ReadFile(filename)
+	if err != nil {
+		panic(err)
+	}
+
+	actual := ExtractDocumentLeadingH1(markdown)
+
+	assert.Equal(t, "a", actual)
+}