Package: mints
MintsClean and elegant CLI development kit.OverviewMints is a microframework that allows building declarative and nice-looking CLI apps.
UnlikeClickorPlac, it utilizesfunction annotationsmore thandecorators.Here is a quick example:# say.pyfrommintsimportcli,Arg,Flag,Opt@clidefsay(phrase:Arg('a phrase to print'),caps:Flag('whether to print phrase in upper-case'),times:Opt[int]('how many times to print')=1):"""Prints a phrase specified number of times."""for_inrange(times):print(phrase.upper()ifcapselsephrase)if__name__=='__main__':cli()And what we get in the command line:$ python3 say.py "Hi!" 
Hi!$ python3 say.py "Hi!" --caps
HI!$ python3 say.py "Hi!" --times 3
Hi!
Hi!
Hi!$ python3 say.py --help
usage: say [-h] [--caps] [--times TIMES] phrase

Prints a phrase specified number of times.

positional arguments:
  phrase         a phrase to print

optional arguments:
  -h, --help     show this help message and exit
  --caps         whether to print phrase in upper-case
  --times TIMES  how many times to printInstall$ pip install mintsGetting startedNote: the examples are notPEP 8compatible: one blank line is used instead of two to separate top-level definitions.In general, writing a CLI app is very similar to writing a regular function.
This is also true for Mints.Consider the following example:# say.pyfrommintsimportcli,Arg@clidefsay(phrase:Arg):print(phrase)if__name__=='__main__':cli()The script can be executed as a command-line app:$ python3 say.py "Hello, world!"
Hello, world!The main idea is very simple: you use theclidecorator to wrap a function that acts as an entry point of the application (sayfunction from the example above), and then call thecli()to make things running.In the next sections you'll find out how to build more complex apps in Mints.ParametersArgArgis an annotation for positional arguments.Positional arguments in CLIs work in the same way as in programming languages.Consider the following function:# test.pyfrommintsimportcli,Arg@clideftest(x:Arg,y:Arg):print(x,y)if__name__=='__main__':cli()$ python test.py 1 2
1 2Note: it's not possible to execute the script without an argument.$ python test.py 1
usage: test [-h] x y
test: error: the following arguments are required: yTo address this issue, you could provide a default value to the argument:# test.pyfrommintsimportcli,Arg@clideftest(x:Arg,y:Arg=2):print(x,y)if__name__=='__main__':cli()$ python test.py 1
1 2FlagFlagis an annotation for flags.Flags are boolean arguments that represent an on/off behavior.
Unlike positional arguments, they should be specified in the command line only with a special syntax.Here is an example of a flag:# test.pyfrommintsimportcli,Flag@clideftest(some:Flag):print(some)if__name__=='__main__':cli()$ python test.py --some
True$ python test.py
FalseOptOptis an annotation for options.Options are simply flags with values (or arguments with names).That's how you use theOpt:# test.pyfrommintsimportcli,Opt@clideftest(some:Opt):print(some)if__name__=='__main__':cli()$ python test.py --some 1
1Note: it's not possible tonotspecify the option by default, as it was for flags.$ python test.py --some
usage: test [-h] --some SOME
test: error: the following arguments are required: --someYou still could provide a default value:# test.pyfrommintsimportcli,Opt@clideftest(some:Opt=1):print(some)if__name__=='__main__':cli()$ python test.py
1Help pageEach CLI in Mints has a built-in help page, which is automatically generated.Consider the following example:# test.pyfrommintsimportcli,Arg@clideftest(some:Arg):print(some)if__name__=='__main__':cli()$ python test.py --help
usage: test [-h] some 

positional arguments:
  some

optional arguments:
  -h, --help  show this help message and exitNote the lack of the program description as well as thesomeargument description.To override the description of the program, put a simple doc-comment to a CLI function.
