Friday, May 10, 2024
HomeJavaScriptChanging Markdown Code Blocks to Gists

Changing Markdown Code Blocks to Gists


File this below the “I’ve no freaking concept who this will probably be helpful for” bucket, however I wrote up a script to assist me with an issue regarding authoring on Medium and figured I would share it. It additionally allowed me to play extra with GitHub’s APIs and that was undoubtedly helpful for me, so hopefully it will likely be helpful for you. Why Medium? I am not a fan of the platform myself, however at work, we use it for our developer weblog so I have to make use of it. On the whole, it is an OK platform, however one factor it does not do nicely is code blocks.

Particularly, it does not have help for any colour coding of your code samples. For very quick blocks of code that is okay, however for any fairly sized code snippet, the shortage of syntax coloring actually begins to make it troublesome to parse.

The “customary” answer is to make a GitHub gist, get the URL of the gist, paste it into Medium, hit enter, and it’ll substitute the URL with an embed. That works however is actually annoying. In my final put up, I had 16 of those and determined I had had sufficient and it was time to take a look at an automation instrument. This is what I constructed.

The First Resolution

My preliminary try (which, technically labored completely, till it did not, I will clarify why later) used this course of:

  • Discover all of the code blocks in a Markdown file
  • For every, try and determine the sort primarily based on any characters after the “`.
  • For every, get the start and finish character positions
  • For all of the matches, create a brand new Gist utilizing a filename primarily based on the kind of code block
  • For the newly created Gist, create an embed string and substitute the textual content in Markdown

I used to be initially going to make use of GitHub’s REST APIs however then found octokit.js, a utility library that makes it extremely simple to make use of.

Additionally, be aware that the code I constructed requires a private entry token. You may generate these shortly through GitHub’s settings. When it comes time to pick the scopes and permissions, simply choose gist as that is all of the code demonstrated right here wants.

Alright, let’s get began. First, my script takes two inputs – the situation of the enter markdown and the situation of the place it ought to be saved:

let enter = course of.argv[2];
let output = course of.argv[3];

if(!enter || !output) {
    console.log(chalk.crimson('Utilization: node gistify.js <<enter file>> <<output file>>'));
    course of.exit(1);
}

if(!fs.existsSync(enter)) {
    console.log(chalk.crimson(`Cannot discover enter file ${enter}`));
    course of.exit(1);
}

// auto take away current output
if(fs.existsSync(output)) fs.unlinkSync(output);

I then learn within the enter file and request the code blocks:

let md = fs.readFileSync(enter,'utf8');
console.log(chalk.inexperienced(`Parsing ${enter} to seek out code blocks.`));

let blocks = getCodeBlocks(md);

The getCodeBlocks operate seems to be for code block markers (three backticks). It makes an attempt to discover a language kind and it will get the vary for every one:

operate getCodeBlocks(str) {
    let outcomes = [];
    let blocksReg = /```(.*?)```/sg;
    let match = null;

    // https://stackoverflow.com/a/2295681/52160
    whereas((match = blocksReg.exec(str)) != null) {
        let outcome = {
            str:match[0],
            begin: match.index, 
            finish: match.index + match[0].size
        }

        // get line one to attempt to determine kind
        let line1 = outcome.str.cut up('n')[0];
        let kind = line1.substitute(/[`r]/g,'');
        if(!kind) kind="plain";
        outcome.kind = kind;
        outcomes.push(outcome);
    }
    return outcomes;
}

Be aware that when a sort isn’t outlined, I set it to “plain”.

As soon as I’ve my outcome, I can verify to see if any had been discovered:

if(blocks.size === 0) {
    console.log('No code blocks had been discovered on this Markdown file. Have a pleasant day.');
    course of.exit(1);
}

console.log(chalk.inexperienced(`We discovered ${blocks.size} code blocks. Starting the Gist conversion.`));

Now I must course of them. I’ll go from the final block to the primary as a result of I’ll be modifying the file contents in a string and if I did it first to final, my ranges can be off.

for(let i=blocks.length-1; i >= 0; i--) {
    let gist = await createGist(blocks[i].str, blocks[i].kind);
    // we care about HTML url

    let embed = toGistEmbed(gist.html_url);
    md = md.substring(0, blocks[i].begin) + embed + md.substring(blocks[i].finish);
    console.log(chalk.yellow(`Processed ${blocks.length-i} on ${blocks.size}`));
}

Let’s first take a look at createGist:

async operate createGist(code, kind) {
    /*
    We swap kind to a filename, will assist with code rendering.
    Proper now, just some and yeah, I may simply use file.TYPE besides
    for plain. I'll come again to that.
    */
    let filename="plain.txt";

    if(kind === 'js') {
        filename="script.js";
    } else if(kind === 'html') {
        filename="file.html";
    } else if(kind === 'py') {
        filename="file.py";
    }

    // take away preliminary and ending ```
    // oops, starting will be ```js. 

    code = code.substitute(/```.*/gm,'').trim();

    let information = {};
    information[filename] = { content material: code };

    
    let physique = {
        description:'', 
        public: true, 
        information
    }

    return (await octokit.request('POST /gists', physique)).information;

}

The API to create a Gist requires a filename. I sniff the sort and use generic names primarily based on the sort. Because the feedback say, I may make this a bit extra versatile.

I then take away the backticks, and name octokit. Discover how easy that half is – one fast API name. The result’s a Gist object that features an html_url worth I exploit in toGistEmbed:

operate toGistEmbed(url) {
    return `<script src="${url}.js"></script>`;
}

It is probably a bit foolish to have a operate for such a less complicated operation, however I figured what the heck. I then write out the file. This is the complete script, as a Gist, as a result of the backticks ended up messing my weblog’s processing a bit:

So… this labored nicely, however I bumped into an issue. If I copied and pasted the outcome into Medium, it robotically escaped the script tags and handled it as code to point out. On to the second answer!

The Second Resolution

To get this working proper in Medium, I made one extremely small change:

operate toGistEmbed(url) {
    //return `<script src="${url}.js"></script>`;
    return url;
}

Yeah, the operate does nothing now, which makes it much more foolish, however I ended up with a outcome that had my Gist URLs within the textual content. Once I pasted this into Medium, I then went to every one, put my cursor on the finish, and hit enter. Nonetheless a little bit of guide work, however far simpler than creating the Gists by hand, one after the other.

As at all times, let me know what you suppose and if you happen to discover this handy, give me a shout-out!

Photograph by David Travis on Unsplash

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments