Repository: springload/draftjs_exporter

Issue:
Change default engine to the new dependency-free one introduced in #77
The exporter now has an engine that doesn't have any dependencies. It should probably be the one activated by default, to make the whole package dependency-free unless another engine is configured. It also happens to be faster, and less memory-hungry.

This is a breaking change though, no matter how little difference there is with the output of the current default (html5lib + BeautifulSoup), so should be part of the 2.0.0 release.

As part of this change, it will also be necessary to move the html5lib / BS4 dependencies to a separate extra like for lxml (`pip install draftjs_exporter[html5lib]`), as well as update the documentation.

Comments:

Patch:
diff --git a/README.rst b/README.rst
index 94898d6..20b58a9 100644
--- a/README.rst
+++ b/README.rst
@@ -248,33 +248,22 @@ See ``examples.py`` in the repository for more details.
 Alternative backing engines
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-By default the exporter uses ``html5lib`` via BeautifulSoup to build the DOM tree. There are two alternative backing engines: ``string`` and ``lxml``.
+By default, the exporter uses a dependency-free engine called ``string`` to build the DOM tree. There are two alternative backing engines: ``html5lib`` (via BeautifulSoup) and ``lxml``.
 
-The ``string`` engine is the fastest, and does not have any dependencies. Its only drawback is that the ``parse_html`` method does not escape/sanitise HTML like that of other engines.
+The ``string`` engine is the fastest, and does not have any dependencies. Its only drawback is that the ``parse_html`` method does not escape/sanitise HTML like that of other engines. It is also more recent, so hasn't been as battle-tested as the other ones.
 
-To use it, add the following to the exporter config:
+*  For ``html5lib``, do ``pip install draftjs_exporter[html5lib]``.
+*  For ``lxml``, do ``pip install draftjs_exporter[lxml]``. It also requires ``libxml2`` and ``libxslt`` to be available on your system.
 
-.. code:: python
-
-    config = {
-        # Specify which DOM backing engine to use.
-        'engine': 'string',
-    }
-
-``lxml`` is also supported. It requires ``libxml2`` and ``libxslt`` to be available on your system.
-
-.. code:: sh
-
-    # Use the `lxml` extra to install the exporter and its lxml dependencies:
-    pip install draftjs_exporter[lxml]
-
-Add the following to the exporter config:
+Then, use the ``engine`` attribute of the exporter config:
 
 .. code:: python
 
     config = {
         # Specify which DOM backing engine to use.
-        'engine': 'lxml',
+        'engine': DOM.HTML5LIB,
+        # Or for lxml:
+        'engine': DOM.LXML,
     }
 
 Custom backing engines
@@ -307,7 +296,10 @@ Here is an example implementation:
             return elt
 
 
-    exporter = HTML({'engine': DOMListTree})
+    exporter = HTML({
+        # Use the dotted module syntax to point to the DOMEngine implementation.
+        'engine': 'myproject.example.DOMListTree'
+    })
 
 Development
 -----------
diff --git a/draftjs_exporter/html.py b/draftjs_exporter/html.py
index 2eb5264..9c4ce2b 100644
--- a/draftjs_exporter/html.py
+++ b/draftjs_exporter/html.py
@@ -25,7 +25,7 @@ class HTML:
         self.block_map = config.get('block_map', BLOCK_MAP)
         self.style_map = config.get('style_map', STYLE_MAP)
 
