layout: post
excerpt: "CLI generator for Shell scripts"
categories: blog
title: CLInt
tags: [bash, productivity, hack, linux]
published: true
date: 2019-06-19 22:00
share: true
---

In the past two years, I built a corpus of well documented and reusable bash scripts for both my main work and my sideprojects.

This is how it happened.

A couple of years ago I went through a long period where writing many scripts was the only way to help me at work.
Testing some modems and command line utilities, I needed to group many commands at once to, as example, establish a full data connection.

Honestly, **my scripts were really hard to reuse**, and months after writing them, always took me time to understand the meaning of all the variables in place.

The main problems were:

1. **no usage/help message:** I was forced to dig into the code to see what it was supposed to do
2. **no flexibility:** hard-coded values and no command line arguments, so the script was able to do only one specific thing

Since I loved [Docopt](http://docopt.org/) - the Python library which parses the help message and provides all the
command line options you need - I looked for a similar project for bash scripting, and I [found one](https://github.com/docopt/docopts).

At that time **Docopts** for bash was written in Python (now it is written in go), but I prefer to avoid
dependencies in my scripts. In python you have the [Python Package Index](https://pypi.org/) and it is perfectly
normal to install a python dependency, but for bash, at least from my point of view, I prefer that each script depends
only on the commands it calls, not on other scripts for extended functionalities.

So I wrote [CLInt](https://github.com/clobrano/CLInt), a **CLI generator from the help message**... ehm... so, what's the difference?

**CLInt works at writing time**.

CLInt auto-generates all the bash code needed to parse the command line options, so there is no external dependency.

As example, consider a script with this usage description:

    ## Test script to show how clint.sh works
    ##
    ## usage: yourscript ... # the usage message (CLInt ignores this part)
    ##
    ## options:              # The options part (CLInt parses this part. Be aware of the format)
    ##      -f, --file <string>      Example of a valued flag. The flag's value is stored in a variable named "$_file"
    ##      -d, --default <string>   Example of a valued flag with a default value (_this_is_the_default) [default: _this_is_the_default]
    ##      -b, --boolean            Example of a no-argument (boolean) flag: its value (0 or 1) is stored in a variable named "$_boolean"
    ##      -v, --verbose            Example of a no-argument (boolean) flag with a default value "0" [default: 0]

Run `clint.sh` and you'll get:

    $ clint.sh -s test-script.sh

    # GENERATED_CODE: start
    # Default values
    _verbose=0
    _default=_this_is_the_default

    # No-arguments is not allowed
    [ $# -eq 0 ] && sed -ne 's/^## \(.*\)/\1/p' $0 && exit 1

    # Converting long-options into short ones
    for arg in "$@"; do
      shift
      case "$arg" in
    "--file") set -- "$@" "-f";;
    "--default") set -- "$@" "-d";;
    "--boolean") set -- "$@" "-b";;
    "--verbose") set -- "$@" "-v";;
      *) set -- "$@" "$arg"
      esac
    done

    function print_illegal() {
        echo Unexpected flag in command line \"$@\"
    }

    # Parsing flags and arguments
    while getopts 'hbvf:d:' OPT; do
        case $OPT in
            h) sed -ne 's/^## \(.*\)/\1/p' $0
               exit 1 ;;
            b) _boolean=1 ;;
            v) _verbose=1 ;;
            f) _file=$OPTARG ;;
            d) _default=$OPTARG ;;
            \?) print_illegal $@ >&2;
                echo "---"
                sed -ne 's/^## \(.*\)/\1/p' $0
                exit 1
                ;;
        esac
    done
    # GENERATED_CODE: end

Copy paste this code into `test-script.sh`, that's it.

I then added some sweets:

- if [xclip](https://github.com/astrand/xclip) is installed, CLInt's output is available in system clipboard as well
- if you use **VIM**, the following configuration let CLInt works in the current open buffer without the hassle of opening a terminal, execute CLInt, and copy the CLInt's autogenerated code.

        command! CLInt r !/path/to/CLInt/clint.sh -s %

This totally changed the quality of my scripts, and it was not because I became a better Bash developer.

All my scripts started to be well documented **just because** the documentation was the only thing I needed to have options and flexibility, and it was super easy and fast to do it (especially on VIM).

CLInt development has stopped some time ago, because it already satisfies all my needs, but it is still young and far from perfect, so I hope that some more people will try it and help me find all the bugs hidden in the project.

