Command-line interface: attention to details

About the Ceylon command-line interface

We’re programmers, so let’s face it, we spend lots of time in those small obscure windows with tiny text, preferably with a green or black background, that Hollywood movies often display scrolling a lot, really fast, you know? Terminals.

While good IDEs are required, and the Ceylon IDE is a must-have for Ceylon developers, they can never replace a good terminal session with the proper command-line interface (CLI) tools.

Oh, I know Eclipse has good Git support and I know a few colleagues who use it for committing and branching and other daily, nay, hourly push/pull command. But I really love my CLI when it comes to using Git, I never use Eclipse for that, so I spend a lot of time in my terminal.

Also, it’s essential to be able to build a Ceylon project from the command-line, and not just from the IDE. Imagine trying to build your project in a remote machine using an IDE with a remote X session? No, that doesn’t cut it, sorry.

So from the start, Ceylon had a CLI. Actually, we had a CLI before we had an IDE, but that didn’t last long. And in the beginning we copied Java and had a few commands such as ceylonc to compile, and ceylon to run. Then we added the API generator tool called ceylondoc, and very soon after that we figured we were doing something horribly wrong.

Back when Java came out in 1995 it was sorta cute and useful that all its commands started with the java prefix, but after you got around to remembering what javac and javadoc stood for, you started to wonder what the hell javap or javah could possibly mean.

Then Git came around and revolutionised not only distributed version-control, it also revolutionised command-line interfaces. Suddenly you could start with remembering a single command called git and find out from here what the full list of subcommands would be. You add --help and it spits out a man page. You have completion out of the box. And everyone can write plugins very easily in just a few lines of shell script. Not only that, but most options can be stored and retrieved in a really straightforward config file, with useful and logical lookup in your project, then home.

It’s really a treasure trove of good ideas, and much more modern than the Java CLI we started copying. So we stopped and rewrote our CLI to copy the Git CLI, and I have to say I’m really glad we did, because we really have a great CLI now.

Discovery

One Command to rule them all, One Command to find them, One Command to bring them all and in the CLI bind them In the Land of Terminal where the Shell lie.

We have a single command called ceylon, and if you do ceylon help or ceylon --help you will get the following:

NAME

        'ceylon' - The top level Ceylon tool is used to execute other Ceylon tools


SYNOPSIS

        ceylon --version
        ceylon [--stacktraces] [--] <command> [<command‑options...>] [<command‑args...>]


DESCRIPTION

        If '--version' is present, print version information and exit. Otherwise '<tool-arguments>' should 
        begin with the name of a ceylon tool. The named tool is loaded and configured with the remaining
        command line arguments and control passes to that tool.

        * 'all' - Compiles ceylon modules for JVM and JS and documents them

        * 'compile' - Compiles Ceylon and Java source code and directly produces module and source archives 
                      in a module repository.

        * 'compile-js' - Compiles Ceylon source code to JavaScript and directly produces module and source 
                         archives in a module repository

        * 'config' - Manages Ceylon configuration files

        * 'doc' - Generates Ceylon API documentation from Ceylon source files

        * 'doc-tool' - Generates documentation about a tool

        * 'help' - Displays help information about other Ceylon tools

        * 'import-jar' - Imports a jar file into a Ceylon module repository

        * 'info' - Prints information about modules in repositories

        * 'new' - Generates a new Ceylon project

        * 'run' - Executes a Ceylon program

        * 'run-js' - Executes a Ceylon program

        * 'src' - Fetches source archives from a repository and extracts their contents into a source directory

        * 'test' - Executes tests

        * 'version' - Shows and updates version numbers in module descriptors

        See 'ceylon help <command>' for more information about a particular subcommand.


OPTIONS

        --stacktraces
            If an error propagates to the top level tool, print its stack trace.


        --version
            Print version information and exit, ignoring all other options and arguments.

Yes it’s a man page, this is what is useful and will help you find your way out of the many ceylon subcommands we have.

A similar thing will happen if you type ceylon compile --help or ceylon help compile.

Completion

We ship with completion support for bash:

$ . /usr/share/ceylon/1.0.0/contrib/scripts/ceylon-completion.bash 
$ ceylon [TAB]
all         compile-js  doc         help        info        run         src         version     
compile     config      doc-tool    import-jar  new         run-js      test        
$ ceylon compile[TAB]
compile     compile-js  
$ ceylon compile --re[TAB]
--rep\=       --resource\=  

As you can see, it’s quite useful, and again, that’s what you expect in this day and age.

Documentation

The tool system that we wrote (well, mostly that Tom Bentley wrote) even allows us to generate HTML, XML and text documentation automatically, for example, the tool documentation pages are entirely generated, and the man pages that we ship in the Ceylon CLI distribution are also generated.

Plugins, plugins

You may not have noticed it but if you type ceylon help on your system, it will likely not include that line:

[...]
DESCRIPTION

        [...]

        * 'all' - Compiles ceylon modules for JVM and JS and documents them

And that’s because, like Git, we support CLI plugins, and ceylon all is a plugin I wrote for myself, that aggregates a bunch of subcommands in a single one.

Ceylon CLI plugins are as straightforward as Git CLI plugins: any executable in your path which starts with ceylon- will be picked up to be a plugin. It will be listed and documented too. If you add documentation in your plugin it will be used.

Here’s my ceylon-all shell script for example:

#!/bin/sh

USAGE='[generic options] module...'
DESCRIPTION='Compiles ceylon modules for JVM and JS and documents them'
LONG_USAGE='ceylon-all allows you to build the specified ceylon modules for the
JVM and JS backends, and generates the API documentation in a single command.'

. $CEYLON_HOME/bin/ceylon-sh-setup

$CEYLON_HOME/bin/ceylon compile $@
$CEYLON_HOME/bin/ceylon compile-js $@
$CEYLON_HOME/bin/ceylon doc $@

As you can see, our CLI will use the USAGE, DESCRIPTION and LONG_USAGE environment variables for completion and documentation. You only have to source the provided $CEYLON_HOME/bin/ceylon-sh-setup shell script to benefit from this. Then you can run whatever you want.

Configuration

There are a lot of options the CLI and IDE have good defaults for, such as the source path, or the output module repository, or the list of module repositories. But when you need something different it gets tiring really fast to have to specify them for every command, so we support configuration files just like Git, in the form of INI files located in your project at .ceylon/config. If that file does not exist or a particular configuration option is not defined in it, we will also try the user’s config in $HOME/.ceylon/config as a fallback.

The syntax is straightforward, take for example the .ceylon/config for the Ceylon SDK:

[repositories]
output=./modules
lookup=./test-deps

[defaults]
encoding=UTF-8

It just specifies that the output repository is modules, the additional lookup repository is test-deps (for test dependencies) and the source encoding is UTF-8.

There’s a ceylon config command which allows you to edit this file if you don’t want to fire up vim for that, and the best part is that this config file is actually used and edited by the IDE too! So no more hassle to keep the CLI and IDE settings in sync.

Welcome to the future! :)