all2md.transforms.metadata

Metadata classes for AST transforms.

This module defines metadata structures for transform registration and discovery. Transform metadata enables CLI argument generation, validation, and plugin discovery through entry points.

Examples

Define a transform with metadata:

>>> from all2md.transforms import TransformMetadata, ParameterSpec
>>> from all2md.ast.transforms import NodeTransformer
>>>
>>> class MyTransform(NodeTransformer):
...     def __init__(self, threshold: int = 10):
...         self.threshold = threshold
...
>>> METADATA = TransformMetadata(
...     name="my-transform",
...     description="Example transform",
...     transformer_class=MyTransform,
...     parameters={
...         'threshold': ParameterSpec(
...             type=int,
...             default=10,
...             help="Threshold value"
...         )
...     }
... )
class all2md.transforms.metadata.ParameterSpec

Bases: object

Specification for a transform parameter.

This class describes a single parameter accepted by a transform, including type information, default values, and metadata for CLI generation.

Parameters:
  • type (type) – Python type of the parameter (e.g., int, str, bool)

  • default (Any, optional) – Default value if parameter is not provided

  • help (str, optional) – Help text describing the parameter (used in CLI –help)

  • cli_flag (str, optional) – Custom CLI flag name (e.g., ‘–my-param’). If None, auto-generated from parameter name

  • required (bool, default = False) – Whether this parameter is required

  • choices (list, optional) – List of valid choices for this parameter

  • validator (callable, optional) – Custom validation function: takes value, returns bool or raises ValueError

  • element_type (type, optional) – For list parameters, the expected type of list elements (e.g., str, int)

  • expose (bool, optional) – Whether to expose this parameter on the CLI when no explicit cli_flag is provided. None defers to global defaults (currently False).

Examples

Simple parameter:
>>> param = ParameterSpec(type=int, default=10, help="Threshold value")
Parameter with choices:
>>> param = ParameterSpec(
...     type=str,
...     default="auto",
...     choices=["auto", "manual", "disabled"],
...     help="Processing mode"
... )
Required parameter with validation:
>>> def validate_positive(value):
...     if value <= 0:
...         raise ValueError("Must be positive")
...     return True
>>> param = ParameterSpec(
...     type=int,
...     required=True,
...     validator=validate_positive,
...     help="Positive integer"
... )
List parameter with element type validation:
>>> param = ParameterSpec(
...     type=list,
...     element_type=str,
...     default=["image", "table"],
...     help="Node types to remove"
... )
type: Type
default: Any = None
help: str = ''
cli_flag: str | None = None
required: bool = False
choices: list[Any] | None = None
validator: Callable[[Any], bool] | None = None
element_type: Type | None = None
expose: bool | None = None
DEFAULT_EXPOSE: ClassVar[bool] = False
validate(value: Any) bool

Validate a parameter value.

Parameters:

value (Any) – Value to validate. For list types, tuples are accepted and coerced to lists automatically.

Returns:

True if valid

Return type:

bool

Raises:

ValueError – If value is invalid

Notes

When validating list parameters, this method accepts both list and tuple types. Tuples are automatically coerced to lists to accommodate CLI parsers that often yield tuples. The coercion is transparent to the caller.

get_cli_flag(param_name: str) str

Get CLI flag name for this parameter.

Parameters:

param_name (str) – Parameter name from the transform

Returns:

CLI flag (e.g., ‘–threshold’)

Return type:

str

should_expose(default: bool | None = None) bool

Determine whether this parameter should surface in the CLI.

get_dest_name(param_name: str, transform_name: str) str

Get argparse dest name for this parameter.

This provides a consistent naming convention for transform parameters in the argparse namespace, avoiding conflicts between transforms.

Parameters:
  • param_name (str) – Parameter name from the transform

  • transform_name (str) – Name of the transform

Returns:

Destination name for argparse (e.g., ‘heading_offset_transform_offset’)

Return type:

str

Notes

The dest name is constructed to avoid collisions: - Format: f’{transform_name}_{param_name}’ - Hyphens converted to underscores for valid Python identifiers - Example: ‘heading-offset’ transform, ‘offset’ param -> ‘heading_offset_offset’

get_argparse_kwargs(param_name: str, transform_name: str) dict

Generate argparse kwargs for this parameter.

This centralizes the logic for converting ParameterSpec to argparse add_argument() kwargs, ensuring consistency between CLI argument definition and parameter extraction.

Parameters:
  • param_name (str) – Parameter name from the transform

  • transform_name (str) – Name of the transform (for help text)

Returns:

Keyword arguments for argparse.ArgumentParser.add_argument()

Return type:

dict

Notes

The returned dict includes: - ‘action’: Tracking action class (TrackingStoreAction, etc.) - ‘type’: Python type for conversion (if applicable) - ‘default’: Default value (if applicable) - ‘help’: Help text - ‘choices’: Valid choices (if specified) - ‘nargs’: Argument count (for list types) - ‘dest’: Destination name in namespace

