Learn Nim: Create a README Template Downloader
Tags: tutorial, nim
Published: 2020-02-08 12:17:37

---
title: Learn Nim: Create a README Template Downloader
published: true
date: 2020-02-08 00:00:00 UTC
cover_image: https://repository-images.githubusercontent.com/842037/cf468f00-88a5-11e9-8d1a-d5fba75d3eb5
canonical_url: https://www.rockyourcode.com/learn-nim-create-a-readme-template-downloader/
---

> Create a command-line tool which downloads a README template for your coding projects

## Why Nim?

**[Nim][nimlang]** is a statically typed systems programming language.

Nim generates small, native _dependency-free_ executables. The language combines a Python-like syntax with powerful features like meta-programming.

Nim supports macOS, Linux, BSD, and Windows. The language is open-source and has no corporate affiliation.

Nim compiles to multiple backends, for example, C, C++, or JavaScript.

The ecosystem and community are small, but the language has reached its first stable release.

If you're interested in a low-level language, then you should take a look at Nim. It's easier to learn than languages like C++ or Rust, but can be a decent replacement for those languages.

More about Nim on the Nim forum: [Why use Nim?][whyusenim]

---

In this blog post, I will **show you how to create a command-line tool** with Nim.

You will learn how to:

- connect to the internet with an HTTP client
- parse command-line options
- create a file on your system (IO)
- compile a Nim application and execute it

## Install Nim

First, [install Nim on your operating system][installnim].

I like to use **[choosenim][choosenim]**. choosenim is a tool that allows you to install Nim and its toolchain easily. You can manage multiple Nim installations on your machine.

Here's how to install Nim with [choosenim][choosenim]:

- Windows:

