Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(settings): Add support to configure fragment types via tables #370

Merged
merged 1 commit into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,21 @@ If ``title_format`` is unspecified or an empty string, the default format will b
If set to ``false``, no title will be created.
This can be useful if the specified template creates the title itself.

Furthermore, you can add your own fragment types using:
Furthermore, you can customize each of your own fragment types using:

.. code-block:: toml

[tool.towncrier]
[[tool.towncrier.type]]
directory = "deprecation"
name = "Deprecations"
showcontent = true
# To add custom fragment types, with default setting, just add an empty section.
[tool.towncrier.feat]
[tool.towncrier.fix]
adiroiban marked this conversation as resolved.
Show resolved Hide resolved

# Custom fragment types can have custom attributes
# that are used when rendering the result based on the template.
[tool.towncrier.chore]
name = "Other Tasks"
showcontent = false
adiroiban marked this conversation as resolved.
Show resolved Hide resolved



Automatic pull request checks
Expand Down
56 changes: 51 additions & 5 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,61 @@ Top level keys
- ``issue_format`` -- A format string for rendering the issue/ticket number in newsfiles. ``#{issue}`` by default.
- ``underlines`` -- The characters used for underlining headers. ``["=", "-", "~"]`` by default.


Custom fragment types
---------------------
``Towncrier`` allows defining custom fragment types. Custom fragment types
will be used instead ``towncrier`` default ones, they are not combined.

Users can configure each of their own custom fragment types by adding tables to
the pyproject.toml named ``[tool.towncrier.type.<a custom fragment type>]``.

These tables may include the following optional keys:

* ``name``: The description of the fragment type, as it must be included
in the news file. If omitted, it defaults to its fragment type,
but capitalized.
* ``showcontent``: Whether if the fragment contents should be included in the
news file. If omitted, it defaults to ``true``



For example, if you want your custom fragment types to be
``["feat", "fix", "chore",]`` and you want all
of them to use the default configuration except ``"chore"`` you can do it as
follows:


.. code-block:: toml

Custom fragment types can be including in the
pyproject.toml.
Each custom type (``[[tool.towncrier.type]]``) has the following keys:

[tool.towncrier]


[tool.towncrier.fragment.feat]
[tool.towncrier.fragment.fix]

[tool.towncrier.fragment.chore]
name = "Other Tasks"
showcontent = false

DEPRECATED: Defining custom fragment types with an array of toml tables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Users can create their own custom fragment types by adding an array of
tables to the pyproject.toml named ``[[tool.towncrier.type]]``.

If still using this way to configure custom fragment types,
please notice that ``fragment_types`` must be empty or not provided.

Each custom type (``[[tool.towncrier.type]]``) has the following
mandatory keys:
* ``directory``: The type of the fragment.
* ``name``: The description of the fragment type, as it must be included
in the news file.
* ``showcontent``: Whether if the fragment contents should be included in the
news file.



For example:

.. code-block:: toml
Expand All @@ -47,4 +88,9 @@ For example:
[[tool.towncrier.type]]
directory = "deprecation"
name = "Deprecations"
showcontent = true
showcontent = true

[[tool.towncrier.type]]
directory = "chore"
name = "Other Tasks"
showcontent = false
14 changes: 14 additions & 0 deletions src/towncrier/_settings/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Subpackage to handle settings parsing."""

from towncrier._settings import load

load_config = load.load_config
ConfigError = load.ConfigError
load_config_from_options = load.load_config_from_options


__all__ = [
"load_config",
"ConfigError",
"load_config_from_options",
]
133 changes: 133 additions & 0 deletions src/towncrier/_settings/fragment_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import abc
import collections as clt


class BaseFragmentTypesLoader:
"""Base class to load fragment types."""

__metaclass__ = abc.ABCMeta

def __init__(self, config):
"""Initialize."""
self.config = config

@classmethod
def factory(cls, config):
fragment_types_class = DefaultFragmentTypesLoader
fragment_types = config.get("fragment", {})
types_config = config.get("type", {})
if fragment_types:
fragment_types_class = TableFragmentTypesLoader
elif types_config:
fragment_types_class = ArrayFragmentTypesLoader

new = fragment_types_class(config)
return new

@abc.abstractmethod
def load(self,):
"""Load fragment types."""


class DefaultFragmentTypesLoader(BaseFragmentTypesLoader):
"""Default towncrier's fragment types."""

