Running Code From READMEs In 10 Lines Of Deno
Tags: deno, typescript, javascript, tutorial
Published: 2020-06-13 14:30:26

Recently I was reading through the Deno [Pogo server framework](https://github.com/sholladay/pogo) README and was about to create a new folder and files so I could test out some of the README examples when I wondered - wouldn't it be great if we could just run the examples straight from a README without having to copy the code?

Perhaps a small program could find markdown blocks and offer the ability to run examples, perhaps a little like a python [Jupyter notebook](https://jupyter.org/), but driven off of a README using existing snippets?

Now this was starting to sound a little complicated, but as a good start, why not build something that will execute code based on a start and end line number?

# Writing a README runner

```ts
const [url, startStr, endStr] = Deno.args;
const start = parseInt(startStr);
const end = parseInt(endStr);
```

First we would need a way to provide the README url and the start / end line arguments - we can do this by making use of the `Deno.args` property which contains an array of all passed args.

The args are stored as strings, so we parse them into integers.

```ts
const response = await fetch(url);
const page = await response.text();
```

`Deno` supports browser APIs meaning we can using the `fetch` method to then retrieve the page from the url.

If we were to log the `page` string at this point we would see the text for the entire webpage.

We would then need to split out the desired section of the README based on our start and end line numbers.

```ts
const lines = page.split("\n");
const script = lines.slice(start - 1, end).join("\n");
```

For this we can split the page contents by the newline `\n` character so we create an array of lines, and then create a `script` string by using the array `slice` method to extract the lines from our start number up to our end number. Because lines start from "line 1" and JavaScript arrays' start from index 0, some care is needed to make sure we shift the start down by 1.

We line join the sliced out part of the array back together using newline characters.

If we were to `console.log` out the `script` object, we should now be able to see just the part of the README we wanted!

```ts
const tmpFilePath = await Deno.makeTempFile({ suffix: ".ts" });
await Deno.writeTextFile(tmpFilePath, script);
await import(`file://${tmpFilePath}`);
```

Finally, we create a temporary typescript file and write our script to this file using the `Deno.makeTempFile()` and `Deno.writeTextFile()` methods. Take care to remember to use `await` as most of Deno's APIs are asynchronous.

We can now use a dynamic import to run our new script which we've saved to a temporary file!

Here's all the code in one block:

```ts
const [url, startStr, endStr] = Deno.args;
const start = parseInt(startStr);
const end = parseInt(endStr);

const response = await fetch(url);
const page = await response.text();

const lines = page.split("\n");
const script = lines.slice(start - 1, end).join("\n");

const tmpFilePath = await Deno.makeTempFile({ suffix: ".ts" });
await Deno.writeTextFile(tmpFilePath, script);
await import(`file://${tmpFilePath}`);
```

And that's it, we can now run a script from a snippet of any valid url with 10 lines of Deno code.

We could then run one of the Pogo examples mentioned before like so:

```bash
deno run --allow-net --allow-read --allow-write --reload ./mod.ts https://raw.githubusercontent.com/sholladay/pogo/master/README.md 31 39
```

Where we have saved the code in a file called `mod.ts`.

> **Note:** We have had to provide quite a few permission flags. The `--allow-net` flag allows us to run the `fetch` command, `--allow-read` allows us to read the provided args and import the file we create, and `--allow-write` allows us to create the temporary file. Always check the code you are about to run to make sure it is safe to do so, and never add permissions to a Deno script unless you are sure it is safe.

This would run the first example in the Pogo README which currently looks like:

```ts
import pogo from 'https://deno.land/x/pogo/main.ts';

const server = pogo.server({ port : 3000 });

server.router.get('/', () => {
    return 'Hello, world!';
});

server.start();
```

> **Note:** If / when the Pogo README changes, the above example may stop working. You might need to adjust the start and end line numbers passed to the command.

For ease, this script is available on GitHub in the [readme-runner](https://github.com/asos-craigmorten/readme-runner) repository, meaning you can run the above example by also using:

```bash
deno run --allow-net --allow-read --allow-write --reload deno run --allow-net --allow-read --allow-write --reload https://raw.githubusercontent.com/asos-craigmorten/readme-runner/main/mod.ts https://raw.githubusercontent.com/sholladay/pogo/master/README.md 31 39 https://raw.githubusercontent.com/sholladay/pogo/master/README.md 31 39
```

---

What are you currently doing with Deno? Do you have any cool ideas of how you can utilise Deno's ability to import code from urls?

Drop your projects, ideas and comments below - it's great to hear what everyone is doing!

That's it for now :wave: