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

Improve news fragment file name parsing #173

Merged
merged 1 commit into from
Apr 15, 2020
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
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