DPDK patches and discussions
 help / color / mirror / Atom feed
From: Nicholas Pratte <npratte@iol.unh.edu>
To: Luca Vizzarro <luca.vizzarro@arm.com>
Cc: dev@dpdk.org, "Juraj Linkeš" <juraj.linkes@pantheon.tech>,
	"Jeremy Spewock" <jspewock@iol.unh.edu>,
	"Paul Szczepanek" <paul.szczepanek@arm.com>
Subject: Re: [PATCH v2 1/8] dts: add params manipulation module
Date: Tue, 28 May 2024 11:40:50 -0400	[thread overview]
Message-ID: <CAKXZ7ehtmKPC7sW6N+Db2oA8THm+3kO_TwR+wp2bv97g6Y8FtQ@mail.gmail.com> (raw)
In-Reply-To: <20240509112057.1167947-2-luca.vizzarro@arm.com>

Tested-by: Nicholas Pratte <npratte@iol.unh.edu>
Reviewed-by: Nicholas Pratte <npratte@iol.unh.edu>

On Thu, May 9, 2024 at 7:21 AM Luca Vizzarro <luca.vizzarro@arm.com> wrote:
>
> This commit introduces a new "params" module, which adds a new way
> to manage command line parameters. The provided Params dataclass
> is able to read the fields of its child class and produce a string
> representation to supply to the command line. Any data structure
> that is intended to represent command line parameters can inherit it.
>
> The main purpose is to make it easier to represent data structures that
> map to parameters. Aiding quicker development, while minimising code
> bloat.
>
> Signed-off-by: Luca Vizzarro <luca.vizzarro@arm.com>
> Reviewed-by: Paul Szczepanek <paul.szczepanek@arm.com>
> ---
>  dts/framework/params/__init__.py | 274 +++++++++++++++++++++++++++++++
>  1 file changed, 274 insertions(+)
>  create mode 100644 dts/framework/params/__init__.py
>
> diff --git a/dts/framework/params/__init__.py b/dts/framework/params/__init__.py
> new file mode 100644
> index 0000000000..aa27e34357
> --- /dev/null
> +++ b/dts/framework/params/__init__.py
> @@ -0,0 +1,274 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2024 Arm Limited
> +
> +"""Parameter manipulation module.
> +
> +This module provides :class:`Params` which can be used to model any data structure
> +that is meant to represent any command parameters.
> +"""
> +
> +from dataclasses import dataclass, fields
> +from enum import Flag
> +from typing import Any, Callable, Iterable, Literal, Reversible, TypedDict, cast
> +
> +from typing_extensions import Self
> +
> +#: Type for a function taking one argument.
> +FnPtr = Callable[[Any], Any]
> +#: Type for a switch parameter.
> +Switch = Literal[True, None]
> +#: Type for a yes/no switch parameter.
> +YesNoSwitch = Literal[True, False, None]
> +
> +
> +def _reduce_functions(funcs: Reversible[FnPtr]) -> FnPtr:
> +    """Reduces an iterable of :attr:`FnPtr` from end to start to a composite function.
> +
> +    If the iterable is empty, the created function just returns its fed value back.
> +    """
> +
> +    def composite_function(value: Any):
> +        for fn in reversed(funcs):
> +            value = fn(value)
> +        return value
> +
> +    return composite_function
> +
> +
> +def convert_str(*funcs: FnPtr):
> +    """Decorator that makes the ``__str__`` method a composite function created from its arguments.
> +
> +    The :attr:`FnPtr`s fed to the decorator are executed from right to left
> +    in the arguments list order.
> +
> +    Example:
> +    .. code:: python
> +
> +        @convert_str(hex_from_flag_value)
> +        class BitMask(enum.Flag):
> +            A = auto()
> +            B = auto()
> +
> +    will allow ``BitMask`` to render as a hexadecimal value.
> +    """
> +
> +    def _class_decorator(original_class):
> +        original_class.__str__ = _reduce_functions(funcs)
> +        return original_class
> +
> +    return _class_decorator
> +
> +
> +def comma_separated(values: Iterable[Any]) -> str:
> +    """Converts an iterable in a comma-separated string."""
> +    return ",".join([str(value).strip() for value in values if value is not None])
> +
> +
> +def bracketed(value: str) -> str:
> +    """Adds round brackets to the input."""
> +    return f"({value})"
> +
> +
> +def str_from_flag_value(flag: Flag) -> str:
> +    """Returns the value from a :class:`enum.Flag` as a string."""
> +    return str(flag.value)
> +
> +
> +def hex_from_flag_value(flag: Flag) -> str:
> +    """Returns the value from a :class:`enum.Flag` converted to hexadecimal."""
> +    return hex(flag.value)
> +
> +
> +class ParamsModifier(TypedDict, total=False):
> +    """Params modifiers dict compatible with the :func:`dataclasses.field` metadata parameter."""
> +
> +    #:
> +    Params_value_only: bool
> +    #:
> +    Params_short: str
> +    #:
> +    Params_long: str
> +    #:
> +    Params_multiple: bool
> +    #:
> +    Params_convert_value: Reversible[FnPtr]
> +
> +
> +@dataclass
> +class Params:
> +    """Dataclass that renders its fields into command line arguments.
> +
> +    The parameter name is taken from the field name by default. The following:
> +
> +    .. code:: python
> +
> +        name: str | None = "value"
> +
> +    is rendered as ``--name=value``.
> +    Through :func:`dataclasses.field` the resulting parameter can be manipulated by applying
> +    this class' metadata modifier functions.
> +
> +    To use fields as switches, set the value to ``True`` to render them. If you
> +    use a yes/no switch you can also set ``False`` which would render a switch
> +    prefixed with ``--no-``. Examples:
> +
> +    .. code:: python
> +
> +        interactive: Switch = True  # renders --interactive
> +        numa: YesNoSwitch   = False # renders --no-numa
> +
> +    Setting ``None`` will prevent it from being rendered. The :attr:`~Switch` type alias is provided
> +    for regular switches, whereas :attr:`~YesNoSwitch` is offered for yes/no ones.
> +
> +    An instance of a dataclass inheriting ``Params`` can also be assigned to an attribute,
> +    this helps with grouping parameters together.
> +    The attribute holding the dataclass will be ignored and the latter will just be rendered as
> +    expected.
> +    """
> +
> +    _suffix = ""
> +    """Holder of the plain text value of Params when called directly. A suffix for child classes."""
> +
> +    """========= BEGIN FIELD METADATA MODIFIER FUNCTIONS ========"""
> +
> +    @staticmethod
> +    def value_only() -> ParamsModifier:
> +        """Injects the value of the attribute as-is without flag.
> +
> +        Metadata modifier for :func:`dataclasses.field`.
> +        """
> +        return ParamsModifier(Params_value_only=True)
> +
> +    @staticmethod
> +    def short(name: str) -> ParamsModifier:
> +        """Overrides any parameter name with the given short option.
> +
> +        Metadata modifier for :func:`dataclasses.field`.
> +
> +        Example:
> +        .. code:: python
> +
> +            logical_cores: str | None = field(default="1-4", metadata=Params.short("l"))
> +
> +        will render as ``-l=1-4`` instead of ``--logical-cores=1-4``.
> +        """
> +        return ParamsModifier(Params_short=name)
> +
> +    @staticmethod
> +    def long(name: str) -> ParamsModifier:
> +        """Overrides the inferred parameter name to the specified one.
> +
> +        Metadata modifier for :func:`dataclasses.field`.
> +
> +        Example:
> +        .. code:: python
> +
> +            x_name: str | None = field(default="y", metadata=Params.long("x"))
> +
> +        will render as ``--x=y``, but the field is accessed and modified through ``x_name``.
> +        """
> +        return ParamsModifier(Params_long=name)
> +
> +    @staticmethod
> +    def multiple() -> ParamsModifier:
> +        """Specifies that this parameter is set multiple times. Must be a list.
> +
> +        Metadata modifier for :func:`dataclasses.field`.
> +
> +        Example:
> +        .. code:: python
> +
> +            ports: list[int] | None = field(
> +                default_factory=lambda: [0, 1, 2],
> +                metadata=Params.multiple() | Params.long("port")
> +            )
> +
> +        will render as ``--port=0 --port=1 --port=2``. Note that modifiers can be chained like
> +        in this example.
> +        """
> +        return ParamsModifier(Params_multiple=True)
> +
> +    @classmethod
> +    def convert_value(cls, *funcs: FnPtr) -> ParamsModifier:
> +        """Takes in a variable number of functions to convert the value text representation.
> +
> +        Metadata modifier for :func:`dataclasses.field`.
> +
> +        The ``metadata`` keyword argument can be used to chain metadata modifiers together.
> +
> +        Functions can be chained together, executed from right to left in the arguments list order.
> +
> +        Example:
> +        .. code:: python
> +
> +            hex_bitmask: int | None = field(
> +                default=0b1101,
> +                metadata=Params.convert_value(hex) | Params.long("mask")
> +            )
> +
> +        will render as ``--mask=0xd``.
> +        """
> +        return ParamsModifier(Params_convert_value=funcs)
> +
> +    """========= END FIELD METADATA MODIFIER FUNCTIONS ========"""
> +
> +    def append_str(self, text: str) -> None:
> +        """Appends a string at the end of the string representation."""
> +        self._suffix += text
> +
> +    def __iadd__(self, text: str) -> Self:
> +        """Appends a string at the end of the string representation."""
> +        self.append_str(text)
> +        return self
> +
> +    @classmethod
> +    def from_str(cls, text: str) -> Self:
> +        """Creates a plain Params object from a string."""
> +        obj = cls()
> +        obj.append_str(text)
> +        return obj
> +
> +    @staticmethod
> +    def _make_switch(
> +        name: str, is_short: bool = False, is_no: bool = False, value: str | None = None
> +    ) -> str:
> +        prefix = f"{'-' if is_short else '--'}{'no-' if is_no else ''}"
> +        name = name.replace("_", "-")
> +        value = f"{' ' if is_short else '='}{value}" if value else ""
> +        return f"{prefix}{name}{value}"
> +
> +    def __str__(self) -> str:
> +        """Returns a string of command-line-ready arguments from the class fields."""
> +        arguments: list[str] = []
> +
> +        for field in fields(self):
> +            value = getattr(self, field.name)
> +            modifiers = cast(ParamsModifier, field.metadata)
> +
> +            if value is None:
> +                continue
> +
> +            value_only = modifiers.get("Params_value_only", False)
> +            if isinstance(value, Params) or value_only:
> +                arguments.append(str(value))
> +                continue
> +
> +            # take the short modifier, or the long modifier, or infer from field name
> +            switch_name = modifiers.get("Params_short", modifiers.get("Params_long", field.name))
> +            is_short = "Params_short" in modifiers
> +
> +            if isinstance(value, bool):
> +                arguments.append(self._make_switch(switch_name, is_short, is_no=(not value)))
> +                continue
> +
> +            convert = _reduce_functions(modifiers.get("Params_convert_value", []))
> +            multiple = modifiers.get("Params_multiple", False)
> +
> +            values = value if multiple else [value]
> +            for value in values:
> +                arguments.append(self._make_switch(switch_name, is_short, value=convert(value)))
> +
> +        if self._suffix:
> +            arguments.append(self._suffix)
> +
> +        return " ".join(arguments)
> --
> 2.34.1
>

  reply	other threads:[~2024-05-28 15:41 UTC|newest]

