Quick start
Quick start
Let's create a simple command-line application using nuclear.
Let's assume we already have our fancy functions as follows:
def say_hello(name: str, decode: bool, repeat: int):
message = f"I'm a {b64decode(name).decode() if decode else name}!"
print(' '.join([message] * repeat))
def calculate_factorial(n: int):
print(reduce(lambda x, y: x * y, range(1, n + 1)))
def calculate_primes(n: int):
print(sorted(reduce((lambda r, x: r - set(range(x**2, n, x)) if (x in r) else r),
range(2, n), set(range(2, n)))))
./quickstart.py hello NAME --decode --repeat=3mapped tosay_hellofunction,./quickstart.py calculate factorial Nmapped tocalculate_factorialfunction,./quickstart.py calculate primes Nmapped tocalculate_primesfunction,
We've just identified 2 main commands in a program: hello and calculate (which in turn contains 2 subcommands: factorial & primes). That forms a tree:
hellocommand has one positional argumentNAME, one boolean flagdecodeand one numerical parameterrepeat.calculatecommand has 2 another subcommands:factorialsubcommand has one positional argumentN,primessubcommand has one positional argumentN,
So our CLI definition may be declared using nuclear in a following way:
CliBuilder().has(
subcommand('hello', run=say_hello).has(
argument('name'),
parameter('repeat', type=int, default=1),
flag('decode', help='Decode name as base64'),
),
subcommand('calculate').has(
subcommand('factorial', run=calculate_factorial,
help='Calculate factorial').has(
argument('n', type=int),
),
subcommand('primes', run=calculate_primes,
help='List prime numbers using Sieve of Eratosthenes').has(
argument('n', type=int, required=False, default=100,
help='maximum number to check'),
),
),
)
Getting it all together, we've bound our function with a Command-Line Interface:
quickstart.py:
#!/usr/bin/env python3
from base64 import b64decode
from functools import reduce
from nuclear import CliBuilder, argument, flag, parameter, subcommand
def say_hello(name: str, decode: bool, repeat: int):
message = f"I'm a {b64decode(name).decode() if decode else name}!"
print(' '.join([message] * repeat))
def calculate_factorial(n: int):
print(reduce(lambda x, y: x * y, range(1, n + 1)))
def calculate_primes(n: int):
print(sorted(reduce((lambda r, x: r - set(range(x**2, n, x)) if (x in r) else r),
range(2, n), set(range(2, n)))))
CliBuilder().has(
subcommand('hello', run=say_hello).has(
argument('name'),
flag('decode', help='Decode name as base64'),
parameter('repeat', type=int, default=1),
),
subcommand('calculate').has(
subcommand('factorial', help='Calculate factorial', run=calculate_factorial).has(
argument('n', type=int),
),
subcommand('primes', help='List prime numbers using Sieve of Eratosthenes', run=calculate_primes).has(
argument('n', type=int, required=False, default=100, help='maximum number to check'),
),
),
).run()
Let's trace what is happening here:
CliBuilder()builds CLI tree for entire application..has(...)allows to embed other nested rules inside that builder. ReturnsCliBuilderitself for further building.subcommand('hello', run=say_hello)bindshellocommand tosay_hellofunction. From now, it will be invoked whenhellocommand occurrs.subcommand.has(...)embeds nested subrules on lower level for that subcommand only.argument('name')declares positional argument. From now, first CLI argument (after binary name and commands) will be recognized asnamevariable.flag('decode')binds--decodekeyword to a flag nameddecode. So as it may be used later on. Providinghelpadds description to help screen.parameter('repeat', type=int, default=1)binds--repeatkeyword to a parameter namedrepeat, which type isintand its default value is1.- Finally, invoking
.run()does all the magic.
It gets system arguments list, starts to process them and invokes most relevant action.
Decorator builder
We can do the same using decorator-based syntax, which binds the functions to the CLI:
cli = CliBuilder()
@cli.add_command('hello')
def say_hello(name: str, decode: bool = False, repeat: int = 1):
"""Say hello to someone"""
message = f"I'm a {b64decode(name).decode() if decode else name}!"
print(' '.join([message] * repeat))
@cli.add_command('calculate', 'factorial')
def calculate_factorial(n: int):
"""Calculate factorial"""
print(reduce(lambda x, y: x * y, range(1, n + 1)))
@cli.add_command('calculate', 'primes')
def calculate_primes(n: int = 100):
"""List prime numbers using Sieve of Eratosthenes"""
print(sorted(reduce((lambda r, x: r - set(range(x**2, n, x)) if (x in r) else r),
range(2, n), set(range(2, n)))))
if __name__ == '__main__':
cli.run()
Help / Usage
CliBuilder has some basic options added by default, e.g. --help.
Thus, you can check the usage by running application with --help flag:
foo@bar:~$ ./quickstart.py --help
Usage:
./quickstart.py [COMMAND] [OPTIONS]
Options:
-h, --help [SUBCOMMANDS...] - Display this help and exit
Commands:
hello NAME
calculate factorial N - Calculate factorial
calculate primes [N] - List prime numbers using Sieve of Eratosthenes
Run "./quickstart.py COMMAND --help" for more information on a command.
As prompted, we can check more detailed subcommand helps:
foo@bar:~$ ./quickstart.py hello --help
Usage:
./quickstart.py hello [OPTIONS] NAME
Arguments:
NAME
Options:
--decode - Decode name as base64
--repeat REPEAT - Default: 1
-h, --help [SUBCOMMANDS...] - Display this help and exit
Injecting parameters
Let's invoke say_hello function on a first run.
Now when we execute our application with required argument provided, we get:
foo@bar:~$ ./quickstart.py hello world
I'm a world!
world has been recognized as name argument.
We've binded say_hello as a default action, so it has been invoked with particular parameters:
say_hello(name='world', decode=False, repeat=1)
- positional argument
namehas been assigned a'world'value. - flag
decodewas not given, so it'sFalseby default. - parameter
repeatwas not given either, so it was set to its default value1.
Let's provide all of the parameters explicitly, then we get:
foo@bar:~$ ./quickstart.py hello UGlja2xl --decode --repeat=3
I'm a Pickle! I'm a Pickle! I'm a Pickle!
foo@bar:~$ ./quickstart.py hello --repeat 3 --decode UGlja2xl
I'm a Pickle! I'm a Pickle! I'm a Pickle!
Invoking other subcommands is just as easy:
foo@bar:~$ ./quickstart.py calculate primes 50
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 49]
When you are writing function for your action and you need to access some of the variables (flags, parameters, arguments, etc.),
just simply add a parameter to the function with a name same as the variable you need.
Then, the proper value will be parsed and injected by nuclear.