11

I have a changelog file formatted using Github's markdown.

Initially I used inline links for every link I needed to add, that is:

This is some [example](http://www.stackoverflow.com) line of text.

Over time, as the file grew in size, it became a bit messy due mainly to this way of inserting links.

I'd like to convert all links from inline to reference (see description of each), that is convert the above line to this:

This is some [example][1] line of text.

[1]: http://www.stackoverflow.com

Since the file is rather large and contains many inline links, I was wondering if there is some automated way to do this. I use Sublime Text 3 to edit, but I couldn't find a suitable package for this task. Perhaps some clever regex?

Gangula
  • 5,193
  • 4
  • 30
  • 59
Gabriel
  • 40,504
  • 73
  • 230
  • 404
  • 1
    Do you expect the "tool" to combine multiple occurrences of the the same URL to the same reference number? – reto Jun 18 '15 at 14:21
  • @reto well it wouldn't hurt, but I'd be willing to fix these instances myself if that's as good as it gets. – Gabriel Jun 18 '15 at 14:22
  • Does Sublime Text 3 come with a script or macro language of its own, or does it have an interface with an external language such as Perl, Python, Javascript or Visual Basic for Applications? – Jongware Jun 18 '15 at 14:59
  • @Jongware as far as I can tell Sublime 3 supports regex and that's it. I don't think it has a script language of its own, at least none that I'm aware of. I think you might be the first SO user I've come across with such a high rep that isn't familiar with Sublime Text. Are you a vi/emacs user? – Gabriel Jun 18 '15 at 15:04
  • 1
    I'm firmly wedded to GUIs. On Windows I use TextPad, on Mac TextWrangler. While neither provide a (very good) scripting language, they can both run console programs and capture its output, so I would go ahead and write a custom tool in C, just for this. The required level of storing/counting/retrieving is beyond what GREP can (and should) do. – Jongware Jun 18 '15 at 15:09
  • 1
    @Gabriel: I might give Sublime a test run. FYI only: it *does* directly support one of the more powerful languages: [Python](http://docs.sublimetext.info/en/latest/reference/plugins.html). – Jongware Jun 18 '15 at 19:28

4 Answers4

9

That's a great requirement!

I've just created a new Node.js program (I know it's not a GUI but seems something more people would like the capability of) to do this on GitHub.

Here's also the code:

// node main.js test.md result.md

var fs = require('fs')
fs.readFile(process.argv[2], 'utf8', function (err, markdown) {
    if (err) {
        return console.log(err);
    }
    var counter = 1;
    var matches = {};
    var matcher = /\[.*?\]\((.*?)\)/g;
    while (match = matcher.exec(markdown)) {
        if (!matches[match[1]]) matches[match[1]] = counter++;
    }
    console.log(matches);
    Object.keys(matches).forEach(function(url) {
        var r = new RegExp("(\\[.*?\\])\\(" + url + "\\)", "g");
        markdown = markdown.replace(r, "$1[" + matches[url] + "]");
        markdown += "\n[" + matches[url] + "]: " + url;
    });

    fs.writeFile(process.argv[3], markdown, 'utf8', function (err) {
        if (err) return console.log(err);
    });

});
bjfletcher
  • 11,168
  • 4
  • 52
  • 67
  • Could you add option to format the number (Like having 3 leading zeros, etc...)? Will it handle the same links to have the same reference number? Actually StackOverflow editor does it very well. – Royi May 18 '21 at 12:29
5

Save this as mdrelink.py in your Packages folder, and you can then run it with

view.run_command('mdrelink');

from within the command console.

I think I got the order thingy right – reversing is necessary because otherwise it would mess up the already cached indexes of next items. It should also automatically skip already used link numbers. My first Python and my first Sublime plugin, so please be gentle with me.

import sublime, sublime_plugin

class mdrelinkCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        oldlinks = []
        self.view.find_all("^\s*(\[\d+\]):", sublime.IGNORECASE, "\\1", oldlinks)
        newlinkpos = self.view.find_all("\[.+?\](\(.+?\))")
        orgtext = []
        self.view.find_all("(\[.+?\])\(.+?\)", sublime.IGNORECASE, "\\1", orgtext)
        orglink = []
        self.view.find_all("\[.+?\]\((.+?)\)", sublime.IGNORECASE, "\\1", orglink)
        orglink.reverse()
        self.view.insert(edit, self.view.size(), '\n\n')
        counter = 1
        newnumbers = []
        for r in newlinkpos:
            while '['+str(counter)+']' in oldlinks:
                 counter += 1
            oldlinks.append('['+str(counter)+']')
            line = '[' + str(counter)+']: '+ orglink.pop() + '\n'
            newnumbers.append('  ['+str(counter)+']')
            self.view.insert(edit, self.view.size(), line)
        for r in reversed(newlinkpos):
            self.view.replace(edit, r, orgtext.pop()+newnumbers.pop())
Jongware
  • 22,200
  • 8
  • 54
  • 100
  • And it only took you a couple of hours from not using Sublime to writing a plugin for it :) You should consider uploading this to https://packagecontrol.io/ which is where all Sublime packages live and can be easily installed via the Package Manager. I'll give this one a try as soon as I can. Thank you Jongware! – Gabriel Jun 18 '15 at 23:27
  • Has anyone made it work? It doesn't seem to work for me. – Royi May 18 '21 at 12:47
