Skip to content

Commit

Permalink
Improve news fragment file name parsing (#173)
Browse files Browse the repository at this point in the history
The new version uses category as the reference point to later infer the
issue number and counter value.
As a consequence, it will return (None, None, None) if the base name
doesn't contain a valid category.

The new parser allows using file names like '123.feature.1.ext' which
are convenient when one wants to use an appropriate extension (e.g. rst,
md) to enable syntax highlighting.
  • Loading branch information
tjanez authored Apr 15, 2020
1 parent fff788d commit 706a48d
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 27 deletions.
54 changes: 29 additions & 25 deletions src/towncrier/_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,39 @@


# Returns ticket, category and counter or (None, None, None) if the basename
# could not be parsed
# could not be parsed or doesn't contain a valid category.
def parse_newfragment_basename(basename, definitions):
parts = basename.split(u".")
invalid = (None, None, None)
parts = basename.split(".")

if len(parts) == 1:
return (None, None, None)
return invalid
if len(parts) == 2:
ticket, category = parts
return ticket, category, 0

# fix-1.2.3.feature and fix.1.feature.2 are valid formats. The former is
# used in projects which don't put ticket numbers to newfragment names.
if parts[-1] in definitions:
category = parts[-1]
ticket = parts[-2]
return ticket, category, 0

# If there is a number after the category then use it as a counter,
# otherwise ignore it.
# This means 1.feature.1 and 1.feature do not conflict but
# 1.feature.rst and 1.feature do.
counter = 0
try:
counter = int(parts[-1])
except ValueError:
pass
category = parts[-2]
ticket = parts[-3]
return ticket, category, counter
return (ticket, category, 0) if category in definitions else invalid

# There are at least 3 parts. Search for a valid category from the second
# part onwards.
# The category is used as the reference point in the parts list to later
# infer the issue number and counter value.
for i in range(1, len(parts)):
if parts[i] in definitions:
# Current part is a valid category according to given definitions.
category = parts[i]
# Use the previous part as the ticket number.
# NOTE: This allows news fragment names like fix-1.2.3.feature or
# something-cool.feature.ext for projects that don't use ticket
# numbers in news fragment names.
ticket = parts[i-1]
counter = 0
# Use the following part as the counter if it exists and is a valid
# digit.
if len(parts) > (i + 1) and parts[i+1].isdigit():
counter = int(parts[i+1])
return ticket, category, counter
else:
# No valid category found.
return invalid


# Returns a structure like:
Expand Down Expand Up @@ -81,7 +85,7 @@ def find_fragments(base_directory, sections, fragment_directory, definitions):
ticket, category, counter = parse_newfragment_basename(
basename, definitions
)
if category is None or category not in definitions:
if category is None:
continue

full_filename = os.path.join(section_dir, basename)
Expand Down
3 changes: 3 additions & 0 deletions src/towncrier/newsfragments/173.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Improve news fragment file name parsing to allow using file names like
``123.feature.1.ext`` which are convenient when one wants to use an appropriate
extension (e.g. ``rst``, ``md``) to enable syntax highlighting.
23 changes: 21 additions & 2 deletions src/towncrier/test/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,24 @@ def test_simple(self):
("123", "feature", 0),
)

def test_invalid_category(self):
self.assertEqual(
parse_newfragment_basename("README.ext", ["feature"]),
(None, None, None),
)

def test_counter(self):
self.assertEqual(
parse_newfragment_basename("123.feature.1", ["feature"]),
("123", "feature", 1),
)

def test_counter_with_extension(self):
self.assertEqual(
parse_newfragment_basename("123.feature.1.ext", ["feature"]),
("123", "feature", 1),
)

def test_ignores_extension(self):
self.assertEqual(
parse_newfragment_basename("123.feature.ext", ["feature"]),
Expand All @@ -31,15 +43,22 @@ def test_non_numeric_ticket(self):
("baz", "feature", 0),
)

def test_non_numeric_ticket_with_extension(self):
self.assertEqual(
parse_newfragment_basename("baz.feature.ext", ["feature"]),
("baz", "feature", 0),
)

def test_dots_in_ticket_name(self):
self.assertEqual(
parse_newfragment_basename("baz.1.2.feature", ["feature"]),
("2", "feature", 0),
)

def test_dots_in_ticket_name_unknown_category(self):
def test_dots_in_ticket_name_invalid_category(self):
self.assertEqual(
parse_newfragment_basename("baz.1.2.notfeature", ["feature"]), ("1", "2", 0)
parse_newfragment_basename("baz.1.2.notfeature", ["feature"]),
(None, None, None),
)

def test_dots_in_ticket_name_and_counter(self):
Expand Down

0 comments on commit 706a48d

Please sign in to comment.