git: 659fd3e0a1eb - main - www/py-beautifulsoup449: Remove obsoleted port

From: Po-Chuan Hsieh <sunpoet_at_FreeBSD.org>
Date: Sun, 04 Sep 2022 03:52:29 UTC
The branch main has been updated by sunpoet:

URL: https://cgit.FreeBSD.org/ports/commit/?id=659fd3e0a1eb0b675cbe82935b133a8f2cbe0c5e

commit 659fd3e0a1eb0b675cbe82935b133a8f2cbe0c5e
Author:     Po-Chuan Hsieh <sunpoet@FreeBSD.org>
AuthorDate: 2022-09-04 03:42:47 +0000
Commit:     Po-Chuan Hsieh <sunpoet@FreeBSD.org>
CommitDate: 2022-09-04 03:42:47 +0000

    www/py-beautifulsoup449: Remove obsoleted port
    
    Use www/py-beautifulsoup instead.
---
 MOVED                                    |   1 +
 www/Makefile                             |   1 -
 www/py-beautifulsoup449/Makefile         |  23 -
 www/py-beautifulsoup449/distinfo         |   3 -
 www/py-beautifulsoup449/files/patch-2to3 | 901 -------------------------------
 www/py-beautifulsoup449/pkg-descr        |  12 -
 6 files changed, 1 insertion(+), 940 deletions(-)

diff --git a/MOVED b/MOVED
index 756478fa2097..2172f0596f52 100644
--- a/MOVED
+++ b/MOVED
@@ -17546,3 +17546,4 @@ mail/rainloop-community|mail/rainloop|2022-09-01|Only one edition "legacy" is pr
 java/openjdk7||2022-09-03|Has expired: EOLed since 31 July 2022
 java/openjdk7-jre||2022-09-03|Has expired: EOLed since 31 July 2022
 devel/py-logfury0|devel/py-logfury|2022-09-04|Remove obsoleted port. Use devel/py-logfury instead
+www/py-beautifulsoup449|www/py-beautifulsoup|2022-09-04|Remove obsoleted port. Use www/py-beautifulsoup instead
diff --git a/www/Makefile b/www/Makefile
index 7ef8a22c1ed8..bb6e8c25069c 100644
--- a/www/Makefile
+++ b/www/Makefile
@@ -1420,7 +1420,6 @@
     SUBDIR += py-azure-storage
     SUBDIR += py-beaker
     SUBDIR += py-beautifulsoup
-    SUBDIR += py-beautifulsoup449
     SUBDIR += py-betamax
     SUBDIR += py-biscuits
     SUBDIR += py-bjoern
