Package: cleo
CleoCreate beautiful and testable command-line interfaces.ResourcesDocumentationIssue TrackerUsageTo make a command that greets you from the command line, creategreet_command.pyand add the following to it:fromcleo.commands.commandimportCommandfromcleo.helpersimportargument,optionclassGreetCommand(Command):name="greet"description="Greets someone"arguments=[argument("name",description="Who do you want to greet?",optional=True)]options=[option("yell","y",description="If set, the task will yell in uppercase letters",flag=True)]defhandle(self):name=self.argument("name")ifname:text=f"Hello{name}"else:text="Hello"ifself.option("yell"):text=text.upper()self.line(text)You also need to create the fileapplication.pyto run at the command line which
creates anApplicationand adds commands to it:#!/usr/bin/env pythonfromgreet_commandimportGreetCommandfromcleo.applicationimportApplicationapplication=Application()application.add(GreetCommand())if__name__=="__main__":application.run()Test the new command by running the following$pythonapplication.pygreetJohnThis will print the following to the command line:Hello JohnYou can also use the--yelloption to make everything uppercase:$pythonapplication.pygreetJohn--yellThis prints:HELLO JOHNColoring the OutputWhenever you output text, you can surround the text with tags to color
its output. For example:# blue textself.line("<info>foo</info>")# green textself.line("<comment>foo</comment>")# cyan textself.line("<question>foo</question>")# bold red textself.line("<error>foo</error>")The closing tag can be replaced by</>, which revokes all formatting
options established by the last opened tag.It is possible to define your own styles using theadd_style()method:self.add_style("fire",fg="red",bg="yellow",options=["bold","blink"])self.line("<fire>foo</fire>")Available foreground and background colors are:black,red,green,yellow,blue,magenta,cyanandwhite.And available options are:bold,underscore,blink,reverseandconceal.You can also set these colors and options inside the tag name:# green textself.line("<fg=green>foo</>")# black text on a cyan backgroundself.line("<fg=black;bg=cyan>foo</>")# bold text on a yellow backgroundself.line("<bg=yellow;options=bold>foo</>")Verbosity LevelsCleo has four verbosity levels. These are defined in theOutputclass:ModeMeaningConsole optionVerbosity.QUIETDo not output any messages-qor--quietVerbosity.NORMALThe default verbosity level(none)Verbosity.VERBOSEIncreased verbosity of messages-vVerbosity.VERY_VERBOSEInformative non essential messages-vvVerbosity.DEBUGDebug messages-vvvIt is possible to print a message in a command for only a specific
verbosity level. For example:ifVerbosity.VERBOSE<=self.io.verbosity:self.line(...)There are also more semantic methods you can use to test for each of the
verbosity levels:ifself.output.is_quiet():# ...ifself.output.is_verbose():# ...You can also pass the verbosity flag directly toline().self.line("",verbosity=Verbosity.VERBOSE)When the quiet level is used, all output is suppressed.Using ArgumentsThe most interesting part of the commands are the arguments and options
that you can make available. Arguments are the strings - separated by
spaces - that come after the command name itself. They are ordered, and
can be optional or required. For example, add an optionallast_nameargument to the command and make thenameargument required:classGreetCommand(Command):name="greet"description="Greets someone"arguments=[argument("name",description="Who do you want to greet?",),argument("last_name",description="Your last name?",optional=True)]options=[option("yell","y",description="If set, the task will yell in uppercase letters",flag=True)]You now have access to alast_nameargument in your command:last_name=self.argument("last_name")iflast_name:text+=f"{last_name}"The command can now be used in either of the following ways:$pythonapplication.pygreetJohn
$pythonapplication.pygreetJohnDoeIt is also possible to let an argument take a list of values (imagine
you want to greet all your friends). For this it must be specified at
the end of the argument list:classGreetCommand(Command):name="greet"description="Greets someone"arguments=[argument("names",description="Who do you want to greet?",multiple=True)]options=[option("yell","y",description="If set, the task will yell in uppercase letters",flag=True)]To use this, just specify as many names as you want:$pythonapplication.pygreetJohnJaneYou can access thenamesargument as a list:names=self.argument("names")ifnames:text="Hello "+", ".join(names)Using OptionsUnlike arguments, options are not ordered (meaning you can specify them
in any order) and are specified with two dashes (e.g.--yell- you can
also declare a one-letter shortcut that you can call with a single dash
like-y). Options arealwaysoptional, and can be setup to accept a
value (e.g.--dir=src) or simply as a boolean flag without a value
(e.g.--yell).Tip: It is also possible to make an optionoptionallyaccept a value (so
that--yellor--yell=loudwork). Options can also be configured to
accept a list of values.For example, add a new option to the command that can be used to specify
how many times in a row the message should be printed:classGreetCommand(Command):name="greet"description="Greets someone"arguments=[argument("name",description="Who do you want to greet?",optional=True)]options=[option("yell","y",description="If set, the task will yell in uppercase letters",flag=True),option("iterations",description="How many times should the message be printed?",default=1)]Next, use this in the command to print the message multiple times:for_inrange(int(self.option("iterations"))):self.line(text)Now, when you run the task, you can optionally specify a--iterationsflag:$pythonapplication.pygreetJohn
$pythonapplication.pygreetJohn--iterations=5The first example will only print once, sinceiterationsis empty and
defaults to1. The second example will print five times.Recall that options don't care about their order. So, either of the
following will work:$pythonapplication.pygreetJohn--iterations=5--yell
$pythonapplication.pygreetJohn--yell--iterations=5Testing CommandsCleo provides several tools to help you test your commands. The most
useful one is theCommandTesterclass. It uses a special IO class to
ease testing without a real console:fromgreet_commandimportGreetCommandfromcleo.applicationimportApplicationfromcleo.testers.command_testerimportCommandTesterdeftest_execute():application=Application()application.add(GreetCommand())command=application.find("greet")command_tester=CommandTester(command)command_tester.execute()assert"..."==command_tester.io.fetch_output()TheCommandTester.io.fetch_output()method returns what would have
been displayed during a normal call from the console.CommandTester.io.fetch_error()is also available to get what you have
been written to the stderr.You can test sending arguments and options to the command by passing
them as a string to theCommandTester.execute()method:fromgreet_commandimportGreetCommandfromcleo.applicationimportApplicationfromcleo.testers.command_testerimportCommandTesterdeftest_execute():application=Application()application.add(GreetCommand())command=application.find("greet")command_tester=CommandTester(command)command_tester.execute("John")assert"John"incommand_tester.io.fetch_output()You can also test a whole console application by using theApplicationTesterclass.Calling an existing CommandIf a command depends on another one being run before it, instead of
asking the user to remember the order of execution, you can call it
directly yourself. This is also useful if you want to create a "meta"
command that just runs a bunch of other commands.Calling a command from another one is straightforward:defhandle(self):return_code=self.call("greet","John --yell")returnreturn_codeIf you want to suppress the output of the executed command, you can use
thecall_silent()method instead.AutocompletionCleo supports automatic (tab) completion inbash,zshandfish.By default, your application will have acompletionscommand. To register these completions for your application, run one of the following in a terminal (replacing[program]with the command you use to run your application):# Bash[program]completionsbash|sudotee/etc/bash_completion.d/[program].bash-completion# Bash - macOS/Homebrew (requires `brew install bash-completion`)[program]completionsbash>$(brew--prefix)/etc/bash_completion.d/[program].bash-completion# Zshmkdir~/.zfuncecho"fpath+=~/.zfunc">>~/.zshrc[program]completionszsh>~/.zfunc/_[program]# Zsh - macOS/Homebrew[program]completionszsh>$(brew--prefix)/share/zsh/site-functions/_[program]# Fish[program]completionsfish>~/.config/fish/completions/[program].fish