/usr/share/doc/python-guacamole-doc/html/_sources/usage/recipes.txt is in python-guacamole-doc 0.9.2-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | .. _commands:
===================
Using Stock Recipes
===================
The Command Recipe
==================
The command recipe contains the distilled, correct behavior for command line
applications. The main face of the command recipe is the
:class:`~guacamole.recipes.cmd.Command` class.
.. note::
Guacamole values conventions. Instead of overriding many of the methods
that comprise the Command class, you can just define a variable that will
take priority. This leads to shorter and more readable code.
Defining commands
-----------------
Let's build a simple hello-world example again:
.. testsetup::
import guacamole
.. doctest::
>>> class HelloWorld(guacamole.Command):
... def invoked(self, ctx):
... print("Hello World!")
The central entry point of each command is the
:meth:`~guacamole.recipes.cmd.Command.invoked()` method. The method is called
once the command is ready to be dispatched. This is what you would put inside
your ``main()`` function, after the boiler-plate code that Guacamole handles
for you. What you do here is up to you.
For now, let's just run our simple example with the convenience method
:meth:`~guacamole.recipes.cmd.Command.main()`. Note that here we're passing
extra arguments to control how the tool executes, normally you would just call
main without any arguments and it will do the right thing.
.. doctest::
>>> HelloWorld().main([], exit=False)
Hello World!
0
For now let's ignore the argument `ctx`. It is extremely handy, as we will see
shortly, but we don't need it yet.
.. note::
This little example is available in the ``examples/`` directory in the
source distribution. The version of Guacaomle packaged in Debian has them
in the directory ``/usr/share/doc/python-guacamole-doc/examples``. As the
directory name implies, you have to install the ``python-guacamole-doc``
package to get them.
Do use the example and play around with it, see how it behaves if you run
it with various arguments. The idea is that Guacamole is supposed to create
*good* command line applications. Good applications do the right stuff
internally. The ``hello-world`` example is trivial but we'll see more of
what is going on internally soon.
Working with arguments & The Context
------------------------------------
Commands typically take arguments. To say which arguments are understood by our
command we need to implement the second method
:meth:`~guacamole.recipes.cmd.Command.register_arguments()`. This method is
called with the familiar :py:class:`argparse.ArgumentParser` instance. You've
seen this code over and over, here you should just focus on configuring the
arguments and options. Guacamole handles the parser for you.
.. doctest::
>>> class HelloWorld(guacamole.Command):
... def register_arguments(self, parser):
... parser.add_argument('name')
... def invoked(self, ctx):
... print("Hello {0}!".format(ctx.args.name))
As you can see, the context is how you reach the command line arguments parsed
by `argparse`. What else is there you might ask? The answer is *everything*.
The context is how *ingredients* can expose useful capabilities to commands.
The command recipe is comprised of several ingredients, as you will later see.
One of those ingredients parsers command line arguments and adds the results to
the context as the ``args`` object.
.. note::
When reading documentation about particular ingredients make sure to see
how they interact with the context. Each ingredient documents that clearly.
Let's run our improved command and see what happens:
.. doctest::
>>> HelloWorld().main(["Guacamole"], exit=False)
Hello Guacamole!
0
No surprises there. We can see that the command printed the hello message and
then returned the exit code ``0``. The exit code is normally passed to the
system so that your application can be scripted.
.. note::
Guacamole will return ``0`` for you if you don't return anything. If you do
return a value we'll just preserve it for you. You can also raise
SystemExit with any value and we'll do the right thing yet again.
This should be all quite familiar to everyone so we won't spend more time on
arguments now. You can read the :py:ref:`argparse-tutorial` if you want.
A small digression, why argparse?
---------------------------------
By default, all command line parsing is handled by :py:mod:`argparse`.
Guacamole doesn't force you to use argparse (nothing really is wired to depend
on it in the core) but the stock set of ingredients do use it. Argparse is
familiar to many developers and by having it by default you can quickly convert
your application code over to guacamole without learning two new things at a
time.
Nesting Commands
----------------
Many common tools expose everything from a top-level command, e.g. ``git
commit``. Here, ``git`` gets invoked, looks at the command line arguments and
delegates the dispatching to the ``git-commit`` command.
All Guacamole commands can be nested. Let's build a quick git-like command to
see how to do that.
.. doctest::
>>> class git_commit(guacamole.Command):
... name = 'commit'
... def invoked(self, ctx):
... print("commit invoked")
>>> class git_log(guacamole.Command):
... def invoked(self, ctx):
... print("log invoked")
>>> class git(guacamole.Command):
... name = 'git'
... sub_commands = (
... (None, git_commit),
... ('log', git_log),
... )
As you see it's all based on declarations. Each command now cares about the
name it is using. Names can be assigned in the ``sub_commands`` list or
individually in each class, by defining the ``name`` attribute.
The name listed in sub_commands takes precedence over the name defined in the
class. Here, the ``git_log`` command doesn't define a ``name`` so we provide
one explicitly as the first element of the pair, as sequence of which is stored
in ``sub_commands``.
.. note::
Behind the scenes Guacamole actually calls a number of methods for
everything. See :meth:`~guacamole.recipes.cmd.Command.get_sub_commands()`
and :meth:`~guacamole.recipes.cmd.Command.get_cmd_name()` for the two used
here. There are *many* more methods though.
Let's invoke our fake git to see how that works now:
.. doctest::
>>> git().main(["commit"], exit=False)
commit invoked
0
>>> git().main(["log"], exit=False)
log invoked
0
So far everything behaves as expected. Let's see what happens if we run
something that we've not coded:
.. doctest::
>>> git().main(["status"], exit=False)
2
This won't fit the *doctest* above (it's printed on stderr) but in reality the
application will also say something like this::
usage: git [-h] {commit,log} ...
setup.py: error: invalid choice: 'status' (choose from 'commit', 'log')
.. note::
Technically the :class:`~guacamole.recipes.cmd.Command` class has numerous
methods. Most of those methods are of no interest to most of the
developers. Feel free to read the API reference later if you are
interested.
|