From 409375833fbe8896c0dea6bfa578abccf89df954 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Sun, 11 Aug 2019 17:46:07 -0500 Subject: [PATCH] ENH: Add single file and no bullet point option The single file options means that the file name is formatted when it is set to False (and appending will never happen). When `all_bullets` is set to false, then fragmets that should be rendered as bullet points will have include the bullet itself. In that case, bullets will be sorted to the front (bullets include "*", "-", and "#." enumeration). To make indentation of the ticket info easier, a `get_indent(text)` function is exposed to the jinja template. --- README.rst | 1 + src/towncrier/_builder.py | 40 +++++++++++++++++-- src/towncrier/_settings.py | 8 ++++ src/towncrier/build.py | 3 +- src/towncrier/newsfragments/158.feature | 8 ++++ .../template-single-file-no-bullets.rst | 38 ++++++++++++++++++ 6 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/towncrier/newsfragments/158.feature create mode 100644 src/towncrier/templates/template-single-file-no-bullets.rst diff --git a/README.rst b/README.rst index 0c4f476d..51d222f5 100644 --- a/README.rst +++ b/README.rst @@ -125,6 +125,7 @@ Towncrier has the following global options, which can be specified in the toml f issue_format = "format string for {issue} (issue is the first part of fragment name)" underlines: "=-~" wrap = false # Wrap text to 79 characters + all_bullets = true # make all fragments bullet points ``` If a single file is used, the content of this file are overwritten each time. diff --git a/src/towncrier/_builder.py b/src/towncrier/_builder.py index d862cec5..e8dc8bea 100644 --- a/src/towncrier/_builder.py +++ b/src/towncrier/_builder.py @@ -117,7 +117,7 @@ def prefixed_lines(): # Takes the output from find_fragments above. Probably it would be useful to # add an example output here. Next time someone digs deep enough to figure it # out, please do so... -def split_fragments(fragments, definitions): +def split_fragments(fragments, definitions, all_bullets=True): output = OrderedDict() @@ -126,7 +126,14 @@ def split_fragments(fragments, definitions): for (ticket, category, counter), content in section_fragments.items(): - content = indent(content.strip(), u" ")[2:] + if all_bullets: + # By default all fragmetns are append by "-" automatically, + # and need to be indented because of that. + # (otherwise, assume they are formatted correctly) + content = indent(content.strip(), u" ")[2:] + else: + # Assume the text is formatted correctly + content = content.rstrip() if definitions[category]["showcontent"] is False: content = u"" @@ -161,6 +168,19 @@ def entry_key(entry): return [issue_key(issue) for issue in issues] +def bullet_key(entry): + text, _ = entry + if not text: + return -1 + if text[:2] == u"- ": + return 0 + elif text[:2] == "* ": + return 1 + elif text[:3] == u"#. ": + return 2 + return 3 + + def render_issue(issue_format, issue): if issue_format is None: try: @@ -181,6 +201,7 @@ def render_fragments( wrap, versiondata, top_underline="=", + all_bullets=False, ): """ Render the fragments into a news file. @@ -213,6 +234,8 @@ def render_fragments( # - Fix the other thing (#1) # - Fix the thing (#2, #7, #123) entries.sort(key=entry_key) + if not all_bullets: + entries.sort(key=bullet_key) # Then we put these nicely sorted entries back in an ordered dict # for the template, after formatting each issue number @@ -225,12 +248,23 @@ def render_fragments( done = [] + def get_indent(text): + # If bullets are not assumed and we wrap, the subsequent + # indentation depends on whether or not this is a bullet point. + # (it is probably usually best to disable wrapping in that case) + if all_bullets or text[:2] == u"- " or text[:2] == u"* ": + return u" " + elif text[:3] == "#. ": + return u" " + return u"" + res = jinja_template.render( sections=data, definitions=definitions, underlines=underlines, versiondata=versiondata, top_underline=top_underline, + get_indent=get_indent, # simplify indentation in the jinja template. ) for line in res.split(u"\n"): @@ -239,7 +273,7 @@ def render_fragments( textwrap.fill( line, width=79, - subsequent_indent=u" ", + subsequent_indent=get_indent(line), break_long_words=False, break_on_hyphens=False, ) diff --git a/src/towncrier/_settings.py b/src/towncrier/_settings.py index 8d576a84..05ad1c58 100644 --- a/src/towncrier/_settings.py +++ b/src/towncrier/_settings.py @@ -79,6 +79,13 @@ def parse_toml(config): failing_option="single_file", ) + all_bullets = config.get("all_bullets", True) + if not isinstance(all_bullets, bool): + raise ConfigError( + "`all_bullets` option must be boolean: false or true.", + failing_option="all_bullets", + ) + return { "package": config.get("package", ""), "package_dir": config.get("package_dir", "."), @@ -93,4 +100,5 @@ def parse_toml(config): "issue_format": config.get("issue_format"), "underlines": config.get("underlines", _underlines), "wrap": wrap, + "all_bullets": all_bullets, } diff --git a/src/towncrier/build.py b/src/towncrier/build.py index d2e933a9..e1787071 100644 --- a/src/towncrier/build.py +++ b/src/towncrier/build.py @@ -120,7 +120,7 @@ def __main( ) click.echo("Rendering news fragments...", err=to_err) - fragments = split_fragments(fragments, definitions) + fragments = split_fragments(fragments, definitions, all_bullets=config["all_bullets"]) if project_version is None: project_version = get_version( @@ -158,6 +158,7 @@ def __main( config["wrap"], {"name": project_name, "version": project_version, "date": project_date}, top_underline=config["underlines"][0], + all_bullets=config["all_bullets"], ) if draft: diff --git a/src/towncrier/newsfragments/158.feature b/src/towncrier/newsfragments/158.feature new file mode 100644 index 00000000..1abe6277 --- /dev/null +++ b/src/towncrier/newsfragments/158.feature @@ -0,0 +1,8 @@ +There is now the option for ``all_bullets = false`` in the configuration. +Setting ``all_bullets`` to false means that news fragments have to include +the bullet point if they should be rendered as enumerations, otherwise +they are rendered directly (this means fragments can include a header.). +It is necessary to set this option to avoid (incorrect) automatic indentation +of multiline fragments that do not include bullet points. +The ``template-single-file-no-bullets.rst`` file gives an example template +using these options. diff --git a/src/towncrier/templates/template-single-file-no-bullets.rst b/src/towncrier/templates/template-single-file-no-bullets.rst new file mode 100644 index 00000000..dcb1812d --- /dev/null +++ b/src/towncrier/templates/template-single-file-no-bullets.rst @@ -0,0 +1,38 @@ +{% set title = "{} {} Release Notes".format(versiondata.name, versiondata.version) %} +{{ "=" * title|length }} +{{ title }} +{{ "=" * title|length }} + +{% for section, _ in sections.items() %} +{% set underline = underlines[0] %}{% if section %}{{ section }} +{{ underline * section|length }}{% 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]['name']|length }} + +{% if definitions[category]['showcontent'] %} +{% for text, values in sections[section][category].items() %} +{{ text }} +{{ get_indent(text) }}({{values|join(', ') }}) + +{% endfor %} +{% else %} +- {{ sections[section][category]['']|join(', ') }} + +{% endif %} +{% if sections[section][category]|length == 0 %} +No significant changes. + +{% else %} +{% endif %} +{% endfor %} +{% else %} +No significant changes. + + +{% endif %} +{% endfor %}