Skip to content

Commit

Permalink
Support for relative links (#33)
Browse files Browse the repository at this point in the history
* Support for relative links

Fixes #25

* Error logging fixes

* Better regexp
  • Loading branch information
Hi-Fi committed Nov 30, 2020
1 parent bcf2acb commit 63fe97b
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 6 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ require (
github.com/reconquest/pkg v0.0.0-20201028091908-8e9a5e0226ef
github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4
github.com/russross/blackfriday v1.5.2
github.com/stretchr/testify v1.5.1 // indirect
github.com/stretchr/testify v1.5.1
gopkg.in/yaml.v2 v2.2.8
)
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwn
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/kovetskiy/gopencils v0.0.0-20201103141120-610929377f9b h1:+PnJcuiUVcU3ixOvpvhyjswKPkxBVN+a2CaFCNNBfvw=
github.com/kovetskiy/gopencils v0.0.0-20201103141120-610929377f9b/go.mod h1:rn9YsgK4kxBDPZn+hOwSmg6MdtWfF2ejC3tvgDjWyBM=
github.com/kovetskiy/gopencils v0.0.0-20201105104258-2a0bfdd710fb h1:e8UwTXL3Nauw5T847OMHZRhAfQy4ntgQ7PUwgZ2ct4w=
github.com/kovetskiy/gopencils v0.0.0-20201105104258-2a0bfdd710fb/go.mod h1:rn9YsgK4kxBDPZn+hOwSmg6MdtWfF2ejC3tvgDjWyBM=
github.com/kovetskiy/ko v0.0.0-20190324102900-26b8dd0988bf h1:4QsqgCcPoqDB91dcp4GffoV6TjwfVURaWpjKWFi0ae0=
Expand Down
6 changes: 6 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ func main() {
}
}

links, err := mark.ResolveRelativeLinks(api, markdown, ".")
if err != nil {
log.Fatalf(err, "unable to resolve relative links")
}
markdown = mark.ReplaceRelativeLinks(markdown, links)

if dryRun {
compileOnly = true

Expand Down
8 changes: 5 additions & 3 deletions pkg/confluence/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ type API struct {

// it's deprecated accordingly to Atlassian documentation,
// but it's only way to set permissions
json *gopencils.Resource
json *gopencils.Resource
BaseURL string
}

type PageInfo struct {
Expand Down Expand Up @@ -87,8 +88,9 @@ func NewAPI(baseURL string, username string, password string) *API {
}

return &API{
rest: rest,
json: json,
rest: rest,
json: json,
BaseURL: strings.TrimSuffix(baseURL, "/"),
}
}

Expand Down
105 changes: 105 additions & 0 deletions pkg/mark/link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package mark

import (
"bytes"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"regexp"

"github.com/kovetskiy/mark/pkg/confluence"
)

type Link struct {
MDLink string
Link string
}

// ResolveRelativeLinks finds links in the markdown, and replaces one pointing
// to other Markdowns either by own link (if not created to Confluence yet) or
// witn actual Confluence link
func ResolveRelativeLinks(
api *confluence.API,
markdown []byte,
base string,
) (links []Link, collectedErrors error) {
currentMarkdownMetadata, onlyMarkdown, err := ExtractMeta(markdown)
if err != nil {
return links, fmt.Errorf("unable to get metadata from handled markdown file. Error %w", err)
}

currentPageLinkString, collectedErrors := getConfluenceLink(api, currentMarkdownMetadata.Space, currentMarkdownMetadata.Title, collectedErrors)

submatchall := collectLinksFromMarkdown(string(onlyMarkdown))

for _, element := range submatchall {
link := Link{
MDLink: element[1],
Link: currentPageLinkString,
}
// If link points to markdown like target, we build link for that in Confluence
if len(element[2]) > 0 {
possibleMDFile := element[2]
filepath := filepath.Join(base, possibleMDFile)
if _, err := os.Stat(filepath); err == nil {
linkMarkdown, err := ioutil.ReadFile(filepath)
if err != nil {
collectedErrors = fmt.Errorf("%w\n unable to read markdown file "+filepath, collectedErrors)
continue
}
// This helps to determine if found link points to file that's not markdown
// or have mark required metadata
meta, _, err := ExtractMeta(linkMarkdown)
if err != nil {
collectedErrors = fmt.Errorf("%w\n unable to get metadata from markdown file "+filepath, collectedErrors)
continue
}

link.Link, collectedErrors = getConfluenceLink(api, meta.Space, meta.Title, collectedErrors)
}
}

if len(element[3]) > 0 {
link.Link = currentPageLinkString + "#" + element[2]
}

links = append(links, link)
}
return links, collectedErrors
}

// ReplaceRelativeLinks replaces relative links between md files (in same
// directory structure) with links working in Confluence
func ReplaceRelativeLinks(markdown []byte, links []Link) []byte {
for _, link := range links {
markdown = bytes.ReplaceAll(
markdown,
[]byte(fmt.Sprintf("](%s)", link.MDLink)),
[]byte(fmt.Sprintf("](%s)", link.Link)),
)
}
return markdown
}

// collectLinksFromMarkdown collects all links from given markdown file
// (including images and external links)
func collectLinksFromMarkdown(markdown string) [][]string {
re := regexp.MustCompile("\\[[^\\]]+\\]\\((([^\\)#]+)?#?([^\\)]+)?)\\)")
return re.FindAllStringSubmatch(markdown, -1)
}

// getConfluenceLink build (to be) link for Conflunce, and tries to verify from API if there's real link available
func getConfluenceLink(api *confluence.API, space, title string, collectedErrors error) (string, error) {
link := fmt.Sprintf("%s/display/%s/%s", api.BaseURL, space, url.QueryEscape(title))
confluencePage, err := api.FindPage(space, title)
if err != nil {
collectedErrors = fmt.Errorf("%w\n "+err.Error(), collectedErrors)
} else if confluencePage != nil {
// Needs baseURL, as REST api response URL doesn't contain subpath ir confluence is server from that
link = api.BaseURL + confluencePage.Links.Full
}

return link, collectedErrors
}
33 changes: 33 additions & 0 deletions pkg/mark/link_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package mark

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestLinkFind(t *testing.T) {
markdown := `
[example1](../path/to/example.md#second-heading)
[example2](../path/to/example.md)
[example3](#heading-in-document)
[Text link that should be put as attachment](../path/to/example.txt)
[Image link that should be put as attachment](../path/to/example.png)
`

links := collectLinksFromMarkdown(markdown)

assert.Equal(t, "../path/to/example.md#second-heading", links[0][1])
assert.Equal(t, "../path/to/example.md", links[0][2])
assert.Equal(t, "second-heading", links[0][3])

assert.Equal(t, "../path/to/example.md", links[1][1])
assert.Equal(t, "../path/to/example.md", links[1][2])
assert.Equal(t, "", links[1][3])

assert.Equal(t, "#heading-in-document", links[2][1])
assert.Equal(t, "", links[2][2])
assert.Equal(t, "heading-in-document", links[2][3])

assert.Equal(t, len(links), 5)
}

0 comments on commit 63fe97b

Please sign in to comment.