Navigation

Cleo 0.2 is now out with a lot of improvements and new features.

What is cleo?

Cleo eases the creation of beautiful and testable command line interfaces.

It is heavily inspired by the Symfony Console Component, with some useful additions.

Full documentation available here: http://cleo.readthedocs.org

Installation

You can install Cleo in two different ways:

Usage

The Application object manages the CLI application:

from cleo import Application

console = Application()
console.run()

The run() method parses the arguments and options passed on the command line and executes the right command.

Registering a new command can easily be done via the register() method, which returns a Command instance:

from cleo.input import InputArgument, InputOption

def hello(input_, output_):
    name = input_.get_argument('name')

    output_.writeln('Hello <info>%s</info>' % name)

console\
    .register('hello')\
    .set_definition([
        InputArgument('name', InputArgument.REQUIRED, 'The name'),
    ])\
    .set_description('Says hello!')\
    .set_code(hello)

You can also register new commands via classes.

from cleo import Command

class HelloCommand(Command):

    def configure(self):
        self.set_name('hello')\
            .set_description('Says hello!')\
            .add_argument('name', InputArgument.REQUIRED, 'The name')

    def execute(input_, output_):
        name = input_.get_argument('name')

        output_.writeln('Hello <info>%s</info>' % name)

console.add(HelloCommand())

But it might be a little too verbose for some. That's why commands can also be declared and registered via dictionaries.

hello_command = {
    'name': 'hello',
    'description': 'Says hello!',
    'arguments': [
        'name': {
            'mode': 'required',
            'description': 'The name'
        }
    ],
    'code': hello
}

console.add(hello_command)

With dictionaries, you will not be able to do as many things as with classes declaration since you won't have access to the Command instance.

Output coloring

Cleo provides output coloring out of the box, you just need to surround the text with tags:

# green text
output_.writeln('<info>foo</info>')

# yellow text
output_.writeln('<comment>foo</comment>')

# black text on a cyan background
output_.writeln('<question>foo</question>')

# white text on a red background
output_.writeln('<error>foo</error>')

Those are the default styles but you can easily define your own :

style = OutputFormatterStyle('red', 'yellow', ['bold', 'blink'])
output_.get_formatter().set_style('fire', style)
output_.writeln('<fire>foo</fire>')

If you don't want to set the styles upfront, you can just as easily set the colors and options inside the tagnames :

# green text
output_.writeln('<fg=green>foo</fg=green>')

# black text on a cyan background
output_.writeln('<fg=black;bg=cyan>foo</fg=black;bg=cyan>')

# bold text on a yellow background
output_.writeln('<bg=yellow;options=bold>foo</bg=yellow;options=bold>')

Testable commands

Cleo provides input and output abstraction so that you can easily unit-test your commands, especially with the CommandTester class:

from unittest import TestCase
from cleo import Application, CommandTester

class GreetCommandTest(TestCase):

    def test_execute(self):
        application = Application()
        application.add(greet_command)
        # Or application.add(GreetCommand()) if using classes

        commmand = application.find('demo:greet')
        command_tester = CommandTester(command)
        command_tester.execute([('command', command.get_name())])

        self.assertRegex('...', command_tester.get_display())

        # ...

Basically, the get_display() method returns what would have been displayed during a normal call from the console.

Helpers

Cleo comes bundled with some nice helpers that will cover some basic needs when developing command-line interfaces.

The DialogHelper will, basically, prompt the user for answers:

# ...
if dialog.ask_confirmation(
    output_,
    '<question>Continue with this action?</question>',
    False
):
    # Some code

This code will prompt for a yes/no anwser, while this code:

# ...
name = dialog.ask(
    output_,
    'Please enter your name',
    'John Doe'
)

will prompt for a more generic answer.

The ProgressHelper allows you to display a progress bar for actions that might take a while:

progress = self.get_helper_set().get('progress')

progress.start(output_, 50)

for _ in range(50)
    # ... do some work

    # advance the progress bar 1 unit
    progress.advance()

progress.finish()

And the TableHelper will display automatically tabular data:

table = app.get_helper_set().get('table')
table.set_headers(['ISBN', 'Title', 'Author'])
table.set_rows([
   ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
   ['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'],
   ['960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'],
   ['80-902734-1-6', 'And Then There Were None', 'Agatha Christie']
])

table.render(output_)

There is a lot more you can do with Cleo, you can just give a look at the documentation: http://cleo.readthedocs.org.

And if you are interested by how it all works, you can just check out the Github Repository, and feel free to contribute.

And, finally, here are some features for the 0.3 version:

  • Set commands with decorators
  • Validators for the arguments and options
  • Autocompletion of commands

You can check the advancement on the Github project

Sébastien Eustace

Sébastien Eustace

sebastien.eustace.io

Born & raised in France, and currently living in the beautiful city of Quito, Ecuador, I'm a software engineer, proud pythonista (but knowledgeable in other languages and technologies as well) but overall an open source lover.

View Comments