Skip to content

Commit

Permalink
some 'extending the parser' proofreading
Browse files Browse the repository at this point in the history
  • Loading branch information
epsy committed Mar 18, 2015
1 parent cb8b7fc commit c24476b
Showing 1 changed file with 65 additions and 53 deletions.
118 changes: 65 additions & 53 deletions docs/parser.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,33 @@ contraints:
* Other arguments are processed by the positional parameters in the order they
are stored.

This document will explain each step of the CLI inference and argument parsing process. An example then shows how you can add a new kind of parameter.
This document explains each step of the CLI inference and argument parsing
process. An example shows how you can add a custom kind of parameter.


.. _parser overview:

Execution overview
------------------

1. `clize.run` is called with either a function, sequencce or mapping thereof.
1. `clize.run` is called with either a function, or a sequence or mapping of
functions.
2. It forwards that to `.Clize.get_cli` in order to get a :ref:`cli
object<cli-object>` to run.
3. If there is more than one function, `.Clize.get_cli` uses
`.SubcommandDispatcher` to wrap it, otherwise it creates a new `.Clize`
instance with it and returns it.
4. `.run` calls that cli object with `sys.argv`.
5. Assuming that object is a `.Clize` instance, it will now actually look at
the function that was passed and create a `.CliSignature` object out of it.
6. It then uses its `~.CliSignature.read_arguments` method with the arguments,
which returns a `~.CliBoundArguments` instance. More on this below.
7. That instance carries which function needs to be run, overriding the default
one if present, as well as the arguments to be passed to it. `.Clize` then
runs that function and lets its return value bubble up to `clize.run` which
prints it.
4. `.run` calls that cli object with `sys.argv` unpacked (ie.
``obj(*sys.argv)``).
5. Assuming that cli object is a `.Clize` instance, it will look at the
function that was passed and determine a `.CliSignature` object from it.
6. `.Clize` calls the cli signature's `~.CliSignature.read_arguments` method
with the command-line arguments, which returns a `~.CliBoundArguments`
instance.
7. That `~.CliBoundArguments` instance carries information such as the
arguments that the function will be called with or the instruction to
replace that function with another entirely. `.Clize` then runs the chosen
function and `clize.run` prints its return value.


.. _parameter conversion:
Expand All @@ -51,15 +55,17 @@ Parameter conversion
In step 5 above, `.CliSignature.from_signature` converts each parameter. Here
is the process followed for each parameter:

1. The annotations are checked for `clize.Parameter.IGNORE`. If found, the
parameter is dropped with no further processing.
2. The annotations are searched for a parameter converter function. If none is
found, `.default_converter` is used.
3. That function is called with the `inspect.Parameter` object and a filtered
sequence of annotations which you should use rather than the parameter's
``annotations`` attribute. Its return value, expected to be a
`clize.Parameter` instance, is added to the list of parameters for the
resulting `~.CliSignature` instance.
1. The annotation for each parameter is read as a sequence. If it isn't one
then it is converted first.
2. The annotations are searched for `clize.parser.Parameter.IGNORE`. If found,
the parameter is dropped with no further processing.
3. The annotations are searched for a :ref:`parameter converter <parameter
converter>` function. If none is found, `.default_converter` is used.
4. The parameter converter is called with the `inspect.Parameter` object
representing the parameter and the sequence of annotations without the
parameter converter. Its return value, expected to be a
`clize.parser.Parameter` instance, is added to the list of parameters for
the resulting `~.CliSignature` instance.


.. _default-converter:
Expand All @@ -73,14 +79,16 @@ The default parameter converter works as follows:
positional parameter. This is used to check if it is legal to assign aliases
to it and to determine what cli parameter class will be used to represent it.
* It looks at the parameter's default value and extracts its type, expecting
that it can be called with a string to produce a valid value. If there isn't
that it is a valid :ref:`value converter <value converter>`. If there isn't
one the parameter is marked as required.
* The annotations sequence is iterated on:

* If the annotation is a `~clize.Parameter` instance, it is returned
* If the annotation is a `~clize.parser.Parameter` instance, it is returned
immediately with no processing.
* If the annotation is callable, it is used as coercion function.
* If it is a string, it is used as an alias if the parameter is not
* If the annotation is a :ref:`value converter <value converter>` it is used
instead of the default value's type. Specifying a value converter is
required when the default value's type isn't a valid one itself.
* If it is a string, it is used as an alias unless the parameter is
positional.