-        DOM.use(config.get('engine', DOM.HTML5LIB))
+        DOM.use(config.get('engine', DOM.STRING))
 
     def render(self, content_state=None):
         """
diff --git a/setup.py b/setup.py
index c6438ed..4f0cb3b 100755
--- a/setup.py
+++ b/setup.py
@@ -11,8 +11,9 @@ try:
 except ImportError:
     from distutils.core import setup
 
+dependencies = []
 
-dependencies = [
+html5lib_dependencies = [
     'beautifulsoup4>=4.4.1,<5',
     'html5lib>=0.999,<=1.0b10',
 ]
@@ -34,7 +35,7 @@ testing_dependencies = [
     'coverage>=4.1.0',
     'flake8>=3.2.0',
     'isort==4.2.5',
-] + lxml_dependencies
+] + html5lib_dependencies + lxml_dependencies
 
 documentation_dependencies = [
 
@@ -78,5 +79,6 @@ setup(
         'testing': testing_dependencies,
         'docs': documentation_dependencies,
         'lxml': lxml_dependencies,
+        'html5lib': html5lib_dependencies,
     },
     zip_safe=False)


Test patch:
diff --git a/tests/test_composite_decorators.py b/tests/test_composite_decorators.py
index 721f4dd..aeb029c 100644
--- a/tests/test_composite_decorators.py
+++ b/tests/test_composite_decorators.py
@@ -55,7 +55,7 @@ class TestHashtag(unittest.TestCase):
         self.assertEqual(DOM.render(DOM.create_element(HASHTAG_DECORATOR['component'], {'block': {'type': BLOCK_TYPES.UNSTYLED}}, '#hashtagtest')), '<span class="hashtag">#hashtagtest</span>')
 
     def test_render_code_block(self):
-        self.assertEqual(DOM.render(DOM.create_element(HASHTAG_DECORATOR['component'], {'block': {'type': BLOCK_TYPES.CODE}}, '#hashtagtest')), '#hashtagtest')
+        self.assertEqual(DOM.create_element(HASHTAG_DECORATOR['component'], {'block': {'type': BLOCK_TYPES.CODE}}, '#hashtagtest'), '#hashtagtest')
 
 
 class TestBR(unittest.TestCase):
@@ -68,7 +68,7 @@ class TestBR(unittest.TestCase):
 
 class TestCompositeDecorators(unittest.TestCase):
     def test_render_decorators_empty(self):
-        self.assertEqual(DOM.render(render_decorators([], 'test https://www.example.com#hash #hashtagtest', {'type': BLOCK_TYPES.UNSTYLED, 'depth': 0})), 'test https://www.example.com#hash #hashtagtest')
+        self.assertEqual(render_decorators([], 'test https://www.example.com#hash #hashtagtest', {'type': BLOCK_TYPES.UNSTYLED, 'depth': 0}), 'test https://www.example.com#hash #hashtagtest')
 
     def test_render_decorators_single(self):
         self.assertEqual(DOM.render(render_decorators([LINKIFY_DECORATOR], 'test https://www.example.com#hash #hashtagtest', {'type': BLOCK_TYPES.UNSTYLED, 'depth': 0})), 'test <a href="https://www.example.com#hash">https://www.example.com#hash</a> #hashtagtest')
diff --git a/tests/test_dom.py b/tests/test_dom.py
index 48eed62..e5d5172 100644
--- a/tests/test_dom.py
+++ b/tests/test_dom.py
@@ -5,6 +5,7 @@ import unittest
 from draftjs_exporter.dom import DOM
 from draftjs_exporter.engines.html5lib import DOM_HTML5LIB
 from draftjs_exporter.engines.lxml import DOM_LXML
+from draftjs_exporter.engines.string import DOMString
 from tests.test_entities import icon
 
 
@@ -29,6 +30,10 @@ class TestDOM(unittest.TestCase):
         DOM.use(DOM.HTML5LIB)
         self.assertEqual(DOM.dom, DOM_HTML5LIB)
 
+    def test_use_string(self):
+        DOM.use(DOM.STRING)
+        self.assertEqual(DOM.dom, DOMString)
+
     def test_use_invalid(self):
         with self.assertRaises(ImportError):
             DOM.use('test')
diff --git a/tests/test_html.py b/tests/test_html.py
index 86a2ceb..069196a 100644
--- a/tests/test_html.py
+++ b/tests/test_html.py
@@ -3,6 +3,8 @@ from __future__ import absolute_import, unicode_literals
 import unittest
 
 from draftjs_exporter.command import Command
+from draftjs_exporter.dom import DOM
+from draftjs_exporter.engines.string import DOMString
 from draftjs_exporter.html import HTML
 
 config = {
@@ -29,6 +31,10 @@ class TestHTML(unittest.TestCase):
     def test_init(self):
         self.assertIsInstance(self.exporter, HTML)
 
+    def test_init_dom_engine_default(self):
+        HTML()
+        self.assertEqual(DOM.dom, DOMString)
+
     def test_render_block_exists(self):
         self.assertTrue('render_block' in dir(self.exporter))
 
diff --git a/tests/test_output.py b/tests/test_output.py
index 28726d7..3922ee4 100644
--- a/tests/test_output.py
+++ b/tests/test_output.py
@@ -43,7 +43,7 @@ config = {
             'props': {'style': {'textDecoration': 'underline'}},
         },
     },
-    'engine': DOM.HTML5LIB
+    'engine': DOM.STRING,
 }
 
 
diff --git a/tests/test_style_state.py b/tests/test_style_state.py
index 8101933..44b3120 100644
--- a/tests/test_style_state.py
+++ b/tests/test_style_state.py
@@ -32,7 +32,7 @@ style_map = {
 
 class TestStyleState(unittest.TestCase):
     def setUp(self):
-        DOM.use(DOM.HTML5LIB)
+        DOM.use(DOM.STRING)
         self.style_state = StyleState(style_map)
 
     def test_init(self):
diff --git a/tests/test_wrapper_state.py b/tests/test_wrapper_state.py
index 60deb78..8d2cebe 100644
--- a/tests/test_wrapper_state.py
+++ b/tests/test_wrapper_state.py
@@ -9,7 +9,7 @@ from example import blockquote, list_item, ordered_list
 
 class TestWrapperState(unittest.TestCase):
     def setUp(self):
-        DOM.use(DOM.HTML5LIB)
+        DOM.use(DOM.STRING)
 
         self.wrapper_state = WrapperState({
             'header-one': 'h1',
@@ -106,7 +106,7 @@ class TestWrapperState(unittest.TestCase):
 
 class TestBlockquote(unittest.TestCase):
     def setUp(self):
-        DOM.use(DOM.HTML5LIB)
+        DOM.use(DOM.STRING)
 
     def test_render_debug(self):
         self.assertEqual(DOM.render_debug(DOM.create_element(blockquote, {
@@ -120,7 +120,7 @@ class TestBlockquote(unittest.TestCase):
 
 class TestListItem(unittest.TestCase):
     def setUp(self):
-        DOM.use(DOM.HTML5LIB)
+        DOM.use(DOM.STRING)
 
     def test_render_debug(self):
         self.assertEqual(DOM.render_debug(DOM.create_element(list_item, {