To assign a description to an argument, instantiate an annotation with thedescriptionargument (it always comes first).# test.pyfrommintsimportcli,Arg@clideftest(some:Arg('some argument')):"""A simple demonstration program."""print(some)if__name__=='__main__':cli()$ python test.py --help
usage: test [-h] some 

A simple demonstration program.

positional arguments:
  some        some argument

optional arguments:
  -h, --help  show this help message and exitShort nameUsually, both flags and options come with a shortcut syntax.
For example, instead of writing:$ python test.py --some 1One could write:$ python test.py -s 1To define a shortcut letter for a flag or an option, theshortparameter of eitherFlagorOptshould be used:# test.pyfrommintsimportcli,Flag@clideftest(some:Flag(short='s')):print(some)if__name__=='__main__':cli()$ python test.py -s
TruePrefixFlags and options are usually called with the-prefix (in short and long variations).
To override this behavior, theprefixparameter of eitherFlagorOptshould be used.# test.pyfrommintsimportcli,Flag@clideftest(some:Flag(prefix='+')):print(some)if__name__=='__main__':cli()$ python test.py ++some
TrueTypesBy default, an argument that is passed from the CLI is ofstrtype if it's annotated with eitherOptorArg, and ofboolif it's annotated withFlag.# test.pyfrommintsimportcli,Arg@clideftest(some:Arg):print(type(some))if__name__=='__main__':cli()$ python test.py 1
<class 'str'>Default typesTo parse a primitive type that is supported by theargparse, use the following syntax:# test.pyfrommintsimportcli,Arg@clideftest(some:Arg[int]):print(type(some))if__name__=='__main__':cli()$ python test.py 1
<class 'int'>User-defined typesTo parse a custom type, register a parser function just for that.You could use either theparsedecorator:# test.pyfrommintsimportcli,Arg# User-defined type.classCustom:def__init__(self,x):self.property=x# A parser for user-defined type.@cli.parsedefcustom(x:str)->Custom:returnCustom(x)@clideftest(some:Arg[Custom]):print(some.property)if__name__=='__main__':cli()$ python test.py 1
1Or theadd_parserfunction:# test.pyfrommintsimportcli,ArgclassCustom:def__init__(self,x):self.property=x@clideftest(some:Arg[Custom]):print(some.property)if__name__=='__main__':cli.add_parser(Custom)cli()$ python test.py 1
1Variable argumentsVariable arguments are also supported through the standardListtype:# test.pyfromtypingimportListfrommintsimportcli,Arg@clideftest(some:Arg[List[int]]):print(some)if__name__=='__main__':cli()$ python test.py 1 2 3
[1, 2, 3]Note that lists arenon-greedy:# test.pyfrommintsimportcli,Arg@clideftest(x:Arg[int],y:Arg[List[int]],z:Arg[int]):print(x,y,z)if__name__=='__main__':cli()$ python test.py 1 2 3 4
1 [2, 3] 4Consider checking therolling dicesexample with a more realistic use case.CommandsComplex command line interfaces likegithave several subcommands, e.g.,git status,git pull,git push, etc.
These subcommands act as separate CLIs and, thus, should be defined as separate functions in Mints.Consider the following example as a mock ofgitCLI:# git.pyfrommintsimportcli,Flag@clidefgit():...@git.commanddefpull(rebase:Flag):ifrebase:print('pulling with rebase')else:print('pulling')@git.commanddefpush():print('pushing')if__name__=='__main__':cli()$ python git.py pull
pulling$ python git.py pull --rebase
pulling with rebase$ python git.py push
pushingSometimes it's needed to have a deeper hierarchy of subcommands.
For example, thedotnetCLI tool allows callingdotnet tool install ....In Mints, this could be implemented in a natural way:# dotnet.pyfrommintsimportcli@clidefdotnet():...@dotnet.commanddeftool():...@tool.commanddefinstall():...if__name__=='__main__':cli()Learn moreLearn more by looking at our carefully preparedexamples.LicenseThe package is licensed under theMITlicense.ContributingBefore creating an issue or submitting a patch, check out ourcontribution guildelines.