Skip to content

Commit

Permalink
ENH: Add single file and no bullet point option
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
seberg committed Aug 12, 2019
1 parent 83c33da commit 1364832
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 13 deletions.
33 changes: 33 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,36 @@ These are:

The start of the filename is the ticket number, and the content is what will end up in the news file.
For example, if ticket #850 is about adding a new widget, the filename would be ``myproject/newsfragments/850.feature`` and the content would be ``myproject.widget has been added``.


Further Options
---------------

Towncrier has the following global options, which can be specified in the toml file:

```
[tool.towncrier]
package = ""
package_dir = "."
single_file = true # if false, filename is formatted like `title_format`.
filename = "NEWS.rst"
directory = "directory/of/news/fragments"
template = "path/to/template.rst"
start_line = "start of generated content"
title_format = "{name} {version} ({project_date})" # or false if template includes title
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.

Furthermore, you can add your own fragment types using:
```
[tool.towncrier]
[[tool.towncrier.type]]
directory = "deprecation"
name = "Deprecations"
showcontent = true
```

40 changes: 37 additions & 3 deletions src/towncrier/_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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""
Expand Down Expand Up @@ -161,6 +168,17 @@ def entry_key(entry):
return [issue_key(issue) for issue in issues]


def bullet_key(entry):
text, _ = entry
if text[0] == u"-":
return 0
elif text[0] == "*":
return 1
elif text[:2] == u"#.":
return 2
return 3


def render_issue(issue_format, issue):
if issue_format is None:
try:
Expand All @@ -181,6 +199,7 @@ def render_fragments(
wrap,
versiondata,
top_underline="=",
all_bullets=False,
):
"""
Render the fragments into a news file.
Expand Down Expand Up @@ -213,6 +232,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
Expand All @@ -225,12 +246,25 @@ 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 not text:
return u""
if all_bullets or text[0] in "-*":
return u" "
elif text[:2] == "#.":
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"):
Expand All @@ -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,
)
Expand Down
10 changes: 10 additions & 0 deletions src/towncrier/_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,18 @@ def parse_toml(config):
else:
wrap = False

single_file = config.get("single_file", True)
if not isinstance(single_file, bool):
raise ValueError("`single_file` option must be a boolean: false or true.")

all_bullets = config.get("all_bullets", True)
if not isinstance(all_bullets, bool):
raise ValueError("`all_bullets` must be boolean: false or true.")

return {
"package": config.get("package", ""),
"package_dir": config.get("package_dir", "."),
"single_file": single_file,
"filename": config.get("filename", "NEWS.rst"),
"directory": config.get("directory"),
"sections": sections,
Expand All @@ -77,4 +86,5 @@ def parse_toml(config):
"issue_format": config.get("issue_format"),
"underlines": config.get("underlines", _underlines),
"wrap": wrap,
"all_bullets": all_bullets,
}
16 changes: 9 additions & 7 deletions src/towncrier/_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@
import os


def append_to_newsfile(directory, filename, start_line, top_line, content):
def append_to_newsfile(directory, filename, start_line, top_line, content, single_file=True):

news_file = os.path.join(directory, filename)

if not os.path.exists(news_file):
existing_content = u""
if single_file:
if not os.path.exists(news_file):
existing_content = u""
else:
with open(news_file, "rb") as f:
existing_content = f.read().decode("utf8")
existing_content = existing_content.split(start_line, 1)
else:
with open(news_file, "rb") as f:
existing_content = f.read().decode("utf8")

existing_content = existing_content.split(start_line, 1)
existing_content = [u""]

if top_line and top_line in existing_content:
raise ValueError("It seems you've already produced newsfiles for this version?")
Expand Down
15 changes: 12 additions & 3 deletions src/towncrier/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,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(
Expand Down Expand Up @@ -152,6 +152,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:
Expand All @@ -167,12 +168,20 @@ def __main(
else:
click.echo("Writing to newsfile...", err=to_err)
start_line = config["start_line"]
news_file = config["filename"]
if not config["single_file"]:
news_file = news_file.format(
name=project_name,
version=project_version,
project_date=project_date,
)
append_to_newsfile(
directory, config["filename"], start_line, top_line, rendered
directory, news_file, start_line, top_line, rendered,
single_file=config["single_file"]
)

click.echo("Staging newsfile...", err=to_err)
stage_newsfile(directory, config["filename"])
stage_newsfile(directory, news_file)

click.echo("Removing news fragments...", err=to_err)
remove_files(fragment_filenames, answer_yes)
Expand Down
8 changes: 8 additions & 0 deletions src/towncrier/newsfragments/158.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
There is now the option for ``single_file = false`` and ``all_bullets = false``
in the configuration. Disabling ``single_file`` causes the filename to be
formatable using `name`, `version` and `project_date`. 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.).
The ``template-single-file-no-bullets.rst`` file gives an example template
using these options.
38 changes: 38 additions & 0 deletions src/towncrier/templates/template-single-file-no-bullets.rst
Original file line number Diff line number Diff line change
@@ -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 %}

0 comments on commit 1364832

Please sign in to comment.