diff --git a/src/towncrier/_builder.py b/src/towncrier/_builder.py index 519275e7..ec26a295 100644 --- a/src/towncrier/_builder.py +++ b/src/towncrier/_builder.py @@ -34,7 +34,7 @@ def find_fragments(base_directory, sections, fragment_directory): """ Sections are a dictonary of section names to paths. """ - content = {} + content = OrderedDict() for key, val in sections.items(): @@ -58,7 +58,7 @@ def find_fragments(base_directory, sections, fragment_directory): def split_fragments(fragments, definitions): - output = {} + output = OrderedDict() for section_name, section_fragments in fragments.items(): section = {} @@ -129,11 +129,11 @@ def render_fragments( jinja_template = Template(template, trim_blocks=True) - data = {} + data = OrderedDict() for section_name, section_value in fragments.items(): - data[section_name] = {} + data[section_name] = OrderedDict() for category_name, category_value in section_value.items(): # Suppose we start with an ordering like this: diff --git a/src/towncrier/newsfragments/70.feature b/src/towncrier/newsfragments/70.feature new file mode 100644 index 00000000..0959a788 --- /dev/null +++ b/src/towncrier/newsfragments/70.feature @@ -0,0 +1 @@ +Instead of sorting sections/types alphabetically (e.g. "bugfix" before "feature" because "b" < "f"), sections/types will now have the same order in the output as they have in your config file. diff --git a/src/towncrier/templates/template.rst b/src/towncrier/templates/template.rst index 76ba2dc5..d77e1c10 100644 --- a/src/towncrier/templates/template.rst +++ b/src/towncrier/templates/template.rst @@ -1,11 +1,11 @@ -{% for section, _ in sections|dictsort(by='key') %} +{% 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|dictsort if category in sections[section]%} +{% for category, val in definitions.items() if category in sections[section]%} {{ definitions[category]['name'] }} {{ underline * definitions[category]['name']|length }} diff --git a/src/towncrier/test/test_cli.py b/src/towncrier/test/test_cli.py index cf59c160..b282f43a 100644 --- a/src/towncrier/test/test_cli.py +++ b/src/towncrier/test/test_cli.py @@ -3,6 +3,7 @@ import os from contextlib import contextmanager +from textwrap import dedent from twisted.trial.unittest import TestCase from click.testing import CliRunner @@ -87,18 +88,101 @@ def test_happy_path_toml(self): u'- Extends levitation (#124)\n\n' ) - def test_sorting(self): + def test_section_and_type_sorting(self): + """ + Sections and types should be output in the same order that they're + defined in the config file. + """ + runner = CliRunner() - with runner.isolated_filesystem(): - setup_simple_project() - with open('foo/newsfragments/123.feature', 'w') as f: - f.write('Adds levitation') + def run_order_scenario(sections, types): + with runner.isolated_filesystem(): + with open('pyproject.toml', 'w') as f: + f.write(dedent(""" + [tool.towncrier] + package = "foo" + directory = "news" + + """)) + + for section in sections: + f.write(dedent(""" + [[tool.towncrier.section]] + path = "{section}" + name = "{section}" + """.format(section=section))) + + for type_ in types: + f.write(dedent(""" + [[tool.towncrier.type]] + directory = "{type_}" + name = "{type_}" + showcontent = true + """.format(type_=type_))) + + os.mkdir('foo') + with open('foo/__init__.py', 'w') as f: + f.write('__version__ = "1.2.3"\n') + os.mkdir('news') + for section in sections: + sectdir = "news/" + section + os.mkdir(sectdir) + for type_ in types: + with open("{}/1.{}".format(sectdir, type_), 'w') as f: + f.write('{} {}'.format(section, type_)) + + return runner.invoke( + _main, ['--draft', '--date', '01-01-2001'], + catch_exceptions=False, + ) - result = runner.invoke(_main, ['--draft', '--date', '01-01-2001']) + result = run_order_scenario( + ["section-a", "section-b"], ["type-1", "type-2"], + ) + self.assertEqual(0, result.exit_code) + self.assertEqual( + result.output, + u'Loading template...\nFinding news fragments...\nRendering news ' + u'fragments...\nDraft only -- nothing has been written.\nWhat is ' + u'seen below is what would be written.\n\nFoo 1.2.3 (01-01-2001)' + u'\n======================\n' + + dedent(""" + section-a + --------- + + type-1 + ~~~~~~ + + - section-a type-1 (#1) + + + type-2 + ~~~~~~ + + - section-a type-2 (#1) + + + section-b + --------- - # Issues should be sorted alphabetic before + type-1 + ~~~~~~ + - section-b type-1 (#1) + + + type-2 + ~~~~~~ + + - section-b type-2 (#1) + + """) + ) + + result = run_order_scenario( + ["section-b", "section-a"], ["type-2", "type-1"], + ) self.assertEqual(0, result.exit_code) self.assertEqual( result.output, @@ -106,5 +190,35 @@ def test_sorting(self): u'fragments...\nDraft only -- nothing has been written.\nWhat is ' u'seen below is what would be written.\n\nFoo 1.2.3 (01-01-2001)' u'\n======================\n' - u'\n\nFeatures\n--------\n\n- Adds levitation (#123)\n\n' + + dedent(""" + section-b + --------- + + type-2 + ~~~~~~ + + - section-b type-2 (#1) + + + type-1 + ~~~~~~ + + - section-b type-1 (#1) + + + section-a + --------- + + type-2 + ~~~~~~ + + - section-a type-2 (#1) + + + type-1 + ~~~~~~ + + - section-a type-1 (#1) + + """) ) diff --git a/src/towncrier/test/test_format.py b/src/towncrier/test/test_format.py index 028dff39..a813a064 100644 --- a/src/towncrier/test/test_format.py +++ b/src/towncrier/test/test_format.py @@ -81,8 +81,8 @@ def test_basic(self): Basic functionality -- getting a bunch of news fragments and formatting them into a rST file -- works. """ - fragments = { - "": { + fragments = OrderedDict([ + ("", { # asciibetical sorting will do 1, 142, 9 # we want 1, 9, 142 instead "142.misc": u"", @@ -94,12 +94,12 @@ def test_basic(self): "72.feature": u"Foo added.", "9.feature": u"Foo added.", "baz.feature": u"Fun!", - }, - "Web": { + }), + ("Names", {}), + ("Web", { "3.bugfix": u"Web fixed.", - }, - "Names": {} - } + }), + ]) definitions = OrderedDict([ ("feature", {"name": "Features", "showcontent": True}), diff --git a/src/towncrier/test/test_write.py b/src/towncrier/test/test_write.py index d549675e..c25dab58 100644 --- a/src/towncrier/test/test_write.py +++ b/src/towncrier/test/test_write.py @@ -16,19 +16,19 @@ class WritingTests(TestCase): def test_append_at_top(self): - fragments = { - "": { + fragments = OrderedDict([ + ("", { "142.misc": u"", "1.misc": u"", "4.feature": u"Stuff!", "2.feature": u"Foo added.", "72.feature": u"Foo added.", - }, - "Web": { + }), + ("Names", {}), + ("Web", { "3.bugfix": u"Web fixed.", - }, - "Names": {} - } + }), + ]) definitions = OrderedDict([ ("feature", {"name": "Features", "showcontent": True}), @@ -100,20 +100,20 @@ def test_append_at_top_with_hint(self): If there is a comment with C{.. towncrier release notes start}, towncrier will add the version notes after it. """ - fragments = { - "": { + fragments = OrderedDict([ + ("", { "142.misc": u"", "1.misc": u"", "4.feature": u"Stuff!", "2.feature": u"Foo added.", "72.feature": u"Foo added.", "99.feature": u"Foo! " * 100 - }, - "Web": { + }), + ("Names", {}), + ("Web", { "3.bugfix": u"Web fixed.", - }, - "Names": {} - } + }), + ]) definitions = OrderedDict([ ("feature", {"name": "Features", "showcontent": True}),