mirror of
https://github.com/kovetskiy/mark.git
synced 2025-04-24 05:42:40 +08:00
Support for relative links (#33)
* Support for relative links Fixes #25 * Error logging fixes * Better regexp
This commit is contained in:
parent
bcf2acb39f
commit
63fe97beaa
2
go.mod
2
go.mod
@ -15,6 +15,6 @@ require (
|
|||||||
github.com/reconquest/pkg v0.0.0-20201028091908-8e9a5e0226ef
|
github.com/reconquest/pkg v0.0.0-20201028091908-8e9a5e0226ef
|
||||||
github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4
|
github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4
|
||||||
github.com/russross/blackfriday v1.5.2
|
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
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@ -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/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 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
|
||||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
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 h1:e8UwTXL3Nauw5T847OMHZRhAfQy4ntgQ7PUwgZ2ct4w=
|
||||||
github.com/kovetskiy/gopencils v0.0.0-20201105104258-2a0bfdd710fb/go.mod h1:rn9YsgK4kxBDPZn+hOwSmg6MdtWfF2ejC3tvgDjWyBM=
|
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=
|
github.com/kovetskiy/ko v0.0.0-20190324102900-26b8dd0988bf h1:4QsqgCcPoqDB91dcp4GffoV6TjwfVURaWpjKWFi0ae0=
|
||||||
|
6
main.go
6
main.go
@ -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 {
|
if dryRun {
|
||||||
compileOnly = true
|
compileOnly = true
|
||||||
|
|
||||||
|
@ -26,7 +26,8 @@ type API struct {
|
|||||||
|
|
||||||
// it's deprecated accordingly to Atlassian documentation,
|
// it's deprecated accordingly to Atlassian documentation,
|
||||||
// but it's only way to set permissions
|
// but it's only way to set permissions
|
||||||
json *gopencils.Resource
|
json *gopencils.Resource
|
||||||
|
BaseURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageInfo struct {
|
type PageInfo struct {
|
||||||
@ -87,8 +88,9 @@ func NewAPI(baseURL string, username string, password string) *API {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &API{
|
return &API{
|
||||||
rest: rest,
|
rest: rest,
|
||||||
json: json,
|
json: json,
|
||||||
|
BaseURL: strings.TrimSuffix(baseURL, "/"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
105
pkg/mark/link.go
Normal file
105
pkg/mark/link.go
Normal file
@ -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
pkg/mark/link_test.go
Normal file
33
pkg/mark/link_test.go
Normal file
@ -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)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user