Thread overview: 159+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-03-26 19:04 [PATCH 0/6] dts: add testpmd params and statefulness Luca Vizzarro
2024-03-26 19:04 ` [PATCH 1/6] dts: add parameters data structure Luca Vizzarro
2024-03-28 16:48   ` Jeremy Spewock
2024-04-09 15:52     ` Luca Vizzarro
2024-04-09 12:10   ` Juraj Linkeš
2024-04-09 16:28     ` Luca Vizzarro
2024-04-10  9:15       ` Juraj Linkeš
2024-04-10  9:51         ` Luca Vizzarro
2024-04-10 10:04           ` Juraj Linkeš
2024-03-26 19:04 ` [PATCH 2/6] dts: use Params for interactive shells Luca Vizzarro
2024-03-28 16:48   ` Jeremy Spewock
2024-04-09 14:56     ` Juraj Linkeš
2024-04-10  9:34       ` Luca Vizzarro
2024-04-10  9:58         ` Juraj Linkeš
2024-05-28 15:43   ` Nicholas Pratte
2024-03-26 19:04 ` [PATCH 3/6] dts: add testpmd shell params Luca Vizzarro
2024-03-28 16:48   ` Jeremy Spewock
2024-04-09 16:37   ` Juraj Linkeš
2024-04-10 10:49     ` Luca Vizzarro
2024-04-10 13:17       ` Juraj Linkeš
2024-03-26 19:04 ` [PATCH 4/6] dts: use testpmd params for scatter test suite Luca Vizzarro
2024-04-09 19:12   ` Juraj Linkeš
2024-04-10 10:53     ` Luca Vizzarro
2024-04-10 13:18       ` Juraj Linkeš
2024-04-26 18:06         ` Jeremy Spewock
2024-04-29  7:45           ` Juraj Linkeš
2024-03-26 19:04 ` [PATCH 5/6] dts: add statefulness to InteractiveShell Luca Vizzarro
2024-03-28 16:48   ` Jeremy Spewock
2024-04-10  6:53     ` Juraj Linkeš
2024-04-10 11:27       ` Luca Vizzarro
2024-04-10 13:35         ` Juraj Linkeš
2024-04-10 14:07           ` Luca Vizzarro
2024-04-12 12:33             ` Juraj Linkeš
2024-04-29 14:48           ` Jeremy Spewock
2024-03-26 19:04 ` [PATCH 6/6] dts: add statefulness to TestPmdShell Luca Vizzarro
2024-03-28 16:48   ` Jeremy Spewock
2024-04-10  7:41     ` Juraj Linkeš
2024-04-10 11:35       ` Luca Vizzarro
2024-04-11 10:30         ` Juraj Linkeš
2024-04-11 11:47           ` Luca Vizzarro
2024-04-11 12:13             ` Juraj Linkeš
2024-04-11 13:59               ` Luca Vizzarro
2024-04-26 18:06               ` Jeremy Spewock
2024-04-29 12:06                 ` Juraj Linkeš
2024-04-10  7:50   ` Juraj Linkeš
2024-04-10 11:37     ` Luca Vizzarro
2024-05-09 11:20 ` [PATCH v2 0/8] dts: add testpmd params Luca Vizzarro
2024-05-09 11:20   ` [PATCH v2 1/8] dts: add params manipulation module Luca Vizzarro
2024-05-28 15:40     ` Nicholas Pratte [this message]
2024-05-28 21:08     ` Jeremy Spewock
2024-06-06  9:19     ` Juraj Linkeš
2024-06-17 11:44       ` Luca Vizzarro
2024-06-18  8:55         ` Juraj Linkeš
2024-05-09 11:20   ` [PATCH v2 2/8] dts: use Params for interactive shells Luca Vizzarro
2024-05-28 17:43     ` Nicholas Pratte
2024-05-28 21:04     ` Jeremy Spewock
2024-06-06 13:14     ` Juraj Linkeš
2024-05-09 11:20   ` [PATCH v2 3/8] dts: refactor EalParams Luca Vizzarro
2024-05-28 15:44     ` Nicholas Pratte
2024-05-28 21:05     ` Jeremy Spewock
2024-06-06 13:17     ` Juraj Linkeš
2024-05-09 11:20   ` [PATCH v2 4/8] dts: remove module-wide imports Luca Vizzarro
2024-05-28 15:45     ` Nicholas Pratte
2024-05-28 21:08     ` Jeremy Spewock
2024-06-06 13:21     ` Juraj Linkeš
2024-05-09 11:20   ` [PATCH v2 5/8] dts: add testpmd shell params Luca Vizzarro
2024-05-28 15:53     ` Nicholas Pratte
2024-05-28 21:05     ` Jeremy Spewock
2024-05-29 15:59       ` Luca Vizzarro
2024-05-29 17:11         ` Jeremy Spewock
2024-05-09 11:20   ` [PATCH v2 6/8] dts: use testpmd params for scatter test suite Luca Vizzarro
2024-05-28 15:49     ` Nicholas Pratte
2024-05-28 21:06       ` Jeremy Spewock
2024-05-09 11:20   ` [PATCH v2 7/8] dts: rework interactive shells Luca Vizzarro
2024-05-28 15:50     ` Nicholas Pratte
2024-05-28 21:07     ` Jeremy Spewock
2024-05-29 15:57       ` Luca Vizzarro
2024-05-09 11:20   ` [PATCH v2 8/8] dts: use Unpack for type checking and hinting Luca Vizzarro
2024-05-28 15:50     ` Nicholas Pratte
2024-05-28 21:08     ` Jeremy Spewock
2024-05-22 15:59   ` [PATCH v2 0/8] dts: add testpmd params Nicholas Pratte
2024-05-30 15:24 ` [PATCH v3 " Luca Vizzarro
2024-05-30 15:24   ` [PATCH v3 1/8] dts: add params manipulation module Luca Vizzarro
2024-05-30 20:12     ` Jeremy Spewock
2024-05-31 15:19     ` Nicholas Pratte
2024-05-30 15:24   ` [PATCH v3 2/8] dts: use Params for interactive shells Luca Vizzarro
2024-05-30 20:12     ` Jeremy Spewock
2024-05-31 15:20     ` Nicholas Pratte
2024-05-30 15:25   ` [PATCH v3 3/8] dts: refactor EalParams Luca Vizzarro
2024-05-30 20:12     ` Jeremy Spewock
2024-05-31 15:21     ` Nicholas Pratte
2024-05-30 15:25   ` [PATCH v3 4/8] dts: remove module-wide imports Luca Vizzarro
2024-05-30 20:12     ` Jeremy Spewock
2024-05-31 15:21     ` Nicholas Pratte
2024-05-30 15:25   ` [PATCH v3 5/8] dts: add testpmd shell params Luca Vizzarro
2024-05-30 20:12     ` Jeremy Spewock
2024-05-31 15:20     ` Nicholas Pratte
2024-06-06 14:37     ` Juraj Linkeš
2024-05-30 15:25   ` [PATCH v3 6/8] dts: use testpmd params for scatter test suite Luca Vizzarro
2024-05-30 20:13     ` Jeremy Spewock
2024-05-31 15:22     ` Nicholas Pratte
2024-06-06 14:38     ` Juraj Linkeš
2024-05-30 15:25   ` [PATCH v3 7/8] dts: rework interactive shells Luca Vizzarro
2024-05-30 20:13     ` Jeremy Spewock
2024-05-31 15:22     ` Nicholas Pratte
2024-06-06 18:03     ` Juraj Linkeš
2024-06-17 12:13       ` Luca Vizzarro
2024-06-18  9:18         ` Juraj Linkeš
2024-05-30 15:25   ` [PATCH v3 8/8] dts: use Unpack for type checking and hinting Luca Vizzarro
2024-05-30 20:13     ` Jeremy Spewock
2024-05-31 15:21     ` Nicholas Pratte
2024-06-06 18:05     ` Juraj Linkeš
2024-06-17 14:42 ` [PATCH v4 0/8] dts: add testpmd params and statefulness Luca Vizzarro
2024-06-17 14:42   ` [PATCH v4 1/8] dts: add params manipulation module Luca Vizzarro
2024-06-17 14:42   ` [PATCH v4 2/8] dts: use Params for interactive shells Luca Vizzarro
2024-06-17 14:42   ` [PATCH v4 3/8] dts: refactor EalParams Luca Vizzarro
2024-06-17 14:42   ` [PATCH v4 4/8] dts: remove module-wide imports Luca Vizzarro
2024-06-17 14:42   ` [PATCH v4 5/8] dts: add testpmd shell params Luca Vizzarro
2024-06-17 14:42   ` [PATCH v4 6/8] dts: use testpmd params for scatter test suite Luca Vizzarro
2024-06-17 14:42   ` [PATCH v4 7/8] dts: rework interactive shells Luca Vizzarro
2024-06-17 14:42   ` [PATCH v4 8/8] dts: use Unpack for type checking and hinting Luca Vizzarro
2024-06-17 14:54 ` [PATCH v5 0/8] dts: add testpmd params Luca Vizzarro
2024-06-17 14:54   ` [PATCH v5 1/8] dts: add params manipulation module Luca Vizzarro
2024-06-17 15:22     ` Nicholas Pratte
2024-06-17 14:54   ` [PATCH v5 2/8] dts: use Params for interactive shells Luca Vizzarro
2024-06-17 15:23     ` Nicholas Pratte
2024-06-17 14:54   ` [PATCH v5 3/8] dts: refactor EalParams Luca Vizzarro
2024-06-17 15:23     ` Nicholas Pratte
2024-06-17 14:54   ` [PATCH v5 4/8] dts: remove module-wide imports Luca Vizzarro
2024-06-17 15:23     ` Nicholas Pratte
2024-06-17 14:54   ` [PATCH v5 5/8] dts: add testpmd shell params Luca Vizzarro
2024-06-17 15:24     ` Nicholas Pratte
2024-06-17 14:54   ` [PATCH v5 6/8] dts: use testpmd params for scatter test suite Luca Vizzarro
2024-06-17 15:24     ` Nicholas Pratte
2024-06-17 14:54   ` [PATCH v5 7/8] dts: rework interactive shells Luca Vizzarro
2024-06-17 15:25     ` Nicholas Pratte
2024-06-17 14:54   ` [PATCH v5 8/8] dts: use Unpack for type checking and hinting Luca Vizzarro
2024-06-17 15:25     ` Nicholas Pratte
2024-06-19 10:23 ` [PATCH v6 0/8] dts: add testpmd params Luca Vizzarro
2024-06-19 10:23   ` [PATCH v6 1/8] dts: add params manipulation module Luca Vizzarro
2024-06-19 12:45     ` Juraj Linkeš
2024-06-19 10:23   ` [PATCH v6 2/8] dts: use Params for interactive shells Luca Vizzarro
2024-06-19 10:23   ` [PATCH v6 3/8] dts: refactor EalParams Luca Vizzarro
2024-06-19 10:23   ` [PATCH v6 4/8] dts: remove module-wide imports Luca Vizzarro
2024-06-19 10:23   ` [PATCH v6 5/8] dts: add testpmd shell params Luca Vizzarro
2024-06-19 10:23   ` [PATCH v6 6/8] dts: use testpmd params for scatter test suite Luca Vizzarro
2024-06-19 10:23   ` [PATCH v6 7/8] dts: rework interactive shells Luca Vizzarro
2024-06-19 12:49     ` Juraj Linkeš
2024-06-19 10:23   ` [PATCH v6 8/8] dts: use Unpack for type checking and hinting Luca Vizzarro
2024-06-19 14:02 ` [PATCH v7 0/8] dts: add testpmd params Luca Vizzarro
2024-06-19 14:02   ` [PATCH v7 1/8] dts: add params manipulation module Luca Vizzarro
2024-06-19 14:02   ` [PATCH v7 2/8] dts: use Params for interactive shells Luca Vizzarro
2024-06-19 14:03   ` [PATCH v7 3/8] dts: refactor EalParams Luca Vizzarro
2024-06-19 14:03   ` [PATCH v7 4/8] dts: remove module-wide imports Luca Vizzarro
2024-06-19 14:03   ` [PATCH v7 5/8] dts: add testpmd shell params Luca Vizzarro
2024-06-19 14:03   ` [PATCH v7 6/8] dts: use testpmd params for scatter test suite Luca Vizzarro
2024-06-19 14:03   ` [PATCH v7 7/8] dts: rework interactive shells Luca Vizzarro
2024-06-19 14:03   ` [PATCH v7 8/8] dts: use Unpack for type checking and hinting Luca Vizzarro
2024-06-20  3:36   ` [PATCH v7 0/8] dts: add testpmd params Thomas Monjalon

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAKXZ7ehtmKPC7sW6N+Db2oA8THm+3kO_TwR+wp2bv97g6Y8FtQ@mail.gmail.com \
    --to=npratte@iol.unh.edu \
    --cc=dev@dpdk.org \
    --cc=jspewock@iol.unh.edu \
    --cc=juraj.linkes@pantheon.tech \
    --cc=luca.vizzarro@arm.com \
    --cc=paul.szczepanek@arm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).