diff --git a/src/towncrier/_builder.py b/src/towncrier/_builder.py index 7fc2a642..71f68e57 100644 --- a/src/towncrier/_builder.py +++ b/src/towncrier/_builder.py @@ -204,20 +204,45 @@ def find_fragments( return content, fragment_files -def get_underline_length(text: str) -> int: +def get_dict_length(obj: dict) -> int: """ - Given `text` determine the underline length needed for the reStructuredText output. + Gets the sum of the underline lengths for all keys and values in a dictionary. + """ + return sum( + get_underline_length(key) + get_underline_length(value) + for key, value in obj.items() + ) - Particularly helps determine if an extra underline is needed for wide characters like emojis. + +def get_list_length(obj: list) -> int: """ - underline_length: int = 0 - for char in text: - if unicodedata.east_asian_width(char) in ("W", "F"): - underline_length += 2 - else: - underline_length += 1 + Gets the sum of the underline lengths for all items in a list. + """ + return sum(get_underline_length(item) for item in obj) + + +def get_string_length(text: str) -> int: + """ + Determines the amount of characters needed to underline a string. + """ + return sum( + 2 if unicodedata.east_asian_width(char) in ("W", "F") else 1 for char in text + ) - return underline_length + +def get_underline_length(obj: str | dict | list) -> int: + """ + Given `obj` determine the underline length needed for the reStructuredText output. + + Particularly helps determine if an extra underline is needed for wide characters like emojis. + """ + if isinstance(obj, dict): + return get_dict_length(obj) + elif isinstance(obj, list): + return get_list_length(obj) + elif isinstance(obj, str): + return get_string_length(obj) + raise ValueError("Object must be a string, list, or dictionary.") def indent(text: str, prefix: str) -> str: @@ -263,10 +288,6 @@ def split_fragments( # it's recorded. content = "" - definitions[category]["underline_length"] = get_underline_length( - definitions[category]["name"] - ) - texts = section.setdefault(category, {}) issues = texts.setdefault(content, []) @@ -432,9 +453,7 @@ def get_indent(text: str) -> str: top_underline=top_underline, get_indent=get_indent, # simplify indentation in the jinja template. issues_by_category=issues_by_category, - versiondata_name_underline_length=get_underline_length( - versiondata.get("name", "") - ), + get_underline_length=get_underline_length, # helps determine length for non-ascii chars ) for line in res.split("\n"): diff --git a/src/towncrier/templates/default.rst b/src/towncrier/templates/default.rst index b40dfab1..9b196e79 100644 --- a/src/towncrier/templates/default.rst +++ b/src/towncrier/templates/default.rst @@ -1,29 +1,29 @@ {% if render_title %} {% if versiondata.name %} {{ versiondata.name }} {{ versiondata.version }} ({{ versiondata.date }}) -{{ top_underline * ((versiondata.version + versiondata.date)|length + 4 + versiondata_name_underline_length)}} +{{ top_underline * (get_underline_length(versiondata.name + versiondata.version + versiondata.date) + 4)}} {% else %} {{ versiondata.version }} ({{ versiondata.date }}) -{{ top_underline * ((versiondata.version + versiondata.date)|length + 3)}} +{{ top_underline * (get_underline_length(versiondata.version + versiondata.date) + 3)}} {% endif %} {% endif %} {% for section, _ in sections.items() %} {% set underline = underlines[0] %}{% if section %}{{section}} -{{ underline * section|length }}{% set underline = underlines[1] %} +{{ underline * get_underline_length(section) }}{% set underline = underlines[1] %} {% endif %} {% if sections[section] %} {% for category, val in definitions.items() if category in sections[section]%} {{ definitions[category]['name'] }} -{{ underline * definitions[category]['underline_length'] }} +{{ underline * get_underline_length(definitions[category]['name']) }} {% for text, values in sections[section][category].items() %} - {% if text %}{{ text }}{% if values %} ({{ values|join(', ') }}){% endif %}{% else %}{{ values|join(', ') }}{% endif %} {% endfor %} -{% if sections[section][category]|length == 0 %} +{% if get_underline_length(sections[section][category]) == 0 %} No significant changes. {% else %} diff --git a/src/towncrier/templates/hr-between-versions.rst b/src/towncrier/templates/hr-between-versions.rst index df95e91c..675196e0 100644 --- a/src/towncrier/templates/hr-between-versions.rst +++ b/src/towncrier/templates/hr-between-versions.rst @@ -1,22 +1,22 @@ {% if render_title %} {% if versiondata.name %} {{ versiondata.name }} {{ versiondata.version }} ({{ versiondata.date }}) -{{ top_underline * ((versiondata.version + versiondata.date)|length + 4 + versiondata_name_underline_length)}} +{{ top_underline * (get_underline_length(versiondata.name + versiondata.version + versiondata.date) + 4)}} {% else %} {{ versiondata.version }} ({{ versiondata.date }}) -{{ top_underline * ((versiondata.version + versiondata.date)|length + 3)}} +{{ top_underline * (get_underline_length(versiondata.version + versiondata.date) + 3)}} {% endif %} {% endif %} {% for section, _ in sections.items() %} {% set underline = underlines[0] %}{% if section %}{{section}} -{{ underline * section|length }}{% set underline = underlines[1] %} +{{ underline * get_underline_length(section) }}{% set underline = underlines[1] %} {% endif %} {% if sections[section] %} {% for category, val in definitions.items() if category in sections[section]%} {{ definitions[category]['name'] }} -{{ underline * definitions[category]['underline_length'] }} +{{ underline * get_underline_length(definitions[category]['name']) }} {% if definitions[category]['showcontent'] %} {% for text, values in sections[section][category].items() %} @@ -28,7 +28,7 @@ - {{ sections[section][category]['']|join(', ') }} {% endif %} -{% if sections[section][category]|length == 0 %} +{% if get_underline_length(sections[section][category]) == 0 %} No significant changes. {% else %} diff --git a/src/towncrier/templates/single-file-no-bullets.rst b/src/towncrier/templates/single-file-no-bullets.rst index 83af1a78..7fbc34d0 100644 --- a/src/towncrier/templates/single-file-no-bullets.rst +++ b/src/towncrier/templates/single-file-no-bullets.rst @@ -1,22 +1,22 @@ {% if render_title %} {% if versiondata.name %} {{ versiondata.name }} {{ versiondata.version }} ({{ versiondata.date }}) -{{ top_underline * ((versiondata.version + versiondata.date)|length + 4 + versiondata_name_underline_length)}} +{{ top_underline * (get_underline_length(versiondata.name + versiondata.version + versiondata.date) + 4)}} {% else %} {{ versiondata.version }} ({{ versiondata.date }}) -{{ top_underline * ((versiondata.version + versiondata.date)|length + 3)}} +{{ top_underline * (get_underline_length(versiondata.version + versiondata.date) + 3)}} {% endif %} {% endif %} {% for section, _ in sections.items() %} {% set underline = underlines[0] %}{% if section %}{{section}} -{{ underline * section|length }}{% set underline = underlines[1] %} +{{ underline * get_underline_length(section) }}{% set underline = underlines[1] %} {% endif %} {% if sections[section] %} {% for category, val in definitions.items() if category in sections[section] %} {{ definitions[category]['name'] }} -{{ underline * definitions[category]['underline_length'] }} +{{ underline * get_underline_length(definitions[category]['name']) }} {% if definitions[category]['showcontent'] %} {% for text, values in sections[section][category].items() %} @@ -28,7 +28,7 @@ - {{ sections[section][category]['']|join(', ') }} {% endif %} -{% if sections[section][category]|length == 0 %} +{% if get_underline_length(sections[section][category]) == 0 %} No significant changes. {% else %}