Examples

>>> param = ParameterSpec(type=int, default=10, help="Threshold")
>>> kwargs = param.get_argparse_kwargs('threshold', 'my-transform')
>>> # Returns: {'action': TrackingStoreAction, 'type': int,
>>> #           'default': 10, 'help': 'Threshold', 'dest': 'my_transform_threshold'}
extract_value(namespace: Any, dest: str) tuple[Any, bool]

Extract parameter value from parsed argparse namespace.

This handles extracting the value and determining if it was explicitly provided by the user (vs. being a default value).

Parameters:
  • namespace (argparse.Namespace) – Parsed command line arguments

  • dest (str) – Destination name in the namespace (from get_dest_name())

Returns:

Tuple of (value, was_provided) where: - value: The parameter value (or None if not provided) - was_provided: True if user explicitly provided this value

Return type:

tuple[Any, bool]

Notes

This method checks the _provided_args set in the namespace to determine if a value was explicitly provided by the user. Only explicitly provided values should be passed to transform constructors.

Examples

>>> namespace = argparse.Namespace(
...     my_transform_threshold=20,
...     _provided_args={'my_transform_threshold'}
... )
>>> param = ParameterSpec(type=int, default=10)
>>> value, provided = param.extract_value(namespace, 'my_transform_threshold')
>>> # Returns: (20, True)
__init__(type: Type, default: Any = None, help: str = '', cli_flag: str | None = None, required: bool = False, choices: list[Any] | None = None, validator: Callable[[Any], bool] | None = None, element_type: Type | None = None, expose: bool | None = None) None
class all2md.transforms.metadata.TransformMetadata

Bases: object

Metadata for a transform.

This class describes a transform for registration, discovery, and CLI integration. It follows the same pattern as ConverterMetadata for consistency.

Parameters:
  • name (str) – Unique identifier for the transform (e.g., “remove-images”)

  • description (str) – Human-readable description of what the transform does

  • transformer_class (type[NodeTransformer]) – The transform class (must inherit from NodeTransformer)

  • parameters (dict[str, ParameterSpec], default = empty dict) – Parameters accepted by the transform constructor

  • priority (int, default = 100) – Execution priority (lower runs first). Used for dependency ordering

  • dependencies (list[str], default = empty list) – Names of transforms that must run before this one

  • version (str, default = "1.0.0") – Transform version (semantic versioning)

  • author (str, optional) – Transform author or maintainer

  • tags (list[str], default = empty list) – Tags for categorization (e.g., [“images”, “cleanup”])

Examples

Basic transform metadata:
>>> metadata = TransformMetadata(
...     name="remove-images",
...     description="Remove all image nodes from the AST",
...     transformer_class=RemoveImagesTransform
... )
Transform with parameters:
>>> metadata = TransformMetadata(
...     name="heading-offset",
...     description="Shift heading levels by an offset",
...     transformer_class=HeadingOffsetTransform,
...     parameters={
...         'offset': ParameterSpec(
...             type=int,
...             default=1,
...             help="Number of levels to shift (positive or negative)"
...         )
...     }
... )
Transform with dependencies:
>>> metadata = TransformMetadata(
...     name="sanitize-links",
...     description="Sanitize and validate all links",
...     transformer_class=SanitizeLinksTransform,
...     dependencies=["extract-metadata"],
...     priority=200
... )
name: str
description: str
transformer_class: Type[NodeTransformer]
parameters: dict[str, ParameterSpec]
priority: int = 100
dependencies: list[str]
version: str = '1.0.0'
author: str | None = None
tags: list[str]
create_instance(strict: bool = False, **kwargs: Any) NodeTransformer

Create an instance of the transform with given parameters.

Parameters:
  • strict (bool, default = False) – If True, log warnings for unknown parameters to aid debugging

  • **kwargs – Parameters to pass to the transform constructor

Returns:

Transform instance

Return type:

NodeTransformer

Raises:

ValueError – If required parameters are missing or validation fails

Examples

>>> metadata = TransformMetadata(
...     name="test",
...     description="Test transform",
...     transformer_class=MyTransform,
...     parameters={'threshold': ParameterSpec(type=int, default=10)}
... )
>>> instance = metadata.create_instance(threshold=20)
get_parameter_names() list[str]

Get list of parameter names.

Returns:

Parameter names

Return type:

list[str]

has_parameter(name: str) bool

Check if transform has a parameter.

Parameters:

name (str) – Parameter name

Returns:

True if parameter exists

Return type:

bool

__init__(name: str, description: str, transformer_class: ~typing.Type[~all2md.ast.transforms.NodeTransformer], parameters: dict[str, ~all2md.transforms.metadata.ParameterSpec] = <factory>, priority: int = 100, dependencies: list[str] = <factory>, version: str = '1.0.0', author: str | None = None, tags: list[str] = <factory>) None