_default_types = clt.OrderedDict(
[
(u"feature", {"name": u"Features", "showcontent": True}),
(u"bugfix", {"name": u"Bugfixes", "showcontent": True}),
(u"doc", {"name": u"Improved Documentation", "showcontent": True}),
(u"removal", {"name": u"Deprecations and Removals", "showcontent": True}),
(u"misc", {"name": u"Misc", "showcontent": False}),
]
)

def load(self,):
"""Load default types."""
return self._default_types


class ArrayFragmentTypesLoader(BaseFragmentTypesLoader):
"""Load fragment types from an toml array of tables.

This loader get the custom fragment types defined through a
toml array of tables, that ``toml`` parses as an array
of mappings.

For example::

[tool.towncrier]
[[tool.towncrier.type]]
directory = "deprecation"
name = "Deprecations"
showcontent = true

"""

def load(self,):
"""Load types from toml array of mappings."""

types = clt.OrderedDict()
types_config = self.config["type"]
for type_config in types_config:
directory = type_config["directory"]
fragment_type_name = type_config["name"]
is_content_required = type_config["showcontent"]
types[directory] = {
"name": fragment_type_name,
"showcontent": is_content_required,
}
return types


class TableFragmentTypesLoader(BaseFragmentTypesLoader):
"""Load fragment types from toml tables.

This loader get the custom fragment types defined through a
toml tables, that ``toml`` parses as an nested mapping.

This loader allows omitting ``name`` and
```showcontent`` fields.
``name`` by default is the capitalized
fragment type.
``showcontent`` is true by default.

For example::

[tool.towncrier]
[tool.towncrier.fragment.chore]
name = "Chores"
showcontent = False

[tool.towncrier.fragment.deprecations]
# name will be "Deprecations"
# The content will be shown.

"""
def __init__(self, config):
"""Initialize."""
self.config = config
self.fragment_options = config.get("fragment", {})

def load(self,):
"""Load types from nested mapping."""
fragment_types = self.fragment_options.keys()
fragment_types = sorted(fragment_types)
custom_types_sequence = [
(fragment_type, self._load_options(fragment_type))
for fragment_type in fragment_types
]
types = clt.OrderedDict(custom_types_sequence)
return types

def _load_options(self, fragment_type):
"""Load fragment options."""
capitalized_fragment_type = fragment_type.capitalize()
options = self.fragment_options.get(fragment_type, {})
fragment_description = options.get("name", capitalized_fragment_type)
show_content = options.get("showcontent", True)
clean_fragment_options = {
"name": fragment_description,
"showcontent": show_content,
}
return clean_fragment_options
22 changes: 5 additions & 17 deletions src/towncrier/_settings.py → src/towncrier/_settings/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import toml

from collections import OrderedDict
from .._settings import fragment_types as ft


class ConfigError(Exception):
Expand All @@ -24,15 +25,6 @@ def __init__(self, *args, **kwargs):
_start_string = u".. towncrier release notes start\n"
_title_format = None
_template_fname = "towncrier:default"
_default_types = OrderedDict(
[
(u"feature", {"name": u"Features", "showcontent": True}),
(u"bugfix", {"name": u"Bugfixes", "showcontent": True}),
(u"doc", {"name": u"Improved Documentation", "showcontent": True}),
(u"removal", {"name": u"Deprecations and Removals", "showcontent": True}),
(u"misc", {"name": u"Misc", "showcontent": False}),
]
)
_underlines = ["=", "-", "~"]


Expand Down Expand Up @@ -92,19 +84,15 @@ def parse_toml(base_path, config):
config = config["tool"]["towncrier"]

sections = OrderedDict()
types = OrderedDict()

if "section" in config:
for x in config["section"]:
sections[x.get("name", "")] = x["path"]
else:
sections[""] = ""

if "type" in config:
for x in config["type"]:
types[x["directory"]] = {"name": x["name"], "showcontent": x["showcontent"]}
else:
types = _default_types
fragment_types_loader = ft.BaseFragmentTypesLoader.factory(
config
)
types = fragment_types_loader.load()

wrap = config.get("wrap", False)

Expand Down
2 changes: 2 additions & 0 deletions src/towncrier/newsfragments/369.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added support to tables in toml settings, which provides a more intuitive
way to configure custom types.
Loading