Source code for semantic_release.commit_parser.scipy

"""
Parses commit messages using `scipy tags <scipy-style>`_ of the form::

    <tag>(<scope>): <subject>

    <body>


The elements <tag>, <scope> and <body> are optional. If no tag is present, the
commit will be added to the changelog section "None" and no version increment
will be performed.

While <scope> is supported here it isn't actually part of the scipy style.
If it is missing, parentheses around it are too. The commit should then be
of the form::

    <tag>: <subject>

    <body>

To communicate a breaking change add "BREAKING CHANGE" into the body at the
beginning of a paragraph. Fill this paragraph with information how to migrate
from the broken behavior to the new behavior. It will be added to the
"Breaking" section of the changelog.

Supported Tags::

    (
        API,
        DEP,
        ENH,
        REV,
        BUG,
        MAINT,
        BENCH,
        BLD,
    )
    DEV, DOC, STY, TST, REL, FEAT, TEST

Supported Changelog Sections::

    breaking, feature, fix, Other, None

.. _`scipy-style`: https://docs.scipy.org/doc/scipy/reference/dev/contributor/development_workflow.html#writing-the-commit-message
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Tuple

from pydantic.dataclasses import dataclass

from semantic_release.commit_parser.angular import (
    AngularCommitParser,
    AngularParserOptions,
)
from semantic_release.commit_parser.token import (
    ParsedMessageResult,
    ParseError,
)
from semantic_release.enums import LevelBump

if TYPE_CHECKING:
    from git.objects.commit import Commit

logger = logging.getLogger(__name__)


def _logged_parse_error(commit: Commit, error: str) -> ParseError:
    logger.debug(error)
    return ParseError(commit, error=error)


tag_to_section = {
    "API": "breaking",
    "BENCH": "None",
    "BLD": "fix",
    "BUG": "fix",
    "DEP": "breaking",
    "DEV": "None",
    "DOC": "documentation",
    "ENH": "feature",
    "MAINT": "fix",
    "REV": "Other",
    "STY": "None",
    "TST": "None",
    "REL": "None",
    # strictly speaking not part of the standard
    "FEAT": "feature",
    "TEST": "None",
}


[docs] @dataclass class ScipyParserOptions(AngularParserOptions): """ Options dataclass for ScipyCommitParser Scipy-style commit messages follow the same format as Angular-style commit just with different tag names. """ major_tags: Tuple[str, ...] = ("API",) minor_tags: Tuple[str, ...] = ("DEP", "DEV", "ENH", "REV", "FEAT") patch_tags: Tuple[str, ...] = ("BLD", "BUG", "MAINT") allowed_tags: Tuple[str, ...] = ( *major_tags, *minor_tags, *patch_tags, "BENCH", "DOC", "STY", "TST", "REL", "TEST", ) # TODO: breaking v10, make consistent with AngularParserOptions default_level_bump: LevelBump = LevelBump.NO_RELEASE def __post_init__(self) -> None: # TODO: breaking v10, remove as the name is now consistent self.default_bump_level = self.default_level_bump super().__post_init__() for tag in self.major_tags: self.tag_to_level[tag] = LevelBump.MAJOR
[docs] class ScipyCommitParser(AngularCommitParser): """Parser for scipy-style commit messages""" # TODO: Deprecate in lieu of get_default_options() parser_options = ScipyParserOptions def __init__(self, options: ScipyParserOptions | None = None) -> None: super().__init__(options)
[docs] @staticmethod def get_default_options() -> ScipyParserOptions: return ScipyParserOptions()
[docs] def parse_message(self, message: str) -> ParsedMessageResult | None: return ( None if not (pmsg_result := super().parse_message(message)) else ParsedMessageResult( **{ **pmsg_result._asdict(), "category": tag_to_section.get(pmsg_result.type, "None"), } ) )