diff --git a/www/py-beautifulsoup449/Makefile b/www/py-beautifulsoup449/Makefile
deleted file mode 100644
index 1acd1d841cf9..000000000000
--- a/www/py-beautifulsoup449/Makefile
+++ /dev/null
@@ -1,23 +0,0 @@
-PORTNAME=	beautifulsoup4
-PORTVERSION=	4.9.3
-CATEGORIES=	www python
-MASTER_SITES=	CHEESESHOP
-PKGNAMEPREFIX=	${PYTHON_PKGNAMEPREFIX}
-PKGNAMESUFFIX=	49
-
-MAINTAINER=	sunpoet@FreeBSD.org
-COMMENT=	HTML/XML Parser for Python
-
-LICENSE=	MIT
-LICENSE_FILE=	${WRKSRC}/COPYING.txt
-
-RUN_DEPENDS=	${PYTHON_PKGNAMEPREFIX}soupsieve>=1.2:www/py-soupsieve@${PY_FLAVOR}
-
-USES=		python:3.7+
-USE_PYTHON=	autoplist concurrent distutils
-
-NO_ARCH=	yes
-
-PORTSCOUT=	limit:^4\.9\.
-
-.include <bsd.port.mk>
diff --git a/www/py-beautifulsoup449/distinfo b/www/py-beautifulsoup449/distinfo
deleted file mode 100644
index 3a02c621b6cd..000000000000
--- a/www/py-beautifulsoup449/distinfo
+++ /dev/null
@@ -1,3 +0,0 @@
-TIMESTAMP = 1650228654
-SHA256 (beautifulsoup4-4.9.3.tar.gz) = 84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25
-SIZE (beautifulsoup4-4.9.3.tar.gz) = 376031
diff --git a/www/py-beautifulsoup449/files/patch-2to3 b/www/py-beautifulsoup449/files/patch-2to3
deleted file mode 100644
index a3249a90c527..000000000000
--- a/www/py-beautifulsoup449/files/patch-2to3
+++ /dev/null
@@ -1,901 +0,0 @@
---- bs4/__init__.py.orig	2020-10-03 15:30:53 UTC
-+++ bs4/__init__.py
-@@ -51,7 +51,7 @@ from .element import (
- 
- # The very first thing we do is give a useful error if someone is
- # running this code under Python 3 without converting it.
--'You are trying to run the Python 2 version of Beautiful Soup under Python 3. This will not work.'<>'You need to convert the code, either by installing it (`python setup.py install`) or by running 2to3 (`2to3 -w bs4`).'
-+'You are trying to run the Python 2 version of Beautiful Soup under Python 3. This will not work.'!='You need to convert the code, either by installing it (`python setup.py install`) or by running 2to3 (`2to3 -w bs4`).'
- 
- # Define some custom warnings.
- class GuessedAtParserWarning(UserWarning):
-@@ -100,7 +100,7 @@ class BeautifulSoup(Tag):
-     # Since BeautifulSoup subclasses Tag, it's possible to treat it as
-     # a Tag with a .name. This name makes it clear the BeautifulSoup
-     # object isn't a real markup tag.
--    ROOT_TAG_NAME = u'[document]'
-+    ROOT_TAG_NAME = '[document]'
- 
-     # If the end-user gives no indication which tree builder they
-     # want, look for one with these features.
-@@ -217,7 +217,7 @@ class BeautifulSoup(Tag):
-         from_encoding = from_encoding or deprecated_argument(
-             "fromEncoding", "from_encoding")
- 
--        if from_encoding and isinstance(markup, unicode):
-+        if from_encoding and isinstance(markup, str):
-             warnings.warn("You provided Unicode markup but also provided a value for from_encoding. Your from_encoding will be ignored.")
-             from_encoding = None
- 
-@@ -234,7 +234,7 @@ class BeautifulSoup(Tag):
-             builder_class = builder
-             builder = None
-         elif builder is None:
--            if isinstance(features, basestring):
-+            if isinstance(features, str):
-                 features = [features]
-             if features is None or len(features) == 0:
-                 features = self.DEFAULT_BUILDER_FEATURES
-@@ -309,13 +309,13 @@ class BeautifulSoup(Tag):
-             markup = markup.read()
-         elif len(markup) <= 256 and (
-                 (isinstance(markup, bytes) and not b'<' in markup)
--                or (isinstance(markup, unicode) and not u'<' in markup)
-+                or (isinstance(markup, str) and not '<' in markup)
-         ):
-             # Print out warnings for a couple beginner problems
-             # involving passing non-markup to Beautiful Soup.
-             # Beautiful Soup will still parse the input as markup,
-             # just in case that's what the user really wants.
--            if (isinstance(markup, unicode)
-+            if (isinstance(markup, str)
-                 and not os.path.supports_unicode_filenames):
-                 possible_filename = markup.encode("utf8")
-             else:
-@@ -323,7 +323,7 @@ class BeautifulSoup(Tag):
-             is_file = False
-             try:
-                 is_file = os.path.exists(possible_filename)
--            except Exception, e:
-+            except Exception as e:
-                 # This is almost certainly a problem involving
-                 # characters not valid in filenames on this
-                 # system. Just let it go.
-@@ -353,9 +353,9 @@ class BeautifulSoup(Tag):
-                 pass
- 
-         if not success:
--            other_exceptions = [unicode(e) for e in rejections]
-+            other_exceptions = [str(e) for e in rejections]
-             raise ParserRejectedMarkup(
--                u"The markup you provided was rejected by the parser. Trying a different parser or a different encoding may help.\n\nOriginal exception(s) from parser:\n " + "\n ".join(other_exceptions)
-+                "The markup you provided was rejected by the parser. Trying a different parser or a different encoding may help.\n\nOriginal exception(s) from parser:\n " + "\n ".join(other_exceptions)
-             )
- 
-         # Clear out the markup and remove the builder's circular
-@@ -406,9 +406,9 @@ class BeautifulSoup(Tag):
-         if isinstance(markup, bytes):
-             space = b' '
-             cant_start_with = (b"http:", b"https:")
--        elif isinstance(markup, unicode):
--            space = u' '
--            cant_start_with = (u"http:", u"https:")
-+        elif isinstance(markup, str):
-+            space = ' '
-+            cant_start_with = ("http:", "https:")
-         else:
-             return
- 
-@@ -545,7 +545,7 @@ class BeautifulSoup(Tag):
-         containerClass = self.string_container(containerClass)
-         
-         if self.current_data:
--            current_data = u''.join(self.current_data)
-+            current_data = ''.join(self.current_data)
-             # If whitespace is not preserved, and this string contains
-             # nothing but ASCII spaces, replace it with a single space
-             # or newline.
-@@ -748,9 +748,9 @@ class BeautifulSoup(Tag):
-                 eventual_encoding = None
-             if eventual_encoding != None:
-                 encoding_part = ' encoding="%s"' % eventual_encoding
--            prefix = u'<?xml version="1.0"%s?>\n' % encoding_part
-+            prefix = '<?xml version="1.0"%s?>\n' % encoding_part
-         else:
--            prefix = u''
-+            prefix = ''
-         if not pretty_print:
-             indent_level = None
-         else:
-@@ -788,4 +788,4 @@ class FeatureNotFound(ValueError):
- if __name__ == '__main__':
-     import sys
-     soup = BeautifulSoup(sys.stdin)
--    print(soup.prettify())
-+    print((soup.prettify()))
---- bs4/builder/_html5lib.py.orig	2020-09-26 14:36:10 UTC
-+++ bs4/builder/_html5lib.py
-@@ -33,7 +33,7 @@ try:
-     # Pre-0.99999999
-     from html5lib.treebuilders import _base as treebuilder_base
-     new_html5lib = False
--except ImportError, e:
-+except ImportError as e:
-     # 0.99999999 and up
-     from html5lib.treebuilders import base as treebuilder_base
-     new_html5lib = True
-@@ -79,7 +79,7 @@ class HTML5TreeBuilder(HTMLTreeBuilder):
-         parser = html5lib.HTMLParser(tree=self.create_treebuilder)
-         self.underlying_builder.parser = parser
-         extra_kwargs = dict()
--        if not isinstance(markup, unicode):
-+        if not isinstance(markup, str):
-             if new_html5lib:
-                 extra_kwargs['override_encoding'] = self.user_specified_encoding
-             else:
-@@ -87,13 +87,13 @@ class HTML5TreeBuilder(HTMLTreeBuilder):
-         doc = parser.parse(markup, **extra_kwargs)
-         
-         # Set the character encoding detected by the tokenizer.
--        if isinstance(markup, unicode):
-+        if isinstance(markup, str):
-             # We need to special-case this because html5lib sets
-             # charEncoding to UTF-8 if it gets Unicode input.
-             doc.original_encoding = None
-         else:
-             original_encoding = parser.tokenizer.stream.charEncoding[0]
--            if not isinstance(original_encoding, basestring):
-+            if not isinstance(original_encoding, str):
-                 # In 0.99999999 and up, the encoding is an html5lib
-                 # Encoding object. We want to use a string for compatibility
-                 # with other tree builders.
-@@ -110,7 +110,7 @@ class HTML5TreeBuilder(HTMLTreeBuilder):
- 
-     def test_fragment_to_document(self, fragment):
-         """See `TreeBuilder`."""
--        return u'<html><head></head><body>%s</body></html>' % fragment
-+        return '<html><head></head><body>%s</body></html>' % fragment
- 
- 
- class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder):
-@@ -217,7 +217,7 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuil
-                 rv.append("|%s<%s>" % (' ' * indent, name))
-                 if element.attrs:
-                     attributes = []
--                    for name, value in element.attrs.items():
-+                    for name, value in list(element.attrs.items()):
-                         if isinstance(name, NamespacedAttribute):
-                             name = "%s %s" % (prefixes[name.namespace], name.name)
-                         if isinstance(value, list):
-@@ -272,7 +272,7 @@ class Element(treebuilder_base.Node):
- 
-     def appendChild(self, node):
-         string_child = child = None
--        if isinstance(node, basestring):
-+        if isinstance(node, str):
-             # Some other piece of code decided to pass in a string
-             # instead of creating a TextElement object to contain the
-             # string.
-@@ -289,7 +289,7 @@ class Element(treebuilder_base.Node):
-             child = node.element
-             node.parent = self
- 
--        if not isinstance(child, basestring) and child.parent is not None:
-+        if not isinstance(child, str) and child.parent is not None:
-             node.element.extract()
- 
-         if (string_child is not None and self.element.contents
-@@ -302,7 +302,7 @@ class Element(treebuilder_base.Node):
-             old_element.replace_with(new_element)
-             self.soup._most_recent_element = new_element
-         else:
--            if isinstance(node, basestring):
-+            if isinstance(node, str):
-                 # Create a brand new NavigableString from this string.
-                 child = self.soup.new_string(node)
- 
-@@ -340,7 +340,7 @@ class Element(treebuilder_base.Node):
- 
-             self.soup.builder._replace_cdata_list_attribute_values(
-                 self.name, attributes)
--            for name, value in attributes.items():
-+            for name, value in list(attributes.items()):
-                 self.element[name] = value
- 
-             # The attributes may contain variables that need substitution.
---- bs4/builder/_htmlparser.py.orig	2020-09-26 14:36:05 UTC
-+++ bs4/builder/_htmlparser.py
-@@ -8,11 +8,11 @@ __all__ = [
-     'HTMLParserTreeBuilder',
-     ]
- 
--from HTMLParser import HTMLParser
-+from html.parser import HTMLParser
- 
- try:
--    from HTMLParser import HTMLParseError
--except ImportError, e:
-+    from html.parser import HTMLParseError
-+except ImportError as e:
-     # HTMLParseError is removed in Python 3.5. Since it can never be
-     # thrown in 3.5, we can just define our own class as a placeholder.
-     class HTMLParseError(Exception):
-@@ -219,14 +219,14 @@ class BeautifulSoupHTMLParser(HTMLParser):
-                     continue
-                 try:
-                     data = bytearray([real_name]).decode(encoding)
--                except UnicodeDecodeError, e:
-+                except UnicodeDecodeError as e:
-                     pass
-         if not data:
-             try:
--                data = unichr(real_name)
--            except (ValueError, OverflowError), e:
-+                data = chr(real_name)
-+            except (ValueError, OverflowError) as e:
-                 pass
--        data = data or u"\N{REPLACEMENT CHARACTER}"
-+        data = data or "\N{REPLACEMENT CHARACTER}"
-         self.handle_data(data)
- 
-     def handle_entityref(self, name):
-@@ -353,7 +353,7 @@ class HTMLParserTreeBuilder(HTMLTreeBuilder):
-          document to Unicode and parsing it. Each strategy will be tried 
-          in turn.
-         """
--        if isinstance(markup, unicode):
-+        if isinstance(markup, str):
-             # Parse Unicode as-is.
-             yield (markup, None, None, False)
-             return
-@@ -376,7 +376,7 @@ class HTMLParserTreeBuilder(HTMLTreeBuilder):
-         try:
-             parser.feed(markup)
-             parser.close()
--        except HTMLParseError, e:
-+        except HTMLParseError as e:
-             warnings.warn(RuntimeWarning(
-                 "Python's built-in HTMLParser cannot parse the given document. This is not a bug in Beautiful Soup. The best solution is to install an external parser (lxml or html5lib), and use Beautiful Soup with that parser. See http://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser for help."))
-             raise e
---- bs4/builder/_lxml.py.orig	2020-09-07 11:13:41 UTC
-+++ bs4/builder/_lxml.py
-@@ -8,11 +8,11 @@ __all__ = [
- 
- try:
-     from collections.abc import Callable # Python 3.6
--except ImportError , e:
-+except ImportError as e:
-     from collections import Callable
- 
- from io import BytesIO
--from StringIO import StringIO
-+from io import StringIO
- from lxml import etree
- from bs4.element import (
-     Comment,
-@@ -35,7 +35,7 @@ LXML = 'lxml'
- 
- def _invert(d):
-     "Invert a dictionary."
--    return dict((v,k) for k, v in d.items())
-+    return dict((v,k) for k, v in list(d.items()))
- 
- class LXMLTreeBuilderForXML(TreeBuilder):
-     DEFAULT_PARSER_CLASS = etree.XMLParser
-@@ -81,7 +81,7 @@ class LXMLTreeBuilderForXML(TreeBuilder):
- 
-         :param mapping: A dictionary mapping namespace prefixes to URIs.
-         """
--        for key, value in mapping.items():
-+        for key, value in list(mapping.items()):
-             if key and key not in self.soup._namespaces:
-                 # Let the BeautifulSoup object know about a new namespace.
-                 # If there are multiple namespaces defined with the same
-@@ -169,12 +169,12 @@ class LXMLTreeBuilderForXML(TreeBuilder):
-         else:
-             self.processing_instruction_class = XMLProcessingInstruction
- 
--        if isinstance(markup, unicode):
-+        if isinstance(markup, str):
-             # We were given Unicode. Maybe lxml can parse Unicode on
-             # this system?
-             yield markup, None, document_declared_encoding, False
- 
--        if isinstance(markup, unicode):
-+        if isinstance(markup, str):
-             # No, apparently not. Convert the Unicode to UTF-8 and
-             # tell lxml to parse it as UTF-8.
-             yield (markup.encode("utf8"), "utf8",
-@@ -189,7 +189,7 @@ class LXMLTreeBuilderForXML(TreeBuilder):
-     def feed(self, markup):
-         if isinstance(markup, bytes):
-             markup = BytesIO(markup)
--        elif isinstance(markup, unicode):
-+        elif isinstance(markup, str):
-             markup = StringIO(markup)
- 
-         # Call feed() at least once, even if the markup is empty,
-@@ -204,7 +204,7 @@ class LXMLTreeBuilderForXML(TreeBuilder):
-                 if len(data) != 0:
-                     self.parser.feed(data)
-             self.parser.close()
--        except (UnicodeDecodeError, LookupError, etree.ParserError), e:
-+        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
-             raise ParserRejectedMarkup(e)
- 
-     def close(self):
-@@ -233,7 +233,7 @@ class LXMLTreeBuilderForXML(TreeBuilder):
-             # Also treat the namespace mapping as a set of attributes on the
-             # tag, so we can recreate it later.
-             attrs = attrs.copy()
--            for prefix, namespace in nsmap.items():
-+            for prefix, namespace in list(nsmap.items()):
-                 attribute = NamespacedAttribute(
-                     "xmlns", prefix, "http://www.w3.org/2000/xmlns/")
-                 attrs[attribute] = namespace
-@@ -242,7 +242,7 @@ class LXMLTreeBuilderForXML(TreeBuilder):
-         # from lxml with namespaces attached to their names, and
-         # turn then into NamespacedAttribute objects.
-         new_attrs = {}
--        for attr, value in attrs.items():
-+        for attr, value in list(attrs.items()):
-             namespace, attr = self._getNsTag(attr)
-             if namespace is None:
-                 new_attrs[attr] = value
-@@ -302,7 +302,7 @@ class LXMLTreeBuilderForXML(TreeBuilder):
- 
-     def test_fragment_to_document(self, fragment):
-         """See `TreeBuilder`."""
--        return u'<?xml version="1.0" encoding="utf-8"?>\n%s' % fragment
-+        return '<?xml version="1.0" encoding="utf-8"?>\n%s' % fragment
- 
- 
- class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML):
-@@ -323,10 +323,10 @@ class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilder
-             self.parser = self.parser_for(encoding)
-             self.parser.feed(markup)
-             self.parser.close()
--        except (UnicodeDecodeError, LookupError, etree.ParserError), e:
-+        except (UnicodeDecodeError, LookupError, etree.ParserError) as e:
-             raise ParserRejectedMarkup(e)
- 
- 
-     def test_fragment_to_document(self, fragment):
-         """See `TreeBuilder`."""
--        return u'<html><body>%s</body></html>' % fragment
-+        return '<html><body>%s</body></html>' % fragment
---- bs4/diagnose.py.orig	2020-05-17 17:55:43 UTC
-+++ bs4/diagnose.py
-@@ -4,8 +4,8 @@
- __license__ = "MIT"
- 
- import cProfile
--from StringIO import StringIO
--from HTMLParser import HTMLParser
-+from io import StringIO
-+from html.parser import HTMLParser
- import bs4
- from bs4 import BeautifulSoup, __version__
- from bs4.builder import builder_registry
-@@ -25,8 +25,8 @@ def diagnose(data):
-     :param data: A string containing markup that needs to be explained.
-     :return: None; diagnostics are printed to standard output.
-     """
--    print("Diagnostic running on Beautiful Soup %s" % __version__)
--    print("Python version %s" % sys.version)
-+    print(("Diagnostic running on Beautiful Soup %s" % __version__))
-+    print(("Python version %s" % sys.version))
- 
-     basic_parsers = ["html.parser", "html5lib", "lxml"]
-     for name in basic_parsers:
-@@ -35,16 +35,16 @@ def diagnose(data):
-                 break
-         else:
-             basic_parsers.remove(name)
--            print(
-+            print((
-                 "I noticed that %s is not installed. Installing it may help." %
--                name)
-+                name))
- 
-     if 'lxml' in basic_parsers:
-         basic_parsers.append("lxml-xml")
-         try:
-             from lxml import etree
--            print("Found lxml version %s" % ".".join(map(str,etree.LXML_VERSION)))
--        except ImportError, e:
-+            print(("Found lxml version %s" % ".".join(map(str,etree.LXML_VERSION))))
-+        except ImportError as e:
-             print(
-                 "lxml is not installed or couldn't be imported.")
- 
-@@ -52,21 +52,21 @@ def diagnose(data):
-     if 'html5lib' in basic_parsers:
-         try:
-             import html5lib
--            print("Found html5lib version %s" % html5lib.__version__)
--        except ImportError, e:
-+            print(("Found html5lib version %s" % html5lib.__version__))
-+        except ImportError as e:
-             print(
-                 "html5lib is not installed or couldn't be imported.")
- 
-     if hasattr(data, 'read'):
-         data = data.read()
-     elif data.startswith("http:") or data.startswith("https:"):
--        print('"%s" looks like a URL. Beautiful Soup is not an HTTP client.' % data)
-+        print(('"%s" looks like a URL. Beautiful Soup is not an HTTP client.' % data))
-         print("You need to use some other library to get the document behind the URL, and feed that document to Beautiful Soup.")
-         return
-     else:
-         try:
-             if os.path.exists(data):
--                print('"%s" looks like a filename. Reading data from the file.' % data)
-+                print(('"%s" looks like a filename. Reading data from the file.' % data))
-                 with open(data) as fp:
-                     data = fp.read()
-         except ValueError:
-@@ -76,19 +76,19 @@ def diagnose(data):
-         print("")
- 
-     for parser in basic_parsers:
--        print("Trying to parse your markup with %s" % parser)
-+        print(("Trying to parse your markup with %s" % parser))
-         success = False
-         try:
-             soup = BeautifulSoup(data, features=parser)
-             success = True
--        except Exception, e:
--            print("%s could not parse the markup." % parser)
-+        except Exception as e:
-+            print(("%s could not parse the markup." % parser))
-             traceback.print_exc()
-         if success:
--            print("Here's what %s did with the markup:" % parser)
--            print(soup.prettify())
-+            print(("Here's what %s did with the markup:" % parser))
-+            print((soup.prettify()))
- 
--        print("-" * 80)
-+        print(("-" * 80))
- 
- def lxml_trace(data, html=True, **kwargs):
-     """Print out the lxml events that occur during parsing.
-@@ -104,7 +104,7 @@ def lxml_trace(data, html=True, **kwargs):
-     """
-     from lxml import etree
-     for event, element in etree.iterparse(StringIO(data), html=html, **kwargs):
--        print("%s, %4s, %s" % (event, element.tag, element.text))
-+        print(("%s, %4s, %s" % (event, element.tag, element.text)))
- 
- class AnnouncingParser(HTMLParser):
-     """Subclass of HTMLParser that announces parse events, without doing
-@@ -193,9 +193,9 @@ def rdoc(num_elements=1000):
- 
- def benchmark_parsers(num_elements=100000):
-     """Very basic head-to-head performance benchmark."""
--    print("Comparative parser benchmark on Beautiful Soup %s" % __version__)
-+    print(("Comparative parser benchmark on Beautiful Soup %s" % __version__))
-     data = rdoc(num_elements)
--    print("Generated a large invalid HTML document (%d bytes)." % len(data))
-+    print(("Generated a large invalid HTML document (%d bytes)." % len(data)))
-     
-     for parser in ["lxml", ["lxml", "html"], "html5lib", "html.parser"]:
-         success = False
-@@ -204,24 +204,24 @@ def benchmark_parsers(num_elements=100000):
-             soup = BeautifulSoup(data, parser)
-             b = time.time()
-             success = True
--        except Exception, e:
--            print("%s could not parse the markup." % parser)
-+        except Exception as e:
-+            print(("%s could not parse the markup." % parser))
-             traceback.print_exc()
-         if success:
--            print("BS4+%s parsed the markup in %.2fs." % (parser, b-a))
-+            print(("BS4+%s parsed the markup in %.2fs." % (parser, b-a)))
- 
-     from lxml import etree
-     a = time.time()
-     etree.HTML(data)
-     b = time.time()
--    print("Raw lxml parsed the markup in %.2fs." % (b-a))
-+    print(("Raw lxml parsed the markup in %.2fs." % (b-a)))
- 
-     import html5lib
-     parser = html5lib.HTMLParser()
-     a = time.time()
-     parser.parse(data)
-     b = time.time()
--    print("Raw html5lib parsed the markup in %.2fs." % (b-a))
-+    print(("Raw html5lib parsed the markup in %.2fs." % (b-a)))
- 
- def profile(num_elements=100000, parser="lxml"):
-     """Use Python's profiler on a randomly generated document."""
---- bs4/element.py.orig	2020-10-02 22:19:12 UTC
-+++ bs4/element.py
-@@ -3,14 +3,14 @@ __license__ = "MIT"
- 
- try:
-     from collections.abc import Callable # Python 3.6
--except ImportError , e:
-+except ImportError as e:
-     from collections import Callable
- import re
- import sys
- import warnings
- try:
-     import soupsieve
--except ImportError, e:
-+except ImportError as e:
-     soupsieve = None
-     warnings.warn(
-         'The soupsieve package is not installed. CSS selectors cannot be used.'
-@@ -57,22 +57,22 @@ def _alias(attr):
- # Source:
- # https://docs.python.org/3/library/codecs.html#python-specific-encodings
- PYTHON_SPECIFIC_ENCODINGS = set([
--    u"idna",
--    u"mbcs",
--    u"oem",
--    u"palmos",
--    u"punycode",
--    u"raw_unicode_escape",
--    u"undefined",
--    u"unicode_escape",
--    u"raw-unicode-escape",
--    u"unicode-escape",
--    u"string-escape",
--    u"string_escape",
-+    "idna",
-+    "mbcs",
-+    "oem",
-+    "palmos",
-+    "punycode",
-+    "raw_unicode_escape",
-+    "undefined",
-+    "unicode_escape",
-+    "raw-unicode-escape",
-+    "unicode-escape",
-+    "string-escape",
-+    "string_escape",
- ])
-     
- 
--class NamespacedAttribute(unicode):
-+class NamespacedAttribute(str):
-     """A namespaced string (e.g. 'xml:lang') that remembers the namespace
-     ('xml') and the name ('lang') that were used to create it.
-     """
-@@ -84,18 +84,18 @@ class NamespacedAttribute(unicode):
-             name = None
- 
-         if name is None:
--            obj = unicode.__new__(cls, prefix)
-+            obj = str.__new__(cls, prefix)
-         elif prefix is None:
-             # Not really namespaced.
--            obj = unicode.__new__(cls, name)
-+            obj = str.__new__(cls, name)
-         else:
--            obj = unicode.__new__(cls, prefix + ":" + name)
-+            obj = str.__new__(cls, prefix + ":" + name)
-         obj.prefix = prefix
-         obj.name = name
-         obj.namespace = namespace
-         return obj
- 
--class AttributeValueWithCharsetSubstitution(unicode):
-+class AttributeValueWithCharsetSubstitution(str):
-     """A stand-in object for a character encoding specified in HTML."""
- 
- class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution):
-@@ -106,7 +106,7 @@ class CharsetMetaAttributeValue(AttributeValueWithChar
-     """
- 
-     def __new__(cls, original_value):
--        obj = unicode.__new__(cls, original_value)
-+        obj = str.__new__(cls, original_value)
-         obj.original_value = original_value
-         return obj
- 
-@@ -134,9 +134,9 @@ class ContentMetaAttributeValue(AttributeValueWithChar
-         match = cls.CHARSET_RE.search(original_value)
-         if match is None:
-             # No substitution necessary.
--            return unicode.__new__(unicode, original_value)
-+            return str.__new__(str, original_value)
- 
--        obj = unicode.__new__(cls, original_value)
-+        obj = str.__new__(cls, original_value)
-         obj.original_value = original_value
-         return obj
- 
-@@ -376,7 +376,7 @@ class PageElement(object):
-             raise ValueError("Cannot insert None into a tag.")
-         if new_child is self:
-             raise ValueError("Cannot insert a tag into itself.")
--        if (isinstance(new_child, basestring)
-+        if (isinstance(new_child, str)
-             and not isinstance(new_child, NavigableString)):
-             new_child = NavigableString(new_child)
- 
-@@ -753,7 +753,7 @@ class PageElement(object):
-                 result = (element for element in generator
-                           if isinstance(element, Tag))
-                 return ResultSet(strainer, result)
--            elif isinstance(name, basestring):
-+            elif isinstance(name, str):
-                 # Optimization to find all tags with a given name.
-                 if name.count(':') == 1:
-                     # This is a name with a prefix. If this is a namespace-aware document,
-@@ -872,7 +872,7 @@ class PageElement(object):
-         return self.parents
- 
- 
--class NavigableString(unicode, PageElement):
-+class NavigableString(str, PageElement):
-     """A Python Unicode string that is part of a parse tree.
- 
-     When Beautiful Soup parses the markup <b>penguin</b>, it will
-@@ -895,10 +895,10 @@ class NavigableString(unicode, PageElement):
-         passed in to the superclass's __new__ or the superclass won't know
-         how to handle non-ASCII characters.
-         """
--        if isinstance(value, unicode):
--            u = unicode.__new__(cls, value)
-+        if isinstance(value, str):
-+            u = str.__new__(cls, value)
-         else:
--            u = unicode.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)
-+            u = str.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)
-         u.setup()
-         return u
- 
-@@ -909,7 +909,7 @@ class NavigableString(unicode, PageElement):
-         return type(self)(self)
- 
-     def __getnewargs__(self):
--        return (unicode(self),)
-+        return (str(self),)
- 
-     def __getattr__(self, attr):
-         """text.string gives you text. This is for backwards
-@@ -975,30 +975,30 @@ class PreformattedString(NavigableString):
- 
- class CData(PreformattedString):
-     """A CDATA block."""
--    PREFIX = u'<![CDATA['
--    SUFFIX = u']]>'
-+    PREFIX = '<![CDATA['
-+    SUFFIX = ']]>'
- 
- class ProcessingInstruction(PreformattedString):
-     """A SGML processing instruction."""
- 
--    PREFIX = u'<?'
--    SUFFIX = u'>'
-+    PREFIX = '<?'
-+    SUFFIX = '>'
- 
- class XMLProcessingInstruction(ProcessingInstruction):
-     """An XML processing instruction."""
--    PREFIX = u'<?'
--    SUFFIX = u'?>'
-+    PREFIX = '<?'
-+    SUFFIX = '?>'
- 
- class Comment(PreformattedString):
-     """An HTML or XML comment."""
--    PREFIX = u'<!--'
--    SUFFIX = u'-->'
-+    PREFIX = '<!--'
-+    SUFFIX = '-->'
- 
- 
- class Declaration(PreformattedString):
-     """An XML declaration."""
--    PREFIX = u'<?'
--    SUFFIX = u'?>'
-+    PREFIX = '<?'
-+    SUFFIX = '?>'
- 
- 
- class Doctype(PreformattedString):
-@@ -1026,8 +1026,8 @@ class Doctype(PreformattedString):
- 
-         return Doctype(value)
- 
--    PREFIX = u'<!DOCTYPE '
--    SUFFIX = u'>\n'
-+    PREFIX = '<!DOCTYPE '
-+    SUFFIX = '>\n'
- 
- 
- class Stylesheet(NavigableString):
-@@ -1263,7 +1263,7 @@ class Tag(PageElement):
-         for string in self._all_strings(True):
-             yield string
- 
--    def get_text(self, separator=u"", strip=False,
-+    def get_text(self, separator="", strip=False,
-                  types=(NavigableString, CData)):
-         """Get all child strings, concatenated using the given separator.
- 
-@@ -1416,7 +1416,7 @@ class Tag(PageElement):
-     def __contains__(self, x):
-         return x in self.contents
- 
--    def __nonzero__(self):
-+    def __bool__(self):
-         "A tag is non-None even if it has no contents."
-         return True
- 
-@@ -1565,8 +1565,8 @@ class Tag(PageElement):
-             else:
-                 if isinstance(val, list) or isinstance(val, tuple):
-                     val = ' '.join(val)
--                elif not isinstance(val, basestring):
--                    val = unicode(val)
-+                elif not isinstance(val, str):
-+                    val = str(val)
-                 elif (
-                         isinstance(val, AttributeValueWithCharsetSubstitution)
-                         and eventual_encoding is not None
-@@ -1575,7 +1575,7 @@ class Tag(PageElement):
- 
-                 text = formatter.attribute_value(val)
-                 decoded = (
--                    unicode(key) + '='
-+                    str(key) + '='
-                     + formatter.quoted_attribute_value(text))
-             attrs.append(decoded)
-         close = ''
-@@ -1934,7 +1934,7 @@ class SoupStrainer(object):
-             else:
-                 attrs = kwargs
-         normalized_attrs = {}
--        for key, value in attrs.items():
-+        for key, value in list(attrs.items()):
-             normalized_attrs[key] = self._normalize_search_value(value)
- 
-         self.attrs = normalized_attrs
-@@ -1943,7 +1943,7 @@ class SoupStrainer(object):
-     def _normalize_search_value(self, value):
-         # Leave it alone if it's a Unicode string, a callable, a
-         # regular expression, a boolean, or None.
--        if (isinstance(value, unicode) or isinstance(value, Callable) or hasattr(value, 'match')
-+        if (isinstance(value, str) or isinstance(value, Callable) or hasattr(value, 'match')
-             or isinstance(value, bool) or value is None):
-             return value
- 
-@@ -1956,7 +1956,7 @@ class SoupStrainer(object):
-             new_value = []
-             for v in value:
-                 if (hasattr(v, '__iter__') and not isinstance(v, bytes)
--                    and not isinstance(v, unicode)):
-+                    and not isinstance(v, str)):
-                     # This is almost certainly the user's mistake. In the
-                     # interests of avoiding infinite loops, we'll let
-                     # it through as-is rather than doing a recursive call.
-@@ -1968,7 +1968,7 @@ class SoupStrainer(object):
-         # Otherwise, convert it into a Unicode string.
-         # The unicode(str()) thing is so this will do the same thing on Python 2
-         # and Python 3.
--        return unicode(str(value))
-+        return str(str(value))
- 
-     def __str__(self):
-         """A human-readable representation of this SoupStrainer."""
-@@ -1996,7 +1996,7 @@ class SoupStrainer(object):
-             markup = markup_name
-             markup_attrs = markup
- 
--        if isinstance(self.name, basestring):
-+        if isinstance(self.name, str):
-             # Optimization for a very common case where the user is
-             # searching for a tag with one specific name, and we're
-             # looking at a tag with a different name.
-@@ -2052,7 +2052,7 @@ class SoupStrainer(object):
-         found = None
-         # If given a list of items, scan it for a text element that
-         # matches.
--        if hasattr(markup, '__iter__') and not isinstance(markup, (Tag, basestring)):
-+        if hasattr(markup, '__iter__') and not isinstance(markup, (Tag, str)):
-             for element in markup:
-                 if isinstance(element, NavigableString) \
-                        and self.search(element):
-@@ -2065,7 +2065,7 @@ class SoupStrainer(object):
-                 found = self.search_tag(markup)
-         # If it's text, make sure the text matches.
-         elif isinstance(markup, NavigableString) or \
--                 isinstance(markup, basestring):
-+                 isinstance(markup, str):
-             if not self.name and not self.attrs and self._matches(markup, self.text):
-                 found = markup
-         else:
-@@ -2110,7 +2110,7 @@ class SoupStrainer(object):
-             return not match_against
- 
-         if (hasattr(match_against, '__iter__')
--            and not isinstance(match_against, basestring)):
-+            and not isinstance(match_against, str)):
-             # We're asked to match against an iterable of items.
-             # The markup must be match at least one item in the
-             # iterable. We'll try each one in turn.
-@@ -2137,7 +2137,7 @@ class SoupStrainer(object):
-         # the tag's name and once against its prefixed name.
-         match = False
-         
--        if not match and isinstance(match_against, unicode):
-+        if not match and isinstance(match_against, str):
-             # Exact string match
-             match = markup == match_against
- 
---- bs4/tests/test_html5lib.py.orig	2020-04-05 19:54:12 UTC
-+++ bs4/tests/test_html5lib.py
-@@ -5,7 +5,7 @@ import warnings
- try:
-     from bs4.builder import HTML5TreeBuilder
-     HTML5LIB_PRESENT = True
--except ImportError, e:
-+except ImportError as e:
-     HTML5LIB_PRESENT = False
- from bs4.element import SoupStrainer
- from bs4.testing import (
-@@ -74,14 +74,14 @@ class HTML5LibBuilderSmokeTest(SoupTest, HTML5TreeBuil
-     def test_reparented_markup(self):
-         markup = '<p><em>foo</p>\n<p>bar<a></a></em></p>'
-         soup = self.soup(markup)
--        self.assertEqual(u"<body><p><em>foo</em></p><em>\n</em><p><em>bar<a></a></em></p></body>", soup.body.decode())
-+        self.assertEqual("<body><p><em>foo</em></p><em>\n</em><p><em>bar<a></a></em></p></body>", soup.body.decode())
-         self.assertEqual(2, len(soup.find_all('p')))
- 
- 
-     def test_reparented_markup_ends_with_whitespace(self):
-         markup = '<p><em>foo</p>\n<p>bar<a></a></em></p>\n'
-         soup = self.soup(markup)
--        self.assertEqual(u"<body><p><em>foo</em></p><em>\n</em><p><em>bar<a></a></em></p>\n</body>", soup.body.decode())
-+        self.assertEqual("<body><p><em>foo</em></p><em>\n</em><p><em>bar<a></a></em></p>\n</body>", soup.body.decode())
-         self.assertEqual(2, len(soup.find_all('p')))
- 
-     def test_reparented_markup_containing_identical_whitespace_nodes(self):
-@@ -127,7 +127,7 @@ class HTML5LibBuilderSmokeTest(SoupTest, HTML5TreeBuil
-     def test_foster_parenting(self):
-         markup = b"""<table><td></tbody>A"""
-         soup = self.soup(markup)
--        self.assertEqual(u"<body>A<table><tbody><tr><td></td></tr></tbody></table></body>", soup.body.decode())
-+        self.assertEqual("<body>A<table><tbody><tr><td></td></tr></tbody></table></body>", soup.body.decode())
- 
-     def test_extraction(self):
-         """
---- bs4/tests/test_lxml.py.orig	2020-04-05 19:54:12 UTC
-+++ bs4/tests/test_lxml.py
-@@ -7,7 +7,7 @@ try:
-     import lxml.etree
-     LXML_PRESENT = True
-     LXML_VERSION = lxml.etree.LXML_VERSION
--except ImportError, e:
-+except ImportError as e:
-     LXML_PRESENT = False
-     LXML_VERSION = (0,)
- 
-@@ -68,7 +68,7 @@ class LXMLTreeBuilderSmokeTest(SoupTest, HTMLTreeBuild
-         # if one is installed.
-         with warnings.catch_warnings(record=True) as w:
-             soup = BeautifulStoneSoup("<b />")
--        self.assertEqual(u"<b/>", unicode(soup.b))
-+        self.assertEqual("<b/>", str(soup.b))
-         self.assertTrue("BeautifulStoneSoup class is deprecated" in str(w[0].message))
- 
-     def test_tracking_line_numbers(self):
---- setup.py.orig	2020-10-03 15:31:00 UTC
-+++ setup.py
-@@ -30,7 +30,6 @@ setup(
-         'lxml' : [ 'lxml'],
-         'html5lib' : ['html5lib'],
-     },
--    use_2to3 = True,
-     classifiers=["Development Status :: 5 - Production/Stable",
-                  "Intended Audience :: Developers",
*** 19 LINES SKIPPED ***