Repository: twisted/towncrier

Issue:
newsfragments directory not created
When running `towncrier --draft` first time, it crashes with:
```
Loading template...
Finding news fragments...
Traceback (most recent call last):
…
  File "/tmp/myvenv/lib/python2.7/site-packages/towncrier/_builder.py", line 46, in find_fragments
    files = os.listdir(section_dir)
OSError: [Errno 2] No such file or directory: '…/PyInstaller/newsfragments'
```

towncrier should not crash with a traceback, but catch the exception and print a nice error message without traceback. Or it should create this directory.

Discussion:
codecov-io: # [Codecov](https://codecov.io/gh/twisted/towncrier/pull/298?src=pr&el=h1) Report
> Merging [#298](https://codecov.io/gh/twisted/towncrier/pull/298?src=pr&el=desc) (0853272) into [master](https://codecov.io/gh/twisted/towncrier/commit/ef1be451c44b71112cf6b6b2f1e90a27dd5ff489?el=desc) (ef1be45) will **decrease** coverage by `5.20%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/twisted/towncrier/pull/298/graphs/tree.svg?width=650&height=150&src=pr&token=9npndnog2E)](https://codecov.io/gh/twisted/towncrier/pull/298?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #298      +/-   ##
==========================================
- Coverage   95.37%   90.17%   -5.21%     
==========================================
  Files          20       11       -9     
  Lines        1038      468     -570     
  Branches      104       94      -10     
==========================================
- Hits          990      422     -568     
+ Misses         27       25       -2     
  Partials       21       21              
```


| [Impacted Files](https://codecov.io/gh/twisted/towncrier/pull/298?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/towncrier/\_builder.py](https://codecov.io/gh/twisted/towncrier/pull/298/diff?src=pr&el=tree#diff-c3JjL3Rvd25jcmllci9fYnVpbGRlci5weQ==) | `93.98% <100.00%> (+0.13%)` | :arrow_up: |
| [src/towncrier/check.py](https://codecov.io/gh/twisted/towncrier/pull/298/diff?src=pr&el=tree#diff-c3JjL3Rvd25jcmllci9jaGVjay5weQ==) | `93.61% <0.00%> (-0.14%)` | :arrow_down: |
| [src/towncrier/build.py](https://codecov.io/gh/twisted/towncrier/pull/298/diff?src=pr&el=tree#diff-c3JjL3Rvd25jcmllci9idWlsZC5weQ==) | `89.87% <0.00%> (-0.13%)` | :arrow_down: |
| [src/towncrier/\_shell.py](https://codecov.io/gh/twisted/towncrier/pull/298/diff?src=pr&el=tree#diff-c3JjL3Rvd25jcmllci9fc2hlbGwucHk=) | `100.00% <0.00%> (ø)` | |
| [src/towncrier/create.py](https://codecov.io/gh/twisted/towncrier/pull/298/diff?src=pr&el=tree#diff-c3JjL3Rvd25jcmllci9jcmVhdGUucHk=) | `100.00% <0.00%> (ø)` | |
| [src/towncrier/test/test\_builder.py](https://codecov.io/gh/twisted/towncrier/pull/298/diff?src=pr&el=tree#diff-c3JjL3Rvd25jcmllci90ZXN0L3Rlc3RfYnVpbGRlci5weQ==) | | |
| [src/towncrier/\_\_main\_\_.py](https://codecov.io/gh/twisted/towncrier/pull/298/diff?src=pr&el=tree#diff-c3JjL3Rvd25jcmllci9fX21haW5fXy5weQ==) | | |
| [src/towncrier/test/test\_write.py](https://codecov.io/gh/twisted/towncrier/pull/298/diff?src=pr&el=tree#diff-c3JjL3Rvd25jcmllci90ZXN0L3Rlc3Rfd3JpdGUucHk=) | | |
| [src/towncrier/test/test\_create.py](https://codecov.io/gh/twisted/towncrier/pull/298/diff?src=pr&el=tree#diff-c3JjL3Rvd25jcmllci90ZXN0L3Rlc3RfY3JlYXRlLnB5) | | |
| [src/towncrier/test/test\_format.py](https://codecov.io/gh/twisted/towncrier/pull/298/diff?src=pr&el=tree#diff-c3JjL3Rvd25jcmllci90ZXN0L3Rlc3RfZm9ybWF0LnB5) | | |
| ... and [3 more](https://codecov.io/gh/twisted/towncrier/pull/298/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/twisted/towncrier/pull/298?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/twisted/towncrier/pull/298?src=pr&el=footer). Last update [ef1be45...0853272](https://codecov.io/gh/twisted/towncrier/pull/298?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

adiroiban: My view now 
![image](https://user-images.githubusercontent.com/204609/102694053-7b489480-4216-11eb-93c3-045bd9b4705e.png)

altendky: Kick CI to watch try to observe codecov responses a bit.

Patch:
diff --git a/src/towncrier/_builder.py b/src/towncrier/_builder.py
index 2a8f0b2..90988ac 100644
--- a/src/towncrier/_builder.py
+++ b/src/towncrier/_builder.py
@@ -4,12 +4,16 @@
 from __future__ import absolute_import, division, print_function
 
 import os
+import sys
 import textwrap
+import traceback
 
 from collections import OrderedDict
 
 from jinja2 import Template
 
+from ._settings import ConfigError
+
 
 def strip_if_integer_string(s):
     try:
@@ -86,7 +90,18 @@ def find_fragments(base_directory, sections, fragment_directory, definitions):
         else:
             section_dir = os.path.join(base_directory, val)
 
-        files = os.listdir(section_dir)
+        if sys.version_info >= (3,):
+            expected_exception = FileNotFoundError
+        else:
+            expected_exception = OSError
+
+        try:
+            files = os.listdir(section_dir)
+        except expected_exception as e:
+            message = "Failed to list the news fragment files.\n{}".format(
+                ''.join(traceback.format_exception_only(type(e), e)),
+            )
+            raise ConfigError(message)
 
         file_content = {}
 
diff --git a/src/towncrier/newsfragments/85.bugfix.rst b/src/towncrier/newsfragments/85.bugfix.rst
new file mode 100644
index 0000000..9db0895
--- /dev/null
+++ b/src/towncrier/newsfragments/85.bugfix.rst
@@ -0,0 +1,1 @@
+A configuration error is triggered when the newsfragment files couldn't be discovered. 


Test patch:
diff --git a/src/towncrier/test/test_build.py b/src/towncrier/test/test_build.py
index ab5f605..d6b7f01 100644
--- a/src/towncrier/test/test_build.py
+++ b/src/towncrier/test/test_build.py
@@ -82,6 +82,35 @@ class TestCli(TestCase):
     def test_subcommand(self):
         self._test_command(_main)
 
+    def test_no_newsfragment_directory(self):
+        """
+        A missing newsfragment directory acts as if there are no changes.
+        """
+        runner = CliRunner()
+
+        with runner.isolated_filesystem():
+            setup_simple_project()
+            os.rmdir("foo/newsfragments")
+
+            result = runner.invoke(_main, ["--draft", "--date", "01-01-2001"])
+
+        self.assertEqual(1, result.exit_code, result.output)
+        self.assertIn("Failed to list the news fragment files.\n", result.output)
+
+    def test_no_newsfragments(self):
+        """
+        An empty newsfragment directory acts as if there are no changes.
+        """
+        runner = CliRunner()
+
+        with runner.isolated_filesystem():
+            setup_simple_project()
+
+            result = runner.invoke(_main, ["--draft", "--date", "01-01-2001"])
+
+        self.assertEqual(0, result.exit_code)
+        self.assertIn("No significant changes.\n", result.output)
+
     def test_collision(self):
         runner = CliRunner()