Navigation

Cleo 0.4 is now out. Commands have been largely improved and are now easier to use. Helpers have also been improved. Overall, Cleo is now more intuitive.

This version breaks the backward compatibility for the following parts:

  • The DialogHelper has been replaced by a more robust QuestionHelper
  • The ProgressHelper has been removed and the ProgressBar class must be used
  • When using decorators or dictionaries, the signature of the callback has changed and accepts only a Command instance

New Commands

The Command class has been improved, with a new way of describing it via docstring and several helper methods:

Command signature

The definition of a command can now be declared in the docstring:

class GreetCommand(Command):
    """
    Greets someone

    demo:greet
        {name? : Who do you want to greet?}
        {--y|yell : If set, the task will yell in uppercase letters}
    """

The handle() method

The logic of the command now needs to be put in the handle() method:

class GreetCommand(Command):
    """
    Greets someone

    demo:greet
        {name? : Who do you want to greet?}
        {--y|yell : If set, the task will yell in uppercase letters}
    """

    def handle(self):
        name = self.argument('name')

        if name:
            text = 'Hello %s' % name
        else:
            text = 'Hello'

        if self.option('yell'):
            text = text.upper()

        self.line(text)

Helper methods

Commands are now easier to use with new helper methods.

argument() and option()

These methods make it easier to access command's arguments and options.

name = self.argument('name')

yell = self.option('yell')

line()

This methods writes a new line to the output:

self.line('New line')

A style can also be passed as a second argument:

self.line('New line', 'comment')

The native styles have their own helper methods:

self.info('foo')

self.comment('foo')

self.question('foo')

self.error('foo')

call()

Calls another command:

return_code = self.call('demo:greet', [
    ('name', 'John'),
    ('--yell', True)
])

If you want to suppress the output of the executed command, you can use the call_silent() method instead.

confirm()

Confirm a question with the user.

confirmed = self.confirm('Continue with this action?', False)

In this case, the user will be asked “Continue with this action?”. If the user answers with y it returns True or False if they answer with n. The second argument to confirm() is the default value to return if the user doesn’t enter any valid input. If the second argument is not provided, True is assumed.

ask()

Prompt the user for input.

name = self.ask('Please enter your name', 'John Doe')

secret()

Prompt the user for input but hide the answer from the console.

password = self.secret('Enter the database password')

choice()

Give the user a single choice from an list of answers.

def handle(self):
    color = self.choice(
        'Please select your favorite color (defaults to red)',
        ['red', 'blue', 'yellow'],
        0
    )

    self.line('You have just selected: %s' % color)

render_table()

Format input to textual table.

def handle(self):
    headers = ['ISBN', 'Title', 'Author']
    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']
    ]

    self.render_table(
        headers,
        rows   
    )

You can also use the table() method to retrieve a Table instance.

progress_bar()

Creates a new progress bar.

def handle(self):
    # Create a new progress bar (50 units)
    progress = self.progress_bar(50)

    # Start and displays the progress bar
    for _ in range(50):
        # ... do some work

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

        # You can also advance the progress bar by more than 1 unit
        # progress.advance(3)

    # Ensure that the progress bar is at 100%
    progress.finish()

set_style()

Sets a new formatting style.

def handle(self):
    self.set_style('fire', fg='red', bg='yellow', options=['bold', 'blink'])

    self.line('<fire>foo</fire>')

Progress Bar

The ProgressHelper has been removed and the improved ProgressBar (or its helper method progress_bar()) must now be used instead: 

def handle(self):
    # Create a new progress bar (50 units)
    progress = self.progress_bar(50)

    # Start and displays the progress bar
    for _ in range(50):
        # ... do some work

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

        # You can also advance the progress bar by more than 1 unit
        # progress.advance(3)

    # Ensure that the progress bar is at 100%
    progress.finish()

Table

The TableHelper has been deprecated and the improved Table (or its helper method table()) should now be used instead:

def handle(self):
    table = self.table()

    headers = ['ISBN', 'Title', 'Author']
    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.set_headers(headers)
    table.set_rows(rows)

    table.render()

Question Helper

The DialogHelper has been removed and the improved QuestionHelper (or one of its helper methods) must now be used instead:

def handle(self):
    confirmed = self.confirm('Continue with this action?', False)

    name = self.ask('Please enter your name', 'John Doe')

    password = self.secret('Enter the database password')

    color = self.choice(
        'Please select your favorite color (defaults to red)',
        ['red', 'blue', 'yellow'],
        0
    )

More verbosity

Two other levels of verbosity (-vv and -vvv) have been added.

Command description format

Commands description can now be output as json and markdown:

console help demo:greet --format json

console help demo:greet --format md

Decorators and dictionaries notation

When using decorators or dictionaries, the signature of the callback has changed and accepts only a Command instance:

def decorated(c):
    """
    :type c: Command
    """

This is so that helper methods can be accessed inside code functions.

Autocompletion

Autocompletion has also been improved, and the old bash_completion.sh has been removed.

To activate support for autocompletion, pass a complete keyword when initializing your application:

application = Application('My Application', '0.1', complete=True)

Now, register completion for your application by running one of the following in a terminal, replacing [program] with the command you use to run your application:

# BASH ~4.x, ZSH
source <([program] _completion --generate-hook)

# BASH ~3.x, ZSH
[program] _completion --generate-hook | source /dev/stdin

# BASH (any version)
eval $([program] _completion --generate-hook)

By default this registers completion for the absolute path to you application, which will work if the program is accessible on your PATH. You can specify a program name to complete for instead using the -p\--program option, which is required if you're using an alias to run the program.

If you want the completion to apply automatically for all new shell sessions, add the command to your shell's profile (eg. ~/.bash_profile or ~/.zshrc)

Fixes

  • Values are now properly cast by validators
  • Fixing "flag" not being set properly
  • Progress bar now behaves properly (Fixes #37)
  • The -n|--no-interaction option behaves properly (Fixes #38 and #39)

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

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