* Finally, depending on the above, a parameter class is picked, instantiated
Expand All @@ -95,8 +103,8 @@ The default parameter converter works as follows:
* An error is raised for ``**kwargs`` parameters, as their expected
equivalent in a CLI is largely subjective. If you want to forward arguments
to another function, consider using :ref:`function
compositing<function-compositing>` instead of having an actual parameter
handle it.
compositing<function-compositing>` instead of having a CLI parameter handle
it.


.. _parser description:
Expand All @@ -105,25 +113,28 @@ The argument parser
-------------------

The argument parsing is orchestrated by `.CliBoundArguments` during its
initialization. For each argument of its input, it calls the
`~.Parameter.read_argument` method of parameter instances from the cli
signature object. When the argument on the input starts with ``-`` it looks in
its named parameter dict, otherwise it picks the next positional parameter.
initialization. For each argument of its input, it selects the appropriate
`.Parameter` instance to handle it. If the argument on the input starts with
``-`` it looks in the `CliSignature.named` dictionary. If not, it picks the
next positional parameter from `CliSignature.positional`. The parameter's
`~.Parameter.read_argument` and `~.Parameter.apply_generic_flags` methods are
called.

.. automoremethod:: .Parameter.read_argument

This method is expected to mutate ``ba``, an instance of `~.CliBoundArguments`.
In particular, it should add any relevant arguments to its
In particular, it should add any relevant arguments to ``ba``'s
`~.CliBoundArguments.args` and `~.CliBoundArguments.kwargs` attributes which
are used when calling the wrapped callable as in ``func(*args, **kwargs)``, or
set the `~.CliBoundArguments.func` attribute which switches the wrapped
callable for another.
are used when calling the wrapped callable as in ``func(*args, **kwargs)``. It
can also set the `~.CliBoundArguments.func` attribute which overrides the
`~clize.Clize` object's wrapped callable.

Part of the parameter's behavior is split from `read_argument` into `apply_generic_flags` in order to facilitate subclassing:
Part of the parameter's behavior is split from `~.Parameter.read_argument` into
`~.Parameter.apply_generic_flags` in order to facilitate subclassing:

.. automoremethod:: .Parameter.apply_generic_flags

This pair of methods are expected to discard the parameter from
The both of these methods are expected to discard the parameter from
`~.CliBoundArguments.unsatisfied`, the list of still-unsatisfied required
parameters, when applicable. The `~.CliBoundArguments.sticky`,
`~.CliBoundArguments.posarg_only` and `~.CliBoundArguments.skip` can also be
Expand Down Expand Up @@ -191,8 +202,8 @@ Also, it hints at the ``list`` keyword on the help page:
-h, --help Show the help
`~clize.parameters.one_of` is implemented in Clize as a wrapper around
`~parameters.mapped` which offers several more features. In this example we
will reimplement the features described above.
`~clize.parameters.mapped` which offers several more features. In this example
we will only reimplement the features described above.


.. _ex parameter converter:
Expand Down Expand Up @@ -229,8 +240,8 @@ Creating a parameter class for us to edit
Here we used `.parser.use_mixin` to implement the parameter annotation. It will
create a parameter instance that inherits from both ``OneOfParameter`` and the
appropriate class for the parameter being annotated:
`parser.PositionalParameter`, `parser.OptionParameter` or
`parser.ExtraPosArgsParameter`. This means our class will be able to override
`~.parser.PositionalParameter`, `~.parser.OptionParameter` or
`~.parser.ExtraPosArgsParameter`. This means our class will be able to override
some of those classes' methods.

For now, it works just like a regular parameter:
Expand All @@ -246,8 +257,8 @@ For now, it works just like a regular parameter:
Changing `~.ParameterWithValue.coerce_value` to validate the value
..................................................................

`parser.PositionalParameter`, `parser.OptionParameter` and
`parser.ExtraPosArgsParameter` all use `ParameterWithValue.coerce_value`. We
`~.parser.PositionalParameter`, `~.parser.OptionParameter` and
`~.parser.ExtraPosArgsParameter` all use `.ParameterWithValue.coerce_value`. We
override it to only accept the values we recorded:

.. code-block:: python
Expand Down Expand Up @@ -286,11 +297,11 @@ Displaying the list of choices
..............................

We can check if the passed value is ``list`` within ``coerce_value``. When that
is the case, we change `~parser.CliBoundArguments.func` and swallow the
following arguments. However, to ensure that the `~.parser.read_arguments`
method doesn't alter this, we need to skip its execution. In order to do this
we will raise an exception from ``coerce_value`` and catch it in
``read_arguments``:
is the case, we change `~.parser.CliBoundArguments.func` and swallow the
following arguments. However, to ensure that the
`~.parser.Parameter.read_argument` method doesn't alter this, we need to skip
its execution. In order to do this we will raise an exception from
``coerce_value`` and catch it in ``read_argument``:

.. code-block:: python
:emphasize-lines: 12, 21-26
Expand Down Expand Up @@ -327,12 +338,13 @@ we will raise an exception from ``coerce_value`` and catch it in
print(val)
On ``ba``, setting `~CliBoundArguments.func` overrides the function to be run
(normally the function passed to `run`). `~CliBoundArguments.args` and
(normally the function passed to `.run`). `~CliBoundArguments.args` and
`~CliBoundArguments.kwargs` are the positional and keyword argument that will
be passed to that function. Setting `sticky` to an `IgnoreAllArguments`
instance swallows all positional arguments instead of adding them to
`~CliBoundArguments.args`, and `posarg_only` makes keyword arguments be
processed as if they were positional arguments so they get ignored too.
be passed to that function. Setting `~.CliBoundArguments.sticky` to an
`IgnoreAllArguments` instance will swallow all positional arguments instead of
adding them to `~CliBoundArguments.args`, and `~CliBoundArguments.posarg_only`
makes keyword arguments be processed as if they were positional arguments so
they get ignored too.

.. code-block:: console
Expand All @@ -355,7 +367,7 @@ Clize uses `Parameter.show_help` to produce the text used to describe
parameters. It uses `Parameter.help_parens` to provide the content inside the
parenthesis after the parameter description.

.. code-block:: python
.. code-block:: pythonz
class OneOfParameter(parser.ParameterWithValue):
Expand Down

0 comments on commit c24476b

Please sign in to comment.