Get the [latest release](https://github.com/dom96/choosenim/releases) and run the `runme.bat` script.

- Unix (macOS, Linux):

```bash
curl https://nim-lang.org/choosenim/init.sh -sSf | sh
```

## (Optional) Nim Basics

Nim offers [splendid tutorials and community resources to get started][learnnim].

I recommend taking a look at [Learn Nim in Y minutes](https://learnxinyminutes.com/docs/nim/) or [Nim By Example](https://nim-by-example.github.io/getting_started/) to get a sense of the language.

## Let's Create Our First Program

### The Goal

I often need a README template for my coding projects.

My favorite template is **[Best-README-Template][bestreadmetemplate]**.  
But there also other good examples, e.g.:

- [PurpleBooth/README-Template][purplebooth]
- [akashnimare/README][akashnimare]
- [dbader/readme-template][dbader]
- [jehna/readme-best-practices][jehna]

We want to create a command-line utility which downloads such a template as `README.md` to the current folder.

You could achieve that goal by using a library like [curl](https://curl.haxx.se/). But what happens if your system doesn't have curl?

Our Nim utility will compile to a stand-alone C binary that will seamlessly run on your system without any dependencies like curl or [wget](https://www.gnu.org/software/wget/).  
And we'll learn a bit Nim along the way.

### 1. Connect to the Internet

Create a new file called `readme_template_downloader.nim`:

```nim
import httpClient
var client = newHttpClient() ## mutable variable `var`
echo client.getContent("https://raw.githubusercontent.com/othneildrew/Best-README-Template/master/README.md")
```

These lines import the [httpclient library](https://nim-lang.org/docs/httpclient.html) and create a new instance of the HTTP client.

[`getContent`](https://nim-lang.org/docs/httpclient.html#getContent%2CHttpClient%2Cstring) is an inbuilt procedure (function) that connects to the URL and returns the content of a GET request. For now, we use `echo` to write to standard output.

Save the file. We'll now compile it to a C binary.

```bash
nim c -d:ssl readme_template_downloader.nim
```

`c` stands for compile, `-d:ssl` is a [flag that allows us to use the OpenSSL library](https://nim-lang.org/docs/httpclient.html#sslslashtls-support).

Now you can run the application. Here's the command for Unix:

```bash
./readme_template_downloader
```

You should now see the result of the README template in your terminal.

You can also compile and run the program in a single step:

```bash
nim c -d:ssl -r readme_template_downloader
```

### 2. Create a Procedure

Procedures in Nim are what most other languages call functions. Let's adjust our file:

```nim
import httpClient

var url = "https://raw.githubusercontent.com/othneildrew/Best-README-Template/master/README.md"

proc downloadTemplate(link: string) =
  var client = newHttpClient()
  echo client.getContent(link)

when isMainModule:
  downloadTemplate(url)
```

The `when` statement is a compile-time statement. If you import the file, Nim won't run the `downloadTemplate` procedure.  
Here the file represents our main module and Nim will invoke the procedure.

In the `downloadTemplate` procedure, we define the input parameter (link is of type `string`), but we allow Nim to infer the type of the output.

Don't forget to re-compile and to rerun the application:

```bash
nim c -d:ssl -r readme_template_downloader
```

### 3. Write to a File (IO)

We're able to get the content of the URL, but we haven't saved it to a file yet.

We'll use the [io module](https://nim-lang.org/docs/io.html), part of the standard library, for that. We don't have to import anything, it works out of the box.

```nim
import httpClient

var url = "https://raw.githubusercontent.com/othneildrew/Best-README-Template/master/README.md"

proc downloadTemplate(link: string) =
  var client = newHttpClient()
  try: ## (A)
    var file = open("README.md", fmWrite)  ## (B)
    defer: file.close()
    file.write(client.getContent(link))
    echo("Success - downloaded template to `README.md`.")
  except IOError as err:  ## (C)
    echo("Failed to download template: " & err.msg)

when isMainModule:
  downloadTemplate(url)
```

On line `(A)`, we use a [`try statement`](https://nim-lang.org/docs/tut2.html#exceptions-try-statement). It's the same as in Python. With a `try statement`, you can handle an exception.

On line `(B`), we use `open` to [create a new file in write mode](https://nim-lang.org/docs/io.html#FileMode). If the file does not exist, Nim will create it. If it already exists, Nim will overwrite it.

`defer` works like a [context manager in Python](https://www.geeksforgeeks.org/context-manager-in-python/). It makes sure that Nim closes the file after the operation finishes.

With `file.write` Nim will save the result of the HTTP GET request to the file.

On line `(C)`, we handle the exception. We can append the message of the IOError to the string that we'll write to standard output.

For example, if we provide an invalid URL for the HTTP client, the CLI program will output a line like this:

```bash
Failed to download template: 404 Bad Request
```

### 4. Let's Code the CLI Interaction

When we run the program with `--help` or `-h`, we want some information about the application. Something like this:

```bash
nim_template -h

README Template Downloader 0.1.0 (download a README Template)

  Allowed arguments:
  - h | --help     : show help
  - v | --version  : show version
  - d | --default  : dowloads "BEST-README-Template"
  - t | --template : download link for template ("RAW")
```

Add these lines to the `readme_template_downloader.nim` file:

```nim
proc writeHelp() =
  echo """
  README Template Downloader 0.1.0 (download a README Template)

  Allowed arguments:

  - h | --help : show help
  - v | --version : show version
  - d | --default : dowloads "BEST-README-Template"
  - t | --template : download link for template ("RAW")
    """

proc writeVersion() =
  echo "README Template Downloader 0.1.0"

```

### 5. Let's Write the CLI Command

We'll write a procedure as the entry point of the script. We'll move the initialization of the url variable into the procedure, too.

```nim
import httpclient, os

## previous code

proc cli() =
  var url: string = "https://raw.githubusercontent.com/othneildrew/Best-README-Template/master/BLANK_README.md"

  if paramCount() == 0:
    writeHelp()
    quit(0) ## exits program with exit status 0

when isMainModule:
  cli()
```

Add `os` to the list of imports at the top of the file for `paramCount` to work.  
[`paramCount`](https://nim-lang.org/docs/os.html#paramCount) returns the number of command-line arguments given to the application.

In our case, we want to show the output of `writeHelp` and exit the program if we don't give any option.

Here's the whole program so far:

```nim
import httpClient, os

proc downloadTemplate(link: string) =
  var client = newHttpClient()
  try:
    var file = open("README.md", fmWrite)
    defer: file.close()
    file.write(client.getContent(link))
    echo("Success - downloaded template to `README.md`.")
  except IOError as err:
    echo("Failed to download template: " & err.msg)

proc writeHelp() =
  echo """
  README Template Downloader 0.1.0 (download a README Template)

  Allowed arguments:

  - h | --help : show help
  - v | --version : show version
  - d | --default : dowloads "BEST-README-Template"
  - t | --template : download link for template ("RAW")
    """

proc writeVersion() =
  echo "README Template Downloader 0.1.0"

proc cli() =
  var url: string = "https://raw.githubusercontent.com/othneildrew/Best-README-Template/master/BLANK_README.md"

  if paramCount() == 0:
    writeHelp()
    quit(0)

when isMainModule:
  cli()
```

Compile and run. You should see the help information in your terminal.

#### 5.1. Parse Command-Line Options

Now we need a way to parse the command-line options that the program supports: `-v`, `--default`, etc.

Nim provides a [`getopt` iterator](https://nim-lang.org/docs/parseopt.html#getopt.i%2COptParser) in the [parseopt module](https://nim-lang.org/docs/parseopt.html).

Add `import parseopt` to the top of the file.

```nim
import httpclient, os, parseopt

## previous code

proc cli() =
  var url: string = "https://raw.githubusercontent.com/othneildrew/Best-README-Template/master/BLANK_README.md"

  if paramCount() == 0:
    writeHelp()
    quit(0)

  for kind, key, val in getopt():    ## (A)
    case kind
    of cmdLongOption, cmdShortOption:
      case key
      of "help", "h":
        writeHelp()
        quit()
      of "version", "v":
        writeVersion()
        quit()
      of "d", "default": discard    ## (B)
      of "t", "template": url = val ## (C)
      else:       ## (D)
        discard
    else:
      discard     ## (D)

  downloadTemplate(url) ## (E)
```

The iterator (line `A`) checks for the long form of the option (`--help`) and the short form (`-h`). The case statement is a multi-branch control-flow construct. See [case statement in the Nim Tutorial](https://nim-lang.org/docs/tut1.html#control-flow-statements-case-statement). The case statement works like the [switch/case statement from JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch).

`--help` and `-h` invoke the `writeHelp` procedure. `--version` and `-v` invoke the `writeVersion` procedure we defined earlier.

`--default` or `-d` is for the default option (see line `B`). If we don't provide any arguments, our application will give us the help information. Thus, we have to provide a command-line argument for downloading the default README template. We can `discard` the value provided to the `-d` option, because we'll invoke the `downloadTemplate` procedure with the default URL later (line `E`).

The `-t` or `--template` (line `C`) change the value of the `url` variable.  
Let's say we run the Nim program like this:

```bash
./readme_template_downloader -t="https://gist.githubusercontent.com/PurpleBooth/109311bb0361f32d87a2/raw/8254b53ab8dcb18afc64287aaddd9e5b6059f880/README-Template.md"
```

Now Nim will overwrite the default url variable with the provided option in `-t`.

We'll discard everything else (lines `D`), because we can ignore any other options we provide to our Nim program.

You can find the complete script as a [GitHub Gist][gist]:

```nim
import httpclient, parseopt, os

proc downloadTemplate(link: string) =
  var client = newHttpClient()
  try:
    var file = open("README.md", fmWrite)
    defer: file.close()
    file.write(client.getContent(link))
    echo("Success - downloaded template to `README.md`.")
  except IOError as err:
    echo("Failed to download template: " & err.msg)

proc writeHelp() =
  echo """
  README Template Downloader 0.1.0 (download a README Template)

  Allowed arguments:
  - h | --help     : show help
  - v | --version  : show version
  - d | --default  : dowloads "BEST-README-Template"
  - t | --template : download link for template ("RAW")
  """

proc writeVersion() =
  echo "README Template Downloader 0.1.0"

proc cli() =
  var url: string = "https://raw.githubusercontent.com/othneildrew/Best-README-Template/master/BLANK_README.md"

  if paramCount() == 0:
    writeHelp()
    quit(0)

  for kind, key, val in getopt():
    case kind
    of cmdLongOption, cmdShortOption:
      case key
      of "help", "h":
        writeHelp()
        quit()
      of "version", "v":
        writeVersion()
        quit()
      of "d", "default": discard
      of "t", "template": url = val
      else:
        discard
    else:
      discard

  downloadTemplate(url)

when isMainModule:
  cli()
```

Don't forget to re-compile the finished application.

## Recap

In this blog post, you learned how to create a Nim utility that downloads a file from the internet to the current folder on your machine.  
You learned how to create an HTTP Client, how to write to a file, and how to parse command-line options.  
Along the way, you gained a basic understanding of the Nim language: how to use variables, procedures (functions), how to handle exceptions.

To learn more about Nim, see [Learn Nim][learnnim].

### Acknowledgments

Credits go to [xmonader for his Nim Days repository][nimdays].

## Links

- [Nim Language][nimlang]
- [Learn Nim][learnnim]
- [Why use Nim?][whyusenim]
- [Best-README-Template][bestreadmetemplate]
- [PurpleBooth/README-Template][purplebooth]
- [akashnimare/README][akashnimare]
- [dbader/readme-template][dbader]
- [jehna/readme-best-practices][jehna]
- [RichardLitt/standard-readme][richardlitt]

[bestreadmetemplate]: https://github.com/othneildrew/Best-README-Template
[nimlang]: https://nim-lang.org/
[gist]: https://gist.github.com/sophiabrandt/5c48685277002c1b1ceb618245bfd481
[nimdays]: https://xmonader.github.io/nimdays/day06_nistow.html
[purplebooth]: https://gist.github.com/PurpleBooth/109311bb0361f32d87a2
[akashnimare]: https://gist.github.com/akashnimare/7b065c12d9750578de8e705fb4771d2f
[dbader]: https://github.com/dbader/readme-template
[jehna]: https://github.com/jehna/readme-best-practices/blob/master/README-default.md
[richardlitt]: https://github.com/RichardLitt/standard-readme#readme
[installnim]: https://nim-lang.org/install.html
[choosenim]: https://github.com/dom96/choosenim#choosenim
[learnnim]: https://nim-lang.org/learn.html
[whyusenim]: https://forum.nim-lang.org/t/5214#32703