3

Came across this question thanks to Google. Maybe this can help others:

My answer isn't Sublime specific, but if you're using JavaScript (Node) already, I'd use a Markdown parser and CST transformer like remark.

For example, to transform all the inline links in README.md to numerically-ascending reference-style links, you could run the following at your project's root:

npm install --save-dev remark-cli remark-renumber-references
npx remark --no-stdout --output --use renumber-references README.md

Or, if you want reference-style links derived from the source uri:

npm install --save-dev remark-cli remark-defsplit
npx remark --no-stdout --output --use defsplit README.md

Hopefully this info helps people like me not waste a whole day hacking together some horrendously unreliable regexp-based solution to this :)

Xunnamius
  • 478
  • 1
  • 8
  • 16
  • I'm getting an error `Could not find module 'reference-links'` – Gangula Sep 20 '22 at 15:16
  • @Gangula might be the global install flag (`-g`). Try `npm install --no-save remark-reference-links remark-cli` (perhaps without `--no-save`) followed by `npx remark README.md -o --use reference-links` at your repo's root directory, assuming you're trying to mutate a file called `README.md`. Actually, I think I'll update the answer and remove sudo/globals. – Xunnamius Sep 20 '22 at 18:03
  • it works now. Although I suggest `npm install --save-dev remark-reference-links remark-cli`, since in this case, these are not production dependencies. – Gangula Sep 20 '22 at 18:23
  • 1
    Good idea. Updated! – Xunnamius Sep 20 '22 at 19:11
  • if I used `remark-reference-links` on an existing markdown, the existing numbers don't get updated. I would like the reference links to be reset - starting from 1. Do you know how to do this? – Gangula Oct 04 '22 at 08:50
  • 1
    @Gangula I'm actually working on a version of remark-reference-links (I'll call it remark-renumber-references) that will do just this, though I'm busy with other projects. In the meantime, try passing over the document twice: once with remark-inline-links (which is the inverse transform of remark-reference-links) and then again with remark-reference-links. This will restart numbering at 1 with references numbered in the order that they appear in the document. This will cause ALL references to be renumbered though, not just numbered references, something remark-renumber-references will solve. – Xunnamius Oct 04 '22 at 11:46
  • 1
    @Gangula [remark-renumber-references](https://npm.im/remark-renumber-references) is live :) – Xunnamius Oct 30 '22 at 02:16
  • 1
    This is really great @Xunnamius, can you add this as a separate answer to this question? I have a couple of questions, which would be better suited in that answer - for example (how to ignore links to headings from same document, etc...) – Gangula Oct 30 '22 at 10:00
  • @Gangula I think it'd be easier to move discussions about the package to [GitHub](https://github.com/Xunnamius/unified-utils/discussions) :) – Xunnamius Oct 30 '22 at 10:58
  • Oh yes, that makes sense. But regardless, it would be helpful for the users coming to this page, if you mention you package in an answer. – Gangula Oct 30 '22 at 11:07
  • Agreed. I updated the answer a little while ago :) – Xunnamius Oct 30 '22 at 11:29
0

extending @bjfletcher's answer, below is the code that considers any existing reference links.
Link to GitHub gist.

// command to run
// node filename.js ReadMe.md RefLinks.md

import * as fs from 'fs'
fs.readFile(process.argv[2], 'utf8', function (err, mainMarkdown) {
    if (err) {
        return console.log(err);
    }
    let newMarkdown = existingRefLinks(mainMarkdown);

    var counter = 1;
    var matches = {};
    var matcher = /\[.*?\]\((.*?)\)/g
    let match;
    while (match = matcher.exec(newMarkdown)) {
        if (!matches[match[1]]) matches[match[1]] = counter++;
    }
    console.log(matches);
    Object.keys(matches).forEach(function (url) {
        var r = new RegExp("(\\[.*?\\])\\(" + url + "\\)", "g");
        newMarkdown = newMarkdown.replace(r, "$1[" + matches[url] + "]");
        newMarkdown += "\n[" + matches[url] + "]: " + url;
    });

    fs.writeFile(process.argv[3], newMarkdown, 'utf8', function (err) {
        if (err) return console.log(err);
    });

});

function existingRefLinks(markdown) {
    let refLinks = {}, match;
    const matcher = /\[(\d)]:\s(.*)/g; // /\[.*?\]\((.*?)\)/g
    while (match = matcher.exec(markdown)) {
        if (!refLinks[match[1]]) refLinks[match[1]] = match[2];
    }
    markdown = markdown.replaceAll(matcher, "")

    Object.keys(refLinks).forEach(function (int) {
        markdown = markdown.replace("][" + int + "]", "](" + refLinks[int] + ")");
    });
    return markdown
}

Note that I prefer using @Xunnamius's answer:

npm install --save-dev remark-reference-links remark-cli
npx remark README.md -o --use reference-links
Gangula
  • 5,193
  • 4
  • 30
  • 59
  • 1
    Although I prefer [@Xunnamius's answer](https://stackoverflow.com/a/65526897/6908282) to use `npm install --save-dev remark-reference-links remark-cli` since it uses remark which is more reliable and handles multiple other scenarios. – Gangula Sep 20 '22 at 18:27