Add auto links: Try to render unmarked urls as links

This commit is contained in:
Niklas Fasching 2018-12-02 18:24:51 +01:00
parent a570fc736f
commit d2d9dc0fc8
4 changed files with 37 additions and 3 deletions

View file

@ -24,8 +24,12 @@ type RegularLink struct {
Protocol string Protocol string
Description []Node Description []Node
URL string URL string
AutoLink bool
} }
var validURLCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;="
var autolinkProtocols = regexp.MustCompile(`(https?|ftp|file)`)
var redundantSpaces = regexp.MustCompile("[ \t]+") var redundantSpaces = regexp.MustCompile("[ \t]+")
var subScriptSuperScriptRegexp = regexp.MustCompile(`([_^])\{(.*?)\}`) var subScriptSuperScriptRegexp = regexp.MustCompile(`([_^])\{(.*?)\}`)
var footnoteRegexp = regexp.MustCompile(`\[fn:([\w-]+?)(:(.*?))?\]`) var footnoteRegexp = regexp.MustCompile(`\[fn:([\w-]+?)(:(.*?))?\]`)
@ -33,7 +37,7 @@ var footnoteRegexp = regexp.MustCompile(`\[fn:([\w-]+?)(:(.*?))?\]`)
func (d *Document) parseInline(input string) (nodes []Node) { func (d *Document) parseInline(input string) (nodes []Node) {
previous, current := 0, 0 previous, current := 0, 0
for current < len(input) { for current < len(input) {
consumed, node := 0, (Node)(nil) rewind, consumed, node := 0, 0, (Node)(nil)
switch input[current] { switch input[current] {
case '^': case '^':
consumed, node = d.parseSubOrSuperScript(input, current) consumed, node = d.parseSubOrSuperScript(input, current)
@ -45,6 +49,9 @@ func (d *Document) parseInline(input string) (nodes []Node) {
consumed, node = d.parseRegularLinkOrFootnoteReference(input, current) consumed, node = d.parseRegularLinkOrFootnoteReference(input, current)
case '\\': case '\\':
consumed, node = d.parseExplicitLineBreak(input, current) consumed, node = d.parseExplicitLineBreak(input, current)
case ':':
rewind, consumed, node = d.parseAutoLink(input, current)
current -= rewind
} }
if consumed != 0 { if consumed != 0 {
if current > previous { if current > previous {
@ -125,6 +132,29 @@ func (d *Document) parseFootnoteReference(input string, start int) (int, Node) {
return 0, nil return 0, nil
} }
func (d *Document) parseAutoLink(input string, start int) (int, int, Node) {
if len(input[start:]) < 3 || input[start+1] != '/' || input[start+2] != '/' {
return 0, 0, nil
}
protocolStart, protocol := start-1, ""
for ; protocolStart > 0 && unicode.IsLetter(rune(input[protocolStart])); protocolStart-- {
}
if m := autolinkProtocols.FindStringSubmatch(input[protocolStart:start]); m != nil {
protocol = m[1]
} else {
return 0, 0, nil
}
end := start
for ; end < len(input) && strings.ContainsRune(validURLCharacters, rune(input[end])); end++ {
}
path := input[start:end]
if path == "://" {
return 0, 0, nil
}
link := RegularLink{protocol, []Node{Text{protocol + path}}, protocol + path, true}
return len(protocol), len(path + protocol), link
}
func (d *Document) parseRegularLink(input string, start int) (int, Node) { func (d *Document) parseRegularLink(input string, start int) (int, Node) {
if len(input[start:]) == 0 || input[start+1] != '[' { if len(input[start:]) == 0 || input[start+1] != '[' {
return 0, nil return 0, nil
@ -147,7 +177,7 @@ func (d *Document) parseRegularLink(input string, start int) (int, Node) {
if len(parts) == 2 { if len(parts) == 2 {
protocol = parts[0] protocol = parts[0]
} }
return consumed, RegularLink{protocol, description, link} return consumed, RegularLink{protocol, description, link, false}
} }
func (d *Document) parseEmphasis(input string, start int) (int, Node) { func (d *Document) parseEmphasis(input string, start int) (int, Node) {

View file

@ -246,7 +246,9 @@ func (w *OrgWriter) writeRegularLink(l RegularLink) {
descriptionWriter := w.emptyClone() descriptionWriter := w.emptyClone()
descriptionWriter.writeNodes(l.Description...) descriptionWriter.writeNodes(l.Description...)
description := descriptionWriter.String() description := descriptionWriter.String()
if l.URL != description { if l.AutoLink {
w.WriteString(l.URL)
} else if l.URL != description {
w.WriteString(fmt.Sprintf("[[%s][%s]]", l.URL, description)) w.WriteString(fmt.Sprintf("[[%s][%s]]", l.URL, description))
} else { } else {
w.WriteString(fmt.Sprintf("[[%s]]", l.URL)) w.WriteString(fmt.Sprintf("[[%s]]", l.URL))

View file

@ -58,6 +58,7 @@
<li>regular link <a href="https://example.com">https://example.com</a> link without description</li> <li>regular link <a href="https://example.com">https://example.com</a> link without description</li>
<li>regular link <a href="https://example.com">example.com</a> link with description</li> <li>regular link <a href="https://example.com">example.com</a> link with description</li>
<li>regular link to a file (image) <img src="my-img.png" alt="file:my-img.png" title="file:my-img.png" /></li> <li>regular link to a file (image) <img src="my-img.png" alt="file:my-img.png" title="file:my-img.png" /></li>
<li>auto link, i.e. not inside <code class="verbatim">\[[square brackets]\]</code> <a href="https://www.example.com">https://www.example.com</a></li>
</ol> </ol>
</li> </li>
</ul> </ul>

View file

@ -50,6 +50,7 @@ this one is cheating a little as tags are ALWAYS printed right aligned to a give
1. regular link [[https://example.com]] link without description 1. regular link [[https://example.com]] link without description
2. regular link [[https://example.com][example.com]] link with description 2. regular link [[https://example.com][example.com]] link with description
3. regular link to a file (image) [[file:my-img.png]] 3. regular link to a file (image) [[file:my-img.png]]
4. auto link, i.e. not inside =\[[square brackets]\]= https://www.example.com
** Footnotes ** Footnotes
- normal footnote reference [fn:1] - normal footnote reference [fn:1]
- further references to the same footnote should not [fn:1] render duplicates in the footnote list - further references to the same footnote should not [fn:1] render duplicates in the footnote list