Repository: LukeCarrier/mkdocs-drawio-exporter

Issue:
Empty document crashes build
Hi, this might be per design currently, but referring to an empty tab in a multipage document causes a crash

```
Error: Export failed: /docs/content/technical/network/site_design.drawio
ERROR   -  Output file not created in cache 
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/mkdocsdrawioexporter/plugin.py", line 85, in on_post_build
    copy_file(cache_filename, abs_dest_path)
  File "/usr/local/lib/python3.7/site-packages/mkdocs/utils/__init__.py", line 100, in copy_file
    shutil.copyfile(source_path, output_path)
  File "/usr/lib64/python3.7/shutil.py", line 120, in copyfile
    with open(src, 'rb') as fsrc:
FileNotFoundError: [Errno 2] No such file or directory: '/docs/content/cache/9a7d5d6802558d95399cdc2f0d36a830a92be749-3'
```

As a workaround I could just put some object in the tab but ... I think as a user I'd expect an empty tab to just produce empty output, somehow?

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b50461..43a45bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 ## 0.6.0: ease containerisation
 
 * New `drawio_args` option allows passing additional args to the Draw.io CLI
+* Improve handling of cases where Draw.io reports a successful export but doesn't write an output file
 
 ## 0.5.0: support MkDocs 1.1
 
diff --git a/mkdocsdrawioexporter/exporter.py b/mkdocsdrawioexporter/exporter.py
index 465e621..74352bd 100644
--- a/mkdocsdrawioexporter/exporter.py
+++ b/mkdocsdrawioexporter/exporter.py
@@ -199,13 +199,15 @@ class DrawIoExporter:
         :param list(str) drawio_args: Additional arguments to append to the Draw.io export command.
         :param str cache_dir: Export cache directory.
         :param str format: Desired export format.
-        :return str: Cached export filename.
+        :return tuple(str, int): Cached export filename.
         """
+        cache_filename = self.make_cache_filename(source_rel, page_index, cache_dir)
+        exit_status = None
+
         if not drawio_executable:
             self.log.warn('Skipping build of "{}" as Draw.io executable not available'.format(source))
-            return
+            return (cache_filename, exit_status)
 
-        cache_filename = self.make_cache_filename(source_rel, page_index, cache_dir)
         if self.use_cached_file(source, cache_filename):
             self.log.debug('Source file appears unchanged; using cached copy from "{}"'.format(cache_filename))
         else:
@@ -213,11 +215,8 @@ class DrawIoExporter:
             exit_status = self.export_file(
                     source, page_index, cache_filename,
                     drawio_executable, drawio_args, format)
-            if exit_status != 0:
-                self.log.error('Export failed with exit status {}'.format(exit_status))
-                return
 
-        return cache_filename
+        return (cache_filename, exit_status)
 
     def make_cache_filename(self, source, page_index, cache_dir):
         """Make the cached filename.
diff --git a/mkdocsdrawioexporter/plugin.py b/mkdocsdrawioexporter/plugin.py
index ae02db1..6ebaae5 100644
--- a/mkdocsdrawioexporter/plugin.py
+++ b/mkdocsdrawioexporter/plugin.py
@@ -78,12 +78,16 @@ class DrawIoExporterPlugin(mkdocs.plugins.BasePlugin):
                     source.source_rel, source.page_index, self.config['format'])
             abs_src_path = os.path.join(config['docs_dir'], source.source_rel)
             abs_dest_path = os.path.join(config['site_dir'], dest_rel_path)
-            cache_filename = self.exporter.ensure_file_cached(
+            cache_filename, exit_status = self.exporter.ensure_file_cached(
                     abs_src_path, source.source_rel, source.page_index,
                     self.config['drawio_executable'], self.config['drawio_args'],
                     self.config['cache_dir'], self.config['format'])
 
+            if exit_status != 0:
+                log.error('Export failed with exit status {}; skipping copy'.format(exit_status))
+                continue
+
             try:
                 copy_file(cache_filename, abs_dest_path)
             except FileNotFoundError:
-                log.exception('Output file not created in cache')
+                log.warn('Export successful, but wrote no output file')


Test patch:
diff --git a/mkdocsdrawioexporter/tests/exporter.py b/mkdocsdrawioexporter/tests/exporter.py
index 40de740..d8ccf66 100644
--- a/mkdocsdrawioexporter/tests/exporter.py
+++ b/mkdocsdrawioexporter/tests/exporter.py
@@ -109,9 +109,10 @@ class ExporterTests(unittest.TestCase):
         self.exporter.export_file = MagicMock()
         self.exporter.export_file.return_value = 0
 
-        result = self.exporter.ensure_file_cached(
+        cache_filename, exit_status = self.exporter.ensure_file_cached(
                 source, source_rel, 0, drawio_executable, [], cache_dir, 'svg')
-        assert result == self.exporter.make_cache_filename.return_value
+        assert cache_filename == self.exporter.make_cache_filename.return_value
+        assert exit_status == 0
 
     def test_ensure_file_cached_aborts_if_drawio_executable_unavailable(self):
         source = sep + join('docs', 'diagram.drawio')
@@ -124,10 +125,10 @@ class ExporterTests(unittest.TestCase):
 
         self.log.warn = MagicMock()
 
-        result = self.exporter.ensure_file_cached(
+        cache_filename, exit_status = self.exporter.ensure_file_cached(
                 source, source_rel, 0, drawio_executable, [], cache_dir, 'svg')
 
-        assert result == None
+        assert exit_status == None
         self.log.warn.assert_called_once()
 
     def test_ensure_file_cached_skips_export_if_cache_fresh(self):
@@ -145,14 +146,15 @@ class ExporterTests(unittest.TestCase):
         self.exporter.export_file = MagicMock()
         self.exporter.export_file.return_value = 0
 
-        result = self.exporter.ensure_file_cached(
+        cache_filename, exit_status = self.exporter.ensure_file_cached(
                 source, source_rel, 0, drawio_executable, [], cache_dir, 'svg')
 
-        assert result == self.exporter.make_cache_filename.return_value
+        assert cache_filename == self.exporter.make_cache_filename.return_value
+        assert exit_status == None
         self.exporter.use_cached_file.assert_called_once()
         assert not self.exporter.export_file.called
 
-    def test_ensure_file_cached_logs_error_if_export_fails(self):
+    def test_ensure_file_cached_returns_exit_status_if_non_zero(self):
         source = sep + join('docs', 'diagram.drawio')
         source_rel = 'diagram.drawio'
         drawio_executable = sep + join('bin', 'drawio')
@@ -169,11 +171,10 @@ class ExporterTests(unittest.TestCase):
 
         self.log.error = MagicMock()
 
-        result = self.exporter.ensure_file_cached(
+        cache_filename, exit_status = self.exporter.ensure_file_cached(
                 source, source_rel, 0, drawio_executable, [], cache_dir, 'svg')
 
-        assert result == None
-        self.log.error.assert_called_once()
+        assert exit_status == 1
 
     def test_make_cache_filename(self):
         cache_dir = sep + 'docs'